Группировка: split-apply-combine
Под «группировкой» мы подразумеваем процесс, включающий один или несколько из следующих шагов:
Разделение данных на группы на основе определённых критериев.
Применение функции к каждой группе независимо.
Объединение результатов в структуру данных.
Из этих шагов этап разделения является наиболее простым. На этапе применения мы можем захотеть сделать одно из следующего:
-
Агрегирование: вычисление сводной статистики (или статистик) для каждой группы. Некоторые примеры:
Вычисление сумм или средних значений по группам.
Вычисление размеров/количества элементов в группах.
-
Преобразование: выполнение вычислений, специфичных для группы, и возвращение объекта с аналогичной индексацией. Некоторые примеры:
Нормализация данных (z-score) внутри группы.
Заполнение пропущенных значений (NaN) в группах значениями, полученными из каждой группы.
-
Фильтрация: отбрасывание некоторых групп на основе группового вычисления, которое возвращает значение True или False. Некоторые примеры:
Отбрасывание данных, принадлежащих группам с небольшим числом элементов.
Фильтрация данных на основе групповой суммы или среднего значения.
Многие из этих операций определены для объектов GroupBy. Эти операции похожи на операции в API агрегирования, API окон и API ресемплирования.
Возможен случай, когда данная операция не попадает ни в одну из этих категорий или представляет собой некоторое их сочетание. В таком случае возможно вычисление операции с помощью метода GroupBy apply. Этот метод проверит результаты этапа применения и попытается осмысленно объединить их в единый результат, если он не подходит ни к одной из трёх вышеперечисленных категорий.
Примечание
Операция, разбитая на несколько шагов с помощью встроенных операций GroupBy, будет более эффективной, чем использование метода apply с пользовательской функцией Python.
Название GroupBy должно быть знакомо тем, кто работал с инструментом на основе SQL (или itertools), в котором можно написать код, например:
SELECT Column1, Column2, mean(Column3), sum(Column4)
FROM SomeTable
GROUP BY Column1, Column2
Мы стремимся сделать такие операции естественными и простыми для выражения с помощью pandas. Мы рассмотрим каждый аспект функциональности GroupBy, а затем предоставим несколько нетривиальных примеров/случаев использования.
См. справочник по рецептам для изучения некоторых продвинутых стратегий.
Разбиение объекта на группы
Абстрактное определение группировки — это предоставление отображения меток к именам групп. Чтобы создать объект GroupBy (подробнее об объекте GroupBy позже), можно сделать следующее:
In [1]: 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"),
...: )
...:
In [2]: speeds
Out[2]:
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
In [3]: grouped = speeds.groupby("class")
In [4]: grouped = speeds.groupby(["class", "order"])
Отображение может быть задано множеством различных способов:
Функция Python, которая будет вызываться для каждой метки индекса.
Список или массив NumPy той же длины, что и индекс.
Словарь или
Series, предоставляющий отображениеlabel -> group name.Для объектов
DataFrame, строка, указывающая либо имя столбца, либо имя уровня индекса, которые будут использоваться для группировки.Список из любого из вышеперечисленных элементов.
Мы обобщаем объекты группировки, как ключи. Например, рассмотрим следующий DataFrame:
Примечание
Строка, переданная в groupby, может относиться либо к столбцу, либо к уровню индекса. Если строка соответствует и имени столбца, и имени уровня индекса, будет поднято исключение ValueError.
In [5]: 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),
...: }
...: )
...:
In [6]: df
Out[6]:
A B C D
0 foo one 0.469112 -0.861849
1 bar one -0.282863 -2.104569
2 foo two -1.509059 -0.494929
3 bar three -1.135632 1.071804
4 foo two 1.212112 0.721555
5 bar two -0.173215 -0.706771
6 foo one 0.119209 -1.039575
7 foo three -1.044236 0.271860
Для DataFrame мы получаем объект GroupBy, вызвав groupby(). Этот метод возвращает экземпляр pandas.api.typing.DataFrameGroupBy. Естественно, мы можем сгруппировать по столбцам A или B, или по обоим:
In [7]: grouped = df.groupby("A")
In [8]: grouped = df.groupby("B")
In [9]: grouped = df.groupby(["A", "B"])
Примечание
df.groupby('A') — это просто синтаксический сахар для df.groupby(df['A']).
Если у нас также есть MultiIndex в столбцах A и B, мы можем сгруппировать по всем столбцам, кроме указанного:
In [10]: df2 = df.set_index(["A", "B"])
In [11]: grouped = df2.groupby(level=df2.index.names.difference(["B"]))
In [12]: grouped.sum()
Out[12]:
C D
A
bar -1.591710 -1.739537
foo -0.752861 -1.402938
Вышеупомянутый GroupBy разделит DataFrame по его индексу (строкам). Чтобы разделить по столбцам, сначала выполните транспонирование:
In [13]: def get_letter_type(letter):
....: if letter.lower() in 'aeiou':
....: return 'vowel'
....: else:
....: return 'consonant'
....:
In [14]: grouped = df.T.groupby(get_letter_type)
Объекты pandas Index поддерживают дубликаты значений. Если в операции groupby используется не уникальный индекс в качестве ключа группы, все значения для одного значения индекса будут рассматриваться как принадлежащие одной группе, и поэтому вывод функций агрегации будет содержать только уникальные значения индекса:
In [15]: index = [1, 2, 3, 1, 2, 3]
In [16]: s = pd.Series([1, 2, 3, 10, 20, 30], index=index)
In [17]: s
Out[17]:
1 1
2 2
3 3
1 10
2 20
3 30
dtype: int64
In [18]: grouped = s.groupby(level=0)
In [19]: grouped.first()
Out[19]:
1 1
2 2
3 3
dtype: int64
In [20]: grouped.last()
Out[20]:
1 10
2 20
3 30
dtype: int64
In [21]: grouped.sum()
Out[21]:
1 11
2 22
3 33
dtype: int64
Обратите внимание, что разбиение не происходит до тех пор, пока это не потребуется. Создание объекта GroupBy только проверяет, что вы передали допустимое отображение.
Примечание
Многие виды сложных манипуляций с данными могут быть выражены с помощью операций GroupBy (хотя нельзя гарантировать, что это будет наиболее эффективная реализация). Вы можете проявить достаточно творческий подход к функциям сопоставления меток.
Сортировка GroupBy
По умолчанию ключи групп сортируются во время операции groupby. Однако вы можете передать sort=False для потенциального ускорения. С sort=False порядок среди ключей группы соответствует порядку появления ключей в исходном DataFrame:
In [22]: df2 = pd.DataFrame({"X": ["B", "B", "A", "A"], "Y": [1, 2, 3, 4]})
In [23]: df2.groupby(["X"]).sum()
Out[23]:
Y
X
A 7
B 3
In [24]: df2.groupby(["X"], sort=False).sum()
Out[24]:
Y
X
B 3
A 7
Обратите внимание, что groupby сохранит порядок сортировки наблюдений внутри каждой группы. Например, группы, созданные в groupby() ниже, расположены в порядке их появления в исходном DataFrame:
In [25]: df3 = pd.DataFrame({"X": ["A", "B", "A", "B"], "Y": [1, 4, 3, 2]})
In [26]: df3.groupby("X").get_group("A")
Out[26]:
X Y
0 A 1
2 A 3
In [27]: df3.groupby(["X"]).get_group(("B",))
Out[27]:
X Y
1 B 4
3 B 2
GroupBy dropna
По умолчанию значения NA исключаются из ключей групп во время операции groupby. Однако в случае, если вы хотите включить значения NA в ключи групп, можно передать dropna=False, чтобы добиться этого.
In [28]: df_list = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]]
In [29]: df_dropna = pd.DataFrame(df_list, columns=["a", "b", "c"])
In [30]: df_dropna
Out[30]:
a b c
0 1 2.0 3
1 1 NaN 4
2 2 1.0 3
3 1 2.0 2
# Default ``dropna`` is set to True, which will exclude NaNs in keys
In [31]: df_dropna.groupby(by=["b"], dropna=True).sum()
Out[31]:
a c
b
1.0 2 3
2.0 2 5
# In order to allow NaN in keys, set ``dropna`` to False
In [32]: df_dropna.groupby(by=["b"], dropna=False).sum()
Out[32]:
a c
b
1.0 2 3
2.0 2 5
NaN 1 4
Значение параметра dropna по умолчанию — True, что означает, что NA не включаются в ключи групп.
Атрибуты объекта GroupBy
Атрибут groups — это словарь, ключи которого — вычисленные уникальные группы, а соответствующие значения — метки осей, принадлежащие каждой группе. В приведенном выше примере у нас есть:
In [33]: df.groupby("A").groups
Out[33]: {'bar': [1, 3, 5], 'foo': [0, 2, 4, 6, 7]}
In [34]: df.T.groupby(get_letter_type).groups
Out[34]: {'consonant': ['B', 'C', 'D'], 'vowel': ['A']}
Вызов стандартной функции Python len для объекта GroupBy возвращает количество групп, что равно длине словаря groups:
In [35]: grouped = df.groupby(["A", "B"])
In [36]: grouped.groups
Out[36]: {('bar', 'one'): [1], ('bar', 'three'): [3], ('bar', 'two'): [5], ('foo', 'one'): [0, 6], ('foo', 'three'): [7], ('foo', 'two'): [2, 4]}
In [37]: len(grouped)
Out[37]: 6
GroupBy будет автоматически заполнять имена столбцов, операции GroupBy и другие атрибуты:
In [38]: n = 10
In [39]: weight = np.random.normal(166, 20, size=n)
In [40]: height = np.random.normal(60, 10, size=n)
In [41]: time = pd.date_range("1/1/2000", periods=n)
In [42]: gender = np.random.choice(["male", "female"], size=n)
In [43]: df = pd.DataFrame(
....: {"height": height, "weight": weight, "gender": gender}, index=time
....: )
....:
In [44]: df
Out[44]:
height weight gender
2000-01-01 42.849980 157.500553 male
2000-01-02 49.607315 177.340407 male
2000-01-03 56.293531 171.524640 male
2000-01-04 48.421077 144.251986 female
2000-01-05 46.556882 152.526206 male
2000-01-06 68.448851 168.272968 female
2000-01-07 70.757698 136.431469 male
2000-01-08 58.909500 176.499753 female
2000-01-09 76.435631 174.094104 female
2000-01-10 45.306120 177.540920 male
In [45]: gb = df.groupby("gender")
In [46]: gb.<TAB> # noqa: E225, E999
gb.agg gb.boxplot gb.cummin gb.describe gb.filter gb.get_group gb.height gb.last gb.median gb.ngroups gb.plot gb.rank gb.std gb.transform
gb.aggregate gb.count gb.cumprod gb.dtype gb.first gb.groups gb.hist gb.max gb.min gb.nth gb.prod gb.resample gb.sum gb.var
gb.apply gb.cummax gb.cumsum gb.fillna gb.gender gb.head gb.indices gb.mean gb.name gb.ohlc gb.quantile gb.size gb.tail gb.weight
GroupBy с MultiIndex
С данными с иерархическим индексом вполне естественно сгруппировать по одному из уровней иерархии.
Давайте создадим Series с двух-уровневым MultiIndex.
In [47]: arrays = [
....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
....: ["one", "two", "one", "two", "one", "two", "one", "two"],
....: ]
....:
In [48]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"])
In [49]: s = pd.Series(np.random.randn(8), index=index)
In [50]: s
Out[50]:
first second
bar one -0.919854
two -0.042379
baz one 1.247642
two -0.009920
foo one 0.290213
two 0.495767
qux one 0.362949
two 1.548106
dtype: float64
Затем мы можем сгруппировать по одному из уровней в s.
In [51]: grouped = s.groupby(level=0)
In [52]: grouped.sum()
Out[52]:
first
bar -0.962232
baz 1.237723
foo 0.785980
qux 1.911055
dtype: float64
Если у MultiIndex заданы имена, их можно передать вместо номера уровня:
In [53]: s.groupby(level="second").sum()
Out[53]:
second
one 0.980950
two 1.991575
dtype: float64
Поддерживается группировка по нескольким уровням.
In [54]: 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"],
....: ]
....:
In [55]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second", "third"])
In [56]: s = pd.Series(np.random.randn(8), index=index)
In [57]: s
Out[57]:
first second third
bar doo one -1.131345
two -0.089329
baz bee one 0.337863
two -0.945867
foo bop one -0.932132
two 1.956030
qux bop one 0.017587
two -0.016692
dtype: float64
In [58]: s.groupby(level=["first", "second"]).sum()
Out[58]:
first second
bar doo -1.220674
baz bee -0.608004
foo bop 1.023898
qux bop 0.000895
dtype: float64
Имена уровней индекса могут быть переданы в качестве ключей.
In [59]: s.groupby(["first", "second"]).sum()
Out[59]:
first second
bar doo -1.220674
baz bee -0.608004
foo bop 1.023898
qux bop 0.000895
dtype: float64
Подробнее о функции sum и агрегировании позже.
Группировка DataFrame с уровнями индекса и столбцами
DataFrame можно сгруппировать по комбинации столбцов и уровней индексов. Вы можете указать имена столбцов и уровней индекса или использовать Grouper.
Давайте сначала создадим DataFrame с MultiIndex:
In [60]: arrays = [
....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
....: ["one", "two", "one", "two", "one", "two", "one", "two"],
....: ]
....:
In [61]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"])
In [62]: df = pd.DataFrame({"A": [1, 1, 1, 1, 2, 2, 3, 3], "B": np.arange(8)}, index=index)
In [63]: df
Out[63]:
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.
In [64]: df.groupby([pd.Grouper(level=1), "A"]).sum()
Out[64]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
Уровни индекса также можно указать по имени.
In [65]: df.groupby([pd.Grouper(level="second"), "A"]).sum()
Out[65]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
Имена уровней индекса могут быть указаны непосредственно в качестве ключей для groupby.
In [66]: df.groupby(["second", "A"]).sum()
Out[66]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
Выбор столбцов DataFrame в GroupBy
После создания объекта GroupBy из DataFrame вы, возможно, захотите выполнить разные действия для каждого столбца. Таким образом, используя [] для объекта GroupBy, аналогично тому, как вы получаете столбец из DataFrame, вы можете сделать следующее:
In [67]: 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),
....: }
....: )
....:
In [68]: df
Out[68]:
A B C D
0 foo one -0.575247 1.346061
1 bar one 0.254161 1.511763
2 foo two -1.143704 1.627081
3 bar three 0.215897 -0.990582
4 foo two 1.193555 -0.441652
5 bar two -0.077118 1.211526
6 foo one -0.408530 0.268520
7 foo three -0.862495 0.024580
In [69]: grouped = df.groupby(["A"])
In [70]: grouped_C = grouped["C"]
In [71]: grouped_D = grouped["D"]
Это в основном синтаксический сахар для альтернативного, гораздо более громоздкого варианта:
In [72]: df["C"].groupby(df["A"])
Out[72]: <pandas.core.groupby.generic.SeriesGroupBy object at 0x7ff2cef1c730>
Кроме того, этот метод позволяет избежать повторного вычисления внутренней информации группировки, полученной из переданного ключа.
Вы также можете включить группирующие столбцы, если хотите работать с ними.
In [73]: grouped[["A", "B"]].sum()
Out[73]:
A B
A
bar barbarbar onethreetwo
foo foofoofoofoofoo onetwotwoonethree
Итерация по группам
С объектом GroupBy итерация по сгруппированным данным очень естественна и работает аналогично itertools.groupby():
In [74]: grouped = df.groupby('A')
In [75]: 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
В случае группировки по нескольким ключам имя группы будет кортежем:
In [76]: for name, group in df.groupby(['A', 'B']):
....: print(name)
....: print(group)
....:
('bar', 'one')
A B C D
1 bar one 0.254161 1.511763
('bar', 'three')
A B C D
3 bar three 0.215897 -0.990582
('bar', 'two')
A B C D
5 bar two -0.077118 1.211526
('foo', 'one')
A B C D
0 foo one -0.575247 1.346061
6 foo one -0.408530 0.268520
('foo', 'three')
A B C D
7 foo three -0.862495 0.02458
('foo', 'two')
A B C D
2 foo two -1.143704 1.627081
4 foo two 1.193555 -0.441652
См. Итерация по группам.
Выбор группы
Отдельную группу можно выбрать, используя DataFrameGroupBy.get_group():
In [77]: grouped.get_group("bar")
Out[77]:
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
Или для объекта, сгруппированного по нескольким столбцам:
In [78]: df.groupby(["A", "B"]).get_group(("bar", "one"))
Out[78]:
A B C D
1 bar one 0.254161 1.511763
Агрегирование
Агрегирование — это операция GroupBy, которая уменьшает размерность объекта группировки. Результат агрегирования представляет собой или, по крайней мере, обрабатывается как скалярное значение для каждого столбца в группе. Например, вычисление суммы каждого столбца в группе значений.
In [79]: 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],
....: }
....: )
....:
In [80]: animals
Out[80]:
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
In [81]: animals.groupby("kind").sum()
Out[81]:
height weight
kind
cat 18.6 17.8
dog 40.0 205.5
В результате ключи групп по умолчанию отображаются в индексе. Их можно вместо этого включить в столбцы, передав as_index=False.
In [82]: animals.groupby("kind", as_index=False).sum()
Out[82]:
kind height weight
0 cat 18.6 17.8
1 dog 40.0 205.5
Встроенные методы агрегирования
Многие распространенные агрегации встроены в объекты GroupBy в качестве методов. Из перечисленных ниже методов те, у которых есть * не имеют эффективной реализации, специфичной для GroupBy.
Метод | Описание |
|---|---|
Вычисление того, истинно ли хоть одно из значений в группах | |
Вычисление того, истинно ли все значения в группах | |
Вычисление количества значений, отличных от NA, в группах | |
| Вычисление ковариации групп |
Вычисление первого значения в каждой группе | |
Вычисление индекса максимального значения в каждой группе | |
Вычисление индекса минимального значения в каждой группе | |
Вычисление последнего значения в каждой группе | |
Вычисление максимального значения в каждой группе | |
Вычисление среднего значения каждой группы | |
Вычисление медианы каждой группы | |
Вычисление минимального значения в каждой группе | |
Вычисление количества уникальных значений в каждой группе | |
Вычисление произведения значений в каждой группе | |
Вычисление заданного квантиля значений в каждой группе | |
Вычисление стандартной ошибки среднего значения значений в каждой группе | |
Вычисление количества значений в каждой группе | |
| Вычисление асимметрии значений в каждой группе |
Вычисление стандартного отклонения значений в каждой группе | |
Вычисление суммы значений в каждой группе | |
Вычисление дисперсии значений в каждой группе |
Примеры:
In [83]: df.groupby("A")[["C", "D"]].max()
Out[83]:
C D
A
bar 0.254161 1.511763
foo 1.193555 1.627081
In [84]: df.groupby(["A", "B"]).mean()
Out[84]:
C D
A B
bar one 0.254161 1.511763
three 0.215897 -0.990582
two -0.077118 1.211526
foo one -0.491888 0.807291
three -0.862495 0.024580
two 0.024925 0.592714
Другой пример агрегации — вычисление размера каждой группы. Это включено в GroupBy в качестве size метода. Он возвращает Series, индекс которого состоит из имен групп, а значения — из размеров каждой группы.
In [85]: grouped = df.groupby(["A", "B"])
In [86]: grouped.size()
Out[86]:
A B
bar one 1
three 1
two 1
foo one 2
three 1
two 2
dtype: int64
Хотя метод DataFrameGroupBy.describe() сам по себе не является редуктором, он может быть использован для удобного получения набора сводных статистик о каждой из групп.
In [87]: grouped.describe()
Out[87]:
C ... D
count mean std ... 50% 75% max
A B ...
bar one 1.0 0.254161 NaN ... 1.511763 1.511763 1.511763
three 1.0 0.215897 NaN ... -0.990582 -0.990582 -0.990582
two 1.0 -0.077118 NaN ... 1.211526 1.211526 1.211526
foo one 2.0 -0.491888 0.117887 ... 0.807291 1.076676 1.346061
three 1.0 -0.862495 NaN ... 0.024580 0.024580 0.024580
two 2.0 0.024925 1.652692 ... 0.592714 1.109898 1.627081
[6 rows x 16 columns]
Другой пример агрегации — вычисление количества уникальных значений каждой группы. Это аналогично функции DataFrameGroupBy.value_counts(), за исключением того, что подсчитываются только уникальные значения.
In [88]: ll = [['foo', 1], ['foo', 2], ['foo', 2], ['bar', 1], ['bar', 1]]
In [89]: df4 = pd.DataFrame(ll, columns=["A", "B"])
In [90]: df4
Out[90]:
A B
0 foo 1
1 foo 2
2 foo 2
3 bar 1
4 bar 1
In [91]: df4.groupby("A")["B"].nunique()
Out[91]:
A
bar 1
foo 2
Name: B, dtype: int64
Примечание
Функции агрегирования не будут возвращать группы, по которым вы агрегируете, в качестве именованных столбцов, по умолчанию. Группированные столбцы будут индексами возвращаемого объекта.
Передача as_index=False будет возвращать группы, по которым вы агрегируете, в качестве именованных столбцов, независимо от того, являются ли они именованными индексами или столбцами в исходных данных.
Метод aggregate()
Примечание
Метод aggregate() может принимать множество различных типов ввода. В этом разделе подробно описывается использование строковых псевдонимов для различных методов GroupBy; другие вводы подробно описаны в следующих разделах.
Любой метод сокращения, реализованный в pandas, может быть передан в виде строки методу aggregate(). Пользователям рекомендуется использовать сокращенную запись agg. Она будет работать так, как будто вызван соответствующий метод.
In [92]: grouped = df.groupby("A")
In [93]: grouped[["C", "D"]].aggregate("sum")
Out[93]:
C D
A
bar 0.392940 1.732707
foo -1.796421 2.824590
In [94]: grouped = df.groupby(["A", "B"])
In [95]: grouped.agg("sum")
Out[95]:
C D
A B
bar one 0.254161 1.511763
three 0.215897 -0.990582
two -0.077118 1.211526
foo one -0.983776 1.614581
three -0.862495 0.024580
two 0.049851 1.185429
Результат агрегирования будет иметь имена групп в качестве нового индекса. В случае нескольких ключей, по умолчанию результат — Многоуровневый индекс. Как упоминалось выше, это можно изменить, используя опцию as_index:
In [96]: grouped = df.groupby(["A", "B"], as_index=False)
In [97]: grouped.agg("sum")
Out[97]:
A B C D
0 bar one 0.254161 1.511763
1 bar three 0.215897 -0.990582
2 bar two -0.077118 1.211526
3 foo one -0.983776 1.614581
4 foo three -0.862495 0.024580
5 foo two 0.049851 1.185429
In [98]: df.groupby("A", as_index=False)[["C", "D"]].agg("sum")
Out[98]:
A C D
0 bar 0.392940 1.732707
1 foo -1.796421 2.824590
Обратите внимание, что можно использовать функцию DataFrame DataFrame.reset_index() для достижения того же результата, так как имена столбцов хранятся в результирующем MultiIndex, хотя это создаст дополнительную копию.
In [99]: df.groupby(["A", "B"]).agg("sum").reset_index()
Out[99]:
A B C D
0 bar one 0.254161 1.511763
1 bar three 0.215897 -0.990582
2 bar two -0.077118 1.211526
3 foo one -0.983776 1.614581
4 foo three -0.862495 0.024580
5 foo two 0.049851 1.185429
Агрегирование с пользовательскими функциями
Пользователи также могут предоставить свои пользовательские функции (UDFs) для кастомного агрегирования.
Предупреждение
При агрегировании с помощью UDF, UDF не должна изменять предоставленный Series. См. Изменение с помощью пользовательских функций (UDF) для получения дополнительной информации.
Примечание
Агрегирование с помощью UDF часто менее производительно, чем использование встроенных методов pandas для GroupBy. Подумайте о том, чтобы разбить сложную операцию на цепочку операций, использующих встроенные методы.
In [100]: animals
Out[100]:
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
In [101]: animals.groupby("kind")[["height"]].agg(lambda x: set(x))
Out[101]:
height
kind
cat {9.1, 9.5}
dog {34.0, 6.0}
Результирующий тип данных будет отражать тип агрегирующей функции. Если результаты из разных групп имеют разные типы данных, то общий тип данных будет определён таким же образом, как и при построении DataFrame.
In [102]: animals.groupby("kind")[["height"]].agg(lambda x: x.astype(int).sum())
Out[102]:
height
kind
cat 18
dog 40
Применение нескольких функций одновременно
На сгруппированном Series, можно передать список или словарь функций методу SeriesGroupBy.agg(), получив на выходе DataFrame:
In [103]: grouped = df.groupby("A")
In [104]: grouped["C"].agg(["sum", "mean", "std"])
Out[104]:
sum mean std
A
bar 0.392940 0.130980 0.181231
foo -1.796421 -0.359284 0.912265
На сгруппированном DataFrame, можно передать список функций методу DataFrameGroupBy.agg() для агрегирования каждого столбца, что даёт агрегированный результат с иерархическим индексом столбцов:
In [105]: grouped[["C", "D"]].agg(["sum", "mean", "std"])
Out[105]:
C D
sum mean std sum mean std
A
bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330
foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
Результирующие агрегации будут именоваться по именам самих функций. Если вам нужно переименование, можно добавить цепочку операций для Series таким образом:
In [106]: (
.....: grouped["C"]
.....: .agg(["sum", "mean", "std"])
.....: .rename(columns={"sum": "foo", "mean": "bar", "std": "baz"})
.....: )
.....:
Out[106]:
foo bar baz
A
bar 0.392940 0.130980 0.181231
foo -1.796421 -0.359284 0.912265
Для сгруппированного DataFrame, переименование выполняется аналогично:
In [107]: (
.....: grouped[["C", "D"]].agg(["sum", "mean", "std"]).rename(
.....: columns={"sum": "foo", "mean": "bar", "std": "baz"}
.....: )
.....: )
.....:
Out[107]:
C D
foo bar baz foo bar baz
A
bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330
foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
Примечание
В общем случае имена выходных столбцов должны быть уникальными, но pandas позволит вам применять ту же функцию (или две функции с одинаковым именем) к одному и тому же столбцу.
In [108]: grouped["C"].agg(["sum", "sum"])
Out[108]:
sum sum
A
bar 0.392940 0.392940
foo -1.796421 -1.796421
pandas также позволяет указывать несколько лямбда-функций. В этом случае pandas будет изменять имена (безымянных) лямбда-функций, добавляя _<i> к каждой последующей лямбда-функции.
In [109]: grouped["C"].agg([lambda x: x.max() - x.min(), lambda x: x.median() - x.mean()])
Out[109]:
<lambda_0> <lambda_1>
A
bar 0.331279 0.084917
foo 2.337259 -0.215962
Именованное агрегирование
Для поддержки столбцевого агрегирования с контролем имён выходных столбцов, pandas принимает специальный синтаксис в DataFrameGroupBy.agg() и SeriesGroupBy.agg(), известный как “именованное агрегирование”, где
Ключевые слова — это имена выходных столбцов
Значения — это кортежи, первый элемент которых — столбец для выбора, а второй — агрегирование, которое нужно применить к этому столбцу. pandas предоставляет именованный кортеж
NamedAggс полями['column', 'aggfunc']для большей ясности. Как обычно, агрегирование может быть вызываемой функцией или строковым псевдонимом.
In [110]: animals
Out[110]:
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
In [111]: 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"),
.....: )
.....:
Out[111]:
min_height max_height average_weight
kind
cat 9.1 9.5 8.90
dog 6.0 34.0 102.75
NamedAgg — это просто namedtuple. Также разрешены обычные кортежи.
In [112]: animals.groupby("kind").agg(
.....: min_height=("height", "min"),
.....: max_height=("height", "max"),
.....: average_weight=("weight", "mean"),
.....: )
.....:
Out[112]:
min_height max_height average_weight
kind
cat 9.1 9.5 8.90
dog 6.0 34.0 102.75
Если имена столбцов, которые вы хотите использовать, не являются допустимыми ключевыми словами Python, создайте словарь и распакуйте аргументы ключевых слов
In [113]: animals.groupby("kind").agg(
.....: **{
.....: "total weight": pd.NamedAgg(column="weight", aggfunc="sum")
.....: }
.....: )
.....:
Out[113]:
total weight
kind
cat 17.8
dog 205.5
При использовании именованного агрегирования дополнительные аргументы ключевых слов не передаются агрегирующим функциям; должны быть переданы только пары (column, aggfunc) как **kwargs. Если ваши агрегирующие функции требуют дополнительных аргументов, примените их частично с помощью functools.partial().
Именованное агрегирование также применимо для агрегирования сгруппированных Series. В этом случае нет выбора столбцов, поэтому значения — это просто функции.
In [114]: animals.groupby("kind").height.agg(
.....: min_height="min",
.....: max_height="max",
.....: )
.....:
Out[114]:
min_height max_height
kind
cat 9.1 9.5
dog 6.0 34.0
Применение разных функций к столбцам DataFrame
Передав словарь методу aggregate, можно применить разное агрегирование к столбцам DataFrame:
In [115]: grouped.agg({"C": "sum", "D": lambda x: np.std(x, ddof=1)})
Out[115]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
Имена функций также могут быть строками. Для того чтобы строка была допустимой, она должна быть реализована для GroupBy:
In [116]: grouped.agg({"C": "sum", "D": "std"})
Out[116]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
Преобразование
Преобразование — это операция GroupBy, результат которой индексируется так же, как и исходный сгруппированный объект. Примеры включают cumsum() и diff().
In [117]: speeds
Out[117]:
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
In [118]: grouped = speeds.groupby("class")["max_speed"]
In [119]: grouped.cumsum()
Out[119]:
falcon 389.0
parrot 413.0
lion 80.2
monkey NaN
leopard 138.2
Name: max_speed, dtype: float64
In [120]: grouped.diff()
Out[120]:
falcon NaN
parrot -365.0
lion NaN
monkey NaN
leopard NaN
Name: max_speed, dtype: float64
В отличие от агрегаций, группировки, используемые для разделения исходного объекта, не включаются в результат.
Примечание
Поскольку преобразования не включают группировки, используемые для разделения результата, аргументы as_index и sort в DataFrame.groupby() и Series.groupby() не влияют на результат.
Частое применение преобразования — добавление результата обратно в исходную DataFrame.
In [121]: result = speeds.copy()
In [122]: result["cumsum"] = grouped.cumsum()
In [123]: result["diff"] = grouped.diff()
In [124]: result
Out[124]:
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 действуют как преобразования.
Метод | Описание |
|---|---|
Заполнить пропущенные значения (NA) в каждой группе с конца | |
Вычислить кумулятивный счет в каждой группе | |
Вычислить кумулятивный максимум в каждой группе | |
Вычислить кумулятивный минимум в каждой группе | |
Вычислить кумулятивное произведение в каждой группе | |
Вычислить кумулятивную сумму в каждой группе | |
Вычислить разницу между соседними значениями в каждой группе | |
Заполнить пропущенные значения (NA) в каждой группе с начала | |
Вычислить процентное изменение между соседними значениями в каждой группе | |
Вычислить ранг каждого значения в каждой группе | |
Сдвинуть значения вверх или вниз в каждой группе |
Кроме того, передача любого встроенного метода агрегации в виде строки в transform() (см. следующий раздел) транслирует результат по группам, производя преобразованный результат. Если у метода агрегации есть эффективная реализация, это также будет производительным.
Метод transform()
Подобно методу метода агрегации, метод transform() может принимать строковые алиасы встроенных методов преобразования в предыдущем разделе. Он также может принимать строковые алиасы встроенных методов агрегации. При предоставлении метода агрегации результат транслируется по группам.
In [125]: speeds
Out[125]:
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
In [126]: grouped = speeds.groupby("class")[["max_speed"]]
In [127]: grouped.transform("cumsum")
Out[127]:
max_speed
falcon 389.0
parrot 413.0
lion 80.2
monkey NaN
leopard 138.2
In [128]: grouped.transform("sum")
Out[128]:
max_speed
falcon 413.0
parrot 413.0
lion 138.2
monkey 138.2
leopard 138.2
Помимо строковых алиасов, метод transform() также может принимать пользовательские функции (UDFs). Пользовательская функция должна:
Возвращать результат, который имеет тот же размер, что и фрагмент группы, или может быть транслирован до размера фрагмента группы (например, скаляр,
grouped.transform(lambda x: x.iloc[-1])).Осуществлять операции по столбцам в фрагменте группы. Преобразование применяется к первому фрагменту группы с использованием chunk.apply.
Не выполнять операции на месте на фрагменте группы. Фрагменты группы должны рассматриваться как неизменяемые, а изменения в фрагменте группы могут привести к непредвиденным результатам. См. Изменение с помощью пользовательских функций (UDF) для получения дополнительной информации.
(Необязательно) действовать сразу со всеми столбцами всего фрагмента группы. Если это поддерживается, используется быстрый путь, начиная со второго фрагмента.
Примечание
Преобразование путем предоставления transform с пользовательской функцией часто менее производительно, чем использование встроенных методов GroupBy. Подумайте о том, чтобы разбить сложную операцию на цепочку операций, использующих встроенные методы.
Все примеры в этом разделе можно сделать более производительными, вызвав встроенные методы вместо использования UDF. См. ниже для примеров.
Изменено в версии 2.0.0: При использовании .transform на сгруппированной DataFrame и если функция преобразования возвращает DataFrame, pandas теперь выравнивает индекс результата с индексом входных данных. Вы можете вызвать .to_numpy() в функции преобразования, чтобы избежать выравнивания.
Аналогично методу aggregate(), результирующий тип данных будет отражать тип данных функции преобразования. Если результаты из разных групп имеют разные типы данных, общий тип данных будет определен так же, как и при построении DataFrame.
Предположим, мы хотим стандартизировать данные в каждой группе:
In [129]: index = pd.date_range("10/1/1999", periods=1100)
In [130]: ts = pd.Series(np.random.normal(0.5, 2, 1100), index)
In [131]: ts = ts.rolling(window=100, min_periods=100).mean().dropna()
In [132]: ts.head()
Out[132]:
2000-01-08 0.779333
2000-01-09 0.778852
2000-01-10 0.786476
2000-01-11 0.782797
2000-01-12 0.798110
Freq: D, dtype: float64
In [133]: ts.tail()
Out[133]:
2002-09-30 0.660294
2002-10-01 0.631095
2002-10-02 0.673601
2002-10-03 0.709213
2002-10-04 0.719369
Freq: D, dtype: float64
In [134]: transformed = ts.groupby(lambda x: x.year).transform(
.....: lambda x: (x - x.mean()) / x.std()
.....: )
.....:
Мы ожидаем, что результат будет иметь среднее значение 0 и стандартное отклонение 1 в каждой группе (с точностью до погрешности с плавающей запятой), что легко проверить:
# Original Data
In [135]: grouped = ts.groupby(lambda x: x.year)
In [136]: grouped.mean()
Out[136]:
2000 0.442441
2001 0.526246
2002 0.459365
dtype: float64
In [137]: grouped.std()
Out[137]:
2000 0.131752
2001 0.210945
2002 0.128753
dtype: float64
# Transformed Data
In [138]: grouped_trans = transformed.groupby(lambda x: x.year)
In [139]: grouped_trans.mean()
Out[139]:
2000 -4.870756e-16
2001 -1.545187e-16
2002 4.136282e-16
dtype: float64
In [140]: grouped_trans.std()
Out[140]:
2000 1.0
2001 1.0
2002 1.0
dtype: float64
Мы также можем визуально сравнить исходные и преобразованные наборы данных.
In [141]: compare = pd.DataFrame({"Original": ts, "Transformed": transformed})
In [142]: compare.plot()
Out[142]: <Axes: >
Функции преобразования, которые возвращают результаты с меньшей размерностью, транслируются для соответствия форме входного массива.
In [143]: ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min())
Out[143]:
2000-01-08 0.623893
2000-01-09 0.623893
2000-01-10 0.623893
2000-01-11 0.623893
2000-01-12 0.623893
...
2002-09-30 0.558275
2002-10-01 0.558275
2002-10-02 0.558275
2002-10-03 0.558275
2002-10-04 0.558275
Freq: D, Length: 1001, dtype: float64
Другое частое преобразование данных — замена пропущенных данных групповым средним.
In [144]: cols = ["A", "B", "C"]
In [145]: values = np.random.randn(1000, 3)
In [146]: values[np.random.randint(0, 1000, 100), 0] = np.nan
In [147]: values[np.random.randint(0, 1000, 50), 1] = np.nan
In [148]: values[np.random.randint(0, 1000, 200), 2] = np.nan
In [149]: data_df = pd.DataFrame(values, columns=cols)
In [150]: data_df
Out[150]:
A B C
0 1.539708 -1.166480 0.533026
1 1.302092 -0.505754 NaN
2 -0.371983 1.104803 -0.651520
3 -1.309622 1.118697 -1.161657
4 -1.924296 0.396437 0.812436
.. ... ... ...
995 -0.093110 0.683847 -0.774753
996 -0.185043 1.438572 NaN
997 -0.394469 -0.642343 0.011374
998 -1.174126 1.857148 NaN
999 0.234564 0.517098 0.393534
[1000 rows x 3 columns]
In [151]: countries = np.array(["US", "UK", "GR", "JP"])
In [152]: key = countries[np.random.randint(0, 4, 1000)]
In [153]: grouped = data_df.groupby(key)
# Non-NA count in each group
In [154]: grouped.count()
Out[154]:
A B C
GR 209 217 189
JP 240 255 217
UK 216 231 193
US 239 250 217
In [155]: transformed = grouped.transform(lambda x: x.fillna(x.mean()))
Мы можем проверить, что групповые средние не изменились в преобразованных данных и что преобразованные данные не содержат значений NA.
In [156]: grouped_trans = transformed.groupby(key)
In [157]: grouped.mean() # original group means
Out[157]:
A B C
GR -0.098371 -0.015420 0.068053
JP 0.069025 0.023100 -0.077324
UK 0.034069 -0.052580 -0.116525
US 0.058664 -0.020399 0.028603
In [158]: grouped_trans.mean() # transformation did not change group means
Out[158]:
A B C
GR -0.098371 -0.015420 0.068053
JP 0.069025 0.023100 -0.077324
UK 0.034069 -0.052580 -0.116525
US 0.058664 -0.020399 0.028603
In [159]: grouped.count() # original has some missing data points
Out[159]:
A B C
GR 209 217 189
JP 240 255 217
UK 216 231 193
US 239 250 217
In [160]: grouped_trans.count() # counts after transformation
Out[160]:
A B C
GR 228 228 228
JP 267 267 267
UK 247 247 247
US 258 258 258
In [161]: grouped_trans.size() # Verify non-NA count equals group size
Out[161]:
GR 228
JP 267
UK 247
US 258
dtype: int64
Как упоминалось в примечании выше, каждый из примеров в этом разделе можно вычислить более эффективно, используя встроенные методы. В приведенном ниже коде неэффективный способ с использованием UDF прокомментирован, а ниже показан более быстрый вариант.
# result = ts.groupby(lambda x: x.year).transform(
# lambda x: (x - x.mean()) / x.std()
# )
In [162]: grouped = ts.groupby(lambda x: x.year)
In [163]: result = (ts - grouped.transform("mean")) / grouped.transform("std")
# result = ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min())
In [164]: grouped = ts.groupby(lambda x: x.year)
In [165]: result = grouped.transform("max") - grouped.transform("min")
# grouped = data_df.groupby(key)
# result = grouped.transform(lambda x: x.fillna(x.mean()))
In [166]: grouped = data_df.groupby(key)
In [167]: result = data_df.fillna(grouped.transform("mean"))
Операции с окном и ресемплированием
Можно использовать resample(), expanding() и rolling() в качестве методов для groupbys.
В примере ниже метод rolling() будет применён к образцам столбца B, сгруппированным по столбцу A.
In [168]: df_re = pd.DataFrame({"A": [1] * 10 + [5] * 10, "B": np.arange(20)})
In [169]: df_re
Out[169]:
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]
In [170]: df_re.groupby("A").rolling(4).B.mean()
Out[170]:
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
Метод expanding() будет накапливать заданную операцию (sum() в примере) для всех элементов каждой группы.
In [171]: df_re.groupby("A").expanding().sum()
Out[171]:
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]
Предположим, что вы хотите использовать метод resample() для получения ежедневной частоты в каждой группе вашего DataFrame и хотите заполнить пропущенные значения методом ffill().
In [172]: df_re = pd.DataFrame(
.....: {
.....: "date": pd.date_range(start="2016-01-01", periods=4, freq="W"),
.....: "group": [1, 1, 2, 2],
.....: "val": [5, 6, 7, 8],
.....: }
.....: ).set_index("date")
.....:
In [173]: df_re
Out[173]:
group val
date
2016-01-03 1 5
2016-01-10 1 6
2016-01-17 2 7
2016-01-24 2 8
In [174]: df_re.groupby("group").resample("1D", include_groups=False).ffill()
Out[174]:
val
group date
1 2016-01-03 5
2016-01-04 5
2016-01-05 5
2016-01-06 5
2016-01-07 5
... ...
2 2016-01-20 7
2016-01-21 7
2016-01-22 7
2016-01-23 7
2016-01-24 8
[16 rows x 1 columns]
Фильтрация
Фильтрация — это операция GroupBy, которая подмножит исходный объект группирования. Она может фильтровать целые группы, части групп или оба варианта. Фильтрации возвращают отфильтрованную версию вызываемого объекта, включая столбцы группировки, если они указаны. В следующем примере class включён в результат.
In [175]: speeds
Out[175]:
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
In [176]: speeds.groupby("class").nth(1)
Out[176]:
class order max_speed
parrot bird Psittaciformes 24.0
monkey mammal Primates NaN
Примечание
В отличие от агрегаций, фильтрации не добавляют ключи групп в индекс результата. Из-за этого передача as_index=False или sort=True не повлияет на эти методы.
Фильтрации будут учитывать подмножество столбцов объекта GroupBy.
In [177]: speeds.groupby("class")[["order", "max_speed"]].nth(1)
Out[177]:
order max_speed
parrot Psittaciformes 24.0
monkey Primates NaN
Встроенные фильтрации
Следующие методы GroupBy действуют как фильтрации. Все эти методы имеют эффективную реализацию, специфичную для GroupBy.
Метод | Описание |
|---|---|
Выбор первой строки(ы) каждой группы | |
Выбор n-ой строки(ы) каждой группы | |
Выбор последней строки(ы) каждой группы |
Пользователи также могут использовать преобразования вместе с булевыми индексами для создания сложных фильтраций в пределах групп. Например, предположим, что нам даны группы продуктов и их объёмы, и мы хотим подмножить данные только до самых больших продуктов, которые занимают не более 90% от общего объёма в каждой группе.
In [178]: product_volumes = pd.DataFrame(
.....: {
.....: "group": list("xxxxyyy"),
.....: "product": list("abcdefg"),
.....: "volume": [10, 30, 20, 15, 40, 10, 20],
.....: }
.....: )
.....:
In [179]: product_volumes
Out[179]:
group product volume
0 x a 10
1 x b 30
2 x c 20
3 x d 15
4 y e 40
5 y f 10
6 y g 20
# Sort by volume to select the largest products first
In [180]: product_volumes = product_volumes.sort_values("volume", ascending=False)
In [181]: grouped = product_volumes.groupby("group")["volume"]
In [182]: cumpct = grouped.cumsum() / grouped.transform("sum")
In [183]: cumpct
Out[183]:
4 0.571429
1 0.400000
2 0.666667
6 0.857143
3 0.866667
0 1.000000
5 1.000000
Name: volume, dtype: float64
In [184]: significant_products = product_volumes[cumpct <= 0.9]
In [185]: significant_products.sort_values(["group", "product"])
Out[185]:
group product volume
1 x b 30
2 x c 20
3 x d 15
4 y e 40
6 y g 20
Метод filter
Примечание
Фильтрация путём предоставления filter с пользовательской функцией (UDF) часто менее производительна, чем использование встроенных методов GroupBy. Рассмотрите возможность разбивки сложной операции на цепочку операций, использующих встроенные методы.
Метод filter принимает пользовательскую функцию (UDF), которая, когда применяется ко всей группе, возвращает либо True, либо False. Результатом метода filter является затем подмножество групп, для которых UDF вернула True.
Предположим, что мы хотим взять только элементы, принадлежащие группам с суммой группы больше 2.
In [186]: sf = pd.Series([1, 1, 2, 3, 3, 3])
In [187]: sf.groupby(sf).filter(lambda x: x.sum() > 2)
Out[187]:
3 3
4 3
5 3
dtype: int64
Другой полезной операцией является фильтрация элементов, принадлежащих группам с небольшим количеством членов.
In [188]: dff = pd.DataFrame({"A": np.arange(8), "B": list("aabbbbcc")})
In [189]: dff.groupby("B").filter(lambda x: len(x) > 2)
Out[189]:
A B
2 2 b
3 3 b
4 4 b
5 5 b
В качестве альтернативы, вместо удаления проблемных групп, можно вернуть объекты с аналогичными индексами, где группы, не прошедшие фильтр, заполнены NaNs.
In [190]: dff.groupby("B").filter(lambda x: len(x) > 2, dropna=False)
Out[190]:
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 с несколькими столбцами фильтры должны явно указать столбец в качестве критерия фильтрации.
In [191]: dff["C"] = np.arange(8)
In [192]: dff.groupby("B").filter(lambda x: len(x["C"]) > 2)
Out[192]:
A B C
2 2 b 2
3 3 b 3
4 4 b 4
5 5 b 5
Гибкое применение apply
Некоторые операции с сгруппированными данными могут не вписываться в категории агрегации, преобразования или фильтрации. Для этого можно использовать функцию apply.
Предупреждение
apply должна попытаться определить из результата, должна ли она действовать как редуктор, преобразователь или фильтр в зависимости от того, что ей передаётся. Таким образом, сгруппированные столбцы могут быть включены в выходные данные или нет. Хотя она пытается интеллектуально угадать, как вести себя, она иногда может ошибаться.
Примечание
Все примеры в этом разделе могут быть более надёжно и эффективно вычислены с использованием других возможностей pandas.
In [193]: df
Out[193]:
A B C D
0 foo one -0.575247 1.346061
1 bar one 0.254161 1.511763
2 foo two -1.143704 1.627081
3 bar three 0.215897 -0.990582
4 foo two 1.193555 -0.441652
5 bar two -0.077118 1.211526
6 foo one -0.408530 0.268520
7 foo three -0.862495 0.024580
In [194]: grouped = df.groupby("A")
# could also just call .describe()
In [195]: grouped["C"].apply(lambda x: x.describe())
Out[195]:
A
bar count 3.000000
mean 0.130980
std 0.181231
min -0.077118
25% 0.069390
...
foo min -1.143704
25% -0.862495
50% -0.575247
75% -0.408530
max 1.193555
Name: C, Length: 16, dtype: float64
Размер возвращаемого результата также может измениться:
In [196]: grouped = df.groupby('A')['C']
In [197]: def f(group):
.....: return pd.DataFrame({'original': group,
.....: 'demeaned': group - group.mean()})
.....:
In [198]: grouped.apply(f)
Out[198]:
original demeaned
A
bar 1 0.254161 0.123181
3 0.215897 0.084917
5 -0.077118 -0.208098
foo 0 -0.575247 -0.215962
2 -1.143704 -0.784420
4 1.193555 1.552839
6 -0.408530 -0.049245
7 -0.862495 -0.503211
apply для Series может работать с возвращаемым значением от применяемой функции, которая сама является Series, и, возможно, преобразовать результат в DataFrame:
In [199]: def f(x):
.....: return pd.Series([x, x ** 2], index=["x", "x^2"])
.....:
In [200]: s = pd.Series(np.random.rand(5))
In [201]: s
Out[201]:
0 0.582898
1 0.098352
2 0.001438
3 0.009420
4 0.815826
dtype: float64
In [202]: s.apply(f)
Out[202]:
x x^2
0 0.582898 0.339770
1 0.098352 0.009673
2 0.001438 0.000002
3 0.009420 0.000089
4 0.815826 0.665572
Аналогично методу aggregate(), результирующий тип данных будет отражать тип данных функции apply. Если результаты из разных групп имеют разные типы данных, то общий тип данных будет определён аналогично построению DataFrame.
Управление расположением сгруппированных столбцов с помощью group_keys
Чтобы контролировать включение сгруппированных столбцов в индексы, можно использовать аргумент group_keys, который по умолчанию равен True.
Сравните
In [203]: df.groupby("A", group_keys=True).apply(lambda x: x, include_groups=False)
Out[203]:
B C D
A
bar 1 one 0.254161 1.511763
3 three 0.215897 -0.990582
5 two -0.077118 1.211526
foo 0 one -0.575247 1.346061
2 two -1.143704 1.627081
4 two 1.193555 -0.441652
6 one -0.408530 0.268520
7 three -0.862495 0.024580
с
In [204]: df.groupby("A", group_keys=False).apply(lambda x: x, include_groups=False)
Out[204]:
B C D
0 one -0.575247 1.346061
1 one 0.254161 1.511763
2 two -1.143704 1.627081
3 three 0.215897 -0.990582
4 two 1.193555 -0.441652
5 two -0.077118 1.211526
6 one -0.408530 0.268520
7 three -0.862495 0.024580
Ускоренные Numba процедуры
Новое в версии 1.1.
Если Numba установлена как необязательная зависимость, методы transform и aggregate поддерживают аргументы engine='numba' и engine_kwargs. См. улучшение производительности с помощью Numba для общего использования аргументов и соображений по производительности.
Подпись функции должна начинаться с values, index точно так же, как данные, принадлежащие каждой группе, будут переданы в values, а индекс группы будет передан в index.
Предупреждение
При использовании engine='numba', внутренней «обратной связи» не будет. Данные группы и индекс группы будут переданы как массивы NumPy в JIT-компилированную пользовательскую функцию, и другие попытки выполнения не будут предприниматься.
Дополнительные полезные функции
Исключение нечисловых столбцов
Рассмотрим снова пример DataFrame, который мы рассматривали:
In [205]: df
Out[205]:
A B C D
0 foo one -0.575247 1.346061
1 bar one 0.254161 1.511763
2 foo two -1.143704 1.627081
3 bar three 0.215897 -0.990582
4 foo two 1.193555 -0.441652
5 bar two -0.077118 1.211526
6 foo one -0.408530 0.268520
7 foo three -0.862495 0.024580
Предположим, что мы хотим вычислить стандартное отклонение, сгруппированное по столбцу A. Существует небольшая проблема, а именно, что нас не интересуют данные в столбце B, так как он не является числовым. Вы можете избежать нечисловых столбцов, указав numeric_only=True:
In [206]: df.groupby("A").std(numeric_only=True)
Out[206]:
C D
A
bar 0.181231 1.366330
foo 0.912265 0.884785
Обратите внимание, что df.groupby('A').colname.std(). более эффективно, чем df.groupby('A').std().colname. Поэтому, если результат функции агрегирования требуется только для одного столбца (здесь colname ), его можно отфильтровать перед применением функции агрегирования.
In [207]: from decimal import Decimal
In [208]: df_dec = pd.DataFrame(
.....: {
.....: "id": [1, 2, 1, 2],
.....: "int_column": [1, 2, 3, 4],
.....: "dec_column": [
.....: Decimal("0.50"),
.....: Decimal("0.15"),
.....: Decimal("0.25"),
.....: Decimal("0.40"),
.....: ],
.....: }
.....: )
.....:
In [209]: df_dec.groupby(["id"])[["dec_column"]].sum()
Out[209]:
dec_column
id
1 0.75
2 0.55
Обработка (не)наблюдаемых категориальных значений
При использовании Categorical группировщика (как единственного группировщика или как части нескольких группировщиков), ключевое слово observed управляет тем, следует ли вернуть декартово произведение всех возможных значений группировщиков (observed=False) или только те, которые были наблюдаемы (observed=True).
Показать все значения:
In [210]: pd.Series([1, 1, 1]).groupby(
.....: pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=False
.....: ).count()
.....:
Out[210]:
a 3
b 0
dtype: int64
Показать только наблюдаемые значения:
In [211]: pd.Series([1, 1, 1]).groupby(
.....: pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=True
.....: ).count()
.....:
Out[211]:
a 3
dtype: int64
Возвращаемый тип данных сгруппированных данных всегда будет включать все категории, по которым производилась группировка.
In [212]: s = (
.....: pd.Series([1, 1, 1])
.....: .groupby(pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=True)
.....: .count()
.....: )
.....:
In [213]: s.index.dtype
Out[213]: CategoricalDtype(categories=['a', 'b'], ordered=False, categories_dtype=object)
Обработка групп NA
Под NA, мы подразумеваем любые NA значения, включая NA, NaN, NaT, и None. Если в ключе группировки присутствуют NA значения, по умолчанию они будут исключены. Другими словами, любая «NA группа» будет отброшена. Вы можете включить группы NA, указав dropna=False.
In [214]: df = pd.DataFrame({"key": [1.0, 1.0, np.nan, 2.0, np.nan], "A": [1, 2, 3, 4, 5]})
In [215]: df
Out[215]:
key A
0 1.0 1
1 1.0 2
2 NaN 3
3 2.0 4
4 NaN 5
In [216]: df.groupby("key", dropna=True).sum()
Out[216]:
A
key
1.0 3
2.0 4
In [217]: df.groupby("key", dropna=False).sum()
Out[217]:
A
key
1.0 3
2.0 4
NaN 8
Группировка с упорядоченными факторами
Категориальные переменные, представленные как экземпляры класса Categorical pandas, могут использоваться в качестве ключей группировки. В этом случае порядок уровней будет сохранён. При observed=False и sort=False, любые не наблюдаемые категории будут в конце результата в порядке.
In [218]: days = pd.Categorical(
.....: values=["Wed", "Mon", "Thu", "Mon", "Wed", "Sat"],
.....: categories=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
.....: )
.....:
In [219]: data = pd.DataFrame(
.....: {
.....: "day": days,
.....: "workers": [3, 4, 1, 4, 2, 2],
.....: }
.....: )
.....:
In [220]: data
Out[220]:
day workers
0 Wed 3
1 Mon 4
2 Thu 1
3 Mon 4
4 Wed 2
5 Sat 2
In [221]: data.groupby("day", observed=False, sort=True).sum()
Out[221]:
workers
day
Mon 8
Tue 0
Wed 5
Thu 1
Fri 0
Sat 2
Sun 0
In [222]: data.groupby("day", observed=False, sort=False).sum()
Out[222]:
workers
day
Wed 5
Mon 8
Thu 1
Sat 2
Tue 0
Fri 0
Sun 0
Группировка с указанием группировщика
Вам может потребоваться указать немного больше данных для правильной группировки. Вы можете использовать pd.Grouper для обеспечения этого локального управления.
In [223]: import datetime
In [224]: df = pd.DataFrame(
.....: {
.....: "Branch": "A A A A A A A B".split(),
.....: "Buyer": "Carl Mark Carl Carl Joe Joe Joe Carl".split(),
.....: "Quantity": [1, 3, 5, 1, 8, 1, 9, 3],
.....: "Date": [
.....: datetime.datetime(2013, 1, 1, 13, 0),
.....: datetime.datetime(2013, 1, 1, 13, 5),
.....: datetime.datetime(2013, 10, 1, 20, 0),
.....: datetime.datetime(2013, 10, 2, 10, 0),
.....: datetime.datetime(2013, 10, 1, 20, 0),
.....: datetime.datetime(2013, 10, 2, 10, 0),
.....: datetime.datetime(2013, 12, 2, 12, 0),
.....: datetime.datetime(2013, 12, 2, 14, 0),
.....: ],
.....: }
.....: )
.....:
In [225]: df
Out[225]:
Branch Buyer Quantity Date
0 A Carl 1 2013-01-01 13:00:00
1 A Mark 3 2013-01-01 13:05:00
2 A Carl 5 2013-10-01 20:00:00
3 A Carl 1 2013-10-02 10:00:00
4 A Joe 8 2013-10-01 20:00:00
5 A Joe 1 2013-10-02 10:00:00
6 A Joe 9 2013-12-02 12:00:00
7 B Carl 3 2013-12-02 14:00:00
Группировка по определённому столбцу с желаемой частотой. Это похоже на повторную выборку.
In [226]: df.groupby([pd.Grouper(freq="1ME", key="Date"), "Buyer"])[["Quantity"]].sum()
Out[226]:
Quantity
Date Buyer
2013-01-31 Carl 1
Mark 3
2013-10-31 Carl 6
Joe 9
2013-12-31 Carl 3
Joe 9
Когда freq указано, объект, возвращаемый pd.Grouper, будет экземпляром pandas.api.typing.TimeGrouper. Если столбец и индекс имеют одинаковое имя, вы можете использовать key для группировки по столбцу и level для группировки по индексу.
In [227]: df = df.set_index("Date")
In [228]: df["Date"] = df.index + pd.offsets.MonthEnd(2)
In [229]: df.groupby([pd.Grouper(freq="6ME", key="Date"), "Buyer"])[["Quantity"]].sum()
Out[229]:
Quantity
Date Buyer
2013-02-28 Carl 1
Mark 3
2014-02-28 Carl 9
Joe 18
In [230]: df.groupby([pd.Grouper(freq="6ME", level="Date"), "Buyer"])[["Quantity"]].sum()
Out[230]:
Quantity
Date Buyer
2013-01-31 Carl 1
Mark 3
2014-01-31 Carl 9
Joe 18
Получение первых строк каждой группы
Как и для DataFrame или Series, вы можете вызвать head и tail для groupby:
In [231]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=["A", "B"])
In [232]: df
Out[232]:
A B
0 1 2
1 1 4
2 5 6
In [233]: g = df.groupby("A")
In [234]: g.head(1)
Out[234]:
A B
0 1 2
2 5 6
In [235]: g.tail(1)
Out[235]:
A B
1 1 4
2 5 6
Это показывает первые или последние n строк из каждой группы.
Получение n-й строки каждой группы
Чтобы выбрать n-й элемент из каждой группы, используйте DataFrameGroupBy.nth() или SeriesGroupBy.nth(). Передаваемые аргументы могут быть любыми целыми числами, списками целых чисел, срезами или списками срезов; примеры см. ниже. Когда n-й элемент группы не существует, ошибка не генерируется; вместо этого не возвращаются соответствующие строки.
В общем, эта операция действует как фильтрация. В некоторых случаях она также вернёт одну строку на группу, что также является сокращением. Однако поскольку, в общем случае, она может вернуть ноль или несколько строк на группу, pandas обрабатывает её как фильтрацию во всех случаях.
In [236]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=["A", "B"])
In [237]: g = df.groupby("A")
In [238]: g.nth(0)
Out[238]:
A B
0 1 NaN
2 5 6.0
In [239]: g.nth(-1)
Out[239]:
A B
1 1 4.0
2 5 6.0
In [240]: g.nth(1)
Out[240]:
A B
1 1 4.0
Если n-й элемент группы не существует, то соответствующая строка не включается в результат. В частности, если указанное n значение больше, чем любая группа, результат будет пустым DataFrame.
In [241]: g.nth(5)
Out[241]:
Empty DataFrame
Columns: [A, B]
Index: []
Если вы хотите выбрать n-й непустой элемент, используйте параметр dropna. Для DataFrame это должно быть либо 'any', либо 'all', точно так же, как вы бы передали dropna:
# nth(0) is the same as g.first()
In [242]: g.nth(0, dropna="any")
Out[242]:
A B
1 1 4.0
2 5 6.0
In [243]: g.first()
Out[243]:
B
A
1 4.0
5 6.0
# nth(-1) is the same as g.last()
In [244]: g.nth(-1, dropna="any")
Out[244]:
A B
1 1 4.0
2 5 6.0
In [245]: g.last()
Out[245]:
B
A
1 4.0
5 6.0
In [246]: g.B.nth(0, dropna="all")
Out[246]:
1 4.0
2 6.0
Name: B, dtype: float64
Вы также можете выбрать несколько строк из каждой группы, указав несколько значений n в виде списка целых чисел.
In [247]: business_dates = pd.date_range(start="4/1/2014", end="6/30/2014", freq="B")
In [248]: df = pd.DataFrame(1, index=business_dates, columns=["a", "b"])
# get the first, 4th, and last date index for each month
In [249]: df.groupby([df.index.year, df.index.month]).nth([0, 3, -1])
Out[249]:
a b
2014-04-01 1 1
2014-04-04 1 1
2014-04-30 1 1
2014-05-01 1 1
2014-05-06 1 1
2014-05-30 1 1
2014-06-02 1 1
2014-06-05 1 1
2014-06-30 1 1
Вы также можете использовать срезы или списки срезов.
In [250]: df.groupby([df.index.year, df.index.month]).nth[1:]
Out[250]:
a b
2014-04-02 1 1
2014-04-03 1 1
2014-04-04 1 1
2014-04-07 1 1
2014-04-08 1 1
... .. ..
2014-06-24 1 1
2014-06-25 1 1
2014-06-26 1 1
2014-06-27 1 1
2014-06-30 1 1
[62 rows x 2 columns]
In [251]: df.groupby([df.index.year, df.index.month]).nth[1:, :-1]
Out[251]:
a b
2014-04-01 1 1
2014-04-02 1 1
2014-04-03 1 1
2014-04-04 1 1
2014-04-07 1 1
... .. ..
2014-06-24 1 1
2014-06-25 1 1
2014-06-26 1 1
2014-06-27 1 1
2014-06-30 1 1
[65 rows x 2 columns]
Перечисление элементов группы
Чтобы увидеть порядок, в котором каждая строка появляется в своей группе, используйте метод cumcount:
In [252]: dfg = pd.DataFrame(list("aaabba"), columns=["A"])
In [253]: dfg
Out[253]:
A
0 a
1 a
2 a
3 b
4 b
5 a
In [254]: dfg.groupby("A").cumcount()
Out[254]:
0 0
1 1
2 2
3 0
4 1
5 3
dtype: int64
In [255]: dfg.groupby("A").cumcount(ascending=False)
Out[255]:
0 3
1 2
2 1
3 1
4 0
5 0
dtype: int64
Перечисление групп
Чтобы увидеть порядок групп (в отличие от порядка строк внутри группы, заданного cumcount ), вы можете использовать DataFrameGroupBy.ngroup().
Обратите внимание, что номера, присвоенные группам, соответствуют порядку, в котором группы будут видны при итерации по объекту groupby, а не порядку их первого наблюдения.
In [256]: dfg = pd.DataFrame(list("aaabba"), columns=["A"])
In [257]: dfg
Out[257]:
A
0 a
1 a
2 a
3 b
4 b
5 a
In [258]: dfg.groupby("A").ngroup()
Out[258]:
0 0
1 0
2 0
3 1
4 1
5 0
dtype: int64
In [259]: dfg.groupby("A").ngroup(ascending=False)
Out[259]:
0 1
1 1
2 1
3 0
4 0
5 1
dtype: int64
Построение графиков
Groupby также работает с некоторыми методами построения графиков. В данном случае, предположим, что мы подозреваем, что значения в столбце 1 в среднем в 3 раза выше в группе «B».
In [260]: np.random.seed(1234)
In [261]: df = pd.DataFrame(np.random.randn(50, 2))
In [262]: df["g"] = np.random.choice(["A", "B"], size=50)
In [263]: df.loc[df["g"] == "B", 1] += 3
Мы можем легко визуализировать это с помощью boxplot:
In [264]: df.groupby("g").boxplot()
Out[264]:
A Axes(0.1,0.15;0.363636x0.75)
B Axes(0.536364,0.15;0.363636x0.75)
dtype: object
Результат вызова boxplot — это словарь, ключами которого являются значения нашего столбца группировки g («A» и «B»). Значения результирующего словаря можно контролировать с помощью ключевого слова return_type функции boxplot . Подробнее см. в документации по визуализации.
Предупреждение
По историческим причинам, df.groupby("g").boxplot() не эквивалентно df.boxplot(by="g") . См. здесь для объяснения.
Цепочная вызов функций
Аналогично функциональности, предоставляемой DataFrame и Series, функции, которые принимают объекты GroupBy, могут быть объединены с помощью метода pipe, что позволит создать более чистый и читаемый синтаксис. Чтобы узнать о .pipe в общем, см. здесь.
Комбинирование .groupby и .pipe часто полезно, когда вам нужно повторно использовать объекты GroupBy.
В качестве примера, представьте себе DataFrame со столбцами для магазинов, продуктов, выручки и проданной продукции. Мы хотим выполнить групповое вычисление цен (т. е. выручки/количества) на магазин и на продукт. Мы могли бы сделать это в многоэтапной операции, но выражение её в терминах конвейера может сделать код более читаемым. Сначала мы задаём данные:
In [265]: n = 1000
In [266]: df = pd.DataFrame(
.....: {
.....: "Store": np.random.choice(["Store_1", "Store_2"], n),
.....: "Product": np.random.choice(["Product_1", "Product_2"], n),
.....: "Revenue": (np.random.random(n) * 50 + 10).round(2),
.....: "Quantity": np.random.randint(1, 10, size=n),
.....: }
.....: )
.....:
In [267]: df.head(2)
Out[267]:
Store Product Revenue Quantity
0 Store_2 Product_1 26.12 1
1 Store_2 Product_1 28.86 1
Теперь мы находим цены на магазин/продукт.
In [268]: (
.....: df.groupby(["Store", "Product"])
.....: .pipe(lambda grp: grp.Revenue.sum() / grp.Quantity.sum())
.....: .unstack()
.....: .round(2)
.....: )
.....:
Out[268]:
Product Product_1 Product_2
Store
Store_1 6.82 7.05
Store_2 6.30 6.64
Конвейер также может быть выразителен, когда вы хотите передать сгруппированный объект в произвольную функцию, например:
In [269]: def mean(groupby):
.....: return groupby.mean()
.....:
In [270]: df.groupby(["Store", "Product"]).pipe(mean)
Out[270]:
Revenue Quantity
Store Product
Store_1 Product_1 34.622727 5.075758
Product_2 35.482815 5.029630
Store_2 Product_1 32.972837 5.237589
Product_2 34.684360 5.224000
Здесь mean принимает объект GroupBy и находит среднее значение столбцов Revenue и Quantity соответственно для каждой комбинации Магазин-Продукт. Функция mean может быть любой функцией, которая принимает объект GroupBy; функция .pipe передаст объект GroupBy в качестве параметра в указанную вами функцию.
Примеры
Мультиколонная факторизация
Используя DataFrameGroupBy.ngroup(), мы можем извлечь информацию о группах аналогичным образом factorize() (как описано далее в API для изменения формы), но это естественным образом применяется к нескольким столбцам смешанного типа и разных источников. Это может быть полезно как промежуточный шаг категоризации при обработке, когда отношения между строками группы важнее их содержимого, или как вход в алгоритм, который принимает только целочисленное кодирование. (Дополнительную информацию о поддержке pandas для полных категориальных данных см. в Введении в категории и документации API.)
In [271]: dfg = pd.DataFrame({"A": [1, 1, 2, 3, 2], "B": list("aaaba")})
In [272]: dfg
Out[272]:
A B
0 1 a
1 1 a
2 2 a
3 3 b
4 2 a
In [273]: dfg.groupby(["A", "B"]).ngroup()
Out[273]:
0 0
1 0
2 1
3 2
4 1
dtype: int64
In [274]: dfg.groupby(["A", [0, 0, 0, 1, 1]]).ngroup()
Out[274]:
0 0
1 0
2 1
3 3
4 2
dtype: int64
Группировка по индексу для «пересэмплирования» данных
Пересэмплирование создаёт новые гипотетические выборки (пересэмплирования) из уже имеющихся наблюдаемых данных или из модели, генерирующей данные. Эти новые выборки похожи на предварительно существующие выборки.
Для работы пересэмплирования с индексами, которые не являются datetimelike, можно использовать следующую процедуру.
В следующих примерах df.index // 5 возвращает целочисленный массив, который используется для определения того, что выбирается для операции группировки.
Примечание
Приведённый ниже пример показывает, как можно уменьшить объём данных путём объединения выборок в меньшее количество. Здесь, используя df.index // 5, мы агрегируем выборки в интервалы. Применение функции std() позволяет агрегировать информацию, содержащуюся во многих выборках, в небольшой набор значений, которые представляют собой их стандартное отклонение, тем самым уменьшая количество выборок.
In [275]: df = pd.DataFrame(np.random.randn(10, 2))
In [276]: df
Out[276]:
0 1
0 -0.793893 0.321153
1 0.342250 1.618906
2 -0.975807 1.918201
3 -0.810847 -1.405919
4 -1.977759 0.461659
5 0.730057 -1.316938
6 -0.751328 0.528290
7 -0.257759 -1.081009
8 0.505895 -1.701948
9 -1.006349 0.020208
In [277]: df.index // 5
Out[277]: Index([0, 0, 0, 0, 0, 1, 1, 1, 1, 1], dtype='int64')
In [278]: df.groupby(df.index // 5).std()
Out[278]:
0 1
0 0.823647 1.312912
1 0.760109 0.942941
Возвращение Series для распространения имён
Группировка столбцов DataFrame, вычисление набора метрик и возвращение именованного Series. Имя Series используется в качестве имени для индекса столбца. Это особенно полезно в сочетании с операциями изменения формы, такими как stacking, в которых имя индекса столбца будет использоваться в качестве имени вставляемого столбца:
In [279]: df = pd.DataFrame(
.....: {
.....: "a": [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
.....: "b": [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
.....: "c": [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
.....: "d": [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
.....: }
.....: )
.....:
In [280]: def compute_metrics(x):
.....: result = {"b_sum": x["b"].sum(), "c_mean": x["c"].mean()}
.....: return pd.Series(result, name="metrics")
.....:
In [281]: result = df.groupby("a").apply(compute_metrics, include_groups=False)
In [282]: result
Out[282]:
metrics b_sum c_mean
a
0 2.0 0.5
1 2.0 0.5
2 2.0 0.5
In [283]: result.stack(future_stack=True)
Out[283]:
a metrics
0 b_sum 2.0
c_mean 0.5
1 b_sum 2.0
c_mean 0.5
2 b_sum 2.0
c_mean 0.5
dtype: float64
© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.2.2/user_guide/groupby.html