Spec-Zone.ru › pandas 1

Основные базовые функции

Здесь мы обсуждаем множество основных функций, общих для структур данных pandas. Для начала давайте создадим некоторые примерные объекты, как мы делали в разделе 10 минут с 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"])

Головная и хвостовая части

Для просмотра небольшой выборки объекта Series или DataFrame используйте методы head() и tail(). По умолчанию отображается пять элементов, но вы можете указать другое количество.

In [4]: long_series = pd.Series(np.random.randn(1000))

In [5]: long_series.head()
Out[5]: 
0   -1.157892
1   -1.344312
2    0.844885
3    1.075770
4   -0.109050
dtype: float64

In [6]: long_series.tail(3)
Out[6]: 
997   -0.289388
998   -1.020544
999    0.589993
dtype: float64

Атрибуты и данные в основе

Объекты pandas имеют ряд атрибутов, позволяющих получить доступ к метаданным

  • shape: предоставляет размерность осей объекта, согласованную с ndarray

  • Метки осей
    • Series: index (только ось)

    • DataFrame: index (строки) и columns (столбцы)

Обратите внимание, что эти атрибуты можно безопасно назначать!

In [7]: df[:2]
Out[7]: 
                   A         B         C
2000-01-01 -0.173215  0.119209 -1.044236
2000-01-02 -0.861849 -2.104569 -0.494929

In [8]: df.columns = [x.lower() for x in df.columns]

In [9]: df
Out[9]: 
                   a         b         c
2000-01-01 -0.173215  0.119209 -1.044236
2000-01-02 -0.861849 -2.104569 -0.494929
2000-01-03  1.071804  0.721555 -0.706771
2000-01-04 -1.039575  0.271860 -0.424972
2000-01-05  0.567020  0.276232 -1.087401
2000-01-06 -0.673690  0.113648 -1.478427
2000-01-07  0.524988  0.404705  0.577046
2000-01-08 -1.715002 -1.039268 -0.370647

Объекты pandas (Index, Series, DataFrame) можно рассматривать как контейнеры для массивов, которые хранят фактические данные и выполняют фактические вычисления. Для многих типов базовый массив является numpy.ndarray. Однако pandas и сторонние библиотеки могут *расширять* систему типов NumPy, добавляя поддержку пользовательских массивов (см. типы данных).

Чтобы получить фактические данные внутри Index или Series, используйте свойство .array

In [10]: s.array
Out[10]: 
<PandasArray>
[ 0.4691122999071863, -0.2828633443286633, -1.5090585031735124,
 -1.1356323710171934,  1.2121120250208506]
Length: 5, dtype: float64

In [11]: s.index.array
Out[11]: 
<PandasArray>
['a', 'b', 'c', 'd', 'e']
Length: 5, dtype: object

array всегда будет ExtensionArray. Точные детали того, что такое ExtensionArray и почему pandas их использует, выходят за рамки данного введения. Для получения дополнительной информации см. типы данных.

Если вам нужен массив NumPy, используйте to_numpy() или numpy.asarray().

In [12]: s.to_numpy()
Out[12]: array([ 0.4691, -0.2829, -1.5091, -1.1356,  1.2121])

In [13]: np.asarray(s)
Out[13]: array([ 0.4691, -0.2829, -1.5091, -1.1356,  1.2121])

Когда Series или Index поддерживаются ExtensionArray, to_numpy() может потребовать копирования данных и приведения значений. Для получения дополнительной информации см. типы данных.

to_numpy() предоставляет некоторый контроль над dtype результирующего numpy.ndarray. Например, рассмотрим даты и время с часовыми поясами. NumPy не имеет типа данных для представления дат и времени с часовыми поясами, поэтому есть два потенциально полезных представления:

  1. Массив object-dtype numpy.ndarray с объектами Timestamp, каждый с правильным tz

  2. Массив datetime64[ns]-dtype numpy.ndarray, где значения были преобразованы в UTC, а часовой пояс отброшен

Часовые пояса могут сохраняться с помощью dtype=object

In [14]: ser = pd.Series(pd.date_range("2000", periods=2, tz="CET"))

In [15]: ser.to_numpy(dtype=object)
Out[15]: 
array([Timestamp('2000-01-01 00:00:00+0100', tz='CET'),
       Timestamp('2000-01-02 00:00:00+0100', tz='CET')], dtype=object)

Или удаляться с помощью dtype='datetime64[ns]'

In [16]: ser.to_numpy(dtype="datetime64[ns]")
Out[16]: 
array(['1999-12-31T23:00:00.000000000', '2000-01-01T23:00:00.000000000'],
      dtype='datetime64[ns]')

Получение «сырых данных» внутри DataFrame может быть немного сложнее. Когда ваш DataFrame имеет только один тип данных для всех столбцов, DataFrame.to_numpy() вернет базовые данные:

In [17]: df.to_numpy()
Out[17]: 
array([[-0.1732,  0.1192, -1.0442],
       [-0.8618, -2.1046, -0.4949],
       [ 1.0718,  0.7216, -0.7068],
       [-1.0396,  0.2719, -0.425 ],
       [ 0.567 ,  0.2762, -1.0874],
       [-0.6737,  0.1136, -1.4784],
       [ 0.525 ,  0.4047,  0.577 ],
       [-1.715 , -1.0393, -0.3706]])

Если DataFrame содержит данные с однотипными данными, ndarray можно фактически изменить на месте, и изменения будут отражены в структуре данных. Для неоднородных данных (например, если столбцы DataFrame имеют не все одинаковые типы данных), это не так. К атрибуту values, в отличие от меток осей, нельзя назначить значение.

Примечание

При работе с неоднородными данными тип данных результирующего ndarray будет выбран для обработки всех участвующих данных. Например, если присутствуют строки, результат будет иметь тип данных object. Если присутствуют только числа с плавающей запятой и целые числа, результирующий массив будет иметь тип данных float.

В прошлом pandas рекомендовал Series.values или DataFrame.values для извлечения данных из Series или DataFrame. Вы все равно можете найти ссылки на них в старых кодовых базах и онлайн. В будущем мы рекомендуем избегать .values и использовать .array или .to_numpy(). .values имеет следующие недостатки:

  1. Когда ваш Series содержит расширенный тип, неясно, возвращает ли Series.values массив NumPy или массив расширения. Series.array всегда вернет ExtensionArray и никогда не скопирует данные. Series.to_numpy() всегда возвращает массив NumPy, потенциально с затратами на копирование/приведение значений.

  2. Когда ваш DataFrame содержит смесь типов данных, DataFrame.values может потребовать копирования данных и приведения значений к общему типу данных — относительно дорогостоящая операция. DataFrame.to_numpy(), будучи методом, делает яснее, что возвращаемый массив NumPy может не быть представлением тех же данных в DataFrame.

Ускоренные операции

pandas поддерживает ускорение определенных типов бинарных числовых и логических операций с использованием библиотеки numexpr и библиотек bottleneck.

Эти библиотеки особенно полезны при работе с большими наборами данных и обеспечивают значительное ускорение. numexpr использует интеллектуальное фрагментацию, кэширование и несколько ядер. bottleneck представляет собой набор специализированных цикловых процедур, которые особенно быстры при работе с массивами, имеющими nans.

Вот пример (используя DataFrames 100 столбцов х 100 000 строк):

Операция

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

Настоятельно рекомендуется установить обе библиотеки. См. раздел Рекомендуемые зависимости для получения дополнительной информации об установке.

Обе библиотеки включены по умолчанию, вы можете управлять этим, установив параметры:

pd.set_option("compute.use_bottleneck", False)
pd.set_option("compute.use_numexpr", False)

Гибкие бинарные операции

При бинарных операциях между структурами данных pandas есть два ключевых момента:

  • Поведение широковещательной передачи между объектами большей (например, DataFrame) и меньшей (например, Series) размерности.

  • Отсутствующие данные в вычислениях.

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

Соответствие/поведение широковещательной передачи

DataFrame имеет методы add(), sub(), mul(), div() и связанные функции radd(), rsub(), … для выполнения бинарных операций. При поведении широковещательной передачи на первом месте стоят входные данные Series. Используя эти функции, вы можете сопоставлять либо по индексу, либо по столбцам с помощью ключевого слова axis:

In [18]: 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 [19]: df
Out[19]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [20]: row = df.iloc[1]

In [21]: column = df["two"]

In [22]: df.sub(row, axis="columns")
Out[22]: 
        one       two     three
a  1.051928 -0.139606       NaN
b  0.000000  0.000000  0.000000
c  0.352192 -0.433754  1.277825
d       NaN -1.632779 -0.562782

In [23]: df.sub(row, axis=1)
Out[23]: 
        one       two     three
a  1.051928 -0.139606       NaN
b  0.000000  0.000000  0.000000
c  0.352192 -0.433754  1.277825
d       NaN -1.632779 -0.562782

In [24]: df.sub(column, axis="index")
Out[24]: 
        one  two     three
a -0.377535  0.0       NaN
b -1.569069  0.0 -1.962513
c -0.783123  0.0 -0.250933
d       NaN  0.0 -0.892516

In [25]: df.sub(column, axis=0)
Out[25]: 
        one  two     three
a -0.377535  0.0       NaN
b -1.569069  0.0 -1.962513
c -0.783123  0.0 -0.250933
d       NaN  0.0 -0.892516

Кроме того, вы можете выровнять уровень MultiIndexed DataFrame с Series.

In [26]: dfmi = df.copy()

In [27]: dfmi.index = pd.MultiIndex.from_tuples(
   ....:     [(1, "a"), (1, "b"), (1, "c"), (2, "a")], names=["first", "second"]
   ....: )
   ....: 

In [28]: dfmi.sub(column, axis=0, level="second")
Out[28]: 
                   one       two     three
first second                              
1     a      -0.377535  0.000000       NaN
      b      -1.569069  0.000000 -1.962513
      c      -0.783123  0.000000 -0.250933
2     a            NaN -1.493173 -2.385688

Series и Index также поддерживают встроенную функцию divmod(). Эта функция выполняет операцию целочисленного деления и остатка одновременно, возвращая кортеж из двух элементов того же типа, что и левая часть. Например:

In [29]: s = pd.Series(np.arange(10))

In [30]: s
Out[30]: 
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64

In [31]: div, rem = divmod(s, 3)

In [32]: div
Out[32]: 
0    0
1    0
2    0
3    1
4    1
5    1
6    2
7    2
8    2
9    3
dtype: int64

In [33]: rem
Out[33]: 
0    0
1    1
2    2
3    0
4    1
5    2
6    0
7    1
8    2
9    0
dtype: int64

In [34]: idx = pd.Index(np.arange(10))

In [35]: idx
Out[35]: Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype='int64')

In [36]: div, rem = divmod(idx, 3)

In [37]: div
Out[37]: Int64Index([0, 0, 0, 1, 1, 1, 2, 2, 2, 3], dtype='int64')

In [38]: rem
Out[38]: Int64Index([0, 1, 2, 0, 1, 2, 0, 1, 2, 0], dtype='int64')

Мы также можем выполнять поэлементное divmod():

In [39]: div, rem = divmod(s, [2, 2, 3, 3, 4, 4, 5, 5, 6, 6])

In [40]: div
Out[40]: 
0    0
1    0
2    0
3    1
4    1
5    1
6    1
7    1
8    1
9    1
dtype: int64

In [41]: rem
Out[41]: 
0    0
1    1
2    2
3    0
4    0
5    1
6    1
7    2
8    2
9    3
dtype: int64

Отсутствующие данные/операции со значениями заполнения

В Series и DataFrame арифметические функции имеют возможность ввода fill_value, а именно значения для подстановки, когда отсутствует не более одного значения в определённой позиции. Например, при сложении двух объектов DataFrame вы можете рассматривать NaN как 0, если хотя бы один из DataFrame не имеет этого значения, в противном случае результат будет NaN (позднее вы можете заменить NaN другим значением с помощью fillna).

In [42]: df
Out[42]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [43]: df2
Out[43]: 
        one       two     three
a  1.394981  1.772517  1.000000
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [44]: df + df2
Out[44]: 
        one       two     three
a  2.789963  3.545034       NaN
b  0.686107  3.824246 -0.100780
c  1.390491  2.956737  2.454870
d       NaN  0.558688 -1.226343

In [45]: df.add(df2, fill_value=0)
Out[45]: 
        one       two     three
a  2.789963  3.545034  1.000000
b  0.686107  3.824246 -0.100780
c  1.390491  2.956737  2.454870
d       NaN  0.558688 -1.226343

Гибкие сравнения

Series и DataFrame имеют бинарные методы сравнения eq, ne, lt, gt, le, и ge, поведение которых аналогично бинарным арифметическим операциям, описанным выше:

In [46]: df.gt(df2)
Out[46]: 
     one    two  three
a  False  False  False
b  False  False  False
c  False  False  False
d  False  False  False

In [47]: df2.ne(df)
Out[47]: 
     one    two  three
