Spec-Zone.ru › pandas 0.18

Преобразование формы и сводные таблицы

Преобразование формы путем поворота объектов DataFrame

Данные часто хранятся в файлах CSV или базах данных в так называемом «стековом» или «записывающем» формате:

In [1]: df
Out[1]: 
         date variable     value
0  2000-01-03        A  0.469112
1  2000-01-04        A -0.282863
2  2000-01-05        A -1.509059
3  2000-01-03        B -1.135632
4  2000-01-04        B  1.212112
5  2000-01-05        B -0.173215
6  2000-01-03        C  0.119209
7  2000-01-04        C -1.044236
8  2000-01-05        C -0.861849
9  2000-01-03        D -2.104569
10 2000-01-04        D -0.494929
11 2000-01-05        D  1.071804

Для любопытных вот как был создан вышеупомянутый DataFrame:

import pandas.util.testing as tm; tm.N = 3
def unpivot(frame):
    N, K = frame.shape
    data = {'value' : frame.values.ravel('F'),
            'variable' : np.asarray(frame.columns).repeat(N),
            'date' : np.tile(np.asarray(frame.index), K)}
    return pd.DataFrame(data, columns=['date', 'variable', 'value'])
df = unpivot(tm.makeTimeDataFrame())

Чтобы выбрать всё для переменной A мы могли бы сделать следующее:

In [2]: df[df['variable'] == 'A']
Out[2]: 
        date variable     value
0 2000-01-03        A  0.469112
1 2000-01-04        A -0.282863
2 2000-01-05        A -1.509059

Но предположим, что мы хотим выполнить операции с временными рядами по переменным. Лучшим представлением будет тот случай, когда columns являются уникальными переменными, а index дат определяет отдельные наблюдения. Для преобразования данных в этот формат используйте функцию pivot.

In [3]: df.pivot(index='date', columns='variable', values='value')
Out[3]: 
variable           A         B         C         D
date                                              
2000-01-03  0.469112 -1.135632  0.119209 -2.104569
2000-01-04 -0.282863  1.212112 -1.044236 -0.494929
2000-01-05 -1.509059 -0.173215 -0.861849  1.071804

Если аргумент values опущен, и входной DataFrame имеет более одной колонки значений, которые не используются в качестве колонок или индексов для pivot, тогда полученный «повернутый» DataFrame будет иметь многоуровневые колонки, верхний уровень которых указывает соответствующую колонку значений:

In [4]: df['value2'] = df['value'] * 2

In [5]: pivoted = df.pivot('date', 'variable')

In [6]: pivoted
Out[6]: 
               value                                  value2            \
variable           A         B         C         D         A         B   
date                                                                     
2000-01-03  0.469112 -1.135632  0.119209 -2.104569  0.938225 -2.271265   
2000-01-04 -0.282863  1.212112 -1.044236 -0.494929 -0.565727  2.424224   
2000-01-05 -1.509059 -0.173215 -0.861849  1.071804 -3.018117 -0.346429   

                                
variable           C         D  
date                            
2000-01-03  0.238417 -4.209138  
2000-01-04 -2.088472 -0.989859  
2000-01-05 -1.723698  2.143608  

Конечно, вы можете затем выбрать подмножества из повернутого DataFrame:

In [7]: pivoted['value2']
Out[7]: 
variable           A         B         C         D
date                                              
2000-01-03  0.938225 -2.271265  0.238417 -4.209138
2000-01-04 -0.565727  2.424224 -2.088472 -0.989859
2000-01-05 -3.018117 -0.346429 -1.723698  2.143608

Обратите внимание, что это возвращает представление данных в базовом DataFrame в случае, если данные имеют однородный тип.

Преобразование формы путем стекирования и расстегивания

Тесно связанные с функцией pivot находятся функции stack и unstack , которые в настоящее время доступны для Series и DataFrame. Эти функции предназначены для совместной работы с объектами MultiIndex (см. раздел о многоуровневом индексировании). Вот, по сути, что делают эти функции:

  • stack: «повернуть» уровень меток колонок (возможно, многоуровневых), возвращая DataFrame с индексом с новым внутренним уровнем меток строк.
  • unstack: обратная операция к stack: «повернуть» уровень (возможно, многоуровневого) индекса строк в ось колонок, создавая преобразованный DataFrame с новым внутренним уровнем меток колонок.

Самый ясный способ объяснения — это пример. Давайте возьмём предыдущий пример набора данных из раздела многоуровневого индексирования:

In [8]: tuples = list(zip(*[['bar', 'bar', 'baz', 'baz',
   ...:                      'foo', 'foo', 'qux', 'qux'],
   ...:                     ['one', 'two', 'one', 'two',
   ...:                      'one', 'two', 'one', 'two']]))
   ...: 

In [9]: index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])

In [10]: df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B'])

In [11]: df2 = df[:4]

In [12]: df2
Out[12]: 
                     A         B
first second                    
bar   one     0.721555 -0.706771
      two    -1.039575  0.271860
baz   one    -0.424972  0.567020
      two     0.276232 -1.087401

Функция stack «сжимает» уровень в колонках DataFrame, чтобы получить:

  • Series в случае простого индекса колонок
  • DataFrame в случае MultiIndex в колонках

