Spec-Zone.ru › pandas 2

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

Здесь мы рассмотрим многие основные функции, общие для структур данных 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]: 
<NumpyExtensionArray>
[ 0.4691122999071863, -0.2828633443286633, -1.5090585031735124,
 -1.1356323710171934,  1.2121120250208506]
Length: 5, dtype: float64

In [11]: s.index.array
Out[11]: 
<NumpyExtensionArray>
['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. Объектный массив numpy.ndarray с объектами Timestamp, каждый из которых содержит правильный tz

  2. Массив numpy.ndarray типа datetime64[ns], в котором значения преобразованы в 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 выбирается так, чтобы вместить все исходные данные. Например, если присутствуют строки, результат будет иметь объектный тип данных. Если имеются только числа с плавающей точкой и целые числа, результирующий массив будет иметь тип данных с плавающей точкой.

Ранее 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 — это набор специализированных процедур на Cython, особенно быстрых при работе с массивами, содержащими 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

Кроме того, можно выровнять уровень DataFrame с MultiIndex по 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]: Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype='int64')

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

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

In [38]: rem
Out[38]: Index([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]: df2 = df.copy()

In [43]: df2.loc["a", "three"] = 1.0

In [44]: df
Out[44]: 
        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 [45]: df2
Out[45]: 
        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 [46]: df + df2
Out[46]: 
        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 [47]: df.add(df2, fill_value=0)
Out[47]: 
        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 [48]: df.gt(df2)
Out[48]: 
     one    two  three
a  False  False  False
b  False  False  False
c  False  False  False
d  False  False  False

In [49]: df2.ne(df)
Out[49]: 
     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 [50]: (df > 0).all()
Out[50]: 
one      False
two       True
three    False
dtype: bool

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

Можно свести результат к одному логическому значению.

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

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

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

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

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

Проверка истинности объекта pandas вызовет ошибку, поскольку результат проверки на пустоту или значений неоднозначен.

In [55]: if df:
   ....:     print(True)
   ....: 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-55-318d08b2571a> in ?()
----> 1 if df:
      2     print(True)

~/work/pandas/pandas/pandas/core/generic.py in ?(self)
   1575     @final
   1576     def __nonzero__(self) -> NoReturn:
-> 1577         raise ValueError(
   1578             f"The truth value of a {type(self).__name__} is ambiguous. "
   1579             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1580         )

ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
In [56]: df and df2
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-56-b241b64bb471> in ?()
----> 1 df and df2

~/work/pandas/pandas/pandas/core/generic.py in ?(self)
   1575     @final
   1576     def __nonzero__(self) -> NoReturn:
-> 1577         raise ValueError(
   1578             f"The truth value of a {type(self).__name__} is ambiguous. "
   1579             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1580         )

ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

Подробнее см. раздел подводные камни.

Сравнение объектов на эквивалентность

Часто один и тот же результат можно вычислить несколькими способами. В качестве простого примера рассмотрим df + df и df * 2. Чтобы проверить, дают ли эти два вычисления одинаковый результат, можно было бы воспользоваться описанным выше инструментом (df + df == df * 2).all(). Однако на самом деле это выражение возвращает False:

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 содержит значения False! Это связано с тем, что NaN не равны друг другу при сравнении:

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

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

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

Обратите внимание: для того чтобы равенство вернуло 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 [69]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar'])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[69], line 1
----> 1 pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar'])

File ~/work/pandas/pandas/pandas/core/ops/common.py:76, in _unpack_zerodim_and_defer.<locals>.new_method(self, other)
     72             return NotImplemented
     74 other = item_from_zerodim(other)
---> 76 return method(self, other)

File ~/work/pandas/pandas/pandas/core/arraylike.py:40, in OpsMixin.__eq__(self, other)
     38 @unpack_zerodim_and_defer("__eq__")
     39 def __eq__(self, other):
---> 40     return self._cmp_method(other, operator.eq)

File ~/work/pandas/pandas/pandas/core/series.py:6125, in Series._cmp_method(self, other, op)
   6122 res_name = ops.get_op_result_name(self, other)
   6124 if isinstance(other, Series) and not self._indexed_same(other):
-> 6125     raise ValueError("Can only compare identically-labeled Series objects")
   6127 lvalues = self._values
   6128 rvalues = extract_array(other, extract_numpy=True, extract_range=True)

ValueError: Can only compare identically-labeled Series objects

In [70]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo'])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[70], line 1
----> 1 pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo'])

File ~/work/pandas/pandas/pandas/core/ops/common.py:76, in _unpack_zerodim_and_defer.<locals>.new_method(self, other)
     72             return NotImplemented
     74 other = item_from_zerodim(other)
---> 76 return method(self, other)

File ~/work/pandas/pandas/pandas/core/arraylike.py:40, in OpsMixin.__eq__(self, other)
     38 @unpack_zerodim_and_defer("__eq__")
     39 def __eq__(self, other):
---> 40     return self._cmp_method(other, operator.eq)

File ~/work/pandas/pandas/pandas/core/series.py:6125, in Series._cmp_method(self, other, op)
   6122 res_name = ops.get_op_result_name(self, other)
   6124 if isinstance(other, Series) and not self._indexed_same(other):
-> 6125     raise ValueError("Can only compare identically-labeled Series objects")
   6127 lvalues = self._values
   6128 rvalues = extract_array(other, extract_numpy=True, extract_range=True)

ValueError: Can only compare identically-labeled Series objects

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

Иногда возникает необходимость объединить два похожих набора данных, отдавая предпочтение значениям из одного из них. Например, два временных ряда могут представлять один и тот же экономический показатель, причём один из них считается «более качественным». Однако ряд более низкого качества может охватывать более длительный период в прошлом или содержать более полные данные. Поэтому мы хотим объединить два объекта 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

Описательная статистика

Существует множество методов для вычисления описательной статистики и выполнения других связанных операций над объектами Series и DataFrame. Большинство из них — это агрегирующие операции (поэтому они возвращают результат меньшей размерности), например sum(), mean() и quantile(), однако некоторые из них, например cumsum() и cumprod(), возвращают объект того же размера. Как правило, эти методы принимают аргумент axis, как и ndarray.{sum, std, …}, но ось можно указать по имени или целым числом:

  • Series: аргумент 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

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

median

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

min

Минимум

max

Максимум

mode

Мода

abs

Абсолютное значение

prod

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

std

Стандартное отклонение выборки с поправкой Бесселя

var

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

sem

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

skew

Асимметрия выборки (3-й момент)

kurt

Эксцесс выборки (4-й момент)

quantile

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

cumsum

Накопительная сумма

cumprod

Накопительное произведение

cummax

Накопительный максимум

cummin

Накопительный минимум

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

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

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

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

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 со смешанными типами 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'

Примечание

В NumPy функции idxmin и idxmax называются argmin и argmax.

Подсчёт значений (построение гистограммы) / мода

Метод 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
Name: count, dtype: int64

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

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

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

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

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

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

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

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

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

Дискретизация и вычисление квантилей

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

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

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

In [131]: factor
Out[131]: 
[(-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 [132]: factor = pd.cut(arr, [-5, -1, 0, 1, 5])

In [133]: factor
Out[133]: 
[(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 [134]: arr = np.random.randn(30)

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

In [136]: factor
Out[136]: 
[(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 [137]: arr = np.random.randn(20)

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

In [139]: factor
Out[139]: 
[(-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. Применение функций к отдельным элементам: map()

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

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

Для начала подготовим данные:

In [140]: 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 [141]: 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 [142]: df_p = pd.DataFrame({"city_and_code": ["Chicago, IL"]})

extract_city_name и add_country_name — это функции, принимающие и возвращающие DataFrames.

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

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

Это эквивалентно следующему:

In [144]: df_p.pipe(extract_city_name).pipe(add_country_name, country_name="US")
Out[144]: 
  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:                Tue, 22 Nov 2022   Prob (F-statistic):           3.48e-15
Time:                        05:34:17   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, которые добавили популярный оператор (%>%) (читается «pipe», то есть «конвейер») для языка R. Реализация pipe здесь довольно лаконична и естественно вписывается в Python. Рекомендуем ознакомиться с исходным кодом pipe().

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

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

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

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

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

In [148]: df.apply(np.cumsum)
Out[148]: 
        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 [149]: df.apply(np.exp)
Out[149]: 
        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 [150]: df.apply("mean")
Out[150]: 
one      0.811094
two      1.360588
three    0.187958
dtype: float64

In [151]: df.apply("mean", axis=1)
Out[151]: 
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 [152]: tsdf = pd.DataFrame(
   .....:     np.random.randn(1000, 3),
   .....:     columns=["A", "B", "C"],
   .....:     index=pd.date_range("1/1/2000", periods=1000),
   .....: )
   .....: 

In [153]: tsdf.apply(lambda x: x.idxmax())
Out[153]: 
A   2000-08-06
B   2001-01-18
C   2001-07-18
dtype: datetime64[ns]

В метод apply() также можно передать дополнительные позиционные и именованные аргументы.

In [154]: def subtract_and_divide(x, sub, divide=1):
   .....:     return (x - sub) / divide
   .....: 

In [155]: df_udf = pd.DataFrame(np.ones((2, 2)))

In [156]: df_udf.apply(subtract_and_divide, args=(5,), divide=3)
Out[156]: 
          0         1
0 -1.333333 -1.333333
1 -1.333333 -1.333333

Ещё одна полезная возможность — передавать методы Series, чтобы выполнять операции Series над каждым столбцом или строкой:

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

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

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. При значении False каждая строка или столбец перед применением функции преобразуется в Series. Если установить значение True, переданная функция будет получать объект ndarray, что может повысить производительность, если индексирование не требуется.

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

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

Воспользуемся DataFrame, аналогичным тому, который создавали выше:

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(lambda x: np.sum(x))
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

Настраиваемая сводка

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

In [176]: from functools import partial

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

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

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

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

In [181]: tsdf.agg(["count", "mean", "std", "min", q_25, "median", q_75, "max"])
Out[181]: 
               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 .agg.

Создадим DataFrame, аналогичный использованному в предыдущих разделах.

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

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

In [184]: tsdf
Out[184]: 
                   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

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

In [185]: tsdf.transform(np.abs)
Out[185]: 
                   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 [186]: tsdf.transform("abs")
Out[186]: 
                   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 [187]: tsdf.transform(lambda x: x.abs())
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() получил одну функцию; это эквивалентно применению ufunc.

In [188]: np.abs(tsdf)
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

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

In [189]: tsdf["A"].transform(np.abs)
Out[189]: 
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 с многоуровневыми именами столбцов. Первый уровень — исходные имена столбцов DataFrame, второй — имена функций преобразования.

In [190]: tsdf.transform([np.abs, lambda x: x + 1])
Out[190]: 
                   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 [191]: tsdf["A"].transform([np.abs, lambda x: x + 1])
Out[191]: 
            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 [192]: tsdf.transform({"A": np.abs, "B": lambda x: x + 1})
Out[192]: 
                   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 [193]: tsdf.transform({"A": np.abs, "B": [lambda x: x + 1, "sqrt"]})
Out[193]: 
                   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 и получить другой массив или значение), методы map() для DataFrame и аналогичный map() для Series принимают любую функцию Python, которая получает одно значение и возвращает одно значение. Например:

In [194]: df4 = df.copy()

In [195]: df4
Out[195]: 
        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 [196]: def f(x):
   .....:     return len(str(x))
   .....: 

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

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

У Series.map() есть дополнительная возможность: с его помощью можно легко связать или сопоставить значения, определённые во вспомогательном ряде. Это тесно связано с объединением таблиц:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In [206]: df
Out[206]: 
        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 [207]: df.reindex(index=["c", "f", "b"], columns=["three", "two", "one"])
Out[207]: 
      three       two       one
c  1.227435  1.478369  0.695246
f       NaN       NaN       NaN
b -0.050390  1.912123  0.343054

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

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

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

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

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

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

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

In [212]: df.reindex(["three", "two", "one"], axis="columns")
Out[212]: 
      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

См. также

MultiIndex / расширенная индексация — ещё более лаконичный способ переиндексации.

Примечание

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

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

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

In [213]: df2 = df.reindex(["a", "b", "c"], columns=["one", "two"])

In [214]: df3 = df2 - df2.mean()

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

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

In [217]: df.reindex_like(df2)
Out[217]: 
        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 [218]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

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

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

In [221]: s1.align(s2)
Out[221]: 
(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 [222]: s1.align(s2, join="inner")
Out[222]: 
(b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64,
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64)

In [223]: s1.align(s2, join="left")
Out[223]: 
(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 по умолчанию метод соединения применяется и к индексу, и к столбцам:

In [224]: df.align(df2, join="inner")
Out[224]: 
(        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 [225]: df.align(df2, join="inner", axis=0)
Out[225]: 
(        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(), с помощью аргумента axis можно выбрать, по какой оси выравнивать оба объекта: по индексу или столбцам DataFrame:

In [226]: df.align(df2.iloc[0], axis=1)
Out[226]: 
(        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 [227]: rng = pd.date_range("1/3/2000", periods=8)

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

In [229]: ts2 = ts.iloc[[0, 3, 6]]

In [230]: ts
Out[230]: 
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 [231]: ts2
Out[231]: 
2000-01-03    0.183051
2000-01-06    2.395489
2000-01-09    0.733639
Freq: 3D, dtype: float64

In [232]: ts2.reindex(ts.index)
Out[232]: 
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 [233]: ts2.reindex(ts.index, method="ffill")
Out[233]: 
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 [234]: ts2.reindex(ts.index, method="bfill")
Out[234]: 
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 [235]: ts2.reindex(ts.index, method="nearest")
Out[235]: 
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

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

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

In [236]: ts2.reindex(ts.index).ffill()
Out[236]: 
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 [237]: ts2.reindex(ts.index, method="ffill", limit=1)
Out[237]: 
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 [238]: ts2.reindex(ts.index, method="ffill", tolerance="1 day")
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

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

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

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

In [239]: df
Out[239]: 
        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 [240]: df.drop(["a", "d"], axis=0)
Out[240]: 
        one       two     three
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435

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

Обратите внимание, что работает и следующий вариант, хотя он несколько менее очевиден и аккуратен:

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

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

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

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

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

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

In [245]: df.rename(
   .....:     columns={"one": "foo", "two": "bar"},
   .....:     index={"a": "apple", "b": "banana", "d": "durian"},
   .....: )
   .....: 
Out[245]: 
             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 [246]: df.rename({"one": "foo", "two": "bar"}, axis="columns")
Out[246]: 
        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 [247]: df.rename({"a": "apple", "b": "banana", "d": "durian"}, axis="index")
Out[247]: 
             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() также принимает скалярное значение или объект, подобный списку, для изменения атрибута Series.name.

In [248]: s.rename("scalar-name")
Out[248]: 
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 [249]: 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 [250]: df
Out[250]: 
         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 [251]: df.rename_axis(index={"let": "abc"})
Out[251]: 
         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 [252]: df.rename_axis(index=str.upper)
Out[252]: 
         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 она рассматривается как массивоподобный объект, и базовая итерация возвращает значения. DataFrame следует соглашению, принятому для объектов типа dict: итерация выполняется по «ключам» объекта.

Кратко, базовая итерация (for i in object) возвращает:

  • Series: значения

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

Например, итерация по DataFrame возвращает имена столбцов:

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

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

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

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

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

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

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

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

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

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

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

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

Никогда не изменяйте объект, по которому выполняется итерация. Это не гарантированно будет работать во всех случаях. В зависимости от типов данных итератор возвращает копию, а не представление, поэтому запись в неё не даст эффекта!

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

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

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

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

items

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

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

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

Например:

In [258]: 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 [259]: 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 [260]: df_orig = pd.DataFrame([[1, 1.5]], columns=["int", "float"])

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

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

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

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

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

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

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

Например, DataFrame можно транспонировать следующим искусственным способом:

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

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

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

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

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

itertuples

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

Например:

In [271]: 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, а просто возвращает значения в именованном кортеже. Поэтому itertuples() сохраняет типы данных значений и обычно работает быстрее, чем iterrows().

Примечание

Имена столбцов будут заменены позиционными именами, если они не являются допустимыми идентификаторами Python, повторяются или начинаются с символа подчёркивания. Если столбцов много (>255), возвращаются обычные кортежи.

Аксессор .dt

Series имеет аксессор, который позволяет кратко получать свойства, связанные с датой и временем, для значений Series, если она содержит данные типа datetime или period. Результатом будет Series с тем же индексом, что и у исходной Series.

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

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

In [275]: s.dt.second
Out[275]: 
0    12
1    12
2    12
3    12
dtype: int32

In [276]: s.dt.day
Out[276]: 
0    1
1    2
2    3
3    4
dtype: int32

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

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

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

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

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

Также можно объединять такие операции в цепочки:

In [281]: s.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[281]: 
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]

Также можно форматировать значения даты и времени как строки с помощью Series.dt.strftime(), который поддерживает тот же формат, что и стандартный strftime().

# DatetimeIndex
In [282]: s = pd.Series(pd.date_range("20130101", periods=4))

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

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

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

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

Аксессор .dt работает с типами period и timedelta.

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

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

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

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

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

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

In [296]: s.dt.components
Out[296]: 
   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.

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

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

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

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

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

Примечание

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

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

Сортировка

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

По индексу

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

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

In [301]: unsorted_df
Out[301]: 
      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 [302]: unsorted_df.sort_index()
Out[302]: 
      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 [303]: unsorted_df.sort_index(ascending=False)
Out[303]: 
      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 [304]: unsorted_df.sort_index(axis=1)
Out[304]: 
        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 [305]: unsorted_df["three"].sort_index()
Out[305]: 
a         NaN
b   -0.098217
c    1.273388
d   -0.252916
Name: three, dtype: float64

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

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

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

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

Сведения о сортировке значений с помощью ключа см. в разделе сортировка значений.

По значениям

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In [326]: df_multi.sort_values(by=["second", "A"])
Out[326]: 
              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 [327]: ser = pd.Series([1, 2, 3])

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

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

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

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

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

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

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

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

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

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

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

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

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

У DataFrame также есть методы nlargest и nsmallest.

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

In [341]: df.nlargest(5, ["a", "c"])
Out[341]: 
    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 [342]: df.nsmallest(3, "a")
Out[342]: 
   a  b    c
0 -2  a  1.0
1 -1  b  2.0
6 -1  f  4.0

In [343]: df.nsmallest(5, ["a", "c"])
Out[343]: 
   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 [344]: df1.columns = pd.MultiIndex.from_tuples(
   .....:     [("a", "one"), ("a", "two"), ("b", "three")]
   .....: )
   .....: 

In [345]: df1.sort_values(by=("a", "two"))
Out[345]: 
    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 не изменяет ваши данные в качестве побочного эффекта; почти каждый метод возвращает новый объект, оставляя исходный объект без изменений. Если данные изменились, значит, вы изменили их явно.

Типы данных

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

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

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

Вид данных

Тип данных

Скаляр

Массив

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

datetime с часовым поясом

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'

числа с плавающей точкой с поддержкой пропущенных значений

Float64Dtype, …

(нет)

arrays.FloatingArray

'Float32', 'Float64'

Строки

StringDtype

str

arrays.StringArray

'string'

Логические значения (с NA)

BooleanDtype

bool

arrays.BooleanArray

'boolean'

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

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

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

Как правило, мы рекомендуем использовать StringDtype. Подробнее см. в разделе Типы текстовых данных.

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

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

In [346]: 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 [347]: dft
Out[347]: 
          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 [348]: dft.dtypes
Out[348]: 
A          float64
B            int64
C           object
D    datetime64[s]
E          float32
F             bool
G             int8
dtype: object

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

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

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

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

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

In [352]: dft.dtypes.value_counts()
Out[352]: 
float64          1
int64            1
object           1
datetime64[s]    1
float32          1
bool             1
int8             1
Name: count, dtype: int64

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

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

In [354]: df1
Out[354]: 
          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 [355]: df1.dtypes
Out[355]: 
A    float32
dtype: object

In [356]: df2 = pd.DataFrame(
   .....:     {
   .....:         "A": pd.Series(np.random.randn(8), dtype="float16"),
   .....:         "B": pd.Series(np.random.randn(8)),
   .....:         "C": pd.Series(np.random.randint(0, 255, size=8), dtype="uint8"),  # [0,255] (range of uint8)
   .....:     }
   .....: )
   .....: 

In [357]: df2
Out[357]: 
          A         B    C
0  0.823242  0.256090   26
1  1.607422  1.426469   86
2 -0.333740 -0.416203   46
3 -0.063477  1.139976  212
4 -1.014648 -1.193477   26
5  0.678711  0.096706    7
6 -0.040863 -1.956850  184
7 -0.357422 -0.714337  206

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

значения по умолчанию

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

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

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

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

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

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

повышение типа

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

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

In [364]: df3
Out[364]: 
          A         B      C
0  1.047606  0.256090   26.0
1  3.497968  1.426469   86.0
2 -0.150862 -0.416203   46.0
3  0.724370  1.139976  212.0
4 -1.203098 -1.193477   26.0
5  1.346426  0.096706    7.0
6 -0.052599 -1.956850  184.0
7 -0.756495 -0.714337  206.0

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

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

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

astype

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

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

In [367]: df3
Out[367]: 
          A         B      C
0  1.047606  0.256090   26.0
1  3.497968  1.426469   86.0
2 -0.150862 -0.416203   46.0
3  0.724370  1.139976  212.0
4 -1.203098 -1.193477   26.0
5  1.346426  0.096706    7.0
6 -0.052599 -1.956850  184.0
7 -0.756495 -0.714337  206.0

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

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

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

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

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

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

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

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

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

In [382]: import datetime

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

In [384]: df = df.T

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

In [386]: df.dtypes
Out[386]: 
0    object
1    object
2    object
dtype: object

Поскольку данные были транспонированы, при первоначальном определении типов все столбцы получили тип object; infer_objects исправит это.

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

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

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

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

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

    In [393]: m = ["5us", pd.Timedelta("1day")]
    
    In [394]: pd.to_timedelta(m)
    Out[394]: TimedeltaIndex(['0 days 00:00:00.000005', '1 days 00:00:00'], dtype='timedelta64[ns]', freq=None)
    

Чтобы принудительно выполнить преобразование, можно передать аргумент errors, указывающий, как pandas должен обрабатывать элементы, которые невозможно преобразовать в нужный тип данных или объект. По умолчанию используется errors='raise', то есть все ошибки, возникшие в процессе преобразования, будут вызваны. Если же указать errors='coerce', ошибки будут игнорироваться, а проблемные элементы преобразуются в pd.NaT (для datetime и timedelta) или np.nan (для числовых данных). Это может быть полезно при чтении данных, большинство которых имеет нужный тип (например, числовой или datetime), но иногда встречаются не соответствующие ему элементы, которые нужно представить как пропущенные значения:

In [395]: import datetime

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

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

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

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

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

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

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

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

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

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

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

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

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

In [407]: import datetime

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

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

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

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

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

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

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

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

In [416]: df.apply(pd.to_timedelta)
Out[416]: 
                       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 [417]: dfi = df3.astype("int32")

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

In [419]: dfi
Out[419]: 
   A  B    C  E
0  1  0   26  1
1  3  1   86  1
2  0  0   46  1
3  0  1  212  1
4 -1 -1   26  1
5  1  0    7  1
6  0 -1  184  1
7  0  0  206  1

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

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

In [422]: casted
Out[422]: 
     A    B    C  E
0  1.0  NaN   26  1
1  3.0  1.0   86  1
2  NaN  NaN   46  1
3  NaN  1.0  212  1
4  NaN  NaN   26  1
5  1.0  NaN    7  1
6  NaN  NaN  184  1
7  NaN  NaN  206  1

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

Типы данных с плавающей точкой при этом не изменяются.

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

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

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

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

In [428]: casted
Out[428]: 
          A         B      C
0  1.047606  0.256090   26.0
1  3.497968  1.426469   86.0
2       NaN       NaN   46.0
3       NaN  1.139976  212.0
4       NaN       NaN   26.0
5  1.346426  0.096706    7.0
6       NaN       NaN  184.0
7       NaN       NaN  206.0

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

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

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

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

In [430]: 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 [431]: df["tdeltas"] = df.dates.diff()

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

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

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

In [435]: df
Out[435]: 
  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 [436]: df.dtypes
Out[436]: 
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 [437]: df.select_dtypes(include=[bool])
Out[437]: 
   bool1  bool2
0   True  False
1  False   True
2   True  False

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

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

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

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

In [439]: df.select_dtypes(include=["number", "bool"], exclude=["unsignedinteger"])
Out[439]: 
   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 [440]: df.select_dtypes(include=["object"])
Out[440]: 
  string
0      a
1      b
2      c

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

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

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

In [442]: subdtypes(np.generic)
Out[442]: 
[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.longdouble]],
      [numpy.complexfloating,
       [numpy.complex64, numpy.complex128, numpy.clongdouble]]]]]],
  [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–2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
© 2011–2025, Open source contributors
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.3.0/user_guide/basics.html

Spec-Zone.ru

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