Стоить отметить, что в случае с групповыми операциями, да и вообще в
pandas
, часто быстрее (а также проще для понимания) писать более подробный код, а не сокращать.
Материал приводятся примеры различных ситуации с групповыми операциями в pandas
, собранные с сайта StackOverflow.
GroupBy.apply()
;GroupBy.apply()
к различным элементам в группе;DataFrame
;itertools.groupby()
в Python;reduce()
;min()
с GroupBy
.GroupBy.apply()
:В отличие от DataFrame.agg()
, вызываемому объекту метода GroupBy.apply()
передается суб-DataFrame
, который дает доступ ко всем исходным столбцам.
>>> import pandas as pd df = pd.DataFrame( { "animal": "cat dog cat fish dog cat cat".split(), "size": list("SSMMMLL"), "weight": [8, 10, 11, 1, 20, 12, 12], "adult": [False] * 5 + [True] * 2, } ) >>> df # animal size weight adult # 0 cat S 8 False # 1 dog S 10 False # 2 cat M 11 False # 3 fish M 1 False # 4 dog M 20 False # 5 cat L 12 True # 6 cat L 12 True # размер животных с наибольшим весом. >>> df.groupby("animal").apply(lambda df: df["size"][df["weight"].idxmax()], include_groups=False) # animal # cat L # dog M # fish M # dtype: object
# исходные данные смотрим выше # здесь получаем объект группировки >>> grby = df.groupby("animal") # теперь из объекта группировки # просто выбираем нужную группу >>> grby.get_group("cat") # animal size weight adult # 0 cat S 8 False # 2 cat M 11 False # 5 cat L 12 True # 6 cat L 12 True
GroupBy.apply()
к различным элементам в группе# исходные данные смотрим выше, объект # группировки `grby` получили в предыдущем примере # определим функцию def grow_up(x): avg_weight = sum(x[x["size"] == "S"].weight * 1.5) avg_weight += sum(x[x["size"] == "M"].weight * 1.25) avg_weight += sum(x[x["size"] == "L"].weight) avg_weight /= len(x) return pd.Series(["L", avg_weight, True], index=["size", "weight", "adult"]) # применяем функцию `grow_up` >>> expected_df = grby.apply(grow_up, include_groups=False) >>> expected_df # size weight adult # animal # cat L 12.4375 True # dog L 20.0000 True # fish L 1.2500 True
>>> df = pd.DataFrame({"A": [1, 1, 2, 2], "B": [1, -1, 1, 2]}) >>> df # A B # 0 1 1 # 1 1 -1 # 2 2 1 # 3 2 2 def replace(g): "Подсчет `mean` в группе" mask = g < 0 # оператор `~` - логическое отрицание отри return g.where(~mask, g[~mask].mean()) # получаем объект группировки по # столбцу `A` (он ушел в индекс) >>> grby = df.groupby("A") # применяем функцию `replace` # к остальной части группы >>> grby.transform(replace) # B # 0 1 # 1 1 # 2 1 # 3 2
df = pd.DataFrame( { "code": ["foo", "bar", "baz"] * 2, "data": [0.16, -0.21, 0.33, 0.45, -0.59, 0.62], "flag": [False, True] * 3, } ) >>> df # code data flag # 0 foo 0.16 False # 1 bar -0.21 True # 2 baz 0.33 False # 3 foo 0.45 True # 4 bar -0.59 False # 5 baz 0.62 True # получаем объект группировки >>> code_groups = df.groupby("code") # сортируем столбец `data` по `sum()` в группах столбца `code` >>> agg_n_sort_order = code_groups[["data"]].transform("sum").sort_values(by="data") >>> agg_n_sort_order # data # 1 -0.80 # 4 -0.80 # 0 0.61 # 3 0.61 # 2 0.95 # 5 0.95 # выравниваем данные по индексу сортировки >>> sorted_df = df.loc[agg_n_sort_order.index] >>> sorted_df # code data flag # 1 bar -0.21 True # 4 bar -0.59 False # 0 foo 0.16 False # 3 foo 0.45 True # 2 baz 0.33 False # 5 baz 0.62 True
# создадим индекс `rng` >>> rng = pd.date_range(start="2014-10-07", periods=10, freq="2min") # создадим серию с индексом `rng` >>> ts = pd.Series(data=list(range(10)), index=rng) >>> ts # 2014-10-07 00:00:00 0 # 2014-10-07 00:02:00 1 # 2014-10-07 00:04:00 2 # 2014-10-07 00:06:00 3 # 2014-10-07 00:08:00 4 # 2014-10-07 00:10:00 5 # 2014-10-07 00:12:00 6 # 2014-10-07 00:14:00 7 # 2014-10-07 00:16:00 8 # 2014-10-07 00:18:00 9 # Freq: 2min, dtype: int64 def mycust(x): if len(x) > 2: return x.iloc[1] * 1.234 return pd.NaT # словарь с именами итоговых столбцов # и именем применяемой функции >>> mhc = {"Mean": "mean", "Max": "max", "Custom": mycust} # делаем повторную выборку с применением функций >>> ts.resample("5min").apply(mhc) # Mean Max Custom # 2014-10-07 00:00:00 1.0 2 1.234 # 2014-10-07 00:05:00 3.5 4 NaT # 2014-10-07 00:10:00 6.0 7 7.404 # 2014-10-07 00:15:00 8.5 9 NaT
DataFrame
.df = pd.DataFrame( {"Color": "Red Red Red Blue".split(), "Value": [100, 150, 50, 50]} ) >>> df # Color Value # 0 Red 100 # 1 Red 150 # 2 Red 50 # 3 Blue 50 # Здесь все просто, `DataFrame` # присваивается новый столбец >>> df["Counts"] = df.groupby(["Color"]).transform(len) >>> df # Color Value Counts # 0 Red 100 3 # 1 Red 150 3 # 2 Red 50 3 # 3 Blue 50 1
df = pd.DataFrame( {"line_race": [10, 10, 8, 10, 10, 8], "beyer": [99, 102, 103, 103, 88, 100]}, index=[ "Last Gunfighter", "Last Gunfighter", "Last Gunfighter", "Paynter", "Paynter", "Paynter", ], ) >>> df # line_race beyer # Last Gunfighter 10 99 # Last Gunfighter 10 102 # Last Gunfighter 8 103 # Paynter 10 103 # Paynter 10 88 # Paynter 8 100 # создаем столбец со смещением значения из столбца `beyer` # на одно значение вниз ДЛЯ КАЖДОЙ ГРУППЫ >>> df["beyer_shifted"] = df.groupby(level=0)["beyer"].shift(1) >>> df # line_race beyer beyer_shifted # Last Gunfighter 10 99 NaN # Last Gunfighter 10 102 99.0 # Last Gunfighter 8 103 102.0 # Paynter 10 103 NaN # Paynter 10 88 103.0 # Paynter 8 100 88.0
df = pd.DataFrame( { "host": ["other", "other", "that", "this", "this"], "service": ["mail", "web", "mail", "mail", "web"], "no": [1, 2, 1, 3, 2], } ).set_index(["host", "service"]) >>> df # no # host service # other mail 1 # web 2 # that mail 1 # this mail 3 # web 2 # группируем и ищем индекс с максимальным значением >>> mask = df.groupby(level=0).agg("idxmax") >>> mask # no # host # other (other, web) # that (that, mail) # this (this, mail) # выравниваем данные по `mask` >>> df_count = df.loc[mask["no"]].reset_index() >>> df_count # host service no # 0 other web 2 # 1 that mail 1 # 2 this mail 3
itertools.groupby()
в Python>>> df = pd.DataFrame([0, 1, 0, 1, 1, 1, 0, 1, 1], columns=["A"]) >>> df # A # 0 0 # 1 1 # 2 0 # 3 1 # 4 1 # 5 1 # 6 0 # 7 1 # 8 1 >>> df["A"].groupby((df["A"] != df["A"].shift()).cumsum()).groups # {1: [0], 2: [1], 3: [2], 4: [3, 4, 5], 5: [6], 6: [7, 8]} >>> df["A"].groupby((df["A"] != df["A"].shift()).cumsum()).cumsum() # 0 0 # 1 1 # 2 0 # 3 1 # 4 2 # 5 3 # 6 0 # 7 1 # 8 2 # Name: A, dtype: int64
reduce()
Функция, которая делает тоже самое, что и functools.reduce()
, за исключением того, что вычисление собирается во время functools.reduce()
. То есть возвращает последовательность всех промежуточных значений.
>>> s = pd.Series([i / 100.0 for i in range(1, 11)]) >>> s # 0 0.01 # 1 0.02 # 2 0.03 # 3 0.04 # 4 0.05 # 5 0.06 # 6 0.07 # 7 0.08 # 8 0.09 # 9 0.10 # dtype: float64 # функцию для сбора # промежуточных значений def cum_ret(x, y): return x * (1 + y) # функция с `reduce` def red(x): return functools.reduce(cum_ret, x, 1.0) # применим ее при расчет расширяющегося окна >>> s.expanding().apply(red, raw=True) # 0 1.010000 # 1 1.030200 # 2 1.061106 # 3 1.103550 # 4 1.158728 # 5 1.228251 # 6 1.314229 # 7 1.419367 # 8 1.547110 # 9 1.701821 # dtype: float64
min()
с GroupBy
df = pd.DataFrame( {"AAA": [1, 1, 1, 2, 2, 2, 3, 3], "BBB": [2, 1, 3, 4, 5, 1, 2, 3]} ) >>> df # AAA BBB # 0 1 2 # 1 1 1 # 2 1 3 # 3 2 4 # 4 2 5 # 5 2 1 # 6 3 2 # 7 3 3
Вариант 1. GroupBy.idxmin() для получения индекса минимумов
>>> df.loc[df.groupby("AAA")["BBB"].idxmin()] # AAA BBB # 1 1 1 # 5 2 1 # 6 3 2
Вариант 2. Сортируем, а потом берем первое значение из каждой группы
>>> df.sort_values(by="BBB").groupby("AAA", as_index=False).first() # AAA BBB # 0 1 1 # 1 2 1 # 2 3 2