Если у колонок есть MultiIndex, вы можете выбрать, какой уровень стекировать. Стекнутый уровень становится новым самым низким уровнем в MultiIndex по колонках:

In [13]: stacked = df2.stack()

In [14]: stacked
Out[14]: 
first  second   
bar    one     A    0.721555
               B   -0.706771
       two     A   -1.039575
               B    0.271860
baz    one     A   -0.424972
               B    0.567020
       two     A    0.276232
               B   -1.087401
dtype: float64

С «стековым» DataFrame или Series (имеющим MultiIndex в качестве index), обратной операцией к stack является unstack, которая по умолчанию расстегивает **последний уровень**:

In [15]: stacked.unstack()
Out[15]: 
                     A         B
first second                    
bar   one     0.721555 -0.706771
      two    -1.039575  0.271860
baz   one    -0.424972  0.567020
      two     0.276232 -1.087401

In [16]: stacked.unstack(1)
Out[16]: 
second        one       two
first                      
bar   A  0.721555 -1.039575
      B -0.706771  0.271860
baz   A -0.424972  0.276232
      B  0.567020 -1.087401

In [17]: stacked.unstack(0)
Out[17]: 
first          bar       baz
second                      
one    A  0.721555 -0.424972
       B -0.706771  0.567020
two    A -1.039575  0.276232
       B  0.271860 -1.087401

Если у индексов есть имена, вы можете использовать имена уровней вместо указания номеров уровней:

In [18]: stacked.unstack('second')
Out[18]: 
second        one       two
first                      
bar   A  0.721555 -1.039575
      B -0.706771  0.271860
baz   A -0.424972  0.276232
      B  0.567020 -1.087401

Обратите внимание, что методы stack и unstack неявно сортируют уровни индекса. Следовательно, вызов stack и затем unstack, или наоборот, приведет к **отсортированной** копии исходного DataFrame или Series:

In [19]: index = pd.MultiIndex.from_product([[2,1], ['a', 'b']])

In [20]: df = pd.DataFrame(np.random.randn(4), index=index, columns=['A'])

In [21]: df
Out[21]: 
            A
2 a -0.370647
  b -1.157892
1 a -1.344312
  b  0.844885

In [22]: all(df.unstack().stack() == df.sort_index())
Out[22]: True

в то время как приведенный выше код сгенерирует TypeError, если вызов sort_index будет удалён.

Несколько уровней

Вы также можете стекировать или расстегивать более одного уровня одновременно, передав список уровней, в этом случае конечный результат будет таким, как если бы каждый уровень в списке обрабатывался индивидуально.

In [23]: columns = pd.MultiIndex.from_tuples([
   ....:         ('A', 'cat', 'long'), ('B', 'cat', 'long'),
   ....:         ('A', 'dog', 'short'), ('B', 'dog', 'short')
   ....:     ],
   ....:     names=['exp', 'animal', 'hair_length']
   ....: )
   ....: 

In [24]: df = pd.DataFrame(np.random.randn(4, 4), columns=columns)

In [25]: df
Out[25]: 
exp                 A         B         A         B
animal            cat       cat       dog       dog
hair_length      long      long     short     short
0            1.075770 -0.109050  1.643563 -1.469388
1            0.357021 -0.674600 -1.776904 -0.968914
2           -1.294524  0.413738  0.276662 -0.472035
3           -0.013960 -0.362543 -0.006154 -0.923061

In [26]: df.stack(level=['animal', 'hair_length'])
Out[26]: 
exp                          A         B
  animal hair_length                    
0 cat    long         1.075770 -0.109050
  dog    short        1.643563 -1.469388
1 cat    long         0.357021 -0.674600
  dog    short       -1.776904 -0.968914
2 cat    long        -1.294524  0.413738
  dog    short        0.276662 -0.472035
3 cat    long        -0.013960 -0.362543
  dog    short       -0.006154 -0.923061

Список уровней может содержать либо имена уровней, либо номера уровней (но не их смесь).

# df.stack(level=['animal', 'hair_length'])
# from above is equivalent to:
In [27]: df.stack(level=[1, 2])
Out[27]: 
exp                          A         B
  animal hair_length                    
0 cat    long         1.075770 -0.109050
  dog    short        1.643563 -1.469388
1 cat    long         0.357021 -0.674600
  dog    short       -1.776904 -0.968914
2 cat    long        -1.294524  0.413738
  dog    short        0.276662 -0.472035
3 cat    long        -0.013960 -0.362543
  dog    short       -0.006154 -0.923061

Пропущенные данные

Эти функции умело обрабатывают пропущенные данные и не ожидают, что каждая подгруппа в многоуровневом индексе будет иметь тот же набор меток. Они также могут обрабатывать несортированные индексы (но, конечно, вы можете сделать их отсортированными, вызвав sort_index). Вот более сложный пример:

In [28]: columns = pd.MultiIndex.from_tuples([('A', 'cat'), ('B', 'dog'),
   ....:                                      ('B', 'cat'), ('A', 'dog')],
   ....:                                     names=['exp', 'animal'])
   ....: 

