Към съдържанието
wannacode

Групи: groupby

Накратко: groupby в pandas разделя редовете по ключ, смята във всяка група (sum, mean, agg) и събира резултата в една таблица.

Проблем

„Колко е средната поръчка?“

Управителят пита колко пазарува средно един клиент на поръчка. Имаш поръчките за годината и редовете им: всеки ред е един продукт в една поръчка, с бройката и цената. Отказаните поръчки не са оборот, затова ги махаш с маска, както в урока за филтрите:

Pythonданни: таблиците на магазина

Редовете са 25 069, а поръчките 10 459. Средната на колоната revenue е 4,44 €, но това е средният ред, а не средната поръчка. Една поръчка има няколко реда и за нея трябва първо да събереш редовете ѝ.

Трябва ти сбор за всяка поръчка: 10 459 числа от 25 069 реда. С for и речник става, но е бавно и дълго. pandas има за това една операция.

Идеята

Раздели, сметни, събери

groupby('order_id') разделя редовете на групи: по една група за всяка различна стойност на order_id. После избираш колона и сметка: ['revenue'].sum() събира оборота във всяка група. Накрая pandas слага резултатите в една Series с индекс order_id.

Това се казва split-apply-combine: раздели, сметни, събери. Ето го върху първите 7 реда:

Pythonданни: таблиците на магазина

Поръчка 1 има един ред (31,6 €), а 2, 3 и 4 имат по два. Поръчка 3 е 37,5 + 8,4 = 45,9 €. От 7 реда излизат 4 числа, по едно на група. Ключът order_id е индексът на резултата и затова стои под името на колоната.

Вместо sum може всяко обобщение от NumPy: mean, min, max, count.

Виж го

Как groupby минава през редовете

Същите 7 реда бавно: всеки ред отива в групата на своя order_id, групата се събира в едно число и числата се подреждат в резултата.

import pandas as pd
items = pd.read_csv('order_items.csv').head(7)
items['revenue'] = items['quantity'] * items['unit_price']
print(items[['order_id', 'revenue']])
items.groupby('order_id')['revenue'].sum()
Стъпка по стъпка, с думи
  1. 7 реда от order_items с оборота на всеки ред: поръчки 1, 2, 3 и 4.
  2. Раздели (split): groupby('order_id') слага всеки ред в групата на своя order_id.
  3. 4 различни order_id, 4 групи. Поръчка 1 има един ред, другите по 2.
  4. Сметни (apply): ['revenue'].sum() във всяка група. Поръчка 3: 37,5 + 8,4 = 45,9.
  5. Събери (combine): 4-те сбора стават една Series с индекс order_id.
  6. 7 реда влязоха, 4 числа излязоха: по едно за всяка група.

Пробвай

Кой продукт се поръчва най-много

В 60-те поръчки от order_items.csv: колко бройки са поръчани от всеки продукт? Запиши ги в units, а в top3 трите продукта с най-много бройки.

Pythonданни: таблиците на магазина

Идеята

Няколко сметки наведнъж: agg

Често за всяка група ти трябват няколко числа. agg приема списък от сметки и дава по колона за всяка:

Pythonданни: таблиците на магазина

Имената на колоните са имената на сметките. За отчет е по-добре да ги кръстиш сам. Това са именувани обобщения (named aggregation): ново_име=('колона', 'сметка'), и всяка може да е от различна колона:

Pythonданни: таблиците на магазина

Поръчка 3 има 2 реда, 7 броя и 45,9 € оборот. Таблицата има по ред за поръчка и по колона за всеки въпрос, който си задал.

Идеята

Броят: size или count

Двата брояча изглеждат еднакво, но броят различни неща. size() брои редовете във всяка група. count() брои попълнените стойности във всяка колона: NaN не се брои. Ето клиентите по година на регистрация:

Pythonданни: таблиците на магазина

През 2025 г. са се регистрирали 16 клиенти, но в колоната city има 15 стойности: при един клиент градът липсва. Ако искаш „колко клиенти“, size() е верният отговор. count() по колона отговаря на „за колко знаем града“.

1. Клиентите от 2025 г. са 16, а при един от тях градът липсва. Какво показва customers.groupby('year')['city'].count() за 2025?

Идеята

Няколко ключа

Ключът може да е списък от колони. Тогава групата е всяка двойка стойности. Ето бройките за годината по продукт и по цена:

Pythonданни: таблиците на магазина

Продуктите са 25, а групите 26: един продукт се е продавал на две цени. Това е кафето (продукт 17): 245 пакета по 13,90 € и 444 пакета по 14,90 €, след като поскъпна на 1 март. Резултатът има индекс на две нива, и units.loc[17] взима групите на кафето.

По-точно, за любопитните

Групите с NaN в ключа изпадат: подаръчната карта е без категория и products.groupby('category_id') я пропуска тихо. groupby('category_id', dropna=False) я пази като група NaN. Провери с len колко реда влизат и колко излизат от групите.

Идеята

Числото на групата на всеки ред: transform

Понякога не искаш да свиеш групата, а да сложиш числото ѝ до всеки неин ред. Например: колко е скъп продуктът спрямо средната цена в своята категория. transform('mean') смята средната на групата и я повтаря на всеки ред, като пази реда и индекса на таблицата:

Pythonданни: таблиците на магазина

transform дава 25 числа, по едно на продукт, а mean() дава 6, по едно на категория. Затова колоната cat_avg пасва направо в таблицата. Кафето струва 14,90 €, а средната в напитките е 5,35 €: 2,79 пъти повече. Прахът за пране е 2,19 пъти над средната за почистване.

По-точно, за любопитните

Резултатът на mean() има индекс category_id, а таблицата products има индекс 0, 1, 2… Ако го присвоиш като колона, pandas подрежда по индекса, а не по категорията, и почти всичко става NaN. transform пази индекса на таблицата. Подаръчната карта е без категория и затова cat_avg при нея е NaN.

Пробвай

Делът на реда в поръчката

За всеки ред от order_items.csv: каква част от оборота на поръчката си носи? Запиши в items колона order_total с оборота на цялата поръчка и колона share с дела на реда.

Pythonданни: таблиците на магазина

Идеята

Ключът като колона: as_index=False

Резултатът на groupby има ключа в индекса. За да го филтрираш, да го слепиш с друга таблица или да го сложиш на графика, ти трябва обикновена колона. Има два начина, с един и същ резултат:

Pythonданни: таблиците на магазина

as_index=False казва това още при групирането, а reset_index() го прави после: индексът става колона, а редовете се номерират 0, 1, 2… Колоните са order_id и revenue.

Упражнение

Средната поръчка

Отговори на управителя от първата карта. Запиши в order_totals оборота на всяка поръчка за годината без отказаните, а в avg_order средната поръчка, закръглена до 2 знака.

Pythonданни: таблиците на магазина

Колко клиенти са поръчали поне веднъж през годината и колко от тях само веднъж? Запиши в per_customer броя поръчки на всеки клиент, в n_customers броя клиенти, а в once колко са с точно една поръчка.

Pythonданни: таблиците на магазина

Тест

Провери се

1. order_items.csv има 152 реда от 60 поръчки. Колко реда има items.groupby('order_id')['revenue'].sum()?
2. Средният ред на поръчка носи 4,44 €, а средната поръчка 10,63 €. Защо се различават?
3. Искаш до всеки продукт средната цена на неговата категория, в нова колона. Кое пишеш?
4. Какво прави as_index=False в groupby?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·