Группировка
«Выручка по каждому магазину», «средний балл по каждому классу», «сколько заказов в каждом городе» — все эти вопросы устроены одинаково: разбить строки на группы по значению столбца и посчитать что-то внутри каждой группы. В 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
Что происходит:
- разделить: строки раскладываются на группы по значению столбца «магазин»;
- применить: в каждой группе столбец «сумма» суммируется;
- объединить: результаты собираются в 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).