In [29]: index = pd.MultiIndex.from_product([('bar', 'baz', 'foo', 'qux'),
   ....:                                     ('one', 'two')],
   ....:                                    names=['first', 'second'])
   ....: 

In [30]: df = pd.DataFrame(np.random.randn(8, 4), index=index, columns=columns)

In [31]: df2 = df.ix[[0, 1, 2, 4, 5, 7]]

In [32]: df2
Out[32]: 
exp                  A         B                   A
animal             cat       dog       cat       dog
first second                                        
bar   one     0.895717  0.805244 -1.206412  2.565646
      two     1.431256  1.340309 -1.170299 -0.226169
baz   one     0.410835  0.813850  0.132003 -0.827317
foo   one    -1.413681  1.607920  1.024180  0.569605
      two     0.875906 -2.211372  0.974466 -2.006747
qux   two    -1.226825  0.769804 -1.281247 -0.727707

Как упоминалось выше, stack может быть вызван с аргументом level для выбора уровня в колонках для стекирования:

In [33]: df2.stack('exp')
Out[33]: 
animal                 cat       dog
first second exp                    
bar   one    A    0.895717  2.565646
             B   -1.206412  0.805244
      two    A    1.431256 -0.226169
             B   -1.170299  1.340309
baz   one    A    0.410835 -0.827317
             B    0.132003  0.813850
foo   one    A   -1.413681  0.569605
             B    1.024180  1.607920
      two    A    0.875906 -2.006747
             B    0.974466 -2.211372
qux   two    A   -1.226825 -0.727707
             B   -1.281247  0.769804

In [34]: df2.stack('animal')
Out[34]: 
exp                         A         B
first second animal                    
bar   one    cat     0.895717 -1.206412
             dog     2.565646  0.805244
      two    cat     1.431256 -1.170299
             dog    -0.226169  1.340309
baz   one    cat     0.410835  0.132003
             dog    -0.827317  0.813850
foo   one    cat    -1.413681  1.024180
             dog     0.569605  1.607920
      two    cat     0.875906  0.974466
             dog    -2.006747 -2.211372
qux   two    cat    -1.226825 -1.281247
             dog    -0.727707  0.769804

Расстегивание может привести к пропущенным значениям, если подгруппы не имеют одинакового набора меток. По умолчанию пропущенные значения будут заменены значением по умолчанию для соответствующего типа данных, NaN для float, NaT для datetimelike и т. д. Для целочисленных типов по умолчанию данные будут преобразовываться в float, а пропущенные значения будут установлены в NaN.

In [35]: df3 = df.iloc[[0, 1, 4, 7], [1, 2]]

In [36]: df3
Out[36]: 
exp                  B          
animal             dog       cat
first second                    
bar   one     0.805244 -1.206412
      two     1.340309 -1.170299
foo   one     1.607920  1.024180
qux   two     0.769804 -1.281247

In [37]: df3.unstack()
Out[37]: 
exp            B                              
animal       dog                 cat          
second       one       two       one       two
first                                         
bar     0.805244  1.340309 -1.206412 -1.170299
foo     1.607920       NaN  1.024180       NaN
qux          NaN  0.769804       NaN -1.281247

В качестве альтернативы, расстегивание принимает необязательный аргумент fill_value для указания значения пропущенных данных.

In [38]: df3.unstack(fill_value=-1e9)
Out[38]: 
exp                B                                          
animal           dog                         cat              
second           one           two           one           two
first                                                         
bar     8.052440e-01  1.340309e+00 -1.206412e+00 -1.170299e+00
foo     1.607920e+00 -1.000000e+09  1.024180e+00 -1.000000e+09
qux    -1.000000e+09  7.698036e-01 -1.000000e+09 -1.281247e+00

С многоуровневым индексом

Расстегивание, когда колонки являются MultiIndex , также аккуратно выполняет необходимые действия:

In [39]: df[:3].unstack(0)
Out[39]: 
exp            A                   B                                      A  \
animal       cat                 dog                cat                 dog   
first        bar       baz       bar      baz       bar       baz       bar   
second                                                                        
one     0.895717  0.410835  0.805244  0.81385 -1.206412  0.132003  2.565646   
two     1.431256       NaN  1.340309      NaN -1.170299       NaN -0.226169   

exp               
animal            
first        baz  
second            
one    -0.827317  
two          NaN  

In [40]: df2.unstack(1)
Out[40]: 
exp            A                   B                                       A  \
animal       cat                 dog                 cat                 dog   
second       one       two       one       two       one       two       one   
first                                                                          
bar     0.895717  1.431256  0.805244  1.340309 -1.206412 -1.170299  2.565646   
baz     0.410835       NaN  0.813850       NaN  0.132003       NaN -0.827317   
foo    -1.413681  0.875906  1.607920 -2.211372  1.024180  0.974466  0.569605   
qux          NaN -1.226825       NaN  0.769804       NaN -1.281247       NaN   

exp               
animal            
second       two  
first             
bar    -0.226169  
baz          NaN  
foo    -2.006747  
qux    -0.727707  

Преобразование формы с помощью Melt

