Първата графика
Накратко: Графиката в matplotlib е фигура с оси: plot чертае линия, bar чертае стълбове, а заглавието, надписите на осите и легендата казват какво се вижда.
Проблем
„Покажи ми годината“
Управителят иска да види как върви годината. Вече знаеш как да сметнеш оборота по месеци: редовете на поръчките без отказаните, сбор по месец. Месецът е първите 7 знака на ordered_at, например „2026-03“:
Числата са верни, но от 12 реда трудно се вижда кое е върх и кое спад. Декември е 15 915,90 €, а януари е 7 228,20 €. Колко голям е спадът спрямо другите месеци? От таблица окото не го хваща. От линия го хваща веднага.
Идеята
Фигура и ос
В урока „Добре дошъл“ видя графика с един ред код. Сега ще видиш какво стои зад нея.
Графиката в matplotlib има два обекта:
- фигурата (
Figure) е целият лист: размерът и записът във файл; - оста (
Axes) е полето с хоризонтала и вертикала, където се рисува.
plt.subplots() дава и двете: fig, ax = plt.subplots(). После рисуваш в оста. ax.plot(x, y) свързва точките с линия:
[графика] в текста на изхода значи, че графиката е без заглавие. Линията вече показва върха през декември и дупката след него. Липсват ѝ обаче три неща: заглавие, надписи на осите и месеци, които се четат.
По-точно, за любопитните
Тук и в Jupyter графиката се показва сама в края на клетката, затова няма plt.show(). В скрипт (python chart.py) я показваш с plt.show() или я записваш с fig.savefig('monthly.png'), иначе скриптът свършва и фигурата изчезва.
plt.plot(...) без fig, ax също рисува: тогава matplotlib сам взима „текущата“ ос. Това е удобно за една бърза графика, но при няколко оси не знаеш коя е текущата. Затова тук винаги рисуваме в изрична ос.
Идеята
Заглавие, надписи и етикети
Графика без надписи кара читателя да гадае. Оста има метод за всяко нещо:
ax.set_titleказва какво гледаш;ax.set_xlabelиax.set_ylabelказват какво има по хоризонталата и по вертикалата;ax.tick_params(axis='x', labelrotation=45)завърта етикетите (tick labels), числата и имената под оста, за да не се застъпват;fig.tight_layout()подрежда листа, за да не се режат завъртените надписи.
marker='o' слага точка на всеки месец:
Сега се вижда, че 11 месеца са между 7 000 и 10 000 €, а декември е над 15 000 €. Януари, февруари и септември са най-ниско. Защо е спадът през септември, ще разберем в проекта.
Пробвай
Поръчките по месеци
Начертай броя поръчки за всеки месец (без отказаните) като линия. Запиши броя в counts, а на графиката сложи заглавие и надписи на двете оси.
Решение
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
sold = orders[orders['status'] != 'cancelled']
counts = sold.groupby(sold['ordered_at'].str[:7]).size()
fig, ax = plt.subplots()
ax.plot(counts.index, counts.values)
ax.set_title('Поръчки по месеци')
ax.set_xlabel('Месец')
ax.set_ylabel('Поръчки')
ax.tick_params(axis='x', labelrotation=45)
fig.tight_layout()sold.groupby(sold['ordered_at'].str[:7]).size() дава 12 числа: 886 поръчки през ноември, 1 211 през декември, 728 през януари и 701 през септември. Линията има същата форма като оборота: върх през декември и дупка след него. С orders вместо sold щеше да преброиш и отказаните поръчки. Без set_xlabel и set_ylabel читателят не знае дали вертикалата е в бройки или в евро.
Идеята
Стълбове: ax.bar
Линията свързва точки и казва „оттук дотук нещо се е менило“. Това има смисъл за месеци, които вървят един след друг. Категориите не вървят една след друга: за тях рисуваш стълбове (bar chart) с ax.bar(имена, стойности).
Оборотът по категории иска името на категорията на всеки ред: продукт → category_id → име. Два map го дават:
Стълбовете са 6, а сборът им е 107 466,20 €. Оборотът за годината е 111 186,20 €. Липсват 3 720 €: подаръчната карта е без категория, map ѝ дава NaN, а groupby пропуска NaN тихо. Графиката изглежда наред. Затова преброй стълбовете и сравни сбора с оборота.
Пробвай
Всички категории
Начертай оборота по категории така, че подаръчната карта да е свой стълб. Запиши оборота по категории в by_cat, сложи заглавие и надпис на вертикалата.
Решение
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.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]
products = pd.read_csv('products.csv')
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('Подаръчна карта')
by_cat = lines.groupby('category')['revenue'].sum()
fig, ax = plt.subplots()
ax.bar(by_cat.index, by_cat.values)
ax.set_title('Оборот по категории, €')
ax.set_ylabel('Оборот, €')
ax.tick_params(axis='x', labelrotation=30)
fig.tight_layout().fillna('Подаръчна карта') след двата map дава име на липсващата категория и groupby я пази. Стълбовете стават 7, а сборът им е точно 111 186,20 €. Млечните са най-високият стълб с 31 427,90 €, а подаръчната карта е най-ниският с 3 720 €. Без fillna графиката има 6 стълба и в нея липсва подаръчната карта.
Идеята
Съкращението: df.plot
Series и DataFrame имат метод plot. Той вика matplotlib вместо теб: kind='line' (по подразбиране), kind='bar' и другите видове, които идват в следващите уроци. С ax=ax рисува в твоята ос. После я довършваш с методите на оста, както досега:
pandas е сложил имената на категориите под стълбовете и ги е завъртял. Сложил е и надпис на хоризонталата: името на индекса, category. Заглавието и вертикалата са от твоя код.
Двата начина дават една и съща графика. ax.bar ти дава пълен контрол. df.plot спестява редове, когато данните вече са в Series или в таблица.
Идеята
Няколко линии и легенда
Декември е върхът на годината. Всички категории ли растат еднакво? Две линии в една ос отговарят. Всеки ax.plot добавя линия, label ѝ дава име, а ax.legend() слага легендата: кутийка, която казва коя линия коя е:
Кухнята скача от 964,80 € през ноември на 3 240,60 € през декември, 3,4 пъти. После пада на 592,80 € през януари. Вероятно тиганът и ножовете се купуват за подарък. Млечните растат по-малко: от 2 792,10 € на 3 561,20 €.
Таблица с колона за всяка категория df.plot рисува наведнъж, по линия за колона, с легенда от имената на колоните. Това е причината в урока за pivot_table таблицата да е широка:
Седем линии в една ос вече се преплитат. Коя графика кога помага и как да покажеш много серии, без да се преплитат, е следващият урок.
Упражнение
Кой носи декември?
Двете големи групи категории са Храни и Дом (parent_id в categories.csv). Начертай оборота на всяка по месеци като две линии в една ос, с легенда, заглавие и надписи на осите. Таблицата по месец и група запиши в pv.
Решение
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.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]
products = pd.read_csv('products.csv')
categories = pd.read_csv('categories.csv')
cat_of = products.set_index('id')['category_id']
cat_name = categories.set_index('id')['name']
parent_of = categories.set_index('id')['parent_id']
lines['group'] = lines['product_id'].map(cat_of).map(parent_of).map(cat_name)
pv = lines.pivot_table(index='month', columns='group', values='revenue', aggfunc='sum')
fig, ax = plt.subplots()
ax.plot(pv.index, pv['Храни'], label='Храни')
ax.plot(pv.index, pv['Дом'], label='Дом')
ax.legend()
ax.set_title('Храни и Дом по месеци, €')
ax.set_xlabel('Месец')
ax.set_ylabel('Оборот, €')
ax.tick_params(axis='x', labelrotation=45)
fig.tight_layout()Групата на реда е три map: продукт → категория → parent_id → име. pivot_table по месец и група дава колона Храни и колона Дом. През декември храните растат с 30% (от 7 097,40 € на 9 249,30 €), а домът повече от двойно (от 2 077,10 € на 4 506,60 €). Подаръчната карта е без група и не влиза в двете линии. Без label легендата е празна, а без ax.legend() няма легенда изобщо.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Типове и липсващи стойности · Групи: groupby · Съединяване: merge · Широко и дълго: pivot_table и melt
- Продължава в: Коя графика за какъв въпрос · Разпределения · Честни графики · Описание на данните
- Задачи по темата: Графики