a  False  False   True
b  False  False  False
c  False  False  False
d   True  False  False

Эти операции создают объект pandas того же типа, что и входное значение слева, с типом данных bool. Эти boolean объекты могут быть использованы в операциях индексирования, см. раздел о индексировании с булевыми значениями.

Булевы сокращения

Вы можете применить сокращения: empty, any(), all() и bool() для подведения итогов булевого результата.

In [48]: (df > 0).all()
Out[48]: 
one      False
two       True
three    False
dtype: bool

In [49]: (df > 0).any()
Out[49]: 
one      True
two      True
three    True
dtype: bool

Вы можете свести к окончательному булевому значению.

In [50]: (df > 0).any().any()
Out[50]: True

Вы можете проверить, является ли объект pandas пустым, с помощью свойства empty.

In [51]: df.empty
Out[51]: False

In [52]: pd.DataFrame(columns=list("ABC")).empty
Out[52]: True

Для оценки одноэлементных объектов pandas в булевом контексте используйте метод bool():

In [53]: pd.Series([True]).bool()
Out[53]: True

In [54]: pd.Series([False]).bool()
Out[54]: False

In [55]: pd.DataFrame([[True]]).bool()
Out[55]: True

In [56]: pd.DataFrame([[False]]).bool()
Out[56]: False

Предупреждение

Вы можете быть искушены сделать следующее:

>>> if df:
...     pass

Или

>>> 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 [57]: df + df == df * 2
Out[57]: 
     one   two  three
a   True  True  False
b   True  True   True
c   True  True   True
d  False  True   True

In [58]: (df + df == df * 2).all()
Out[58]: 
one      False
two       True
three    False
dtype: bool

Обратите внимание, что булевый DataFrame df + df == df * 2 содержит некоторые ложные значения! Это происходит потому, что NaN не сравниваются как равные:

In [59]: np.nan == np.nan
Out[59]: False

Таким образом, NDFrames (такие как Series и DataFrame) имеют метод equals() для проверки равенства, при этом NaN в соответствующих позициях считаются равными.

In [60]: (df + df).equals(df * 2)
Out[60]: True

Обратите внимание, что индекс Series или DataFrame должен быть в том же порядке, чтобы равенство было истинным:

In [61]: df1 = pd.DataFrame({"col": ["foo", 0, np.nan]})

In [62]: df2 = pd.DataFrame({"col": [np.nan, 0, "foo"]}, index=[2, 1, 0])

In [63]: df1.equals(df2)
Out[63]: False

In [64]: df1.equals(df2.sort_index())
Out[64]: True

Сравнение массивоподобных объектов

Вы можете удобно выполнять поэлементные сравнения при сравнении структуры данных pandas со скалярным значением:

In [65]: pd.Series(["foo", "bar", "baz"]) == "foo"
Out[65]: 
0     True
1    False
2    False
dtype: bool

In [66]: pd.Index(["foo", "bar", "baz"]) == "foo"
Out[66]: array([ True, False, False])

pandas также обрабатывает поэлементные сравнения между различными массивоподобными объектами одинаковой длины:

In [67]: pd.Series(["foo", "bar", "baz"]) == pd.Index(["foo", "bar", "qux"])
Out[67]: 
0     True
1     True
2    False
dtype: bool

In [68]: pd.Series(["foo", "bar", "baz"]) == np.array(["foo", "bar", "qux"])
Out[68]: 
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 [69]: np.array([1, 2, 3]) == np.array([2])
Out[69]: array([False,  True, False])

или оно может вернуть False, если широковещательная передача невозможна:

In [70]: np.array([1, 2, 3]) == np.array([1, 2])
Out[70]: False

Объединение перекрывающихся наборов данных

Иногда возникает проблема объединения двух похожих наборов данных, где значения одного набора предпочтительнее другого. Например, две серии данных, представляющие определённый экономический показатель, где одна считается «более качественной». Однако серия с более низким качеством может охватывать более ранний период времени или иметь более полное покрытие данных. Таким образом, мы хотим объединить два объекта DataFrame, где отсутствующие значения в одном DataFrame условно заполняются аналогичными значениями из другого DataFrame. Функция, реализующая эту операцию, — combine_first(), что мы иллюстрируем:

In [71]: df1 = pd.DataFrame(
   ....:     {"A": [1.0, np.nan, 3.0, 5.0, np.nan], "B": [np.nan, 2.0, 3.0, np.nan, 6.0]}
   ....: )
   ....: 

In [72]: df2 = pd.DataFrame(
   ....:     {
   ....:         "A": [5.0, 2.0, 4.0, np.nan, 3.0, 7.0],
   ....:         "B": [np.nan, np.nan, 3.0, 4.0, 6.0, 8.0],
   ....:     }
   ....: )
   ....: 

In [73]: df1
Out[73]: 
     A    B
0  1.0  NaN
1  NaN  2.0
2  3.0  3.0
3  5.0  NaN
4  NaN  6.0

In [74]: df2
Out[74]: 
     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 [75]: df1.combine_first(df2)
Out[75]: 
     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 [76]: def combiner(x, y):
   ....:     return np.where(pd.isna(x), y, x)
   ....: 

In [77]: df1.combine(df2, combiner)
Out[77]: 
     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. Большинство из них являются агрегациями (поэтому производят результат меньшей размерности), такие как sum(), mean() и quantile(), но некоторые из них, такие как cumsum() и cumprod(), производят объект той же размерности. Как правило, эти методы принимают аргумент axis, как и ndarray.{sum, std, …}, но ось может быть указана по имени или целочисленно:

  • Ряд: аргумент axis не нужен

  • DataFrame: “index” (axis=0, по умолчанию), “columns” (axis=1)

Например:

In [78]: df
Out[78]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [79]: df.mean(0)
Out[79]: 
one      0.811094
two      1.360588
three    0.187958
dtype: float64

In [80]: df.mean(1)
Out[80]: 
a    1.583749
b    0.734929
c    1.133683
d   -0.166914
dtype: float64

Все такие методы имеют опцию skipna указывающую, следует ли исключить пропущенные данные (True по умолчанию):

In [81]: df.sum(0, skipna=False)
Out[81]: 
one           NaN
two      5.442353
three         NaN
dtype: float64

In [82]: df.sum(axis=1, skipna=True)
Out[82]: 
a    3.167498
b    2.204786
c    3.401050
d   -0.333828
dtype: float64

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

In [83]: ts_stand = (df - df.mean()) / df.std()

In [84]: ts_stand.std()
Out[84]: 
one      1.0
two      1.0
three    1.0
dtype: float64

In [85]: xs_stand = df.sub(df.mean(1), axis=0).div(df.std(1), axis=0)

In [86]: xs_stand.std(1)
Out[86]: 
a    1.0
b    1.0
c    1.0
d    1.0
dtype: float64

Обратите внимание, что методы, такие как cumsum() и cumprod(), сохраняют местоположение NaN значений. Это несколько отличается от expanding() и rolling(), так как NaN поведение дополнительно определяется параметром min_periods.

In [87]: df.cumsum()
Out[87]: 
        one       two     three
a  1.394981  1.772517       NaN
b  1.738035  3.684640 -0.050390
c  2.433281  5.163008  1.177045
d       NaN  5.442353  0.563873

Вот сводная таблица часто используемых функций. Каждая также принимает необязательный level параметр, который применяется только если у объекта есть иерархический индекс.

Функция

Описание

count

Количество наблюдений без NA

sum

Сумма значений

mean

Среднее значение

mad

Среднее абсолютное отклонение

median

Арифметическая медиана

min

Минимум

max

Максимум

mode

Мода

abs

Модуль

prod

Произведение значений

std

Корректированное по Бесселю выборочное стандартное отклонение

var

Несмещенная дисперсия

sem

Стандартная ошибка среднего

skew

Выборовый коэффициент асимметрии (третий момент)

kurt

Выборовый эксцесс (четвертый момент)

quantile

Выборочный квантиль (значение в %)

cumsum

Кумулятивная сумма

cumprod

Кумулятивное произведение

cummax

Кумулятивный максимум

cummin

Кумулятивный минимум

Обратите внимание, что по случайности некоторые методы NumPy, такие как mean, std, и sum, по умолчанию будут исключать NAs при вводе Series:

In [88]: np.mean(df["one"])
Out[88]: 0.8110935116651192

In [89]: np.mean(df["one"].to_numpy())
Out[89]: nan

Series.nunique() вернет количество уникальных значений без NA в Series:

In [90]: series = pd.Series(np.random.randn(500))

In [91]: series[20:500] = np.nan

In [92]: series[10:20] = 5

In [93]: series.nunique()
Out[93]: 11

Обобщение данных: describe

Существует удобная функция describe(), которая вычисляет различные сводные статистические данные о Series или столбцах DataFrame (исключая, конечно, NA):

In [94]: series = pd.Series(np.random.randn(1000))

In [95]: series[::2] = np.nan

In [96]: series.describe()
Out[96]: 
count    500.000000
mean      -0.021292
std        1.015906
min       -2.683763
25%       -0.699070
50%       -0.069718
75%        0.714483
max        3.160915
dtype: float64

In [97]: frame = pd.DataFrame(np.random.randn(1000, 5), columns=["a", "b", "c", "d", "e"])

In [98]: frame.iloc[::2] = np.nan

In [99]: frame.describe()
Out[99]: 
                a           b           c           d           e
count  500.000000  500.000000  500.000000  500.000000  500.000000
mean     0.033387    0.030045   -0.043719   -0.051686    0.005979
std      1.017152    0.978743    1.025270    1.015988    1.006695
min     -3.000951   -2.637901   -3.303099   -3.159200   -3.188821
25%     -0.647623   -0.576449   -0.712369   -0.691338   -0.691115
50%      0.047578   -0.021499   -0.023888   -0.032652   -0.025363
75%      0.729907    0.775880    0.618896    0.670047    0.649748
max      2.740139    2.752332    3.004229    2.728702    3.240991

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

In [100]: series.describe(percentiles=[0.05, 0.25, 0.75, 0.95])
Out[100]: 
count    500.000000
mean      -0.021292
std        1.015906
min       -2.683763
5%        -1.645423
25%       -0.699070
50%       -0.069718
75%        0.714483
95%        1.711409
max        3.160915
dtype: float64

По умолчанию медиана всегда включается.

Для объекта Series, не являющегося числовым, describe() даст краткое описание количества уникальных значений и наиболее часто встречающихся значений:

In [101]: s = pd.Series(["a", "a", "b", "b", "a", "a", np.nan, "c", "d", "a"])

In [102]: s.describe()
Out[102]: 
count     9
unique    4
top       a
freq      5
dtype: object

Обратите внимание, что для смешанного типа DataFrame object, describe() ограничит сводку только числовыми столбцами или, если таковых нет, только категориальными столбцами:

In [103]: frame = pd.DataFrame({"a": ["Yes", "Yes", "No", "No"], "b": range(4)})

In [104]: frame.describe()
Out[104]: 
              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 [105]: frame.describe(include=["object"])
Out[105]: 
          a
count     4
unique    2
top     Yes
freq      2

In [106]: frame.describe(include=["number"])
Out[106]: 
              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 [107]: frame.describe(include="all")
Out[107]: 
          a         b
count     4  4.000000
unique    2       NaN
top     Yes       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 [108]: s1 = pd.Series(np.random.randn(5))

In [109]: s1
Out[109]: 
0    1.118076
1   -0.352051
2   -1.242883
3   -1.277155
4   -0.641184
dtype: float64

In [110]: s1.idxmin(), s1.idxmax()
Out[110]: (3, 0)

In [111]: df1 = pd.DataFrame(np.random.randn(5, 3), columns=["A", "B", "C"])

In [112]: df1
Out[112]: 
          A         B         C
0 -0.327863 -0.946180 -0.137570
1 -0.186235 -0.257213 -0.486567
2 -0.507027 -0.871259 -0.111110
3  2.000339 -2.430505  0.089759
4 -0.321434 -0.033695  0.096271

In [113]: df1.idxmin(axis=0)
Out[113]: 
A    2
B    3
C    1
dtype: int64

In [114]: df1.idxmax(axis=1)
Out[114]: 
0    C
1    A
2    C
3    A
4    C
dtype: object

Если есть несколько строк (или столбцов), соответствующих минимальному или максимальному значению, idxmin() и idxmax() возвращают первую совпадающую метку:

In [115]: df3 = pd.DataFrame([2, 1, 1, 3, np.nan], columns=["A"], index=list("edcba"))

In [116]: df3
Out[116]: 
     A
e  2.0
d  1.0
c  1.0
b  3.0
a  NaN

In [117]: df3["A"].idxmin()
Out[117]: 'd'

Примечание

idxmin и idxmax называются argmin и argmax в NumPy.

Количество значений (гистограммы)/ мода

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

In [118]: data = np.random.randint(0, 7, size=50)