Функция melt() полезна для преобразования DataFrame в формат, где одна или несколько колонок являются идентификационными переменными, а все остальные колонки, рассматриваемые как измерительные переменные, «раскладываются» по оси строк, оставляя только две колонки, не являющиеся идентификационными, «переменная» и «значение». Имена этих колонок можно настроить, передав параметры var_name и value_name.

Например,

In [41]: cheese = pd.DataFrame({'first' : ['John', 'Mary'],
   ....:                        'last' : ['Doe', 'Bo'],
   ....:                        'height' : [5.5, 6.0],
   ....:                        'weight' : [130, 150]})
   ....: 

In [42]: cheese
Out[42]: 
  first  height last  weight
0  John     5.5  Doe     130
1  Mary     6.0   Bo     150

In [43]: pd.melt(cheese, id_vars=['first', 'last'])
Out[43]: 
  first last variable  value
0  John  Doe   height    5.5
1  Mary   Bo   height    6.0
2  John  Doe   weight  130.0
3  Mary   Bo   weight  150.0

In [44]: pd.melt(cheese, id_vars=['first', 'last'], var_name='quantity')
Out[44]: 
  first last quantity  value
0  John  Doe   height    5.5
1  Mary   Bo   height    6.0
2  John  Doe   weight  130.0
3  Mary   Bo   weight  150.0

Другой способ преобразования — использование удобной функции панельных данных wide_to_long.

In [45]: dft = pd.DataFrame({"A1970" : {0 : "a", 1 : "b", 2 : "c"},
   ....:                     "A1980" : {0 : "d", 1 : "e", 2 : "f"},
   ....:                     "B1970" : {0 : 2.5, 1 : 1.2, 2 : .7},
   ....:                     "B1980" : {0 : 3.2, 1 : 1.3, 2 : .1},
   ....:                     "X"     : dict(zip(range(3), np.random.randn(3)))
   ....:                    })
   ....: 

In [46]: dft["id"] = dft.index

In [47]: dft
Out[47]: 
  A1970 A1980  B1970  B1980         X  id
0     a     d    2.5    3.2 -0.121306   0
1     b     e    1.2    1.3 -0.097883   1
2     c     f    0.7    0.1  0.695775   2

In [48]: pd.wide_to_long(dft, ["A", "B"], i="id", j="year")
Out[48]: 
                X  A    B
id year                  
0  1970 -0.121306  a  2.5
1  1970 -0.097883  b  1.2
2  1970  0.695775  c  0.7
0  1980 -0.121306  d  3.2
1  1980 -0.097883  e  1.3
2  1980  0.695775  f  0.1

Объединение со статистикой и GroupBy

Не должно быть неожиданностью, что объединение pivot / stack / unstack с GroupBy и базовыми статистическими функциями Series и DataFrame может привести к весьма выразительным и быстрым манипуляциям с данными.

In [49]: df
Out[49]: 
exp                  A         B                   A
animal             cat       dog       cat       dog
first second                                        
bar   one     0.895717  0.805244 -1.206412  2.565646
      two     1.431256  1.340309 -1.170299 -0.226169
baz   one     0.410835  0.813850  0.132003 -0.827317
      two    -0.076467 -1.187678  1.130127 -1.436737
foo   one    -1.413681  1.607920  1.024180  0.569605
      two     0.875906 -2.211372  0.974466 -2.006747
qux   one    -0.410001 -0.078638  0.545952 -1.219217
      two    -1.226825  0.769804 -1.281247 -0.727707

In [50]: df.stack().mean(1).unstack()
Out[50]: 
animal             cat       dog
first second                    
bar   one    -0.155347  1.685445
      two     0.130479  0.557070
baz   one     0.271419 -0.006733
      two     0.526830 -1.312207
foo   one    -0.194750  1.088763
      two     0.925186 -2.109060
qux   one     0.067976 -0.648927
      two    -1.254036  0.021048

# same result, another way
In [51]: df.groupby(level=1, axis=1).mean()
Out[51]: 
animal             cat       dog
first second                    
bar   one    -0.155347  1.685445
      two     0.130479  0.557070
baz   one     0.271419 -0.006733
      two     0.526830 -1.312207
foo   one    -0.194750  1.088763
      two     0.925186 -2.109060
qux   one     0.067976 -0.648927
      two    -1.254036  0.021048

In [52]: df.stack().groupby(level=1).mean()
Out[52]: 
exp            A         B
second                    
one     0.071448  0.455513
two    -0.424186 -0.204486

In [53]: df.mean().unstack(0)
Out[53]: 
exp            A         B
animal                    
cat     0.060843  0.018596
dog    -0.413580  0.232430

Сводные таблицы

Функция pandas.pivot_table может использоваться для создания сводных таблиц в стиле электронных таблиц. См. пособие для некоторых продвинутых стратегий.

Она принимает ряд аргументов:

  • data: Объект DataFrame
  • values: колонка или список колонок для агрегирования
  • index: колонка, Grouper, массив, имеющий такую же длину, как данные, или список из них. Ключи для группировки по индексу сводной таблицы. Если передается массив, он используется аналогично значениям колонок.
  • columns: колонка, Grouper, массив, имеющий такую же длину, как данные, или список из них. Ключи для группировки по колонке сводной таблицы. Если передается массив, он используется аналогично значениям колонок.
  • aggfunc: функция для агрегирования, по умолчанию numpy.mean

