Основные базовые функции
Здесь мы обсуждаем множество основных функций, общих для структур данных 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: индекс (только ось)
DataFrame: индекс (строки) и колонки
Обратите внимание, что эти атрибуты можно безопасно присваивать!
In [7]: df[:2]
Out[7]:
A B C
2000-01-01 -0.173215 0.119209 -1.044236
2000-01-02 -0.861849 -2.104569 -0.494929
In [8]: df.columns = [x.lower() for x in df.columns]
In [9]: df
Out[9]:
a b c
2000-01-01 -0.173215 0.119209 -1.044236
2000-01-02 -0.861849 -2.104569 -0.494929
2000-01-03 1.071804 0.721555 -0.706771
2000-01-04 -1.039575 0.271860 -0.424972
2000-01-05 0.567020 0.276232 -1.087401
2000-01-06 -0.673690 0.113648 -1.478427
2000-01-07 0.524988 0.404705 0.577046
2000-01-08 -1.715002 -1.039268 -0.370647
Объекты pandas (Index, Series, DataFrame) можно рассматривать как контейнеры для массивов, которые содержат фактические данные и выполняют фактические вычисления. Для многих типов базовый массив является numpy.ndarray. Однако pandas и сторонние библиотеки могут расширять систему типов NumPy, чтобы добавить поддержку пользовательских массивов (см. dtypes).
Чтобы получить фактические данные внутри Index или Series, используйте свойство .array
In [10]: s.array
Out[10]:
<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 их использует, выходят за рамки этого введения. См. dtypes для получения дополнительной информации.
Если вам нужен массив NumPy, используйте to_numpy() или numpy.asarray().
In [12]: s.to_numpy()
Out[12]: array([ 0.4691, -0.2829, -1.5091, -1.1356, 1.2121])
In [13]: np.asarray(s)
Out[13]: array([ 0.4691, -0.2829, -1.5091, -1.1356, 1.2121])
Когда Series или Index поддерживаются ExtensionArray, to_numpy() может потребовать копирования данных и принудительного преобразования значений. См. dtypes для получения дополнительной информации.
to_numpy() дает некоторый контроль над dtype полученного numpy.ndarray. Например, рассмотрим даты и время с часовыми поясами. NumPy не имеет типа данных для представления дат и времени с часовым поясом, поэтому есть два потенциально полезных представления:
Массив
numpy.ndarrayтипа object с объектами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 не имеют одинаковый тип dtype) это не так. В отличие от меток осей, к атрибуту значений нельзя присваивать значения.
Примечание
При работе с разнородными данными тип данных результирующего массива ndarray выбирается для размещения всех вовлеченных данных. Например, если есть строки, результат будет иметь тип object. Если есть только числа с плавающей запятой и целые числа, результирующий массив будет иметь тип float.
В прошлом 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 — это набор специализированных цитоновых процедур, которые особенно быстры при работе с массивами, имеющими nans.
Вот пример (используя 100-столбцовую x 100 000-строчную DataFrames):
Операция | 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
Кроме того, вы можете согласовать уровень MultiIndexed DataFrame с Series.
In [26]: dfmi = df.copy()
In [27]: dfmi.index = pd.MultiIndex.from_tuples(
....: [(1, "a"), (1, "b"), (1, "c"), (2, "a")], names=["first", "second"]
....: )
....:
In [28]: dfmi.sub(column, axis=0, level="second")
Out[28]:
one two three
first second
1 a -0.377535 0.000000 NaN
b -1.569069 0.000000 -1.962513
c -0.783123 0.000000 -0.250933
2 a NaN -1.493173 -2.385688
Series и Index также поддерживают встроенную функцию divmod(). Эта функция выполняет операцию целочисленного деления и остатка одновременно, возвращая кортеж из двух элементов того же типа, что и левая часть. Например:
In [29]: s = pd.Series(np.arange(10))
In [30]: s
Out[30]:
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
dtype: int64
In [31]: div, rem = divmod(s, 3)
In [32]: div
Out[32]:
0 0
1 0
2 0
3 1
4 1
5 1
6 2
7 2
8 2
9 3
dtype: int64
In [33]: rem
Out[33]:
0 0
1 1
2 2
3 0
4 1
5 2
6 0
7 1
8 2
9 0
dtype: int64
In [34]: idx = pd.Index(np.arange(10))
In [35]: idx
Out[35]: Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype='int64')
In [36]: div, rem = divmod(idx, 3)
In [37]: div
Out[37]: Index([0, 0, 0, 1, 1, 1, 2, 2, 2, 3], dtype='int64')
In [38]: rem
Out[38]: Index([0, 1, 2, 0, 1, 2, 0, 1, 2, 0], dtype='int64')
Мы также можем выполнить поэлементное divmod():
In [39]: div, rem = divmod(s, [2, 2, 3, 3, 4, 4, 5, 5, 6, 6])
In [40]: div
Out[40]:
0 0
1 0
2 0
3 1
4 1
5 1
6 1
7 1
8 1
9 1
dtype: int64
In [41]: rem
Out[41]:
0 0
1 1
2 2
3 0
4 0
5 1
6 1
7 2
8 2
9 3
dtype: int64
Пропущенные данные/операции со значениями заполнения
В Series и DataFrame арифметические функции имеют возможность ввода fill_value, то есть значения для подстановки, когда отсутствует не более одного значения в определенной позиции. Например, при сложении двух DataFrame вы можете задать NaN как 0, если в обоих DataFrame отсутствует это значение, в противном случае результат будет NaN (позже вы можете заменить NaN на другое значение, используя fillna).
In [42]: df2 = df.copy()
In [43]: df2.loc["a", "three"] = 1.0
In [44]: df
Out[44]:
one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [45]: df2
Out[45]:
one two three
a 1.394981 1.772517 1.000000
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [46]: df + df2
Out[46]:
one two three
a 2.789963 3.545034 NaN
b 0.686107 3.824246 -0.100780
c 1.390491 2.956737 2.454870
d NaN 0.558688 -1.226343
In [47]: df.add(df2, fill_value=0)
Out[47]:
one two three
a 2.789963 3.545034 1.000000
b 0.686107 3.824246 -0.100780
c 1.390491 2.956737 2.454870
d NaN 0.558688 -1.226343
Гибкие сравнения
Series и DataFrame имеют бинарные методы сравнения eq, ne, lt, gt, le, и ge, поведение которых аналогично описанным выше бинарным арифметическим операциям:
In [48]: df.gt(df2)
Out[48]:
one two three
a False False False
b False False False
c False False False
d False False False
In [49]: df2.ne(df)
Out[49]:
one two three
a False False True
b False False False
c False False False
d True False False
Эти операции создают объект pandas того же типа, что и входное значение слева, имеющий тип bool. Эти boolean объекты могут использоваться в операциях индексирования, см. раздел Индексирование с использованием булевых значений.
Булевы сводки
Вы можете применить сводки: empty, any(), all() и bool(), чтобы подытожить булевский результат.
In [50]: (df > 0).all()
Out[50]:
one False
two True
three False
dtype: bool
In [51]: (df > 0).any()
Out[51]:
one True
two True
three True
dtype: bool
Вы можете свести результат к конечному булевому значению.
In [52]: (df > 0).any().any()
Out[52]: True
Вы можете проверить, пустой ли объект pandas, с помощью свойства empty.
In [53]: df.empty
Out[53]: False
In [54]: pd.DataFrame(columns=list("ABC")).empty
Out[54]: True
Предупреждение
Использование оператора истинности для объекта pandas вызовет ошибку, так как проверка пустоты или значений неоднозначна.
In [55]: if df:
....: print(True)
....:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<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(). Но на самом деле это выражение ложно:
In [57]: df + df == df * 2
Out[57]:
one two three
a True True False
b True True True
c True True True
d False True True
In [58]: (df + df == df * 2).all()
Out[58]:
one False
two True
three False
dtype: bool
Обратите внимание, что булевый DataFrame df + df == df * 2 содержит некоторые ложные значения! Это связано с тем, что NaN не сравнивается как равный:
In [59]: np.nan == np.nan
Out[59]: False
Поэтому NDFrames (такие как Series и DataFrames) имеют метод equals() для проверки равенства, где NaN в соответствующих позициях считаются равными.
In [60]: (df + df).equals(df * 2)
Out[60]: True
Обратите внимание, что индекс Series или DataFrame должен быть в том же порядке, чтобы равенство было истинным:
In [61]: df1 = pd.DataFrame({"col": ["foo", 0, np.nan]})
In [62]: df2 = pd.DataFrame({"col": [np.nan, 0, "foo"]}, index=[2, 1, 0])
In [63]: df1.equals(df2)
Out[63]: False
In [64]: df1.equals(df2.sort_index())
Out[64]: True
Сравнение массивоподобных объектов
Вы можете удобно выполнять поэлементные сравнения при сравнении структуры данных pandas со скалярным значением:
In [65]: pd.Series(["foo", "bar", "baz"]) == "foo"
Out[65]:
0 True
1 False
2 False
dtype: bool
In [66]: pd.Index(["foo", "bar", "baz"]) == "foo"
Out[66]: array([ True, False, False])
pandas также обрабатывает поэлементные сравнения между различными массивоподобными объектами одинаковой длины:
In [67]: pd.Series(["foo", "bar", "baz"]) == pd.Index(["foo", "bar", "qux"])
Out[67]:
0 True
1 True
2 False
dtype: bool
In [68]: pd.Series(["foo", "bar", "baz"]) == np.array(["foo", "bar", "qux"])
Out[68]:
0 True
1 True
2 False
dtype: bool
Попытка сравнения Index или Series объектов различной длины приведет к ошибке ValueError:
In [69]: pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar'])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[69], line 1
----> 1 pd.Series(['foo', 'bar', 'baz']) == pd.Series(['foo', 'bar'])
File ~/work/pandas/pandas/pandas/core/ops/common.py:76, in _unpack_zerodim_and_defer.<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:6114, in Series._cmp_method(self, other, op)
6111 res_name = ops.get_op_result_name(self, other)
6113 if isinstance(other, Series) and not self._indexed_same(other):
-> 6114 raise ValueError("Can only compare identically-labeled Series objects")
6116 lvalues = self._values
6117 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:6114, in Series._cmp_method(self, other, op)
6111 res_name = ops.get_op_result_name(self, other)
6113 if isinstance(other, Series) and not self._indexed_same(other):
-> 6114 raise ValueError("Can only compare identically-labeled Series objects")
6116 lvalues = self._values
6117 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
Статистическое описание
Существует большое количество методов для вычисления описательной статистики и других связанных операций над рядом, таблицей данных. Большинство из них являются агрегациями (отсюда получается результат меньшей размерности), такие как sum(), mean() и quantile(), но некоторые из них, такие как cumsum() и cumprod(), производят объект той же размерности. В общем случае, эти методы принимают аргумент axis, как и ndarray.{sum, std, …}, но ось может быть задана именем или целым числом:
Ряд: аргумент axis не нужен
Таблица данных: “индекс” (axis=0, по умолчанию), “колонки” (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 параметр, который применяется только в случае, если объект имеет иерархический индекс.
Функция | Описание |
|---|---|
| Количество ненулевых наблюдений |
| Сумма значений |
| Среднее значение |
| Медиана арифметическая |
| Минимальное значение |
| Максимальное значение |
| Мода |
| Модуль |
| Произведение значений |
| Стандартное отклонение (с поправкой на Бесселя) |
| Дисперсия (несмещённая) |
| Стандартная ошибка среднего |
| Коэффициент асимметрии выборки (третий момент) |
| Коэффициент эксцесса выборки (четвёртый момент) |
| Квантиль (значение в %) выборки |
| Кумулятивная сумма |
| Кумулятивное произведение |
| Наибольшее кумулятивное значение |
| Наименьшее кумулятивное значение |
Обратите внимание, что по совпадению некоторые методы NumPy, такие как mean, std, и sum, по умолчанию исключают значения NaN на входных данных Series:
In [88]: np.mean(df["one"])
Out[88]: 0.8110935116651192
In [89]: np.mean(df["one"].to_numpy())
Out[89]: nan
Series.nunique() вернёт количество уникальных ненулевых значений в ряду:
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(), которая вычисляет различные сводные статистические данные о ряду или столбцах таблицы данных (исключая, конечно, NaN):
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
По умолчанию медиана всегда включается.
Для объекта ряда нечислового типа 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
Обратите внимание, что для объекта таблицы данных смешанного типа describe() ограничит сводку только числовыми столбцами или, если таковых нет, только категориальными столбцами:
In [103]: frame = pd.DataFrame({"a": ["Yes", "Yes", "No", "No"], "b": range(4)})
In [104]: frame.describe()
Out[104]:
b
count 4.000000
mean 1.500000
std 1.290994
min 0.000000
25% 0.750000
50% 1.500000
75% 2.250000
max 3.000000
Это поведение можно контролировать, предоставив список типов в качестве include/exclude аргументов. Также можно использовать специальное значение all:
In [105]: frame.describe(include=["object"])
Out[105]:
a
count 4
unique 2
top Yes
freq 2
In [106]: frame.describe(include=["number"])
Out[106]:
b
count 4.000000
mean 1.500000
std 1.290994
min 0.000000
25% 0.750000
50% 1.500000
75% 2.250000
max 3.000000
In [107]: frame.describe(include="all")
Out[107]:
a b
count 4 4.000000
unique 2 NaN
top Yes NaN
freq 2 NaN
mean NaN 1.500000
std NaN 1.290994
min NaN 0.000000
25% NaN 0.750000
50% NaN 1.500000
75% NaN 2.250000
max NaN 3.000000
Эта функция основана на select_dtypes. Для получения подробностей о допустимых входных данных обратитесь к ней.
Индекс минимальных/максимальных значений
Функции idxmin() и idxmax() для Series и DataFrame вычисляют метки индексов с минимальными и максимальными соответствующими значениями:
In [108]: s1 = pd.Series(np.random.randn(5))
In [109]: s1
Out[109]:
0 1.118076
1 -0.352051
2 -1.242883
3 -1.277155
4 -0.641184
dtype: float64
In [110]: s1.idxmin(), s1.idxmax()
Out[110]: (3, 0)
In [111]: df1 = pd.DataFrame(np.random.randn(5, 3), columns=["A", "B", "C"])
In [112]: df1
Out[112]:
A B C
0 -0.327863 -0.946180 -0.137570
1 -0.186235 -0.257213 -0.486567
2 -0.507027 -0.871259 -0.111110
3 2.000339 -2.430505 0.089759
4 -0.321434 -0.033695 0.096271
In [113]: df1.idxmin(axis=0)
Out[113]:
A 2
B 3
C 1
dtype: int64
In [114]: df1.idxmax(axis=1)
Out[114]:
0 C
1 A
2 C
3 A
4 C
dtype: object
Если существует несколько строк (или столбцов), соответствующих минимальному или максимальному значению, idxmin() и idxmax() возвращают первый соответствующий индекс:
In [115]: df3 = pd.DataFrame([2, 1, 1, 3, np.nan], columns=["A"], index=list("edcba"))
In [116]: df3
Out[116]:
A
e 2.0
d 1.0
c 1.0
b 3.0
a NaN
In [117]: df3["A"].idxmin()
Out[117]: 'd'
Примечание
idxmin и idxmax называются argmin и argmax в NumPy.
Количество значений (гистограммы)/ мода
Метод Series value_counts() вычисляет гистограмму одномерного массива значений. Также может использоваться как функция для обычных массивов:
In [118]: data = np.random.randint(0, 7, size=50)
In [119]: data
Out[119]:
array([6, 6, 2, 3, 5, 3, 2, 5, 4, 5, 4, 3, 4, 5, 0, 2, 0, 4, 2, 0, 3, 2,
2, 5, 6, 5, 3, 4, 6, 4, 3, 5, 6, 4, 3, 6, 2, 6, 6, 2, 3, 4, 2, 1,
6, 2, 6, 1, 5, 4])
In [120]: s = pd.Series(data)
In [121]: s.value_counts()
Out[121]:
6 10
2 10
4 9
3 8
5 8
0 3
1 2
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, которые представили популярный оператор (%>%) (чтение трубы) для 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. Они определят, как значения списков (list-likes) расширяются (или нет) до DataFrame.
apply() в сочетании с некоторыми уловками можно использовать для ответа на многие вопросы о наборе данных. Например, предположим, что мы хотели извлечь дату, когда максимальное значение для каждого столбца произошло:
In [152]: tsdf = pd.DataFrame(
.....: np.random.randn(1000, 3),
.....: columns=["A", "B", "C"],
.....: index=pd.date_range("1/1/2000", periods=1000),
.....: )
.....:
In [153]: tsdf.apply(lambda x: x.idxmax())
Out[153]:
A 2000-08-06
B 2001-01-18
C 2001-07-18
dtype: datetime64[ns]
Вы также можете передать дополнительные аргументы и ключевые аргументы методу apply().
In [154]: def subtract_and_divide(x, sub, divide=1):
.....: return (x - sub) / divide
.....:
In [155]: df_udf = pd.DataFrame(np.ones((2, 2)))
In [156]: df_udf.apply(subtract_and_divide, args=(5,), divide=3)
Out[156]:
0 1
0 -1.333333 -1.333333
1 -1.333333 -1.333333
Ещё одна полезная функция — возможность передачи методов Series для выполнения некоторых операций Series на каждом столбце или строке:
In [157]: tsdf = pd.DataFrame(
.....: np.random.randn(10, 3),
.....: columns=["A", "B", "C"],
.....: index=pd.date_range("1/1/2000", periods=10),
.....: )
.....:
In [158]: tsdf.iloc[3:7] = np.nan
In [159]: tsdf
Out[159]:
A B C
2000-01-01 -0.158131 -0.232466 0.321604
2000-01-02 -1.810340 -3.105758 0.433834
2000-01-03 -1.209847 -1.156793 -0.136794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 -0.653602 0.178875 1.008298
2000-01-09 1.007996 0.462824 0.254472
2000-01-10 0.307473 0.600337 1.643950
In [160]: tsdf.apply(pd.Series.interpolate)
Out[160]:
A B C
2000-01-01 -0.158131 -0.232466 0.321604
2000-01-02 -1.810340 -3.105758 0.433834
2000-01-03 -1.209847 -1.156793 -0.136794
2000-01-04 -1.098598 -0.889659 0.092225
2000-01-05 -0.987349 -0.622526 0.321243
2000-01-06 -0.876100 -0.355392 0.550262
2000-01-07 -0.764851 -0.088259 0.779280
2000-01-08 -0.653602 0.178875 1.008298
2000-01-09 1.007996 0.462824 0.254472
2000-01-10 0.307473 0.600337 1.643950
Наконец, apply() принимает аргумент raw, который по умолчанию равен False, что преобразует каждую строку или столбец в Series перед применением функции. Если установить его в значение True, переданная функция вместо этого получит объект ndarray, что положительно скажется на производительности, если вам не нужна функциональность индексации.
Интерфейс агрегирования
Интерфейс агрегирования позволяет выразить несколько операций агрегирования в одном компактном виде. Этот интерфейс схож в объектах pandas, см. интерфейс groupby, интерфейс окна и интерфейс ресемплирования. Точкой входа для агрегирования является DataFrame.aggregate(), или псевдоним DataFrame.agg().
Мы будем использовать аналогичную начальную таблицу, как и выше:
In [161]: tsdf = pd.DataFrame(
.....: np.random.randn(10, 3),
.....: columns=["A", "B", "C"],
.....: index=pd.date_range("1/1/2000", periods=10),
.....: )
.....:
In [162]: tsdf.iloc[3:7] = np.nan
In [163]: tsdf
Out[163]:
A B C
2000-01-01 1.257606 1.004194 0.167574
2000-01-02 -0.749892 0.288112 -0.757304
2000-01-03 -0.207550 -0.298599 0.116018
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.814347 -0.257623 0.869226
2000-01-09 -0.250663 -1.206601 0.896839
2000-01-10 2.169758 -1.333363 0.283157
Использование единственной функции эквивалентно apply(). Также можно передавать именованные методы в виде строк. Эти методы вернут Series агрегированного результата:
In [164]: tsdf.agg(lambda x: np.sum(x))
Out[164]:
A 3.033606
B -1.803879
C 1.575510
dtype: float64
In [165]: tsdf.agg("sum")
Out[165]:
A 3.033606
B -1.803879
C 1.575510
dtype: float64
# these are equivalent to a ``.sum()`` because we are aggregating
# on a single function
In [166]: tsdf.sum()
Out[166]:
A 3.033606
B -1.803879
C 1.575510
dtype: float64
Одиночные агрегации над Series вернут скалярное значение:
In [167]: tsdf["A"].agg("sum")
Out[167]: 3.033606102414146
Агрегирование с несколькими функциями
Можно передавать несколько аргументов агрегирования в виде списка. Результаты каждой переданной функции будут строкой в результирующей DataFrame. Они естественно получают имена от функции агрегирования.
In [168]: tsdf.agg(["sum"])
Out[168]:
A B C
sum 3.033606 -1.803879 1.57551
Несколько функций дадут несколько строк:
In [169]: tsdf.agg(["sum", "mean"])
Out[169]:
A B C
sum 3.033606 -1.803879 1.575510
mean 0.505601 -0.300647 0.262585
На Series, несколько функций вернут Series, индексированное именами функций:
In [170]: tsdf["A"].agg(["sum", "mean"])
Out[170]:
sum 3.033606
mean 0.505601
Name: A, dtype: float64
Передача lambda функции даст <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() можно легко создать пользовательскую функцию описания, аналогичную встроенной функции описания.
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
Интерфейс преобразования
Метод transform() возвращает объект с такой же индексацией (такого же размера), как и исходный. Этот интерфейс позволяет указать несколько операций одновременно вместо одной за раз. Его интерфейс очень похож на .agg интерфейс.
Мы создаем таблицу, аналогичную использовавшейся в предыдущих разделах.
In [182]: tsdf = pd.DataFrame(
.....: np.random.randn(10, 3),
.....: columns=["A", "B", "C"],
.....: index=pd.date_range("1/1/2000", periods=10),
.....: )
.....:
In [183]: tsdf.iloc[3:7] = np.nan
In [184]: tsdf
Out[184]:
A B C
2000-01-01 -0.428759 -0.864890 -0.675341
2000-01-02 -0.168731 1.338144 -1.279321
2000-01-03 -1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 -1.240447 -0.201052
2000-01-09 -0.157795 0.791197 -1.144209
2000-01-10 -0.030876 0.371900 0.061932
Преобразуем всю таблицу. .transform() позволяет передавать функции в качестве: функции NumPy, имени функции в виде строки или пользовательской функции.
In [185]: tsdf.transform(np.abs)
Out[185]:
A B C
2000-01-01 0.428759 0.864890 0.675341
2000-01-02 0.168731 1.338144 1.279321
2000-01-03 1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 1.240447 0.201052
2000-01-09 0.157795 0.791197 1.144209
2000-01-10 0.030876 0.371900 0.061932
In [186]: tsdf.transform("abs")
Out[186]:
A B C
2000-01-01 0.428759 0.864890 0.675341
2000-01-02 0.168731 1.338144 1.279321
2000-01-03 1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 1.240447 0.201052
2000-01-09 0.157795 0.791197 1.144209
2000-01-10 0.030876 0.371900 0.061932
In [187]: tsdf.transform(lambda x: x.abs())
Out[187]:
A B C
2000-01-01 0.428759 0.864890 0.675341
2000-01-02 0.168731 1.338144 1.279321
2000-01-03 1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 1.240447 0.201052
2000-01-09 0.157795 0.791197 1.144209
2000-01-10 0.030876 0.371900 0.061932
Здесь transform() получил одну функцию; это эквивалентно применению ufunc.
In [188]: np.abs(tsdf)
Out[188]:
A B C
2000-01-01 0.428759 0.864890 0.675341
2000-01-02 0.168731 1.338144 1.279321
2000-01-03 1.621034 0.438107 0.903794
2000-01-04 NaN NaN NaN
2000-01-05 NaN NaN NaN
2000-01-06 NaN NaN NaN
2000-01-07 NaN NaN NaN
2000-01-08 0.254374 1.240447 0.201052
2000-01-09 0.157795 0.791197 1.144209
2000-01-10 0.030876 0.371900 0.061932
Передача одной функции в .transform() с Series вернёт одно Series в ответ.
In [189]: tsdf["A"].transform(np.abs)
Out[189]:
2000-01-01 0.428759
2000-01-02 0.168731
2000-01-03 1.621034
2000-01-04 NaN
2000-01-05 NaN
2000-01-06 NaN
2000-01-07 NaN
2000-01-08 0.254374
2000-01-09 0.157795
2000-01-10 0.030876
Freq: D, Name: A, dtype: float64
Преобразование с несколькими функциями
Передача нескольких функций вернёт таблицу DataFrame с многоуровневым индексом столбцов. Первый уровень будет именами исходных столбцов таблицы; второй уровень – именами функций преобразования.
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
См. также
Многоуровневые индексы / Продвинутая индексация — это еще более компактный способ переиндексации.
Примечание
При написании чувствительного к производительности кода есть веская причина потратить некоторое время, чтобы стать «переиндексационным ниндзя»: многие операции выполняются быстрее на предварительно выровненных данных. Добавление двух невыровненных DataFrames внутри вызывает шаг переиндексации. При исследовательском анализе вы вряд ли заметите разницу (поскольку 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)
Для DataFrames метод join будет применяться по умолчанию к индексу и столбцам:
In [224]: df.align(df2, join="inner")
Out[224]:
( one two
a 1.394981 1.772517
b 0.343054 1.912123
c 0.695246 1.478369,
one two
a 1.394981 1.772517
b 0.343054 1.912123
c 0.695246 1.478369)
Вы также можете передать опцию axis для выравнивания только по указанной оси:
In [225]: df.align(df2, join="inner", axis=0)
Out[225]:
( one two three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435,
one two
a 1.394981 1.772517
b 0.343054 1.912123
c 0.695246 1.478369)
Если вы передаете Series в DataFrame.align(), вы можете выбрать выравнивание обоих объектов по индексу или столбцам DataFrame с помощью аргумента axis.
In [226]: df.align(df2.iloc[0], axis=1)
Out[226]:
( one three two
a 1.394981 NaN 1.772517
b 0.343054 -0.050390 1.912123
c 0.695246 1.227435 1.478369
d NaN -0.613172 0.279344,
one 1.394981
three NaN
two 1.772517
Name: a, dtype: float64)
Заполнение при переиндексации
reindex() принимает необязательный параметр method, который представляет собой метод заполнения, выбранный из следующей таблицы:
Метод | Действие |
|---|---|
pad / ffill | Заполнение значений вперед |
bfill / backfill | Заполнение значений назад |
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() позволяет переименовать ось на основе некоторого соответствия (словарь или Series) или произвольной функции.
In [243]: s
Out[243]:
a -0.186646
b -1.692424
c -0.303893
d -1.425662
e 1.114285
dtype: float64
In [244]: s.rename(str.upper)
Out[244]:
A -0.186646
B -1.692424
C -0.303893
D -1.425662
E 1.114285
dtype: float64
Если вы передаёте функцию, она должна возвращать значение при вызове с любыми метками (и должна генерировать набор уникальных значений). Также можно использовать словарь или Series:
In [245]: df.rename(
.....: columns={"one": "foo", "two": "bar"},
.....: index={"a": "apple", "b": "banana", "d": "durian"},
.....: )
.....:
Out[245]:
foo bar three
apple 1.394981 1.772517 NaN
banana 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
durian NaN 0.279344 -0.613172
Если соответствие не включает метку столбца/индекса, она не переименовывается. Обратите внимание, что дополнительные метки в соответствие не вызывают ошибок.
DataFrame.rename() также поддерживает вызов в стиле «оси», где вы указываете один mapper и axis, к которому вы применяете это соответствие.
In [246]: df.rename({"one": "foo", "two": "bar"}, axis="columns")
Out[246]:
foo bar three
a 1.394981 1.772517 NaN
b 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
d NaN 0.279344 -0.613172
In [247]: df.rename({"a": "apple", "b": "banana", "d": "durian"}, axis="index")
Out[247]:
one two three
apple 1.394981 1.772517 NaN
banana 0.343054 1.912123 -0.050390
c 0.695246 1.478369 1.227435
durian NaN 0.279344 -0.613172
Наконец, rename() также принимает скаляр или список для изменения атрибута Series.name.
In [248]: s.rename("scalar-name")
Out[248]:
a -0.186646
b -1.692424
c -0.303893
d -1.425662
e 1.114285
Name: scalar-name, dtype: float64
Методы DataFrame.rename_axis() и Series.rename_axis() позволяют изменить конкретные имена MultiIndex (в отличие от меток).
In [249]: df = pd.DataFrame(
.....: {"x": [1, 2, 3, 4, 5, 6], "y": [10, 20, 30, 40, 50, 60]},
.....: index=pd.MultiIndex.from_product(
.....: [["a", "b", "c"], [1, 2]], names=["let", "num"]
.....: ),
.....: )
.....:
In [250]: df
Out[250]:
x y
let num
a 1 1 10
2 2 20
b 1 3 30
2 4 40
c 1 5 50
2 6 60
In [251]: df.rename_axis(index={"let": "abc"})
Out[251]:
x y
abc num
a 1 1 10
2 2 20
b 1 3 30
2 4 40
c 1 5 50
2 6 60
In [252]: df.rename_axis(index=str.upper)
Out[252]:
x y
LET NUM
a 1 1 10
2 2 20
b 1 3 30
2 4 40
c 1 5 50
2 6 60
Итерация
Поведение базовой итерации по объектам pandas зависит от типа. При итерации по Series она рассматривается как массив, и базовая итерация производит значения. DataFrame следуют соглашению dict-like, итерируясь по «ключам» объектов.
Короче говоря, базовая итерация (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 также имеют метод dict-like items() для итерации по парам (ключ, значение).
Для итерации по строкам 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-like интерфейсом, items() итерирует по парам ключ-значение:
Series: пары (индекс, скалярное значение)
DataFrame: пары (столбец, Series)
Например:
In [258]: for label, ser in df.items():
.....: print(label)
.....: print(ser)
.....:
a
0 1
1 2
2 3
Name: a, dtype: int64
b
0 a
1 b
2 c
Name: b, dtype: object
iterrows
iterrows() позволяет вам итерироваться по строкам DataFrame как по объектам Series. Он возвращает итератор, возвращающий каждое значение индекса вместе с Series, содержащей данные в каждой строке:
In [259]: for row_index, row in df.iterrows():
.....: print(row_index, row, sep="\n")
.....:
0
a 1
b a
Name: 0, dtype: object
1
a 2
b b
Name: 1, dtype: object
2
a 3
b c
Name: 2, dtype: object
Примечание
Поскольку iterrows() возвращает Series для каждой строки, она не сохраняет типы данных между строками (типы данных сохраняются между столбцами для DataFrame). Например,
In [260]: df_orig = pd.DataFrame([[1, 1.5]], columns=["int", "float"])
In [261]: df_orig.dtypes
Out[261]:
int int64
float float64
dtype: object
In [262]: row = next(df_orig.iterrows())[1]
In [263]: row
Out[263]:
int 1.0
float 1.5
Name: 0, dtype: float64
Все значения в row, возвращаемые как Series, теперь повышаются до плавающей точки, а также исходное целое значение в столбце x.
In [264]: row["int"].dtype
Out[264]: dtype('float64')
In [265]: df_orig["int"].dtype
Out[265]: dtype('int64')
Для сохранения типов данных при итерации по строкам лучше использовать itertuples(), который возвращает именованные кортежи значений и который, как правило, намного быстрее, чем iterrows().
Например, искусственный способ транспонировать DataFrame:
In [266]: df2 = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
In [267]: print(df2)
x y
0 1 4
1 2 5
2 3 6
In [268]: print(df2.T)
0 1 2
x 1 2 3
y 4 5 6
In [269]: df2_t = pd.DataFrame({idx: values for idx, values in df2.iterrows()})
In [270]: print(df2_t)
0 1 2
x 1 2 3
y 4 5 6
itertuples
Метод 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 accessor
Series имеет аксессор для краткого возврата свойств datetime для значений 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]
Вы также можете форматировать значения datetime как строки с помощью 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 при обращении с не datetime-подобными значениями.
Векторизованные строковые методы
Series оснащена набором строковых методов обработки, которые облегчают работу с каждым элементом массива. Возможно, самое главное, эти методы автоматически исключают пропущенные/NA значения. К ним можно получить доступ через атрибут Series’s 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 не имеет побочного эффекта изменения ваших данных; почти каждый метод возвращает новый объект, оставляя исходный объект нетронутым. Если данные изменены, это произошло потому, что вы сделали это явно.
dtypes
В основном, pandas использует массивы NumPy и типы данных для Series или отдельных столбцов DataFrame. NumPy предоставляет поддержку для float, int, bool, timedelta64[ns] и datetime64[ns] (обратите внимание, что NumPy не поддерживает временные метки с учётом часового пояса).
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
defaults
По умолчанию, целочисленные типы данных являются 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
Поскольку данные были транспонированы, исходное определение типов хранило все столбцы как объект, что 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()(преобразование в объекты даты и времени)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()(преобразование в объекты временных интервалов)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', эти ошибки будут проигнорированы, и pandas преобразует проблемные элементы в pd.NaT (для дат и времени) или np.nan (для чисел). Это может быть полезно, если вы читаете данные, которые в основном имеют желаемый тип данных (например, числовой, дата и время), но иногда содержат несоответствующие элементы, которые вы хотите представить как пропущенные:
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)
Поскольку эти методы применяются только к одномерным массивам, спискам или скалярам, их нельзя напрямую использовать с многомерными объектами, такими как DataFrames. Однако с помощью apply() мы можем эффективно «применять» функцию к каждому столбцу:
In [407]: import datetime
In [408]: df = pd.DataFrame([["2016-07-09", datetime.datetime(2016, 3, 2)]] * 2, dtype="O")
In [409]: df
Out[409]:
0 1
0 2016-07-09 2016-03-02 00:00:00
1 2016-07-09 2016-03-02 00:00:00
In [410]: df.apply(pd.to_datetime)
Out[410]:
0 1
0 2016-07-09 2016-03-02
1 2016-07-09 2016-03-02
In [411]: df = pd.DataFrame([["1.1", 2, 3]] * 2, dtype="O")
In [412]: df
Out[412]:
0 1 2
0 1.1 2 3
1 1.1 2 3
In [413]: df.apply(pd.to_numeric)
Out[413]:
0 1 2
0 1.1 2 3
1 1.1 2 3
In [414]: df = pd.DataFrame([["5us", pd.Timedelta("1day")]] * 2, dtype="O")
In [415]: df
Out[415]:
0 1
0 5us 1 days 00:00:00
1 5us 1 days 00:00:00
In [416]: df.apply(pd.to_timedelta)
Out[416]:
0 1
0 0 days 00:00:00.000005 1 days
1 0 days 00:00:00.000005 1 days
ловушки
Выполнение операций выбора данных типа integer может легко привести к приведению данных к типу floating. Тип данных входных данных будет сохранен в тех случаях, когда nans не добавляются. См. также Поддержка целочисленных значений NA.
In [417]: dfi = df3.astype("int32")
In [418]: dfi["E"] = 1
In [419]: dfi
Out[419]:
A B C E
0 1 0 26 1
1 3 1 86 1
2 0 0 46 1
3 0 1 212 1
4 -1 -1 26 1
5 1 0 7 1
6 0 -1 184 1
7 0 0 206 1
In [420]: dfi.dtypes
Out[420]:
A int32
B int32
C int32
E int64
dtype: object
In [421]: casted = dfi[dfi > 0]
In [422]: casted
Out[422]:
A B C E
0 1.0 NaN 26 1
1 3.0 1.0 86 1
2 NaN NaN 46 1
3 NaN 1.0 212 1
4 NaN NaN 26 1
5 1.0 NaN 7 1
6 NaN NaN 184 1
7 NaN NaN 206 1
In [423]: casted.dtypes
Out[423]:
A float64
B float64
C int32
E int64
dtype: object
В то время как типы данных float остаются неизменными.
In [424]: dfa = df3.copy()
In [425]: dfa["A"] = dfa["A"].astype("float32")
In [426]: dfa.dtypes
Out[426]:
A float32
B float64
C float64
dtype: object
In [427]: casted = dfa[df2 > 0]
In [428]: casted
Out[428]:
A B C
0 1.047606 0.256090 26.0
1 3.497968 1.426469 86.0
2 NaN NaN 46.0
3 NaN 1.139976 212.0
4 NaN NaN 26.0
5 1.346426 0.096706 7.0
6 NaN NaN 184.0
7 NaN NaN 206.0
In [429]: casted.dtypes
Out[429]:
A float32
B float64
C float64
dtype: object
Выбор столбцов на основе dtype
Метод 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–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.2.2/user_guide/basics.html