Основная базовая функциональность#

Здесь мы обсудим множество основных функций, общих для структур данных 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 (строк) и столбцы

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

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 для добавления поддержки пользовательских массивов (см. dtypes).

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

In [10]: s.array
Out[10]: 

[ 0.4691122999071863, -0.2828633443286633, -1.5090585031735124,
 -1.1356323710171934,  1.2121120250208506]
Length: 5, dtype: float64

In [11]: s.index.array
Out[11]: 

['a', 'b', 'c', 'd', 'e']
Length: 5, dtype: object

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

Если вам нужен массив 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])

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

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

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

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

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

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

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

Или отброшено с dtype='datetime64[ns]'

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

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

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

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

Примечание

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

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

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

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

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

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

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

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

Операция

0.11.0 (мс)

Предыдущая версия (мс)

Отношение к предыдущему

df1 > df2

13.32

125.35

0.1063

df1 * df2

21.71

36.63

0.5928

df1 + df2

22.04

36.50

0.6039

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

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

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. Используя эти функции, вы можете либо сопоставлять по index или столбцы через ось ключевое слово:

In [18]: df = pd.DataFrame(
   ....:     {
   ....:         "one": pd.Series(np.random.randn(3), index=["a", "b", "c"]),
   ....:         "two": pd.Series(np.random.randn(4), index=["a", "b", "c", "d"]),
   ....:         "three": pd.Series(np.random.randn(3), index=["b", "c", "d"]),
   ....:     }
   ....: )
   ....: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In [35]: idx
Out[35]: 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)
 in ?()
----> 1 if df:
      2     print(True)

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

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)
 in ?()
----> 1 df and df2

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

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(). Но на самом деле это выражение ложно:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Попытка сравнить Index или Series объекты разной длины вызовут ValueError:

