Нашли ошибку?
Скачать чистый
урок 1 из 5≈ 20 минупражнения: 0 из 4

Группировка

Разделить строки на группы, посчитать итог в каждой и собрать результаты: groupby(...)[...].sum() и другие агрегаты. Чем size отличается от count и почему после groupby нужно выбрать столбец.

Python запустится при первом выполнении

«Сколько выручки принёс каждый город?», «какой средний чек по каждому каналу?», «сколько дождливых дней в каждом месяце?» Слова «каждый» и «по каждому» — признак самой частой операции в анализе данных: группировки.

Вручную — долго

Маленькая таблица: продажи кофе в трёх городах. Посчитать чашки по городам мы уже умеем — фильтром:

[ ]Три города — три строки кода
import pandas as pd

sales = pd.DataFrame({
    "city": ["Омск", "Тула", "Омск", "Пермь", "Тула", "Омск"],
    "cups": [30, 12, 18, 25, 20, 12],
})
print(sales.loc[sales["city"] == "Омск", "cups"].sum())
print(sales.loc[sales["city"] == "Пермь", "cups"].sum())
print(sales.loc[sales["city"] == "Тула", "cups"].sum())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
60
25
32

Три города — три почти одинаковые строки. Сто городов — сто строк. И нужно заранее знать, какие города есть.

groupby: разделить, посчитать, объединить

То же самое одной строкой:

[ ]Чашки по городам
sales.groupby("city")["cups"].sum()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
city
Омск     60
Пермь    25
Тула     32
Name: cups, dtype: int64

Что здесь произошло, показывает схема:

Запись читается слева направо, по шагам:

  1. sales.groupby("city") — разделить строки на группы: в одну группу попадают строки с одинаковым значением city.
  2. ["cups"] — взять в каждой группе столбец, который будем считать.
  3. .sum() — посчитать итог в каждой группе и объединить итоги в один результат.

Результат — Series. Его метки — значения столбца, по которому группировали, отсортированные по порядку; значения — итоги групп:

[ ]Что получилось
totals = sales.groupby("city")["cups"].sum()
print(type(totals).__name__)
print(totals.index.tolist())
print(totals["Тула"])
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
Series
['Омск', 'Пермь', 'Тула']
32

Теперь настоящие данные — выручка магазина по городам:

[ ]Выручка по городам
orders = pd.read_csv("data/shop_orders.csv")
orders["revenue"] = orders["price"] * orders["quantity"]
orders.groupby("city")["revenue"].sum()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
city
Екатеринбург        423350
Казань              440150
Москва             1310110
Новосибирск         324090
Санкт-Петербург     803720
Name: revenue, dtype: int64

Полезная привычка — проверить, что ничего не потерялось: сумма по группам должна совпасть с общей суммой.

[ ]Сумма групп равна общему итогу
by_city = orders.groupby("city")["revenue"].sum()
print(by_city.sum())
print(orders["revenue"].sum())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
3301420
3301420
[ ]Упражнение 1Выручка по категориям решено

Сохраните в by_category выручку (столбец revenue) по каждой категории товаров. В top_category сохраните название категории с наибольшей выручкой.

by_category = ...
top_category = ...

В первом модуле мы знали только, что кофе — это 40 % строк. Теперь видно главное: кофе — это 58 % денег.

Пояснение с ответом появится, когда решите упражнение «Выручка по категориям».

Не только сумма

После группировки можно вызвать любой итог, который есть у Series: mean, median, min, max, count, nunique, std…

[ ]Самая высокая цена в категории и средняя выручка строки по каналам
print(orders.groupby("category")["price"].max())
print(orders.groupby("channel")["revenue"].mean().round(1))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
category
Аксессуары    3200
Кофе          1450
Посуда        2400
Сладости       320
Чай            540
Name: price, dtype: int64
channel
маркетплейс    1397.2
приложение     1319.3
сайт           1339.4
Name: revenue, dtype: float64