Рассмотрим набор данных, подобный этому:

In [54]: import datetime

In [55]: df = pd.DataFrame({'A': ['one', 'one', 'two', 'three'] * 6,
   ....:                    'B': ['A', 'B', 'C'] * 8,
   ....:                    'C': ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 4,
   ....:                    'D': np.random.randn(24),
   ....:                    'E': np.random.randn(24),
   ....:                    'F': [datetime.datetime(2013, i, 1) for i in range(1, 13)] +
   ....:                         [datetime.datetime(2013, i, 15) for i in range(1, 13)]})
   ....: 

In [56]: df
Out[56]: 
        A  B    C         D         E          F
0     one  A  foo  0.341734 -0.317441 2013-01-01
1     one  B  foo  0.959726 -1.236269 2013-02-01
2     two  C  foo -1.110336  0.896171 2013-03-01
3   three  A  bar -0.619976 -0.487602 2013-04-01
4     one  B  bar  0.149748 -0.082240 2013-05-01
5     one  C  bar -0.732339 -2.182937 2013-06-01
6     two  A  foo  0.687738  0.380396 2013-07-01
..    ... ..  ...       ...       ...        ...
17    one  C  bar -0.345352  0.206053 2013-06-15
18    two  A  foo  1.314232 -0.251905 2013-07-15
19  three  B  foo  0.690579 -2.213588 2013-08-15
20    one  C  foo  0.995761  1.063327 2013-09-15
21    one  A  bar  2.396780  1.266143 2013-10-15
22    two  B  bar  0.014871  0.299368 2013-11-15
23  three  C  bar  3.357427 -0.863838 2013-12-15

[24 rows x 6 columns]

Мы можем очень легко создать сводные таблицы из этих данных:

In [57]: pd.pivot_table(df, values='D', index=['A', 'B'], columns=['C'])
Out[57]: 
C             bar       foo
A     B                    
one   A  1.120915 -0.514058
      B -0.338421  0.002759
      C -0.538846  0.699535
three A -1.181568       NaN
      B       NaN  0.433512
      C  0.588783       NaN
two   A       NaN  1.000985
      B  0.158248       NaN
      C       NaN  0.176180

In [58]: pd.pivot_table(df, values='D', index=['B'], columns=['A', 'C'], aggfunc=np.sum)
Out[58]: 
A       one               three                 two          
C       bar       foo       bar       foo       bar       foo
B                                                            
A  2.241830 -1.028115 -2.363137       NaN       NaN  2.001971
B -0.676843  0.005518       NaN  0.867024  0.316495       NaN
C -1.077692  1.399070  1.177566       NaN       NaN  0.352360

In [59]: pd.pivot_table(df, values=['D','E'], index=['B'], columns=['A', 'C'], aggfunc=np.sum)
Out[59]: 
          D                                                           E  \
A       one               three                 two                 one   
C       bar       foo       bar       foo       bar       foo       bar   
B                                                                         
A  2.241830 -1.028115 -2.363137       NaN       NaN  2.001971  2.786113   
B -0.676843  0.005518       NaN  0.867024  0.316495       NaN  1.368280   
C -1.077692  1.399070  1.177566       NaN       NaN  0.352360 -1.976883   

                                                     
A               three                 two            
C       foo       bar       foo       bar       foo  
B                                                    
A -0.043211  1.922577       NaN       NaN  0.128491  
B -1.103384       NaN -2.128743 -0.194294       NaN  
C  1.495717 -0.263660       NaN       NaN  0.872482  

Результирующий объект — DataFrame, имеющий потенциально многоуровневые индексы в строках и колонках. Если имя колонки values не задано, сводная таблица будет включать все данные, которые можно агрегировать, в дополнительном уровне иерархии в колонках:

In [60]: pd.pivot_table(df, index=['A', 'B'], columns=['C'])
Out[60]: 
                D                   E          
C             bar       foo       bar       foo
A     B                                        
one   A  1.120915 -0.514058  1.393057 -0.021605
      B -0.338421  0.002759  0.684140 -0.551692
      C -0.538846  0.699535 -0.988442  0.747859
three A -1.181568       NaN  0.961289       NaN
      B       NaN  0.433512       NaN -1.064372
      C  0.588783       NaN -0.131830       NaN
two   A       NaN  1.000985       NaN  0.064245
      B  0.158248       NaN -0.097147       NaN
      C       NaN  0.176180       NaN  0.436241

Также можно использовать Grouper для index и columns ключевых слов. Подробности о Grouper, см. Группировка со спецификацией Grouper.

In [61]: pd.pivot_table(df, values='D', index=pd.Grouper(freq='M', key='F'), columns='C')
Out[61]: 
C                bar       foo
F                             
2013-01-31       NaN -0.514058
2013-02-28       NaN  0.002759
2013-03-31       NaN  0.176180
2013-04-30 -1.181568       NaN
2013-05-31 -0.338421       NaN
2013-06-30 -0.538846       NaN
2013-07-31       NaN  1.000985
2013-08-31       NaN  0.433512
2013-09-30       NaN  0.699535
2013-10-31  1.120915       NaN
2013-11-30  0.158248       NaN
2013-12-31  0.588783       NaN