In [119]: data
Out[119]: 
array([6, 6, 2, 3, 5, 3, 2, 5, 4, 5, 4, 3, 4, 5, 0, 2, 0, 4, 2, 0, 3, 2,
       2, 5, 6, 5, 3, 4, 6, 4, 3, 5, 6, 4, 3, 6, 2, 6, 6, 2, 3, 4, 2, 1,
       6, 2, 6, 1, 5, 4])

In [120]: s = pd.Series(data)

In [121]: s.value_counts()
Out[121]: 
6    10
2    10
4     9
3     8
5     8
0     3
1     2
dtype: int64

In [122]: pd.value_counts(data)
Out[122]: 
6    10
2    10
4     9
3     8
5     8
0     3
1     2
dtype: int64

New in version 1.1.0.

Метод value_counts() может использоваться для подсчета комбинаций по нескольким столбцам. По умолчанию используются все столбцы, но можно выбрать подмножество с помощью аргумента subset.

In [123]: data = {"a": [1, 2, 3, 4], "b": ["x", "x", "y", "y"]}

In [124]: frame = pd.DataFrame(data)

In [125]: frame.value_counts()
Out[125]: 
a  b
1  x    1
2  x    1
3  y    1
4  y    1
dtype: int64

Аналогично, вы можете получить наиболее часто встречающееся значение(я), т.е. моду, значений в Series или DataFrame:

In [126]: s5 = pd.Series([1, 1, 3, 3, 3, 5, 5, 7, 7, 7])

In [127]: s5.mode()
Out[127]: 
0    3
1    7
dtype: int64

In [128]: df5 = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randint(0, 7, size=50),
   .....:         "B": np.random.randint(-10, 15, size=50),
   .....:     }
   .....: )
   .....: 

In [129]: df5.mode()
Out[129]: 
     A   B
0  1.0  -9
1  NaN  10
2  NaN  13
END_OF_DOCUMENT_MARKER

Дискретизация и квантилирование

Непрерывные значения могут быть дискретизированы с помощью функций cut() (бины на основе значений) и qcut() (бины на основе выборочных квантилей):

In [130]: arr = np.random.randn(20)

In [131]: factor = pd.cut(arr, 4)

In [132]: factor
Out[132]: 
[(-0.251, 0.464], (-0.968, -0.251], (0.464, 1.179], (-0.251, 0.464], (-0.968, -0.251], ..., (-0.251, 0.464], (-0.968, -0.251], (-0.968, -0.251], (-0.968, -0.251], (-0.968, -0.251]]
Length: 20
Categories (4, interval[float64, right]): [(-0.968, -0.251] < (-0.251, 0.464] < (0.464, 1.179] <
                                           (1.179, 1.893]]

In [133]: factor = pd.cut(arr, [-5, -1, 0, 1, 5])

In [134]: factor
Out[134]: 
[(0, 1], (-1, 0], (0, 1], (0, 1], (-1, 0], ..., (-1, 0], (-1, 0], (-1, 0], (-1, 0], (-1, 0]]
Length: 20
Categories (4, interval[int64, right]): [(-5, -1] < (-1, 0] < (0, 1] < (1, 5]]

qcut() вычисляет выборочные квантили. Например, мы можем разбить некоторые нормально распределенные данные на равные квартили следующим образом:

In [135]: arr = np.random.randn(30)

In [136]: factor = pd.qcut(arr, [0, 0.25, 0.5, 0.75, 1])

In [137]: factor
Out[137]: 
[(0.569, 1.184], (-2.278, -0.301], (-2.278, -0.301], (0.569, 1.184], (0.569, 1.184], ..., (-0.301, 0.569], (1.184, 2.346], (1.184, 2.346], (-0.301, 0.569], (-2.278, -0.301]]
Length: 30
Categories (4, interval[float64, right]): [(-2.278, -0.301] < (-0.301, 0.569] < (0.569, 1.184] <
                                           (1.184, 2.346]]

In [138]: pd.value_counts(factor)
Out[138]: 
(-2.278, -0.301]    8
(1.184, 2.346]      8
(-0.301, 0.569]     7
(0.569, 1.184]      7
dtype: int64

Мы также можем передать бесконечные значения для определения бинов:

In [139]: arr = np.random.randn(20)

In [140]: factor = pd.cut(arr, [-np.inf, 0, np.inf])

In [141]: factor
Out[141]: 
[(-inf, 0.0], (0.0, inf], (0.0, inf], (-inf, 0.0], (-inf, 0.0], ..., (-inf, 0.0], (-inf, 0.0], (-inf, 0.0], (0.0, inf], (0.0, inf]]
Length: 20
Categories (2, interval[float64, right]): [(-inf, 0.0] < (0.0, inf]]

Применение функций

Чтобы применить свои или функции из других библиотек к объектам pandas, вы должны знать о трёх методах ниже. Выбор подходящего метода зависит от того, ожидает ли ваша функция работать с целой DataFrame или Series, по строкам или столбцам, или поэлементно.

  1. Применение функций к таблице: pipe()

  2. Применение функции по строкам или столбцам: apply()

  3. API агрегации: agg() и transform()

  4. Применение поэлементных функций: applymap()

Применение функции к таблице

DataFrames и Series могут быть переданы в функции. Однако, если функция должна быть вызвана в цепочке, рассмотрите использование метода pipe().

Сначала немного подготовительных действий:

In [142]: def extract_city_name(df):
   .....:     """
   .....:     Chicago, IL -> Chicago for city_name column
   .....:     """
   .....:     df["city_name"] = df["city_and_code"].str.split(",").str.get(0)
   .....:     return df
   .....: 

In [143]: def add_country_name(df, country_name=None):
   .....:     """
   .....:     Chicago -> Chicago-US for city_name column
   .....:     """
   .....:     col = "city_name"
   .....:     df["city_and_country"] = df[col] + country_name
   .....:     return df
   .....: 

In [144]: df_p = pd.DataFrame({"city_and_code": ["Chicago, IL"]})

extract_city_name и add_country_name являются функциями, принимающими и возвращающими DataFrames.

Теперь сравните следующее:

In [145]: add_country_name(extract_city_name(df_p), country_name="US")
Out[145]: 
  city_and_code city_name city_and_country
0   Chicago, IL   Chicago        ChicagoUS

Эквивалентно:

In [146]: df_p.pipe(extract_city_name).pipe(add_country_name, country_name="US")
Out[146]: 
  city_and_code city_name city_and_country
0   Chicago, IL   Chicago        ChicagoUS

pandas рекомендует второй стиль, который известен как цепочка методов. pipe упрощает использование ваших собственных или функций из других библиотек в цепочках методов вместе с методами pandas.

В приведенном выше примере функции extract_city_name и add_country_name ожидали DataFrame в качестве первого позиционного аргумента. Что если функция, которую вы хотите применить, принимает данные, скажем, во втором аргументе? В этом случае передайте pipe кортеж из (callable, data_keyword). .pipe передаст DataFrame в указанный в кортеже аргумент.

Например, мы можем подобрать регрессию с помощью statsmodels. Их API ожидает формулу вначале и DataFrame во втором аргументе, data. Мы передаём функцию, пару «ключ-значение» (sm.ols, 'data') в pipe:

In [147]: import statsmodels.formula.api as sm

In [148]: bb = pd.read_csv("data/baseball.csv", index_col="id")

In [149]: (
   .....:     bb.query("h > 0")
   .....:     .assign(ln_h=lambda df: np.log(df.h))
   .....:     .pipe((sm.ols, "data"), "hr ~ ln_h + year + g + C(lg)")
   .....:     .fit()
   .....:     .summary()
   .....: )
   .....: 
Out[149]: 
<class 'statsmodels.iolib.summary.Summary'>
"""
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                     hr   R-squared:                       0.685
Model:                            OLS   Adj. R-squared:                  0.665
Method:                 Least Squares   F-statistic:                     34.28
Date:                Mon, 19 Sep 2022   Prob (F-statistic):           3.48e-15
Time:                        12:06:27   Log-Likelihood:                -205.92
No. Observations:                  68   AIC:                             421.8
Df Residuals:                      63   BIC:                             432.9
Df Model:                           4                                         
Covariance Type:            nonrobust                                         
===============================================================================
                  coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------
Intercept   -8484.7720   4664.146     -1.819      0.074   -1.78e+04     835.780
C(lg)[T.NL]    -2.2736      1.325     -1.716      0.091      -4.922       0.375
ln_h           -1.3542      0.875     -1.547      0.127      -3.103       0.395
year            4.2277      2.324      1.819      0.074      -0.417       8.872
g               0.1841      0.029      6.258      0.000       0.125       0.243
==============================================================================
Omnibus:                       10.875   Durbin-Watson:                   1.999
Prob(Omnibus):                  0.004   Jarque-Bera (JB):               17.298
Skew:                           0.537   Prob(JB):                     0.000175
Kurtosis:                       5.225   Cond. No.                     1.49e+07
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.49e+07. This might indicate that there are
strong multicollinearity or other numerical problems.
"""

Метод pipe вдохновлен конвейерами Unix и, в последнее время, dplyr и magrittr, которые представили популярный оператор (%>%) (чтение конвейера) для R. Реализация pipe здесь довольно чистая и органично вписывается в Python. Мы рекомендуем ознакомиться с исходным кодом pipe().

Применение функции по строкам или столбцам

Произвольные функции могут быть применены по осям DataFrame с помощью метода apply(), который, как и методы описательной статистики, принимает необязательный аргумент axis:

In [150]: df.apply(np.mean)
Out[150]: 
one      0.811094
two      1.360588
three    0.187958
dtype: float64

In [151]: df.apply(np.mean, axis=1)
Out[151]: 
a    1.583749
b    0.734929
c    1.133683
d   -0.166914
dtype: float64

In [152]: df.apply(lambda x: x.max() - x.min())
Out[152]: 
one      1.051928
two      1.632779
three    1.840607
dtype: float64

In [153]: df.apply(np.cumsum)
Out[153]: 
        one       two     three
a  1.394981  1.772517       NaN
b  1.738035  3.684640 -0.050390
c  2.433281  5.163008  1.177045
d       NaN  5.442353  0.563873

In [154]: df.apply(np.exp)
Out[154]: 
        one       two     three
a  4.034899  5.885648       NaN
b  1.409244  6.767440  0.950858
c  2.004201  4.385785  3.412466
d       NaN  1.322262  0.541630

Метод apply() также обрабатывает имена строковых методов.

In [155]: df.apply("mean")
Out[155]: 
one      0.811094
two      1.360588
three    0.187958
dtype: float64

In [156]: df.apply("mean", axis=1)
Out[156]: 
a    1.583749
b    0.734929
c    1.133683
d   -0.166914
dtype: float64

Тип возвращаемого значения функции, переданной методу apply(), влияет на тип конечного результата от DataFrame.apply для поведения по умолчанию:

  • Если применённая функция возвращает Series, конечный результат — DataFrame. Столбцы соответствуют индексу Series возвращённого применённой функцией.

  • Если применённая функция возвращает любой другой тип, конечный результат — Series.

Это поведение по умолчанию можно переопределить, используя result_type, который принимает три варианта: reduce, broadcast, и expand. Они определят, как спископодобные значения возвращаемых результатов расширяются (или нет) до DataFrame.

apply() в сочетании с хитростью может использоваться для ответа на многие вопросы о наборе данных. Например, предположим, что мы хотим извлечь дату, когда произошло максимальное значение для каждого столбца:

In [157]: tsdf = pd.DataFrame(
   .....:     np.random.randn(1000, 3),
   .....:     columns=["A", "B", "C"],
   .....:     index=pd.date_range("1/1/2000", periods=1000),
   .....: )
   .....: 

In [158]: tsdf.apply(lambda x: x.idxmax())
Out[158]: 
A   2000-08-06
B   2001-01-18
C   2001-07-18
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 [159]: tsdf
Out[159]: 
                   A         B         C
2000-01-01 -0.158131 -0.232466  0.321604
2000-01-02 -1.810340 -3.105758  0.433834
2000-01-03 -1.209847 -1.156793 -0.136794
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.653602  0.178875  1.008298
2000-01-09  1.007996  0.462824  0.254472
2000-01-10  0.307473  0.600337  1.643950

In [160]: tsdf.apply(pd.Series.interpolate)
Out[160]: 
                   A         B         C
2000-01-01 -0.158131 -0.232466  0.321604
2000-01-02 -1.810340 -3.105758  0.433834
2000-01-03 -1.209847 -1.156793 -0.136794
2000-01-04 -1.098598 -0.889659  0.092225
2000-01-05 -0.987349 -0.622526  0.321243
2000-01-06 -0.876100 -0.355392  0.550262
2000-01-07 -0.764851 -0.088259  0.779280
2000-01-08 -0.653602  0.178875  1.008298
2000-01-09  1.007996  0.462824  0.254472
2000-01-10  0.307473  0.600337  1.643950

Наконец, apply() принимает аргумент raw, который по умолчанию равен False, что преобразует каждую строку или столбец в Series перед применением функции. Если установить его в True, передаваемая функция вместо этого получит объект ndarray, что положительно сказывается на производительности, если вам не нужна функциональность индексации.

