«Сколько выручки принёс каждый город?», «какой средний чек по каждому каналу?», «сколько дождливых дней в каждом месяце?» Слова «каждый» и «по каждому» — признак самой частой операции в анализе данных: группировки.
Вручную — долго
Маленькая таблица: продажи кофе в трёх городах. Посчитать чашки по городам мы уже умеем — фильтром:
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
Что здесь произошло, показывает схема:
| city | cups |
|---|---|
| Омск | 30 |
| Тула | 12 |
| Омск | 18 |
| Пермь | 25 |
| Тула | 20 |
| Омск | 12 |
| Омск | 30 |
| Омск | 18 |
| Омск | 12 |
| Пермь | 25 |
| Тула | 12 |
| Тула | 20 |
| city | cups |
|---|---|
| Омск | 60 |
| Пермь | 25 |
| Тула | 32 |
Запись читается слева направо, по шагам:
sales.groupby("city")— разделить строки на группы: в одну группу попадают строки с одинаковым значениемcity.["cups"]— взять в каждой группе столбец, который будем считать..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
Сохраните в by_category выручку (столбец revenue) по каждой категории товаров. В top_category сохраните
название категории с наибольшей выручкой.
orders.groupby("category")["revenue"].sum().idxmax().Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
by_category = ...
top_category = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
by_category = orders.groupby("category")["revenue"].sum()
top_category = by_category.idxmax()В первом модуле мы знали только, что кофе — это 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()?
Сохраните в channel_revenue выручку по каждому каналу, в channel_orders — число заказов (разных order_id)
в каждом канале. Разделите одно на другое и сохраните средний чек по каналам в channel_check.
orders.groupby("channel")["order_id"].nunique().channel_revenue / channel_orders.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
channel_revenue = ...
channel_orders = ...
channel_check = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
channel_revenue = orders.groupby("channel")["revenue"].sum()
channel_orders = orders.groupby("channel")["order_id"].nunique()
channel_check = channel_revenue / channel_ordersНесколько столбцов и ни одного
Если после groupby указать список столбцов, получится таблица — итог по каждому:
orders.groupby("city")[["revenue", "quantity"]].sum()| revenue | quantity | |
|---|---|---|
| city | ||
| Екатеринбург | 423350 | 660 |
| Казань | 440150 | 675 |
| Москва | 1310110 | 2058 |
| Новосибирск | 324090 | 509 |
| Санкт-Петербург | 803720 | 1308 |
А если столбец не указать вовсе, pandas попытается посчитать итог по всем столбцам — включая текстовые:
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: два пропуска. Если пропусков нет, числа совпадают —
и поэтому ошибку легко не заметить.
Сохраните в city_temp средний дневной максимум (temp_max) по каждому городу, в warmest — название самого
тёплого города. В wind_gaps сохраните число пропусков в столбце wind_ms по каждому городу: число строк
группы минус число непустых значений.
weather.groupby("city")["temp_max"].mean(); самый тёплый — idxmax().weather.groupby("city").size() - weather.groupby("city")["wind_ms"].count().Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
city_temp = ...
warmest = ...
wind_gaps = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
city_temp = weather.groupby("city")["temp_max"].mean()
warmest = city_temp.idxmax()
wind_gaps = weather.groupby("city").size() - weather.groupby("city")["wind_ms"].count()Результат — обычный 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
Сохраните в product_qty, сколько штук каждого товара продано (сумма quantity по product), в top3 — три
товара с наибольшим числом штук. В customer_revenue сохраните выручку по каждому покупателю
(customer_id), в best_customer — код покупателя, который потратил больше всех.
product_qty.nlargest(3).orders.groupby("customer_id")["revenue"].sum() и idxmax().Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
product_qty = ...
top3 = ...
customer_revenue = ...
best_customer = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
product_qty = orders.groupby("product")["quantity"].sum()
top3 = product_qty.nlargest(3)
customer_revenue = orders.groupby("customer_id")["revenue"].sum()
best_customer = customer_revenue.idxmax()Итоги
таблица.groupby("ключ")["столбец"].sum()— разделить по ключу, взять столбец, посчитать итог в каждой группе.- Результат — Series: метки — значения ключа, значения — итоги.
- Итог может быть любым:
sum,mean,max,count,nunique… size()— строк в группе,count()— непустых значений столбца.- Сумма по группам должна сходиться с общей суммой — проверяйте.