Коя графика за какъв въпрос
Накратко: Видът графика се избира по въпроса: подредени стълбове за сравнение, линия за промяна във времето и пак стълбове, а не кръг, за дял от цялото.
Проблем
Четири въпроса, едни данни
Управителят има четири въпроса за годината:
- Кои продукти носят най-много пари?
- Как върви оборотът ден след ден?
- Каква част от оборота е всяка категория?
- Колко е обичайната поръчка?
Данните са едни и същи: поръчките и редовете им. Графиката обаче е различна за всеки въпрос. Една графика, която отговаря на всичките, няма. Има графика, която отговаря добре на един въпрос, и много, които отговарят на него зле.
Идеята
Първо въпросът, после графиката
Не започвай от „искам кръг“ или „искам линия“. Започни от въпроса и виж какъв вид е:
- сравнение („кое е повече“): стълбове, подредени по стойност; при дълги имена хоризонтални (
ax.barh); - промяна във времето („как върви“): линия;
- дял от цялото („каква част“): пак подредени стълбове, в проценти; кръгът се чете по-трудно;
- разпределение („колко е обичайното, колко се пръска“): хистограма, в следващия урок;
- връзка („растат ли заедно“): разсейване (scatter), също в следващия урок.
Да видим първите три със същите данни.
Идеята
Сравнение: подредени хоризонтални стълбове
„Кои продукти носят най-много?“ е сравнение между 25 продукта. Имената им са дълги, затова стълбовете са хоризонтални: ax.barh(имена, стойности). Имената стоят отляво и се четат, без да се въртят.
Подреждането с sort_values() прави графиката класация. barh рисува първия ред най-долу, затова възходящият ред слага най-големия най-горе. figsize=(7, 7) дава на фигурата размер в инчове: 25 стълба искат височина.
Сиренето носи най-много: 10 325,30 €. След него са кафето на зърна (9 585,10 €) и кашкавалът (8 616,50 €). Подредени, стълбовете отговарят на въпроса още преди да прочетеш числата.
По-точно, за любопитните
Без sort_values() продуктите идват по азбучен ред от groupby. Азбуката е удобна, когато търсиш един продукт по име. За въпроса „кое е най-много“ трябва ред по стойност.
Пробвай
Най-продаваните на бройки
Кои 10 продукта са продадени в най-много бройки за годината? Запиши ги в top (продуктът е индексът, бройките са стойността) и ги начертай като хоризонтални стълбове с най-големия най-горе.
Решение
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
products = pd.read_csv('products.csv')
# Оборотът е без отказаните поръчки.
sold = orders[orders['status'] != 'cancelled']
lines = items.merge(sold, left_on='order_id', right_on='id')
lines['revenue'] = lines['quantity'] * lines['unit_price']
lines['month'] = lines['ordered_at'].str[:7]
lines['product'] = lines['product_id'].map(products.set_index('id')['name'])
top = lines.groupby('product')['quantity'].sum().nlargest(10).sort_values()
fig, ax = plt.subplots()
ax.barh(top.index, top.values)
ax.set_title('Най-продаваните продукти')
ax.set_xlabel('Бройки за годината')
fig.tight_layout()lines.groupby('product')['quantity'].sum().nlargest(10).sort_values() дава десетте. Най-горе е белият хляб с 4 223 броя, после прясното мляко (3 882) и минералната вода (3 707). Сиренето, което носи най-много пари, е едва десето по бройки (1 307). „Най-продаван“ зависи от това дали мериш в бройки, или в евро. nlargest дава най-големия първи и без sort_values() той отива най-долу.
Идеята
Промяна във времето: линия
„Как върви оборотът ден след ден?“ е промяна във времето. Линията свързва всеки ден със следващия и окото следи посоката. 365 стълба биха били ограда, в която не се вижда нищо.
Оборотът по дни скача заради седмичния ритъм. Втора линия със средното за 7 дни (rolling(7) от урока за датите) показва посоката под шума:
Дните са 365. Най-силният е 26 декември 2025 с 852,90 €. Тънката линия е зъбеста всяка седмица, а дебелата показва големия върх през декември и спадовете през януари и септември. По хоризонталата са дати: ordered_at е превърнат в дата с pd.to_datetime и matplotlib сам слага месеците.
Идеята
Малки графики една до друга
В предния урок седемте категории в една ос се преплитаха. Друг начин е всяка категория да е в своя малка графика, а графиките да стоят една до друга. Това се казва малки графики (small multiples).
plt.subplots(2, 3) дава фигура с 2 реда и 3 колони оси. axes е масив от NumPy с форма (2, 3), а axes.flat ги дава една по една. sharey=True дава на всички една и съща вертикална скала. fig.suptitle е заглавие на цялата фигура. По хоризонталата е само номерът на месеца (pv.index.str[5:]), за да се събере:
С обща скала височините се сравняват: млечните са най-високо през цялата година, а кухнята е ниско с един връх през декември. Всяка линия се чете сама, без легенда и без цветове за разпознаване.
Идеята
Дял от цялото: стълбове срещу кръг
„Каква част от оборота е всяка категория?“ е въпрос за дял. Често за него се рисува кръгова диаграма (pie chart). Ето кръга и подредените стълбове с едни и същи числа, една до друга. plt.subplots(1, 2) дава две оси: тук ги кръщаваме left и right:
От кръга се вижда, че млечните са най-голямото парче, малко над една четвърт. Почистването (9,8%) и кухнята (10,2%) обаче изглеждат еднакви. Хлябът (11,9%) също е близо до тях. Стълбовете вдясно ги нареждат и разликите се виждат.
Идеята
Защо кръгът подвежда
Стълбовете се сравняват по дължина от една и съща линия. Окото прави това точно. Кръгът кара окото да сравнява ъгли и площи на парчета, които са завъртени различно. Това окото прави зле, особено при близки дялове.
Кръгът има и други проблеми:
- над 4–5 парчета надписите се трупат;
- два кръга един до друг (декември срещу година) почти не се сравняват;
- цветът е единственото, което свързва парчето с името.
Кръгът върши работа в един случай: две-три парчета и въпрос „повече или по-малко от половината?“. За всичко друго подредените стълбове казват същото по-ясно.
Идеята
Разпределение и връзка
Остават два вида въпроси. За тях трябват графики, които идват в следващия урок:
- разпределение: „колко е обичайната поръчка?“ Отговорът не е едно число, а как се пръскат 10 459 поръчки: колко са малки, колко са големи. Това показва хистограмата;
- връзка: „поръчките с повече редове по-скъпи ли са?“ Всяка поръчка става точка с две числа. Облакът от точки е разсейване (scatter).
Упражнение
Какво носи декември
Декември е най-силният месец. Повече поръчки ли има, или по-големи? Запиши в orders_n броя поръчки за всеки месец (без отказаните), в avg средната поръчка в евро, и ги начертай като две линии в две оси една до друга, всяка със заглавие.
Решение
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
products = pd.read_csv('products.csv')
# Оборотът е без отказаните поръчки.
sold = orders[orders['status'] != 'cancelled']
lines = items.merge(sold, left_on='order_id', right_on='id')
lines['revenue'] = lines['quantity'] * lines['unit_price']
lines['month'] = lines['ordered_at'].str[:7]
lines['product'] = lines['product_id'].map(products.set_index('id')['name'])
orders_n = sold.groupby(sold['ordered_at'].str[:7]).size()
revenue = lines.groupby('month')['revenue'].sum()
avg = revenue / orders_n
fig, (left, right) = plt.subplots(1, 2, figsize=(10, 4))
left.plot(orders_n.index, orders_n.values)
left.set_title('Поръчки на месец')
right.plot(avg.index, avg.values)
right.set_title('Средна поръчка, €')
left.tick_params(axis='x', labelrotation=90)
right.tick_params(axis='x', labelrotation=90)
fig.tight_layout()orders_n дава 1 211 поръчки през декември срещу 701 до 961 в другите месеци. avg = revenue / orders_n дава 13,14 € средна поръчка през декември, а в другите месеци тя е между 9,93 и 10,49 €. Декември е и повече поръчки, и по-големи. В една ос средната поръчка (около 10) щеше да лежи на нулата под поръчките (стотици). lines.groupby('month')['revenue'].mean() е средният ред, не средната поръчка.
Каква част от оборота през декември 2025 е всяка категория, с подаръчната карта? Запиши в share дела в проценти, подреден, и го начертай като хоризонтални стълбове с най-големия най-горе.
Решение
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
products = pd.read_csv('products.csv')
# Оборотът е без отказаните поръчки.
sold = orders[orders['status'] != 'cancelled']
lines = items.merge(sold, left_on='order_id', right_on='id')
lines['revenue'] = lines['quantity'] * lines['unit_price']
lines['month'] = lines['ordered_at'].str[:7]
lines['product'] = lines['product_id'].map(products.set_index('id')['name'])
categories = pd.read_csv('categories.csv')
cat_of = products.set_index('id')['category_id']
cat_name = categories.set_index('id')['name']
lines['category'] = lines['product_id'].map(cat_of).map(cat_name).fillna('Подаръчна карта')
dec = lines[lines['month'] == '2025-12']
share = (dec.groupby('category')['revenue'].sum() / dec['revenue'].sum() * 100).sort_values()
fig, ax = plt.subplots()
ax.barh(share.index, share.values)
ax.set_title('Делът на категориите през декември')
ax.set_xlabel('Дял от оборота, %')
fig.tight_layout()Филтърът lines['month'] == '2025-12' оставя декември. Сборът по категория, разделен на сбора на месеца и по 100, дава дяловете. Млечните са най-горе с 22,4%, но кухнята е близо с 20,4% (за цялата година е 10,2%). Подаръчната карта е 13,6% от декември, а за годината е 3,3%. С кръг тези разлики щяха да се гадаят. Без филтъра щяха да излязат дяловете за цялата година.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Съединяване: merge · Широко и дълго: pivot_table и melt · Дати и времеви редове · Първата графика
- Продължава в: Разпределения · Честни графики · Защо оборотът падна?
- Задачи по темата: Графики