API агрегирования

API агрегирования позволяет выразить, возможно, несколько операций агрегирования одним кратким способом. Этот API похож в объектах pandas, см. API группировки, API окна и API ресемплирования. Точкой входа для агрегирования является DataFrame.aggregate(), или псевдоним DataFrame.agg().

Мы будем использовать аналогичную исходную таблицу из примера выше:

In [161]: tsdf = pd.DataFrame(
   .....:     np.random.randn(10, 3),
   .....:     columns=["A", "B", "C"],
   .....:     index=pd.date_range("1/1/2000", periods=10),
   .....: )
   .....: 

In [162]: tsdf.iloc[3:7] = np.nan

In [163]: tsdf
Out[163]: 
                   A         B         C
2000-01-01  1.257606  1.004194  0.167574
2000-01-02 -0.749892  0.288112 -0.757304
2000-01-03 -0.207550 -0.298599  0.116018
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.814347 -0.257623  0.869226
2000-01-09 -0.250663 -1.206601  0.896839
2000-01-10  2.169758 -1.333363  0.283157

Использование одной функции эквивалентно apply(). Вы также можете передавать именованные методы в виде строк. Они вернут Series агрегированного результата:

In [164]: tsdf.agg(np.sum)
Out[164]: 
A    3.033606
B   -1.803879
C    1.575510
dtype: float64

In [165]: tsdf.agg("sum")
Out[165]: 
A    3.033606
B   -1.803879
C    1.575510
dtype: float64

# these are equivalent to a ``.sum()`` because we are aggregating
# on a single function
In [166]: tsdf.sum()
Out[166]: 
A    3.033606
B   -1.803879
C    1.575510
dtype: float64

Одиночные агрегации по Series вернут скалярное значение:

In [167]: tsdf["A"].agg("sum")
Out[167]: 3.033606102414146

Агрегирование с несколькими функциями

Вы можете передать несколько аргументов агрегирования в виде списка. Результаты каждой из переданных функций будут строкой в результирующей DataFrame. Они естественным образом названы по имени функции агрегирования.

In [168]: tsdf.agg(["sum"])
Out[168]: 
            A         B        C
sum  3.033606 -1.803879  1.57551

Несколько функций дают несколько строк:

In [169]: tsdf.agg(["sum", "mean"])
Out[169]: 
             A         B         C
sum   3.033606 -1.803879  1.575510
mean  0.505601 -0.300647  0.262585

На Series, несколько функций возвращают Series, индексированную именами функций:

In [170]: tsdf["A"].agg(["sum", "mean"])
Out[170]: 
sum     3.033606
mean    0.505601
Name: A, dtype: float64

Передача функции lambda вернёт <lambda> именованную строку:

In [171]: tsdf["A"].agg(["sum", lambda x: x.mean()])
Out[171]: 
sum         3.033606
<lambda>    0.505601
Name: A, dtype: float64

Передача именованной функции вернёт имя для строки:

In [172]: def mymean(x):
   .....:     return x.mean()
   .....: 

In [173]: tsdf["A"].agg(["sum", mymean])
Out[173]: 
sum       3.033606
mymean    0.505601
Name: A, dtype: float64

Агрегирование со словарем

Передача словаря имён столбцов в скалярное значение или список скалярных значений, для DataFrame.agg позволяет настроить, какие функции применяются к каким столбцам. Обратите внимание, что результаты не упорядочены каким-либо определённым образом, вы можете использовать OrderedDict вместо этого, чтобы гарантировать порядок.

In [174]: tsdf.agg({"A": "mean", "B": "sum"})
Out[174]: 
A    0.505601
B   -1.803879
dtype: float64

Передача списка-подобного объекта создаст DataFrame результат. Вы получите матрицеподобный результат всех агрегаторов. Результат будет состоять из всех уникальных функций. Те, что не указаны для конкретного столбца, будут NaN:

In [175]: tsdf.agg({"A": ["mean", "min"], "B": "sum"})
Out[175]: 
             A         B
mean  0.505601       NaN
min  -0.749892       NaN
sum        NaN -1.803879

Смешанные типы данных

Устарело начиная с версии 1.4.0: Попытка определить, какие столбцы нельзя агрегировать, и безмолвная отбраковка этих столбцов из результатов устарела и будет удалена в будущей версии. Если любая часть столбцов или операций потерпит неудачу, вызов .agg вызовет исключение.

При наличии смешанных типов данных, которые нельзя агрегировать, .agg будет принимать только допустимые агрегации. Это аналогично тому, как работает .groupby.agg.

In [176]: mdf = pd.DataFrame(
   .....:     {
   .....:         "A": [1, 2, 3],
   .....:         "B": [1.0, 2.0, 3.0],
   .....:         "C": ["foo", "bar", "baz"],
   .....:         "D": pd.date_range("20130101", periods=3),
   .....:     }
   .....: )
   .....: 

In [177]: mdf.dtypes
Out[177]: 
A             int64
B           float64
C            object
D    datetime64[ns]
dtype: object
In [178]: mdf.agg(["min", "sum"])
Out[178]: 
     A    B          C          D
min  1  1.0        bar 2013-01-01
sum  6  6.0  foobarbaz        NaT

Пользовательская функция describe

С помощью .agg() можно легко создать пользовательскую функцию describe, аналогичную встроенной функции describe.

In [179]: from functools import partial

In [180]: q_25 = partial(pd.Series.quantile, q=0.25)

In [181]: q_25.__name__ = "25%"

In [182]: q_75 = partial(pd.Series.quantile, q=0.75)

In [183]: q_75.__name__ = "75%"

In [184]: tsdf.agg(["count", "mean", "std", "min", q_25, "median", q_75, "max"])
Out[184]: 
               A         B         C
count   6.000000  6.000000  6.000000
mean    0.505601 -0.300647  0.262585
std     1.103362  0.887508  0.606860
min    -0.749892 -1.333363 -0.757304
25%    -0.239885 -0.979600  0.128907
median  0.303398 -0.278111  0.225365
75%     1.146791  0.151678  0.722709
max     2.169758  1.004194  0.896839

API преобразования

Метод transform() возвращает объект, индексированный так же, как и исходный (того же размера). Этот API позволяет вам предоставлять несколько операций одновременно, а не по одной. Его API очень похож на API .agg.

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

In [185]: tsdf = pd.DataFrame(
   .....:     np.random.randn(10, 3),
   .....:     columns=["A", "B", "C"],
   .....:     index=pd.date_range("1/1/2000", periods=10),
   .....: )
   .....: 

In [186]: tsdf.iloc[3:7] = np.nan

In [187]: tsdf
Out[187]: 
                   A         B         C
2000-01-01 -0.428759 -0.864890 -0.675341
2000-01-02 -0.168731  1.338144 -1.279321
2000-01-03 -1.621034  0.438107  0.903794
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.254374 -1.240447 -0.201052
2000-01-09 -0.157795  0.791197 -1.144209
2000-01-10 -0.030876  0.371900  0.061932

Преобразуйте всю таблицу. .transform() позволяет передавать функции в виде: функции NumPy, имени функции в виде строки или пользовательской функции.

In [188]: tsdf.transform(np.abs)
Out[188]: 
                   A         B         C
2000-01-01  0.428759  0.864890  0.675341
2000-01-02  0.168731  1.338144  1.279321
2000-01-03  1.621034  0.438107  0.903794
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.254374  1.240447  0.201052
2000-01-09  0.157795  0.791197  1.144209
2000-01-10  0.030876  0.371900  0.061932

In [189]: tsdf.transform("abs")
Out[189]: 
                   A         B         C
2000-01-01  0.428759  0.864890  0.675341
2000-01-02  0.168731  1.338144  1.279321
2000-01-03  1.621034  0.438107  0.903794
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.254374  1.240447  0.201052
2000-01-09  0.157795  0.791197  1.144209
2000-01-10  0.030876  0.371900  0.061932

In [190]: tsdf.transform(lambda x: x.abs())
Out[190]: 
                   A         B         C
2000-01-01  0.428759  0.864890  0.675341
2000-01-02  0.168731  1.338144  1.279321
2000-01-03  1.621034  0.438107  0.903794
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.254374  1.240447  0.201052
2000-01-09  0.157795  0.791197  1.144209
2000-01-10  0.030876  0.371900  0.061932

Здесь transform() получил одну функцию; это эквивалентно применению ufunc.

In [191]: np.abs(tsdf)
Out[191]: 
                   A         B         C
2000-01-01  0.428759  0.864890  0.675341
2000-01-02  0.168731  1.338144  1.279321
2000-01-03  1.621034  0.438107  0.903794
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.254374  1.240447  0.201052
2000-01-09  0.157795  0.791197  1.144209
2000-01-10  0.030876  0.371900  0.061932

Передача одной функции в .transform() с Series вернёт одно Series в качестве результата.

In [192]: tsdf["A"].transform(np.abs)
Out[192]: 
2000-01-01    0.428759
2000-01-02    0.168731
2000-01-03    1.621034
2000-01-04         NaN
2000-01-05         NaN
2000-01-06         NaN
2000-01-07         NaN
2000-01-08    0.254374
2000-01-09    0.157795
2000-01-10    0.030876
Freq: D, Name: A, dtype: float64

Преобразование с несколькими функциями

Передача нескольких функций вернёт DataFrame с многоуровневым индексом столбцов. Первый уровень будет именами столбцов исходной таблицы, второй уровень – именами преобразующих функций.

In [193]: tsdf.transform([np.abs, lambda x: x + 1])
Out[193]: 
                   A                   B                   C          
            absolute  <lambda>  absolute  <lambda>  absolute  <lambda>
2000-01-01  0.428759  0.571241  0.864890  0.135110  0.675341  0.324659
2000-01-02  0.168731  0.831269  1.338144  2.338144  1.279321 -0.279321
2000-01-03  1.621034 -0.621034  0.438107  1.438107  0.903794  1.903794
2000-01-04       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-08  0.254374  1.254374  1.240447 -0.240447  0.201052  0.798948
2000-01-09  0.157795  0.842205  0.791197  1.791197  1.144209 -0.144209
2000-01-10  0.030876  0.969124  0.371900  1.371900  0.061932  1.061932

Передача нескольких функций в Series вернёт DataFrame. Результирующие имена столбцов будут именами преобразующих функций.

In [194]: tsdf["A"].transform([np.abs, lambda x: x + 1])
Out[194]: 
            absolute  <lambda>
2000-01-01  0.428759  0.571241
2000-01-02  0.168731  0.831269
2000-01-03  1.621034 -0.621034
2000-01-04       NaN       NaN
2000-01-05       NaN       NaN
2000-01-06       NaN       NaN
2000-01-07       NaN       NaN
2000-01-08  0.254374  1.254374
2000-01-09  0.157795  0.842205
2000-01-10  0.030876  0.969124

Преобразование со словарем

Передача словаря функций позволит выборочно преобразовывать каждый столбец.

In [195]: tsdf.transform({"A": np.abs, "B": lambda x: x + 1})
Out[195]: 
                   A         B
2000-01-01  0.428759  0.135110
2000-01-02  0.168731  2.338144
2000-01-03  1.621034  1.438107
2000-01-04       NaN       NaN
2000-01-05       NaN       NaN
2000-01-06       NaN       NaN
2000-01-07       NaN       NaN
2000-01-08  0.254374 -0.240447
2000-01-09  0.157795  1.791197
2000-01-10  0.030876  1.371900

Передача словаря списков создаст DataFrame с многоуровневым индексом столбцов этими выборочными преобразованиями.

In [196]: tsdf.transform({"A": np.abs, "B": [lambda x: x + 1, "sqrt"]})
Out[196]: 
                   A         B          
            absolute  <lambda>      sqrt
2000-01-01  0.428759  0.135110       NaN
2000-01-02  0.168731  2.338144  1.156782
2000-01-03  1.621034  1.438107  0.661897
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.254374 -0.240447       NaN
2000-01-09  0.157795  1.791197  0.889493
2000-01-10  0.030876  1.371900  0.609836

Применение поэлементных функций

Поскольку не все функции могут быть векторизованы (принимать массивы NumPy и возвращать другой массив или значение), методы applymap() для DataFrame и аналогично map() для Series принимают любую функцию Python, которая принимает одно значение и возвращает одно значение. Например:

In [197]: df4
Out[197]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [198]: def f(x):
   .....:     return len(str(x))
   .....: 

In [199]: df4["one"].map(f)
Out[199]: 
a    18
b    19
c    18
d     3
Name: one, dtype: int64

In [200]: df4.applymap(f)
Out[200]: 
   one  two  three
a   18   17      3
b   19   18     20
c   18   18     16
d    3   19     19

Series.map() имеет дополнительную функцию; она может быть использована для лёгкого «связывания» или «отображения» значений, определённых во вспомогательной серии. Это тесно связано с функциональностью слияния/соединения:

