ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

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, добавьте пропуск в измеряемый столбец и зафиксируйте различие.

Что важно запомнить

Источники