Pandas DataFrame.groupby: группировка и агрегация с примерами
Автор: Казачкин Даниил Михайлович · Обновлено
Метод DataFrame.groupby объединяет строки таблицы в группы и позволяет посчитать для каждой группы сумму, среднее, количество или сразу несколько показателей. Его удобно понимать как последовательность split-apply-combine: разделить строки по ключу, применить вычисление независимо к каждой части и собрать результат.
Первый рабочий пример groupby
Создадим маленькую таблицу продаж и получим отчёт по городам. Пример запускается после установки pandas командой python -m pip install pandas.
import pandas as pd
sales = pd.DataFrame(
{
"city": ["Москва", "Казань", "Москва", "Казань", "Тула"],
"category": ["книги", "книги", "курсы", "курсы", "книги"],
"revenue": [1200, 800, 2400, 1600, 700],
"orders": [2, 1, 3, 2, 1],
}
)
report = (
sales.groupby("city", as_index=False)
.agg(revenue_total=("revenue", "sum"), orders_total=("orders", "sum"))
.sort_values("revenue_total", ascending=False)
)
print(report.to_string(index=False))Ожидаемый результат содержит три строки: Москва с выручкой 3600 и пятью заказами, Казань с 2400 и тремя заказами, Тула с 700 и одним заказом. Параметр as_index=False оставляет city обычным столбцом, что удобно для последующей выгрузки или объединения таблиц. Без него ключ группы по умолчанию станет индексом результата.
Несколько ключей и именованные агрегации
В groupby(["city", "category"]) группа определяется парой значений. Именованная агрегация задаётся в форме новое_имя=("исходный_столбец", "функция"): так схема результата видна прямо в коде и не требует исправлять многоуровневые имена столбцов.
by_city_and_category = sales.groupby(
["city", "category"],
as_index=False,
sort=False,
).agg(
revenue_total=("revenue", "sum"),
revenue_mean=("revenue", "mean"),
row_count=("revenue", "size"),
)
print(by_city_and_category)size считает строки, включая строки с пропуском в измеряемом столбце, а count считает только непустые значения выбранного столбца. Поэтому перед отчётом важно решить, что именно означает «количество»: записи, непустые заказы или уникальные клиенты. Для уникальных значений подходит nunique.
agg, transform, filter и apply
agg уменьшает каждую группу до итоговой строки или нескольких показателей. transform возвращает результат той же длины, что исходные данные, поэтому им удобно добавлять групповой показатель к каждой записи. Например, доля строки в выручке своего города вычисляется без ручного join.
sales_with_share = sales.assign(
city_revenue=sales.groupby("city")["revenue"].transform("sum")
)
sales_with_share["city_share"] = (
sales_with_share["revenue"] / sales_with_share["city_revenue"]
)
print(sales_with_share[["city", "revenue", "city_share"]])filter оставляет или удаляет целые группы по условию. apply наиболее гибок, но пользовательская Python-функция обычно медленнее специализированных agg, transform и filter. Сначала ищите встроенную групповую операцию и используйте apply, только когда форма вычисления действительно не выражается проще.
Пропуски, порядок и категории
По умолчанию строки с NA в ключе группировки не образуют отдельную группу: действует dropna=True. Если такие строки должны попасть в контрольную группу, укажите dropna=False явно и затем подпишите пропущенный ключ. Иначе сумма отчёта может незаметно оказаться меньше суммы исходной таблицы.
sort=False не сортирует ключи групп и часто сохраняет более естественный порядок их первого появления. При этом порядок строк внутри каждой группы сохраняется независимо от этого параметра. Для категориальных ключей поведение набора групп связано с observed; если отчёт должен быть одинаковым при обновлении pandas, задайте нужное значение явно, а не полагайтесь на версионный default.
Полезная проверка после агрегации — сравнить контрольные итоги:
source_total = sales["revenue"].sum()
grouped_total = sales.groupby("city", dropna=False)["revenue"].sum().sum()
assert source_total == grouped_totalОшибки и диагностика
Если появляется KeyError, выведите sales.columns.tolist() и проверьте пробелы, регистр и точное имя ключа. Если результат имеет «лишний» индекс, используйте as_index=False сразу или осознанно вызовите reset_index() после агрегации. Если число строк не совпало с ожиданием, сравните groupby(...).size(), count() и количество пропусков через isna().sum().
Другая частая ошибка — выбрать столбец до агрегации и затем ожидать остальные поля: выражение sales.groupby("city")["revenue"] создаёт группировку Series, где столбца orders уже нет. Для нескольких метрик используйте именованную агрегацию на исходном DataFrame.
Практика: отчёт по заказам
Добавьте в таблицу столбцы manager и customer_id, а также одну строку с пропущенным городом. Постройте отчёт по паре city, manager: общая выручка, медианная выручка, число строк и число уникальных клиентов. Включите dropna=False, отсортируйте итог по выручке и проверьте контрольную сумму. Затем через transform("sum") добавьте каждой исходной строке долю в выручке менеджера.
Самопроверка: объясните, почему результат агрегации короче исходной таблицы, а результат transform имеет ту же длину. Смените size на count, добавьте пропуск в измеряемый столбец и зафиксируйте различие.
Что важно запомнить
groupbyопределяет группы, а итог появляется послеagg,transform,filterили другой операции.as_index=Falseдаёт табличный SQL-подобный результат с ключами в столбцах.sizeсчитает строки,count— непустые значения,nunique— уникальные значения.- Явные
dropna,sortиobservedделают смысл отчёта понятнее и устойчивее к изменениям данных и версии библиотеки.
Связанные исследования
- Split–apply–combine: исследовательская основа pandas GroupBy — Научная основа pandas GroupBy: split–apply–combine, различия agg, transform, filter и apply, пропущенные ключи, инварианты и границы статистических выводов.