Вы можете отобразить красивую выходную таблицу, опуская пропущенные значения, вызвав to_string по желанию:

In [62]: table = pd.pivot_table(df, index=['A', 'B'], columns=['C'])

In [63]: print(table.to_string(na_rep=''))
                D                   E          
C             bar       foo       bar       foo
A     B                                        
one   A  1.120915 -0.514058  1.393057 -0.021605
      B -0.338421  0.002759  0.684140 -0.551692
      C -0.538846  0.699535 -0.988442  0.747859
three A -1.181568            0.961289          
      B            0.433512           -1.064372
      C  0.588783           -0.131830          
two   A            1.000985            0.064245
      B  0.158248           -0.097147          
      C            0.176180            0.436241

Обратите внимание, что pivot_table также доступен в качестве метода экземпляра DataFrame.

Добавление полей "margin"

Если вы передадите margins=True в pivot_table, будут добавлены специальные колонки и строки All с агрегатами частичных групп по категориям в строках и колонках:

In [64]: df.pivot_table(index=['A', 'B'], columns='C', margins=True, aggfunc=np.std)
Out[64]: 
                D                             E                    
C             bar       foo       All       bar       foo       All
A     B                                                            
one   A  1.804346  1.210272  1.569879  0.179483  0.418374  0.858005
      B  0.690376  1.353355  0.898998  1.083825  0.968138  1.101401
      C  0.273641  0.418926  0.771139  1.689271  0.446140  1.422136
three A  0.794212       NaN  0.794212  2.049040       NaN  2.049040
      B       NaN  0.363548  0.363548       NaN  1.625237  1.625237
      C  3.915454       NaN  3.915454  1.035215       NaN  1.035215
two   A       NaN  0.442998  0.442998       NaN  0.447104  0.447104
      B  0.202765       NaN  0.202765  0.560757       NaN  0.560757
      C       NaN  1.819408  1.819408       NaN  0.650439  0.650439
All      1.556686  0.952552  1.246608  1.250924  0.899904  1.059389

Перекрёстные таблицы

Используйте функцию crosstab для вычисления перекрёстной таблицы двух (или более) факторов. По умолчанию crosstab вычисляет таблицу частот факторов, если не передается массив значений и функция агрегирования.

Она принимает ряд аргументов:

  • index: массив-подобный объект, значения для группировки в строках
  • columns: массив-подобный объект, значения для группировки в колонках
  • values: массив-подобный объект, необязательно, массив значений для агрегирования в соответствии с факторами
  • aggfunc: функция, необязательно, если массив значений не передан, вычисляет таблицу частот
  • rownames: последовательность, по умолчанию None, должна соответствовать количеству переданных массивов строк
  • colnames: последовательность, по умолчанию None, если передана, должна соответствовать количеству переданных массивов колонок
  • margins: булево значение, по умолчанию False, Добавить поля "margin" (суммарные значения)
  • normalize: булево значение, {‘all’, ‘index’, ‘columns’}, или {0,1}, по умолчанию False. Нормализовать, поделив все значения на сумму значений.

Любые переданные Series будут использовать свои атрибуты имени, если не указаны имена строк или колонок для перекрёстной таблицы

Например:

In [65]: foo, bar, dull, shiny, one, two = 'foo', 'bar', 'dull', 'shiny', 'one', 'two'

In [66]: a = np.array([foo, foo, bar, bar, foo, foo], dtype=object)

In [67]: b = np.array([one, one, two, one, two, one], dtype=object)

In [68]: c = np.array([dull, dull, shiny, dull, dull, shiny], dtype=object)

In [69]: pd.crosstab(a, [b, c], rownames=['a'], colnames=['b', 'c'])
Out[69]: 
b    one        two      
c   dull shiny dull shiny
a                        
bar    1     0    0     1
foo    2     1    1     0

Если crosstab получит только две Series, она предоставит таблицу частот.

In [70]: df = pd.DataFrame({'A': [1, 2, 2, 2, 2], 'B': [3, 3, 4, 4, 4],
   ....:                    'C': [1, 1, np.nan, 1, 1]})
   ....: 

In [71]: df
Out[71]: 
   A  B    C
0  1  3  1.0
1  2  3  1.0
2  2  4  NaN
3  2  4  1.0
4  2  4  1.0

In [72]: pd.crosstab(df.A, df.B)
Out[72]: 
B  3  4
A      
1  1  0
2  1  3

Нормализация

Введено в версии 0.18.1.

Таблицы частот также могут быть нормализованы, чтобы показать проценты вместо подсчетов, используя аргумент normalize:

In [73]: pd.crosstab(df.A, df.B, normalize=True)
Out[73]: 
B    3    4
A          
1  0.2  0.0
2  0.2  0.6

normalize также может нормализовать значения в каждой строке или в каждой колонке:

In [74]: pd.crosstab(df.A, df.B, normalize='columns')
Out[74]: 
B    3    4
A          
1  0.5  0.0
2  0.5  1.0

crosstab также может получить третью Series и функцию агрегирования (aggfunc), которая будет применяться к значениям третьей Series внутри каждой группы, определенной первыми двумя Series:

