Широко и дълго: pivot_table и melt
Накратко: pivot_table прави широка таблица с колона за всяка категория, а melt връща дългата с ред за всеки факт; формата се избира според въпроса.
Проблем
„Дай ми го по месеци и категории“
Управителят иска оборота за годината в една таблица: месеците надолу, категориите надясно, както в Excel. Знаеш как: слепваш редовете с поръчките, продуктите и категориите (merge) и групираш по два ключа. Отказаните поръчки не са оборот, затова ги махаш. Подаръчната карта е без категория и получава свое име, иначе ще изпадне от групите:
Числата са верни, но са 84 реда: 12 месеца по 7 категории, един под друг. За да сравниш януари с юли, търсиш с очи из списъка. Таблицата, която управителят иска, е 12 реда и 7 колони със същите 84 числа.
Месецът тук е номерът му: 1 е януари 2026, а 11 и 12 са ноември и декември 2025.
Идеята
Дълго и широко
Едни и същи числа могат да стоят в две форми:
- Дълга (long): ред за всеки факт. Колоните са
month,categoryиrevenue, и всяка двойка месец–категория е отделен ред. - Широка (wide): ред за всеки месец и колона за всяка категория. Категорията вече не е колона: стойностите ѝ са станали имена на колони.
От дълга към широка с резултата на groupby е един ред: unstack() взима последното ниво на индекса (категорията) и го прави колони:
Формата е (12, 7): 12 месеца и 7 категории. Януари: 593 € кухня, 2 038 € млечни, 1 225 € напитки.
Идеята
Направо широко: pivot_table
pivot_table прави групирането и подредбата наведнъж. Казваш му четири неща:
index: какво е в редовете (месецът);columns: какво става колони (категорията);values: коя колона смята (оборотът);aggfunc: как смята ('sum').
Сега се чете с поглед. Декември е пикът: кухнята прави 3 241 €, над 4 пъти повече от обикновен месец, а подаръчната карта 2 160 €. Лятото носи напитките и плодовете: напитките са 1 756 € през юли срещу 1 225 € през януари. Септември е слаб почти навсякъде. Защо, ще разберем в проекта.
По-точно, за любопитните
Има и pivot без „table“: той само подрежда и не смята. Ако двойка месец–категория се среща в повече от един ред, pivot спира с грешка, а pivot_table ги събира с aggfunc. За сурови редове ти трябва pivot_table.
Пробвай
Бройките по категории
Колко броя е продала всяка категория всеки месец? Запиши в units таблица с категориите в редовете и месеците в колоните.
Решение
import pandas as pd
orders = pd.read_csv('year_orders.csv').rename(columns={'id': 'order_id'})
products = pd.read_csv('products.csv').rename(columns={'id': 'product_id'})
categories = pd.read_csv('categories.csv').rename(columns={'id': 'category_id', 'name': 'category'})
orders = orders[orders['status'] != 'cancelled']
orders['month'] = pd.to_datetime(orders['ordered_at']).dt.month
lines = pd.read_csv('year_items.csv').merge(orders[['order_id', 'month']], on='order_id')
lines = lines.merge(products[['product_id', 'category_id']], on='product_id', how='left')
lines = lines.merge(categories[['category_id', 'category']], on='category_id', how='left')
lines['category'] = lines['category'].fillna('Подаръчна карта')
lines['revenue'] = lines['quantity'] * lines['unit_price']
units = lines.pivot_table(index='category', columns='month', values='quantity', aggfunc='sum')
unitslines.pivot_table(index='category', columns='month', values='quantity', aggfunc='sum') дава 7 реда и 12 колони: млечните продават 1 159 броя през декември. Без aggfunc таблицата излиза със същата форма, но в клетките стои средната бройка на ред, около 1–1,6, защото по подразбиране pivot_table смята mean. Изглежда като отговор и затова е опасно.
Идеята
Празните клетки: fill_value
В широката таблица всяка двойка има клетка, дори когато в данните няма такава двойка. Ето колко е купил всеки клиент от всяка категория в 60-те поръчки:
35 клетки са NaN. Клиент 3 не е купувал напитки, почистване и хляб. Тук празното значи „нула покупки“, а не „не знаем“. Затова е вярно да го смениш с нула: fill_value=0.
Това е случаят от урока за обобщенията, в който 0 е правилна замяна: нулата наистина значи нула. Ако клетката беше празна, защото отчетът липсва, нулата щеше да лъже.
Идеята
Сборовете: margins
margins=True добавя ред и колона All със сборовете. Тук са показани две от категориите и общото:
Колоната All е оборотът за месеца, а редът All е оборотът на категорията за годината: 11 304 € кухня, 17 811 € напитки. В ъгъла е общото: 111 186,20 €. Септември е 7 129 €, по-малко от август (8 813 €) и октомври (9 920 €).
Идеята
Обратно към дълго: melt
Управителят праща плана за следващите три месеца. Таблицата е от Excel и е широка: по ред за категория и по колона за месец.
Широката таблица е удобна за четене, но не и за смятане. Месецът не е колона, а е в имената на колоните, и няма по какво да групираш. melt я разтапя обратно в дълга: id_vars е колоната, която остава, var_name кръщава колоната с имената на старите колони, а value_name колоната с числата:
От 7 реда и 4 колони стават 21 реда и 3 колони: всяко число от плана е отделен ред. Сега месецът е колона и groupby работи:
Планът за декември е 16 650 €, а за ноември 9 700 €.
Пробвай
Коя форма за какво
Упражнение
Отчетът за управителя
Направи отчета от първата карта: report с оборота по месеци (редове) и категории (колони), със сборовете All. После запиши в dec_share каква част от оборота за годината е декември.
Решение
import pandas as pd
orders = pd.read_csv('year_orders.csv').rename(columns={'id': 'order_id'})
products = pd.read_csv('products.csv').rename(columns={'id': 'product_id'})
categories = pd.read_csv('categories.csv').rename(columns={'id': 'category_id', 'name': 'category'})
orders = orders[orders['status'] != 'cancelled']
orders['month'] = pd.to_datetime(orders['ordered_at']).dt.month
lines = pd.read_csv('year_items.csv').merge(orders[['order_id', 'month']], on='order_id')
lines = lines.merge(products[['product_id', 'category_id']], on='product_id', how='left')
lines = lines.merge(categories[['category_id', 'category']], on='category_id', how='left')
lines['category'] = lines['category'].fillna('Подаръчна карта')
lines['revenue'] = lines['quantity'] * lines['unit_price']
report = lines.pivot_table(index='month', columns='category', values='revenue', aggfunc='sum', margins=True)
dec_share = report.loc[12, 'All'] / report.loc['All', 'All']
print(round(dec_share * 100, 1))lines.pivot_table(index='month', columns='category', values='revenue', aggfunc='sum', margins=True) дава таблицата със сборовете. report.loc[12, 'All'] / report.loc['All', 'All'] е около 0,143: декември носи 14,3% от оборота за годината, а е един от 12 месеца. Без aggfunc в ъгъла щеше да стои средният ред, а не оборотът.
Колко е планът за трите месеца общо за всяка категория? Направи plan дълга (plan_long с колони category, month и plan) и запиши в per_category сбора за всяка категория.
Решение
import pandas as pd
plan = pd.read_csv('plan.csv')
plan_long = plan.melt(id_vars='category', var_name='month', value_name='plan')
per_category = plan_long.groupby('category')['plan'].sum()
print(per_category)plan.melt(id_vars='category', var_name='month', value_name='plan') дава 21 реда, а plan_long.groupby('category')['plan'].sum() дава по едно число за категория: кухнята е 5 050 €, а млечните 8 700 €. Без id_vars и колоната category се разтапя в редовете и таблицата е 28 реда, с имена на категории между числата.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Нови колони · Типове и липсващи стойности · Групи: groupby · Съединяване: merge
- Продължава в: Първата графика · Коя графика за какъв въпрос · Честни графики · A/B тест
- Задачи по темата: Подреждане на данни