In [201]: s = pd.Series(
   .....:     ["six", "seven", "six", "seven", "six"], index=["a", "b", "c", "d", "e"]
   .....: )
   .....: 

In [202]: t = pd.Series({"six": 6.0, "seven": 7.0})

In [203]: s
Out[203]: 
a      six
b    seven
c      six
d    seven
e      six
dtype: object

In [204]: s.map(t)
Out[204]: 
a    6.0
b    7.0
c    6.0
d    7.0
e    6.0
dtype: float64
END_OF_DOCUMENT_MARKER

Переиндексация и изменение меток

reindex() — это основной метод выравнивания данных в pandas. Он используется для реализации почти всех других функций, опирающихся на функциональность выравнивания по меткам. Переиндексация означает приведение данных к соответствию заданному набору меток по определённой оси. Это достигается несколькими способами:

  • Переупорядочивает существующие данные для соответствия новому набору меток

  • Вставляет маркеры отсутствующих значений (NA) в позиции меток, где для данной метки не было данных

  • Если указано, заполняет данные для пропущенных меток с помощью логики (очень актуально при работе с временными рядами)

Вот простой пример:

In [205]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [206]: s
Out[206]: 
a    1.695148
b    1.328614
c    1.234686
d   -0.385845
e   -1.326508
dtype: float64

In [207]: s.reindex(["e", "b", "f", "d"])
Out[207]: 
e   -1.326508
b    1.328614
f         NaN
d   -0.385845
dtype: float64

Здесь метка f не содержалась в Series и поэтому отображается как NaN в результате.

С DataFrame вы можете одновременно переиндексировать индекс и столбцы:

In [208]: df
Out[208]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [209]: df.reindex(index=["c", "f", "b"], columns=["three", "two", "one"])
Out[209]: 
      three       two       one
c  1.227435  1.478369  0.695246
f       NaN       NaN       NaN
b -0.050390  1.912123  0.343054

Вы также можете использовать reindex с ключевым словом axis:

In [210]: df.reindex(["c", "f", "b"], axis="index")
Out[210]: 
        one       two     three
c  0.695246  1.478369  1.227435
f       NaN       NaN       NaN
b  0.343054  1.912123 -0.050390

Обратите внимание, что объекты Index, содержащие фактические метки осей, могут быть **общими** между объектами. Так что если у нас есть Series и DataFrame, можно сделать следующее:

In [211]: rs = s.reindex(df.index)

In [212]: rs
Out[212]: 
a    1.695148
b    1.328614
c    1.234686
d   -0.385845
dtype: float64

In [213]: rs.index is df.index
Out[213]: True

Это означает, что индекс переиндексированной Series — это тот же объект Python, что и индекс DataFrame.

DataFrame.reindex() также поддерживает вызов в стиле «ось», где вы указываете один аргумент labels и axis к которому он применяется.

In [214]: df.reindex(["c", "f", "b"], axis="index")
Out[214]: 
        one       two     three
c  0.695246  1.478369  1.227435
f       NaN       NaN       NaN
b  0.343054  1.912123 -0.050390

In [215]: df.reindex(["three", "two", "one"], axis="columns")
Out[215]: 
      three       two       one
a       NaN  1.772517  1.394981
b -0.050390  1.912123  0.343054
c  1.227435  1.478369  0.695246
d -0.613172  0.279344       NaN

См. также

Многоуровневые индексы / Расширенный индексирование — ещё более лаконичный способ переиндексации.

Примечание

При написании чувствительного к производительности кода есть веская причина потратить некоторое время на изучение переиндексации: **многие операции выполняются быстрее на предварительно выровненных данных**. Добавление двух невыровненных DataFrame внутри вызывает шаг переиндексации. При исследовательском анализе вы вряд ли заметите разницу (потому что reindex был существенно оптимизирован), но когда важны циклы процессора, добавление нескольких явных вызовов reindex здесь и там может повлиять на результат.

Переиндексация для выравнивания с другим объектом

Вы можете взять объект и переиндексировать его оси так, чтобы они имели те же метки, что и другой объект. Хотя синтаксис для этого прост, но и громоздок. Это достаточно распространённая операция, поэтому метод reindex_like() разработан для упрощения этой задачи:

In [216]: df2
Out[216]: 
        one       two
a  1.394981  1.772517
b  0.343054  1.912123
c  0.695246  1.478369

In [217]: df3
Out[217]: 
        one       two
a  0.583888  0.051514
b -0.468040  0.191120
c -0.115848 -0.242634

In [218]: df.reindex_like(df2)
Out[218]: 
        one       two
a  1.394981  1.772517
b  0.343054  1.912123
c  0.695246  1.478369

Выравнивание объектов с помощью align

Метод align() — самый быстрый способ одновременного выравнивания двух объектов. Он поддерживает аргумент join (связанный с соединением и слиянием):

  • join='outer': взять объединение индексов (по умолчанию)

  • join='left': использовать индекс вызываемого объекта

  • join='right': использовать индекс переданного объекта

  • join='inner': пересечь индексы

Он возвращает кортеж с обеими переиндексированными Series:

In [219]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [220]: s1 = s[:4]

In [221]: s2 = s[1:]

In [222]: s1.align(s2)
Out[222]: 
(a   -0.186646
 b   -1.692424
 c   -0.303893
 d   -1.425662
 e         NaN
 dtype: float64,
 a         NaN
 b   -1.692424
 c   -0.303893
 d   -1.425662
 e    1.114285
 dtype: float64)

In [223]: s1.align(s2, join="inner")
Out[223]: 
(b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64,
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64)

In [224]: s1.align(s2, join="left")
Out[224]: 
(a   -0.186646
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64,
 a         NaN
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64)

Для DataFrame метод join будет применяться к индексу и столбцам по умолчанию:

In [225]: df.align(df2, join="inner")
Out[225]: 
(        one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369,
         one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369)

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

In [226]: df.align(df2, join="inner", axis=0)
Out[226]: 
(        one       two     three
 a  1.394981  1.772517       NaN
 b  0.343054  1.912123 -0.050390
 c  0.695246  1.478369  1.227435,
         one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369)

Если вы передаёте Series в DataFrame.align(), вы можете выбрать выравнивание обоих объектов по индексу DataFrame или столбцам, используя аргумент axis:

In [227]: df.align(df2.iloc[0], axis=1)
Out[227]: 
(        one     three       two
 a  1.394981       NaN  1.772517
 b  0.343054 -0.050390  1.912123
 c  0.695246  1.227435  1.478369
 d       NaN -0.613172  0.279344,
 one      1.394981
 three         NaN
 two      1.772517
 Name: a, dtype: float64)

Заполнение при переиндексации

reindex() принимает необязательный параметр method, который является методом заполнения, выбранным из следующей таблицы:

Метод

Действие

pad / ffill

Заполнить значения вперёд

bfill / backfill

Заполнить значения назад

nearest

Заполнить от ближайшего значения индекса

Мы иллюстрируем эти методы заполнения на простой Series:

In [228]: rng = pd.date_range("1/3/2000", periods=8)

In [229]: ts = pd.Series(np.random.randn(8), index=rng)

In [230]: ts2 = ts[[0, 3, 6]]

In [231]: ts
Out[231]: 
2000-01-03    0.183051
2000-01-04    0.400528
2000-01-05   -0.015083
2000-01-06    2.395489
2000-01-07    1.414806
2000-01-08    0.118428
2000-01-09    0.733639
2000-01-10   -0.936077
Freq: D, dtype: float64

In [232]: ts2
Out[232]: 
2000-01-03    0.183051
2000-01-06    2.395489
2000-01-09    0.733639
Freq: 3D, dtype: float64

In [233]: ts2.reindex(ts.index)
Out[233]: 
2000-01-03    0.183051
2000-01-04         NaN
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07         NaN
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10         NaN
Freq: D, dtype: float64

In [234]: ts2.reindex(ts.index, method="ffill")
Out[234]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    0.183051
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    2.395489
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

In [235]: ts2.reindex(ts.index, method="bfill")
Out[235]: 
2000-01-03    0.183051
2000-01-04    2.395489
2000-01-05    2.395489
2000-01-06    2.395489
2000-01-07    0.733639
2000-01-08    0.733639
2000-01-09    0.733639
2000-01-10         NaN
Freq: D, dtype: float64

In [236]: ts2.reindex(ts.index, method="nearest")
Out[236]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    2.395489
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    0.733639
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

Эти методы требуют, чтобы индексы были **упорядоченными** в порядке возрастания или убывания.

Обратите внимание, что тот же результат можно было бы получить, используя fillna (кроме method='nearest') или interpolate:

In [237]: ts2.reindex(ts.index).fillna(method="ffill")
Out[237]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    0.183051
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    2.395489
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

reindex() выбросит ошибку ValueError, если индекс не монотонно возрастает или убывает. fillna() и interpolate() не будут выполнять никаких проверок на порядок индекса.

Ограничения при заполнении при переиндексации

Аргументы limit и tolerance предоставляют дополнительный контроль над заполнением при переиндексации. Limit определяет максимальное количество последовательных совпадений:

In [238]: ts2.reindex(ts.index, method="ffill", limit=1)
Out[238]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

В отличие от этого, tolerance определяет максимальное расстояние между значениями индекса и индексатора:

In [239]: ts2.reindex(ts.index, method="ffill", tolerance="1 day")
Out[239]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

Заметьте, что при использовании на DatetimeIndex, TimedeltaIndex или PeriodIndex, tolerance будет приведено к Timedelta если это возможно. Это позволяет вам указать tolerance с соответствующими строками.

Удаление меток с оси

Метод, тесно связанный с reindex — функция drop(). Она удаляет набор меток с оси:

In [240]: df
Out[240]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [241]: df.drop(["a", "d"], axis=0)
Out[241]: 
        one       two     three
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435

In [242]: df.drop(["one"], axis=1)
Out[242]: 
        two     three
a  1.772517       NaN
b  1.912123 -0.050390
c  1.478369  1.227435
d  0.279344 -0.613172

Обратите внимание, что следующее также работает, но немного менее очевидно/чисто:

In [243]: df.reindex(df.index.difference(["a", "d"]))
Out[243]: 
        one       two     three
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435

Переименование/сопоставление меток

Метод rename() позволяет переименовать ось на основе сопоставления (словаря или Series) или произвольной функции.

In [244]: s
Out[244]: 
a   -0.186646
b   -1.692424
c   -0.303893
d   -1.425662
e    1.114285
dtype: float64

In [245]: s.rename(str.upper)
Out[245]: 
A   -0.186646
B   -1.692424
C   -0.303893
D   -1.425662
E    1.114285
dtype: float64

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

In [246]: df.rename(
   .....:     columns={"one": "foo", "two": "bar"},
   .....:     index={"a": "apple", "b": "banana", "d": "durian"},
   .....: )
   .....: 
Out[246]: 
             foo       bar     three
apple   1.394981  1.772517       NaN
banana  0.343054  1.912123 -0.050390
c       0.695246  1.478369  1.227435
durian       NaN  0.279344 -0.613172

Если в сопоставлении нет метки столбца/индекса, она не переименовывается. Обратите внимание, что дополнительные метки в сопоставлении не вызывают ошибки.

DataFrame.rename() также поддерживает вызов в стиле «ось», где вы указываете одну mapper и axis для применения этого сопоставления.

In [247]: df.rename({"one": "foo", "two": "bar"}, axis="columns")
Out[247]: 
        foo       bar     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [248]: df.rename({"a": "apple", "b": "banana", "d": "durian"}, axis="index")
Out[248]: 
             one       two     three
apple   1.394981  1.772517       NaN
banana  0.343054  1.912123 -0.050390
c       0.695246  1.478369  1.227435
durian       NaN  0.279344 -0.613172

Метод rename() также предоставляет параметр inplace, который по умолчанию False и копирует исходные данные. Передайте inplace=True для переименования данных на месте.

Наконец, rename() также принимает скаляр или список для изменения атрибута Series.name.

In [249]: s.rename("scalar-name")
Out[249]: 
a   -0.186646
b   -1.692424
c   -0.303893
d   -1.425662
e    1.114285
Name: scalar-name, dtype: float64

Методы DataFrame.rename_axis() и Series.rename_axis() позволяют изменить определенные имена MultiIndex (в отличие от меток).

In [250]: df = pd.DataFrame(
   .....:     {"x": [1, 2, 3, 4, 5, 6], "y": [10, 20, 30, 40, 50, 60]},
   .....:     index=pd.MultiIndex.from_product(
   .....:         [["a", "b", "c"], [1, 2]], names=["let", "num"]
   .....:     ),
   .....: )
   .....: 

In [251]: df
Out[251]: 
         x   y
let num       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

In [252]: df.rename_axis(index={"let": "abc"})
Out[252]: 
         x   y
abc num       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

In [253]: df.rename_axis(index=str.upper)
Out[253]: 
         x   y
LET NUM       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

Итерация

Поведение базовой итерации по объектам pandas зависит от типа. При итерации по Series она рассматривается как массивоподобная, и базовая итерация производит значения. DataFrames следуют соглашению dict-подобной итерации по «ключам» объектов.