In [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..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:6133, in Series._cmp_method(self, other, op)
   6130 res_name = ops.get_op_result_name(self, other)
   6132 if isinstance(other, Series) and not self._indexed_same(other):
-> 6133     raise ValueError("Can only compare identically-labeled Series objects")
   6135 lvalues = self._values
   6136 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..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:6133, in Series._cmp_method(self, other, op)
   6130 res_name = ops.get_op_result_name(self, other)
   6132 if isinstance(other, Series) and not self._indexed_same(other):
-> 6133     raise ValueError("Can only compare identically-labeled Series objects")
   6135 lvalues = self._values
   6136 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#

The 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(), создают объект того же размера. Как правило, эти методы принимают ось аргумент, так же как 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

Количество ненулевых наблюдений

sum

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

mean

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

median

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

min

Минимум

max

Максимум

mode

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() вернет количество уникальных не-NA значений в Series:

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

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

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

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

Суммирование данных: describe#

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

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

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

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

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

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

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

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

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

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

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

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

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

Обратите внимание, что для объекта DataFrame смешанного типа, 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. См. там подробности о допустимых входных данных.

Индекс минимальных/максимальных значений#

The idxmin() и idxmax() функции на Series и DataFrame вычисляют метки индекса с минимальными и максимальными соответствующими значениями:

In [108]: s1 = pd.Series(np.random.randn(5))

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

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

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

The 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') to 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]:

"""
                           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

The 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. Это определит, как list-like значения возвращаются (или нет) в 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, что преобразует каждую строку или столбец в Series перед применением функции. Когда установлено значение True, переданная функция будет получать объект ndarray, что имеет положительные последствия для производительности, если вам не нужна функциональность индексирования.

API агрегации#

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

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

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

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

In [163]: tsdf
Out[163]: 
                   A         B         C
2000-01-01  1.257606  1.004194  0.167574
2000-01-02 -0.749892  0.288112 -0.757304
2000-01-03 -0.207550 -0.298599  0.116018
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.814347 -0.257623  0.869226
2000-01-09 -0.250663 -1.206601  0.896839
2000-01-10  2.169758 -1.333363  0.283157

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

In [164]: tsdf.agg(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 функция будет выдавать именованная строка:

In [171]: tsdf["A"].agg(["sum", lambda x: x.mean()])
Out[171]: 
sum         3.033606
    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, аналогичную встроенной функция 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 преобразования#

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

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

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

Преобразовать весь фрейм. .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 с MultiIndex. Первый уровень будет содержать исходные имена столбцов фрейма; второй уровень будет содержать имена преобразующих функций.

In [190]: tsdf.transform([np.abs, lambda x: x + 1])
Out[190]: 
                   A                   B                   C          
            absolute    absolute    absolute  
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  
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 с MultiIndex с этими выборочными преобразованиями.

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

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

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

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

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

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 объекты, содержащие фактические метки осей, могут быть shared между объектами. Так, если у нас есть 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#

The 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)

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

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(), вы можете выбрать выравнивание обоих объектов либо по индексу, либо по столбцам DataFrame, используя axis аргумент:

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

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

ближайший

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

Мы иллюстрируем эти методы заполнения на простом 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

Эти методы требуют, чтобы индексы были ordered возрастающий или убывающий.

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

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() не будет выполнять проверки порядка индекса.

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

The 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

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

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 если возможно. Это позволяет указать допуск с помощью соответствующих строк.

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

Метод, тесно связанный с 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

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

The rename() метод позволяет переименовать ось на основе некоторого отображения (словаря или 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

Если вы передаете функцию, она должна возвращать значение при вызове с любой из меток (и должна создавать набор уникальных значений). Также можно использовать словарь или 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 он рассматривается как подобный массиву, и базовая итерация производит значения. DataFrames следуют соглашению, подобному словарю, итерируя по «ключам» объектов.

Короче говоря, базовая итерация (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() метод для итерации по парам (ключ, значение).

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

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

  • itertuples(): Итерация по строкам DataFrame как namedtuples значений. Это намного быстрее, чем 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#

В соответствии с интерфейсом, подобным словарю, 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, теперь преобразуется в числа с плавающей точкой, также исходное целочисленное значение в столбце 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#

The itertuples() метод вернёт итератор, выдающий namedtuple для каждой строки в 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; он просто возвращает значения внутри namedtuple. Поэтому, itertuples() сохраняет тип данных значений и обычно быстрее, так как iterrows().

Примечание

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

.dt аксессор#

Series имеет аксессор для краткого возврата свойств, подобных datetime, для values 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]: 

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

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

The .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 если вы обращаетесь с не datetime-подобными значениями.

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

Series оснащен набором методов обработки строк, которые упрощают работу с каждым элементом массива. Возможно, самое важное, что эти методы автоматически исключают пропущенные/NA значения. Доступ к ним осуществляется через 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    
6    caba
7     dog
8     cat
dtype: string

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

Примечание

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

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

Сортировка#

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

По индексу#

The 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

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

По значениям#

The Series.sort_values() метод используется для сортировки Series по его значениям. The 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

The 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    
5    
dtype: string

In [315]: s.sort_values(na_position="first")
Out[315]: 
2    
5    
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

Копирование#

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

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

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

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

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

dtypes#

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

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

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

Тип данных

Тип данных

Скаляр

Массив

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

datetime с учетом часового пояса

DatetimeTZDtype

Timestamp

arrays.DatetimeArray

'datetime64[ns, ]'

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

CategoricalDtype

(нет)

Categorical

'category'

период (временные промежутки)

PeriodDtype

Period

arrays.PeriodArray 'Period[]'

'period[]',

разреженный

SparseDtype

(нет)

arrays.SparseArray

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

интервалы

IntervalDtype

Interval

arrays.IntervalArray

'interval', 'Interval', 'Interval[]', 'Interval[datetime64[ns, ]]', 'Interval[timedelta64[]]'

nullable integer

Int64Dtype, …

(нет)

arrays.IntegerArray

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

nullable float

Float64Dtype, …

(нет)

arrays.FloatingArray

'Float32', 'Float64'

Строки

StringDtype

str

arrays.StringArray

'string'

Булевый (с NA)

BooleanDtype

bool

arrays.BooleanArray

'boolean'

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

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

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

Обычно мы рекомендуем использовать StringDtype. См. Текстовые типы данных подробнее.

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

Удобный 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 object, используйте dtype атрибут.

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

Если объект pandas содержит данные с несколькими типами данных в одном столбце, dtype столбца будет выбран так, чтобы вместить все типы данных (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

Числовые dtypes будут распространяться и могут сосуществовать в DataFrames. Если передан dtype (либо напрямую через 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 и типы float являются float64, независимо платформы (32-бит или 64-бит). Все следующее приведет к int64 dtypes.

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 to 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 dtype в другие типы. В случаях, когда данные уже имеют правильный тип, но хранятся в 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 должен обрабатывать элементы, которые не могут быть преобразованы в желаемый dtype или объект. По умолчанию, errors='raise', что означает, что любые ошибки, встреченные в процессе, будут вызваны во время конвертации. Однако, если errors='coerce', эти ошибки будут проигнорированы, и pandas преобразует проблемные элементы в 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, который предоставляет возможность понизить тип данных для новых (или уже существующих) числовых данных до меньшего dtype, что может сэкономить память:

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)

Поскольку эти методы применяются только к одномерным массивам, спискам или скалярам; они не могут использоваться напрямую с многомерными объектами, такими как DataFrames. Однако с 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

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

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#

The select_dtypes() метод реализует выборку столбцов на основе их dtype.

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

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 dtype:

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

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

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 и не будут отображаться с помощью указанной выше функции.