nunique считает разные значения внутри группы. Так находят число заказов: строк в городе много, а разных order_id меньше.

[ ]Сколько заказов в каждом городе
orders.groupby("city")["order_id"].nunique()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
city
Екатеринбург       203
Казань             214
Москва             627
Новосибирск        163
Санкт-Петербург    369
Name: order_id, dtype: int64
Вопрос

В столбце channel три разных значения. Сколько значений в результате orders.groupby("channel")["price"].max()?

[ ]Упражнение 2Средний чек по каналам решено

Сохраните в channel_revenue выручку по каждому каналу, в channel_orders — число заказов (разных order_id) в каждом канале. Разделите одно на другое и сохраните средний чек по каналам в channel_check.

channel_revenue = ...
channel_orders = ...
channel_check = ...

Несколько столбцов и ни одного

Если после groupby указать список столбцов, получится таблица — итог по каждому:

[ ]Выручка и штуки по городам
orders.groupby("city")[["revenue", "quantity"]].sum()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
revenue quantity
city
Екатеринбург 423350 660
Казань 440150 675
Москва 1310110 2058
Новосибирск 324090 509
Санкт-Петербург 803720 1308

А если столбец не указать вовсе, pandas попытается посчитать итог по всем столбцам — включая текстовые:

[ ]Среднее по всем столбцам сразуошибка: TypeError
orders.groupby("city").mean()
Ошибкасохранённый · нажмите «Выполнить», чтобы получить свой
TypeError: dtype 'str' does not support operation 'mean'

size и count

Сколько строк в каждой группе? Для этого есть два похожих метода, и разница между ними важна:

  • size() — число строк в группе; столбец выбирать не нужно;
  • count() — число непустых значений выбранного столбца.
[ ]Дней в году и дней с известной температурой
weather = pd.read_csv("data/weather.csv")
print(weather.groupby("city").size())
print(weather.groupby("city")["temp_max"].count())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
city
Казань             365
Москва             365
Новосибирск        365
Санкт-Петербург    365
Сочи               365
dtype: int64
city
Казань             363
Москва             365
Новосибирск        365
Санкт-Петербург    365
Сочи               365
Name: temp_max, dtype: int64

В Казани 365 строк, но temp_max известна только в 363: два пропуска. Если пропусков нет, числа совпадают — и поэтому ошибку легко не заметить.

[ ]Упражнение 3Погода по городам решено

Сохраните в city_temp средний дневной максимум (temp_max) по каждому городу, в warmest — название самого тёплого города. В wind_gaps сохраните число пропусков в столбце wind_ms по каждому городу: число строк группы минус число непустых значений.

city_temp = ...
warmest = ...
wind_gaps = ...

Результат — обычный Series

С результатом группировки можно делать всё, что умеет Series: сортировать, брать первые, фильтровать.

[ ]Пять товаров с наибольшей выручкой
orders.groupby("product")["revenue"].sum().sort_values(ascending=False).head(5)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
product
Эфиопия 250 г             426600
Бразилия 1 кг             375390
Эспрессо-смесь 1 кг       372650
Колумбия 250 г            372600
Кофе без кофеина 250 г    367200
Name: revenue, dtype: int64
[ ]Упражнение 4Лидеры решено

Сохраните в product_qty, сколько штук каждого товара продано (сумма quantity по product), в top3 — три товара с наибольшим числом штук. В customer_revenue сохраните выручку по каждому покупателю (customer_id), в best_customer — код покупателя, который потратил больше всех.

product_qty = ...
top3 = ...
customer_revenue = ...
best_customer = ...

Итоги

  • таблица.groupby("ключ")["столбец"].sum() — разделить по ключу, взять столбец, посчитать итог в каждой группе.
  • Результат — Series: метки — значения ключа, значения — итоги.
  • Итог может быть любым: sum, mean, max, count, nunique…
  • size() — строк в группе, count() — непустых значений столбца.
  • Сумма по группам должна сходиться с общей суммой — проверяйте.

Подробнее в справочнике

Нашли ошибку?