Короче говоря, базовая итерация (for i in object) производит:

  • Series: значения

  • DataFrame: метки столбцов

Таким образом, например, итерация по DataFrame дает вам имена столбцов:

In [254]: df = pd.DataFrame(
   .....:     {"col1": np.random.randn(3), "col2": np.random.randn(3)}, index=["a", "b", "c"]
   .....: )
   .....: 

In [255]: for col in df:
   .....:     print(col)
   .....: 
col1
col2

Объекты pandas также имеют dict-подобный метод items() для итерации по парам (ключ, значение).

Для итерации по строкам DataFrame можно использовать следующие методы:

  • iterrows(): Итерируется по строкам DataFrame в виде пар (индекс, Series). Это преобразует строки в объекты Series, что может изменить типы данных и имеет некоторые последствия для производительности.

  • itertuples(): Итерируется по строкам DataFrame как namedtuples значений. Это намного быстрее, чем iterrows(), и в большинстве случаев предпочтительнее для итерации по значениям DataFrame.

Предупреждение

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

  • Ищите векторизованное решение: многие операции можно выполнить с помощью встроенных методов или функций NumPy, (булевых) индексов и т. д.

  • Если у вас есть функция, которая не может работать со всем DataFrame/Series сразу, лучше использовать apply() вместо итерации по значениям. См. документацию по применению функций.

  • Если вам необходимо выполнять итеративные манипуляции со значениями, но производительность важна, рассмотрите возможность написания внутреннего цикла с помощью cython или numba. См. раздел улучшение производительности для примеров этого подхода.

Предупреждение

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

Например, в следующем случае установка значения не оказывает никакого влияния:

In [256]: df = pd.DataFrame({"a": [1, 2, 3], "b": ["a", "b", "c"]})

In [257]: for index, row in df.iterrows():
   .....:     row["a"] = 10
   .....: 

In [258]: df
Out[258]: 
   a  b
0  1  a
1  2  b
2  3  c

items

Согласуясь с dict-подобным интерфейсом, items() итерируется по парам ключ-значение:

  • Series: пары (индекс, скалярное значение)

  • DataFrame: пары (столбец, Series)

Например:

In [259]: for label, ser in df.items():
   .....:     print(label)
   .....:     print(ser)
   .....: 
a
0    1
1    2
2    3
Name: a, dtype: int64
b
0    a
1    b
2    c
Name: b, dtype: object

iterrows

iterrows() позволяет итерироваться по строкам DataFrame как по объектам Series. Он возвращает итератор, генерирующий каждое значение индекса вместе с Series, содержащей данные в каждой строке:

In [260]: for row_index, row in df.iterrows():
   .....:     print(row_index, row, sep="\n")
   .....: 
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 [261]: df_orig = pd.DataFrame([[1, 1.5]], columns=["int", "float"])

In [262]: df_orig.dtypes
Out[262]: 
int        int64
float    float64
dtype: object

In [263]: row = next(df_orig.iterrows())[1]

In [264]: row
Out[264]: 
int      1.0
float    1.5
Name: 0, dtype: float64

Все значения в row, возвращенные как Series, теперь преобразуются в float, а также исходное целое значение в столбце x.

In [265]: row["int"].dtype
Out[265]: dtype('float64')

In [266]: df_orig["int"].dtype
Out[266]: dtype('int64')

Для сохранения типов данных при итерации по строкам лучше использовать itertuples(), который возвращает namedtuples значений и который, как правило, намного быстрее, чем iterrows().

Например, искусственный способ транспонировать DataFrame:

In [267]: df2 = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})

In [268]: print(df2)
   x  y
0  1  4
1  2  5
2  3  6

In [269]: print(df2.T)
   0  1  2
x  1  2  3
y  4  5  6

In [270]: df2_t = pd.DataFrame({idx: values for idx, values in df2.iterrows()})

In [271]: print(df2_t)
   0  1  2
x  1  2  3
y  4  5  6

itertuples

Метод itertuples() вернет итератор, генерирующий namedtuple для каждой строки в DataFrame. Первый элемент кортежа будет значением индекса строки, а оставшиеся значения — значениями строки.

Например:

In [272]: 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/период. Это вернёт Series, индексированную как существующая Series.

# datetime
In [273]: s = pd.Series(pd.date_range("20130101 09:10:12", periods=4))

In [274]: s
Out[274]: 
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 [275]: s.dt.hour
Out[275]: 
0    9
1    9
2    9
3    9
dtype: int64

In [276]: s.dt.second
Out[276]: 
0    12
1    12
2    12
3    12
dtype: int64

In [277]: s.dt.day
Out[277]: 
0    1
1    2
2    3
3    4
dtype: int64

Это позволяет использовать выражения, подобные этому:

In [278]: s[s.dt.day == 2]
Out[278]: 
1   2013-01-02 09:10:12
dtype: datetime64[ns]

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

In [279]: stz = s.dt.tz_localize("US/Eastern")

In [280]: stz
Out[280]: 
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 [281]: stz.dt.tz
Out[281]: <DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>

Вы также можете объединять такие типы операций:

In [282]: s.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[282]: 
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 [283]: s = pd.Series(pd.date_range("20130101", periods=4))

In [284]: s
Out[284]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
3   2013-01-04
dtype: datetime64[ns]

In [285]: s.dt.strftime("%Y/%m/%d")
Out[285]: 
0    2013/01/01
1    2013/01/02
2    2013/01/03
3    2013/01/04
dtype: object
# PeriodIndex
In [286]: s = pd.Series(pd.period_range("20130101", periods=4))

In [287]: s
Out[287]: 
0    2013-01-01
1    2013-01-02
2    2013-01-03
3    2013-01-04
dtype: period[D]

In [288]: s.dt.strftime("%Y/%m/%d")
Out[288]: 
0    2013/01/01
1    2013/01/02
2    2013/01/03
3    2013/01/04
dtype: object

Аксессор .dt работает для типов period и timedelta.

# period
In [289]: s = pd.Series(pd.period_range("20130101", periods=4, freq="D"))

In [290]: s
Out[290]: 
0    2013-01-01
1    2013-01-02
2    2013-01-03
3    2013-01-04
dtype: period[D]

In [291]: s.dt.year
Out[291]: 
0    2013
1    2013
2    2013
3    2013
dtype: int64

In [292]: s.dt.day
Out[292]: 
0    1
1    2
2    3
3    4
dtype: int64
# timedelta
In [293]: s = pd.Series(pd.timedelta_range("1 day 00:00:05", periods=4, freq="s"))

In [294]: s
Out[294]: 
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 [295]: s.dt.days
Out[295]: 
0    1
1    1
2    1
3    1
dtype: int64

In [296]: s.dt.seconds
Out[296]: 
0    5
1    6
2    7
3    8
dtype: int64

In [297]: s.dt.components
Out[297]: 
   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 при обращении с не datetime-подобными значениями.

Векторизованные методы для строк

Series оснащены набором методов обработки строк, которые упрощают работу с каждым элементом массива. Возможно, самое важное, что эти методы автоматически исключают пропущенные/NA значения. К ним можно получить доступ через атрибут str Series, и они обычно имеют названия, соответствующие эквивалентным (скалярным) встроенным методам для строк. Например:

In [298]: s = pd.Series(
   .....:     ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
   .....: )
   .....: 

In [299]: s.str.lower()
Out[299]: 
0       a
1       b
2       c
3    aaba
4    baca
5    <NA>
6    caba
7     dog
8     cat
dtype: string

Также предоставляются мощные методы для сопоставления с шаблонами, но обратите внимание, что сопоставление с шаблонами по умолчанию использует регулярные выражения (и в некоторых случаях всегда использует их).

Примечание

До pandas 1.0 методы для строк были доступны только для Series типа object -dtype Series. В pandas 1.0 был добавлен тип StringDtype, специально предназначенный для строк. См. Типы данных для текста для получения дополнительной информации.

Для получения полного описания см. Векторизованные методы для строк.

Сортировка

pandas поддерживает три вида сортировки: сортировка по меткам индекса, сортировка по значениям столбцов и сортировка по комбинации обоих.

По индексу

Для сортировки pandas объекта по его уровням индекса используются методы Series.sort_index() и DataFrame.sort_index().

In [300]: 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 [301]: unsorted_df = df.reindex(
   .....:     index=["a", "d", "c", "b"], columns=["three", "two", "one"]
   .....: )
   .....: 

In [302]: unsorted_df
Out[302]: 
      three       two       one
a       NaN -1.152244  0.562973
d -0.252916 -0.109597       NaN
c  1.273388 -0.167123  0.640382
b -0.098217  0.009797 -1.299504

# DataFrame
In [303]: unsorted_df.sort_index()
Out[303]: 
      three       two       one
a       NaN -1.152244  0.562973
b -0.098217  0.009797 -1.299504
c  1.273388 -0.167123  0.640382
d -0.252916 -0.109597       NaN

In [304]: unsorted_df.sort_index(ascending=False)
Out[304]: 
      three       two       one
d -0.252916 -0.109597       NaN
c  1.273388 -0.167123  0.640382
b -0.098217  0.009797 -1.299504
a       NaN -1.152244  0.562973

In [305]: unsorted_df.sort_index(axis=1)
Out[305]: 
        one     three       two
a  0.562973       NaN -1.152244
d       NaN -0.252916 -0.109597
c  0.640382  1.273388 -0.167123
b -1.299504 -0.098217  0.009797

# Series
In [306]: unsorted_df["three"].sort_index()
Out[306]: 
a         NaN
b   -0.098217
c    1.273388
d   -0.252916
Name: three, dtype: float64

Новое в версии 1.1.0.

Сортировка по индексу также поддерживает параметр key, который принимает функцию вызова для применения к индексу, который сортируется. Для объектов MultiIndex, ключ применяется к каждому уровню к уровням, указанным в level.

In [307]: s1 = pd.DataFrame({"a": ["B", "a", "C"], "b": [1, 2, 3], "c": [2, 3, 4]}).set_index(
   .....:     list("ab")
   .....: )
   .....: 

In [308]: s1
Out[308]: 
     c
a b   
B 1  2
a 2  3
C 3  4
In [309]: s1.sort_index(level="a")
Out[309]: 
     c
a b   
B 1  2
C 3  4
a 2  3

In [310]: s1.sort_index(level="a", key=lambda idx: idx.str.lower())
Out[310]: 
     c
a b   
a 2  3
B 1  2
C 3  4

Дополнительную информацию о сортировке по ключу по значению см. в разделе сортировка по значениям.

По значениям

Для сортировки Series по её значениям используется метод Series.sort_values(). Метод DataFrame.sort_values() используется для сортировки DataFrame по значениям столбцов или строк. Необязательный параметр by к методу DataFrame.sort_values() может быть использован для указания одного или нескольких столбцов, используемых для определения порядка сортировки.

In [311]: df1 = pd.DataFrame(
   .....:     {"one": [2, 1, 1, 1], "two": [1, 3, 2, 4], "three": [5, 4, 3, 2]}
   .....: )
   .....: 

In [312]: df1.sort_values(by="two")
Out[312]: 
   one  two  three
0    2    1      5
2    1    2      3
1    1    3      4
3    1    4      2

Параметр by может принимать список имён столбцов, например:

In [313]: df1[["one", "two", "three"]].sort_values(by=["one", "two"])
Out[313]: 
   one  two  three
2    1    2      3
1    1    3      4
3    1    4      2
0    2    1      5

Эти методы имеют специальное обращение с значениями NA через аргумент na_position:

In [314]: s[2] = np.nan

In [315]: s.sort_values()
Out[315]: 
0       A
3    Aaba
1       B
4    Baca
6    CABA
8     cat
7     dog
2    <NA>
5    <NA>
dtype: string

In [316]: s.sort_values(na_position="first")
Out[316]: 
2    <NA>
5    <NA>
0       A
3    Aaba
1       B
4    Baca
6    CABA
8     cat
7     dog
dtype: string

Новое в версии 1.1.0.

Сортировка также поддерживает параметр key, который принимает функцию вызова для применения к сортируемым значениям.

In [317]: s1 = pd.Series(["B", "a", "C"])
In [318]: s1.sort_values()
Out[318]: 
0    B
2    C
1    a
dtype: object

In [319]: s1.sort_values(key=lambda x: x.str.lower())
Out[319]: 
1    a
0    B
2    C
dtype: object

Функции key будет передан Series значений и она должна вернуть Series или массив той же формы с преобразованными значениями. Для объектов DataFrame, ключ применяется к каждому столбцу, поэтому ключ по-прежнему должен ожидать Series и возвращать Series, например.

In [320]: df = pd.DataFrame({"a": ["B", "a", "C"], "b": [1, 2, 3]})
In [321]: df.sort_values(by="a")
Out[321]: 
   a  b
0  B  1
2  C  3
1  a  2

In [322]: df.sort_values(by="a", key=lambda col: col.str.lower())
Out[322]: 
   a  b