In [75]: pd.crosstab(df.A, df.B, values=df.C, aggfunc=np.sum)
Out[75]: 
B    3    4
A          
1  1.0  NaN
2  1.0  2.0

Добавление полей "margin"

Наконец, можно добавить поля "margin" или нормализовать этот результат.

In [76]: pd.crosstab(df.A, df.B, values=df.C, aggfunc=np.sum, normalize=True,
   ....:             margins=True)
   ....: 
Out[76]: 
B       3    4   All
A                   
1    0.25  0.0  0.25
2    0.25  0.5  0.75
All  0.50  0.5  1.00

Разбиение на плитки

Функция cut вычисляет группировки для значений входного массива и часто используется для преобразования непрерывных переменных в дискретные или категориальные переменные:

In [77]: ages = np.array([10, 15, 13, 12, 23, 25, 28, 59, 60])

In [78]: pd.cut(ages, bins=3)
Out[78]: 
[(9.95, 26.667], (9.95, 26.667], (9.95, 26.667], (9.95, 26.667], (9.95, 26.667], (9.95, 26.667], (26.667, 43.333], (43.333, 60], (43.333, 60]]
Categories (3, object): [(9.95, 26.667] < (26.667, 43.333] < (43.333, 60]]

Если ключевое слово bins — целое число, то образуются равные интервалы. В качестве альтернативы мы можем указать настраиваемые границы интервалов:

In [79]: pd.cut(ages, bins=[0, 18, 35, 70])
Out[79]: 
[(0, 18], (0, 18], (0, 18], (0, 18], (18, 35], (18, 35], (18, 35], (35, 70], (35, 70]]
Categories (3, object): [(0, 18] < (18, 35] < (35, 70]]

Вычисление индикаторных/фиктивных переменных

Для преобразования категориальной переменной в «фиктивную» или «индикаторную» DataFrame, например, колонки в DataFrame (Series), которая имеет k различных значений, можно получить DataFrame, содержащий k колонок из 1 и 0:

In [80]: df = pd.DataFrame({'key': list('bbacab'), 'data1': range(6)})

In [81]: pd.get_dummies(df['key'])
Out[81]: 
     a    b    c
0  0.0  1.0  0.0
1  0.0  1.0  0.0
2  1.0  0.0  0.0
3  0.0  0.0  1.0
4  1.0  0.0  0.0
5  0.0  1.0  0.0

Иногда бывает полезно добавлять префикс к именам колонок, например, при объединении результата с исходным DataFrame:

In [82]: dummies = pd.get_dummies(df['key'], prefix='key')

In [83]: dummies
Out[83]: 
   key_a  key_b  key_c
0    0.0    1.0    0.0
1    0.0    1.0    0.0
2    1.0    0.0    0.0
3    0.0    0.0    1.0
4    1.0    0.0    0.0
5    0.0    1.0    0.0

In [84]: df[['data1']].join(dummies)
Out[84]: 
   data1  key_a  key_b  key_c
0      0    0.0    1.0    0.0
1      1    0.0    1.0    0.0
2      2    1.0    0.0    0.0
3      3    0.0    0.0    1.0
4      4    1.0    0.0    0.0
5      5    0.0    1.0    0.0

Эта функция часто используется вместе с функциями дискретизации, такими как cut:

In [85]: values = np.random.randn(10)

In [86]: values
Out[86]: 
array([ 0.4082, -1.0481, -0.0257, -0.9884,  0.0941,  1.2627,  1.29  ,
        0.0824, -0.0558,  0.5366])

In [87]: bins = [0, 0.2, 0.4, 0.6, 0.8, 1]

In [88]: pd.get_dummies(pd.cut(values, bins))
Out[88]: 
   (0, 0.2]  (0.2, 0.4]  (0.4, 0.6]  (0.6, 0.8]  (0.8, 1]
0       0.0         0.0         1.0         0.0       0.0
1       0.0         0.0         0.0         0.0       0.0
2       0.0         0.0         0.0         0.0       0.0
3       0.0         0.0         0.0         0.0       0.0
4       1.0         0.0         0.0         0.0       0.0
5       0.0         0.0         0.0         0.0       0.0
6       0.0         0.0         0.0         0.0       0.0
7       1.0         0.0         0.0         0.0       0.0
8       0.0         0.0         0.0         0.0       0.0
9       0.0         0.0         1.0         0.0       0.0

См. также Series.str.get_dummies.

Введено в версии 0.15.0.

get_dummies() также принимает DataFrame. По умолчанию все категориальные переменные (категориальные в статистическом смысле, те, у которых object или categorical тип данных) кодируются как фиктивные переменные.

In [89]: df = pd.DataFrame({'A': ['a', 'b', 'a'], 'B': ['c', 'c', 'b'],
   ....:                    'C': [1, 2, 3]})
   ....: 

In [90]: pd.get_dummies(df)
Out[90]: 
   C  A_a  A_b  B_b  B_c
0  1  1.0  0.0  0.0  1.0
1  2  0.0  1.0  0.0  1.0
2  3  1.0  0.0  1.0  0.0

Все столбцы, не являющиеся объектами, включаются в выходные данные без изменений.

