Основные базовые функции
Здесь мы обсудим множество основных функций, общих для структур данных pandas. Вот как создать некоторые объекты, используемые в примерах из предыдущего раздела:
In [1]: index = pd.date_range('1/1/2000', periods=8)
In [2]: s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])
In [3]: df = pd.DataFrame(np.random.randn(8, 3), index=index,
...: columns=['A', 'B', 'C'])
...:
In [4]: wp = pd.Panel(np.random.randn(2, 5, 4), items=['Item1', 'Item2'],
...: major_axis=pd.date_range('1/1/2000', periods=5),
...: minor_axis=['A', 'B', 'C', 'D'])
...:
Заголовки и хвосты
Чтобы просмотреть небольшую выборку объекта Series или DataFrame, используйте методы head() и tail(). По умолчанию отображается пять элементов, но вы можете указать любое количество.
In [5]: long_series = pd.Series(np.random.randn(1000)) In [6]: long_series.head() Out[6]: 0 -0.305384 1 -0.479195 2 0.095031 3 -0.270099 4 -0.707140 dtype: float64 In [7]: long_series.tail(3) Out[7]: 997 0.588446 998 0.026465 999 -1.728222 dtype: float64
Атрибуты и исходный массив ndarray
Объекты pandas имеют ряд атрибутов, позволяющих получить доступ к метаданным
- shape: задаёт размерность осей объекта, согласуясь с ndarray
- Метки осей
- Series: index (только ось)
- DataFrame: index (строки) и columns (колонки)
- Panel: items, major_axis и minor_axis
Обратите внимание, что эти атрибуты можно безопасно назначить!
In [8]: df[:2]
Out[8]:
A B C
2000-01-01 0.187483 -1.933946 0.377312
2000-01-02 0.734122 2.141616 -0.011225
In [9]: df.columns = [x.lower() for x in df.columns]
In [10]: df
Out[10]:
a b c
2000-01-01 0.187483 -1.933946 0.377312
2000-01-02 0.734122 2.141616 -0.011225
2000-01-03 0.048869 -1.360687 -0.479010
2000-01-04 -0.859661 -0.231595 -0.527750
2000-01-05 -1.296337 0.150680 0.123836
2000-01-06 0.571764 1.555563 -0.823761
2000-01-07 0.535420 -1.032853 1.469725
2000-01-08 1.304124 1.449735 0.203109
Чтобы получить фактические данные внутри структуры данных, достаточно обратиться к свойству values:
In [11]: s.values
Out[11]: array([ 0.1122, 0.8717, -0.8161, -0.7849, 1.0307])
In [12]: df.values
Out[12]:
array([[ 0.1875, -1.9339, 0.3773],
[ 0.7341, 2.1416, -0.0112],
[ 0.0489, -1.3607, -0.479 ],
[-0.8597, -0.2316, -0.5278],
[-1.2963, 0.1507, 0.1238],
[ 0.5718, 1.5556, -0.8238],
[ 0.5354, -1.0329, 1.4697],
[ 1.3041, 1.4497, 0.2031]])
In [13]: wp.values
Out[13]:
array([[[-1.032 , 0.9698, -0.9627, 1.3821],
[-0.9388, 0.6691, -0.4336, -0.2736],
[ 0.6804, -0.3084, -0.2761, -1.8212],
[-1.9936, -1.9274, -2.0279, 1.625 ],
[ 0.5511, 3.0593, 0.4553, -0.0307]],
[[ 0.9357, 1.0612, -2.1079, 0.1999],
[ 0.3236, -0.6416, -0.5875, 0.0539],
[ 0.1949, -0.382 , 0.3186, 2.0891],
[-0.7283, -0.0903, -0.7482, 1.3189],
[-2.0298, 0.7927, 0.461 , -0.5427]]])
Если DataFrame или Panel содержат данные с однородным типом, массив ndarray можно фактически изменить на месте, и изменения будут отражены в структуре данных. Для разнородных данных (например, некоторые столбцы DataFrame не имеют одного и того же типа данных), это не будет так. В отличие от меток осей, свойству values нельзя присвоить значение.
Примечание
При работе с разнородными данными тип данных результирующего массива ndarray будет выбран для обеспечения совместимости со всеми вовлечёнными данными. Например, если используются строки, результат будет иметь тип object. Если присутствуют только числа с плавающей точкой и целые числа, результирующий массив будет иметь тип float.
Ускоренные операции
pandas поддерживает ускорение некоторых типов бинарных числовых и булевых операций, используя библиотеку numexpr (начиная с версии 0.11.0) и библиотеки bottleneck.
Эти библиотеки особенно полезны при работе с большими наборами данных и обеспечивают значительное ускорение. numexpr использует разумное дробление, кэширование и несколько ядер. bottleneck представляет собой набор специализированных цитоновых процедур, которые особенно быстры при работе с массивами, имеющими nans.
Вот пример (используя 100 столбцов и 100 000 строк DataFrames):
| Операция | 0.11.0 (мс) | Предыдущая версия (мс) | Отношение к предыдущей |
|---|---|---|---|
df1 > df2 | 13.32 | 125.35 | 0.1063 |
df1 * df2 | 21.71 | 36.63 | 0.5928 |
df1 + df2 | 22.04 | 36.50 | 0.6039 |
Настоятельно рекомендуется установить обе библиотеки. Более подробная информация об установке приведена в разделе Рекомендуемые зависимости.
Гибкие бинарные операции
При бинарных операциях между структурами данных pandas есть две ключевые особенности:
- Поведение векторизации между объектами более высокого (например, DataFrame) и более низкого (например, Series) измерения.
- Отсутствующие данные в вычислениях
Мы продемонстрируем, как управлять этими проблемами независимо, хотя их можно обрабатывать одновременно.
Соответствие/векторизация
DataFrame имеет методы add(), sub(), mul(), div() и соответствующие функции radd(), rsub(), ... для выполнения бинарных операций. Для поведения векторизации в качестве входных данных в первую очередь рассматривается Series. С помощью этих функций вы можете соответствовать меткам index или columns через ключевое слово axis:
In [14]: df = pd.DataFrame({'one' : pd.Series(np.random.randn(3), index=['a', 'b', 'c']),
....: 'two' : pd.Series(np.random.randn(4), index=['a', 'b', 'c', 'd']),
....: 'three' : pd.Series(np.random.randn(3), index=['b', 'c', 'd'])})
....:
In [15]: df
Out[15]:
one three two
a -0.626544 NaN -0.351587
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
d NaN 1.124472 -1.101558
In [16]: row = df.ix[1]
In [17]: column = df['two']
In [18]: df.sub(row, axis='columns')
Out[18]:
one three two
a -0.487650 NaN -1.487837
b 0.000000 0.000000 0.000000
c 0.150512 0.639504 -1.585038
d NaN 1.301762 -2.237808
In [19]: df.sub(row, axis=1)
Out[19]:
one three two
a -0.487650 NaN -1.487837
b 0.000000 0.000000 0.000000
c 0.150512 0.639504 -1.585038
d NaN 1.301762 -2.237808
In [20]: df.sub(column, axis='index')
Out[20]:
one three two
a -0.274957 NaN 0.0
b -1.275144 -1.313539 0.0
c 0.460406 0.911003 0.0
d NaN 2.226031 0.0
In [21]: df.sub(column, axis=0)
Out[21]:
one three two
a -0.274957 NaN 0.0
b -1.275144 -1.313539 0.0
c 0.460406 0.911003 0.0
d NaN 2.226031 0.0
Кроме того, вы можете выровнять уровень многоиндексированного DataFrame с Series.
In [22]: dfmi = df.copy()
In [23]: dfmi.index = pd.MultiIndex.from_tuples([(1,'a'),(1,'b'),(1,'c'),(2,'a')],
....: names=['first','second'])
....:
In [24]: dfmi.sub(column, axis=0, level='second')
Out[24]:
one three two
first second
1 a -0.274957 NaN 0.000000
b -1.275144 -1.313539 0.000000
c 0.460406 0.911003 0.000000
2 a NaN 1.476060 -0.749971
С Panel описание поведения векторизации немного сложнее, поэтому методы арифметических операций (и, возможно, запутанно?) дают вам возможность указать векторную ось. Например, предположим, что мы хотим вычесть среднее значение из данных по определённой оси. Это можно сделать, вычислив среднее значение по оси и выполнив векторизацию по этой же оси:
In [25]: major_mean = wp.mean(axis='major')
In [26]: major_mean
Out[26]:
Item1 Item2
A -0.546569 -0.260774
B 0.492478 0.147993
C -0.649010 -0.532794
D 0.176307 0.623812
In [27]: wp.sub(major_mean, axis='major')
Out[27]:
<class 'pandas.core.panel.Panel'>
Dimensions: 2 (items) x 5 (major_axis) x 4 (minor_axis)
Items axis: Item1 to Item2
Major_axis axis: 2000-01-01 00:00:00 to 2000-01-05 00:00:00
Minor_axis axis: A to D
И аналогично для axis="items" и axis="minor".
Примечание
Я мог бы согласиться на изменение аргумента axis в методах DataFrame для соответствия поведению векторизации в Panel. Хотя это потребует переходного периода, чтобы пользователи могли изменить свой код...
Series и Index также поддерживают встроенную функцию divmod(). Эта функция выполняет целочисленное деление и нахождение остатка одновременно, возвращая кортеж с типом, соответствующим левой части выражения. Например:
In [28]: s = pd.Series(np.arange(10)) In [29]: s Out[29]: 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 dtype: int64 In [30]: div, rem = divmod(s, 3) In [31]: div Out[31]: 0 0 1 0 2 0 3 1 4 1 5 1 6 2 7 2 8 2 9 3 dtype: int64 In [32]: rem Out[32]: 0 0 1 1 2 2 3 0 4 1 5 2 6 0 7 1 8 2 9 0 dtype: int64 In [33]: idx = pd.Index(np.arange(10)) In [34]: idx Out[34]: Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype='int64') In [35]: div, rem = divmod(idx, 3) In [36]: div Out[36]: Int64Index([0, 0, 0, 1, 1, 1, 2, 2, 2, 3], dtype='int64') In [37]: rem Out[37]: Int64Index([0, 1, 2, 0, 1, 2, 0, 1, 2, 0], dtype='int64')
Мы также можем выполнить поэлементное divmod():
In [38]: div, rem = divmod(s, [2, 2, 3, 3, 4, 4, 5, 5, 6, 6]) In [39]: div Out[39]: 0 0 1 0 2 0 3 1 4 1 5 1 6 1 7 1 8 1 9 1 dtype: int64 In [40]: rem Out[40]: 0 0 1 1 2 2 3 0 4 0 5 1 6 1 7 2 8 2 9 3 dtype: int64
Отсутствующие данные/операции со значениями заполнения
В Series и DataFrame (но ещё не в Panel) арифметические функции имеют возможность ввода fill_value, то есть значения для подстановки, когда отсутствует максимум одно значение в определённой позиции. Например, при сложении двух объектов DataFrame вы можете захотеть рассматривать NaN как 0, если оба DataFrame пропускают это значение, в противном случае результат будет NaN (позже вы можете заменить NaN другим значением с помощью fillna если хотите).
In [41]: df
Out[41]:
one three two
a -0.626544 NaN -0.351587
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
d NaN 1.124472 -1.101558
In [42]: df2
Out[42]:
one three two
a -0.626544 1.000000 -0.351587
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
d NaN 1.124472 -1.101558
In [43]: df + df2
Out[43]:
one three two
a -1.253088 NaN -0.703174
b -0.277789 -0.354579 2.272499
c 0.023235 0.924429 -0.897577
d NaN 2.248945 -2.203116
In [44]: df.add(df2, fill_value=0)
Out[44]:
one three two
a -1.253088 1.000000 -0.703174
b -0.277789 -0.354579 2.272499
c 0.023235 0.924429 -0.897577
d NaN 2.248945 -2.203116
Гибкие сравнения
Начиная с версии 0.8, pandas ввёл бинарные методы сравнения eq, ne, lt, gt, le и ge для Series и DataFrame, поведение которых аналогично описанным выше бинарным арифметическим операциям:
In [45]: df.gt(df2)
Out[45]:
one three two
a False False False
b False False False
c False False False
d False False False
In [46]: df2.ne(df)
Out[46]:
one three two
a False True False
b False False False
c False False False
d True False False
Эти операции создают объект pandas того же типа, что и входной объект слева, если тип данных bool. Эти boolean объекты могут быть использованы в операциях индексирования, см. здесь
Булевы редукции
Вы можете применить редукции: empty, any(), all() и bool(), чтобы получить способ обобщения булевого результата.
In [47]: (df > 0).all() Out[47]: one False three False two False dtype: bool In [48]: (df > 0).any() Out[48]: one True three True two True dtype: bool
Вы можете свести результат к окончательному булевому значению.
In [49]: (df > 0).any().any() Out[49]: True
Вы можете проверить, пуст ли объект pandas, с помощью свойства empty.
In [50]: df.empty
Out[50]: False
In [51]: pd.DataFrame(columns=list('ABC')).empty
Out[51]: True
Для оценки булевого значения одного элемента в объекте pandas используйте метод bool():
In [52]: pd.Series([True]).bool() Out[52]: True In [53]: pd.Series([False]).bool() Out[53]: False In [54]: pd.DataFrame([[True]]).bool() Out[54]: True In [55]: pd.DataFrame([[False]]).bool() Out[55]: False
Предупреждение
Вы можете быть искушены сделать следующее:
>>> if df:
...
Или
>>> df and df2
Оба варианта приведут к ошибке, так как вы пытаетесь сравнить несколько значений.
ValueError: The truth value of an array is ambiguous. Use a.empty, a.any() or a.all().
См. подводные камни для более подробного обсуждения.
Сравнение объектов на эквивалентность
Часто вы можете найти более одного способа вычисления одного и того же результата. Например, рассмотрим df+df и df*2. Чтобы проверить, эти два вычисления дают один и тот же результат, используя инструменты, показанные выше, вы можете предположить использование (df+df == df*2).all(). Но на самом деле это выражение ложно:
In [56]: df+df == df*2
Out[56]:
one three two
a True False True
b True True True
c True True True
d False True True
In [57]: (df+df == df*2).all()
Out[57]:
one False
three False
two True
dtype: bool
Обратите внимание, что булевый DataFrame df+df == df*2 содержит некоторые ложные значения! Это связано с тем, что NaN не сравниваются как равные:
In [58]: np.nan == np.nan Out[58]: False
Таким образом, начиная с версии 0.13.1, NDFrames (такие как Series, DataFrame и Panel) имеют метод equals() для проверки на равенство, где NaN в соответствующих позициях рассматриваются как равные.
In [59]: (df+df).equals(df*2) Out[59]: True
Обратите внимание, что индекс Series или DataFrame должен быть в том же порядке, чтобы равенство было истинным:
In [60]: df1 = pd.DataFrame({'col':['foo', 0, np.nan]})
In [61]: df2 = pd.DataFrame({'col':[np.nan, 0, 'foo']}, index=[2,1,0])
In [62]: df1.equals(df2)
Out[62]: False
In [63]: df1.equals(df2.sort_index())
Out[63]: True
Сравнение массивоподобных объектов
Вы можете удобно выполнять поэлементные сравнения при сравнении структуры данных pandas со скалярным значением:
In [64]: pd.Series(['foo', 'bar', 'baz']) == 'foo' Out[64]: 0 True 1 False 2 False dtype: bool In [65]: pd.Index(['foo', 'bar', 'baz']) == 'foo' Out[65]: array([ True, False, False], dtype=bool)
Pandas также обрабатывает поэлементные сравнения между различными массивоподобными объектами одинаковой длины:
In [66]: pd.Series(['foo', 'bar', 'baz']) == pd.Index(['foo', 'bar', 'qux']) Out[66]: 0 True 1 True 2 False dtype: bool In [67]: pd.Series(['foo', 'bar', 'baz']) == np.array(['foo', 'bar', 'qux']) Out[67]: 0 True 1 True 2 False dtype: bool
Попытка сравнения Index или Series объектов различной длины вызовет ошибку ValueError:
In [55]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar']) ValueError: Series lengths must match to compare In [56]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo']) ValueError: Series lengths must match to compare
Обратите внимание, что это отличается от поведения numpy, где сравнение может быть векторизировано:
In [68]: np.array([1, 2, 3]) == np.array([2]) Out[68]: array([False, True, False], dtype=bool)
или может вернуть False, если векторизация невозможна:
In [69]: np.array([1, 2, 3]) == np.array([1, 2]) Out[69]: False
Объединение перекрывающихся наборов данных
Иногда возникает проблема объединения двух похожих наборов данных, где значения одного предпочтительнее другого. Например, две временные ряды, представляющие определённый экономический показатель, где одна из них считается «более качественной». Однако, менее качественная временная ряд может охватывать более ранний период или иметь более полные данные. Поэтому мы хотим объединить два объекта DataFrame, где отсутствующие значения в одном DataFrame условно заполняются подобными значениями из другого DataFrame. Функция, реализующая эту операцию, — combine_first(), что мы иллюстрируем:
In [70]: df1 = pd.DataFrame({'A' : [1., np.nan, 3., 5., np.nan],
....: 'B' : [np.nan, 2., 3., np.nan, 6.]})
....:
In [71]: df2 = pd.DataFrame({'A' : [5., 2., 4., np.nan, 3., 7.],
....: 'B' : [np.nan, np.nan, 3., 4., 6., 8.]})
....:
In [72]: df1
Out[72]:
A B
0 1.0 NaN
1 NaN 2.0
2 3.0 3.0
3 5.0 NaN
4 NaN 6.0
In [73]: df2
Out[73]:
A B
0 5.0 NaN
1 2.0 NaN
2 4.0 3.0
3 NaN 4.0
4 3.0 6.0
5 7.0 8.0
In [74]: df1.combine_first(df2)
Out[74]:
A B
0 1.0 NaN
1 2.0 2.0
2 3.0 3.0
3 5.0 4.0
4 3.0 6.0
5 7.0 8.0
Общее объединение DataFrame
combine_first() метод, описанный выше, вызывает более общий метод DataFrame combine(). Этот метод принимает другой DataFrame и функцию объединения, выравнивает входной DataFrame, а затем передает функцию объединения парам Series (т.е., столбцам, имеющим одинаковые имена).
Например, чтобы воспроизвести combine_first(), как показано выше:
In [75]: combiner = lambda x, y: np.where(pd.isnull(x), y, x)
In [76]: df1.combine(df2, combiner)
Out[76]:
A B
0 1.0 NaN
1 2.0 2.0
2 3.0 3.0
3 5.0 4.0
4 3.0 6.0
5 7.0 8.0
Дескриптивные статистики
Большое количество методов для вычисления дескриптивных статистик и других связанных операций с Series, DataFrame и Panel. Большинство из них являются агрегациями (следовательно, производят результат с меньшей размерностью), например, sum(), mean() и quantile(), но некоторые из них, такие как cumsum() и cumprod(), производят объект той же размерности. Как правило, эти методы принимают аргумент axis, точно так же, как ndarray.{sum, std, ...}, но ось можно указать по имени или целочисленно:
- Series: аргумент axis не требуется
- DataFrame: “index” (axis=0, по умолчанию), “columns” (axis=1)
- Panel: “items” (axis=0), “major” (axis=1, по умолчанию), “minor” (axis=2)
Например:
In [77]: df
Out[77]:
one three two
a -0.626544 NaN -0.351587
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
d NaN 1.124472 -1.101558
In [78]: df.mean(0)
Out[78]:
one -0.251274
three 0.469799
two -0.191421
dtype: float64
In [79]: df.mean(1)
Out[79]:
a -0.489066
b 0.273355
c 0.008348
d 0.011457
dtype: float64
Все такие методы имеют опцию skipna , сигнализирующую о том, нужно ли исключить пропущенные данные (True по умолчанию):
In [80]: df.sum(0, skipna=False) Out[80]: one NaN three NaN two -0.765684 dtype: float64 In [81]: df.sum(axis=1, skipna=True) Out[81]: a -0.978131 b 0.820066 c 0.025044 d 0.022914 dtype: float64
В сочетании с поведением широковещательной передачи/арифметики можно очень лаконично описать различные статистические процедуры, такие как стандартизация (приведение данных к нулевому среднему и стандартному отклонению 1):
In [82]: ts_stand = (df - df.mean()) / df.std() In [83]: ts_stand.std() Out[83]: one 1.0 three 1.0 two 1.0 dtype: float64 In [84]: xs_stand = df.sub(df.mean(1), axis=0).div(df.std(1), axis=0) In [85]: xs_stand.std(1) Out[85]: a 1.0 b 1.0 c 1.0 d 1.0 dtype: float64
Обратите внимание, что такие методы, как cumsum() и cumprod() сохраняют положение значений NA:
In [86]: df.cumsum()
Out[86]:
one three two
a -0.626544 NaN -0.351587
b -0.765438 -0.177289 0.784662
c -0.753821 0.284925 0.335874
d NaN 1.409398 -0.765684
Вот сводная таблица быстрого справочника по общим функциям. Каждый из них также принимает необязательный параметр level , который применяется только в том случае, если объект имеет многоуровневый индекс.
| Функция | Описание |
|---|---|
count | Количество ненулевых наблюдений |
sum | Сумма значений |
mean | Среднее значение значений |
mad | Среднее абсолютное отклонение |
median | Арифметическая медиана значений |
min | Минимальное значение |
max | Максимальное значение |
mode | Мода |
abs | Модуль (абсолютное значение) |
prod | Произведение значений |
std | Исправленное по Бесселю выборочное стандартное отклонение |
var | Несмещенная дисперсия |
sem | Стандартная ошибка среднего |
skew | Выборовое асимметричное (3-й момент) |
kurt | Выборовый эксцесс (4-й момент) |
quantile | Выборовый квантиль (%) |
cumsum | Кумулятивная сумма |
cumprod | Кумулятивное произведение |
cummax | Накопленный максимум |
cummin | Накопленный минимум |
Обратите внимание, что по случайности некоторые методы NumPy, такие как mean, std, и sum, по умолчанию будут исключать NA при вводе Series:
In [87]: np.mean(df['one']) Out[87]: -0.2512736517583951 In [88]: np.mean(df['one'].values) Out[88]: nan
Series также имеет метод nunique(), который возвращает количество уникальных ненулевых значений:
In [89]: series = pd.Series(np.random.randn(500)) In [90]: series[20:500] = np.nan In [91]: series[10:20] = 5 In [92]: series.nunique() Out[92]: 11
Обобщение данных: describe
Существует удобная функция describe(), которая вычисляет различные сводные статистические данные о Series или столбцах DataFrame (исключая, разумеется, NA):
In [93]: series = pd.Series(np.random.randn(1000))
In [94]: series[::2] = np.nan
In [95]: series.describe()
Out[95]:
count 500.000000
mean -0.039663
std 1.069371
min -3.463789
25% -0.731101
50% -0.058918
75% 0.672758
max 3.120271
dtype: float64
In [96]: frame = pd.DataFrame(np.random.randn(1000, 5), columns=['a', 'b', 'c', 'd', 'e'])
In [97]: frame.ix[::2] = np.nan
In [98]: frame.describe()
Out[98]:
a b c d e
count 500.000000 500.000000 500.000000 500.000000 500.000000
mean 0.000954 -0.044014 0.075936 -0.003679 0.020751
std 1.005133 0.974882 0.967432 1.004732 0.963812
min -3.010899 -2.782760 -3.401252 -2.944925 -3.794127
25% -0.682900 -0.681161 -0.528190 -0.663503 -0.615717
50% -0.001651 -0.006279 0.040098 -0.003378 0.006282
75% 0.656439 0.632852 0.717919 0.687214 0.653423
max 3.007143 2.627688 2.702490 2.850852 3.072117
Вы можете выбрать определенные процентили для включения в вывод:
In [99]: series.describe(percentiles=[.05, .25, .75, .95]) Out[99]: count 500.000000 mean -0.039663 std 1.069371 min -3.463789 5% -1.741334 25% -0.731101 50% -0.058918 75% 0.672758 95% 1.854383 max 3.120271 dtype: float64
По умолчанию медиана всегда включается.
Для нечислового объекта Series, describe() даст простое описание количества уникальных значений и наиболее часто встречающихся значений:
In [100]: s = pd.Series(['a', 'a', 'b', 'b', 'a', 'a', np.nan, 'c', 'd', 'a']) In [101]: s.describe() Out[101]: count 9 unique 4 top a freq 5 dtype: object
Обратите внимание, что для смешанного DataFrame, describe() будет ограничивать вывод только числовыми столбцами, или, если таковых нет, только категориальными:
In [102]: frame = pd.DataFrame({'a': ['Yes', 'Yes', 'No', 'No'], 'b': range(4)})
In [103]: frame.describe()
Out[103]:
b
count 4.000000
mean 1.500000
std 1.290994
min 0.000000
25% 0.750000
50% 1.500000
75% 2.250000
max 3.000000
Это поведение можно контролировать, указав список типов как include/exclude аргументы. Специальное значение all также может быть использовано:
In [104]: frame.describe(include=['object'])
Out[104]:
a
count 4
unique 2
top No
freq 2
In [105]: frame.describe(include=['number'])
Out[105]:
b
count 4.000000
mean 1.500000
std 1.290994
min 0.000000
25% 0.750000
50% 1.500000
75% 2.250000
max 3.000000
In [106]: frame.describe(include='all')
Out[106]:
a b
count 4 4.000000
unique 2 NaN
top No NaN
freq 2 NaN
mean NaN 1.500000
std NaN 1.290994
min NaN 0.000000
25% NaN 0.750000
50% NaN 1.500000
75% NaN 2.250000
max NaN 3.000000
Эта функция полагается на select_dtypes. Обратитесь к ней для получения подробной информации о допустимых входных данных.
Индекс минимальных/максимальных значений
Функции idxmin() и idxmax() для Series и DataFrame вычисляют метки индексов с минимальными и максимальными соответствующими значениями:
In [107]: s1 = pd.Series(np.random.randn(5))
In [108]: s1
Out[108]:
0 -0.872725
1 1.522411
2 0.080594
3 -1.676067
4 0.435804
dtype: float64
In [109]: s1.idxmin(), s1.idxmax()
Out[109]: (3, 1)
In [110]: df1 = pd.DataFrame(np.random.randn(5,3), columns=['A','B','C'])
In [111]: df1
Out[111]:
A B C
0 0.445734 -1.649461 0.169660
1 1.246181 0.131682 -2.001988
2 -1.273023 0.870502 0.214583
3 0.088452 -0.173364 1.207466
4 0.546121 0.409515 -0.310515
In [112]: df1.idxmin(axis=0)
Out[112]:
A 2
B 0
C 1
dtype: int64
In [113]: df1.idxmax(axis=1)
Out[113]:
0 A
1 A
2 B
3 C
4 A
dtype: object
Если существует несколько строк (или столбцов), соответствующих минимальному или максимальному значению, idxmin() и idxmax() возвращают первый соответствующий индекс:
In [114]: df3 = pd.DataFrame([2, 1, 1, 3, np.nan], columns=['A'], index=list('edcba'))
In [115]: df3
Out[115]:
A
e 2.0
d 1.0
c 1.0
b 3.0
a NaN
In [116]: df3['A'].idxmin()
Out[116]: 'd'
Примечание
idxmin и idxmax называются argmin и argmax в NumPy.
Счет значений (гистограммы) / Мода
Метод Series value_counts() и функция верхнего уровня вычисляют гистограмму одномерного массива значений. Ее также можно использовать как функцию для обычных массивов:
In [117]: data = np.random.randint(0, 7, size=50)
In [118]: data
Out[118]:
array([5, 3, 2, 2, 1, 4, 0, 4, 0, 2, 0, 6, 4, 1, 6, 3, 3, 0, 2, 1, 0, 5, 5,
3, 6, 1, 5, 6, 2, 0, 0, 6, 3, 3, 5, 0, 4, 3, 3, 3, 0, 6, 1, 3, 5, 5,
0, 4, 0, 6])
In [119]: s = pd.Series(data)
In [120]: s.value_counts()
Out[120]:
0 11
3 10
6 7
5 7
4 5
2 5
1 5
dtype: int64
In [121]: pd.value_counts(data)
Out[121]:
0 11
3 10
6 7
5 7
4 5
2 5
1 5
dtype: int64
Аналогично, вы можете получить наиболее часто встречающиеся значение(я) (моду) значений в Series или DataFrame:
In [122]: s5 = pd.Series([1, 1, 3, 3, 3, 5, 5, 7, 7, 7])
In [123]: s5.mode()
Out[123]:
0 3
1 7
dtype: int64
In [124]: df5 = pd.DataFrame({"A": np.random.randint(0, 7, size=50),
.....: "B": np.random.randint(-10, 15, size=50)})
.....:
In [125]: df5.mode()
Out[125]:
A B
0 1 -5
Дискретизация и квантилирование
Непрерывные значения можно дискретизировать с помощью функций cut() (бины на основе значений) и qcut() (бины на основе выборочных квантилей):
In [126]: arr = np.random.randn(20) In [127]: factor = pd.cut(arr, 4) In [128]: factor Out[128]: [(-0.645, 0.336], (-2.61, -1.626], (-1.626, -0.645], (-1.626, -0.645], (-1.626, -0.645], ..., (0.336, 1.316], (0.336, 1.316], (0.336, 1.316], (0.336, 1.316], (-2.61, -1.626]] Length: 20 Categories (4, object): [(-2.61, -1.626] < (-1.626, -0.645] < (-0.645, 0.336] < (0.336, 1.316]] In [129]: factor = pd.cut(arr, [-5, -1, 0, 1, 5]) In [130]: factor Out[130]: [(-1, 0], (-5, -1], (-1, 0], (-5, -1], (-1, 0], ..., (0, 1], (1, 5], (0, 1], (0, 1], (-5, -1]] Length: 20 Categories (4, object): [(-5, -1] < (-1, 0] < (0, 1] < (1, 5]]
qcut() вычисляет выборочные квантили. Например, мы могли бы разбить некоторые данные, распределенные нормально, на равные четверти (квартили):
In [131]: arr = np.random.randn(30) In [132]: factor = pd.qcut(arr, [0, .25, .5, .75, 1]) In [133]: factor Out[133]: [(-0.139, 1.00736], (1.00736, 1.976], (1.00736, 1.976], [-1.0705, -0.439], [-1.0705, -0.439], ..., (1.00736, 1.976], [-1.0705, -0.439], (-0.439, -0.139], (-0.439, -0.139], (-0.439, -0.139]] Length: 30 Categories (4, object): [[-1.0705, -0.439] < (-0.439, -0.139] < (-0.139, 1.00736] < (1.00736, 1.976]] In [134]: pd.value_counts(factor) Out[134]: (1.00736, 1.976] 8 [-1.0705, -0.439] 8 (-0.139, 1.00736] 7 (-0.439, -0.139] 7 dtype: int64
Мы также можем передавать бесконечные значения для определения бинов:
In [135]: arr = np.random.randn(20) In [136]: factor = pd.cut(arr, [-np.inf, 0, np.inf]) In [137]: factor Out[137]: [(-inf, 0], (0, inf], (0, inf], (0, inf], (-inf, 0], ..., (-inf, 0], (0, inf], (-inf, 0], (-inf, 0], (0, inf]] Length: 20 Categories (2, object): [(-inf, 0] < (0, inf]]
Применение функций
Чтобы применить свои или функции из других библиотек к объектам pandas, вы должны знать три метода ниже. Подходящий метод зависит от того, ожидает ли ваша функция работать с целым DataFrame или Series, по строкам или столбцам, или поэлементно.
-
Применение функции к таблице:
pipe() -
Применение функции к строкам или столбцам:
apply() -
Элементное применение функции:
applymap()
Применение функции к таблице
Новое в версии 0.16.2.
DataFrames и Series конечно же, можно просто передать в функции. Однако, если функция должна вызываться в цепочке, рассмотрите использование метода pipe(). Сравните следующее
# f, g, and h are functions taking and returning ``DataFrames`` >>> f(g(h(df), arg1=1), arg2=2, arg3=3)
с эквивалентным
>>> (df.pipe(h)
.pipe(g, arg1=1)
.pipe(f, arg2=2, arg3=3)
)
Pandas рекомендует второй стиль, который известен как цепочка методов. pipe упрощает использование собственных функций или функций из других библиотек в цепочках методов вместе с методами pandas.
В примере выше, функции f, g, и h каждая ожидала DataFrame в качестве первого позиционного аргумента. А что, если функция, которую вы хотите применить, принимает свои данные, скажем, как второй аргумент? В этом случае, передайте pipe кортеж (callable, data_keyword). .pipe передаст DataFrame в указанный аргумент в кортеже.
Например, мы можем подогнать регрессию, используя statsmodels. Их API ожидает формулу первой и DataFrame как второй аргумент, data. Мы передаём функцию, пару ключ-значение (sm.poisson, 'data') в pipe:
In [138]: import statsmodels.formula.api as sm
In [139]: bb = pd.read_csv('data/baseball.csv', index_col='id')
In [140]: (bb.query('h > 0')
.....: .assign(ln_h = lambda df: np.log(df.h))
.....: .pipe((sm.poisson, 'data'), 'hr ~ ln_h + year + g + C(lg)')
.....: .fit()
.....: .summary()
.....: )
.....:
Optimization terminated successfully.
Current function value: 2.116284
Iterations 24
Out[140]:
<class 'statsmodels.iolib.summary.Summary'>
"""
Poisson Regression Results
==============================================================================
Dep. Variable: hr No. Observations: 68
Model: Poisson Df Residuals: 63
Method: MLE Df Model: 4
Date: Sat, 24 Dec 2016 Pseudo R-squ.: 0.6878
Time: 18:31:33 Log-Likelihood: -143.91
converged: True LL-Null: -460.91
LLR p-value: 6.774e-136
===============================================================================
coef std err z P>|z| [95.0% Conf. Int.]
-------------------------------------------------------------------------------
Intercept -1267.3636 457.867 -2.768 0.006 -2164.767 -369.960
C(lg)[T.NL] -0.2057 0.101 -2.044 0.041 -0.403 -0.008
ln_h 0.9280 0.191 4.866 0.000 0.554 1.302
year 0.6301 0.228 2.762 0.006 0.183 1.077
g 0.0099 0.004 2.754 0.006 0.003 0.017
===============================================================================
"""
Метод pipe вдохновлен unix-трубами и более поздними dplyr и magrittr, которые представили популярный (%>%) (читай труба) оператор для R. Реализация pipe здесь достаточно чистая и органично вписывается в python. Мы рекомендуем вам просмотреть исходный код (pd.DataFrame.pipe?? в IPython).
Применение функции к строкам или столбцам
Произвольные функции могут быть применены вдоль осей DataFrame или Panel с помощью метода apply(), который, как и методы описательной статистики, принимает необязательный axis аргумент:
In [141]: df.apply(np.mean)
Out[141]:
one -0.251274
three 0.469799
two -0.191421
dtype: float64
In [142]: df.apply(np.mean, axis=1)
Out[142]:
a -0.489066
b 0.273355
c 0.008348
d 0.011457
dtype: float64
In [143]: df.apply(lambda x: x.max() - x.min())
Out[143]:
one 0.638161
three 1.301762
two 2.237808
dtype: float64
In [144]: df.apply(np.cumsum)
Out[144]:
one three two
a -0.626544 NaN -0.351587
b -0.765438 -0.177289 0.784662
c -0.753821 0.284925 0.335874
d NaN 1.409398 -0.765684
In [145]: df.apply(np.exp)
Out[145]:
one three two
a 0.534436 NaN 0.703570
b 0.870320 0.837537 3.115063
c 1.011685 1.587586 0.638401
d NaN 3.078592 0.332353
В зависимости от возвращаемого типа функции, переданной в apply(), результат будет либо меньшей размерности, либо той же размерности.
apply() в сочетании с некоторыми хитростями может использоваться для ответа на многие вопросы о наборе данных. Например, предположим, что мы хотим извлечь дату, когда максимальное значение для каждого столбца произошло:
In [146]: tsdf = pd.DataFrame(np.random.randn(1000, 3), columns=['A', 'B', 'C'],
.....: index=pd.date_range('1/1/2000', periods=1000))
.....:
In [147]: tsdf.apply(lambda x: x.idxmax())
Out[147]:
A 2001-04-27
B 2002-06-02
C 2000-04-02
dtype: datetime64[ns]
Вы также можете передать дополнительные аргументы и ключевые аргументы в метод apply(). Например, рассмотрим следующую функцию, которую вы хотите применить:
def subtract_and_divide(x, sub, divide=1):
return (x - sub) / divide
Затем вы можете применить эту функцию следующим образом:
df.apply(subtract_and_divide, args=(5,), divide=3)
Еще одна полезная функция — возможность передавать методы Series для выполнения некоторых операций Series для каждого столбца или строки:
In [148]: tsdf
Out[148]:
A B C
2000-01-01 1.796883 -0.930690 3.542846
2000-01-02 -1.242888 -0.695279 -1.000884
2000-01-03 -0.720299 0.546303 -0.082042
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 -0.527402 0.933507 0.129646
2000-01-09 -0.338903 -1.265452 -1.969004
2000-01-10 0.532566 0.341548 0.150493
In [149]: tsdf.apply(pd.Series.interpolate)
Out[149]:
A B C
2000-01-01 1.796883 -0.930690 3.542846
2000-01-02 -1.242888 -0.695279 -1.000884
2000-01-03 -0.720299 0.546303 -0.082042
2000-01-04 -0.681720 0.623743 -0.039704
2000-01-05 -0.643140 0.701184 0.002633
2000-01-06 -0.604561 0.778625 0.044971
2000-01-07 -0.565982 0.856066 0.087309
2000-01-08 -0.527402 0.933507 0.129646
2000-01-09 -0.338903 -1.265452 -1.969004
2000-01-10 0.532566 0.341548 0.150493
Наконец, apply() принимает аргумент raw, который по умолчанию равен False, что преобразует каждую строку или столбец в Series перед применением функции. При значении True переданная функция вместо этого получит объект ndarray, что положительно сказывается на производительности, если вам не нужна функциональность индексирования.
См. также
Раздел GroupBy демонстрирует связанные, гибкие функции для группировки по определенному критерию, применения и объединения результатов в Series, DataFrame и т. д.
Применение элементных Python-функций
Поскольку не все функции могут быть векторизованы (принимать массивы NumPy и возвращать другой массив или значение), методы applymap() в DataFrame и аналогично map() в Series принимают любую Python-функцию, принимающую одно значение и возвращающую одно значение. Например:
In [150]: df4
Out[150]:
one three two
a -0.626544 NaN -0.351587
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
d NaN 1.124472 -1.101558
In [151]: f = lambda x: len(str(x))
In [152]: df4['one'].map(f)
Out[152]:
a 14
b 15
c 15
d 3
Name: one, dtype: int64
In [153]: df4.applymap(f)
Out[153]:
one three two
a 14 3 15
b 15 15 11
c 15 14 15
d 3 13 14
Series.map() обладает дополнительной возможностью, позволяющей легко «связывать» или «отображать» значения, определённые дополнительной серией. Это тесно связано с функциональностью слияния/соединения:
In [154]: s = pd.Series(['six', 'seven', 'six', 'seven', 'six'],
.....: index=['a', 'b', 'c', 'd', 'e'])
.....:
In [155]: t = pd.Series({'six' : 6., 'seven' : 7.})
In [156]: s
Out[156]:
a six
b seven
c six
d seven
e six
dtype: object
In [157]: s.map(t)
Out[157]:
a 6.0
b 7.0
c 6.0
d 7.0
e 6.0
dtype: float64
Применение с Panel
Применение с Panel передаст Series в применённую функцию. Если применённая функция возвращает Series, результат применения будет Panel. Если применённая функция сводится к скаляру, результат применения будет DataFrame.
Примечание
До версии 0.13.1 apply на Panel работало только с ufuncs (например, np.sum/np.max).
In [158]: import pandas.util.testing as tm
In [159]: panel = tm.makePanel(5)
In [160]: panel
Out[160]:
<class 'pandas.core.panel.Panel'>
Dimensions: 3 (items) x 5 (major_axis) x 4 (minor_axis)
Items axis: ItemA to ItemC
Major_axis axis: 2000-01-03 00:00:00 to 2000-01-07 00:00:00
Minor_axis axis: A to D
In [161]: panel['ItemA']
Out[161]:
A B C D
2000-01-03 0.330418 1.893177 0.801111 0.528154
2000-01-04 1.761200 0.170247 0.445614 -0.029371
2000-01-05 0.567133 -0.916844 1.453046 -0.631117
2000-01-06 -0.251020 0.835024 2.430373 -0.172441
2000-01-07 1.020099 1.259919 0.653093 -1.020485
Трансформационное применение.
In [162]: result = panel.apply(lambda x: x*2, axis='items')
In [163]: result
Out[163]:
<class 'pandas.core.panel.Panel'>
Dimensions: 3 (items) x 5 (major_axis) x 4 (minor_axis)
Items axis: ItemA to ItemC
Major_axis axis: 2000-01-03 00:00:00 to 2000-01-07 00:00:00
Minor_axis axis: A to D
In [164]: result['ItemA']
Out[164]:
A B C D
2000-01-03 0.660836 3.786354 1.602222 1.056308
2000-01-04 3.522400 0.340494 0.891228 -0.058742
2000-01-05 1.134266 -1.833689 2.906092 -1.262234
2000-01-06 -0.502039 1.670047 4.860747 -0.344882
2000-01-07 2.040199 2.519838 1.306185 -2.040969
Операция сокращения.
In [165]: panel.apply(lambda x: x.dtype, axis='items')
Out[165]:
A B C D
2000-01-03 float64 float64 float64 float64
2000-01-04 float64 float64 float64 float64
2000-01-05 float64 float64 float64 float64
2000-01-06 float64 float64 float64 float64
2000-01-07 float64 float64 float64 float64
Аналогичная операция сокращения.
In [166]: panel.apply(lambda x: x.sum(), axis='major_axis')
Out[166]:
ItemA ItemB ItemC
A 3.427831 -2.581431 0.840809
B 3.241522 -1.409935 -1.114512
C 5.783237 0.319672 -0.431906
D -1.325260 -2.914834 0.857043
Это последнее сокращение эквивалентно
In [167]: panel.sum('major_axis')
Out[167]:
ItemA ItemB ItemC
A 3.427831 -2.581431 0.840809
B 3.241522 -1.409935 -1.114512
C 5.783237 0.319672 -0.431906
D -1.325260 -2.914834 0.857043
Трансформационная операция, возвращающая Panel, но вычисляющая z-оценку по major_axis.
In [168]: result = panel.apply(
.....: lambda x: (x-x.mean())/x.std(),
.....: axis='major_axis')
.....:
In [169]: result
Out[169]:
<class 'pandas.core.panel.Panel'>
Dimensions: 3 (items) x 5 (major_axis) x 4 (minor_axis)
Items axis: ItemA to ItemC
Major_axis axis: 2000-01-03 00:00:00 to 2000-01-07 00:00:00
Minor_axis axis: A to D
In [170]: result['ItemA']
Out[170]:
A B C D
2000-01-03 -0.469761 1.156225 -0.441347 1.341731
2000-01-04 1.422763 -0.444015 -0.882647 0.398661
2000-01-05 -0.156654 -1.453694 0.367936 -0.619210
2000-01-06 -1.238841 0.173423 1.581149 0.156654
2000-01-07 0.442494 0.568061 -0.625091 -1.277837
Apply также может принимать несколько осей в аргументе axis. Это передаст DataFrame поперечного сечения в применённую функцию.
In [171]: f = lambda x: ((x.T-x.mean(1))/x.std(1)).T
In [172]: result = panel.apply(f, axis = ['items','major_axis'])
In [173]: result
Out[173]:
<class 'pandas.core.panel.Panel'>
Dimensions: 4 (items) x 5 (major_axis) x 3 (minor_axis)
Items axis: A to D
Major_axis axis: 2000-01-03 00:00:00 to 2000-01-07 00:00:00
Minor_axis axis: ItemA to ItemC
In [174]: result.loc[:,:,'ItemA']
Out[174]:
A B C D
2000-01-03 0.864236 1.132969 0.557316 0.575106
2000-01-04 0.795745 0.652527 0.534808 -0.070674
2000-01-05 -0.310864 0.558627 1.086688 -1.051477
2000-01-06 -0.001065 0.832460 0.846006 0.043602
2000-01-07 1.128946 1.152469 -0.218186 -0.891680
Это эквивалентно следующему
In [175]: result = pd.Panel(dict([ (ax, f(panel.loc[:,:,ax]))
.....: for ax in panel.minor_axis ]))
.....:
In [176]: result
Out[176]:
<class 'pandas.core.panel.Panel'>
Dimensions: 4 (items) x 5 (major_axis) x 3 (minor_axis)
Items axis: A to D
Major_axis axis: 2000-01-03 00:00:00 to 2000-01-07 00:00:00
Minor_axis axis: ItemA to ItemC
In [177]: result.loc[:,:,'ItemA']
Out[177]:
A B C D
2000-01-03 0.864236 1.132969 0.557316 0.575106
2000-01-04 0.795745 0.652527 0.534808 -0.070674
2000-01-05 -0.310864 0.558627 1.086688 -1.051477
2000-01-06 -0.001065 0.832460 0.846006 0.043602
2000-01-07 1.128946 1.152469 -0.218186 -0.891680
Переиндексация и изменение меток
reindex() — это фундаментальный метод выравнивания данных в pandas. Он используется для реализации почти всех других функций, использующих функциональность выравнивания по меткам. Переиндексация означает приведение данных к соответствию заданному набору меток по определённой оси. Это выполняет несколько вещей:
- Переупорядочивает существующие данные в соответствии с новым набором меток
- Вставляет маркеры пропущенных значений (NA) в позиции меток, где не было данных для этой метки
- При необходимости, заполняет данные для пропущенных меток с помощью логики (очень важно при работе с временными рядами)
Вот простой пример:
In [178]: s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e']) In [179]: s Out[179]: a -1.010924 b -0.672504 c -1.139222 d 0.354653 e 0.563622 dtype: float64 In [180]: s.reindex(['e', 'b', 'f', 'd']) Out[180]: e 0.563622 b -0.672504 f NaN d 0.354653 dtype: float64
Здесь метка f не содержалась в Series и поэтому появляется как NaN в результате.
В DataFrame вы можете одновременно переиндексировать индекс и столбцы:
In [181]: df
Out[181]:
one three two
a -0.626544 NaN -0.351587
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
d NaN 1.124472 -1.101558
In [182]: df.reindex(index=['c', 'f', 'b'], columns=['three', 'two', 'one'])
Out[182]:
three two one
c 0.462215 -0.448789 0.011617
f NaN NaN NaN
b -0.177289 1.136249 -0.138894
Для удобства вы можете использовать метод reindex_axis(), который принимает метки и ключевой axis параметр.
Обратите внимание, что объекты Index содержащие фактические метки осей, могут быть **общими** между объектами. Итак, если у нас есть Series и DataFrame, можно сделать следующее:
In [183]: rs = s.reindex(df.index) In [184]: rs Out[184]: a -1.010924 b -0.672504 c -1.139222 d 0.354653 dtype: float64 In [185]: rs.index is df.index Out[185]: True
Это означает, что индекс переиндексированного Series — это тот же объект Python, что и индекс DataFrame.
См. также
MultiIndex/Расширенная индексация — ещё более компактный способ переиндексации.
Примечание
При написании производительного кода есть веская причина уделить время изучению переиндексации: многие операции быстрее на предварительно выровненных данных. Добавление двух невыровненных DataFrame внутри приводит к шагу переиндексации. При исследовательском анализе вы едва заметите разницу (потому что reindex был сильно оптимизирован), но когда счёт идёт на секунды, добавление нескольких явных вызовов reindex здесь и там может иметь влияние.
Переиндексация для выравнивания с другим объектом
Вы можете захотеть взять объект и переиндексировать его оси так, чтобы они были помечены одинаково как у другого объекта. Хотя синтаксис для этого прост, но громоздкий, эта операция достаточно распространена, и метод reindex_like() доступен для упрощения этой задачи:
In [186]: df2
Out[186]:
one two
a -0.626544 -0.351587
b -0.138894 1.136249
c 0.011617 -0.448789
In [187]: df3
Out[187]:
one two
a -0.375270 -0.463545
b 0.112379 1.024292
c 0.262891 -0.560746
In [188]: df.reindex_like(df2)
Out[188]:
one two
a -0.626544 -0.351587
b -0.138894 1.136249
c 0.011617 -0.448789
Выравнивание объектов друг с другом с помощью align
Метод align() — самый быстрый способ одновременного выравнивания двух объектов. Он поддерживает аргумент join (связанный с соединением и слиянием):
-
join='outer': взять объединение индексов (по умолчанию) -
join='left': использовать индекс вызываемого объекта -
join='right': использовать индекс переданного объекта -
join='inner': пересечь индексы
Возвращает кортеж с обеими переиндексированными Series:
In [189]: s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e']) In [190]: s1 = s[:4] In [191]: s2 = s[1:] In [192]: s1.align(s2) Out[192]: (a -0.365106 b 1.092702 c -1.481449 d 1.781190 e NaN dtype: float64, a NaN b 1.092702 c -1.481449 d 1.781190 e -0.031543 dtype: float64) In [193]: s1.align(s2, join='inner') Out[193]: (b 1.092702 c -1.481449 d 1.781190 dtype: float64, b 1.092702 c -1.481449 d 1.781190 dtype: float64) In [194]: s1.align(s2, join='left') Out[194]: (a -0.365106 b 1.092702 c -1.481449 d 1.781190 dtype: float64, a NaN b 1.092702 c -1.481449 d 1.781190 dtype: float64)
Для DataFrames, метод join по умолчанию применяется как к индексу, так и к столбцам:
In [195]: df.align(df2, join='inner') Out[195]: ( one two a -0.626544 -0.351587 b -0.138894 1.136249 c 0.011617 -0.448789, one two a -0.626544 -0.351587 b -0.138894 1.136249 c 0.011617 -0.448789)
Вы также можете передать axis опцию, чтобы выровнять только по указанной оси:
In [196]: df.align(df2, join='inner', axis=0) Out[196]: ( one three two a -0.626544 NaN -0.351587 b -0.138894 -0.177289 1.136249 c 0.011617 0.462215 -0.448789, one two a -0.626544 -0.351587 b -0.138894 1.136249 c 0.011617 -0.448789)
Если вы передадите Series в DataFrame.align(), вы можете выбрать выравнивание обоих объектов по индексу DataFrame или столбцам, используя параметр axis:
In [197]: df.align(df2.ix[0], axis=1) Out[197]: ( one three two a -0.626544 NaN -0.351587 b -0.138894 -0.177289 1.136249 c 0.011617 0.462215 -0.448789 d NaN 1.124472 -1.101558, one -0.626544 three NaN two -0.351587 Name: a, dtype: float64)
Заполнение при переиндексации
reindex() принимает необязательный параметр method, который представляет собой метод заполнения, выбранный из следующей таблицы:
| Метод | Действие |
|---|---|
| pad / ffill | Заполнить значения вперёд |
| bfill / backfill | Заполнить значения назад |
| nearest | Заполнить из ближайшего значения индекса |
Мы проиллюстрируем эти методы заполнения на простом Series:
In [198]: rng = pd.date_range('1/3/2000', periods=8)
In [199]: ts = pd.Series(np.random.randn(8), index=rng)
In [200]: ts2 = ts[[0, 3, 6]]
In [201]: ts
Out[201]:
2000-01-03 0.480993
2000-01-04 0.604244
2000-01-05 -0.487265
2000-01-06 1.990533
2000-01-07 0.327007
2000-01-08 1.053639
2000-01-09 -2.927808
2000-01-10 0.082065
Freq: D, dtype: float64
In [202]: ts2
Out[202]:
2000-01-03 0.480993
2000-01-06 1.990533
2000-01-09 -2.927808
dtype: float64
In [203]: ts2.reindex(ts.index)
Out[203]:
2000-01-03 0.480993
2000-01-04 NaN
2000-01-05 NaN
2000-01-06 1.990533
2000-01-07 NaN
2000-01-08 NaN
2000-01-09 -2.927808
2000-01-10 NaN
Freq: D, dtype: float64
In [204]: ts2.reindex(ts.index, method='ffill')
Out[204]:
2000-01-03 0.480993
2000-01-04 0.480993
2000-01-05 0.480993
2000-01-06 1.990533
2000-01-07 1.990533
2000-01-08 1.990533
2000-01-09 -2.927808
2000-01-10 -2.927808
Freq: D, dtype: float64
In [205]: ts2.reindex(ts.index, method='bfill')
Out[205]:
2000-01-03 0.480993
2000-01-04 1.990533
2000-01-05 1.990533
2000-01-06 1.990533
2000-01-07 -2.927808
2000-01-08 -2.927808
2000-01-09 -2.927808
2000-01-10 NaN
Freq: D, dtype: float64
In [206]: ts2.reindex(ts.index, method='nearest')
Out[206]:
2000-01-03 0.480993
2000-01-04 0.480993
2000-01-05 1.990533
2000-01-06 1.990533
2000-01-07 1.990533
2000-01-08 -2.927808
2000-01-09 -2.927808
2000-01-10 -2.927808
Freq: D, dtype: float64
Эти методы требуют, чтобы индексы были упорядочены, в порядке возрастания или убывания.
Обратите внимание, что тот же результат можно было бы получить, используя fillna (кроме method='nearest') или interpolate:
In [207]: ts2.reindex(ts.index).fillna(method='ffill') Out[207]: 2000-01-03 0.480993 2000-01-04 0.480993 2000-01-05 0.480993 2000-01-06 1.990533 2000-01-07 1.990533 2000-01-08 1.990533 2000-01-09 -2.927808 2000-01-10 -2.927808 Freq: D, dtype: float64
reindex() сгенерирует ValueError, если индекс не является монотонно возрастающим или убывающим. fillna() и interpolate() не проверяют порядок индекса.
Ограничения на заполнение при переиндексации
Аргументы limit и tolerance обеспечивают дополнительный контроль над заполнением при переиндексации. Limit определяет максимальное количество последовательных совпадений:
In [208]: ts2.reindex(ts.index, method='ffill', limit=1) Out[208]: 2000-01-03 0.480993 2000-01-04 0.480993 2000-01-05 NaN 2000-01-06 1.990533 2000-01-07 1.990533 2000-01-08 NaN 2000-01-09 -2.927808 2000-01-10 -2.927808 Freq: D, dtype: float64
В отличие от этого, tolerance определяет максимальное расстояние между значениями индекса и индексатора:
In [209]: ts2.reindex(ts.index, method='ffill', tolerance='1 day') Out[209]: 2000-01-03 0.480993 2000-01-04 0.480993 2000-01-05 NaN 2000-01-06 1.990533 2000-01-07 1.990533 2000-01-08 NaN 2000-01-09 -2.927808 2000-01-10 -2.927808 Freq: D, dtype: float64
Обратите внимание, что при использовании с DatetimeIndex, TimedeltaIndex или PeriodIndex, tolerance будет приведено к Timedelta, если это возможно. Это позволяет указать tolerance с соответствующими строками.
Удаление меток с оси
Метод, тесно связанный с reindex это функция drop(). Она удаляет набор меток с оси:
In [210]: df
Out[210]:
one three two
a -0.626544 NaN -0.351587
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
d NaN 1.124472 -1.101558
In [211]: df.drop(['a', 'd'], axis=0)
Out[211]:
one three two
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
In [212]: df.drop(['one'], axis=1)
Out[212]:
three two
a NaN -0.351587
b -0.177289 1.136249
c 0.462215 -0.448789
d 1.124472 -1.101558
Обратите внимание, что следующее также работает, но немного менее очевидно / чисто:
In [213]: df.reindex(df.index.difference(['a', 'd']))
Out[213]:
one three two
b -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
Переименование/сопоставление меток
Метод rename() позволяет переименовать ось на основе некоторого отображения (словарь или Series) или произвольной функции.
In [214]: s Out[214]: a -0.365106 b 1.092702 c -1.481449 d 1.781190 e -0.031543 dtype: float64 In [215]: s.rename(str.upper) Out[215]: A -0.365106 B 1.092702 C -1.481449 D 1.781190 E -0.031543 dtype: float64
Если вы передаёте функцию, она должна возвращать значение при вызове с любой из меток (и должна создавать набор уникальных значений). Также можно использовать словарь или Series:
In [216]: df.rename(columns={'one' : 'foo', 'two' : 'bar'},
.....: index={'a' : 'apple', 'b' : 'banana', 'd' : 'durian'})
.....:
Out[216]:
foo three bar
apple -0.626544 NaN -0.351587
banana -0.138894 -0.177289 1.136249
c 0.011617 0.462215 -0.448789
durian NaN 1.124472 -1.101558
Если отображение не включает метку столбца/индекса, она не переименовывается. Дополнительные метки в отображении также не вызывают ошибку.
Метод rename() также предоставляет параметр inplace, который по умолчанию False и копирует исходные данные. Передайте inplace=True для переименования данных на месте.
Новое в версии 0.18.0.
Наконец, rename() также принимает скаляр или список для изменения атрибута Series.name.
In [217]: s.rename("scalar-name")
Out[217]:
a -0.365106
b 1.092702
c -1.481449
d 1.781190
e -0.031543
Name: scalar-name, dtype: float64
Класс Panel имеет связанный класс rename_axis(), который может переименовать любую из его трёх осей.
Итерация
Поведение базовой итерации по объектам pandas зависит от типа. При итерации по Series, она рассматривается как массивоподобный объект, и базовая итерация возвращает значения. Другие структуры данных, такие как DataFrame и Panel, следуют соглашению итерации по типу «словарь», перебирая «ключи» объектов.
Короче говоря, базовая итерация (for i in object) возвращает:
- Series: значения
- DataFrame: метки столбцов
- Panel: метки элементов
Таким образом, например, итерация по DataFrame даёт вам имена столбцов:
In [218]: df = pd.DataFrame({'col1' : np.random.randn(3), 'col2' : np.random.randn(3)},
.....: index=['a', 'b', 'c'])
.....:
In [219]: for col in df:
.....: print(col)
.....:
col1
col2
Объекты Pandas также имеют метод iteritems() (подобно словарю), для итерации по парам «ключ-значение».
Чтобы пройтись по строкам DataFrame, можно использовать следующие методы:
-
iterrows(): Итерирование по строкам DataFrame как пары (индекс, Series). Это преобразует строки в объекты Series, что может изменить типы данных и имеет некоторые последствия для производительности. -
itertuples(): Итерирование по строкам DataFrame как namedtuple значений. Это намного быстрее, чемiterrows(), и во многих случаях предпочтительнее для итерации по значениям DataFrame.
Предупреждение
Итерация по объектам pandas, как правило, медленная. Во многих случаях ручная итерация по строкам не нужна и может быть избегнута с помощью одного из следующих подходов:
- Поиск векторизованного решения: многие операции могут быть выполнены с помощью встроенных методов или функций numpy, (булевых) индексов и т. д.
- Если у вас есть функция, которая не может работать с полным DataFrame/Series за один раз, лучше использовать
apply()вместо итерации по значениям. См. документацию по применению функций. - Если вам нужно выполнять итерационные манипуляции со значениями, но производительность важна, рассмотрите возможность написания внутреннего цикла с использованием, например, cython или numba. См. раздел улучшение производительности для некоторых примеров такого подхода.
Предупреждение
Вы никогда не должны изменять то, по чему вы итерируетесь. Это не гарантируется, что будет работать во всех случаях. В зависимости от типов данных итератор возвращает копию, а не представление, и запись в неё не повлияет!
Например, в следующем случае установка значения не имеет никакого эффекта:
In [220]: df = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})
In [221]: for index, row in df.iterrows():
.....: row['a'] = 10
.....:
In [222]: df
Out[222]:
a b
0 1 a
1 2 b
2 3 c
iteritems
В соответствии с интерфейсом типа «словарь», iteritems() итерируется по парам «ключ-значение»:
- Series: пары (индекс, скалярное значение)
- DataFrame: пары (столбец, Series)
- Panel: пары (элемент, DataFrame)
Например:
In [223]: for item, frame in wp.iteritems():
.....: print(item)
.....: print(frame)
.....:
Item1
A B C D
2000-01-01 -1.032011 0.969818 -0.962723 1.382083
2000-01-02 -0.938794 0.669142 -0.433567 -0.273610
2000-01-03 0.680433 -0.308450 -0.276099 -1.821168
2000-01-04 -1.993606 -1.927385 -2.027924 1.624972
2000-01-05 0.551135 3.059267 0.455264 -0.030740
Item2
A B C D
2000-01-01 0.935716 1.061192 -2.107852 0.199905
2000-01-02 0.323586 -0.641630 -0.587514 0.053897
2000-01-03 0.194889 -0.381994 0.318587 2.089075
2000-01-04 -0.728293 -0.090255 -0.748199 1.318931
2000-01-05 -2.029766 0.792652 0.461007 -0.542749
iterrows
iterrows() позволяет проходить по строкам DataFrame как объектам Series. Он возвращает итератор, возвращающий каждое значение индекса вместе со Series, содержащей данные в каждой строке:
In [224]: for row_index, row in df.iterrows():
.....: print('%s\n%s' % (row_index, row))
.....:
0
a 1
b a
Name: 0, dtype: object
1
a 2
b b
Name: 1, dtype: object
2
a 3
b c
Name: 2, dtype: object
Примечание
Поскольку iterrows() возвращает Series для каждой строки, он не сохраняет типы данных между строками (типы данных сохраняются между столбцами для DataFrame). Например,
In [225]: df_orig = pd.DataFrame([[1, 1.5]], columns=['int', 'float']) In [226]: df_orig.dtypes Out[226]: int int64 float float64 dtype: object In [227]: row = next(df_orig.iterrows())[1] In [228]: row Out[228]: int 1.0 float 1.5 Name: 0, dtype: float64
Все значения в row, возвращаемые как Series, теперь повышаются до float, а также исходное целочисленное значение в столбце x:
In [229]: row['int'].dtype
Out[229]: dtype('float64')
In [230]: df_orig['int'].dtype
Out[230]: dtype('int64')
Для сохранения типов данных при итерации по строкам лучше использовать itertuples(), который возвращает namedtuple значений и который, как правило, намного быстрее, чем iterrows.
Например, выдуманный способ транспонирования DataFrame:
In [231]: df2 = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})
In [232]: print(df2)
x y
0 1 4
1 2 5
2 3 6
In [233]: print(df2.T)
0 1 2
x 1 2 3
y 4 5 6
In [234]: df2_t = pd.DataFrame(dict((idx,values) for idx, values in df2.iterrows()))
In [235]: print(df2_t)
0 1 2
x 1 2 3
y 4 5 6
itertuples
Метод itertuples() вернёт итератор, возвращающий namedtuple для каждой строки в DataFrame. Первый элемент кортежа будет соответствующим значением индекса строки, а оставшиеся значения — значениями строки.
Например,
In [236]: for row in df.itertuples(): .....: print(row) .....: Pandas(Index=0, a=1, b='a') Pandas(Index=1, a=2, b='b') Pandas(Index=2, a=3, b='c')
Этот метод не преобразует строку в объект Series, а только возвращает значения внутри namedtuple. Поэтому, itertuples() сохраняет тип данных значений и, как правило, быстрее, чем iterrows().
Примечание
Имена столбцов будут переименованы в позиционные имена, если они являются недопустимыми идентификаторами Python, повторяющимися или начинаются с подчеркивания. При большом количестве столбцов (>255) возвращаются обычные кортежи.
.dt аксессор
Series имеет аксессор для краткого возврата свойств типа datetime для *значений* Series, если это Series типа datetime/period. Это вернет Series, индексированную так же, как и существующая Series.
# datetime
In [237]: s = pd.Series(pd.date_range('20130101 09:10:12', periods=4))
In [238]: s
Out[238]:
0 2013-01-01 09:10:12
1 2013-01-02 09:10:12
2 2013-01-03 09:10:12
3 2013-01-04 09:10:12
dtype: datetime64[ns]
In [239]: s.dt.hour
Out[239]:
0 9
1 9
2 9
3 9
dtype: int64
In [240]: s.dt.second
Out[240]:
0 12
1 12
2 12
3 12
dtype: int64
In [241]: s.dt.day
Out[241]:
0 1
1 2
2 3
3 4
dtype: int64
Это позволяет создавать выражения, подобные этому:
In [242]: s[s.dt.day==2] Out[242]: 1 2013-01-02 09:10:12 dtype: datetime64[ns]
Вы можете легко создавать преобразования, учитывающие часовой пояс:
In [243]: stz = s.dt.tz_localize('US/Eastern')
In [244]: stz
Out[244]:
0 2013-01-01 09:10:12-05:00
1 2013-01-02 09:10:12-05:00
2 2013-01-03 09:10:12-05:00
3 2013-01-04 09:10:12-05:00
dtype: datetime64[ns, US/Eastern]
In [245]: stz.dt.tz
Out[245]: <DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>
Вы также можете объединять эти типы операций:
In [246]: s.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
Out[246]:
0 2013-01-01 04:10:12-05:00
1 2013-01-02 04:10:12-05:00
2 2013-01-03 04:10:12-05:00
3 2013-01-04 04:10:12-05:00
dtype: datetime64[ns, US/Eastern]
Вы также можете форматировать значения datetime как строки с помощью Series.dt.strftime(), который поддерживает тот же формат, что и стандартный strftime().
# DatetimeIndex
In [247]: s = pd.Series(pd.date_range('20130101', periods=4))
In [248]: s
Out[248]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
3 2013-01-04
dtype: datetime64[ns]
In [249]: s.dt.strftime('%Y/%m/%d')
Out[249]:
0 2013/01/01
1 2013/01/02
2 2013/01/03
3 2013/01/04
dtype: object
# PeriodIndex
In [250]: s = pd.Series(pd.period_range('20130101', periods=4))
In [251]: s
Out[251]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
3 2013-01-04
dtype: object
In [252]: s.dt.strftime('%Y/%m/%d')
Out[252]:
0 2013/01/01
1 2013/01/02
2 2013/01/03
3 2013/01/04
dtype: object
Аксессор .dt работает для типов period и timedelta.
# period
In [253]: s = pd.Series(pd.period_range('20130101', periods=4, freq='D'))
In [254]: s
Out[254]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
3 2013-01-04
dtype: object
In [255]: s.dt.year
Out[255]:
0 2013
1 2013
2 2013
3 2013
dtype: int64
In [256]: s.dt.day
Out[256]:
0 1
1 2
2 3
3 4
dtype: int64
# timedelta
In [257]: s = pd.Series(pd.timedelta_range('1 day 00:00:05', periods=4, freq='s'))
In [258]: s
Out[258]:
0 1 days 00:00:05
1 1 days 00:00:06
2 1 days 00:00:07
3 1 days 00:00:08
dtype: timedelta64[ns]
In [259]: s.dt.days
Out[259]:
0 1
1 1
2 1
3 1
dtype: int64
In [260]: s.dt.seconds
Out[260]:
0 5
1 6
2 7
3 8
dtype: int64
In [261]: s.dt.components
Out[261]:
days hours minutes seconds milliseconds microseconds nanoseconds
0 1 0 0 5 0 0 0
1 1 0 0 6 0 0 0
2 1 0 0 7 0 0 0
3 1 0 0 8 0 0 0
Примечание
Series.dt вызовет TypeError при доступе к значениям, не являющимся datetimelike.
Векторизованные методы строк
Series оснащены набором методов обработки строк, которые упрощают работу с каждым элементом массива. Возможно, самое важное, что эти методы автоматически исключают отсутствующие/NA значения. К ним можно получить доступ через атрибут str Series, и у них, как правило, есть имена, соответствующие эквивалентным (скалярным) встроенным методам строк. Например:
In [262]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat']) In [263]: s.str.lower() Out[263]: 0 a 1 b 2 c 3 aaba 4 baca 5 NaN 6 caba 7 dog 8 cat dtype: object
Также предоставляются мощные методы сопоставления с образцом, но обратите внимание, что сопоставление с образцом по умолчанию использует регулярные выражения (regular expressions) (и в некоторых случаях всегда их использует).
Полное описание см. в разделе Векторизованные методы строк.
Сортировка
Предупреждение
API сортировки существенно изменен в версии 0.17.0, см. здесь для этих изменений. В частности, все методы сортировки теперь по умолчанию возвращают новый объект и **НЕ** работают на месте (за исключением передачи inplace=True).
Существует два очевидных типа сортировки, которые могут вас заинтересовать: сортировка по меткам и сортировка по фактическим значениям.
По индексу
Основными методами сортировки меток осей (индексов) являются Series.sort_index() и DataFrame.sort_index() методы.
In [264]: unsorted_df = df.reindex(index=['a', 'd', 'c', 'b'], .....: columns=['three', 'two', 'one']) .....: # DataFrame In [265]: unsorted_df.sort_index() Out[265]: three two one a NaN NaN NaN b NaN NaN NaN c NaN NaN NaN d NaN NaN NaN In [266]: unsorted_df.sort_index(ascending=False) Out[266]: three two one d NaN NaN NaN c NaN NaN NaN b NaN NaN NaN a NaN NaN NaN In [267]: unsorted_df.sort_index(axis=1) Out[267]: one three two a NaN NaN NaN d NaN NaN NaN c NaN NaN NaN b NaN NaN NaN # Series In [268]: unsorted_df['three'].sort_index() Out[268]: a NaN b NaN c NaN d NaN Name: three, dtype: float64
По значениям
Методы Series.sort_values() и DataFrame.sort_values() являются точками входа для сортировки по значениям (то есть значения в столбце или строке). DataFrame.sort_values() может принимать необязательный аргумент by для axis=0, который будет использовать произвольный вектор или имя столбца DataFrame для определения порядка сортировки:
In [269]: df1 = pd.DataFrame({'one':[2,1,1,1],'two':[1,3,2,4],'three':[5,4,3,2]})
In [270]: df1.sort_values(by='two')
Out[270]:
one three two
0 2 5 1
2 1 3 2
1 1 4 3
3 1 2 4
Аргумент by может принимать список имен столбцов, например:
In [271]: df1[['one', 'two', 'three']].sort_values(by=['one','two']) Out[271]: one two three 2 1 2 3 1 1 3 4 3 1 4 2 0 2 1 5
Эти методы имеют специальное обращение с NA-значениями с помощью аргумента na_position:
In [272]: s[2] = np.nan In [273]: s.sort_values() Out[273]: 0 A 3 Aaba 1 B 4 Baca 6 CABA 8 cat 7 dog 2 NaN 5 NaN dtype: object In [274]: s.sort_values(na_position='first') Out[274]: 2 NaN 5 NaN 0 A 3 Aaba 1 B 4 Baca 6 CABA 8 cat 7 dog dtype: object
searchsorted
Series имеет метод searchsorted(), который работает аналогично numpy.ndarray.searchsorted().
In [275]: ser = pd.Series([1, 2, 3]) In [276]: ser.searchsorted([0, 3]) Out[276]: array([0, 2]) In [277]: ser.searchsorted([0, 4]) Out[277]: array([0, 3]) In [278]: ser.searchsorted([1, 3], side='right') Out[278]: array([1, 3]) In [279]: ser.searchsorted([1, 3], side='left') Out[279]: array([0, 2]) In [280]: ser = pd.Series([3, 1, 2]) In [281]: ser.searchsorted([0, 3], sorter=np.argsort(ser)) Out[281]: array([0, 2])
наименьшие / наибольшие значения
Введено в версии 0.14.0.
Series имеет методы nsmallest() и nlargest(), которые возвращают наименьшие или наибольшие n значений. Для большого Series это может быть намного быстрее, чем сортировка всего Series и вызов head(n) на результате.
In [282]: s = pd.Series(np.random.permutation(10)) In [283]: s Out[283]: 0 9 1 8 2 5 3 3 4 6 5 7 6 0 7 2 8 4 9 1 dtype: int64 In [284]: s.sort_values() Out[284]: 6 0 9 1 7 2 3 3 8 4 2 5 4 6 5 7 1 8 0 9 dtype: int64 In [285]: s.nsmallest(3) Out[285]: 6 0 9 1 7 2 dtype: int64 In [286]: s.nlargest(3) Out[286]: 0 9 1 8 5 7 dtype: int64
Введено в версии 0.17.0.
DataFrame также имеет методы nlargest и nsmallest.
In [287]: df = pd.DataFrame({'a': [-2, -1, 1, 10, 8, 11, -1],
.....: 'b': list('abdceff'),
.....: 'c': [1.0, 2.0, 4.0, 3.2, np.nan, 3.0, 4.0]})
.....:
In [288]: df.nlargest(3, 'a')
Out[288]:
a b c
5 11 f 3.0
3 10 c 3.2
4 8 e NaN
In [289]: df.nlargest(5, ['a', 'c'])
Out[289]:
a b c
5 11 f 3.0
3 10 c 3.2
4 8 e NaN
2 1 d 4.0
1 -1 b 2.0
6 -1 f 4.0
In [290]: df.nsmallest(3, 'a')
Out[290]:
a b c
0 -2 a 1.0
1 -1 b 2.0
6 -1 f 4.0
1 -1 b 2.0
6 -1 f 4.0
In [291]: df.nsmallest(5, ['a', 'c'])
Out[291]:
a b c
0 -2 a 1.0
1 -1 b 2.0
6 -1 f 4.0
1 -1 b 2.0
6 -1 f 4.0
2 1 d 4.0
4 8 e NaN
Сортировка по столбцу с многоуровневым индексом
При сортировке по столбцу с многоуровневым индексом необходимо явно указать сортировку и полностью указать все уровни для by.
In [292]: df1.columns = pd.MultiIndex.from_tuples([('a','one'),('a','two'),('b','three')])
In [293]: df1.sort_values(by=('a','two'))
Out[293]:
a b
one two three
3 1 2 4
2 1 3 2
1 1 4 3
0 2 5 1
Копирование
Метод copy() для объектов pandas копирует исходные данные (хотя не индексы осей, поскольку они неизменяемы) и возвращает новый объект. Обратите внимание, что **копировать объекты редко необходимо**. Например, есть всего несколько способов изменить DataFrame *на месте*:
- Вставка, удаление или изменение столбца
- Присвоение атрибутам
indexилиcolumns - Для однородных данных, прямое изменение значений через атрибут
valuesили расширенный индексирование
Важно отметить, что ни один метод pandas не вызывает побочного эффекта изменения ваших данных; почти все методы возвращают новые объекты, оставляя исходный объект нетронутым. Если данные изменяются, это происходит потому, что вы сделали это явно.
Типы данных
Основные типы данных, хранящиеся в объектах pandas, это float, int, bool, datetime64[ns] и datetime64[ns, tz] (в >= 0.17.0), timedelta[ns], category (в >= 0.15.0) и object. Кроме того, эти типы данных имеют размер элементов, например int64 и int32. См. Series с часовым поясом для получения более подробной информации о типах данных datetime64[ns, tz].
Удобный атрибут dtypes для DataFrames возвращает Series с типом данных каждого столбца.
In [294]: dft = pd.DataFrame(dict(A = np.random.rand(3),
.....: B = 1,
.....: C = 'foo',
.....: D = pd.Timestamp('20010102'),
.....: E = pd.Series([1.0]*3).astype('float32'),
.....: F = False,
.....: G = pd.Series([1]*3,dtype='int8')))
.....:
In [295]: dft
Out[295]:
A B C D E F G
0 0.954940 1 foo 2001-01-02 1.0 False 1
1 0.318163 1 foo 2001-01-02 1.0 False 1
2 0.985803 1 foo 2001-01-02 1.0 False 1
In [296]: dft.dtypes
Out[296]:
A float64
B int64
C object
D datetime64[ns]
E float32
F bool
G int8
dtype: object
Для Series используйте атрибут dtype.
In [297]: dft['A'].dtype
Out[297]: dtype('float64')
Если объект pandas содержит данные нескольких типов данных *В ОДНОМ СТОЛБЦЕ*, тип данных столбца будет выбран для поддержки всех типов данных (object является наиболее общим).
# these ints are coerced to floats In [298]: pd.Series([1, 2, 3, 4, 5, 6.]) Out[298]: 0 1.0 1 2.0 2 3.0 3 4.0 4 5.0 5 6.0 dtype: float64 # string data forces an ``object`` dtype In [299]: pd.Series([1, 2, 3, 6., 'foo']) Out[299]: 0 1 1 2 2 3 3 6 4 foo dtype: object
Метод get_dtype_counts() вернёт количество столбцов каждого типа в DataFrame:
In [300]: dft.get_dtype_counts() Out[300]: bool 1 datetime64[ns] 1 float32 1 float64 1 int64 1 int8 1 object 1 dtype: int64
Числовые типы данных будут распространяться и могут сосуществовать в DataFrame (начиная с версии 0.11.0). Если тип данных передаётся (прямо через ключевое слово dtype, переданный ndarray, или переданный Series, то он сохранится в операциях DataFrame. Кроме того, различные числовые типы данных **НЕ** будут комбинироваться. Следующий пример позволит вам это почувствовать.
In [301]: df1 = pd.DataFrame(np.random.randn(8, 1), columns=['A'], dtype='float32')
In [302]: df1
Out[302]:
A
0 0.647650
1 0.822993
2 1.778703
3 -1.543048
4 -0.123256
5 2.239740
6 -0.143778
7 -2.885090
In [303]: df1.dtypes
Out[303]:
A float32
dtype: object
In [304]: df2 = pd.DataFrame(dict( A = pd.Series(np.random.randn(8), dtype='float16'),
.....: B = pd.Series(np.random.randn(8)),
.....: C = pd.Series(np.array(np.random.randn(8), dtype='uint8')) ))
.....:
In [305]: df2
Out[305]:
A B C
0 0.027588 0.296947 0
1 -1.150391 0.007045 255
2 0.246460 0.707877 1
3 -0.455078 0.950661 0
4 -1.507812 0.087527 0
5 -0.502441 -0.339212 0
6 0.528809 -0.278698 0
7 0.590332 1.775379 0
In [306]: df2.dtypes
Out[306]:
A float16
B float64
C uint8
dtype: object
Значения по умолчанию
По умолчанию целочисленные типы данных являются int64, а плавающие типы данных float64, *НЕЗАВИСИМО* от платформы (32-битная или 64-битная). Следующие все приведут к типам данных int64.
In [307]: pd.DataFrame([1, 2], columns=['a']).dtypes
Out[307]:
a int64
dtype: object
In [308]: pd.DataFrame({'a': [1, 2]}).dtypes
Out[308]:
a int64
dtype: object
In [309]: pd.DataFrame({'a': 1 }, index=list(range(2))).dtypes
Out[309]:
a int64
dtype: object
Однако Numpy выберет *зависимые от платформы* типы при создании массивов. Следующее **ПОДТВЕРДИТ** результат int32 на 32-битной платформе.
In [310]: frame = pd.DataFrame(np.array([1, 2]))
Преобразование типов
Типы могут потенциально быть *преобразованы* при объединении с другими типами, что означает их повышение с текущего типа (скажем int до float).
In [311]: df3 = df1.reindex_like(df2).fillna(value=0.0) + df2
In [312]: df3
Out[312]:
A B C
0 0.675238 0.296947 0.0
1 -0.327398 0.007045 255.0
2 2.025163 0.707877 1.0
3 -1.998126 0.950661 0.0
4 -1.631068 0.087527 0.0
5 1.737299 -0.339212 0.0
6 0.385030 -0.278698 0.0
7 -2.294758 1.775379 0.0
In [313]: df3.dtypes
Out[313]:
A float32
B float64
C float64
dtype: object
Атрибут values в DataFrame возвращает *наименьший общий знаменатель* типов данных, что означает тип данных, который может вместить **ВСЕ** типы данных в результирующем однородном массиве numpy. Это может привести к некоторым *преобразованиям типов*.
In [314]: df3.values.dtype
Out[314]: dtype('float64')
astype
Вы можете использовать метод astype() для явного преобразования типов данных из одного в другой. По умолчанию они возвращают копию, даже если тип данных не изменился (передайте copy=False для изменения этого поведения). Кроме того, они вызовут исключение, если операция astype некорректна.
Преобразование типов всегда происходит в соответствии с правилами **numpy**. Если в операции участвуют два разных типа данных, то более *общий* из них будет использоваться в качестве результата операции.
In [315]: df3
Out[315]:
A B C
0 0.675238 0.296947 0.0
1 -0.327398 0.007045 255.0
2 2.025163 0.707877 1.0
3 -1.998126 0.950661 0.0
4 -1.631068 0.087527 0.0
5 1.737299 -0.339212 0.0
6 0.385030 -0.278698 0.0
7 -2.294758 1.775379 0.0
In [316]: df3.dtypes
Out[316]:
A float32
B float64
C float64
dtype: object
# conversion of dtypes
In [317]: df3.astype('float32').dtypes
Out[317]:
A float32
B float32
C float32
dtype: object
Преобразовать подмножество столбцов в указанный тип с помощью astype()
In [318]: dft = pd.DataFrame({'a': [1,2,3], 'b': [4,5,6], 'c': [7, 8, 9]})
In [319]: dft[['a','b']] = dft[['a','b']].astype(np.uint8)
In [320]: dft
Out[320]:
a b c
0 1 4 7
1 2 5 8
2 3 6 9
In [321]: dft.dtypes
Out[321]:
a uint8
b uint8
c int64
dtype: object
Примечание
При попытке преобразовать подмножество столбцов в указанный тип с помощью astype() и loc() происходит расширение типов.
loc() пытается вписать то, что мы присваиваем, в текущие типы данных, в то время как [] перезапишет их, взяв тип данных из правой части. Поэтому следующий фрагмент кода даёт нежелательный результат.
In [322]: dft = pd.DataFrame({'a': [1,2,3], 'b': [4,5,6], 'c': [7, 8, 9]})
In [323]: dft.loc[:, ['a', 'b']].astype(np.uint8).dtypes
Out[323]:
a uint8
b uint8
dtype: object
In [324]: dft.loc[:, ['a', 'b']] = dft.loc[:, ['a', 'b']].astype(np.uint8)
In [325]: dft.dtypes
Out[325]:
a int64
b int64
c int64
dtype: object
Преобразование объектов
pandas предлагает различные функции для попытки принудительного преобразования типов из типа данных object в другие типы. Следующие функции доступны для одномерных массивов объектов или скаляров:
-
to_numeric()(преобразование в числовые типы данных)In [326]: m = ['1.1', 2, 3] In [327]: pd.to_numeric(m) Out[327]: array([ 1.1, 2. , 3. ])
-
to_datetime()(преобразование в объекты datetime)In [328]: import datetime In [329]: m = ['2016-07-09', datetime.datetime(2016, 3, 2)] In [330]: pd.to_datetime(m) Out[330]: DatetimeIndex(['2016-07-09', '2016-03-02'], dtype='datetime64[ns]', freq=None)
-
to_timedelta()(преобразование в объекты timedelta)In [331]: m = ['5us', pd.Timedelta('1day')] In [332]: pd.to_timedelta(m) Out[332]: TimedeltaIndex(['0 days 00:00:00.000005', '1 days 00:00:00'], dtype='timedelta64[ns]', freq=None)
Для принудительного преобразования можно передать аргумент errors, который указывает, как pandas должен обрабатывать элементы, которые не могут быть преобразованы в желаемый тип данных или объект. По умолчанию, errors='raise', что означает, что любые ошибки, возникшие во время процесса преобразования, будут подняты. Однако, если errors='coerce', эти ошибки будут проигнорированы, и pandas преобразует проблемные элементы в pd.NaT (для datetime и timedelta) или np.nan (для числовых). Это может быть полезно, если вы читаете данные, которые в основном имеют желаемый тип данных (например, числовой, datetime), но иногда содержат несовместимые элементы, которые вы хотите представить как пропущенные значения:
In [333]: import datetime
In [334]: m = ['apple', datetime.datetime(2016, 3, 2)]
In [335]: pd.to_datetime(m, errors='coerce')
Out[335]: DatetimeIndex(['NaT', '2016-03-02'], dtype='datetime64[ns]', freq=None)
In [336]: m = ['apple', 2, 3]
In [337]: pd.to_numeric(m, errors='coerce')
Out[337]: array([ nan, 2., 3.])
In [338]: m = ['apple', pd.Timedelta('1day')]
In [339]: pd.to_timedelta(m, errors='coerce')
Out[339]: TimedeltaIndex([NaT, '1 days'], dtype='timedelta64[ns]', freq=None)
Параметр errors имеет третью опцию errors='ignore', которая просто вернёт переданные данные, если при преобразовании в нужный тип данных возникнут ошибки:
In [340]: import datetime
In [341]: m = ['apple', datetime.datetime(2016, 3, 2)]
In [342]: pd.to_datetime(m, errors='ignore')
Out[342]: array(['apple', datetime.datetime(2016, 3, 2, 0, 0)], dtype=object)
In [343]: m = ['apple', 2, 3]
In [344]: pd.to_numeric(m, errors='ignore')
Out[344]: array(['apple', 2, 3], dtype=object)
In [345]: m = ['apple', pd.Timedelta('1day')]
In [346]: pd.to_timedelta(m, errors='ignore')
Out[346]: array(['apple', Timedelta('1 days 00:00:00')], dtype=object)
В дополнение к преобразованию объектов, to_numeric() предоставляет ещё один аргумент downcast, который даёт возможность понизить разрядность только что (или уже) числовых данных до меньшего типа, что может экономить память:
In [347]: m = ['1', 2, 3] In [348]: pd.to_numeric(m, downcast='integer') # smallest signed int dtype Out[348]: array([1, 2, 3], dtype=int8) In [349]: pd.to_numeric(m, downcast='signed') # same as 'integer' Out[349]: array([1, 2, 3], dtype=int8) In [350]: pd.to_numeric(m, downcast='unsigned') # smallest unsigned int dtype Out[350]: array([1, 2, 3], dtype=uint8) In [351]: pd.to_numeric(m, downcast='float') # smallest float dtype Out[351]: array([ 1., 2., 3.], dtype=float32)
Поскольку эти методы применяются только к одномерным массивам, спискам или скалярам, они не могут быть использованы непосредственно для многомерных объектов, таких как DataFrames. Однако с помощью apply() мы можем эффективно «применить» функцию к каждому столбцу:
In [352]: import datetime
In [353]: df = pd.DataFrame([['2016-07-09', datetime.datetime(2016, 3, 2)]] * 2, dtype='O')
In [354]: df
Out[354]:
0 1
0 2016-07-09 2016-03-02 00:00:00
1 2016-07-09 2016-03-02 00:00:00
In [355]: df.apply(pd.to_datetime)
Out[355]:
0 1
0 2016-07-09 2016-03-02
1 2016-07-09 2016-03-02
In [356]: df = pd.DataFrame([['1.1', 2, 3]] * 2, dtype='O')
In [357]: df
Out[357]:
0 1 2
0 1.1 2 3
1 1.1 2 3
In [358]: df.apply(pd.to_numeric)
Out[358]:
0 1 2
0 1.1 2 3
1 1.1 2 3
In [359]: df = pd.DataFrame([['5us', pd.Timedelta('1day')]] * 2, dtype='O')
In [360]: df
Out[360]:
0 1
0 5us 1 days 00:00:00
1 5us 1 days 00:00:00
In [361]: df.apply(pd.to_timedelta)
Out[361]:
0 1
0 00:00:00.000005 1 days
1 00:00:00.000005 1 days
Особенности
Выполнение операций выбора на данных типа integer может легко привести к расширению данных до floating. Тип данных входных данных сохранится в случаях, когда nans не вводятся (начиная с 0.11.0). См. также Особенности с целочисленными пропущенными значениями
In [362]: dfi = df3.astype('int32')
In [363]: dfi['E'] = 1
In [364]: dfi
Out[364]:
A B C E
0 0 0 0 1
1 0 0 255 1
2 2 0 1 1
3 -1 0 0 1
4 -1 0 0 1
5 1 0 0 1
6 0 0 0 1
7 -2 1 0 1
In [365]: dfi.dtypes
Out[365]:
A int32
B int32
C int32
E int64
dtype: object
In [366]: casted = dfi[dfi>0]
In [367]: casted
Out[367]:
A B C E
0 NaN NaN NaN 1
1 NaN NaN 255.0 1
2 2.0 NaN 1.0 1
3 NaN NaN NaN 1
4 NaN NaN NaN 1
5 1.0 NaN NaN 1
6 NaN NaN NaN 1
7 NaN 1.0 NaN 1
In [368]: casted.dtypes
Out[368]:
A float64
B float64
C float64
E int64
dtype: object
В то время как типы данных float останутся неизменными.
In [369]: dfa = df3.copy()
In [370]: dfa['A'] = dfa['A'].astype('float32')
In [371]: dfa.dtypes
Out[371]:
A float32
B float64
C float64
dtype: object
In [372]: casted = dfa[df2>0]
In [373]: casted
Out[373]:
A B C
0 0.675238 0.296947 NaN
1 NaN 0.007045 255.0
2 2.025163 0.707877 1.0
3 NaN 0.950661 NaN
4 NaN 0.087527 NaN
5 NaN NaN NaN
6 0.385030 NaN NaN
7 -2.294758 1.775379 NaN
In [374]: casted.dtypes
Out[374]:
A float32
B float64
C float64
dtype: object
Выбор столбцов на основе типа данных
Новое в версии 0.14.1.
Метод select_dtypes() реализует подмножество столбцов на основе их типов данных.
Сначала давайте создадим DataFrame с различными типами данных:
In [375]: df = pd.DataFrame({'string': list('abc'),
.....: 'int64': list(range(1, 4)),
.....: 'uint8': np.arange(3, 6).astype('u1'),
.....: 'float64': np.arange(4.0, 7.0),
.....: 'bool1': [True, False, True],
.....: 'bool2': [False, True, False],
.....: 'dates': pd.date_range('now', periods=3).values,
.....: 'category': pd.Series(list("ABC")).astype('category')})
.....:
In [376]: df['tdeltas'] = df.dates.diff()
In [377]: df['uint64'] = np.arange(3, 6).astype('u8')
In [378]: df['other_dates'] = pd.date_range('20130101', periods=3).values
In [379]: df['tz_aware_dates'] = pd.date_range('20130101', periods=3, tz='US/Eastern')
In [380]: df
Out[380]:
bool1 bool2 category dates float64 int64 string \
0 True False A 2016-12-24 18:31:36.297875 4.0 1 a
1 False True B 2016-12-25 18:31:36.297875 5.0 2 b
2 True False C 2016-12-26 18:31:36.297875 6.0 3 c
uint8 tdeltas uint64 other_dates tz_aware_dates
0 3 NaT 3 2013-01-01 2013-01-01 00:00:00-05:00
1 4 1 days 4 2013-01-02 2013-01-02 00:00:00-05:00
2 5 1 days 5 2013-01-03 2013-01-03 00:00:00-05:00
И типы данных
In [381]: df.dtypes Out[381]: bool1 bool bool2 bool category category dates datetime64[ns] float64 float64 int64 int64 string object uint8 uint8 tdeltas timedelta64[ns] uint64 uint64 other_dates datetime64[ns] tz_aware_dates datetime64[ns, US/Eastern] dtype: object
select_dtypes() имеет два параметра include и exclude, которые позволяют вам сказать «дайте мне столбцы С этими типами данных» (include) и/или «дайте столбцы БЕЗ этих типов данных» (exclude).
Например, для выбора bool столбцов
In [382]: df.select_dtypes(include=[bool]) Out[382]: bool1 bool2 0 True False 1 False True 2 True False
Вы также можете передать имя типа данных в иерархии типов данных NumPy:
In [383]: df.select_dtypes(include=['bool']) Out[383]: bool1 bool2 0 True False 1 False True 2 True False
select_dtypes() также работает с общими типами данных.
Например, для выбора всех числовых и логических столбцов, исключая беззнаковые целые числа
In [384]: df.select_dtypes(include=['number', 'bool'], exclude=['unsignedinteger']) Out[384]: bool1 bool2 float64 int64 tdeltas 0 True False 4.0 1 NaT 1 False True 5.0 2 1 days 2 True False 6.0 3 1 days
Для выбора строковых столбцов необходимо использовать тип данных object:
In [385]: df.select_dtypes(include=['object']) Out[385]: string 0 a 1 b 2 c
Чтобы увидеть все дочерние типы данных общего типа dtype , например, numpy.number, вы можете определить функцию, которая возвращает дерево дочерних типов данных:
In [386]: def subdtypes(dtype): .....: subs = dtype.__subclasses__() .....: if not subs: .....: return dtype .....: return [dtype, [subdtypes(dt) for dt in subs]] .....:
Все типы данных NumPy являются подклассами numpy.generic:
In [387]: subdtypes(np.generic)
Out[387]:
[numpy.generic,
[[numpy.number,
[[numpy.integer,
[[numpy.signedinteger,
[numpy.int8,
numpy.int16,
numpy.int32,
numpy.int64,
numpy.int64,
numpy.timedelta64]],
[numpy.unsignedinteger,
[numpy.uint8,
numpy.uint16,
numpy.uint32,
numpy.uint64,
numpy.uint64]]]],
[numpy.inexact,
[[numpy.floating,
[numpy.float16, numpy.float32, numpy.float64, numpy.float128]],
[numpy.complexfloating,
[numpy.complex64, numpy.complex128, numpy.complex256]]]]]],
[numpy.flexible,
[[numpy.character, [numpy.string_, numpy.unicode_]],
[numpy.void, [numpy.record]]]],
numpy.bool_,
numpy.datetime64,
numpy.object_]]
Примечание
Pandas также определяет типы category, и datetime64[ns, tz], которые не интегрированы в обычную иерархию NumPy и не будут отображаться с помощью вышеупомянутой функции.
Примечание
Параметры include и exclude должны быть последовательностями, не являющимися строками.
© 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.19.2/basics.html