1  a  2
0  B  1
2  C  3

Имя или тип каждого столбца может быть использован для применения различных функций к различным столбцам.

По индексам и значениям

Строки, переданные в качестве параметра by к методу DataFrame.sort_values(), могут ссылаться на имена столбцов или уровни индекса.

# Build MultiIndex
In [323]: idx = pd.MultiIndex.from_tuples(
   .....:     [("a", 1), ("a", 2), ("a", 2), ("b", 2), ("b", 1), ("b", 1)]
   .....: )
   .....: 

In [324]: idx.names = ["first", "second"]

# Build DataFrame
In [325]: df_multi = pd.DataFrame({"A": np.arange(6, 0, -1)}, index=idx)

In [326]: df_multi
Out[326]: 
              A
first second   
a     1       6
      2       5
      2       4
b     2       3
      1       2
      1       1

Сортировка по ‘second’ (индекс) и ‘A’ (столбец)

In [327]: df_multi.sort_values(by=["second", "A"])
Out[327]: 
              A
first second   
b     1       1
      1       2
a     1       6
b     2       3
a     2       4
      2       5

Примечание

Если строка соответствует имени столбца и имени уровня индекса, то выводится предупреждение, и приоритет отдаётся столбцу. В будущей версии это приведёт к ошибке неоднозначности.

searchsorted

Series имеет метод searchsorted(), который работает аналогично numpy.ndarray.searchsorted().

In [328]: ser = pd.Series([1, 2, 3])

In [329]: ser.searchsorted([0, 3])
Out[329]: array([0, 2])

In [330]: ser.searchsorted([0, 4])
Out[330]: array([0, 3])

In [331]: ser.searchsorted([1, 3], side="right")
Out[331]: array([1, 3])

In [332]: ser.searchsorted([1, 3], side="left")
Out[332]: array([0, 2])

In [333]: ser = pd.Series([3, 1, 2])

In [334]: ser.searchsorted([0, 3], sorter=np.argsort(ser))
Out[334]: array([0, 2])

наименьшие / наибольшие значения

Series имеет методы nsmallest() и nlargest(), которые возвращают наименьшие или наибольшие значения \(n\). Для больших Series это может быть намного быстрее, чем сортировка всей Series и вызов head(n) на результате.

In [335]: s = pd.Series(np.random.permutation(10))

In [336]: s
Out[336]: 
0    2
1    0
2    3
3    7
4    1
5    5
6    9
7    6
8    8
9    4
dtype: int64

In [337]: s.sort_values()
Out[337]: 
1    0
4    1
0    2
2    3
9    4
5    5
7    6
3    7
8    8
6    9
dtype: int64

In [338]: s.nsmallest(3)
Out[338]: 
1    0
4    1
0    2
dtype: int64

In [339]: s.nlargest(3)
Out[339]: 
6    9
8    8
3    7
dtype: int64

Series также имеет методы nlargest и nsmallest.

In [340]: 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 [341]: df.nlargest(3, "a")
Out[341]: 
    a  b    c
5  11  f  3.0
3  10  c  3.2
4   8  e  NaN

In [342]: df.nlargest(5, ["a", "c"])
Out[342]: 
    a  b    c
5  11  f  3.0
3  10  c  3.2
4   8  e  NaN
2   1  d  4.0
6  -1  f  4.0

In [343]: df.nsmallest(3, "a")
Out[343]: 
   a  b    c
0 -2  a  1.0
1 -1  b  2.0
6 -1  f  4.0

In [344]: df.nsmallest(5, ["a", "c"])
Out[344]: 
   a  b    c
0 -2  a  1.0
1 -1  b  2.0
6 -1  f  4.0
2  1  d  4.0
4  8  e  NaN

Сортировка по столбцу с MultiIndex

При сортировке по столбцу с MultiIndex необходимо явно указать сортировку и полностью указать все уровни для by.

In [345]: df1.columns = pd.MultiIndex.from_tuples(
   .....:     [("a", "one"), ("a", "two"), ("b", "three")]
   .....: )
   .....: 

In [346]: df1.sort_values(by=("a", "two"))
Out[346]: 
    a         b
  one two three
0   2   1     5
2   1   2     3
1   1   3     4
3   1   4     2

Копирование

Метод copy() для объектов pandas копирует данные (хотя не оси индексов, так как они неизменяемы) и возвращает новый объект. Обратите внимание, что редко требуется копировать объекты. Например, существует лишь несколько способов изменить DataFrame непосредственно:

  • Вставка, удаление или изменение столбца.

  • Присвоение атрибутам index или columns.

  • Для однородных данных непосредственное изменение значений через атрибут values или расширенную индексацию.

Для ясности, ни один метод pandas не приводит к модификации ваших данных; почти каждый метод возвращает новый объект, оставляя исходный объект без изменений. Если данные изменяются, это происходит потому, что вы сделали это явно.

END_OF_DOCUMENT_MARKER

dtypes

В основном, pandas использует массивы NumPy и типы данных для Series или отдельных столбцов DataFrame. NumPy предоставляет поддержку для float, int, bool, timedelta64[ns] и datetime64[ns] (обратите внимание, что NumPy не поддерживает временные метки даты и времени с часовыми поясами).

pandas и сторонние библиотеки расширяют систему типов NumPy в нескольких местах. В этом разделе описаны расширения, внесенные pandas внутри. См. Типы расширений, чтобы узнать, как создать собственные расширения, которые работают с pandas. См. Расширенные типы данных для списка сторонних библиотек, которые реализовали расширение.

В следующей таблице перечислены все типы расширений pandas. Для методов, требующих аргументов dtype, строки можно указывать, как указано. Более подробную информацию о каждом типе см. в соответствующих разделах документации.

Тип данных

Тип данных

Скалярное значение

Массив

Псевдонимы строк

Дата и время с часовым поясом

DatetimeTZDtype

Timestamp

arrays.DatetimeArray

'datetime64[ns, <tz>]'

Категориальные

CategoricalDtype

(нет)

Categorical

'category'

Периоды (временные интервалы)

PeriodDtype

Period

arrays.PeriodArray 'Period[<freq>]'

'period[<freq>]',

Разреженный

SparseDtype

(нет)

arrays.SparseArray

'Sparse', 'Sparse[int]', 'Sparse[float]'

Интервалы

IntervalDtype

Interval

arrays.IntervalArray

'interval', 'Interval', 'Interval[<numpy_dtype>]', 'Interval[datetime64[ns, <tz>]]', 'Interval[timedelta64[<freq>]]'

Целые числа с возможностью пропусков

Int64Dtype, …

(нет)

arrays.IntegerArray

'Int8', 'Int16', 'Int32', 'Int64', 'UInt8', 'UInt16', 'UInt32', 'UInt64'

Строки

StringDtype

str

arrays.StringArray

'string'

Булевы (с пропусками)

BooleanDtype

bool

arrays.BooleanArray

'boolean'

pandas имеет два способа хранения строк.

  1. object тип данных, который может содержать любые объекты Python, включая строки.

  2. StringDtype, который предназначен для строк.

В целом, рекомендуется использовать StringDtype. Дополнительную информацию см. в разделе Типы данных текста.

Наконец, произвольные объекты могут храниться с помощью типа данных object, но следует избегать этого по возможности (для производительности и взаимодействия с другими библиотеками и методами. См. Преобразование объектов).

Удобный атрибут dtypes для DataFrame возвращает Series с типом данных каждого столбца.

In [347]: dft = pd.DataFrame(
   .....:     {
   .....:         "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 [348]: dft
Out[348]: 
          A  B    C          D    E      F  G
0  0.035962  1  foo 2001-01-02  1.0  False  1
1  0.701379  1  foo 2001-01-02  1.0  False  1
2  0.281885  1  foo 2001-01-02  1.0  False  1

In [349]: dft.dtypes
Out[349]: 
A           float64
B             int64
C            object
D    datetime64[ns]
E           float32
F              bool
G              int8
dtype: object

Для объекта Series, используйте атрибут dtype.

In [350]: dft["A"].dtype
Out[350]: dtype('float64')

Если объект pandas содержит данные с несколькими типами данных в одном столбце, тип данных столбца будет выбран для размещения всех типов данных (object является наиболее общим).

# these ints are coerced to floats
In [351]: pd.Series([1, 2, 3, 4, 5, 6.0])
Out[351]: 
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 [352]: pd.Series([1, 2, 3, 6.0, "foo"])
Out[352]: 
0      1
1      2
2      3
3    6.0
4    foo
dtype: object

Количество столбцов каждого типа в DataFrame можно найти, вызвав DataFrame.dtypes.value_counts().

In [353]: dft.dtypes.value_counts()
Out[353]: 
float64           1
int64             1
object            1
datetime64[ns]    1
float32           1
bool              1
int8              1
dtype: int64

Числовые типы данных будут передаваться и могут сосуществовать в DataFrame. Если тип данных передается (либо непосредственно через ключевое слово dtype, либо переданный ndarray, либо переданный Series ), то он будет сохранен в операциях DataFrame. Кроме того, различные числовые типы данных НЕ будут объединены. Следующий пример даст вам представление.

In [354]: df1 = pd.DataFrame(np.random.randn(8, 1), columns=["A"], dtype="float32")

In [355]: df1
Out[355]: 
          A
0  0.224364
1  1.890546
2  0.182879
3  0.787847
4 -0.188449
5  0.667715
6 -0.011736
7 -0.399073

In [356]: df1.dtypes
Out[356]: 
A    float32
dtype: object

In [357]: df2 = pd.DataFrame(
   .....:     {
   .....:         "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 [358]: df2
Out[358]: 
          A         B    C
0  0.823242  0.256090    0
1  1.607422  1.426469    0
2 -0.333740 -0.416203  255
3 -0.063477  1.139976    0
4 -1.014648 -1.193477    0
5  0.678711  0.096706    0
6 -0.040863 -1.956850    1
7 -0.357422 -0.714337    0

In [359]: df2.dtypes
Out[359]: 
A    float16
B    float64
C      uint8
dtype: object

defaults

По умолчанию целочисленные типы являются int64, а типы с плавающей точкой — float64, независимо от платформы (32-битная или 64-битная). Следующие действия приведут к типам данных int64.

In [360]: pd.DataFrame([1, 2], columns=["a"]).dtypes
Out[360]: 
a    int64
dtype: object

In [361]: pd.DataFrame({"a": [1, 2]}).dtypes
Out[361]: 
a    int64
dtype: object

In [362]: pd.DataFrame({"a": 1}, index=list(range(2))).dtypes
Out[362]: 
a    int64
dtype: object

Обратите внимание, что Numpy будет выбирать типы, зависящие от платформы, при создании массивов. Следующие действия ПРИВЕДУТ к int32 на 32-битной платформе.

In [363]: frame = pd.DataFrame(np.array([1, 2]))

преобразование к более общему типу

Типы могут быть преобразованы к более общему типу при объединении с другими типами, что означает их повышение с текущего типа (например, int до float).

In [364]: df3 = df1.reindex_like(df2).fillna(value=0.0) + df2

In [365]: df3
Out[365]: 
          A         B      C
0  1.047606  0.256090    0.0
1  3.497968  1.426469    0.0
2 -0.150862 -0.416203  255.0
3  0.724370  1.139976    0.0
4 -1.203098 -1.193477    0.0
5  1.346426  0.096706    0.0
6 -0.052599 -1.956850    1.0
7 -0.756495 -0.714337    0.0

In [366]: df3.dtypes
Out[366]: 
A    float32
B    float64
C    float64
dtype: object

DataFrame.to_numpy() вернёт наименьший общий тип типов, то есть тип, который может вместить ВСЕ типы в результирующем однородном массиве NumPy с типом данных. Это может привести к преобразованию к более общему типу.

In [367]: df3.to_numpy().dtype
Out[367]: dtype('float64')

astype

Вы можете использовать метод astype() для явного преобразования типов данных из одного в другой. По умолчанию эти методы возвращают копию, даже если тип данных не изменился (передайте copy=False чтобы изменить это поведение). Кроме того, они будут генерировать исключение, если операция astype недопустима.

Преобразование к более общему типу всегда происходит согласно правилам NumPy. Если в операции участвуют два разных типа данных, то более общий тип будет использован в качестве результата операции.

In [368]: df3
Out[368]: 
          A         B      C
0  1.047606  0.256090    0.0
1  3.497968  1.426469    0.0
2 -0.150862 -0.416203  255.0
3  0.724370  1.139976    0.0
4 -1.203098 -1.193477    0.0
5  1.346426  0.096706    0.0
6 -0.052599 -1.956850    1.0
7 -0.756495 -0.714337    0.0

In [369]: df3.dtypes
Out[369]: 
A    float32
B    float64
C    float64
dtype: object

# conversion of dtypes
In [370]: df3.astype("float32").dtypes
Out[370]: 
A    float32
B    float32
C    float32
dtype: object

Преобразуйте подмножество столбцов к указанному типу, используя astype().

In [371]: dft = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})

In [372]: dft[["a", "b"]] = dft[["a", "b"]].astype(np.uint8)

In [373]: dft
Out[373]: 
   a  b  c
0  1  4  7
1  2  5  8
2  3  6  9

In [374]: dft.dtypes
Out[374]: 
a    uint8
b    uint8
c    int64
dtype: object

Преобразуйте определённые столбцы к указанному типу данных, передав словарь в astype().

In [375]: dft1 = pd.DataFrame({"a": [1, 0, 1], "b": [4, 5, 6], "c": [7, 8, 9]})

In [376]: dft1 = dft1.astype({"a": np.bool_, "c": np.float64})

In [377]: dft1
Out[377]: 
       a  b    c
0   True  4  7.0
1  False  5  8.0
2   True  6  9.0

In [378]: dft1.dtypes
Out[378]: 
a       bool
b      int64
c    float64
dtype: object

Примечание

При попытке преобразовать подмножество столбцов к указанному типу, используя astype() и loc(), происходит преобразование к более общему типу.

loc() пытается подогнать то, что мы присваиваем, к текущим типам данных, в то время как [] перезаписывает их, принимая тип данных из правой части. Поэтому следующий фрагмент кода даёт нежелательный результат.

In [379]: dft = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})