Вы можете управлять столбцами, которые кодируются, с помощью ключевого слова columns.

In [91]: pd.get_dummies(df, columns=['A'])
Out[91]: 
   B  C  A_a  A_b
0  c  1  1.0  0.0
1  c  2  0.0  1.0
2  b  3  1.0  0.0

Обратите внимание, что столбец B по-прежнему включён в выходные данные, он просто не был закодирован. Вы можете удалить B перед вызовом get_dummies, если не хотите включать его в выходные данные.

Как и в случае с версией Series, вы можете передать значения для prefix и prefix_sep. По умолчанию имя столбца используется в качестве префикса, а «_» — в качестве разделителя префикса. Вы можете указать prefix и prefix_sep тремя способами:

  • строка: Используйте одно и то же значение для prefix или prefix_sep для каждого столбца, который нужно закодировать
  • список: Длина списка должна совпадать с количеством столбцов, которые нужно закодировать.
  • словарь: Сопоставление имени столбца с префиксом
In [92]: simple = pd.get_dummies(df, prefix='new_prefix')

In [93]: simple
Out[93]: 
   C  new_prefix_a  new_prefix_b  new_prefix_b  new_prefix_c
0  1           1.0           0.0           0.0           1.0
1  2           0.0           1.0           0.0           1.0
2  3           1.0           0.0           1.0           0.0

In [94]: from_list = pd.get_dummies(df, prefix=['from_A', 'from_B'])

In [95]: from_list
Out[95]: 
   C  from_A_a  from_A_b  from_B_b  from_B_c
0  1       1.0       0.0       0.0       1.0
1  2       0.0       1.0       0.0       1.0
2  3       1.0       0.0       1.0       0.0

In [96]: from_dict = pd.get_dummies(df, prefix={'B': 'from_B', 'A': 'from_A'})

In [97]: from_dict
Out[97]: 
   C  from_A_a  from_A_b  from_B_b  from_B_c
0  1       1.0       0.0       0.0       1.0
1  2       0.0       1.0       0.0       1.0
2  3       1.0       0.0       1.0       0.0

Введено в версии 0.18.0.

Иногда будет полезно сохранить только k-1 уровней категориальной переменной, чтобы избежать коллинеарности при передаче результата в статистические модели. Вы можете переключиться на этот режим, включив drop_first.

In [98]: s = pd.Series(list('abcaa'))

In [99]: pd.get_dummies(s)
Out[99]: 
     a    b    c
0  1.0  0.0  0.0
1  0.0  1.0  0.0
2  0.0  0.0  1.0
3  1.0  0.0  0.0
4  1.0  0.0  0.0

In [100]: pd.get_dummies(s, drop_first=True)
Out[100]: 
     b    c
0  0.0  0.0
1  1.0  0.0
2  0.0  1.0
3  0.0  0.0
4  0.0  0.0

Если столбец содержит только один уровень, он будет опущен в результате.

In [101]: df = pd.DataFrame({'A':list('aaaaa'),'B':list('ababc')})

In [102]: pd.get_dummies(df)
Out[102]: 
   A_a  B_a  B_b  B_c
0  1.0  1.0  0.0  0.0
1  1.0  0.0  1.0  0.0
2  1.0  1.0  0.0  0.0
3  1.0  0.0  1.0  0.0
4  1.0  0.0  0.0  1.0

In [103]: pd.get_dummies(df, drop_first=True)
Out[103]: 
   B_b  B_c
0  0.0  0.0
1  1.0  0.0
2  0.0  0.0
3  1.0  0.0
4  0.0  1.0

Факторизация значений

Для кодирования значений 1-d как перечислимого типа используйте factorize:

In [104]: x = pd.Series(['A', 'A', np.nan, 'B', 3.14, np.inf])

In [105]: x
Out[105]: 
0       A
1       A
2     NaN
3       B
4    3.14
5     inf
dtype: object

In [106]: labels, uniques = pd.factorize(x)

In [107]: labels
Out[107]: array([ 0,  0, -1,  1,  2,  3])

In [108]: uniques
Out[108]: Index([u'A', u'B', 3.14, inf], dtype='object')

Обратите внимание, что factorize аналогично numpy.unique, но отличается по обработке NaN:

Примечание

Следующая numpy.unique не будет работать в Python 3 из-за ошибки упорядочивания. См. также Здесь

In [109]: pd.factorize(x, sort=True)
Out[109]: 
(array([ 2,  2, -1,  3,  0,  1]),
 Index([3.14, inf, u'A', u'B'], dtype='object'))

In [110]: np.unique(x, return_inverse=True)[::-1]
Out[110]: (array([3, 3, 0, 4, 1, 2]), array([nan, 3.14, inf, 'A', 'B'], dtype=object))

Примечание

Если вам нужно обработать только один столбец как категориальную переменную (как в R’s factor), вы можете использовать df["cat_col"] = pd.Categorical(df["col"]) или df["cat_col"] = df["col"].astype("category"). Для полной документации по Categorical, см. введение в категориальные типы Введение в категориальные типы и документацию API Документация API. Эта функция была введена в версии 0.15.

© 2008–2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/0.18.1/reshaping.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API