Основные базовые функции
Здесь мы рассмотрим многие основные функции, общие для структур данных pandas. Для начала создадим несколько примеров объектов, как мы это делали в разделе 10 минут на знакомство с pandas:
In [1]: index = pd.date_range("1/1/2000", periods=8)
In [2]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
In [3]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])
Начало и конец
Чтобы просмотреть небольшой фрагмент объекта Series или DataFrame, используйте методы head() и tail(). По умолчанию отображаются пять элементов, но вы можете указать другое количество.
In [4]: long_series = pd.Series(np.random.randn(1000))
In [5]: long_series.head()
Out[5]:
0 -1.157892
1 -1.344312
2 0.844885
3 1.075770
4 -0.109050
dtype: float64
In [6]: long_series.tail(3)
Out[6]:
997 -0.289388
998 -1.020544
999 0.589993
dtype: float64
Атрибуты и базовые данные
Объекты pandas имеют ряд атрибутов, позволяющих получить доступ к метаданным
shape: задаёт размеры осей объекта, как и в ndarray
-
- Метки осей
-
Series: index (единственная ось)
DataFrame: index (строки) и columns (столбцы)
Обратите внимание: этим атрибутам можно безопасно присваивать значения!
In [7]: df[:2]
Out[7]:
A B C
2000-01-01 -0.173215 0.119209 -1.044236
2000-01-02 -0.861849 -2.104569 -0.494929
In [8]: df.columns = [x.lower() for x in df.columns]
In [9]: df
Out[9]:
a b c
2000-01-01 -0.173215 0.119209 -1.044236
2000-01-02 -0.861849 -2.104569 -0.494929
2000-01-03 1.071804 0.721555 -0.706771
2000-01-04 -1.039575 0.271860 -0.424972
2000-01-05 0.567020 0.276232 -1.087401
2000-01-06 -0.673690 0.113648 -1.478427
2000-01-07 0.524988 0.404705 0.577046
2000-01-08 -1.715002 -1.039268 -0.370647
Объекты pandas (Index, Series, DataFrame) можно рассматривать как контейнеры для массивов, которые хранят фактические данные и выполняют вычисления. Для многих типов базовый массив — это numpy.ndarray. Однако pandas и сторонние библиотеки могут расширять систему типов NumPy, добавляя поддержку пользовательских массивов (см. раздел типы данных).
Чтобы получить фактические данные внутри объекта Index или Series, используйте свойство .array
In [10]: s.array
Out[10]:
<NumpyExtensionArray>
[ 0.4691122999071863, -0.2828633443286633, -1.5090585031735124,
-1.1356323710171934, 1.2121120250208506]
Length: 5, dtype: float64
In [11]: s.index.array
Out[11]:
<NumpyExtensionArray>
['a', 'b', 'c', 'd', 'e']
Length: 5, dtype: object
array всегда будет объектом ExtensionArray. Точные сведения о том, что такое ExtensionArray и зачем pandas использует такие объекты, выходят за рамки этого введения. Подробнее см. раздел типы данных.
Если вам нужен массив NumPy, используйте to_numpy() или numpy.asarray().
In [12]: s.to_numpy()
Out[12]: array([ 0.4691, -0.2829, -1.5091, -1.1356, 1.2121])
In [13]: np.asarray(s)
Out[13]: array([ 0.4691, -0.2829, -1.5091, -1.1356, 1.2121])
Если Series или Index основан на объекте ExtensionArray, вызов to_numpy() может привести к копированию данных и приведению значений к другому типу. Подробнее см. раздел типы данных.
to_numpy() позволяет в некоторой степени управлять dtype результирующего объекта numpy.ndarray. Например, рассмотрим даты и время с часовыми поясами. В NumPy нет типа данных для представления дат и времени с часовым поясом, поэтому можно использовать два варианта представления:
Объектный массив
numpy.ndarrayс объектамиTimestamp, каждый из которых содержит правильныйtzМассив
numpy.ndarrayтипаdatetime64[ns], в котором значения преобразованы в UTC, а часовой пояс отброшен
Часовые пояса можно сохранить с помощью dtype=object
In [14]: ser = pd.Series(pd.date_range("2000", periods=2, tz="CET"))
In [15]: ser.to_numpy(dtype=object)
Out[15]:
array([Timestamp('2000-01-01 00:00:00+0100', tz='CET'),
Timestamp('2000-01-02 00:00:00+0100', tz='CET')], dtype=object)
Или отбросить с помощью dtype='datetime64[ns]'
In [16]: ser.to_numpy(dtype="datetime64[ns]")
Out[16]:
array(['1999-12-31T23:00:00.000000000', '2000-01-01T23:00:00.000000000'],
dtype='datetime64[ns]')
Получить «необработанные данные» внутри объекта DataFrame может быть несколько сложнее. Если DataFrame содержит данные одного типа во всех столбцах, DataFrame.to_numpy() вернёт базовые данные:
In [17]: df.to_numpy()
Out[17]:
array([[-0.1732, 0.1192, -1.0442],
[-0.8618, -2.1046, -0.4949],
[ 1.0718, 0.7216, -0.7068],
[-1.0396, 0.2719, -0.425 ],
[ 0.567 , 0.2762, -1.0874],
[-0.6737, 0.1136, -1.4784],
[ 0.525 , 0.4047, 0.577 ],
[-1.715 , -1.0393, -0.3706]])
Если DataFrame содержит данные одного типа, ndarray можно изменить непосредственно, и эти изменения отразятся в структуре данных. Для данных разных типов (например, если типы данных в столбцах DataFrame различаются) это не сработает. Самому атрибуту values, в отличие от меток осей, нельзя присваивать значения.
Примечание
При работе с данными разных типов тип данных результирующего ndarray выбирается так, чтобы вместить все исходные данные. Например, если присутствуют строки, результат будет иметь объектный тип данных. Если имеются только числа с плавающей точкой и целые числа, результирующий массив будет иметь тип данных с плавающей точкой.
Ранее pandas рекомендовала использовать Series.values или DataFrame.values для извлечения данных из Series или DataFrame. Эти атрибуты всё ещё встречаются в старом коде и в интернете. В дальнейшем мы рекомендуем отказаться от .values и использовать .array или .to_numpy(). У .values есть следующие недостатки:
Если Series содержит тип-расширение, неясно, возвращает ли
Series.valuesмассив NumPy или массив-расширение.Series.arrayвсегда возвращает объектExtensionArrayи никогда не копирует данные.Series.to_numpy()всегда возвращает массив NumPy, при необходимости копируя данные и приводя значения к другому типу.Если DataFrame содержит данные разных типов, вызов
DataFrame.valuesможет привести к копированию данных и приведению значений к общему типу, что является сравнительно затратной операцией. ПосколькуDataFrame.to_numpy()является методом, яснее, что возвращённый массив NumPy может не быть представлением тех же данных, что содержатся в DataFrame.
Ускоренные операции
pandas поддерживает ускорение некоторых типов бинарных числовых и логических операций с помощью библиотеки numexpr и библиотек bottleneck.
Эти библиотеки особенно полезны при работе с большими наборами данных и значительно повышают скорость вычислений. numexpr использует интеллектуальную разбивку на блоки, кэширование и несколько ядер. bottleneck — это набор специализированных процедур на Cython, особенно быстрых при работе с массивами, содержащими nans.
Вот пример (с использованием DataFrames размером 100 столбцов × 100 000 строк):
Операция | 0.11.0 (мс) | Предыдущая версия (мс) | Отношение к предыдущей версии |
|---|---|---|---|
| 13.32 | 125.35 | 0.1063 |
| 21.71 | 36.63 | 0.5928 |
| 22.04 | 36.50 | 0.6039 |
Настоятельно рекомендуем установить обе библиотеки. Дополнительную информацию об установке см. в разделе Рекомендуемые зависимости.
По умолчанию обе библиотеки включены. Это можно изменить, установив параметры:
pd.set_option("compute.use_bottleneck", False)
pd.set_option("compute.use_numexpr", False)
Гибкие бинарные операции
При выполнении бинарных операций над структурами данных pandas важны два момента:
Поведение при трансляции между объектами с большей (например, DataFrame) и меньшей (например, Series) размерностью.
Обработка пропущенных данных при вычислениях.
Мы покажем, как решать эти задачи независимо друг от друга, хотя их можно обрабатывать и одновременно.
Сопоставление / трансляция
У DataFrame есть методы add(), sub(), mul(), div() и связанные функции radd(), rsub(), … для выполнения бинарных операций. При трансляции особый интерес представляет входной объект Series. С помощью этих функций можно выполнять сопоставление по индексу или столбцам, используя аргумент axis:
In [18]: df = pd.DataFrame(
....: {
....: "one": pd.Series(np.random.randn(3), index=["a", "b", "c"]),
....: "two": pd.Series(np.random.randn(4), index=["a", "b", "c", "d"]),
....: "three": pd.Series(np.random.randn(3), index=["b", "c", "d"]),
....: }
....: )
....:
In [19]: df
Out[19]:
one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [20]: row = df.iloc[1]
In [21]: column = df["two"]
In [22]: df.sub(row, axis="columns")
Out[22]:
one two three
a 1.051928 -0.139606 NaN
b 0.000000 0.000000 0.000000
c 0.352192 -0.433754 1.277825
d NaN -1.632779 -0.562782
In [23]: df.sub(row, axis=1)
Out[23]:
one two three
a 1.051928 -0.139606 NaN
b 0.000000 0.000000 0.000000
c 0.352192 -0.433754 1.277825
d NaN -1.632779 -0.562782
In [24]: df.sub(column, axis="index")
Out[24]:
one two three
a -0.377535 0.0 NaN
b -1.569069 0.0 -1.962513
c -0.783123 0.0 -0.250933
d NaN 0.0 -0.892516
In [25]: df.sub(column, axis=0)
Out[25]:
one two three
a -0.377535 0.0 NaN
b -1.569069 0.0 -1.962513
c -0.783123 0.0 -0.250933
d NaN 0.0 -0.892516
Кроме того, можно выровнять уровень DataFrame с MultiIndex по Series.
In [26]: dfmi = df.copy()
In [27]: dfmi.index = pd.MultiIndex.from_tuples(
....: [(1, "a"), (1, "b"), (1, "c"), (2, "a")], names=["first", "second"]
....: )
....:
In [28]: dfmi.sub(column, axis=0, level="second")
Out[28]:
one two three
first second
1 a -0.377535 0.000000 NaN
b -1.569069 0.000000 -1.962513
c -0.783123 0.000000 -0.250933
2 a NaN -1.493173 -2.385688
Series и Index также поддерживают встроенную функцию divmod(). Эта функция одновременно выполняет деление нацело и вычисление остатка, возвращая кортеж из двух элементов того же типа, что и левый операнд. Например:
In [29]: s = pd.Series(np.arange(10))
In [30]: s
Out[30]:
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
dtype: int64
In [31]: div, rem = divmod(s, 3)
In [32]: div
Out[32]:
0 0
1 0
2 0
3 1
4 1
5 1
6 2
7 2
8 2
9 3
dtype: int64
In [33]: rem
Out[33]:
0 0
1 1
2 2
3 0
4 1
5 2
6 0
7 1
8 2
9 0
dtype: int64
In [34]: idx = pd.Index(np.arange(10))
In [35]: idx
Out[35]: Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype='int64')
In [36]: div, rem = divmod(idx, 3)
In [37]: div
Out[37]: Index([0, 0, 0, 1, 1, 1, 2, 2, 2, 3], dtype='int64')
In [38]: rem
Out[38]: Index([0, 1, 2, 0, 1, 2, 0, 1, 2, 0], dtype='int64')
Можно также поэлементно применить divmod():
In [39]: div, rem = divmod(s, [2, 2, 3, 3, 4, 4, 5, 5, 6, 6])
In [40]: div
Out[40]:
0 0
1 0
2 0
3 1
4 1
5 1
6 1
7 1
8 1
9 1
dtype: int64
In [41]: rem
Out[41]:
0 0
1 1
2 2
3 0
4 0
5 1
6 1
7 2
8 2
9 3
dtype: int64
Пропущенные данные / операции со значениями заполнения
В Series и DataFrame арифметические функции позволяют указать fill_value — значение, подставляемое, если в данной позиции отсутствует не более одного значения. Например, при сложении двух объектов DataFrame можно считать NaN равным 0, если только это значение не пропущено в обоих DataFrame; в таком случае результатом будет NaN (при необходимости позже можно заменить NaN другим значением с помощью fillna).
In [42]: df2 = df.copy()
In [43]: df2.loc["a", "three"] = 1.0
In [44]: df
Out[44]:
one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [45]: df2
Out[45]:
one two three
a 1.394981 1.772517 1.000000
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [46]: df + df2
Out[46]:
one two three
a 2.789963 3.545034 NaN
b 0.686107 3.824246 -0.100780
c 1.390491 2.956737 2.454870
d NaN 0.558688 -1.226343
In [47]: df.add(df2, fill_value=0)
Out[47]:
one two three
a 2.789963 3.545034 1.000000
b 0.686107 3.824246 -0.100780
c 1.390491 2.956737 2.454870
d NaN 0.558688 -1.226343
Гибкие сравнения
Для Series и DataFrame доступны бинарные методы сравнения eq, ne, lt, gt, le и ge, поведение которых аналогично описанным выше бинарным арифметическим операциям:
In [48]: df.gt(df2)
Out[48]:
one two three
a False False False
b False False False
c False False False
d False False False
In [49]: df2.ne(df)
Out[49]:
one two three
a False False True
b False False False
c False False False
d True False False
Эти операции возвращают объект pandas того же типа, что и входной объект слева, с типом данных bool. Эти объекты boolean можно использовать для индексирования; см. раздел Логическое индексирование.
Логические свёртки
Для получения сводного логического результата можно применить функции свёртки: empty, any(), all() и bool().
In [50]: (df > 0).all()
Out[50]:
one False
two True
three False
dtype: bool
In [51]: (df > 0).any()
Out[51]:
one True
two True
three True
dtype: bool
Можно свести результат к одному логическому значению.
In [52]: (df > 0).any().any()
Out[52]: True
Проверить, пуст ли объект pandas, можно с помощью свойства empty.
In [53]: df.empty
Out[53]: False
In [54]: pd.DataFrame(columns=list("ABC")).empty
Out[54]: True
Предупреждение
Проверка истинности объекта pandas вызовет ошибку, поскольку результат проверки на пустоту или значений неоднозначен.
In [55]: if df:
....: print(True)
....:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-55-318d08b2571a> in ?()
----> 1 if df:
2 print(True)
~/work/pandas/pandas/pandas/core/generic.py in ?(self)
1575 @final
1576 def __nonzero__(self) -> NoReturn:
-> 1577 raise ValueError(
1578 f"The truth value of a {type(self).__name__} is ambiguous. "
1579 "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
1580 )
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
In [56]: df and df2
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-56-b241b64bb471> in ?()
----> 1 df and df2
~/work/pandas/pandas/pandas/core/generic.py in ?(self)
1575 @final
1576 def __nonzero__(self) -> NoReturn:
-> 1577 raise ValueError(
1578 f"The truth value of a {type(self).__name__} is ambiguous. "
1579 "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
1580 )
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Подробнее см. раздел подводные камни.
Сравнение объектов на эквивалентность
Часто один и тот же результат можно вычислить несколькими способами. В качестве простого примера рассмотрим df + df и df * 2. Чтобы проверить, дают ли эти два вычисления одинаковый результат, можно было бы воспользоваться описанным выше инструментом (df + df == df * 2).all(). Однако на самом деле это выражение возвращает False:
In [57]: df + df == df * 2
Out[57]:
one two three
a True True False
b True True True
c True True True
d False True True
In [58]: (df + df == df * 2).all()
Out[58]:
one False
two True
three False
dtype: bool
Обратите внимание, что логический DataFrame df + df == df * 2 содержит значения False! Это связано с тем, что NaN не равны друг другу при сравнении:
In [59]: np.nan == np.nan
Out[59]: False
Поэтому у объектов NDFrame (например, Series и DataFrame) есть метод equals() для проверки равенства, при которой NaN в соответствующих позициях считаются равными.
In [60]: (df + df).equals(df * 2)
Out[60]: True
Обратите внимание: для того чтобы равенство вернуло True, индексы Series или DataFrame должны иметь одинаковый порядок:
In [61]: df1 = pd.DataFrame({"col": ["foo", 0, np.nan]})
In [62]: df2 = pd.DataFrame({"col": [np.nan, 0, "foo"]}, index=[2, 1, 0])
In [63]: df1.equals(df2)
Out[63]: False
In [64]: df1.equals(df2.sort_index())
Out[64]: True
Сравнение объектов, подобных массивам
При сравнении структуры данных pandas со скалярным значением удобно выполнять поэлементное сравнение:
In [65]: pd.Series(["foo", "bar", "baz"]) == "foo"
Out[65]:
0 True
1 False
2 False
dtype: bool
In [66]: pd.Index(["foo", "bar", "baz"]) == "foo"
Out[66]: array([ True, False, False])
pandas также выполняет поэлементное сравнение разных объектов, подобных массивам, одинаковой длины:
In [67]: pd.Series(["foo", "bar", "baz"]) == pd.Index(["foo", "bar", "qux"])
Out[67]:
0 True
1 True
2 False
dtype: bool
In [68]: pd.Series(["foo", "bar", "baz"]) == np.array(["foo", "bar", "qux"])
Out[68]:
0 True
1 True
2 False
dtype: bool
Попытка сравнить объекты Index или Series разной длины вызовет ValueError:
In [69]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar'])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[69], line 1
----> 1 pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar'])
File ~/work/pandas/pandas/pandas/core/ops/common.py:76, in _unpack_zerodim_and_defer.<locals>.new_method(self, other)
72 return NotImplemented
74 other = item_from_zerodim(other)
---> 76 return method(self, other)
File ~/work/pandas/pandas/pandas/core/arraylike.py:40, in OpsMixin.__eq__(self, other)
38 @unpack_zerodim_and_defer("__eq__")
39 def __eq__(self, other):
---> 40 return self._cmp_method(other, operator.eq)
File ~/work/pandas/pandas/pandas/core/series.py:6125, in Series._cmp_method(self, other, op)
6122 res_name = ops.get_op_result_name(self, other)
6124 if isinstance(other, Series) and not self._indexed_same(other):
-> 6125 raise ValueError("Can only compare identically-labeled Series objects")
6127 lvalues = self._values
6128 rvalues = extract_array(other, extract_numpy=True, extract_range=True)
ValueError: Can only compare identically-labeled Series objects
In [70]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo'])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[70], line 1
----> 1 pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo'])
File ~/work/pandas/pandas/pandas/core/ops/common.py:76, in _unpack_zerodim_and_defer.<locals>.new_method(self, other)
72 return NotImplemented
74 other = item_from_zerodim(other)
---> 76 return method(self, other)
File ~/work/pandas/pandas/pandas/core/arraylike.py:40, in OpsMixin.__eq__(self, other)
38 @unpack_zerodim_and_defer("__eq__")
39 def __eq__(self, other):
---> 40 return self._cmp_method(other, operator.eq)
File ~/work/pandas/pandas/pandas/core/series.py:6125, in Series._cmp_method(self, other, op)
6122 res_name = ops.get_op_result_name(self, other)
6124 if isinstance(other, Series) and not self._indexed_same(other):
-> 6125 raise ValueError("Can only compare identically-labeled Series objects")
6127 lvalues = self._values
6128 rvalues = extract_array(other, extract_numpy=True, extract_range=True)
ValueError: Can only compare identically-labeled Series objects
Объединение перекрывающихся наборов данных
Иногда возникает необходимость объединить два похожих набора данных, отдавая предпочтение значениям из одного из них. Например, два временных ряда могут представлять один и тот же экономический показатель, причём один из них считается «более качественным». Однако ряд более низкого качества может охватывать более длительный период в прошлом или содержать более полные данные. Поэтому мы хотим объединить два объекта DataFrame так, чтобы пропущенные значения в одном DataFrame при определённых условиях заполнялись соответствующими значениями из другого DataFrame. Эту операцию выполняет функция combine_first(). Рассмотрим пример:
In [71]: df1 = pd.DataFrame(
....: {"A": [1.0, np.nan, 3.0, 5.0, np.nan], "B": [np.nan, 2.0, 3.0, np.nan, 6.0]}
....: )
....:
In [72]: df2 = pd.DataFrame(
....: {
....: "A": [5.0, 2.0, 4.0, np.nan, 3.0, 7.0],
....: "B": [np.nan, np.nan, 3.0, 4.0, 6.0, 8.0],
....: }
....: )
....:
In [73]: df1
Out[73]:
A B
0 1.0 NaN
1 NaN 2.0
2 3.0 3.0
3 5.0 NaN
4 NaN 6.0
In [74]: df2
Out[74]:
A B
0 5.0 NaN
1 2.0 NaN
2 4.0 3.0
3 NaN 4.0
4 3.0 6.0
5 7.0 8.0
In [75]: df1.combine_first(df2)
Out[75]:
A B
0 1.0 NaN
1 2.0 2.0
2 3.0 3.0
3 5.0 4.0
4 3.0 6.0
5 7.0 8.0
Общее объединение DataFrame
Описанный выше метод combine_first() вызывает более общий метод DataFrame.combine(). Этот метод принимает другой DataFrame и функцию-комбинатор, выравнивает входной DataFrame, а затем передаёт функции-комбинатору пары объектов Series (то есть столбцы с одинаковыми именами).
Например, чтобы, как и выше, воспроизвести работу combine_first():
In [76]: def combiner(x, y):
....: return np.where(pd.isna(x), y, x)
....:
In [77]: df1.combine(df2, combiner)
Out[77]:
A B
0 1.0 NaN
1 2.0 2.0
2 3.0 3.0
3 5.0 4.0
4 3.0 6.0
5 7.0 8.0
Описательная статистика
Существует множество методов для вычисления описательной статистики и выполнения других связанных операций над объектами Series и DataFrame. Большинство из них — это агрегирующие операции (поэтому они возвращают результат меньшей размерности), например sum(), mean() и quantile(), однако некоторые из них, например cumsum() и cumprod(), возвращают объект того же размера. Как правило, эти методы принимают аргумент axis, как и ndarray.{sum, std, …}, но ось можно указать по имени или целым числом:
Series: аргумент axis не требуется
DataFrame: «index» (axis=0, по умолчанию), «columns» (axis=1)
Например:
In [78]: df
Out[78]:
one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [79]: df.mean(0)
Out[79]:
one 0.811094
two 1.360588
three 0.187958
dtype: float64
In [80]: df.mean(1)
Out[80]:
a 1.583749
b 0.734929
c 1.133683
d -0.166914
dtype: float64
У всех таких методов есть параметр skipna, который указывает, следует ли исключать пропущенные данные (по умолчанию — True):
In [81]: df.sum(0, skipna=False)
Out[81]:
one NaN
two 5.442353
three NaN
dtype: float64
In [82]: df.sum(axis=1, skipna=True)
Out[82]:
a 3.167498
b 2.204786
c 3.401050
d -0.333828
dtype: float64
Сочетая это с широковещанием и арифметическими операциями, можно лаконично описать различные статистические процедуры, например стандартизацию (приведение среднего значения данных к нулю, а стандартного отклонения — к 1):
In [83]: ts_stand = (df - df.mean()) / df.std()
In [84]: ts_stand.std()
Out[84]:
one 1.0
two 1.0
three 1.0
dtype: float64
In [85]: xs_stand = df.sub(df.mean(1), axis=0).div(df.std(1), axis=0)
In [86]: xs_stand.std(1)
Out[86]:
a 1.0
b 1.0
c 1.0
d 1.0
dtype: float64
Обратите внимание, что методы вроде cumsum() и cumprod() сохраняют расположение значений NaN. Это несколько отличается от поведения expanding() и rolling(), поскольку поведение NaN также определяется параметром min_periods.
In [87]: df.cumsum()
Out[87]:
one two three
a 1.394981 1.772517 NaN
b 1.738035 3.684640 -0.050390
c 2.433281 5.163008 1.177045
d NaN 5.442353 0.563873
Ниже приведена краткая справочная таблица распространённых функций. Каждая из них также принимает необязательный параметр level, который применяется только в том случае, если у объекта есть иерархический индекс.
Функция | Описание |
|---|---|
| Количество наблюдений, не равных NA |
| Сумма значений |
| Среднее значение |
| Арифметическая медиана значений |
| Минимум |
| Максимум |
| Мода |
| Абсолютное значение |
| Произведение значений |
| Стандартное отклонение выборки с поправкой Бесселя |
| Несмещённая дисперсия |
| Стандартная ошибка среднего |
| Асимметрия выборки (3-й момент) |
| Эксцесс выборки (4-й момент) |
| Квантиль выборки (значение на уровне %) |
| Накопительная сумма |
| Накопительное произведение |
| Накопительный максимум |
| Накопительный минимум |
Обратите внимание, что некоторые методы NumPy, например mean, std и sum, по умолчанию исключают NA при получении на вход Series:
In [88]: np.mean(df["one"])
Out[88]: 0.8110935116651192
In [89]: np.mean(df["one"].to_numpy())
Out[89]: nan
Series.nunique() возвращает количество уникальных значений Series, не равных NA:
In [90]: series = pd.Series(np.random.randn(500))
In [91]: series[20:500] = np.nan
In [92]: series[10:20] = 5
In [93]: series.nunique()
Out[93]: 11
Сводная информация о данных: describe
Удобная функция describe() вычисляет различные сводные статистики для Series или столбцов DataFrame (разумеется, исключая NA):
In [94]: series = pd.Series(np.random.randn(1000))
In [95]: series[::2] = np.nan
In [96]: series.describe()
Out[96]:
count 500.000000
mean -0.021292
std 1.015906
min -2.683763
25% -0.699070
50% -0.069718
75% 0.714483
max 3.160915
dtype: float64
In [97]: frame = pd.DataFrame(np.random.randn(1000, 5), columns=["a", "b", "c", "d", "e"])
In [98]: frame.iloc[::2] = np.nan
In [99]: frame.describe()
Out[99]:
a b c d e
count 500.000000 500.000000 500.000000 500.000000 500.000000
mean 0.033387 0.030045 -0.043719 -0.051686 0.005979
std 1.017152 0.978743 1.025270 1.015988 1.006695
min -3.000951 -2.637901 -3.303099 -3.159200 -3.188821
25% -0.647623 -0.576449 -0.712369 -0.691338 -0.691115
50% 0.047578 -0.021499 -0.023888 -0.032652 -0.025363
75% 0.729907 0.775880 0.618896 0.670047 0.649748
max 2.740139 2.752332 3.004229 2.728702 3.240991
В выходные данные можно включить определённые процентили:
In [100]: series.describe(percentiles=[0.05, 0.25, 0.75, 0.95])
Out[100]:
count 500.000000
mean -0.021292
std 1.015906
min -2.683763
5% -1.645423
25% -0.699070
50% -0.069718
75% 0.714483
95% 1.711409
max 3.160915
dtype: float64
По умолчанию медиана всегда включается.
Для Series с нечисловыми данными describe() выводит краткую сводку с количеством уникальных значений и наиболее часто встречающимися значениями:
In [101]: s = pd.Series(["a", "a", "b", "b", "a", "a", np.nan, "c", "d", "a"])
In [102]: s.describe()
Out[102]:
count 9
unique 4
top a
freq 5
dtype: object
Обратите внимание, что для DataFrame со смешанными типами describe() включает в сводку только числовые столбцы, а если их нет — только категориальные:
In [103]: frame = pd.DataFrame({"a": ["Yes", "Yes", "No", "No"], "b": range(4)})
In [104]: frame.describe()
Out[104]:
b
count 4.000000
mean 1.500000
std 1.290994
min 0.000000
25% 0.750000
50% 1.500000
75% 2.250000
max 3.000000
Этим поведением можно управлять, передав список типов в аргументах include/exclude. Также можно использовать специальное значение all:
In [105]: frame.describe(include=["object"])
Out[105]:
a
count 4
unique 2
top Yes
freq 2
In [106]: frame.describe(include=["number"])
Out[106]:
b
count 4.000000
mean 1.500000
std 1.290994
min 0.000000
25% 0.750000
50% 1.500000
75% 2.250000
max 3.000000
In [107]: frame.describe(include="all")
Out[107]:
a b
count 4 4.000000
unique 2 NaN
top Yes NaN
freq 2 NaN
mean NaN 1.500000
std NaN 1.290994
min NaN 0.000000
25% NaN 0.750000
50% NaN 1.500000
75% NaN 2.250000
max NaN 3.000000
Эта возможность основана на select_dtypes. Подробные сведения о допустимых входных данных см. там.
Индекс минимальных и максимальных значений
Функции idxmin() и idxmax() для Series и DataFrame вычисляют метки индексов, соответствующие минимальным и максимальным значениям:
In [108]: s1 = pd.Series(np.random.randn(5))
In [109]: s1
Out[109]:
0 1.118076
1 -0.352051
2 -1.242883
3 -1.277155
4 -0.641184
dtype: float64
In [110]: s1.idxmin(), s1.idxmax()
Out[110]: (3, 0)
In [111]: df1 = pd.DataFrame(np.random.randn(5, 3), columns=["A", "B", "C"])
In [112]: df1
Out[112]:
A B C
0 -0.327863 -0.946180 -0.137570
1 -0.186235 -0.257213 -0.486567
2 -0.507027 -0.871259 -0.111110
3 2.000339 -2.430505 0.089759
4 -0.321434 -0.033695 0.096271
In [113]: df1.idxmin(axis=0)
Out[113]:
A 2
B 3
C 1
dtype: int64
In [114]: df1.idxmax(axis=1)
Out[114]:
0 C
1 A
2 C
3 A
4 C
dtype: object
Если минимальному или максимальному значению соответствуют несколько строк (или столбцов), idxmin() и idxmax() возвращают индекс первого совпадения:
In [115]: df3 = pd.DataFrame([2, 1, 1, 3, np.nan], columns=["A"], index=list("edcba"))
In [116]: df3
Out[116]:
A
e 2.0
d 1.0
c 1.0
b 3.0
a NaN
In [117]: df3["A"].idxmin()
Out[117]: 'd'
Примечание
В NumPy функции idxmin и idxmax называются argmin и argmax.
Подсчёт значений (построение гистограммы) / мода
Метод Series value_counts() вычисляет гистограмму одномерного массива значений. Его также можно использовать как функцию для обычных массивов:
In [118]: data = np.random.randint(0, 7, size=50)
In [119]: data
Out[119]:
array([6, 6, 2, 3, 5, 3, 2, 5, 4, 5, 4, 3, 4, 5, 0, 2, 0, 4, 2, 0, 3, 2,
2, 5, 6, 5, 3, 4, 6, 4, 3, 5, 6, 4, 3, 6, 2, 6, 6, 2, 3, 4, 2, 1,
6, 2, 6, 1, 5, 4])
In [120]: s = pd.Series(data)
In [121]: s.value_counts()
Out[121]:
6 10
2 10
4 9
3 8
5 8
0 3
1 2
Name: count, dtype: int64
Метод value_counts() можно использовать для подсчёта сочетаний значений в нескольких столбцах. По умолчанию используются все столбцы, но подмножество можно выбрать с помощью аргумента subset.
In [122]: data = {"a": [1, 2, 3, 4], "b": ["x", "x", "y", "y"]}
In [123]: frame = pd.DataFrame(data)
In [124]: frame.value_counts()
Out[124]:
a b
1 x 1
2 x 1
3 y 1
4 y 1
Name: count, dtype: int64
Аналогичным образом можно получить наиболее часто встречающееся значение или значения, то есть моду, для значений в Series или DataFrame:
In [125]: s5 = pd.Series([1, 1, 3, 3, 3, 5, 5, 7, 7, 7])
In [126]: s5.mode()
Out[126]:
0 3
1 7
dtype: int64
In [127]: df5 = pd.DataFrame(
.....: {
.....: "A": np.random.randint(0, 7, size=50),
.....: "B": np.random.randint(-10, 15, size=50),
.....: }
.....: )
.....:
In [128]: df5.mode()
Out[128]:
A B
0 1.0 -9
1 NaN 10
2 NaN 13
Дискретизация и вычисление квантилей
Непрерывные значения можно дискретизировать с помощью функций cut() (интервалы задаются на основе значений) и qcut() (интервалы задаются на основе квантилей выборки):
In [129]: arr = np.random.randn(20)
In [130]: factor = pd.cut(arr, 4)
In [131]: factor
Out[131]:
[(-0.251, 0.464], (-0.968, -0.251], (0.464, 1.179], (-0.251, 0.464], (-0.968, -0.251], ..., (-0.251, 0.464], (-0.968, -0.251], (-0.968, -0.251], (-0.968, -0.251], (-0.968, -0.251]]
Length: 20
Categories (4, interval[float64, right]): [(-0.968, -0.251] < (-0.251, 0.464] < (0.464, 1.179] <
(1.179, 1.893]]
In [132]: factor = pd.cut(arr, [-5, -1, 0, 1, 5])
In [133]: factor
Out[133]:
[(0, 1], (-1, 0], (0, 1], (0, 1], (-1, 0], ..., (-1, 0], (-1, 0], (-1, 0], (-1, 0], (-1, 0]]
Length: 20
Categories (4, interval[int64, right]): [(-5, -1] < (-1, 0] < (0, 1] < (1, 5]]
qcut() вычисляет квантили выборки. Например, можно разбить нормально распределённые данные на квартили одинакового размера следующим образом:
In [134]: arr = np.random.randn(30)
In [135]: factor = pd.qcut(arr, [0, 0.25, 0.5, 0.75, 1])
In [136]: factor
Out[136]:
[(0.569, 1.184], (-2.278, -0.301], (-2.278, -0.301], (0.569, 1.184], (0.569, 1.184], ..., (-0.301, 0.569], (1.184, 2.346], (1.184, 2.346], (-0.301, 0.569], (-2.278, -0.301]]
Length: 30
Categories (4, interval[float64, right]): [(-2.278, -0.301] < (-0.301, 0.569] < (0.569, 1.184] <
(1.184, 2.346]]
Для определения интервалов также можно передать бесконечные значения:
In [137]: arr = np.random.randn(20)
In [138]: factor = pd.cut(arr, [-np.inf, 0, np.inf])
In [139]: factor
Out[139]:
[(-inf, 0.0], (0.0, inf], (0.0, inf], (-inf, 0.0], (-inf, 0.0], ..., (-inf, 0.0], (-inf, 0.0], (-inf, 0.0], (0.0, inf], (0.0, inf]]
Length: 20
Categories (2, interval[float64, right]): [(-inf, 0.0] < (0.0, inf]]
Применение функций
Чтобы применять собственные функции или функции из других библиотек к объектам pandas, следует знать о трёх приведённых ниже методах. Выбор подходящего метода зависит от того, должна ли функция работать со всем объектом DataFrame или Series, построчно или по столбцам, либо с отдельными элементами.
Применение функции ко всей таблице
В функции можно передавать DataFrames и Series. Однако, если функцию нужно вызвать в цепочке, используйте метод pipe().
Для начала подготовим данные:
In [140]: def extract_city_name(df):
.....: """
.....: Chicago, IL -> Chicago for city_name column
.....: """
.....: df["city_name"] = df["city_and_code"].str.split(",").str.get(0)
.....: return df
.....:
In [141]: def add_country_name(df, country_name=None):
.....: """
.....: Chicago -> Chicago-US for city_name column
.....: """
.....: col = "city_name"
.....: df["city_and_country"] = df[col] + country_name
.....: return df
.....:
In [142]: df_p = pd.DataFrame({"city_and_code": ["Chicago, IL"]})
extract_city_name и add_country_name — это функции, принимающие и возвращающие DataFrames.
Теперь сравним следующие варианты:
In [143]: add_country_name(extract_city_name(df_p), country_name="US")
Out[143]:
city_and_code city_name city_and_country
0 Chicago, IL Chicago ChicagoUS
Это эквивалентно следующему:
In [144]: df_p.pipe(extract_city_name).pipe(add_country_name, country_name="US")
Out[144]:
city_and_code city_name city_and_country
0 Chicago, IL Chicago ChicagoUS
В pandas рекомендуется использовать второй стиль, известный как цепочка вызовов методов. pipe упрощает применение собственных функций и функций из других библиотек в цепочках вызовов вместе с методами pandas.
В приведённом выше примере функции extract_city_name и add_country_name ожидали получить DataFrame в качестве первого позиционного аргумента. Что делать, если данные, к которым нужно применить функцию, передаются, например, вторым аргументом? В таком случае передайте в pipe кортеж с (callable, data_keyword). .pipe передаст DataFrame аргументу, указанному в кортеже.
Например, с помощью statsmodels можно выполнить регрессионный анализ. API этой библиотеки ожидает формулу первым аргументом, а DataFrame — вторым аргументом, data. Передадим функцию и пару ключевого слова с аргументом (sm.ols, 'data') в pipe:
In [147]: import statsmodels.formula.api as sm
In [148]: bb = pd.read_csv("data/baseball.csv", index_col="id")
In [149]: (
.....: bb.query("h > 0")
.....: .assign(ln_h=lambda df: np.log(df.h))
.....: .pipe((sm.ols, "data"), "hr ~ ln_h + year + g + C(lg)")
.....: .fit()
.....: .summary()
.....: )
.....:
Out[149]:
<class 'statsmodels.iolib.summary.Summary'>
"""
OLS Regression Results
==============================================================================
Dep. Variable: hr R-squared: 0.685
Model: OLS Adj. R-squared: 0.665
Method: Least Squares F-statistic: 34.28
Date: Tue, 22 Nov 2022 Prob (F-statistic): 3.48e-15
Time: 05:34:17 Log-Likelihood: -205.92
No. Observations: 68 AIC: 421.8
Df Residuals: 63 BIC: 432.9
Df Model: 4
Covariance Type: nonrobust
===============================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------
Intercept -8484.7720 4664.146 -1.819 0.074 -1.78e+04 835.780
C(lg)[T.NL] -2.2736 1.325 -1.716 0.091 -4.922 0.375
ln_h -1.3542 0.875 -1.547 0.127 -3.103 0.395
year 4.2277 2.324 1.819 0.074 -0.417 8.872
g 0.1841 0.029 6.258 0.000 0.125 0.243
==============================================================================
Omnibus: 10.875 Durbin-Watson: 1.999
Prob(Omnibus): 0.004 Jarque-Bera (JB): 17.298
Skew: 0.537 Prob(JB): 0.000175
Kurtosis: 5.225 Cond. No. 1.49e+07
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.49e+07. This might indicate that there are
strong multicollinearity or other numerical problems.
"""
Метод pipe вдохновлён конвейерами Unix, а позднее — библиотеками dplyr и magrittr, которые добавили популярный оператор (%>%) (читается «pipe», то есть «конвейер») для языка R. Реализация pipe здесь довольно лаконична и естественно вписывается в Python. Рекомендуем ознакомиться с исходным кодом pipe().
Применение функции по строкам или столбцам
Произвольные функции можно применять вдоль осей DataFrame с помощью метода apply(). Как и методы описательной статистики, он принимает необязательный аргумент axis:
In [145]: df.apply(lambda x: np.mean(x))
Out[145]:
one 0.811094
two 1.360588
three 0.187958
dtype: float64
In [146]: df.apply(lambda x: np.mean(x), axis=1)
Out[146]:
a 1.583749
b 0.734929
c 1.133683
d -0.166914
dtype: float64
In [147]: df.apply(lambda x: x.max() - x.min())
Out[147]:
one 1.051928
two 1.632779
three 1.840607
dtype: float64
In [148]: df.apply(np.cumsum)
Out[148]:
one two three
a 1.394981 1.772517 NaN
b 1.738035 3.684640 -0.050390
c 2.433281 5.163008 1.177045
d NaN 5.442353 0.563873
In [149]: df.apply(np.exp)
Out[149]:
one two three
a 4.034899 5.885648 NaN
b 1.409244 6.767440 0.950858
c 2.004201 4.385785 3.412466
d NaN 1.322262 0.541630
Метод apply() также принимает имя метода в виде строки.
In [150]: df.apply("mean")
Out[150]:
one 0.811094
two 1.360588
three 0.187958
dtype: float64
In [151]: df.apply("mean", axis=1)
Out[151]:
a 1.583749
b 0.734929
c 1.133683
d -0.166914
dtype: float64
Тип возвращаемого значения функции, переданной в apply(), определяет тип итогового результата DataFrame.apply при поведении по умолчанию:
Если применённая функция возвращает
Series, итоговый результат —DataFrame. Столбцы соответствуют индексуSeries, возвращённого применённой функцией.Если применённая функция возвращает значение любого другого типа, итоговый результат —
Series.
Это поведение по умолчанию можно изменить с помощью параметра result_type, который принимает три значения: reduce, broadcast и expand. Они определяют, будут ли возвращаемые спископодобные значения преобразованы в DataFrame.
Метод apply() в сочетании с некоторыми дополнительными приёмами позволяет ответить на множество вопросов о наборе данных. Например, предположим, что нужно извлечь дату, когда в каждом столбце встретилось максимальное значение:
In [152]: tsdf = pd.DataFrame(
.....: np.random.randn(1000, 3),
.....: columns=["A", "B", "C"],
.....: index=pd.date_range("1/1/2000", periods=1000),
.....: )
.....:
In [153]: tsdf.apply(lambda x: x.idxmax())
Out[153]:
A 2000-08-06
B 2001-01-18
C 2001-07-18
dtype: datetime64[ns]
В метод apply() также можно передать дополнительные позиционные и именованные аргументы.
In [154]: def subtract_and_divide(x, sub, divide=1):
.....: return (x - sub) / divide
.....:
In [155]: df_udf = pd.DataFrame(np.ones((2, 2)))
In [156]: df_udf.apply(subtract_and_divide, args=(5,), divide=3)
Out[156]:
0 1
0 -1.333333 -1.333333
1 -1.333333 -1.333333
Ещё одна полезная возможность — передавать методы Series, чтобы выполнять операции Series над каждым столбцом или строкой:
In [157]: tsdf = pd.DataFrame(
.....: np.random.randn(10, 3),
.....: columns=["A", "B", "C"],
.....: index=pd.date_range("1/1/2000", periods=10),
.....: )
.....:
In [158]: tsdf.iloc[3:7] = np.nan
In [159]: tsdf
Out[159]:
A B C
2000-01-01 -0.158131 -0.232466 0.321604
2000-01-02 -1.810340 -3.105758 0.433834
2000-01-03 -1.209847 -1.156793 -0.136794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 -0.653602 0.178875 1.008298
2000-01-09 1.007996 0.462824 0.254472
2000-01-10 0.307473 0.600337 1.643950
In [160]: tsdf.apply(pd.Series.interpolate)
Out[160]:
A B C
2000-01-01 -0.158131 -0.232466 0.321604
2000-01-02 -1.810340 -3.105758 0.433834
2000-01-03 -1.209847 -1.156793 -0.136794
2000-01-04 -1.098598 -0.889659 0.092225
2000-01-05 -0.987349 -0.622526 0.321243
2000-01-06 -0.876100 -0.355392 0.550262
2000-01-07 -0.764851 -0.088259 0.779280
2000-01-08 -0.653602 0.178875 1.008298
2000-01-09 1.007996 0.462824 0.254472
2000-01-10 0.307473 0.600337 1.643950
Наконец, метод apply() принимает аргумент raw, который по умолчанию равен False. При значении False каждая строка или столбец перед применением функции преобразуется в Series. Если установить значение True, переданная функция будет получать объект ndarray, что может повысить производительность, если индексирование не требуется.
API агрегирования
API агрегирования позволяет лаконично задать одну или несколько операций агрегирования. Это API устроено сходным образом для разных объектов pandas; см. API groupby, API оконных функций и API ресэмплирования. Точкой входа для агрегирования служит DataFrame.aggregate() или его псевдоним DataFrame.agg().
Воспользуемся DataFrame, аналогичным тому, который создавали выше:
In [161]: tsdf = pd.DataFrame(
.....: np.random.randn(10, 3),
.....: columns=["A", "B", "C"],
.....: index=pd.date_range("1/1/2000", periods=10),
.....: )
.....:
In [162]: tsdf.iloc[3:7] = np.nan
In [163]: tsdf
Out[163]:
A B C
2000-01-01 1.257606 1.004194 0.167574
2000-01-02 -0.749892 0.288112 -0.757304
2000-01-03 -0.207550 -0.298599 0.116018
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.814347 -0.257623 0.869226
2000-01-09 -0.250663 -1.206601 0.896839
2000-01-10 2.169758 -1.333363 0.283157
Использование одной функции эквивалентно apply(). Также можно передавать имена методов в виде строк. Они возвращают Series с результатами агрегирования:
In [164]: tsdf.agg(lambda x: np.sum(x))
Out[164]:
A 3.033606
B -1.803879
C 1.575510
dtype: float64
In [165]: tsdf.agg("sum")
Out[165]:
A 3.033606
B -1.803879
C 1.575510
dtype: float64
# these are equivalent to a ``.sum()`` because we are aggregating
# on a single function
In [166]: tsdf.sum()
Out[166]:
A 3.033606
B -1.803879
C 1.575510
dtype: float64
При применении к Series одной операции агрегирования возвращается скалярное значение:
In [167]: tsdf["A"].agg("sum")
Out[167]: 3.033606102414146
Агрегирование с помощью нескольких функций
В качестве списка можно передать несколько аргументов агрегирования. Результат каждой переданной функции станет строкой в результирующем DataFrame. Названия строк автоматически соответствуют функциям агрегирования.
In [168]: tsdf.agg(["sum"])
Out[168]:
A B C
sum 3.033606 -1.803879 1.57551
Несколько функций создают несколько строк:
In [169]: tsdf.agg(["sum", "mean"])
Out[169]:
A B C
sum 3.033606 -1.803879 1.575510
mean 0.505601 -0.300647 0.262585
Для Series несколько функций возвращают Series с индексом, состоящим из имён функций:
In [170]: tsdf["A"].agg(["sum", "mean"])
Out[170]:
sum 3.033606
mean 0.505601
Name: A, dtype: float64
Передача функции lambda создаёт именованную строку <lambda>:
In [171]: tsdf["A"].agg(["sum", lambda x: x.mean()])
Out[171]:
sum 3.033606
<lambda> 0.505601
Name: A, dtype: float64
При передаче именованной функции её имя используется в качестве имени строки:
In [172]: def mymean(x):
.....: return x.mean()
.....:
In [173]: tsdf["A"].agg(["sum", mymean])
Out[173]:
sum 3.033606
mymean 0.505601
Name: A, dtype: float64
Агрегирование с помощью словаря
Передача в DataFrame.agg словаря, сопоставляющего имена столбцов скалярным значениям или спискам скалярных значений, позволяет настроить функции, применяемые к каждому столбцу. Обратите внимание, что порядок результатов не гарантируется. Чтобы гарантировать порядок, можно использовать OrderedDict.
In [174]: tsdf.agg({"A": "mean", "B": "sum"})
Out[174]:
A 0.505601
B -1.803879
dtype: float64
Передача спископодобного объекта создаёт результат типа DataFrame. Будет возвращён результат, похожий на матрицу, со всеми функциями агрегирования. Результат будет содержать все уникальные функции. Для столбцов, для которых функция не задана, будет указано NaN:
In [175]: tsdf.agg({"A": ["mean", "min"], "B": "sum"})
Out[175]:
A B
mean 0.505601 NaN
min -0.749892 NaN
sum NaN -1.803879
Настраиваемая сводка
С помощью .agg() можно легко создать собственную функцию сводки, аналогичную встроенной функции describe.
In [176]: from functools import partial
In [177]: q_25 = partial(pd.Series.quantile, q=0.25)
In [178]: q_25.__name__ = "25%"
In [179]: q_75 = partial(pd.Series.quantile, q=0.75)
In [180]: q_75.__name__ = "75%"
In [181]: tsdf.agg(["count", "mean", "std", "min", q_25, "median", q_75, "max"])
Out[181]:
A B C
count 6.000000 6.000000 6.000000
mean 0.505601 -0.300647 0.262585
std 1.103362 0.887508 0.606860
min -0.749892 -1.333363 -0.757304
25% -0.239885 -0.979600 0.128907
median 0.303398 -0.278111 0.225365
75% 1.146791 0.151678 0.722709
max 2.169758 1.004194 0.896839
API преобразования
Метод transform() возвращает объект с теми же индексами и размером, что и исходный. Этот API позволяет одновременно передавать несколько операций, а не выполнять их по одной. Он во многом похож на API .agg.
Создадим DataFrame, аналогичный использованному в предыдущих разделах.
In [182]: tsdf = pd.DataFrame(
.....: np.random.randn(10, 3),
.....: columns=["A", "B", "C"],
.....: index=pd.date_range("1/1/2000", periods=10),
.....: )
.....:
In [183]: tsdf.iloc[3:7] = np.nan
In [184]: tsdf
Out[184]:
A B C
2000-01-01 -0.428759 -0.864890 -0.675341
2000-01-02 -0.168731 1.338144 -1.279321
2000-01-03 -1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 -1.240447 -0.201052
2000-01-09 -0.157795 0.791197 -1.144209
2000-01-10 -0.030876 0.371900 0.061932
Преобразуем весь DataFrame. .transform() принимает в качестве входных функций функцию NumPy, имя функции в виде строки или определённую пользователем функцию.
In [185]: tsdf.transform(np.abs)
Out[185]:
A B C
2000-01-01 0.428759 0.864890 0.675341
2000-01-02 0.168731 1.338144 1.279321
2000-01-03 1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 1.240447 0.201052
2000-01-09 0.157795 0.791197 1.144209
2000-01-10 0.030876 0.371900 0.061932
In [186]: tsdf.transform("abs")
Out[186]:
A B C
2000-01-01 0.428759 0.864890 0.675341
2000-01-02 0.168731 1.338144 1.279321
2000-01-03 1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 1.240447 0.201052
2000-01-09 0.157795 0.791197 1.144209
2000-01-10 0.030876 0.371900 0.061932
In [187]: tsdf.transform(lambda x: x.abs())
Out[187]:
A B C
2000-01-01 0.428759 0.864890 0.675341
2000-01-02 0.168731 1.338144 1.279321
2000-01-03 1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 1.240447 0.201052
2000-01-09 0.157795 0.791197 1.144209
2000-01-10 0.030876 0.371900 0.061932
Здесь transform() получил одну функцию; это эквивалентно применению ufunc.
In [188]: np.abs(tsdf)
Out[188]:
A B C
2000-01-01 0.428759 0.864890 0.675341
2000-01-02 0.168731 1.338144 1.279321
2000-01-03 1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 1.240447 0.201052
2000-01-09 0.157795 0.791197 1.144209
2000-01-10 0.030876 0.371900 0.061932
Передача одной функции в .transform() вместе с Series вернёт один Series.
In [189]: tsdf["A"].transform(np.abs)
Out[189]:
2000-01-01 0.428759
2000-01-02 0.168731
2000-01-03 1.621034
2000-01-04 NaN
2000-01-05 NaN
2000-01-06 NaN
2000-01-07 NaN
2000-01-08 0.254374
2000-01-09 0.157795
2000-01-10 0.030876
Freq: D, Name: A, dtype: float64
Преобразование с помощью нескольких функций
Передача нескольких функций возвращает DataFrame с многоуровневыми именами столбцов. Первый уровень — исходные имена столбцов DataFrame, второй — имена функций преобразования.
In [190]: tsdf.transform([np.abs, lambda x: x + 1])
Out[190]:
A B C
absolute <lambda> absolute <lambda> absolute <lambda>
2000-01-01 0.428759 0.571241 0.864890 0.135110 0.675341 0.324659
2000-01-02 0.168731 0.831269 1.338144 2.338144 1.279321 -0.279321
2000-01-03 1.621034 -0.621034 0.438107 1.438107 0.903794 1.903794
2000-01-04 NaN NaN NaN NaN NaN NaN
2000-01-05 NaN NaN NaN NaN NaN NaN
2000-01-06 NaN NaN NaN NaN NaN NaN
2000-01-07 NaN NaN NaN NaN NaN NaN
2000-01-08 0.254374 1.254374 1.240447 -0.240447 0.201052 0.798948
2000-01-09 0.157795 0.842205 0.791197 1.791197 1.144209 -0.144209
2000-01-10 0.030876 0.969124 0.371900 1.371900 0.061932 1.061932
Передача нескольких функций для Series возвращает DataFrame. Именами его столбцов будут имена функций преобразования.
In [191]: tsdf["A"].transform([np.abs, lambda x: x + 1])
Out[191]:
absolute <lambda>
2000-01-01 0.428759 0.571241
2000-01-02 0.168731 0.831269
2000-01-03 1.621034 -0.621034
2000-01-04 NaN NaN
2000-01-05 NaN NaN
2000-01-06 NaN NaN
2000-01-07 NaN NaN
2000-01-08 0.254374 1.254374
2000-01-09 0.157795 0.842205
2000-01-10 0.030876 0.969124
Преобразование с помощью словаря
Передача словаря функций позволяет выборочно выполнять преобразования для каждого столбца.
In [192]: tsdf.transform({"A": np.abs, "B": lambda x: x + 1})
Out[192]:
A B
2000-01-01 0.428759 0.135110
2000-01-02 0.168731 2.338144
2000-01-03 1.621034 1.438107
2000-01-04 NaN NaN
2000-01-05 NaN NaN
2000-01-06 NaN NaN
2000-01-07 NaN NaN
2000-01-08 0.254374 -0.240447
2000-01-09 0.157795 1.791197
2000-01-10 0.030876 1.371900
Передача словаря списков создаёт DataFrame с многоуровневыми именами столбцов, содержащий выбранные преобразования.
In [193]: tsdf.transform({"A": np.abs, "B": [lambda x: x + 1, "sqrt"]})
Out[193]:
A B
absolute <lambda> sqrt
2000-01-01 0.428759 0.135110 NaN
2000-01-02 0.168731 2.338144 1.156782
2000-01-03 1.621034 1.438107 0.661897
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 -0.240447 NaN
2000-01-09 0.157795 1.791197 0.889493
2000-01-10 0.030876 1.371900 0.609836
Применение функций к отдельным элементам
Поскольку не все функции можно векторизовать (то есть передать им массивы NumPy и получить другой массив или значение), методы map() для DataFrame и аналогичный map() для Series принимают любую функцию Python, которая получает одно значение и возвращает одно значение. Например:
In [194]: df4 = df.copy()
In [195]: df4
Out[195]:
one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [196]: def f(x):
.....: return len(str(x))
.....:
In [197]: df4["one"].map(f)
Out[197]:
a 18
b 19
c 18
d 3
Name: one, dtype: int64
In [198]: df4.map(f)
Out[198]:
one two three
a 18 17 3
b 19 18 20
c 18 18 16
d 3 19 19
У Series.map() есть дополнительная возможность: с его помощью можно легко связать или сопоставить значения, определённые во вспомогательном ряде. Это тесно связано с объединением таблиц:
In [199]: s = pd.Series(
.....: ["six", "seven", "six", "seven", "six"], index=["a", "b", "c", "d", "e"]
.....: )
.....:
In [200]: t = pd.Series({"six": 6.0, "seven": 7.0})
In [201]: s
Out[201]:
a six
b seven
c six
d seven
e six
dtype: object
In [202]: s.map(t)
Out[202]:
a 6.0
b 7.0
c 6.0
d 7.0
e 6.0
dtype: float64
Переиндексация и изменение меток
reindex() — это фундаментальный метод выравнивания данных в pandas. Он используется для реализации почти всех остальных функций, основанных на выравнивании по меткам. Переиндексировать — значит привести данные в соответствие с заданным набором меток вдоль определённой оси. Это позволяет выполнить несколько действий:
Переупорядочить существующие данные в соответствии с новым набором меток
Вставить маркеры отсутствующих значений (NA) в позиции меток, для которых нет данных
Если указано, заполнить данные для отсутствующих меток с помощью логики (особенно важно при работе с временными рядами)
Вот простой пример:
In [203]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
In [204]: s
Out[204]:
a 1.695148
b 1.328614
c 1.234686
d -0.385845
e -1.326508
dtype: float64
In [205]: s.reindex(["e", "b", "f", "d"])
Out[205]:
e -1.326508
b 1.328614
f NaN
d -0.385845
dtype: float64
Здесь метка f отсутствовала в Series, поэтому в результате она представлена как NaN.
Для DataFrame можно одновременно переиндексировать индекс и столбцы:
In [206]: df
Out[206]:
one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [207]: df.reindex(index=["c", "f", "b"], columns=["three", "two", "one"])
Out[207]:
three two one
c 1.227435 1.478369 0.695246
f NaN NaN NaN
b -0.050390 1.912123 0.343054
Обратите внимание, что объекты Index, содержащие фактические метки осей, могут быть общими для нескольких объектов. Поэтому, если у нас есть Series и DataFrame, можно выполнить следующее:
In [208]: rs = s.reindex(df.index)
In [209]: rs
Out[209]:
a 1.695148
b 1.328614
c 1.234686
d -0.385845
dtype: float64
In [210]: rs.index is df.index
Out[210]: True
Это означает, что индекс переиндексированной Series — тот же объект Python, что и индекс DataFrame.
DataFrame.reindex() также поддерживает вызов в «стиле оси», при котором указывается один аргумент labels и axis, к которой он применяется.
In [211]: df.reindex(["c", "f", "b"], axis="index")
Out[211]:
one two three
c 0.695246 1.478369 1.227435
f NaN NaN NaN
b 0.343054 1.912123 -0.050390
In [212]: df.reindex(["three", "two", "one"], axis="columns")
Out[212]:
three two one
a NaN 1.772517 1.394981
b -0.050390 1.912123 0.343054
c 1.227435 1.478369 0.695246
d -0.613172 0.279344 NaN
См. также
MultiIndex / расширенная индексация — ещё более лаконичный способ переиндексации.
Примечание
При написании кода, чувствительного к производительности, есть веская причина потратить время и освоить переиндексацию: многие операции выполняются быстрее на предварительно выровненных данных. При сложении двух невыровненных DataFrame внутри выполняется переиндексация. При исследовательском анализе вы вряд ли заметите разницу (поскольку reindex сильно оптимизирован), но когда важен каждый такт процессора, несколько явных вызовов reindex могут повлиять на производительность.
Переиндексация для выравнивания с другим объектом
Возможно, вам потребуется переиндексировать оси объекта так, чтобы их метки совпадали с метками другого объекта. Хотя синтаксис для этого прост, но многословен, это достаточно распространённая операция, поэтому для упрощения доступен метод reindex_like():
In [213]: df2 = df.reindex(["a", "b", "c"], columns=["one", "two"])
In [214]: df3 = df2 - df2.mean()
In [215]: df2
Out[215]:
one two
a 1.394981 1.772517
b 0.343054 1.912123
c 0.695246 1.478369
In [216]: df3
Out[216]:
one two
a 0.583888 0.051514
b -0.468040 0.191120
c -0.115848 -0.242634
In [217]: df.reindex_like(df2)
Out[217]:
one two
a 1.394981 1.772517
b 0.343054 1.912123
c 0.695246 1.478369
Выравнивание объектов друг с другом с помощью align
Метод align() — самый быстрый способ одновременно выровнять два объекта. Он поддерживает аргумент join (связанный с соединением и объединением):
join='outer': использовать объединение индексов (по умолчанию)
join='left': использовать индекс вызывающего объекта
join='right': использовать индекс переданного объекта
join='inner': использовать пересечение индексов
Метод возвращает кортеж с обеими переиндексированными Series:
In [218]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
In [219]: s1 = s[:4]
In [220]: s2 = s[1:]
In [221]: s1.align(s2)
Out[221]:
(a -0.186646
b -1.692424
c -0.303893
d -1.425662
e NaN
dtype: float64,
a NaN
b -1.692424
c -0.303893
d -1.425662
e 1.114285
dtype: float64)
In [222]: s1.align(s2, join="inner")
Out[222]:
(b -1.692424
c -0.303893
d -1.425662
dtype: float64,
b -1.692424
c -0.303893
d -1.425662
dtype: float64)
In [223]: s1.align(s2, join="left")
Out[223]:
(a -0.186646
b -1.692424
c -0.303893
d -1.425662
dtype: float64,
a NaN
b -1.692424
c -0.303893
d -1.425662
dtype: float64)
Для DataFrame по умолчанию метод соединения применяется и к индексу, и к столбцам:
In [224]: df.align(df2, join="inner")
Out[224]:
( one two
a 1.394981 1.772517
b 0.343054 1.912123
c 0.695246 1.478369,
one two
a 1.394981 1.772517
b 0.343054 1.912123
c 0.695246 1.478369)
Можно также передать параметр axis, чтобы выравнивание выполнялось только по указанной оси:
In [225]: df.align(df2, join="inner", axis=0)
Out[225]:
( one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435,
one two
a 1.394981 1.772517
b 0.343054 1.912123
c 0.695246 1.478369)
Если передать Series в DataFrame.align(), с помощью аргумента axis можно выбрать, по какой оси выравнивать оба объекта: по индексу или столбцам DataFrame:
In [226]: df.align(df2.iloc[0], axis=1)
Out[226]:
( one three two
a 1.394981 NaN 1.772517
b 0.343054 -0.050390 1.912123
c 0.695246 1.227435 1.478369
d NaN -0.613172 0.279344,
one 1.394981
three NaN
two 1.772517
Name: a, dtype: float64)
Заполнение при переиндексации
reindex() принимает необязательный параметр method — метод заполнения, выбираемый из следующей таблицы:
Метод | Действие |
|---|---|
pad / ffill | Заполнение значениями вперёд |
bfill / backfill | Заполнение значениями назад |
nearest | Заполнение значением ближайшего индекса |
Покажем эти методы заполнения на простой Series:
In [227]: rng = pd.date_range("1/3/2000", periods=8)
In [228]: ts = pd.Series(np.random.randn(8), index=rng)
In [229]: ts2 = ts.iloc[[0, 3, 6]]
In [230]: ts
Out[230]:
2000-01-03 0.183051
2000-01-04 0.400528
2000-01-05 -0.015083
2000-01-06 2.395489
2000-01-07 1.414806
2000-01-08 0.118428
2000-01-09 0.733639
2000-01-10 -0.936077
Freq: D, dtype: float64
In [231]: ts2
Out[231]:
2000-01-03 0.183051
2000-01-06 2.395489
2000-01-09 0.733639
Freq: 3D, dtype: float64
In [232]: ts2.reindex(ts.index)
Out[232]:
2000-01-03 0.183051
2000-01-04 NaN
2000-01-05 NaN
2000-01-06 2.395489
2000-01-07 NaN
2000-01-08 NaN
2000-01-09 0.733639
2000-01-10 NaN
Freq: D, dtype: float64
In [233]: ts2.reindex(ts.index, method="ffill")
Out[233]:
2000-01-03 0.183051
2000-01-04 0.183051
2000-01-05 0.183051
2000-01-06 2.395489
2000-01-07 2.395489
2000-01-08 2.395489
2000-01-09 0.733639
2000-01-10 0.733639
Freq: D, dtype: float64
In [234]: ts2.reindex(ts.index, method="bfill")
Out[234]:
2000-01-03 0.183051
2000-01-04 2.395489
2000-01-05 2.395489
2000-01-06 2.395489
2000-01-07 0.733639
2000-01-08 0.733639
2000-01-09 0.733639
2000-01-10 NaN
Freq: D, dtype: float64
In [235]: ts2.reindex(ts.index, method="nearest")
Out[235]:
2000-01-03 0.183051
2000-01-04 0.183051
2000-01-05 2.395489
2000-01-06 2.395489
2000-01-07 2.395489
2000-01-08 0.733639
2000-01-09 0.733639
2000-01-10 0.733639
Freq: D, dtype: float64
Для работы этих методов индексы должны быть упорядочены по возрастанию или убыванию.
Обратите внимание, что такого же результата можно было бы добиться с помощью ffill (за исключением method='nearest') или interpolate:
In [236]: ts2.reindex(ts.index).ffill()
Out[236]:
2000-01-03 0.183051
2000-01-04 0.183051
2000-01-05 0.183051
2000-01-06 2.395489
2000-01-07 2.395489
2000-01-08 2.395489
2000-01-09 0.733639
2000-01-10 0.733639
Freq: D, dtype: float64
reindex() вызовет ValueError, если индекс не упорядочен монотонно по возрастанию или убыванию. Методы fillna() и interpolate() не проверяют порядок индекса.
Ограничения заполнения при переиндексации
Аргументы limit и tolerance обеспечивают дополнительный контроль над заполнением при переиндексации. Параметр limit задаёт максимальное количество последовательных совпадений:
In [237]: ts2.reindex(ts.index, method="ffill", limit=1)
Out[237]:
2000-01-03 0.183051
2000-01-04 0.183051
2000-01-05 NaN
2000-01-06 2.395489
2000-01-07 2.395489
2000-01-08 NaN
2000-01-09 0.733639
2000-01-10 0.733639
Freq: D, dtype: float64
В отличие от него, tolerance задаёт максимальное расстояние между значениями индекса и индексатора:
In [238]: ts2.reindex(ts.index, method="ffill", tolerance="1 day")
Out[238]:
2000-01-03 0.183051
2000-01-04 0.183051
2000-01-05 NaN
2000-01-06 2.395489
2000-01-07 2.395489
2000-01-08 NaN
2000-01-09 0.733639
2000-01-10 0.733639
Freq: D, dtype: float64
Обратите внимание: при применении к DatetimeIndex, TimedeltaIndex или PeriodIndex значение tolerance будет приведено к Timedelta, если это возможно. Это позволяет задавать tolerance с помощью подходящих строк.
Удаление меток с оси
Метод, тесно связанный с reindex, — функция drop(). Она удаляет набор меток с оси:
In [239]: df
Out[239]:
one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [240]: df.drop(["a", "d"], axis=0)
Out[240]:
one two three
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
In [241]: df.drop(["one"], axis=1)
Out[241]:
two three
a 1.772517 NaN
b 1.912123 -0.050390
c 1.478369 1.227435
d 0.279344 -0.613172
Обратите внимание, что работает и следующий вариант, хотя он несколько менее очевиден и аккуратен:
In [242]: df.reindex(df.index.difference(["a", "d"]))
Out[242]:
one two three
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
Переименование / сопоставление меток
Метод rename() позволяет переименовать метки оси на основе некоторого сопоставления (dict или Series) либо произвольной функции.
In [243]: s
Out[243]:
a -0.186646
b -1.692424
c -0.303893
d -1.425662
e 1.114285
dtype: float64
In [244]: s.rename(str.upper)
Out[244]:
A -0.186646
B -1.692424
C -0.303893
D -1.425662
E 1.114285
dtype: float64
Если передать функцию, она должна возвращать значение для любой метки, с которой её вызовут (и создавать набор уникальных значений). Также можно использовать dict или Series:
In [245]: df.rename(
.....: columns={"one": "foo", "two": "bar"},
.....: index={"a": "apple", "b": "banana", "d": "durian"},
.....: )
.....:
Out[245]:
foo bar three
apple 1.394981 1.772517 NaN
banana 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
durian NaN 0.279344 -0.613172
Если в сопоставлении нет метки столбца или индекса, она не переименовывается. Обратите внимание: дополнительные метки в сопоставлении не вызывают ошибку.
DataFrame.rename() также поддерживает вызов в «стиле оси», при котором указывается один параметр mapper и axis, к которой нужно применить это сопоставление.
In [246]: df.rename({"one": "foo", "two": "bar"}, axis="columns")
Out[246]:
foo bar three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [247]: df.rename({"a": "apple", "b": "banana", "d": "durian"}, axis="index")
Out[247]:
one two three
apple 1.394981 1.772517 NaN
banana 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
durian NaN 0.279344 -0.613172
Наконец, rename() также принимает скалярное значение или объект, подобный списку, для изменения атрибута Series.name.
In [248]: s.rename("scalar-name")
Out[248]:
a -0.186646
b -1.692424
c -0.303893
d -1.425662
e 1.114285
Name: scalar-name, dtype: float64
Методы DataFrame.rename_axis() и Series.rename_axis() позволяют изменить имена оси MultiIndex (в отличие от меток).
In [249]: df = pd.DataFrame(
.....: {"x": [1, 2, 3, 4, 5, 6], "y": [10, 20, 30, 40, 50, 60]},
.....: index=pd.MultiIndex.from_product(
.....: [["a", "b", "c"], [1, 2]], names=["let", "num"]
.....: ),
.....: )
.....:
In [250]: df
Out[250]:
x y
let num
a 1 1 10
2 2 20
b 1 3 30
2 4 40
c 1 5 50
2 6 60
In [251]: df.rename_axis(index={"let": "abc"})
Out[251]:
x y
abc num
a 1 1 10
2 2 20
b 1 3 30
2 4 40
c 1 5 50
2 6 60
In [252]: df.rename_axis(index=str.upper)
Out[252]:
x y
LET NUM
a 1 1 10
2 2 20
b 1 3 30
2 4 40
c 1 5 50
2 6 60
Итерация
Поведение при базовой итерации по объектам pandas зависит от их типа. При итерации по Series она рассматривается как массивоподобный объект, и базовая итерация возвращает значения. DataFrame следует соглашению, принятому для объектов типа dict: итерация выполняется по «ключам» объекта.
Кратко, базовая итерация (for i in object) возвращает:
Series: значения
DataFrame: метки столбцов
Например, итерация по DataFrame возвращает имена столбцов:
In [253]: df = pd.DataFrame(
.....: {"col1": np.random.randn(3), "col2": np.random.randn(3)}, index=["a", "b", "c"]
.....: )
.....:
In [254]: for col in df:
.....: print(col)
.....:
col1
col2
Объекты pandas также имеют метод items(), аналогичный методу dict, для итерации по парам (ключ, значение).
Для итерации по строкам DataFrame можно использовать следующие методы:
iterrows(): итерация по строкам DataFrame в виде пар (индекс, Series). Строки преобразуются в объекты Series, что может изменить типы данных и повлиять на производительность.itertuples(): итерация по строкам DataFrame в виде именованных кортежей со значениями. Этот метод значительно быстрее, чемiterrows(), и в большинстве случаев предпочтителен для итерации по значениям DataFrame.
Предупреждение
Итерация по объектам pandas, как правило, медленная. Во многих случаях ручная итерация по строкам не нужна, и её можно избежать одним из следующих способов:
Найдите векторизованное решение: многие операции можно выполнить с помощью встроенных методов или функций NumPy, булевой индексации и т. д.
Если функция не может обрабатывать весь DataFrame/Series сразу, лучше использовать
apply(), а не перебирать значения. См. документацию о применении функций.Если необходимо выполнять итеративные операции над значениями, но важна производительность, рассмотрите возможность написания внутреннего цикла на cython или numba. Примеры такого подхода приведены в разделе повышение производительности.
Предупреждение
Никогда не изменяйте объект, по которому выполняется итерация. Это не гарантированно будет работать во всех случаях. В зависимости от типов данных итератор возвращает копию, а не представление, поэтому запись в неё не даст эффекта!
Например, в следующем случае присваивание значения не даст эффекта:
In [255]: df = pd.DataFrame({"a": [1, 2, 3], "b": ["a", "b", "c"]})
In [256]: for index, row in df.iterrows():
.....: row["a"] = 10
.....:
In [257]: df
Out[257]:
a b
0 1 a
1 2 b
2 3 c
items
В соответствии с интерфейсом, аналогичным dict, метод items() перебирает пары ключ-значение:
Series: пары (индекс, скалярное значение)
DataFrame: пары (столбец, Series)
Например:
In [258]: for label, ser in df.items():
.....: print(label)
.....: print(ser)
.....:
a
0 1
1 2
2 3
Name: a, dtype: int64
b
0 a
1 b
2 c
Name: b, dtype: object
iterrows
iterrows() позволяет перебирать строки DataFrame в виде объектов Series. Метод возвращает итератор, который для каждой строки выдаёт значение индекса и Series с данными этой строки:
In [259]: for row_index, row in df.iterrows():
.....: print(row_index, row, sep="\n")
.....:
0
a 1
b a
Name: 0, dtype: object
1
a 2
b b
Name: 1, dtype: object
2
a 3
b c
Name: 2, dtype: object
Примечание
Поскольку iterrows() возвращает Series для каждой строки, он не сохраняет типы данных между строками (в DataFrame типы данных сохраняются между столбцами). Например:
In [260]: df_orig = pd.DataFrame([[1, 1.5]], columns=["int", "float"])
In [261]: df_orig.dtypes
Out[261]:
int int64
float float64
dtype: object
In [262]: row = next(df_orig.iterrows())[1]
In [263]: row
Out[263]:
int 1.0
float 1.5
Name: 0, dtype: float64
Все значения в row, возвращённые как Series, теперь приведены к типу float, включая исходное целочисленное значение в столбце x:
In [264]: row["int"].dtype
Out[264]: dtype('float64')
In [265]: df_orig["int"].dtype
Out[265]: dtype('int64')
Чтобы сохранять типы данных при итерации по строкам, лучше использовать itertuples(), который возвращает именованные кортежи со значениями и обычно намного быстрее, чем iterrows().
Например, DataFrame можно транспонировать следующим искусственным способом:
In [266]: df2 = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
In [267]: print(df2)
x y
0 1 4
1 2 5
2 3 6
In [268]: print(df2.T)
0 1 2
x 1 2 3
y 4 5 6
In [269]: df2_t = pd.DataFrame({idx: values for idx, values in df2.iterrows()})
In [270]: print(df2_t)
0 1 2
x 1 2 3
y 4 5 6
itertuples
Метод itertuples() возвращает итератор, выдающий именованный кортеж для каждой строки DataFrame. Первый элемент кортежа — соответствующее значение индекса строки, остальные элементы — значения строки.
Например:
In [271]: for row in df.itertuples():
.....: print(row)
.....:
Pandas(Index=0, a=1, b='a')
Pandas(Index=1, a=2, b='b')
Pandas(Index=2, a=3, b='c')
Этот метод не преобразует строку в объект Series, а просто возвращает значения в именованном кортеже. Поэтому itertuples() сохраняет типы данных значений и обычно работает быстрее, чем iterrows().
Примечание
Имена столбцов будут заменены позиционными именами, если они не являются допустимыми идентификаторами Python, повторяются или начинаются с символа подчёркивания. Если столбцов много (>255), возвращаются обычные кортежи.
Аксессор .dt
Series имеет аксессор, который позволяет кратко получать свойства, связанные с датой и временем, для значений Series, если она содержит данные типа datetime или period. Результатом будет Series с тем же индексом, что и у исходной Series.
# datetime
In [272]: s = pd.Series(pd.date_range("20130101 09:10:12", periods=4))
In [273]: s
Out[273]:
0 2013-01-01 09:10:12
1 2013-01-02 09:10:12
2 2013-01-03 09:10:12
3 2013-01-04 09:10:12
dtype: datetime64[ns]
In [274]: s.dt.hour
Out[274]:
0 9
1 9
2 9
3 9
dtype: int32
In [275]: s.dt.second
Out[275]:
0 12
1 12
2 12
3 12
dtype: int32
In [276]: s.dt.day
Out[276]:
0 1
1 2
2 3
3 4
dtype: int32
Это позволяет записывать удобные выражения, например:
In [277]: s[s.dt.day == 2]
Out[277]:
1 2013-01-02 09:10:12
dtype: datetime64[ns]
Можно легко выполнять преобразования с учётом часового пояса:
In [278]: stz = s.dt.tz_localize("US/Eastern")
In [279]: stz
Out[279]:
0 2013-01-01 09:10:12-05:00
1 2013-01-02 09:10:12-05:00
2 2013-01-03 09:10:12-05:00
3 2013-01-04 09:10:12-05:00
dtype: datetime64[ns, US/Eastern]
In [280]: stz.dt.tz
Out[280]: <DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>
Также можно объединять такие операции в цепочки:
In [281]: s.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[281]:
0 2013-01-01 04:10:12-05:00
1 2013-01-02 04:10:12-05:00
2 2013-01-03 04:10:12-05:00
3 2013-01-04 04:10:12-05:00
dtype: datetime64[ns, US/Eastern]
Также можно форматировать значения даты и времени как строки с помощью Series.dt.strftime(), который поддерживает тот же формат, что и стандартный strftime().
# DatetimeIndex
In [282]: s = pd.Series(pd.date_range("20130101", periods=4))
In [283]: s
Out[283]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
3 2013-01-04
dtype: datetime64[ns]
In [284]: s.dt.strftime("%Y/%m/%d")
Out[284]:
0 2013/01/01
1 2013/01/02
2 2013/01/03
3 2013/01/04
dtype: object
# PeriodIndex
In [285]: s = pd.Series(pd.period_range("20130101", periods=4))
In [286]: s
Out[286]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
3 2013-01-04
dtype: period[D]
In [287]: s.dt.strftime("%Y/%m/%d")
Out[287]:
0 2013/01/01
1 2013/01/02
2 2013/01/03
3 2013/01/04
dtype: object
Аксессор .dt работает с типами period и timedelta.
# period
In [288]: s = pd.Series(pd.period_range("20130101", periods=4, freq="D"))
In [289]: s
Out[289]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
3 2013-01-04
dtype: period[D]
In [290]: s.dt.year
Out[290]:
0 2013
1 2013
2 2013
3 2013
dtype: int64
In [291]: s.dt.day
Out[291]:
0 1
1 2
2 3
3 4
dtype: int64
# timedelta
In [292]: s = pd.Series(pd.timedelta_range("1 day 00:00:05", periods=4, freq="s"))
In [293]: s
Out[293]:
0 1 days 00:00:05
1 1 days 00:00:06
2 1 days 00:00:07
3 1 days 00:00:08
dtype: timedelta64[ns]
In [294]: s.dt.days
Out[294]:
0 1
1 1
2 1
3 1
dtype: int64
In [295]: s.dt.seconds
Out[295]:
0 5
1 6
2 7
3 8
dtype: int32
In [296]: s.dt.components
Out[296]:
days hours minutes seconds milliseconds microseconds nanoseconds
0 1 0 0 5 0 0 0
1 1 0 0 6 0 0 0
2 1 0 0 7 0 0 0
3 1 0 0 8 0 0 0
Примечание
При обращении к Series.dt со значениями, не относящимися к типам даты и времени, будет вызвано исключение TypeError.
Векторизованные строковые методы
Series предоставляет набор методов обработки строк, упрощающих работу с каждым элементом массива. В частности, эти методы автоматически исключают отсутствующие значения/NA. Доступ к ним осуществляется через атрибут Series str; их имена, как правило, совпадают с именами соответствующих встроенных методов для отдельных строк. Например:
In [297]: s = pd.Series( .....: ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string" .....: ) .....: In [298]: s.str.lower() Out[298]: 0 a 1 b 2 c 3 aaba 4 baca 5 <NA> 6 caba 7 dog 8 cat dtype: string
Также доступны мощные методы поиска по шаблону, однако учтите, что по умолчанию поиск по шаблону обычно использует регулярные выражения (а в некоторых случаях использует их всегда).
Примечание
До pandas 1.0 строковые методы были доступны только для object-Series. В pandas 1.0 появился тип StringDtype, предназначенный для строк. Подробнее см. раздел Текстовые типы данных.
Полное описание см. в разделе Векторизованные строковые методы.
Сортировка
pandas поддерживает три вида сортировки: по меткам индекса, по значениям столбцов и по сочетанию обоих способов.
По индексу
Методы Series.sort_index() и DataFrame.sort_index() используются для сортировки объекта pandas по уровням его индекса.
In [299]: df = pd.DataFrame(
.....: {
.....: "one": pd.Series(np.random.randn(3), index=["a", "b", "c"]),
.....: "two": pd.Series(np.random.randn(4), index=["a", "b", "c", "d"]),
.....: "three": pd.Series(np.random.randn(3), index=["b", "c", "d"]),
.....: }
.....: )
.....:
In [300]: unsorted_df = df.reindex(
.....: index=["a", "d", "c", "b"], columns=["three", "two", "one"]
.....: )
.....:
In [301]: unsorted_df
Out[301]:
three two one
a NaN -1.152244 0.562973
d -0.252916 -0.109597 NaN
c 1.273388 -0.167123 0.640382
b -0.098217 0.009797 -1.299504
# DataFrame
In [302]: unsorted_df.sort_index()
Out[302]:
three two one
a NaN -1.152244 0.562973
b -0.098217 0.009797 -1.299504
c 1.273388 -0.167123 0.640382
d -0.252916 -0.109597 NaN
In [303]: unsorted_df.sort_index(ascending=False)
Out[303]:
three two one
d -0.252916 -0.109597 NaN
c 1.273388 -0.167123 0.640382
b -0.098217 0.009797 -1.299504
a NaN -1.152244 0.562973
In [304]: unsorted_df.sort_index(axis=1)
Out[304]:
one three two
a 0.562973 NaN -1.152244
d NaN -0.252916 -0.109597
c 0.640382 1.273388 -0.167123
b -1.299504 -0.098217 0.009797
# Series
In [305]: unsorted_df["three"].sort_index()
Out[305]:
a NaN
b -0.098217
c 1.273388
d -0.252916
Name: three, dtype: float64
Сортировка по индексу также поддерживает параметр key, принимающий вызываемую функцию для применения к сортируемому индексу. Для объектов MultiIndex ключ применяется к каждому уровню, указанному в level.
In [306]: s1 = pd.DataFrame({"a": ["B", "a", "C"], "b": [1, 2, 3], "c": [2, 3, 4]}).set_index(
.....: list("ab")
.....: )
.....:
In [307]: s1
Out[307]:
c
a b
B 1 2
a 2 3
C 3 4
In [308]: s1.sort_index(level="a")
Out[308]:
c
a b
B 1 2
C 3 4
a 2 3
In [309]: s1.sort_index(level="a", key=lambda idx: idx.str.lower())
Out[309]:
c
a b
a 2 3
B 1 2
C 3 4
Сведения о сортировке значений с помощью ключа см. в разделе сортировка значений.
По значениям
Метод Series.sort_values() используется для сортировки Series по значениям. Метод DataFrame.sort_values() используется для сортировки DataFrame по значениям столбцов или строк. Необязательный параметр by метода DataFrame.sort_values() позволяет указать один или несколько столбцов, по которым определяется порядок сортировки.
In [310]: df1 = pd.DataFrame(
.....: {"one": [2, 1, 1, 1], "two": [1, 3, 2, 4], "three": [5, 4, 3, 2]}
.....: )
.....:
In [311]: df1.sort_values(by="two")
Out[311]:
one two three
0 2 1 5
2 1 2 3
1 1 3 4
3 1 4 2
Параметр by может принимать список имён столбцов, например:
In [312]: df1[["one", "two", "three"]].sort_values(by=["one", "two"])
Out[312]:
one two three
2 1 2 3
1 1 3 4
3 1 4 2
0 2 1 5
Эти методы обрабатывают значения NA особым образом с помощью аргумента na_position:
In [313]: s[2] = np.nan
In [314]: s.sort_values()
Out[314]:
0 A
3 Aaba
1 B
4 Baca
6 CABA
8 cat
7 dog
2 <NA>
5 <NA>
dtype: string
In [315]: s.sort_values(na_position="first")
Out[315]:
2 <NA>
5 <NA>
0 A
3 Aaba
1 B
4 Baca
6 CABA
8 cat
7 dog
dtype: string
Сортировка также поддерживает параметр key, принимающий вызываемую функцию для применения к сортируемым значениям.
In [316]: s1 = pd.Series(["B", "a", "C"])
In [317]: s1.sort_values()
Out[317]:
0 B
2 C
1 a
dtype: object
In [318]: s1.sort_values(key=lambda x: x.str.lower())
Out[318]:
1 a
0 B
2 C
dtype: object
Функции key передаётся Series со значениями; она должна возвращать Series или массив той же формы с преобразованными значениями. Для объектов DataFrame ключ применяется отдельно к каждому столбцу, поэтому функция должна по-прежнему ожидать Series и возвращать Series. Например:
In [319]: df = pd.DataFrame({"a": ["B", "a", "C"], "b": [1, 2, 3]})
In [320]: df.sort_values(by="a")
Out[320]:
a b
0 B 1
2 C 3
1 a 2
In [321]: df.sort_values(by="a", key=lambda col: col.str.lower())
Out[321]:
a b
1 a 2
0 B 1
2 C 3
Для применения разных функций к разным столбцам можно использовать имя или тип каждого столбца.
По индексам и значениям
Строки, переданные в качестве параметра by методу DataFrame.sort_values(), могут обозначать как столбцы, так и имена уровней индекса.
# Build MultiIndex
In [322]: idx = pd.MultiIndex.from_tuples(
.....: [("a", 1), ("a", 2), ("a", 2), ("b", 2), ("b", 1), ("b", 1)]
.....: )
.....:
In [323]: idx.names = ["first", "second"]
# Build DataFrame
In [324]: df_multi = pd.DataFrame({"A": np.arange(6, 0, -1)}, index=idx)
In [325]: df_multi
Out[325]:
A
first second
a 1 6
2 5
2 4
b 2 3
1 2
1 1
Сортировка по «second» (индекс) и «A» (столбец)
In [326]: df_multi.sort_values(by=["second", "A"])
Out[326]:
A
first second
b 1 1
1 2
a 1 6
b 2 3
a 2 4
2 5
Примечание
Если строка совпадает и с именем столбца, и с именем уровня индекса, выводится предупреждение, а приоритет отдаётся столбцу. В будущей версии это приведёт к ошибке неоднозначности.
searchsorted
У Series есть метод searchsorted(), работающий аналогично numpy.ndarray.searchsorted().
In [327]: ser = pd.Series([1, 2, 3])
In [328]: ser.searchsorted([0, 3])
Out[328]: array([0, 2])
In [329]: ser.searchsorted([0, 4])
Out[329]: array([0, 3])
In [330]: ser.searchsorted([1, 3], side="right")
Out[330]: array([1, 3])
In [331]: ser.searchsorted([1, 3], side="left")
Out[331]: array([0, 2])
In [332]: ser = pd.Series([3, 1, 2])
In [333]: ser.searchsorted([0, 3], sorter=np.argsort(ser))
Out[333]: array([0, 2])
Наименьшие / наибольшие значения
У Series есть методы nsmallest() и nlargest(), возвращающие наименьшие или наибольшие \(n\) значений. Для большой Series это может быть намного быстрее, чем сортировать всю Series и вызывать head(n) для результата.
In [334]: s = pd.Series(np.random.permutation(10))
In [335]: s
Out[335]:
0 2
1 0
2 3
3 7
4 1
5 5
6 9
7 6
8 8
9 4
dtype: int64
In [336]: s.sort_values()
Out[336]:
1 0
4 1
0 2
2 3
9 4
5 5
7 6
3 7
8 8
6 9
dtype: int64
In [337]: s.nsmallest(3)
Out[337]:
1 0
4 1
0 2
dtype: int64
In [338]: s.nlargest(3)
Out[338]:
6 9
8 8
3 7
dtype: int64
У DataFrame также есть методы nlargest и nsmallest.
In [339]: df = pd.DataFrame(
.....: {
.....: "a": [-2, -1, 1, 10, 8, 11, -1],
.....: "b": list("abdceff"),
.....: "c": [1.0, 2.0, 4.0, 3.2, np.nan, 3.0, 4.0],
.....: }
.....: )
.....:
In [340]: df.nlargest(3, "a")
Out[340]:
a b c
5 11 f 3.0
3 10 c 3.2
4 8 e NaN
In [341]: df.nlargest(5, ["a", "c"])
Out[341]:
a b c
5 11 f 3.0
3 10 c 3.2
4 8 e NaN
2 1 d 4.0
6 -1 f 4.0
In [342]: df.nsmallest(3, "a")
Out[342]:
a b c
0 -2 a 1.0
1 -1 b 2.0
6 -1 f 4.0
In [343]: df.nsmallest(5, ["a", "c"])
Out[343]:
a b c
0 -2 a 1.0
1 -1 b 2.0
6 -1 f 4.0
2 1 d 4.0
4 8 e NaN
Сортировка по столбцу MultiIndex
При сортировке столбца MultiIndex необходимо явно указать все уровни для by.
In [344]: df1.columns = pd.MultiIndex.from_tuples(
.....: [("a", "one"), ("a", "two"), ("b", "three")]
.....: )
.....:
In [345]: df1.sort_values(by=("a", "two"))
Out[345]:
a b
one two three
0 2 1 5
2 1 2 3
1 1 3 4
3 1 4 2
Копирование
Метод copy() объектов pandas копирует базовые данные (но не индексы осей, поскольку они неизменяемы) и возвращает новый объект. Обратите внимание, что копировать объекты обычно не требуется. Например, существует всего несколько способов изменить DataFrame на месте:
Вставить, удалить или изменить столбец.
Выполнить присваивание атрибутам
indexилиcolumns.Для однородных данных напрямую изменить значения через атрибут
valuesили с помощью расширенной индексации.
Важно отметить, что ни один метод pandas не изменяет ваши данные в качестве побочного эффекта; почти каждый метод возвращает новый объект, оставляя исходный объект без изменений. Если данные изменились, значит, вы изменили их явно.
Типы данных
В большинстве случаев pandas использует массивы и типы данных NumPy для Series или отдельных столбцов DataFrame. NumPy поддерживает float, int, bool, timedelta64[ns] и datetime64[ns] (обратите внимание, что NumPy не поддерживает объекты datetime с часовыми поясами).
pandas и сторонние библиотеки в некоторых случаях расширяют систему типов NumPy. В этом разделе описаны расширения, добавленные pandas. О том, как написать собственное расширение, работающее с pandas, см. в разделе Типы расширений. Список сторонних библиотек, реализовавших расширения, см. на странице экосистемы.
В следующей таблице перечислены все типы расширений pandas. Для методов, которым требуются аргументы dtype, можно указать строки, как показано в таблице. Подробнее о каждом типе см. в соответствующих разделах документации.
Вид данных | Тип данных | Скаляр | Массив | Строковые псевдонимы | ||
|---|---|---|---|---|---|---|
| ||||||
(нет) |
| |||||
|
| |||||
(нет) |
| |||||
| ||||||
| (нет) |
| ||||
| (нет) |
| ||||
| ||||||
| ||||||
В pandas есть два способа хранить строки.
Тип данных
object, способный хранить любые объекты Python, включая строки.StringDtype, предназначенный для строк.
Как правило, мы рекомендуем использовать StringDtype. Подробнее см. в разделе Типы текстовых данных.
Наконец, произвольные объекты можно хранить с помощью типа данных object, однако по возможности этого следует избегать (из соображений производительности и совместимости с другими библиотеками и методами). См. раздел преобразование объектов.
Удобный атрибут dtypes объекта DataFrame возвращает Series с типом данных каждого столбца.
In [346]: dft = pd.DataFrame(
.....: {
.....: "A": np.random.rand(3),
.....: "B": 1,
.....: "C": "foo",
.....: "D": pd.Timestamp("20010102"),
.....: "E": pd.Series([1.0] * 3).astype("float32"),
.....: "F": False,
.....: "G": pd.Series([1] * 3, dtype="int8"),
.....: }
.....: )
.....:
In [347]: dft
Out[347]:
A B C D E F G
0 0.035962 1 foo 2001-01-02 1.0 False 1
1 0.701379 1 foo 2001-01-02 1.0 False 1
2 0.281885 1 foo 2001-01-02 1.0 False 1
In [348]: dft.dtypes
Out[348]:
A float64
B int64
C object
D datetime64[s]
E float32
F bool
G int8
dtype: object
Для объекта Series используйте атрибут dtype.
In [349]: dft["A"].dtype
Out[349]: dtype('float64')
Если объект pandas содержит данные разных типов в одном столбце, тип данных столбца будет выбран так, чтобы вместить все типы данных (object — наиболее общий тип).
# these ints are coerced to floats
In [350]: pd.Series([1, 2, 3, 4, 5, 6.0])
Out[350]:
0 1.0
1 2.0
2 3.0
3 4.0
4 5.0
5 6.0
dtype: float64
# string data forces an ``object`` dtype
In [351]: pd.Series([1, 2, 3, 6.0, "foo"])
Out[351]:
0 1
1 2
2 3
3 6.0
4 foo
dtype: object
Количество столбцов каждого типа в DataFrame можно узнать, вызвав DataFrame.dtypes.value_counts().
In [352]: dft.dtypes.value_counts()
Out[352]:
float64 1
int64 1
object 1
datetime64[s] 1
float32 1
bool 1
int8 1
Name: count, dtype: int64
Числовые типы данных сохраняются и могут сосуществовать в DataFrame. Если тип данных передан (непосредственно через ключевое слово dtype, в переданном объекте ndarray или в переданном объекте Series), он будет сохранён при операциях с DataFrame. Кроме того, разные числовые типы данных НЕ объединяются. Следующий пример даст представление об этом.
In [353]: df1 = pd.DataFrame(np.random.randn(8, 1), columns=["A"], dtype="float32")
In [354]: df1
Out[354]:
A
0 0.224364
1 1.890546
2 0.182879
3 0.787847
4 -0.188449
5 0.667715
6 -0.011736
7 -0.399073
In [355]: df1.dtypes
Out[355]:
A float32
dtype: object
In [356]: df2 = pd.DataFrame(
.....: {
.....: "A": pd.Series(np.random.randn(8), dtype="float16"),
.....: "B": pd.Series(np.random.randn(8)),
.....: "C": pd.Series(np.random.randint(0, 255, size=8), dtype="uint8"), # [0,255] (range of uint8)
.....: }
.....: )
.....:
In [357]: df2
Out[357]:
A B C
0 0.823242 0.256090 26
1 1.607422 1.426469 86
2 -0.333740 -0.416203 46
3 -0.063477 1.139976 212
4 -1.014648 -1.193477 26
5 0.678711 0.096706 7
6 -0.040863 -1.956850 184
7 -0.357422 -0.714337 206
In [358]: df2.dtypes
Out[358]:
A float16
B float64
C uint8
dtype: object
значения по умолчанию
По умолчанию целочисленные типы имеют тип int64, а типы с плавающей точкой — float64, независимо от платформы (32- или 64-разрядной). Во всех следующих случаях результатом будут типы данных int64.
In [359]: pd.DataFrame([1, 2], columns=["a"]).dtypes
Out[359]:
a int64
dtype: object
In [360]: pd.DataFrame({"a": [1, 2]}).dtypes
Out[360]:
a int64
dtype: object
In [361]: pd.DataFrame({"a": 1}, index=list(range(2))).dtypes
Out[361]:
a int64
dtype: object
Обратите внимание, что при создании массивов NumPy выбирает типы, зависящие от платформы. Следующий пример ПРИВЕДЁТ к результату int32 на 32-разрядной платформе.
In [362]: frame = pd.DataFrame(np.array([1, 2]))
повышение типа
При объединении с данными других типов типы могут быть повышены, то есть преобразованы из текущего типа (например, int в float).
In [363]: df3 = df1.reindex_like(df2).fillna(value=0.0) + df2
In [364]: df3
Out[364]:
A B C
0 1.047606 0.256090 26.0
1 3.497968 1.426469 86.0
2 -0.150862 -0.416203 46.0
3 0.724370 1.139976 212.0
4 -1.203098 -1.193477 26.0
5 1.346426 0.096706 7.0
6 -0.052599 -1.956850 184.0
7 -0.756495 -0.714337 206.0
In [365]: df3.dtypes
Out[365]:
A float32
B float64
C float64
dtype: object
DataFrame.to_numpy() возвращает тип данных, представляющий собой наименьший общий знаменатель, то есть тип, способный вместить ВСЕ типы в результирующем однородном массиве NumPy. Это может привести к повышению типа.
In [366]: df3.to_numpy().dtype
Out[366]: dtype('float64')
astype
Для явного преобразования типов данных используйте метод astype(). По умолчанию он возвращает копию, даже если тип данных не изменился (передайте copy=False, чтобы изменить это поведение). Кроме того, метод вызовет исключение, если операция astype недопустима.
Повышение типа всегда выполняется в соответствии с правилами NumPy. Если операция затрагивает два разных типа данных, результатом будет более общий тип.
In [367]: df3
Out[367]:
A B C
0 1.047606 0.256090 26.0
1 3.497968 1.426469 86.0
2 -0.150862 -0.416203 46.0
3 0.724370 1.139976 212.0
4 -1.203098 -1.193477 26.0
5 1.346426 0.096706 7.0
6 -0.052599 -1.956850 184.0
7 -0.756495 -0.714337 206.0
In [368]: df3.dtypes
Out[368]:
A float32
B float64
C float64
dtype: object
# conversion of dtypes
In [369]: df3.astype("float32").dtypes
Out[369]:
A float32
B float32
C float32
dtype: object
Преобразуйте подмножество столбцов в указанный тип с помощью astype().
In [370]: dft = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
In [371]: dft[["a", "b"]] = dft[["a", "b"]].astype(np.uint8)
In [372]: dft
Out[372]:
a b c
0 1 4 7
1 2 5 8
2 3 6 9
In [373]: dft.dtypes
Out[373]:
a uint8
b uint8
c int64
dtype: object
Преобразуйте выбранные столбцы в конкретный тип данных, передав словарь в astype().
In [374]: dft1 = pd.DataFrame({"a": [1, 0, 1], "b": [4, 5, 6], "c": [7, 8, 9]})
In [375]: dft1 = dft1.astype({"a": np.bool_, "c": np.float64})
In [376]: dft1
Out[376]:
a b c
0 True 4 7.0
1 False 5 8.0
2 True 6 9.0
In [377]: dft1.dtypes
Out[377]:
a bool
b int64
c float64
dtype: object
Примечание
При попытке преобразовать подмножество столбцов в указанный тип с помощью astype() и loc() происходит повышение типа.
loc() пытается вписать присваиваемое значение в текущие типы данных, а [] перезаписывает их, используя тип данных правой части. Поэтому следующий фрагмент кода приводит к непредусмотренному результату.
In [378]: dft = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
In [379]: dft.loc[:, ["a", "b"]].astype(np.uint8).dtypes
Out[379]:
a uint8
b uint8
dtype: object
In [380]: dft.loc[:, ["a", "b"]] = dft.loc[:, ["a", "b"]].astype(np.uint8)
In [381]: dft.dtypes
Out[381]:
a int64
b int64
c int64
dtype: object
преобразование объектов
В pandas есть несколько функций для преобразования типов данных из типа object в другие типы. Если данные уже имеют нужный тип, но хранятся в массиве object, методы DataFrame.infer_objects() и Series.infer_objects() можно использовать для мягкого преобразования в нужный тип.
In [382]: import datetime In [383]: df = pd.DataFrame( .....: [ .....: [1, 2], .....: ["a", "b"], .....: [datetime.datetime(2016, 3, 2), datetime.datetime(2016, 3, 2)], .....: ] .....: ) .....: In [384]: df = df.T In [385]: df Out[385]: 0 1 2 0 1 a 2016-03-02 00:00:00 1 2 b 2016-03-02 00:00:00 In [386]: df.dtypes Out[386]: 0 object 1 object 2 object dtype: object
Поскольку данные были транспонированы, при первоначальном определении типов все столбцы получили тип object; infer_objects исправит это.
In [387]: df.infer_objects().dtypes Out[387]: 0 int64 1 object 2 datetime64[ns] dtype: object
Для одномерных массивов объектов или скаляров доступны следующие функции, выполняющие принудительное преобразование объектов в указанный тип:
-
to_numeric()(преобразование в числовые типы данных)In [388]: m = ["1.1", 2, 3] In [389]: pd.to_numeric(m) Out[389]: array([1.1, 2. , 3. ])
-
to_datetime()(преобразование в объекты datetime)In [390]: import datetime In [391]: m = ["2016-07-09", datetime.datetime(2016, 3, 2)] In [392]: pd.to_datetime(m) Out[392]: DatetimeIndex(['2016-07-09', '2016-03-02'], dtype='datetime64[ns]', freq=None)
-
to_timedelta()(преобразование в объекты timedelta)In [393]: m = ["5us", pd.Timedelta("1day")] In [394]: pd.to_timedelta(m) Out[394]: TimedeltaIndex(['0 days 00:00:00.000005', '1 days 00:00:00'], dtype='timedelta64[ns]', freq=None)
Чтобы принудительно выполнить преобразование, можно передать аргумент errors, указывающий, как pandas должен обрабатывать элементы, которые невозможно преобразовать в нужный тип данных или объект. По умолчанию используется errors='raise', то есть все ошибки, возникшие в процессе преобразования, будут вызваны. Если же указать errors='coerce', ошибки будут игнорироваться, а проблемные элементы преобразуются в pd.NaT (для datetime и timedelta) или np.nan (для числовых данных). Это может быть полезно при чтении данных, большинство которых имеет нужный тип (например, числовой или datetime), но иногда встречаются не соответствующие ему элементы, которые нужно представить как пропущенные значения:
In [395]: import datetime
In [396]: m = ["apple", datetime.datetime(2016, 3, 2)]
In [397]: pd.to_datetime(m, errors="coerce")
Out[397]: DatetimeIndex(['NaT', '2016-03-02'], dtype='datetime64[ns]', freq=None)
In [398]: m = ["apple", 2, 3]
In [399]: pd.to_numeric(m, errors="coerce")
Out[399]: array([nan, 2., 3.])
In [400]: m = ["apple", pd.Timedelta("1day")]
In [401]: pd.to_timedelta(m, errors="coerce")
Out[401]: TimedeltaIndex([NaT, '1 days'], dtype='timedelta64[ns]', freq=None)
Помимо преобразования объектов, функция to_numeric() принимает дополнительный аргумент downcast, позволяющий понизить тип вновь преобразованных (или уже имеющихся) числовых данных до типа меньшего размера, чтобы сэкономить память:
In [402]: m = ["1", 2, 3]
In [403]: pd.to_numeric(m, downcast="integer") # smallest signed int dtype
Out[403]: array([1, 2, 3], dtype=int8)
In [404]: pd.to_numeric(m, downcast="signed") # same as 'integer'
Out[404]: array([1, 2, 3], dtype=int8)
In [405]: pd.to_numeric(m, downcast="unsigned") # smallest unsigned int dtype
Out[405]: array([1, 2, 3], dtype=uint8)
In [406]: pd.to_numeric(m, downcast="float") # smallest float dtype
Out[406]: array([1., 2., 3.], dtype=float32)
Поскольку эти методы применимы только к одномерным массивам, спискам или скалярам, их нельзя напрямую использовать для многомерных объектов, таких как DataFrame. Однако с помощью apply() можно эффективно «применить» функцию к каждому столбцу:
In [407]: import datetime
In [408]: df = pd.DataFrame([["2016-07-09", datetime.datetime(2016, 3, 2)]] * 2, dtype="O")
In [409]: df
Out[409]:
0 1
0 2016-07-09 2016-03-02 00:00:00
1 2016-07-09 2016-03-02 00:00:00
In [410]: df.apply(pd.to_datetime)
Out[410]:
0 1
0 2016-07-09 2016-03-02
1 2016-07-09 2016-03-02
In [411]: df = pd.DataFrame([["1.1", 2, 3]] * 2, dtype="O")
In [412]: df
Out[412]:
0 1 2
0 1.1 2 3
1 1.1 2 3
In [413]: df.apply(pd.to_numeric)
Out[413]:
0 1 2
0 1.1 2 3
1 1.1 2 3
In [414]: df = pd.DataFrame([["5us", pd.Timedelta("1day")]] * 2, dtype="O")
In [415]: df
Out[415]:
0 1
0 5us 1 days 00:00:00
1 5us 1 days 00:00:00
In [416]: df.apply(pd.to_timedelta)
Out[416]:
0 1
0 0 days 00:00:00.000005 1 days
1 0 days 00:00:00.000005 1 days
подводные камни
Операции выборки данных типа integer могут легко привести к повышению типа данных до floating. Тип данных входных данных сохраняется, если не добавляются nans. См. также раздел Поддержка NA для целых чисел.
In [417]: dfi = df3.astype("int32")
In [418]: dfi["E"] = 1
In [419]: dfi
Out[419]:
A B C E
0 1 0 26 1
1 3 1 86 1
2 0 0 46 1
3 0 1 212 1
4 -1 -1 26 1
5 1 0 7 1
6 0 -1 184 1
7 0 0 206 1
In [420]: dfi.dtypes
Out[420]:
A int32
B int32
C int32
E int64
dtype: object
In [421]: casted = dfi[dfi > 0]
In [422]: casted
Out[422]:
A B C E
0 1.0 NaN 26 1
1 3.0 1.0 86 1
2 NaN NaN 46 1
3 NaN 1.0 212 1
4 NaN NaN 26 1
5 1.0 NaN 7 1
6 NaN NaN 184 1
7 NaN NaN 206 1
In [423]: casted.dtypes
Out[423]:
A float64
B float64
C int32
E int64
dtype: object
Типы данных с плавающей точкой при этом не изменяются.
In [424]: dfa = df3.copy()
In [425]: dfa["A"] = dfa["A"].astype("float32")
In [426]: dfa.dtypes
Out[426]:
A float32
B float64
C float64
dtype: object
In [427]: casted = dfa[df2 > 0]
In [428]: casted
Out[428]:
A B C
0 1.047606 0.256090 26.0
1 3.497968 1.426469 86.0
2 NaN NaN 46.0
3 NaN 1.139976 212.0
4 NaN NaN 26.0
5 1.346426 0.096706 7.0
6 NaN NaN 184.0
7 NaN NaN 206.0
In [429]: casted.dtypes
Out[429]:
A float32
B float64
C float64
dtype: object
Выбор столбцов по типу данных dtype
Метод select_dtypes() позволяет выбирать подмножество столбцов на основе их типа данных dtype.
Сначала создадим DataFrame с большим количеством столбцов разных типов:
In [430]: df = pd.DataFrame(
.....: {
.....: "string": list("abc"),
.....: "int64": list(range(1, 4)),
.....: "uint8": np.arange(3, 6).astype("u1"),
.....: "float64": np.arange(4.0, 7.0),
.....: "bool1": [True, False, True],
.....: "bool2": [False, True, False],
.....: "dates": pd.date_range("now", periods=3),
.....: "category": pd.Series(list("ABC")).astype("category"),
.....: }
.....: )
.....:
In [431]: df["tdeltas"] = df.dates.diff()
In [432]: df["uint64"] = np.arange(3, 6).astype("u8")
In [433]: df["other_dates"] = pd.date_range("20130101", periods=3)
In [434]: df["tz_aware_dates"] = pd.date_range("20130101", periods=3, tz="US/Eastern")
In [435]: df
Out[435]:
string int64 uint8 ... uint64 other_dates tz_aware_dates
0 a 1 3 ... 3 2013-01-01 2013-01-01 00:00:00-05:00
1 b 2 4 ... 4 2013-01-02 2013-01-02 00:00:00-05:00
2 c 3 5 ... 5 2013-01-03 2013-01-03 00:00:00-05:00
[3 rows x 12 columns]
И типы данных:
In [436]: df.dtypes
Out[436]:
string object
int64 int64
uint8 uint8
float64 float64
bool1 bool
bool2 bool
dates datetime64[ns]
category category
tdeltas timedelta64[ns]
uint64 uint64
other_dates datetime64[ns]
tz_aware_dates datetime64[ns, US/Eastern]
dtype: object
Метод select_dtypes() принимает два параметра — include и exclude, которые позволяют указать «вернуть столбцы с такими типами данных» (include) и/или «вернуть столбцы без таких типов данных» (exclude).
Например, чтобы выбрать столбцы типа bool:
In [437]: df.select_dtypes(include=[bool])
Out[437]:
bool1 bool2
0 True False
1 False True
2 True False
Можно также передать имя типа данных из иерархии типов NumPy:
In [438]: df.select_dtypes(include=["bool"])
Out[438]:
bool1 bool2
0 True False
1 False True
2 True False
Метод select_dtypes() также работает с обобщёнными типами данных.
Например, чтобы выбрать все числовые и логические столбцы, исключив беззнаковые целые числа:
In [439]: df.select_dtypes(include=["number", "bool"], exclude=["unsignedinteger"])
Out[439]:
int64 float64 bool1 bool2 tdeltas
0 1 4.0 True False NaT
1 2 5.0 False True 1 days
2 3 6.0 True False 1 days
Чтобы выбрать строковые столбцы, необходимо использовать тип данных object:
In [440]: df.select_dtypes(include=["object"])
Out[440]:
string
0 a
1 b
2 c
Чтобы просмотреть все дочерние типы обобщённого типа dtype, например numpy.number, можно определить функцию, которая возвращает дерево дочерних типов данных:
In [441]: def subdtypes(dtype):
.....: subs = dtype.__subclasses__()
.....: if not subs:
.....: return dtype
.....: return [dtype, [subdtypes(dt) for dt in subs]]
.....:
Все типы данных NumPy являются подклассами numpy.generic:
In [442]: subdtypes(np.generic)
Out[442]:
[numpy.generic,
[[numpy.number,
[[numpy.integer,
[[numpy.signedinteger,
[numpy.int8,
numpy.int16,
numpy.int32,
numpy.int64,
numpy.longlong,
numpy.timedelta64]],
[numpy.unsignedinteger,
[numpy.uint8,
numpy.uint16,
numpy.uint32,
numpy.uint64,
numpy.ulonglong]]]],
[numpy.inexact,
[[numpy.floating,
[numpy.float16, numpy.float32, numpy.float64, numpy.longdouble]],
[numpy.complexfloating,
[numpy.complex64, numpy.complex128, numpy.clongdouble]]]]]],
[numpy.flexible,
[[numpy.character, [numpy.bytes_, numpy.str_]],
[numpy.void, [numpy.record]]]],
numpy.bool_,
numpy.datetime64,
numpy.object_]]
Примечание
В pandas также определены типы category и datetime64[ns, tz], которые не входят в стандартную иерархию NumPy и не отображаются приведённой выше функцией.
© 2008–2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
© 2011–2025, Open source contributors
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.3.0/user_guide/basics.html