</>
Topic 5 of 7

Course materials are currently available in Russian only.

Группировка

«Выручка по каждому магазину», «средний балл по каждому классу», «сколько заказов в каждом городе» — все эти вопросы устроены одинаково: разбить строки на группы по значению столбца и посчитать что-то внутри каждой группы. В pandas для этого есть groupby.

Разделить — применить — объединить

import pandas as pd

sales = pd.DataFrame({
    "магазин": ["А", "Б", "А", "В", "Б", "А"],
    "сумма":   [100, 250, 300, 50, 150, 200],
})

print(sales.groupby("магазин")["сумма"].sum())
магазин
А    600
Б    400
В     50
Name: сумма, dtype: int64

Что происходит:

  1. разделить: строки раскладываются на группы по значению столбца «магазин»;
  2. применить: в каждой группе столбец «сумма» суммируется;
  3. объединить: результаты собираются в Series, где подписи — ключи групп.

Вместо sum можно использовать mean, min, max, count, median и другие агрегаты.

Размер групп

print(sales.groupby("магазин").size())   # сколько строк в каждой группе
print(sales["магазин"].value_counts())   # почти то же, но отсортировано по частоте

Несколько агрегатов сразу

agg применяет несколько функций и возвращает таблицу:

stats = sales.groupby("магазин")["сумма"].agg(["sum", "mean", "count"])
print(stats)
         sum        mean  count
магазин
А        600  200.000000      3
Б        400  200.000000      2
В         50   50.000000      1

С именованными агрегатами можно сразу дать столбцам понятные имена и считать разные столбцы:

sales.groupby("магазин").agg(выручка=("сумма", "sum"), чеков=("сумма", "count"))

Ключ группы — в столбец

Результат groupby хранит ключи групп в индексе. Чтобы снова сделать их обычным столбцом (например, для сортировки по нескольким столбцам), вызывают reset_index():

stats = stats.reset_index()
stats = stats.sort_values("sum", ascending=False)

По умолчанию группы упорядочены по возрастанию ключа. С groupby(..., sort=False) — в порядке первого появления.

Группировка по нескольким столбцам

orders = pd.DataFrame({
    "город": ["Уфа", "Уфа", "Омск", "Уфа"],
    "месяц": [1, 2, 1, 1],
    "сумма": [10, 20, 30, 40],
})
print(orders.groupby(["город", "месяц"])["сумма"].sum())

Ключом группы становится пара значений, а порядок — сначала по городу, потом по месяцу.

Первые строки в каждой группе

Чтобы выбрать «лучшего в каждой группе», удобно сначала отсортировать таблицу так, чтобы нужная строка в каждой группе шла первой, а потом взять первую строку каждой группы:

students = students.sort_values(["балл"], ascending=False)
top2 = students.groupby("класс").head(2)    # по два лучших в каждом классе

head(k) у сгруппированной таблицы оставляет первые k строк каждой группы в текущем порядке строк.

Ещё один способ — idxmax: df.loc[df.groupby("класс")["балл"].idxmax()] выбирает в каждой группе строку с максимумом. Но при равных максимумах idxmax берёт первую по таблице, а не по какому-то другому правилу — иногда это не то, что нужно.

Тип столбца при чтении

Если значения столбца похожи на числа, но на самом деле это коды (номера классов «7», почтовые индексы «01234»), read_csv может превратить их в числа и потерять ведущие нули или порядок сортировки. Тип можно указать явно: pd.read_csv(..., dtype={"класс": str}).

Итог

  • df.groupby("ключ")["столбец"].sum() — агрегат по группам; также mean, min, max, count;
  • size() — размеры групп, agg([...]) и именованные агрегаты — несколько агрегатов;
  • reset_index() превращает ключи групп в столбцы;
  • groupby([a, b]) — группы по паре значений;
  • «первый в каждой группе» — сортировка + groupby(...).head(1).