Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → pandas groupby: агрегации на практике

pandas groupby: именованные агрегации, transform и типовые ошибки

Практика groupby в pandas: именованные агрегации agg, несколько функций, transform против agg, группировка по нескольким ключам, as_index и сортировка — с антипаттернами.

Прямой ответ

Современный стандарт groupby — именованные агрегации: df.groupby("город").agg(заказов=("id", "count"), средний_чек=("сумма", "mean")) — имена колонок результата задаются явно, код читается как SQL, и нет сюрпризов с мультииндекс-колонками из старых стилей. Три концепции покрывают 95% задач: agg (группа → одно значение), transform (группа → значение на каждую строку исходной формы — доли, отклонения от среднего группы), и filter (группа целиком остаётся/исключается). Ошибки, которые стоят часов: забытый as_index=False (ключ уходит в индекс и «теряется» при экспорте), groupby по неочищенной колонке (типы и категории — до группировки, см. очистку), и apply там, где хватает agg/transform (скорость — в статье про векторизацию).

Именованные агрегации: каноническая форма

Синтаксис имя_результата=("исходная_колонка", "функция") решает главные боли старого стиля: предсказуемые имена колонок (не ("сумма", "mean")-кортежи), разные функции для разных колонок в одном вызове, несколько функций для одной колонки (min_=("цена","min"), max_=("цена","max")). Доступные «функции» — строки методов Series ("sum", "nunique", "first") или вызываемые объекты (своя функция, лямбда — с оговоркой про скорость). Для нескольких ключей — список: groupby(["регион", "категория"]), и результат часто удобнее «плоским» (as_index=False или .reset_index() — при выгрузке в CSV/Excel мультииндекс всё равно разворачивается).

transform: агрегация, возвращающая форму

transform — недооценённый инструмент: функция применяется к группе, но результат растягивается обратно на исходные строки. Типовые задачи одним выражением: доля от группы (df["сумма"] / df.groupby("заказ")["сумма"].transform("sum")), отклонение от среднего группы, флаг «в группе больше N записей», заполнение пропусков медианой группы (fillna(groupby(...)["col"].transform("median")) — осмысленнее глобальной медианы; про пропуски вообще — в паттернах очистки). Правило различения agg/transform простое: результат нужен «по группам» (сводная таблица) — agg; «в исходных строках» (обогащение) — transform.

Несколько ключей, порядки и сортировка

Группировка по datetime — через производные колонки (df["месяц"] = df["дата"].dt.to_period("M") — о корректности дат и зон в статье про datetime) или pd.Grouper(key="дата", freq="M") прямо в groupby. Полезные флаги: sort=False (не сортировать ключи — быстрее, когда порядок не важен), observed=True для категориальных ключей (не плодить пустые комбинации категорий — на мультиключах разница огромна), dropna=False (группы из NA-ключа, когда «неизвестно» — осмысленная категория). Сводные таблицы — частный случай: pivot_table(values=..., index=..., columns=..., aggfunc=...) для двумерных отчётов вместо ручного unstack.

apply в groupby: когда оправдан

groupby(...).apply(func) даёт произвольную функцию над группой-датафреймом — мощно и медленно (группа материализуется, Python-интерпретатор на группу). Оправдан для genuinely-сложной логики: топ-N внутри группы (хотя часто хватает nlargest после sort), кастомные статистики без векторной формы, «подгонка модели на каждой группе». Перед apply честно проверьте альтернативы: именованные agg покрывают большинство «сложных» случаев, nlargest/head внутри групп — sort_values().groupby().head(N), кумулятивы — cumsum/cummax через groupby напрямую. И всегда: apply на миллионах групп — профиль сначала (подходы — в статье про производительность pandas).

Антипаттерны и диагностика

Первый — группировка по «грязному» ключу: «Москва» и «москва » — две группы; унификация строк/категорий до groupby обязательна. Второй — count против size: count не считает NA, size считает строки — для «сколько записей в группе» обычно нужен size (или count по заведомо непустой колонке). Третий — молчаливое исчезновение колонок: agg по конкретным колонкам забывает остальные — проверяйте состав результата, а не только его форму. Четвёртый — сравнение групп без проверки размеров: «среднее по группе из 2 записей» против «группы из 2000» — добавляйте размер группы колонкой (.size() рядом с агрегатами) и фильтруйте малые группы осознанно. Пятый — agg-функция с побочными эффектами: apply/agg должны быть чистыми, иначе порядок групп меняет результат.

Практический сценарий: ежемесячная сводка продаж одним выражением

Задача из реальной практики: по таблице заказов посчитать за каждый месяц количество заказов, выручку, средний чек и долю возвращённых — и тут же обогатить исходные строки отклонением от среднего чека месяца. Решение собирается в два приёма. Сводка — именованные агрегации по производному периоду: df.assign(месяц=df["дата"].dt.to_period("M")).groupby("месяц").agg(заказов=("id", "size"), выручка=("сумма", "sum"), средний_чек=("сумма", "mean"), возвраты=("возврат", "mean")) — одна строка, читающаяся как техническое задание; size вместо count здесь сознательно (считаем записи, а не непустые значения). Обогащение — transform: df["откл_от_месяца"] = df["сумма"] - df.groupby(df["дата"].dt.to_period("M"))["сумма"].transform("mean") — результат той же формы, что исходная таблица, его можно фильтровать (df[df["откл_от_месяца"] > 0] — заказы выше среднего своего месяца) и рисовать. Ключевой момент — оба выражения работают по одному ключу-периоду, вычисленному одинаково; если бы месяц считался в двух местах по-разному (например, с разными часовыми поясами), сводка и обогащение разъехались бы незаметно. Проверка результата — контрольное соотношение: сумма выручки по сводке равна df["сумма"].sum() по исходной таблице; расхождение означает потерянные строки (NA в ключе, фильтры) и ловится одной строкой ассерта.

FAQ

Как получить и агрегаты, и исходные строки? transform (обогащение) или merge агрегатов обратно по ключу (сводка рядом с данными); приём merge и его ловушки — в статье про merge/join.

Чем groupby отличается от pivot_table? pivot_table — надстройка для двумерных сводок (строки × колонки) с заполнением пропусков; groupby — общий механизм. Для «таблица как в Excel» — pivot_table, для пайплайна данных — groupby.

Можно ли группировать по нескольким колонкам с разными функциями? Да: именованные agg задают колонку и функцию попарно — groupby(["a","b"]).agg(x=("c","sum"), y=("d","max")) — каждая пара независима.

Почему groupby медленный на категориальном ключе? Без observed=True pandas строит декартово произведение категорий (включая ненаблюдаемые комбинации) — на двух ключах по 100 категорий это 10 000 групп вместо фактических сотен; флаг решает.

Источники: руководство пользователя pandas (groupby, basics) — ссылки в шапке.