Коротко
groupbyработает по схеме разделение → применение → объединение (split-apply-combine): строки делятся на группы по ключу, к каждой группе применяется функция, результаты собираются в одну таблицу.- Сам
df.groupby("city")ничего не считает — это «ленивый» объект; результат появляется после агрегации:.sum(),.mean(),.agg(...). - Ключи группировки по умолчанию уходят в индекс результата и сортируются.
size()— число строк в группе,count()— число непустых значений в столбце.- Строки с
NaNв ключе по умолчанию выпадают (dropna=True).
| city | cups |
|---|---|
| Омск | 30 |
| Тула | 12 |
| Омск | 18 |
| Пермь | 25 |
| Тула | 20 |
| Омск | 12 |
| Омск | 30 |
| Омск | 18 |
| Омск | 12 |
| Пермь | 25 |
| Тула | 12 |
| Тула | 20 |
| city | cups |
|---|---|
| Омск | 60 |
| Пермь | 25 |
| Тула | 32 |
Синтаксис
df.groupby(by, as_index=True, sort=True, dropna=True, observed=True)
df.groupby(by)["столбец"].sum() # → Series
df.groupby(by)[["a", "b"]].mean() # → DataFrame
df.groupby(by)["столбец"].agg(["sum", "mean"])Параметры
byключ: имя столбца, список имён, Series или функция от метки индексаобязательныйas_indexTrue — ключи в индекс результата; False — в обычные столбцыTruesortсортировать группы по ключу; False — порядок первого появленияTruedropnaвыбрасывать строки, у которых ключ — NaNTrueobservedдля категориальных ключей: только встречающиеся категорииTrueПримеры
# продажи сети кофеен за день; у одной записи нет оценки
sales = pd.DataFrame({
"city": ["Омск", "Тула", "Омск", "Пермь", "Тула", "Омск"],
"drink": ["латте", "капучино", "капучино", "латте", "латте", "латте"],
"cups": [30, 12, 18, 25, 20, 12],
"rating": [4.5, 4.0, None, 4.8, 3.9, 4.2],
})sales.groupby("city")["cups"].sum()city Омск 60 Пермь 25 Тула 32 Name: cups, dtype: int64
sales.groupby("city")[["cups", "rating"]].mean()cups rating city Омск 20.0 4.35 Пермь 25.0 4.80 Тула 16.0 3.95
mean пропускает NaN: у Омска средняя оценка посчитана по двум записям из трёх.
sales.groupby("city")["cups"].agg(["sum", "mean", "max"])sum mean max city Омск 60 20.0 30 Пермь 25 25.0 25 Тула 32 16.0 20
Подробнее — в статье agg и именованная агрегация.
sales.groupby(["city", "drink"])["cups"].sum()city drink
Омск капучино 18
латте 42
Пермь латте 25
Тула капучино 12
латте 20
Name: cups, dtype: int64sales.groupby(["city", "drink"], as_index=False)["cups"].sum()city drink cups 0 Омск капучино 18 1 Омск латте 42 2 Пермь латте 25 3 Тула капучино 12 4 Тула латте 20
То же самое даёт .reset_index() после агрегации.
groups = sales.groupby("city")
pd.DataFrame({
"size": groups.size(), # все строки группы
"count": groups["rating"].count(), # только непустые значения
})size count city Омск 3 2 Пермь 1 1 Тула 2 2
sales.groupby("city", sort=False)["cups"].sum() # в порядке первого появленияcity Омск 60 Тула 32 Пермь 25 Name: cups, dtype: int64
for city, part in sales.groupby("city"):
print(city, "→ строк:", len(part), "| чашек:", part["cups"].sum())Омск → строк: 3 | чашек: 60 Пермь → строк: 1 | чашек: 25 Тула → строк: 2 | чашек: 32
Цикл удобен, чтобы понять, как разбились данные. Для вычислений используйте агрегации — они намного быстрее.
Подводные камни
mean() по всему DataFrame падает на текстовых столбцах
Агрегации не отбрасывают молча нечисловые столбцы. Выберите нужные столбцы или передайте numeric_only=True. При этом sum() на строках не падает, а склеивает их — тоже вряд ли то, что нужно.
sales.groupby("city").mean() # столбец drink — строки, среднего у них нетTypeError: dtype 'str' does not support operation 'mean'
Строки с пустым ключом исчезают
Сумма по группам может не сойтись с общей суммой: строки с NaN в ключе отброшены. Чтобы увидеть их отдельной группой, передайте dropna=False.
deals = pd.DataFrame({
"manager": ["Аня", None, "Аня", "Борис"],
"amount": [300, 500, 200, 400],
})
print(deals.groupby("manager")["amount"].sum())
deals.groupby("manager", dropna=False)["amount"].sum()manager Аня 500 Борис 400 Name: amount, dtype: int64 manager Аня 500 Борис 400 NaN 500 Name: amount, dtype: int64
Пустые категории не попадают в результат
visits = pd.DataFrame({
"day": pd.Categorical(["пн", "ср", "пн"], categories=["пн", "вт", "ср"]),
"guests": [40, 55, 35],
})
print(visits.groupby("day")["guests"].sum())
visits.groupby("day", observed=False)["guests"].sum() # все категории, даже пустыеday пн 75 ср 55 Name: guests, dtype: int64 day пн 75 вт 0 ср 55 Name: guests, dtype: int64
MultiIndex там, где его не ждали
Группировка по нескольким ключам даёт MultiIndex. Если дальше нужны обычные столбцы — например, для merge или сохранения в CSV, — передайте as_index=False или вызовите .reset_index().