groupby_df = DataFrame.groupby(by=None, axis=no_default, level=None, as_index=True, sort=True, group_keys=True, observed=no_default, dropna=True) groupby_s = Series.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, observed=.no_default, dropna=True)
by=None
- используется для определения групп. Если by
является функцией, то она вызывается для каждого значения индексной метки объекта. Если передан словарь dict
или Series
, то ключи будут сопоставляться с индексными метками, а для определения групп будет использоваться ЗНАЧЕНИЯ словаря или Series
(сначала выравниваются значения Series
). Если передан список или numpy.ndarray
длины, равной выбранной оси axis
, то для определения групп используются значения как есть. Индексная метка или их список могут быть переданы в группу по столбцам. Обратите внимание, что кортеж интерпретируется как (одиночный) ключ.axis=no_default
- устарело с версии 2.1.0. В будущей версии будет вести себя как axis=0
. Для axis=1
необходимо использовать DataFrame.T.groupby(...)
. В Series.groupby()
НЕ используется и равна 0. level=None
- если ось axis
является MultiIndex
(иерархической), группируйте ее по определенному уровню или уровням. Не указывайте одновременно аргументы by
и level
.as_index=True
- возвращает объект с метками групп в качестве индекса. Актуально только для входных данных DataFrame
. Значение as_index=False
- это фактически сгруппированный вывод в стиле SQL. Этот аргумент не влияет на фильтрацию, а также на преобразования.sort=True
- сортировка ключей групп. Чтобы повысить производительность, нужно отключать эту функцию. Обратите внимание, что она не влияет на порядок наблюдений внутри каждой группы. Метод .groupby()
сохраняет порядок строк в каждой группе. Если задано значение False
, то группы будут отображаться в том же порядке, что и в исходном DataFrame
. Этот аргумент не влияет на фильтрацию, а также на преобразования.group_keys=True
- при вызове метода DataFrame.apply()
при переданном аргументе by
выдает результат с аналогичным индексом (т. е. преобразование). Чтобы идентифицировать части, нужно добавить групповые ключи в index
. По умолчанию, если метки индекса (и столбца) результата совпадают с входными данными, то ключи групп не включаются, и в противном случае включаются.observed=no_default
- устарело, начиная с версии 2.1.0. В будущей версии pandas
значение по умолчанию изменится на True
. (применялся только в том случае, если какие-либо из групповых элементов являлся категориальным)dropna=True
- если True
и если ключи группы содержат значения NA
, то значения NA
вместе со строкой/столбцом будут удалены. Если False
, то значения NA
также будут рассматриваться как ключ в группах.DataFrameGroupBy
, содержащий информацию о группах.Метод DataFrame.groupby()
модуля pandas
группирует DataFrame
с помощью mapper
или по последовательности столбцов.
Операция groupby
включает в себя некоторую комбинацию разбиения объекта, применения функции и объединения результатов. Метод можно использовать для группировки больших объемов данных и вычислительных операций в полученных группах.
Атрибут DataFrameGroupBy.groups
- представляет собой словарь, ключами которого являются вычисленные уникальные группы, а соответствующие значения - метки осей, принадлежащие каждой группе.
>>> import pandas as pd >>> import numpy as np df = pd.DataFrame( { "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"], "B": ["one", "one", "two", "three", "two", "two", "one", "three"], "C": np.random.randn(8), "D": np.random.randn(8), } ) >>> df # A B C D # 0 foo one -1.468449 0.181378 # 1 bar one 2.275577 1.118370 # 2 foo two -1.207740 -0.049727 # 3 bar three 0.004842 -0.867390 # 4 foo two 0.306493 -1.486156 # 5 bar two -0.542448 -0.034376 # 6 foo one -1.764169 -0.780487 # 7 foo three 1.869280 -0.337706 >>> df.groupby("A").groups # {'bar': [1, 3, 5], 'foo': [0, 2, 4, 6, 7]} >>> df.T.groupby(get_letter_type).groups # {'consonant': ['B', 'C', 'D'], 'vowel': ['A']}
Вызов стандартной функции Python len()
для объекта GroupBy
просто возвращает длину словаря groups
, так что это просто удобство:
>>> grouped = df.groupby(["A", "B"]) >>> grouped.groups # { # ('bar', 'one'): [1], # ('bar', 'three'): [3], # ('bar', 'two'): [5], # ('foo', 'one'): [0, 6], # ('foo', 'three'): [7], # ('foo', 'two'): [2, 4] # } >>> len(grouped) # 6
Для каждого объекта DataFrameGroupBy
создается свой набор атрибутов и методов (в зависимости от анализируемых данных), который можно посмотреть в консоле Python3, поставив после объекта точку и нажав 2 раза <TAB>
:
>>> grouped.<TAB><TAB> grouped.A grouped.boxplot( grouped.describe( ... grouped.B grouped.corr( grouped.diff( ... grouped.C grouped.corrwith( grouped.dtypes ... grouped.D grouped.count() grouped.ewm( ... grouped.agg( grouped.cov( grouped.expanding( ... grouped.aggregate( grouped.cumcount( grouped.ffill( ... grouped.all( grouped.cummax( grouped.fillna( ... grouped.any( grouped.cummin( grouped.filter( ... grouped.apply( grouped.cumprod( grouped.first( ... grouped.bfill( grouped.cumsum( grouped.get_group( ...
Все атрибуты и методы объекта DataFrameGroupBy
рассматриваться не будут, в виду их большого количества. Их описание можно посмотреть в официальной документации к pandas
или функцией help()
. Ниже будут рассмотрены основные операции и перечислены агрегирующие методы/функции.
NA
;DataFrame
с MultiIndex
;DataFrameGroupBy
;DataFrame
в GroupBy
;DataFrameGroupBy
;DataFrameGroupBy
;GroupBy
;DataFrameGroupBy.aggregate()
;GroupBy
;DataFrameGroupBy.transform()
;GroupBy
;GroupBy
, метод DataFrameGroupBy.filter()
;DataFrame.resample()
в GroupBy
;group_keys
.Абстрактное определение группировки заключается в обеспечении сопоставления меток с именами групп. Чтобы создать объект DataFrameGroupBy
можно сделать следующее:
speeds = pd.DataFrame( [ ("bird", "Falconiformes", 389.0), ("bird", "Psittaciformes", 24.0), ("mammal", "Carnivora", 80.2), ("mammal", "Primates", np.nan), ("mammal", "Carnivora", 58), ], index=["falcon", "parrot", "lion", "monkey", "leopard"], columns=("class", "order", "max_speed"), ) # группируем по столбцу `class` >>> g = speeds.groupby("class") >>> g # <pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fd573ad72e0> # названия групп - ключи словаря >>> g.groups # {'bird': ['falcon', 'parrot'], 'mammal': ['lion', 'monkey', 'leopard']} >>> g.get_group('bird') # class order max_speed # falcon bird Falconiformes 389.0 # parrot bird Psittaciformes 24.0 # можно группировать по нескольким столбцам >>> speeds.groupby(["class", "order"]) # <pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fd573ad7df0>
df.groupby('class')
- это просто синтаксический сахар дляdf.groupby(df['class'])
.
Сопоставление может быть задано различными способами:
NumPy
той же длины, что и индекс.dict
или Series
, сопоставляющие индексные метки и название группы.DataFrame
- строка, указывающая имя столбца или имя уровня индекса, используемое для группировки.Внимание! Строка, передаваемая в
DataFrame.groupby()
, может относиться либо к столбцу, либо к уровню индекса. Если строка совпадает как с именем столбца, так и с именем уровня индекса, будет выдана ошибкаValueError
.
В совокупности, группирующие объекты называются ключами. Используем созданный ранее DataFrame
. Если есть MultiIndex
для столбцов A
и B
, то можно группировать по всем столбцам, кроме того, который указали:
# установим столбцы в качестве `MultiIndex` >>> df2 = df.set_index(["A", "B"]) >>> df2 # C D # A B # foo one -1.468449 0.181378 # bar one 2.275577 1.118370 # foo two -1.207740 -0.049727 # bar three 0.004842 -0.867390 # foo two 0.306493 -1.486156 # bar two -0.542448 -0.034376 # foo one -1.764169 -0.780487 # three 1.869280 -0.337706 # index.difference() возвращает новый индекс с элементами, # отсутствующими в столбце `B`, т.е. разность множеств двух объектов Index. >>> grouped = df2.groupby(level=df2.index.names.difference(["B"])) >>> grouped.sum() # C D # A # bar 1.737971 0.216603 # foo -2.264585 -2.472699
Приведенный выше GroupBy
делит DataFrame
по индексным меткам (строкам). Чтобы разбить по столбцам, сначала выполним транспонирование (атрибут df.T
):
# определим функцию сопоставления # индексных меток def get_letter_type(letter): if letter.lower() in 'aeiou': return 'vowel' else: return 'consonant' >>> grouped = df.T.groupby(get_letter_type) >>> grouped.first() # 0 1 2 3 4 5 6 7 # consonant one one two three two two one three # vowel foo bar foo bar foo bar foo foo >>> grouped.size() # consonant 3 # vowel 1 # dtype: int64 >>> grouped.groups # {'consonant': ['B', 'C', 'D'], 'vowel': ['A']}
Объекты pandas.Index
поддерживают повторяющиеся значения. Если в операции groupby
в качестве ключа группы используется неуникальный индекс, все значения одного и того же значения индекса будут считаться находящимися в одной группе, и, таким образом, выходные данные функций агрегирования будут содержать только уникальные значения индекса:
# допустим есть индекс >>> lst = [1, 2, 3, 1, 2, 3] # серия с индексом `lst` >>> s = pd.Series([1, 2, 3, 10, 20, 30], lst) >>> s # 1 1 # 2 2 # 3 3 # 1 10 # 2 20 # 3 30 # dtype: int64 # группируем по индексу `level=0` >>> grouped = s.groupby(level=0) >>> grouped.first() # 1 1 # 2 2 # 3 3 # dtype: int64 >>> grouped.sum() # 1 11 # 2 22 # 3 33 # dtype: int64
Обратите внимание, что разделение не происходит до тех пор, пока оно не понадобится. Создание объекта GroupBy
только проверяет, передано ли допустимое сопоставление.
Заметка. Многие виды сложных манипуляций с данными могут быть выражены в терминах операций
GroupBy
(хотя нельзя гарантировать, что это будет наиболее эффективная реализация). Можно проявить творческий подход к функциям сопоставления меток.
По умолчанию ключи групп сортируются во время операции groupby
. Тем не менее, можно передать sort=False
для потенциального ускорения операции группировки. При sort=False
порядок между группами-ключами соответствует порядку появления ключей в исходном DataFrame
:
>>> df2 = pd.DataFrame({"X": ["B", "B", "A", "A"], "Y": [1, 2, 3, 4]}) >>> df2.groupby(["X"]).sum() # Y # X # A 7 # B 3 >>> df2.groupby(["X"], sort=False).sum() # Y # X # B 3 # A 7
Обратите внимание, что groupby
сохранит порядок, в котором наблюдения сортируются внутри каждой группы. Например, группы, созданные DataFrame.groupby()
ниже, расположены в том порядке, в котором они появились в исходном DataFrame
:
>>> df3 = pd.DataFrame({"X": ["A", "B", "A", "B"], "Y": [1, 4, 3, 2]}) >>> df3 # X Y # 0 A 1 # 1 B 4 # 2 A 3 # 3 B 2 # смотрим на индексные метки >>> df3.groupby(["X"]).get_group("A") # X Y # 0 A 1 # 2 A 3 >>> df3.groupby(["X"]).get_group("B") # X Y # 1 B 4 # 3 B 2
NA
По умолчанию пропущенные/пустые значения NA
исключаются из групповых ключей. Однако, если нужно включить значения NA
в групповые ключи, то можно передать аргумент dropna=False
. Создадим DataFrame
, удобный для понимания о чем речь.
>>> df_list = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]] >>> df_dropna = pd.DataFrame(df_list, columns=["a", "b", "c"]) >>> df_dropna a b c 0 1 2.0 3 1 1 NaN 4 2 2 1.0 3 3 1 2.0 2
По умолчанию dropna=True
, что исключает NaN
в ключах.
>>> df_dropna.groupby(by=["b"], dropna=True).sum() # a c # b # 1.0 2 3 # 2.0 2 5
Чтобы разрешить использование NaN
в ключах, установим для dropna=False
:
>>> df_dropna.groupby(by=["b"], dropna=False).sum() # a c # b # 1.0 2 3 # 2.0 2 5 # NaN 1 4
MultiIndex
Иерархическое/многоуровневое индексирование очень интересно, т.к. оно позволяет производить сложный анализ и манипулирование данными. По сути, многоуровневое индексирование позволяет хранить и манипулировать данными с произвольным количеством измерений в структурах данных меньшей размерности, таких как Series (1d) и DataFrame (2d).
Данные с иерархической индексацией вполне естественно группировать по одному из уровней иерархии. Создадим Series
с MultiIndex
, удобный для понимания о чем речь.
arrays = [ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ["one", "two", "one", "two", "one", "two", "one", "two"], ] >>> index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"]) >>> s = pd.Series(np.random.randn(8), index=index) >>> s # first second # bar one -0.511733 # two -0.357432 # baz one -1.258535 # two -1.442737 # foo one -2.372564 # two 0.655380 # qux one 0.941400 # two -2.647630 # dtype: float64
Можно сгруппировать по одному из уровней.
>>> grouped = s.groupby(level=0) >>> grouped.sum() # first # bar -0.869164 # baz -2.701272 # foo -1.717184 # qux -1.706230 # dtype: float64
Если в MultiIndex
указаны имена, их можно передать вместо номера уровня:
>>> s.groupby(level="second").sum() # second # one -3.201431 # two -3.792419 # dtype: float64
Поддерживается группировка с несколькими уровнями. Создадим Series
с MultiIndex
, удобный для понимания о чем речь.
arrays = [ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ["doo", "doo", "bee", "bee", "bop", "bop", "bop", "bop"], ["one", "two", "one", "two", "one", "two", "one", "two"], ] >>> index = pd.MultiIndex.from_arrays(arrays, names=["first", "second", "third"]) >>> s = pd.Series(np.random.randn(8), index=index) >>> s # first second third # bar doo one -0.771680 # two -0.660627 # baz bee one -0.022898 # two -0.404116 # foo bop one 0.780148 # two -1.699268 # qux bop one 0.742693 # two -2.246963 # dtype: float64 >>> s.groupby(level=["first", "second"]).sum() # first second # bar doo -1.432306 # baz bee -0.427015 # foo bop -0.919120 # qux bop -1.504270 # dtype: float64
Имена уровней индекса могут быть предоставлены в качестве ключей.
>>> s.groupby(["first", "second"]).sum() # first second # bar doo -1.432306 # baz bee -0.427015 # foo bop -0.919120 # qux bop -1.504270 # dtype: float64
DataFrame
по уровням индексов и столбцовDataFrame
может быть сгруппирован по комбинации столбцов и уровней индекса. Можно указать имена столбцов и индексов или использовать группировщик pandas.Grouper()
.
Создадим DataFrame
с MultiIndex
, удобный для понимания темы:
arrays = [ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ["one", "two", "one", "two", "one", "two", "one", "two"], ] >>> index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"]) >>> df = pd.DataFrame({"A": [1, 1, 1, 1, 2, 2, 3, 3], "B": np.arange(8)}, index=index) >>> df # A B # first second # bar one 1 0 # two 1 1 # baz one 1 2 # two 1 3 # foo one 2 4 # two 2 5 # qux one 3 6 # two 3 7
Группируем df
по второму уровню индекса second
и столбцу A
.
>>> df.groupby([pd.Grouper(level=1), "A"]).sum() # B # second A # one 1 2 # 2 4 # 3 6 # two 1 4 # 2 5 # 3 7
Уровни индекса также могут быть указаны по названию
df.groupby([pd.Grouper(level="second"), "A"]).sum() # B # second A # one 1 2 # 2 4 # 3 6 # two 1 4 # 2 5 # 3 7
Имена уровней индекса могут быть указаны в качестве ключей непосредственно в groupby
:
df.groupby(["second", "A"]).sum() # B # second A # one 1 2 # 2 4 # 3 6 # two 1 4 # 2 5 # 3 7
DataFrameGroupBy
DataFrame
в GroupBy
После создания объекта GroupBy
из DataFrame
может потребоваться сделать что-то отдельное для каждого столбца. Таким образом, используя []
для объекта GroupBy
аналогично тому, который используется для получения столбца из DataFrame
, можно сделать:
df = pd.DataFrame( { "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"], "B": ["one", "one", "two", "three", "two", "two", "one", "three"], "C": np.random.randn(8), "D": np.random.randn(8), } ) >>> grouped = df.groupby(["A"]) >>> grouped_C = grouped["C"] >>> grouped_D = grouped["D"]
В основном это синтаксический сахар для альтернативы, которая гораздо более подробна:
>>> df["C"].groupby(df["A"])
Кроме того, этот метод позволяет избежать повторного вычисления внутренней информации о группировке, полученной из переданного ключа.
DataFrameGroupBy
С объектом GroupBy
итерация по сгруппированным данным очень естественна и функционирует аналогично itertools.groupby()
:
>>> grouped = df.groupby('A') for name, group in grouped: print(name) print(group) # bar # A B C D # 1 bar one 0.254161 1.511763 # 3 bar three 0.215897 -0.990582 # 5 bar two -0.077118 1.211526 # foo # A B C D # 0 foo one -0.575247 1.346061 # 2 foo two -1.143704 1.627081 # 4 foo two 1.193555 -0.441652 # 6 foo one -0.408530 0.268520 # 7 foo three -0.862495 0.024580
В случае группировки по нескольким ключам, имя группы будет кортежем:
for name, group in df.groupby(['A', 'B']): print(name) print(group) # ('bar', 'one') # A B C D # 1 bar one -0.650714 -0.992193 # ('bar', 'three') # A B C D # 3 bar three 0.467266 1.232117 # ('bar', 'two') # A B C D # 5 bar two 0.02017 -0.593099 # ('foo', 'one') # A B C D # 0 foo one -1.416678 1.366671 # 6 foo one 0.440195 -0.141176 # ('foo', 'three') # A B C D # 7 foo three -0.040652 -1.705956 # ('foo', 'two') # A B C D # 2 foo two 0.410918 1.245727 # 4 foo two 0.548223 -0.065070
DataFrameGroupBy
Одну группу можно выбрать с помощью метода DataFrameGroupBy.get_group()
:
>>> grouped.get_group("bar") # A B C D # 1 bar one -0.650714 -0.992193 # 3 bar three 0.467266 1.232117 # 5 bar two 0.020170 -0.593099
Или для объекта, сгруппированного по нескольким столбцам
>>> df.groupby(["A", "B"]).get_group(("bar", "one")) # A B C D # 1 bar one -0.650714 -0.992193
GroupBy
Агрегирование - это операция GroupBy
, которая уменьшает размерность объекта группировки. Результатом агрегирования является или, по крайней мере, обрабатывается скалярное значение для каждого столбца в группе. Например, получение суммы каждого столбца в группе значений.
animals = pd.DataFrame( { "kind": ["cat", "dog", "cat", "dog"], "height": [9.1, 6.0, 9.5, 34.0], "weight": [7.9, 7.5, 9.9, 198.0], } ) >>> animals # kind height weight # 0 cat 9.1 7.9 # 1 dog 6.0 7.5 # 2 cat 9.5 9.9 # 3 dog 34.0 198.0 >>> animals.groupby("kind").sum() # height weight # kind # cat 18.6 17.8 # dog 40.0 205.5
В результате ключи групп отображаются в индексе по умолчанию. Или их можно включить в столбцы, передав аргумент as_index=False
.
>>> animals.groupby("kind", as_index=False).sum() # kind height weight # 0 cat 18.6 17.8 # 1 dog 40.0 205.5
Многие распространенные функции агрегирования встроены в объект GroupBy
в качестве методов. В перечисленных ниже методах те, у которых есть *
, не имеют оптимизированной для Cython реализации.
Метод `GroupBy` | Описание |
any() | являются ли какие-либо значения в группах истинными |
all() | являются ли истинными все значения в группах |
count() | количество значений, отличных от NA, в группах |
cov() * | ковариация групп |
first() | первое встречающееся значение в каждой группе |
idxmax() * | индекс максимального значения в каждой группе |
idxmin() * | индекс минимального значения в каждой группе |
last() | последнее встречающееся значение в каждой группе |
max() | максимальное значение в каждой группе |
mean() | среднее значение для каждой группы |
median() | медиана каждой группы |
min() | минимальное значение в каждой группе |
nunique() | количество уникальных значений в каждой группе |
prod() | произведение значений в каждой группе |
quantile() | заданный квантиль значений в каждой группе |
sem() | стандартная ошибка среднего значения значений в каждой группе |
size() | количество значений в каждой группе |
skew() * | перекос значений в каждой группе |
std() | стандартное отклонение значений в каждой группе |
sum() | сумма значений в каждой группе |
var() | разница значений в каждой группе |
Несколько примеров:
>>> df.groupby("A")[["C", "D"]].max() # C D # A # bar 0.467266 1.232117 # foo 0.548223 1.366671 >>> df.groupby(["A", "B"]).mean() # C D # A B # bar one -0.650714 -0.992193 # three 0.467266 1.232117 # two 0.020170 -0.593099 # foo one -0.488241 0.612748 # three -0.040652 -1.705956 # two 0.479571 0.590328
Другим простым примером агрегирования является вычисление размера каждой группы. Он включен в GroupBy
в качестве метода .size()
. Возвращает ряд, индексом которого являются имена групп, а значениями - размеры каждой группы.
>>> grouped = df.groupby(["A", "B"]) >>> grouped.size() # A B # bar one 1 # three 1 # two 1 # foo one 2 # three 1 # two 2 # dtype: int64
Метод DataFrameGroupBy.describe()
можно использовать для удобного создания коллекции сводной статистики по каждой из групп
>>> grouped.describe() # C ... D # count mean std ... 50% 75% max # A B ... # bar one 1.0 -0.650714 NaN ... -0.992193 -0.992193 -0.992193 # three 1.0 0.467266 NaN ... 1.232117 1.232117 1.232117 # two 1.0 0.020170 NaN ... -0.593099 -0.593099 -0.593099 # foo one 2.0 -0.488241 1.313007 ... 0.612748 0.989710 1.366671 # three 1.0 -0.040652 NaN ... -1.705956 -1.705956 -1.705956 # two 2.0 0.479571 0.097090 ... 0.590328 0.918027 1.245727 # # [6 rows x 16 columns]
Другим примером агрегирования является вычисление количества уникальных значений каждой группы DataFrameGroupBy.nunique()
. Это похоже на метод DataFrame.value_counts()
, за исключением того, что она подсчитывает только количество уникальных значений.
>>> ll = [['foo', 1], ['foo', 2], ['foo', 2], ['bar', 1], ['bar', 1]] >>> df4 = pd.DataFrame(ll, columns=["A", "B"]) >>> df4 # A B # 0 foo 1 # 1 foo 2 # 2 foo 2 # 3 bar 1 # 4 bar 1 >>> df4.groupby("A")["B"].nunique() # A # bar 1 # foo 2 # Name: B, dtype: int64
Если значение as_index=True
(по умолчанию), то методы агрегирования не будут возвращать группы, которые агрегируются, как именованные столбцы. Сгруппированные столбцы будут индексами возвращаемого объекта.
Передача as_index=False
вернет группы, по которым выполняется агрегирование, если они называются индексами или столбцами.
DataFrameGroupBy.aggregate()
Метод DataFrameGroupBy.aggregate()
может принимать множество различных типов входных данных. Начнем с примеров использования строковых псевдонимов для различных методов GroupBy
Любой агрегирующий метод, реализуемый в pandas
, может быть передан в виде строки методу .aggregate()
или его псевдониму .agg()
. Пользователям рекомендуется использовать сокращенный метод DataFrameGroupBy.agg()
.
>>> grouped = df.groupby("A") >>> grouped[["C", "D"]].aggregate("sum") # C D # A bar -0.163277 -0.353176 foo -0.057993 0.700196 >>> grouped = df.groupby(["A", "B"]) >>> grouped.agg("sum") # C D # A B # bar one -0.650714 -0.992193 # three 0.467266 1.232117 # two 0.020170 -0.593099 # foo one -0.976483 1.225496 # three -0.040652 -1.705956 # two 0.959141 1.180656
Результат агрегирования будет иметь имена групп в качестве нового индекса вдоль сгруппированной оси. В случае нескольких ключей результатом по умолчанию является MultiIndex
. Как упоминалось выше, это можно изменить с помощью аргумента as_index
:
>>> grouped = df.groupby(["A", "B"], as_index=False) >>> grouped.agg("sum") # A B C D # 0 bar one -0.650714 -0.992193 # 1 bar three 0.467266 1.232117 # 2 bar two 0.020170 -0.593099 # 3 foo one -0.976483 1.225496 # 4 foo three -0.040652 -1.705956 # 5 foo two 0.959141 1.180656 >>> df.groupby("A", as_index=False)[["C", "D"]].agg("sum") # A C D # 0 bar -0.163277 -0.353176 # 1 foo -0.057993 0.700196
Обратите внимание, что для достижения того же результата можно использовать метод
DataFrame.reset_index()
, т.к. имена столбцов хранятся в результирующемMultiIndex
, хотя это приведет к созданию дополнительной копии...
>>> df.groupby(["A", "B"]).agg("sum").reset_index() # A B C D # 0 bar one -0.650714 -0.992193 # 1 bar three 0.467266 1.232117 # 2 bar two 0.020170 -0.593099 # 3 foo one -0.976483 1.225496 # 4 foo three -0.040652 -1.705956 # 5 foo two 0.959141 1.180656
Пользователи также могут предоставлять собственные функции (UDF) для агрегирования данных.
Предупреждение. При статистической обработке с помощью собственных функции, определяемая пользователем функция не должна изменять предоставленный
Series
. Общим правилом в программировании является то, что не следует изменять контейнер во время его итерации. Мутация сделает итератор недействительным, что приведет к неожиданному поведению.Заметка. Статистическая обработка с помощью определяемой пользователем функции часто менее эффективна, чем использование встроенных методов в
GroupBy
. Лучше разбить сложную операцию на цепочку операций, использующих встроенные методы.
>>> animals # kind height weight # 0 cat 9.1 7.9 # 1 dog 6.0 7.5 # 2 cat 9.5 9.9 # 3 dog 34.0 198.0 >>> animals.groupby("kind")[["height"]].agg(lambda x: set(x)) # height # kind # cat {9.1, 9.5} # dog {34.0, 6.0}
Результирующий тип dtype
будет отражать тип агрегатирующей функции. Если результаты из разных групп имеют разные dtype
, то общий dtype
будет определен так же, как и построение DataFrame
>>> animals.groupby("kind")[["height"]].agg(lambda x: x.astype(int).sum()) # height # kind # cat 18 # dog 40
С помощью сгруппированных серий Series
можно передать список или словарь функций для агрегирования, выведя DataFrame
:
>>> grouped = df.groupby("A") >>> grouped["C"].agg(["sum", "mean", "std"]) # sum mean std # A # bar -0.163277 -0.054426 0.562711 # foo -0.057993 -0.011599 0.817179
В сгруппированном DataFrame
можно передать список функций, применяемых к каждому столбцу, что приведет к агрегированному результату с иерархическим индексом:
>>> grouped[["C", "D"]].agg(["sum", "mean", "std"]) # C D # sum mean std sum mean std # A # bar -0.163277 -0.054426 0.562711 -0.353176 -0.117725 1.185906 # foo -0.057993 -0.011599 0.817179 0.700196 0.140039 1.250602
Результаты именуются в соответствии с самими функциями. Если нужно их переименовать, то можно добавить цепочку операций для Series
следующим образом:
( grouped["C"] .agg(["sum", "mean", "std"]) .rename(columns={"sum": "foo", "mean": "bar", "std": "baz"}) ) # foo bar baz # A # bar -0.163277 -0.054426 0.562711 # foo -0.057993 -0.011599 0.817179
Для сгруппированного DataFrame
можно переименовать аналогичным образом:
( grouped[["C", "D"]].agg(["sum", "mean", "std"]).rename( columns={"sum": "foo", "mean": "bar", "std": "baz"} ) ) # C D # foo bar baz foo bar baz # A # bar -0.163277 -0.054426 0.562711 -0.353176 -0.117725 1.185906 # foo -0.057993 -0.011599 0.817179 0.700196 0.140039 1.250602
Как правило, имена выходных столбцов должны быть уникальными, но
pandas
позволит применить одну и ту же функцию (или две функции с одинаковым именем) к одному и тому же столбцу.>>> grouped["C"].agg(["sum", "sum"]) # sum sum # A # bar -0.163277 -0.163277 # foo -0.057993 -0.057993
Pandas
также позволяет предоставлять несколько lambda-выражений. В этом случаеpandas
будет добавлять_i
к каждой последующейlambda
-функции.>>> grouped["C"].agg([lambda x: x.max() - x.min(), lambda x: x.median() - x.mean()]) # <lambda_0> <lambda_1> # A # bar 1.117980 0.074596 # foo 1.964901 0.422516
pandas.NamedAgg(column, aggfunc)
Для поддержки агрегирования для конкретных столбцов с контролем над именами выходных столбцов pandas принимает специальный синтаксис в DataFrameGroupBy.agg()
и SeriesGroupBy.agg()
, известный как "именованная агрегация"
Библиотека предоставляет класс pandas.NamedAgg(column, aggfunc)
, где
column
- это имена выходных столбцовaggfunc
- функция, применяемая к указанному столбцу. Если строка, то это должна быть встроенная функция pandas
.>>> animals # kind height weight # 0 cat 9.1 7.9 # 1 dog 6.0 7.5 # 2 cat 9.5 9.9 # 3 dog 34.0 198.0 animals.groupby("kind").agg( min_height=pd.NamedAgg(column="height", aggfunc="min"), max_height=pd.NamedAgg(column="height", aggfunc="max"), average_weight=pd.NamedAgg(column="weight", aggfunc="mean"), ) # min_height max_height average_weight # kind # cat 9.1 9.5 8.90 # dog 6.0 34.0 102.75
Класс pandas.NamedAgg
представляет собой именованный кортеж. Простые кортежи также разрешены.
animals.groupby("kind").agg( min_height=("height", "min"), max_height=("height", "max"), average_weight=("weight", "mean"), ) # min_height max_height average_weight # kind # cat 9.1 9.5 8.90 # dog 6.0 34.0 102.75
Если нужные имена столбцов не являются допустимыми ключевыми словами Python, то можно создать словарь и распаковать ключевые аргументы:
>>> d = {"total weight": pd.NamedAgg(column="weight", aggfunc="sum")} >>> animals.groupby("kind").agg(**d) # total weight # kind # cat 17.8 # dog 205.5
При использовании именованного агрегирования дополнительные ключевые аргументы не передаются в функции агрегирования. Только пары (column, aggfunc)
должны передаваться как **kwargs
. Если функции агрегирования требуют дополнительных аргументов, то нужно применять их с помощью functools.partial()
.
Именованное агрегирование также допустимо для групп по рядам. В этом случае выбор столбцов отсутствует, поэтому значения являются просто функциями.
animals.groupby("kind").height.agg( min_height="min", max_height="max", ) # min_height max_height # kind # cat 9.1 9.5 # dog 6.0 34.0
Передавая словарь dict
в метод DataFrameGroupBy.agg()
, можно применить различное агрегирование к столбцам DataFrame
:
>>> d = {"C": "sum", "D": lambda x: np.std(x, ddof=1)} >>> grouped.agg(d) # C D # A # bar -0.163277 1.185906 # foo -0.057993 1.250602
Имена функций также могут быть строками. Для того, чтобы строка была валидной, она должна быть реализована как метод GroupBy
.
>>> d = {"C": "sum", "D": "std"} >>> grouped.agg(d) # C D # A # bar -0.163277 1.185906 # foo -0.057993 1.250602
GroupBy
Преобразование - это операция GroupBy
, результат которой индексируется так же, как и группируемая. Типичными примерами являются DataFrameGroupBy.cumsum()
и DataFrameGroupBy.diff()
.
>>> speeds # class order max_speed # falcon bird Falconiformes 389.0 # parrot bird Psittaciformes 24.0 # lion mammal Carnivora 80.2 # monkey mammal Primates NaN # leopard mammal Carnivora 58.0 >>> grouped = speeds.groupby("class")["max_speed"] >>> grouped.cumsum() # falcon 389.0 # parrot 413.0 # lion 80.2 # monkey NaN # leopard 138.2 # Name: max_speed, dtype: float64 >>> grouped.diff() # falcon NaN # parrot -365.0 # lion NaN # monkey NaN # leopard NaN # Name: max_speed, dtype: float64
В отличие от агрегаций, группировки, которые используются для разделения исходного объекта, не включаются в результат
Заметка. Так как преобразования не включают группировки, используемые для разделения результата, аргументы
as_index
иsort
вDataFrame.groupby()
иSeries.groupby()
не имеют никакого эффекта.
Обычно преобразование используется для добавления результата обратно в исходный DataFrame
.
>>> result = speeds.copy() >>> result["cumsum"] = grouped.cumsum() >>> result["diff"] = grouped.diff() >>> result # class order max_speed cumsum diff # falcon bird Falconiformes 389.0 389.0 NaN # parrot bird Psittaciformes 24.0 413.0 -365.0 # lion mammal Carnivora 80.2 80.2 NaN # monkey mammal Primates NaN NaN NaN # leopard mammal Carnivora 58.0 138.2 NaN
GroupBy
Следующие методы GroupBy
действуют как преобразования. Из этих методов только DataFrameGroupBy.fillna()
не имеет реализации, оптимизированной для Cython.
Метод `GroupBy` | Описание |
bfill() | снова заполнит значения `NA` внутри каждой группы |
cumcount() | совокупный подсчет внутри каждой группы |
cummax() | совокупный максимум внутри каждой группы |
cummin() | совокупный минимум внутри каждой группы |
cumprod() | совокупный продукт внутри каждой группы |
cumsum() | совокупная сумма внутри каждой группы |
diff() | разница между соседними значениями внутри каждой группы |
ffill() | прямое заполнение значений `NA` внутри каждой группы |
fillna() | заполняет значения `NA` внутри каждой группы |
pct_change() | процентное изменение между соседними значениями внутри каждой группы |
rank() | ранг каждого значения внутри каждой группы |
shift() | смещение значений вверх или вниз внутри каждой группы |
Кроме того, передача любого встроенного метода агрегирования в виде строки в метод DataFrameGroupBy.transform()
приведет к трансляции результата по группе, создавая преобразованный результат. Если метод агрегации оптимизирован для Cython, он также будет производительным.
DataFrameGroupBy.transform()
Аналогично методу агрегирования, метод DataFrameGroupBy.transform()
может принимать строковые псевдонимы для встроенных методов преобразования. Он также может принимать строковые псевдонимы для встроенных методов агрегирования. Если указан метод агрегирования, то результат будет транслироваться по всей группе.
>>> speeds # class order max_speed # falcon bird Falconiformes 389.0 # parrot bird Psittaciformes 24.0 # lion mammal Carnivora 80.2 # monkey mammal Primates NaN # leopard mammal Carnivora 58.0 >>> grouped = speeds.groupby("class")[["max_speed"]] >>> grouped.transform("cumsum") # max_speed # falcon 389.0 # parrot 413.0 # lion 80.2 # monkey NaN # leopard 138.2 >>> grouped.transform("sum") # max_speed # falcon 413.0 # parrot 413.0 # lion 138.2 # monkey 138.2 # leopard 138.2
В дополнение к псевдонимам строк метод .transform()
также может принимать пользовательские функции. Определяемая пользователем функция должна:
grouped.transform(lambda x: x.iloc[-1])
).chunk.apply
.Заметка. Преобразование путем предоставления определяемой пользователем функции часто менее производительно, чем использование встроенных методов
GroupBy
. Лучше разбить сложную операцию на цепочку операций, использующих встроенные методы.При использовании
DataFrameGroupBy.transform()
для сгруппированногоDataFrame
и функции преобразования возвращаетDataFrame
,pandas
выравнивает индекс результата с индексом входных данных. Можно вызвать.to_numpy()
внутри функции преобразования, чтобы избежать выравнивания.
Аналогично методу DataFrameGroupBy.agg()
, результирующий тип dtype
будет отражать возвращаемый тип функции преобразования. Если результаты из разных групп имеют разные dtypes
, то общий dtype
будет определен так же, как и построение DataFrame
.
Предположим, стоит задача стандартизировать данные внутри каждой группы. Для более ясного понимания, построим Series
:
>>> index = pd.date_range("10/1/1999", periods=1100) >>> ts = pd.Series(np.random.normal(0.5, 2, 1100), index) >>> ts = ts.rolling(window=100, min_periods=100).mean().dropna() >>> ts.head() # 2000-01-08 0.115936 # 2000-01-09 0.154858 # 2000-01-10 0.177720 # 2000-01-11 0.133501 # 2000-01-12 0.139585 # Freq: D, dtype: float64 >>> ts.tail() # 2002-09-30 0.568053 # 2002-10-01 0.530893 # 2002-10-02 0.593561 # 2002-10-03 0.590922 # 2002-10-04 0.581752 # Freq: D, dtype: float64 # НЕ производительный способ # пример для понимания материала >>> transformed = ts.groupby(lambda x: x.year).transform( ... lambda x: (x - x.mean()) / x.std() ... ) # !!! более быстрый аналог # grouped = ts.groupby(lambda x: x.year) # result = (ts - grouped.transform("mean")) / grouped.transform("std")
Ожидаем, что результат теперь будет иметь среднее значение 0 и стандартное отклонение 1 в каждой группе, что можно легко проверить:
# Исходные данные >>> grouped = ts.groupby(lambda x: x.year) >>> grouped.mean() # 2000 0.466281 # 2001 0.432381 # 2002 0.454201 # dtype: float64 >>> grouped.std() # 2000 0.128578 # 2001 0.082351 # 2002 0.155647 # dtype: float64 # Преобразованные данные >>> grouped_trans = transformed.groupby(lambda x: x.year) >>> grouped_trans.mean() # 2000 -3.554878e-16 # 2001 -5.770783e-16 # 2002 1.058119e-16 # dtype: float64 >>> grouped_trans.std() # 2000 1.0 # 2001 1.0 # 2002 1.0 # dtype: float64
Функции преобразования, выходные данные которых имеют меньшую размерность, транслируются в соответствии с формой входного массива.
# НЕ производительный способ # пример для понимания материала >>> ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min()) # 2000-01-08 0.665077 # 2000-01-09 0.665077 # 2000-01-10 0.665077 # 2000-01-11 0.665077 # 2000-01-12 0.665077 # ... # 2002-09-30 0.597013 # 2002-10-01 0.597013 # 2002-10-02 0.597013 # 2002-10-03 0.597013 # 2002-10-04 0.597013 # Freq: D, Length: 1001, dtype: float64 # !!! более быстрый аналог # grouped = ts.groupby(lambda x: x.year) # result = grouped.transform("max") - grouped.transform("min")
Другим распространенным преобразованием данных является замена отсутствующих данных групповым средним. Для этого создадим новую Series
для лучшего понимания материала.
>>> cols = ["A", "B", "C"] >>> values = np.random.randn(1000, 3) >>> values[np.random.randint(0, 1000, 100), 0] = np.nan >>> values[np.random.randint(0, 1000, 50), 1] = np.nan >>> values[np.random.randint(0, 1000, 200), 2] = np.nan >>> data_df = pd.DataFrame(values, columns=cols) >>> data_df # A B C # 0 0.517832 0.204400 1.449715 # 1 NaN -0.044095 0.102307 # 2 NaN 0.768912 NaN # 3 -0.278080 -1.554905 -0.256375 # 4 0.605055 1.822569 0.579360 # .. ... ... ... # 995 1.817169 0.504035 NaN # 996 -0.406149 -2.320358 NaN # 997 1.213487 0.481328 0.438197 # 998 -2.669296 1.888546 -0.365595 # 999 0.011614 -0.443773 0.138153 # # [1000 rows x 3 columns] >>> countries = np.array(["US", "UK", "GR", "JP"]) >>> key = countries[np.random.randint(0, 4, 1000)] >>> grouped = data_df.groupby(key) # Количество не-NA в каждой группе >>> grouped.count() # A B C # GR 219 235 198 # JP 226 233 201 # UK 217 230 205 # US 243 253 212 # НЕ производительный способ # пример для понимания материала >>> transformed = grouped.transform(lambda x: x.fillna(x.mean())) # !!! более быстрый аналог # result = data_df.fillna(grouped.transform("mean"))
Можно убедиться, что групповые средние не изменились в преобразованных данных и что преобразованные данные не содержат NA
.
>>> grouped_trans = transformed.groupby(key) # исходная группа >>> grouped.mean() # A B C # GR 0.020678 -0.049657 0.110486 # JP 0.104313 0.059318 -0.042290 # UK 0.010247 0.035147 -0.108634 # US -0.060774 -0.000013 0.053476 # трансформация не изменила группу >>> grouped_trans.mean() # A B C # GR 0.020678 -0.049657 0.110486 # JP 0.104313 0.059318 -0.042290 # UK 0.010247 0.035147 -0.108634 # US -0.060774 -0.000013 0.053476 # В оригинале отсутствуют некоторые данные >>> grouped.count() # A B C # GR 219 235 198 # JP 226 233 201 # UK 217 230 205 # US 243 253 212 # подсчитывает после преобразования >>> grouped_trans.count() # A B C # GR 245 245 245 # JP 248 248 248 # UK 242 242 242 # US 265 265 265 # количество значений, отличных от NA, равно размеру группы >>> grouped_trans.size() # GR 245 # JP 248 # UK 242 # US 265 # dtype: int64
GroupBy
В качестве методов для объекта группировки можно использовать методы .resample()
, .expanding()
и .rolling()
.
DataFrame.rolling()
в GroupBy
В приведенном ниже примере метод DataFrame.rolling()
будет применен к выборкам столбца B
, основанным на группах столбца A
с длиной окна в 4 наблюдения. Для лучшего понимания, что происходит, создадим новый DataFrame
:
>>> df_re = pd.DataFrame({"A": [1] * 10 + [5] * 10, "B": np.arange(20)}) >>> df_re # A B # 0 1 0 # 1 1 1 # 2 1 2 # 3 1 3 # 4 1 4 # .. .. .. # 15 5 15 # 16 5 16 # 17 5 17 # 18 5 18 # 19 5 19 # # [20 rows x 2 columns] >>> df_re.groupby("A").rolling(4).B.mean() # A # 1 0 NaN # 1 NaN # 2 NaN # 3 1.5 # 4 2.5 # ... # 5 15 13.5 # 16 14.5 # 17 15.5 # 18 16.5 # 19 17.5 # Name: B, Length: 20, dtype: float64
DataFrame.expanding()
в операциях GroupBy
Метод DataFrame.expanding()
будет накапливать заданную операцию (sum()
в примере) для всех членов каждой конкретной группы.
>>> df_re.groupby("A").expanding().sum() # B # A # 1 0 0.0 # 1 1.0 # 2 3.0 # 3 6.0 # 4 10.0 # ... ... # 5 15 75.0 # 16 91.0 # 17 108.0 # 18 126.0 # 19 145.0 # # [20 rows x 1 columns]
DataFrame.resample()
в операциях GroupBy
Предположим, что необходимо использовать метод DataFrame.resample()
для получения ежедневной частоты наблюдений для каждой группы DataFrame
, при этом восполним пропущенные значения с помощью метода .ffill()
. Для лучшего понимания, что происходит, создадим новый DataFrame
:
df_re = pd.DataFrame( { "date": pd.date_range(start="2024-01-01", periods=4, freq="W"), "group": [1, 1, 2, 2], "val": [5, 6, 7, 8], } ).set_index("date") # данные наблюдений с частотой 7 дней >>> df_re # group val # date # 2024-01-07 1 5 # 2024-01-14 1 6 # 2024-01-21 2 7 # 2024-01-28 2 8 # получаем частоту наблюдений 1 день и # восполняем пропущенные значения >>> df_re.groupby("group").resample("1D").ffill() # group val # group date # 1 2024-01-07 1 5 # 2024-01-08 1 5 # 2024-01-09 1 5 # 2024-01-10 1 5 # 2024-01-11 1 5 # 2024-01-12 1 5 # 2024-01-13 1 5 # 2024-01-14 1 6 # 2 2024-01-21 2 7 # 2024-01-22 2 7 # 2024-01-23 2 7 # 2024-01-24 2 7 # 2024-01-25 2 7 # 2024-01-26 2 7 # 2024-01-27 2 7 # 2024-01-28 2 8
GroupBy
, метод DataFrameGroupBy.filter()
Метод DataFrameGroupBy.filter()
может отфильтровывать либо целые группы, либо часть групп, либо и то, и другое. Возвращает отфильтрованную версию вызывающего объекта, включая столбцы группировки, если они указаны.
Метод DataFrameGroupBy.filter()
принимает определяемую пользователем функцию, которая при применении ко всей группе возвращает значение True
или False
. Результатом метода является подмножество групп, для которых определяемая пользователем функция вернула значение True
.
Фильтрация с предоставлением пользовательской функцией в качестве фильтра часто менее эффективна, чем использование встроенных методов
GroupBy
. Лучше разбить сложную операцию на цепочку операций, использующих встроенные методы.
Предположим, что необходимо взять только элементы, принадлежащие группам с суммой групп больше 2.
>>> sf = pd.Series([1, 1, 2, 3, 3, 3]) >>> sf.groupby(sf).filter(lambda x: x.sum() > 2) # 3 3 # 4 3 # 5 3 # dtype: int64
Еще одной полезной операцией является фильтрация элементов, принадлежащих группам, состоящим только из пары участников.
>>> dff = pd.DataFrame({"A": np.arange(8), "B": list("aabbbbcc")}) >>> dff.groupby("B").filter(lambda x: len(x) > 2) # A B # 2 2 b # 3 3 b # 4 4 b # 5 5 b
В качестве альтернативы, вместо отбрасывания ненужных групп, можно вернуть объекты с одинаковым индексом, где группы, не прошедшие фильтр, заполняются NaN
:
>>> dff.groupby("B").filter(lambda x: len(x) > 2, dropna=False) # A B # 0 NaN NaN # 1 NaN NaN # 2 2.0 b # 3 3.0 b # 4 4.0 b # 5 5.0 b # 6 NaN NaN # 7 NaN NaN
Для DataFrames
с несколькими столбцами фильтры должны явно указывать столбец в качестве критерия.
# добавим столбец `С` >>> dff["C"] = np.arange(8) >>> dff.groupby("B").filter(lambda x: len(x["C"]) > 2) # A B C # 2 2 b 2 # 3 3 b 3 # 4 4 b 4 # 5 5 b 5
DataFrame.apply()
и групповые операцииНекоторые операции с сгруппированными данными могут не вписываться в категории агрегирования, преобразования или фильтрации. Для этого можно использовать метод DataFrame.apply()
.
Предупреждение. Сгруппированные столбцы могут быть включены в выходные данные или нет в зависимости от того, что будет делать пользовательская функция (агрегировать, трансформировать или фильтровать), переданная в метод
DataFrame.apply()
. МетодDataFrame.apply()
пытается разумно угадать, как себя вести, но иногда он может ошибаться...Заметка. Все примеры, приведенные в этом разделе, могут быть вычислены более надежно и эффективно, используя другие методы
pandas
.
Для примеров используем ранее созданный DataFrame
:
>>> df # A B C D # 0 foo one -1.416678 1.366671 # 1 bar one -0.650714 -0.992193 # 2 foo two 0.410918 1.245727 # 3 bar three 0.467266 1.232117 # 4 foo two 0.548223 -0.065070 # 5 bar two 0.020170 -0.593099 # 6 foo one 0.440195 -0.141176 # 7 foo three -0.040652 -1.705956 >>> grouped = df.groupby("A") # можно было бы просто вызвать `grouped["C"].describe().T` >>> grouped["C"].apply(lambda x: x.describe()) # A # bar count 3.000000 # mean -0.054426 # std 0.562711 # min -0.650714 # 25% -0.315272 # 50% 0.020170 # 75% 0.243718 # max 0.467266 # foo count 5.000000 # mean -0.011599 # std 0.817179 # min -1.416678 # 25% -0.040652 # 50% 0.410918 # 75% 0.440195 # max 0.548223 # Name: C, dtype: float64
Размер возвращаемого результата также может измениться:
>>> grouped = df.groupby('A')['C'] # определим пользовательскую функцию # возвращающую `DataFrame` def f(group): return pd.DataFrame({'original': group, 'demeaned': group - group.mean()}) >>> grouped.apply(f) # original demeaned # A # bar 1 -0.650714 -0.596288 # 3 0.467266 0.521692 # 5 0.020170 0.074596 # foo 0 -1.416678 -1.405079 # 2 0.410918 0.422516 # 4 0.548223 0.559822 # 6 0.440195 0.451793 # 7 -0.040652 -0.029053
Метод Series.аpply()
может работать со значением, возвращаемым из применённой функции, которая сама по себе является серией, и, возможно, приводить результат к DataFrame
:
# определим пользовательскую функцию # возвращающую `Series` def f(x): return pd.Series([x, x ** 2], index=["x", "x^2"]) # создаем `Series` с которым будем работать >>> s = pd.Series(np.random.rand(5)) >>> s # 0 0.572127 # 1 0.100233 # 2 0.718959 # 3 0.261216 # 4 0.617886 # dtype: float64 >>> s.apply(f) # x x^2 # 0 0.572127 0.327329 # 1 0.100233 0.010047 # 2 0.718959 0.516902 # 3 0.261216 0.068234 # 4 0.617886 0.381783
Как и в случае с методом DataFrameGroupBy.agg()
, результирующий тип dtype
будет отражать возвращаемый пользовательской функцией тип. Если результаты из разных групп имеют разные dtypes
, то общий dtype
будет определен так же, как и построение DataFrame
.
group_keys
Чтобы указать, включаются ли сгруппированные столбцы в индексы, можно использовать аргумент group_keys
, который по умолчанию имеет значение True
.
# сравним вывод >>> df.groupby("A", group_keys=True).apply(lambda x: x) # A B C D # A # bar 1 bar one -0.650714 -0.992193 # 3 bar three 0.467266 1.232117 # 5 bar two 0.020170 -0.593099 # foo 0 foo one -1.416678 1.366671 # 2 foo two 0.410918 1.245727 # 4 foo two 0.548223 -0.065070 # 6 foo one 0.440195 -0.141176 # 7 foo three -0.040652 -1.705956 # с выводом >>> df.groupby("A", group_keys=False).apply(lambda x: x) # A B C D # 0 foo one -1.416678 1.366671 # 1 bar one -0.650714 -0.992193 # 2 foo two 0.410918 1.245727 # 3 bar three 0.467266 1.232117 # 4 foo two 0.548223 -0.065070 # 5 bar two 0.020170 -0.593099 # 6 foo one 0.440195 -0.141176 # 7 foo three -0.040652 -1.705956