filter групп, pivot_table и crosstab
groupby умеет не только агрегировать, но и отбирать целые группы, а pivot_table и crosstab превращают длинные данные в наглядные сводки.
filter оставляет группы целиком по условию на группу. pivot_table — сводная таблица: значения раскладываются по строкам и столбцам с агрегацией.
filter: отбор целых групп
Иногда нужно оставить не отдельные строки, а целые группы, удовлетворяющие условию: «только города, где больше 100 заказов», «только клиенты с суммарной покупкой выше порога». Это делает filter — он получает под-DataFrame группы и должен вернуть True (оставить всю группу) или False (выбросить целиком).
# оставить только города, где суммарный чек > 1500
df.groupby("город").filter(lambda g: g["чек"].sum() > 1500)
Покажем механику на чистом Python — сначала считаем агрегат по группе, потом решаем судьбу всей группы:
orders = [
("Москва", 1200), ("Сочи", 800), ("Москва", 1800),
("Сочи", 1000), ("Москва", 600), ("Тверь", 300),
]
# split
groups = {}
for city, chek in orders:
groups.setdefault(city, []).append(chek)
# условие на ВСЮ группу: суммарный чек > 1500
keep_cities = {c for c, v in groups.items() if sum(v) > 1500}
print("оставляем города:", sorted(keep_cities))
# filter: оставляем строки, чья группа прошла условие
result = [(c, chek) for c, chek in orders if c in keep_cities]
for row in result:
print(row)
Вывод:
оставляем города: ['Москва', 'Сочи']
('Москва', 1200)
('Сочи', 800)
('Москва', 1800)
('Сочи', 1000)
('Москва', 600)
Москва (сумма 3600) и Сочи (1800) прошли порог 1500, поэтому сохранились все их строки. Тверь (300) не прошла — её единственная строка пропадает целиком. Здесь важно увидеть принцип: filter решает судьбу всей группы по агрегату на группу, а не каждой строки по отдельности.
pivot_table: сводная таблица
pivot_table — это группировка сразу по двум измерениям: одно идёт в строки, другое в столбцы, а на пересечении — агрегат. Это та самая «сводная таблица», что в Excel, но программно и воспроизводимо.
df.pivot_table(
index="город", # строки
columns="месяц", # столбцы
values="чек", # что агрегируем
aggfunc="sum", # как агрегируем (по умолчанию mean)
fill_value=0, # чем заполнить пустые пересечения
margins=True, # добавить строку/столбец "All" с итогами
)
# янв фев All
# город
# Москва 1800 1800 3600
# Сочи 800 1000 1800
# All 2600 2800 5400
Ключевые параметры: aggfunc (можно список: ["sum", "mean"]), fill_value (вместо NaN в пустых ячейках), margins (итоги по строкам и столбцам). По сути это groupby([index, columns]).agg(...), но сразу разложенный в матрицу.
crosstab: частотная таблица
Когда нужно просто посчитать, сколько раз встречается каждая комбинация двух категорий (таблица сопряжённости), удобнее crosstab — частный случай pivot_table для подсчёта частот:
pd.crosstab(df["город"], df["тип_оплаты"])
# карта наличные
# город
# Москва 5 2
# Сочи 3 4
# доли по строкам вместо абсолютных чисел
pd.crosstab(df["город"], df["тип_оплаты"], normalize="index")
Параметр normalize превращает счётчики в доли (по строкам, столбцам или всей таблице) — удобно для анализа структуры. По сути crosstab — это удобная обёртка над pivot_table с aggfunc="count": если передать ему values= и свой aggfunc=, он перестанет просто считать частоты и начнёт агрегировать значения, превратившись в обычную сводную таблицу.
pivot_table и groupby — одно и то же под разным углом
Полезно увидеть, что pivot_table — это «разложенный в матрицу» groupby по двум ключам. Эти два кода эквивалентны по содержанию, отличается только форма:
# длинный результат (MultiIndex: город × месяц)
df.groupby(["город", "месяц"])["чек"].sum()
# тот же результат, развёрнутый в матрицу
df.groupby(["город", "месяц"])["чек"].sum().unstack()
# и это же — одной командой
df.pivot_table(index="город", columns="месяц", values="чек", aggfunc="sum")
Связь с разделом про реструктуризацию прямая: pivot_table = groupby(...).agg(...) + unstack(). Понимание этого избавляет от заучивания: вы выбираете форму (длинную или широкую) под задачу, а не запоминаете отдельные «магические» функции.
Работа с результатом группировки
Результат groupby/pivot_table — обычный DataFrame или Series, но с ключами группировки в индексе (часто MultiIndex). Полезные приёмы дальнейшей работы:
res = df.groupby("город")["чек"].sum()
res.reset_index() # вернуть индекс-город обратно в столбец
res.sort_values(ascending=False) # отсортировать по значению
res.nlargest(3) # топ-3 группы
g = df.groupby(["город", "месяц"])["чек"].sum()
g.unstack() # развернуть второй уровень индекса в столбцы
reset_index() часто нужен, чтобы отдать результат в график или дальнейший merge. unstack() превращает MultiIndex-результат группировки в широкую таблицу — фактически тот же эффект, что у pivot_table (подробнее о stack/unstack — в разделе про реструктуризацию).
Подводные камни
- filter возвращает строки, а не группы. На выходе — отфильтрованный DataFrame из исходных строк, а не сводка по группам; не путайте с
agg. - NaN в pivot_table. Пустые пересечения дают
NaN; используйтеfill_value=0, если это уместно (но не для средних — там ноль исказит смысл). - aggfunc по умолчанию — mean. Частая ошибка: ждать сумму, а получить среднее. Указывайте
aggfuncявно. - crosstab считает частоты, а не значения. Чтобы агрегировать значения, передайте
values=иaggfunc=— тогда это уже почти pivot_table.
Лучшие практики
- Нужно отобрать целые группы по условию на группу — это
filter. - Сводка «строки × столбцы × агрегат» —
pivot_tableс явнымaggfuncи осмысленнымfill_value. - Подсчёт комбинаций категорий —
crosstab, при необходимости сnormalize. - Перед выгрузкой/визуализацией приводите результат в порядок:
reset_index(), сортировка,unstack().
Итог
filterоставляет или выбрасывает группы целиком по условию на группу.pivot_table— сводка по двум измерениям с агрегацией и итогами.crosstab— частотная таблица комбинаций категорий, с опцией долей.- Результат группировки — DataFrame с ключами в индексе;
reset_index/unstackприводят его к нужной форме.