groupby и transform: групповые признаки на уровне строк и фильтрация групп
Содержание курса
Фильтрация строк по условию на групповом агрегате
До этого момента transform использовался для добавления нового столбца с групповым показателем. Но та же идея — результат той же длины, что исходный DataFrame — открывает второй сценарий: фильтрацию строк по условию на уровне группы, а не на уровне строки.
Представь задачу: есть таблица продаж по магазинам, и нужно оставить только строки из магазинов, чья суммарная выручка превышает пороговое значение. Наивное решение — посчитать groupby('store')['revenue'].agg('sum'), получить сжатую таблицу, отфильтровать там нужные магазины, а потом вернуться к исходному DataFrame и выкинуть строки через merge или isin. Это работает, но требует нескольких шагов.
С transform паттерн короче:
import pandas as pd
df = pd.DataFrame({
'store': ['A', 'B', 'A', 'C', 'B', 'C', 'A'],
'revenue': [100, 50, 200, 30, 80, 20, 150]
})
group_sum = df.groupby('store')['revenue'].transform('sum')
mask = group_sum > 200
df_filtered = df.loc[mask]
print(df_filtered)
Вывод:
store revenue 0 A 100 2 A 200 6 A 150
Что происходит по шагам. transform('sum') для каждой строки возвращает суммарную выручку её магазина: строки магазина A получают 450, строки B — 130, строки C — 50. Результат — Series длиной 7 с теми же индексами, что у df. Сравнение > 200 превращает её в булевую маску той же длины. df.loc[mask] отбирает только строки, для которых маска истинна, — то есть все строки магазина A.
Ключевое здесь: маска живёт в пространстве строк, а решение принято на уровне группы. Магазины B и C не проходят порог целиком — ни одна их строка не попадает в результат, даже если отдельные транзакции большие. Это именно групповая фильтрация, а не строчная.
Паттерн одинаков для любого агрегата. Хочешь оставить группы, где среднее выше порога — меняешь 'sum' на 'mean' и подбираешь нужный threshold. Хочешь отбросить группы с единственной строкой — transform('count') > 1. Маска в любом случае строится за одно выражение и напрямую передаётся в df.loc без промежуточных таблиц.
