Преобразование формы и сводные таблицы
Преобразование формы путем поворота объектов 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