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