In [380]: dft.loc[:, ["a", "b"]].astype(np.uint8).dtypes
Out[380]: 
a    uint8
b    uint8
dtype: object

In [381]: dft.loc[:, ["a", "b"]] = dft.loc[:, ["a", "b"]].astype(np.uint8)

In [382]: dft.dtypes
Out[382]: 
a    int64
b    int64
c    int64
dtype: object

преобразование объектов

pandas предлагает различные функции для попытки принудительного преобразования типов из типа object в другие типы. В тех случаях, когда данные уже имеют правильный тип, но хранятся в массиве object, методы DataFrame.infer_objects() и Series.infer_objects() могут быть использованы для мягкого преобразования к правильному типу.

In [383]: import datetime

In [384]: df = pd.DataFrame(
   .....:     [
   .....:         [1, 2],
   .....:         ["a", "b"],
   .....:         [datetime.datetime(2016, 3, 2), datetime.datetime(2016, 3, 2)],
   .....:     ]
   .....: )
   .....: 

In [385]: df = df.T

In [386]: df
Out[386]: 
   0  1          2
0  1  a 2016-03-02
1  2  b 2016-03-02

In [387]: df.dtypes
Out[387]: 
0            object
1            object
2    datetime64[ns]
dtype: object

Поскольку данные были транспонированы, первоначальное вычисление сохраняло все столбцы как объект, что infer_objects исправит.

In [388]: df.infer_objects().dtypes
Out[388]: 
0             int64
1            object
2    datetime64[ns]
dtype: object

Для одномерных массивов объектов или скаляров доступны следующие функции для жёсткого преобразования объектов к указанному типу:

  • to_numeric() (преобразование в числовые типы данных)

    In [389]: m = ["1.1", 2, 3]
    
    In [390]: pd.to_numeric(m)
    Out[390]: array([1.1, 2. , 3. ])
    
  • to_datetime() (преобразование в объекты datetime)

    In [391]: import datetime
    
    In [392]: m = ["2016-07-09", datetime.datetime(2016, 3, 2)]
    
    In [393]: pd.to_datetime(m)
    Out[393]: DatetimeIndex(['2016-07-09', '2016-03-02'], dtype='datetime64[ns]', freq=None)
    
  • to_timedelta() (преобразование в объекты timedelta)

    In [394]: m = ["5us", pd.Timedelta("1day")]
    
    In [395]: pd.to_timedelta(m)
    Out[395]: 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 [396]: import datetime

In [397]: m = ["apple", datetime.datetime(2016, 3, 2)]

In [398]: pd.to_datetime(m, errors="coerce")
Out[398]: DatetimeIndex(['NaT', '2016-03-02'], dtype='datetime64[ns]', freq=None)

In [399]: m = ["apple", 2, 3]

In [400]: pd.to_numeric(m, errors="coerce")
Out[400]: array([nan,  2.,  3.])

In [401]: m = ["apple", pd.Timedelta("1day")]

In [402]: pd.to_timedelta(m, errors="coerce")
Out[402]: TimedeltaIndex([NaT, '1 days'], dtype='timedelta64[ns]', freq=None)

Параметр errors имеет третий вариант errors='ignore', который просто вернёт переданные данные, если при преобразовании в желаемый тип данных возникнут какие-либо ошибки:

In [403]: import datetime

In [404]: m = ["apple", datetime.datetime(2016, 3, 2)]

In [405]: pd.to_datetime(m, errors="ignore")
Out[405]: Index(['apple', 2016-03-02 00:00:00], dtype='object')

In [406]: m = ["apple", 2, 3]

In [407]: pd.to_numeric(m, errors="ignore")
Out[407]: array(['apple', 2, 3], dtype=object)

In [408]: m = ["apple", pd.Timedelta("1day")]

In [409]: pd.to_timedelta(m, errors="ignore")
Out[409]: array(['apple', Timedelta('1 days 00:00:00')], dtype=object)

Помимо преобразования объектов, to_numeric() предоставляет ещё один аргумент downcast, который даёт возможность уменьшить новый (или уже существующий) числовой тип данных до более компактного, что может сэкономить память:

In [410]: m = ["1", 2, 3]

In [411]: pd.to_numeric(m, downcast="integer")  # smallest signed int dtype
Out[411]: array([1, 2, 3], dtype=int8)

In [412]: pd.to_numeric(m, downcast="signed")  # same as 'integer'
Out[412]: array([1, 2, 3], dtype=int8)

In [413]: pd.to_numeric(m, downcast="unsigned")  # smallest unsigned int dtype
Out[413]: array([1, 2, 3], dtype=uint8)

In [414]: pd.to_numeric(m, downcast="float")  # smallest float dtype
Out[414]: array([1., 2., 3.], dtype=float32)

Поскольку эти методы применяются только к одномерным массивам, спискам или скалярам, их нельзя напрямую использовать с многомерными объектами, такими как DataFrames. Однако с помощью apply() мы можем «применить» функцию к каждому столбцу эффективно:

In [415]: import datetime

In [416]: df = pd.DataFrame([["2016-07-09", datetime.datetime(2016, 3, 2)]] * 2, dtype="O")

In [417]: df
Out[417]: 
            0                    1
0  2016-07-09  2016-03-02 00:00:00
1  2016-07-09  2016-03-02 00:00:00

In [418]: df.apply(pd.to_datetime)
Out[418]: 
           0          1
0 2016-07-09 2016-03-02
1 2016-07-09 2016-03-02

In [419]: df = pd.DataFrame([["1.1", 2, 3]] * 2, dtype="O")

In [420]: df
Out[420]: 
     0  1  2
0  1.1  2  3
1  1.1  2  3

In [421]: df.apply(pd.to_numeric)
Out[421]: 
     0  1  2
0  1.1  2  3
1  1.1  2  3

In [422]: df = pd.DataFrame([["5us", pd.Timedelta("1day")]] * 2, dtype="O")

In [423]: df
Out[423]: 
     0                1
0  5us  1 days 00:00:00
1  5us  1 days 00:00:00

In [424]: df.apply(pd.to_timedelta)
Out[424]: 
                       0      1
0 0 days 00:00:00.000005 1 days
1 0 days 00:00:00.000005 1 days

подводные камни

Выполнение операций выбора на данных типа integer может легко привести к преобразованию данных к типу floating. Тип данных входных данных будет сохранён в случаях, когда nans не вводятся. См. также Поддержка целых значений NA.

In [425]: dfi = df3.astype("int32")

In [426]: dfi["E"] = 1

In [427]: dfi
Out[427]: 
   A  B    C  E
0  1  0    0  1
1  3  1    0  1
2  0  0  255  1
3  0  1    0  1
4 -1 -1    0  1
5  1  0    0  1
6  0 -1    1  1
7  0  0    0  1

In [428]: dfi.dtypes
Out[428]: 
A    int32
B    int32
C    int32
E    int64
dtype: object

In [429]: casted = dfi[dfi > 0]

In [430]: casted
Out[430]: 
     A    B      C  E
0  1.0  NaN    NaN  1
1  3.0  1.0    NaN  1
2  NaN  NaN  255.0  1
3  NaN  1.0    NaN  1
4  NaN  NaN    NaN  1
5  1.0  NaN    NaN  1
6  NaN  NaN    1.0  1
7  NaN  NaN    NaN  1

In [431]: casted.dtypes
Out[431]: 
A    float64
B    float64
C    float64
E      int64
dtype: object

В то время как типы данных float остаются неизменными.

In [432]: dfa = df3.copy()

In [433]: dfa["A"] = dfa["A"].astype("float32")

In [434]: dfa.dtypes
Out[434]: 
A    float32
B    float64
C    float64
dtype: object

In [435]: casted = dfa[df2 > 0]

In [436]: casted
Out[436]: 
          A         B      C
0  1.047606  0.256090    NaN
1  3.497968  1.426469    NaN
2       NaN       NaN  255.0
3       NaN  1.139976    NaN
4       NaN       NaN    NaN
5  1.346426  0.096706    NaN
6       NaN       NaN    1.0
7       NaN       NaN    NaN

In [437]: casted.dtypes
Out[437]: 
A    float32
B    float64
C    float64
dtype: object

Выбор столбцов на основе типа данных

Метод select_dtypes() реализует подмножество столбцов на основе их типа данных.

Сначала создадим DataFrame с множеством различных типов данных:

In [438]: 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),
   .....:         "category": pd.Series(list("ABC")).astype("category"),
   .....:     }
   .....: )
   .....: 

In [439]: df["tdeltas"] = df.dates.diff()

In [440]: df["uint64"] = np.arange(3, 6).astype("u8")

In [441]: df["other_dates"] = pd.date_range("20130101", periods=3)

In [442]: df["tz_aware_dates"] = pd.date_range("20130101", periods=3, tz="US/Eastern")

In [443]: df
Out[443]: 
  string  int64  uint8  ...  uint64  other_dates            tz_aware_dates
0      a      1      3  ...       3   2013-01-01 2013-01-01 00:00:00-05:00
1      b      2      4  ...       4   2013-01-02 2013-01-02 00:00:00-05:00
2      c      3      5  ...       5   2013-01-03 2013-01-03 00:00:00-05:00

[3 rows x 12 columns]

И типы данных:

In [444]: df.dtypes
Out[444]: 
string                                object
int64                                  int64
uint8                                  uint8
float64                              float64
bool1                                   bool
bool2                                   bool
dates                         datetime64[ns]
category                            category
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 [445]: df.select_dtypes(include=[bool])
Out[445]: 
   bool1  bool2
0   True  False
1  False   True
2   True  False

Вы также можете передать имя типа данных в иерархии типов данных NumPy:

In [446]: df.select_dtypes(include=["bool"])
Out[446]: 
   bool1  bool2
0   True  False
1  False   True
2   True  False

select_dtypes() также работает с обобщёнными типами данных.

Например, чтобы выбрать все числовые и булевы столбцы, исключая беззнаковые целые числа:

In [447]: df.select_dtypes(include=["number", "bool"], exclude=["unsignedinteger"])
Out[447]: 
   int64  float64  bool1  bool2 tdeltas
0      1      4.0   True  False     NaT
1      2      5.0  False   True  1 days
2      3      6.0   True  False  1 days

Чтобы выбрать строковые столбцы, вы должны использовать тип данных object:

In [448]: df.select_dtypes(include=["object"])
Out[448]: 
  string
0      a
1      b
2      c

Чтобы увидеть все дочерние типы данных для обобщённого типа dtype вроде numpy.number, вы можете определить функцию, которая возвращает дерево дочерних типов данных:

In [449]: def subdtypes(dtype):
   .....:     subs = dtype.__subclasses__()
   .....:     if not subs:
   .....:         return dtype
   .....:     return [dtype, [subdtypes(dt) for dt in subs]]
   .....: 

Все типы данных NumPy являются подклассами numpy.generic:

In [450]: subdtypes(np.generic)
Out[450]: 
[numpy.generic,
 [[numpy.number,
   [[numpy.integer,
     [[numpy.signedinteger,
       [numpy.int8,
        numpy.int16,
        numpy.int32,
        numpy.int64,
        numpy.longlong,
        numpy.timedelta64]],
      [numpy.unsignedinteger,
       [numpy.uint8,
        numpy.uint16,
        numpy.uint32,
        numpy.uint64,
        numpy.ulonglong]]]],
    [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.bytes_, numpy.str_]],
    [numpy.void, [numpy.record]]]],
  numpy.bool_,
  numpy.datetime64,
  numpy.object_]]

Примечание

pandas также определяет типы category, и datetime64[ns, tz], которые не интегрированы в обычную иерархию NumPy и не отображаются с помощью вышеуказанной функции.

© 2008–2022, 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/1.5.0/user_guide/basics.html

Spec-Zone.ru

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