Візуалізація даних за допомогою matplotlib

Візуалізація категоріальних даних — bar charts та pie charts

Стовпчасті діаграми для порівняння категорій, горизонтальні та вертикальні bar charts, групові та stacked bars, pie charts та коли їх уникати

Візуалізація категоріальних даних — bar charts та pie charts

🎯 Мета підтеми

  • Навчитися будувати bar charts для порівняння категорій
  • Розрізняти вертикальні та горизонтальні bar charts
  • Опанувати групові та stacked bar charts
  • Зрозуміти, коли pie charts доречні (спойлер: майже ніколи)
  • Навчитися вибирати правильний тип графіка для категоріальних даних

🔑 Ключові терміни

  • Bar chart — стовпчаста діаграма для порівняння категорій
  • Pie chart — кругова діаграма для часток цілого
  • Grouped bars — кілька стовпців поруч для порівняння груп
  • Stacked bars — стовпці один на одному для показу складу
  • Категоріальні дані — дискретні значення (країни, продукти, спеціальності)

Плавний старт: Bar Chart за 3 рядки коду

Коли дані дискретні — мікросервіси, типи помилок, канали зв'язку чи географічні регіони — класичні графіки ліній або скаттерів не підходять. Основним інструментом для швидкого порівняння значень у категоріях є стовпчаста діаграма (bar chart).

Погляньмо, як від трьох мінімальних рядків перейти до інформативного інженерного звіту:

hello_barchart.ipynb
Python 3 (ipykernel)
Code
md

Три рівні створення стовпчастої діаграми

Від сирих стовпців до професійного дашборду з автоматичними підписами значень

In [1]:
0.18s
import matplotlib.pyplot as plt

# Дані: кількість критичних помилок 5xx за мікросервісами платформи
services = ["Auth", "Billing", "Orders", "Pay", "Notify"]
errors = [12, 45, 8, 29, 17]

# --- РІВЕНЬ 0: Мінімальний код (3 рядки) ---
plt.bar(services, errors)
plt.show()

# --- РІВЕНЬ 1: Додавання контексту та сітки ---
fig, ax = plt.subplots(figsize=(6.5, 4))
ax.bar(services, errors, color='#2563eb', edgecolor='#1e3a8a')
ax.set_xlabel("Мікросервіс")
ax.set_ylabel("Кількість інцидентів (5xx)")
ax.set_title("Помилки за мікросервісами")
ax.grid(axis='y', linestyle=':', alpha=0.6)
plt.show()

# --- РІВЕНЬ 2: Професійний інженерний звіт з ax.bar_label ---
fig, ax = plt.subplots(figsize=(7, 4.5))

# Виділяємо сервіс із найбільшою кількістю помилок червоним кольором
colors = ['#94a3b8' if e < 40 else '#ef4444' for e in errors]
bars = ax.bar(services, errors, color=colors, edgecolor='#0f172a', linewidth=1.2, width=0.6)

# Автоматичне нанесення точних чисел над стовпцями (Matplotlib 3.4+)
ax.bar_label(bars, padding=3, fontweight='bold', fontsize=9.5, color='#1e293b')

ax.set_title("Аудит інцидентів мікросервісів за добу", fontsize=12, fontweight='bold')
ax.set_xlabel("Мікросервіс", fontsize=10)
ax.set_ylabel("Кількість інцидентів (5xx)", fontsize=10)
ax.set_ylim(0, 52)
ax.grid(axis='y', linestyle=':', alpha=0.6)
plt.show()
Out[1]:
md

Чому Bar Chart перевершує таблиці та кругові діаграми?

  1. Точність візуального сприйняття: людина найкраще порівнює довжини відрізків зі спільної базової лінії (вісь 0). Ми миттєво бачимо, що Billing має у 3 рази більше інцидентів, ніж Notify.
  2. Масштабованість: стовпчаста діаграма однаково добре відображає як 3 категорії, так і 20 (особливо в горизонтальному форматі).
  3. Гнучкість акцентування: легке додавання кольорових акцентів, планок похибок (yerr) та точних підписів (ax.bar_label).

🧭 Наскрізна інженерна історія: Аудит надійності та оптимізація хмарної архітектури

Уявіть, що ви працюєте Site Reliability Engineer (SRE) або хмарним архітектором у високонавантаженому фінтех-стартапі. Керівництво ініціювало глобальний аудит інфраструктури:

  • Крок 1 (Вертикальний Bar Chart): Порівняти частоту збоїв між мікросервісами та виявити найбільш нестабільний компонент.
  • Крок 2 (Анатомія ax.bar та похибки yerr): Додати статистичні планки розкиду навантаження CPU, щоб оцінити пікові коливання.
  • Крок 3 (Горизонтальний ax.barh): Скласти рейтинг затримок (P99 Latency) для 10+ API-ендпоінтів із довгими назвами URL-шляхів та побудувати Waterfall розбивки фаз запиту (DNS $\to$ TLS $\to$ Execution).
  • Крок 4 (Grouped Bars): Порівняти поведінку системи на різних контурах (Dev, Staging, Production).
  • Крок 5 (Stacked Bars & 100% Stacked): Проаналізувати помісячну структуру витрат на хмарні сервіси (Compute, Storage, Network) як в абсолютних доларах, так і у відсоткових частках.
  • Крок 6 (Анатомія ax.pie та Donut Chart): Зрозуміти, чому класичні кругові діаграми вводять в оману, як за необхідності збудувати сучасний Donut Chart та коли краще замінити його стовпцями.
  • Крок 7 (Логіка сортування): Мінімізувати когнітивне навантаження команди завдяки впорядкуванню даних за спаданням.

Покроково розгляньмо всі ці інструменти.


Історія про менеджера та квартальні продажі

Менеджер Андрій отримав звіт: продажі за 4 квартали у трьох регіонах. Бухгалтер надіслав таблицю:

Регіон  | Q1  | Q2  | Q3  | Q4
--------|-----|-----|-----|-----
Київ    | 120 | 150 | 180 | 220
Львів   | 80  | 95  | 110 | 130
Одеса   | 200 | 190 | 185 | 175

Андрій дивиться на числа і не розуміє:

  • Який регіон найсильніший?
  • Де є зростання, а де спад?
  • Наскільки Київ відстає від Одеси?

Один bar chart — і все стає зрозуміло за 3 секунди:

sales_barchart.ipynb
Python 3 (ipykernel)
Code
md

Квартальні продажі трьох регіонів

Один графік замінює 10 хвилин аналізу таблиці

In [1]:
0.15s
import matplotlib.pyplot as plt
import numpy as np

regions = ["Київ", "Львів", "Одеса"]
q1 = [120, 80, 200]
q2 = [150, 95, 190]
q3 = [180, 110, 185]
q4 = [220, 130, 175]

x = np.arange(len(regions))
width = 0.2

fig, ax = plt.subplots(figsize=(10, 6))

bars1 = ax.bar(x - 1.5*width, q1, width, label="Q1", color="#FF6B6B", edgecolor="black")
bars2 = ax.bar(x - 0.5*width, q2, width, label="Q2", color="#4ECDC4", edgecolor="black")
bars3 = ax.bar(x + 0.5*width, q3, width, label="Q3", color="#95E1D3", edgecolor="black")
bars4 = ax.bar(x + 1.5*width, q4, width, label="Q4", color="#FFD93D", edgecolor="black")

ax.set_title("Квартальні продажі за регіонами (тис. грн)", 
             fontsize=14, fontweight="bold")
ax.set_ylabel("Продажі (тис. грн)", fontsize=12)
ax.set_xlabel("Регіон", fontsize=12)
ax.set_xticks(x)
ax.set_xticklabels(regions)
ax.legend(loc="upper left", fontsize=11)
ax.grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.show()
Out[1]:
md

Висновки одразу видно:

  1. Київ — стабільне зростання з Q1 до Q4 (120 → 220)
  2. Львів — теж зростання, але обсяги менші (80 → 130)
  3. Одеса — найбільший регіон, але спад (200 → 175)

Це те, що неможливо побачити швидко в таблиці з числами!

Відмінність bar chart від гістограми:
Bar ChartГістограма
Для категоріальних даних (міста, продукти)Для неперервних даних (зріст, вік)
Стовпці розділені проміжкамиСтовпці стикаються (bins)
Порядок категорій можна змінюватиПорядок bins фіксований (числова вісь)
Вимірює кількість, суму, середнє тощоВимірює частоту (кількість у інтервалі)
Приклад: «Продажі по містах» → bar chart. «Розподіл цін квартир» → гістограма.

Вертикальні bar charts

Базовий тип — стовпці вертикально. Зручно для порівняння кількох категорій.

vertical_bars.ipynb
Python 3 (ipykernel)
Code
In [1]:
import matplotlib.pyplot as plt

# Дані: топ-5 мов програмування за популярністю (умовні дані)
languages = ["Python", "JavaScript", "Java", "C++", "C#"]
popularity = [85, 78, 72, 65, 60]

fig, ax = plt.subplots(figsize=(10, 6))

bars = ax.bar(languages, popularity, color="steelblue", edgecolor="navy", linewidth=1.5)

# Додаємо значення над стовпцями
for bar in bars:
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2, height + 1,
            f"{height}%", ha="center", va="bottom", fontsize=11, fontweight="bold")

ax.set_title("Топ-5 мов програмування за популярністю (2026)", 
             fontsize=14, fontweight="bold")
ax.set_ylabel("Популярність (%)", fontsize=12)
ax.set_xlabel("Мова програмування", fontsize=12)
ax.set_ylim(0, 100)
ax.grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.show()
Out[1]:
Додавання підписів над стовпцями:
for bar in bars:
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2,  # Центр стовпця по X
            height + offset,                   # Трохи вище стовпця
            f"{height:.1f}",                   # Текст (форматоване значення)
            ha="center",                       # Горизонтальне вирівнювання
            va="bottom",                       # Вертикальне вирівнювання
            fontsize=10, fontweight="bold")
Це робить графік самодостатнім — не потрібно вдивлятися в сітку, щоб дізнатися точне значення.

Повна сигнатура та анатомія параметрів ax.bar()

Метод ax.bar() будує прямокутні стовпці вертикально від базової лінії (за замовчуванням $y=0$). Він повертає об'єкт BarContainer, що містить список графічних прямокутників matplotlib.patches.Rectangle.

ax.bar(
    x,
    height,
    width=0.8,
    bottom=None,
    *,
    align='center',
    data=None,
    **kwargs
) -> BarContainer
ПараметрТипЗа замовчуваннямОпис та інженерне призначення
xfloat | ArrayLikeобов'язковийКоординати X (числа або список категоріальних текстових міток).
heightfloat | ArrayLikeобов'язковийВисота стовпців (числові значення вимірюваної величини).
widthfloat | ArrayLike0.8Ширина стовпців. Значення width=1.0 робить стовпці впритул без проміжків.
bottomfloat | ArrayLike0Нижня координата стовпця. Використовується для побудови складених стовпців (Stacked bars).
align{'center', 'edge'}'center'Вирівнювання відносно координати $x$: по центру або по лівому краю.
colorcolor | SequenceNone (C0)Колір заливки стовпців (можна передати список для вибіркового акцентування).
edgecolorcolor | SequenceNoneКолір обведення контуру стовпців (додає чіткості).
linewidthfloatrcParamsТовщина лінії контуру стовпця.
yerrfloat | ArrayLikeNoneПланки похибок (error bars) для відображення дисперсії, STD або довірчого інтервалу.
capsizefloat0Довжина горизонтальних засічок на планках похибок (у пунктах).
Сучасний метод ax.bar_label() (Matplotlib 3.4+)Замість написання ручних циклів з for bar in bars: ax.text(...), сучасний Matplotlib надає високорівневий метод:
bars = ax.bar(categories, values)
ax.bar_label(bars, padding=3, fmt="%.1f", fontweight="bold")
Параметр padding задає відступ тексту від вершини стовпця, а fmt — формат виведення чисел.
bar_parameters_deep_dive.ipynb
Python 3 (ipykernel)
Code
md

Дослідження параметрів ax.bar(): ширина, вирівнювання, похибки та стилізація

Порівняння значень width, центровок align, планок похибок yerr та комбінування bottom із штрихуванням

In [1]:
0.22s
import matplotlib.pyplot as plt
import numpy as np

cats = ['Node 1', 'Node 2', 'Node 3', 'Node 4']
vals = [42, 68, 55, 83]
errs = [4, 6, 5, 8]
x = np.arange(len(cats))

fig, axes = plt.subplots(2, 2, figsize=(13, 9.5))

# 1. Варіації width (ширина стовпців)
axes[0, 0].bar(x - 0.25, vals, width=0.2, color='#0284c7', label="width = 0.2 (вузькі)")
axes[0, 0].bar(x, vals, width=0.45, color='#0ea5e9', label="width = 0.45 (стандарт)")
axes[0, 0].bar(x + 0.25, vals, width=0.8, color='#38bdf8', alpha=0.4, label="width = 0.8 (широкі)")
axes[0, 0].set_title("Параметр width: керування шириною стовпця", fontweight='bold')
axes[0, 0].set_xticks(x)
axes[0, 0].set_xticklabels(cats)
axes[0, 0].set_ylabel("Навантаження CPU (%)")
axes[0, 0].legend()
axes[0, 0].grid(axis='y', linestyle=':', alpha=0.6)

# 2. Параметр align: 'center' vs 'edge'
axes[0, 1].bar(x, vals, width=0.4, align='center', color='#8b5cf6', alpha=0.7, label="align='center' (по центру мітки)")
axes[0, 1].bar(x, [v * 0.7 for v in vals], width=0.4, align='edge', color='#ec4899', alpha=0.7, label="align='edge' (по краю мітки)")
axes[0, 1].set_title("Параметр align: центрування відносно tick", fontweight='bold')
axes[0, 1].set_xticks(x)
axes[0, 1].set_xticklabels(cats)
axes[0, 1].set_ylabel("Навантаження CPU (%)")
axes[0, 1].legend()
axes[0, 1].grid(axis='y', linestyle=':', alpha=0.6)

# 3. Планки похибок: yerr та capsize
b_err = axes[1, 0].bar(cats, vals, yerr=errs, capsize=6, color='#10b981', edgecolor='#064e3b', linewidth=1.2, width=0.55)
axes[1, 0].bar_label(b_err, padding=7, fmt='%.0f%%', fontweight='bold', fontsize=9)
axes[1, 0].set_title("Параметри yerr та capsize: планки розкиду / STD", fontweight='bold')
axes[1, 0].set_ylabel("Середнє CPU % ± STD")
axes[1, 0].set_ylim(0, 105)
axes[1, 0].grid(axis='y', linestyle=':', alpha=0.6)

# 4. Стилізація та контури: color, edgecolor, linewidth, bottom, hatch
base = [15, 20, 10, 25]
axes[1, 1].bar(cats, base, color='#cbd5e1', edgecolor='#475569', linewidth=1, label="Базовий рівень (bottom)", width=0.55)
axes[1, 1].bar(cats, vals, bottom=base, color='#f59e0b', edgecolor='#78350f', linewidth=1.5, hatch='//', label="Додаткове навантаження", width=0.55)
axes[1, 1].set_title("Параметри bottom, edgecolor та hatch: шарувата структура", fontweight='bold')
axes[1, 1].set_ylabel("Сумарне навантаження (%)")
axes[1, 1].legend()
axes[1, 1].grid(axis='y', linestyle=':', alpha=0.6)

plt.tight_layout()
plt.show()
Out[1]:

Горизонтальні bar charts

Коли категорій багато або назви довгі, горизонтальні стовпці зручніші.

horizontal_bars.ipynb
Python 3 (ipykernel)
Code
In [1]:
import matplotlib.pyplot as plt

# Дані: середній час відповіді різних відділів компанії (години)
departments = [
    "Технічна підтримка",
    "Відділ продажів",
    "Бухгалтерія",
    "HR (кадри)",
    "IT (розробка)",
    "Маркетинг"
]
response_time = [2.5, 4.0, 8.5, 6.0, 3.0, 5.5]

fig, ax = plt.subplots(figsize=(10, 7))

bars = ax.barh(departments, response_time, color="coral", edgecolor="darkred", linewidth=1.5)

# Додаємо значення праворуч від стовпців
for i, (bar, value) in enumerate(zip(bars, response_time)):
    ax.text(value + 0.3, bar.get_y() + bar.get_height()/2,
            f"{value} год", va="center", fontsize=11, fontweight="bold")

ax.set_title("Середній час відповіді відділів компанії", 
             fontsize=14, fontweight="bold")
ax.set_xlabel("Час (години)", fontsize=12)
ax.grid(axis="x", alpha=0.3)
ax.set_xlim(0, max(response_time) + 2)

plt.tight_layout()
plt.show()
Out[1]:
Коли використовувати горизонтальні bar charts:
  1. Довгі назви категорій — «Технічна підтримка клієнтів» не поміститься вертикально
  2. Багато категорій (>7–8) — вертикально стане тісно
  3. Порівняння рангів — зручніше читати зверху вниз (топ-10, рейтинги)
Вертикальні зручніші для:
  • Часових даних (місяці, квартали — звичка читати зліва направо)
  • Невеликої кількості категорій (3–6)

Повна сигнатура та анатомія параметрів ax.barh()

Горизонтальні стовпці будуються за допомогою ax.barh(). Головна відмінність від вертикальних полягає у рокіровці параметрів: аргумент width відповідає за горизонтальну довжину (значення величини), а аргумент height — за товщину смуги.

ax.barh(
    y,
    width,
    height=0.8,
    left=None,
    *,
    align='center',
    data=None,
    **kwargs
) -> BarContainer
ПараметрТипЗа замовчуваннямОпис та інженерне призначення
yfloat | ArrayLikeобов'язковийКоординати Y (позиції на вертикальній осі або текстові мітки категорій).
widthfloat | ArrayLikeобов'язковийГоризонтальна довжина смуг (числові значення метрик).
heightfloat | ArrayLike0.8Товщина смуги (вертикальний розмір кожного прямокутника).
leftfloat | ArrayLike0Зсув лівого краю смуги. Аналог bottom у ax.bar: дозволяє будувати горизонтальні Stacked бари або Waterfall діаграми фаз виконання.
align{'center', 'edge'}'center'Вирівнювання смуги відносно координати $y$.
xerrfloat | ArrayLikeNoneГоризонтальні планки похибок / довірчих інтервалів.
capsizefloat0Довжина вертикальних засічок на планках похибок xerr.
barh_parameters_deep_dive.ipynb
Python 3 (ipykernel)
Code
md

Дослідження параметрів ax.barh(): товщина, похибки, Waterfall та порогові лінії

Порівняння height, планок xerr, каскадного зсуву left та сортування з критичною лінією SLA

In [1]:
0.24s
import matplotlib.pyplot as plt
import numpy as np

endpoints = ["/api/v1/auth/login", "/api/v1/checkout", "/api/v1/search", "/api/v1/orders/create", "/api/v1/user/profile"]
latency_ms = [45, 185, 320, 210, 65]
xerr_ms = [5, 18, 35, 22, 8]
y_pos = np.arange(len(endpoints))

fig, axes = plt.subplots(2, 2, figsize=(13, 9.5))

# 1. Варіації height (товщина горизонтальних смуг)
axes[0, 0].barh(y_pos - 0.2, latency_ms, height=0.25, color='#0ea5e9', label="height = 0.25 (тонкі)")
axes[0, 0].barh(y_pos + 0.15, latency_ms, height=0.45, color='#0284c7', label="height = 0.45 (стандарт)")
axes[0, 0].set_yticks(y_pos)
axes[0, 0].set_yticklabels(endpoints, fontsize=8.5)
axes[0, 0].set_xlabel("Latency P99 (ms)")
axes[0, 0].set_title("Параметр height: товщина горизонтальних смуг", fontweight='bold')
axes[0, 0].legend()
axes[0, 0].grid(axis='x', linestyle=':', alpha=0.6)

# 2. Планки похибок: xerr та capsize
b_errh = axes[0, 1].barh(endpoints, latency_ms, xerr=xerr_ms, capsize=4, color='#f97316', edgecolor='#9a3412', height=0.55)
axes[0, 1].bar_label(b_errh, padding=6, fmt='%.0f ms', fontweight='bold', fontsize=8.5)
axes[0, 1].set_xlabel("Latency P99 (ms) ± STD")
axes[0, 1].set_title("Параметр xerr: горизонтальні похибки вимірювань", fontweight='bold')
axes[0, 1].set_xlim(0, 390)
axes[0, 1].grid(axis='x', linestyle=':', alpha=0.6)

# 3. Зсув зліва: параметр left (Waterfall ефект / фази запиту)
dns_time = [10, 12, 15, 14, 11]
tls_time = [25, 30, 40, 35, 28]
server_exec = [10, 143, 265, 161, 26]
axes[1, 0].barh(endpoints, dns_time, height=0.5, color='#64748b', label="DNS Lookup")
axes[1, 0].barh(endpoints, tls_time, left=dns_time, height=0.5, color='#3b82f6', label="TLS Handshake")
left_exec = np.array(dns_time) + np.array(tls_time)
axes[1, 0].barh(endpoints, server_exec, left=left_exec, height=0.5, color='#ef4444', label="Server Processing")
axes[1, 0].set_xlabel("Час виконання за фазами (ms)")
axes[1, 0].set_title("Параметр left: зміщення початку (Waterfall-структура)", fontweight='bold')
axes[1, 0].legend(loc='lower right')
axes[1, 0].grid(axis='x', linestyle=':', alpha=0.6)

# 4. Сортування + колірне кодування порогів SLA
sorted_indices = np.argsort(latency_ms)
s_endpoints = [endpoints[i] for i in sorted_indices]
s_lat = [latency_ms[i] for i in sorted_indices]
bar_cols = ['#10b981' if v < 100 else '#f59e0b' if v < 250 else '#ef4444' for v in s_lat]
b_sort = axes[1, 1].barh(s_endpoints, s_lat, color=bar_cols, edgecolor='#0f172a', height=0.55)
axes[1, 1].bar_label(b_sort, padding=4, fmt='%.0f ms', fontweight='bold', fontsize=8.5)
axes[1, 1].axvline(200, color='#dc2626', linestyle='--', linewidth=1.5, label="SLA поріг (200 ms)")
axes[1, 1].set_xlabel("Latency P99 (ms)")
axes[1, 1].set_title("Сортування та порогові лінії (axvline)", fontweight='bold')
axes[1, 1].set_xlim(0, 370)
axes[1, 1].legend()
axes[1, 1].grid(axis='x', linestyle=':', alpha=0.6)

plt.tight_layout()
plt.show()
Out[1]:

Групові bar charts (Grouped Bars)

Порівняння кількох груп для кожної категорії.

grouped_bars.ipynb
Python 3 (ipykernel)
Code
In [1]:
import matplotlib.pyplot as plt
import numpy as np

# Дані: середні бали студентів трьох спеціальностей за 4 предмети
subjects = ["Математика", "Фізика", "Програмування", "Англійська"]
cs_scores = [85, 78, 92, 70]         # Computer Science
eng_scores = [80, 88, 75, 82]        # Engineering
math_scores = [95, 90, 65, 75]       # Mathematics

x = np.arange(len(subjects))
width = 0.25

fig, ax = plt.subplots(figsize=(12, 6))

bars1 = ax.bar(x - width, cs_scores, width, label="Computer Science", 
               color="#3498db", edgecolor="black")
bars2 = ax.bar(x, eng_scores, width, label="Engineering", 
               color="#e74c3c", edgecolor="black")
bars3 = ax.bar(x + width, math_scores, width, label="Mathematics", 
               color="#2ecc71", edgecolor="black")

ax.set_title("Середні бали студентів за спеціальностями", 
             fontsize=14, fontweight="bold")
ax.set_ylabel("Середній бал (0–100)", fontsize=12)
ax.set_xlabel("Предмет", fontsize=12)
ax.set_xticks(x)
ax.set_xticklabels(subjects)
ax.legend(fontsize=11, loc="upper right")
ax.grid(axis="y", alpha=0.3)
ax.set_ylim(0, 100)

plt.tight_layout()
plt.show()
Out[1]:

Stacked bar charts (складені стовпці)

Показують склад кожної категорії та загальну суму.

stacked_bars.ipynb
Python 3 (ipykernel)
Code
In [1]:
import matplotlib.pyplot as plt
import numpy as np

# Дані: джерела енергії в різних країнах (%)
countries = ["Україна", "Німеччина", "Франція", "Норвегія"]
fossil = [60, 40, 10, 5]      # Викопне паливо
nuclear = [25, 10, 70, 0]     # Ядерна
renewable = [15, 50, 20, 95]  # Відновлювані

fig, ax = plt.subplots(figsize=(10, 7))

ax.bar(countries, fossil, label="Викопне паливо", color="#D35400", edgecolor="black")
ax.bar(countries, nuclear, bottom=fossil, label="Ядерна енергія", 
       color="#9B59B6", edgecolor="black")

# Для третього шару потрібна сума попередніх
bottom_renewable = np.array(fossil) + np.array(nuclear)
ax.bar(countries, renewable, bottom=bottom_renewable, 
       label="Відновлювані джерела", color="#27AE60", edgecolor="black")

ax.set_title("Структура енергетики за країнами (%)", 
             fontsize=14, fontweight="bold")
ax.set_ylabel("Частка (%)", fontsize=12)
ax.set_ylim(0, 100)
ax.legend(loc="upper left", fontsize=11)
ax.grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.show()
Out[1]:
Обмеження stacked bar charts:
  1. Важко порівнювати середні шари — лише нижній шар має спільну базову лінію. Верхні шари «плавають» на різних висотах.
  2. Не показує абсолютні значення — лише відсотки або частки. Якщо потрібні абсолютні числа → grouped bars.
  3. Максимум 3–4 шари — більше стає нечитабельним.
Альтернатива: 100% stacked bars (усі стовпці однакової висоти, показують тільки пропорції) або grouped bars, якщо потрібне точне порівняння.

Математика позиціонування: Групові, Складені та 100% нормовані стовпці

Побудова складних категоріальних порівнянь у чистому Matplotlib базується на простій векторній арифметиці масивів NumPy:

  1. Групові стовпці (Grouped): щоб стовпці не накладалися, кожна група зміщується вздовж осі X на фіксований крок. Для $K$ груп із шириною стовпця $w$: $$x_k = x + \left(k - \frac{K-1}{2}\right) \cdot w$$
  2. Складені стовпці (Stacked): координата X залишається однаковою, але кожен новий шар опирається на суму висот попередніх шарів через параметр bottom: $$\text{bottom}k = \sum^{k-1} \text{height}_j$$
  3. 100% нормовані стовпці (Normalized Stacked): абсолютні значення діляться на суму категорії $\text{totals} = \sum y_i$, що перетворює стовпці на стабільну шкалу 0–100%.
grouped_stacked_math.ipynb
Python 3 (ipykernel)
Code
md

Практичне порівняння: Grouped, Stacked, 100% Normalized та сумарні підписи

Аналіз витрат на хмарну інфраструктуру за 4 місяці

In [1]:
0.26s
import matplotlib.pyplot as plt
import numpy as np

months = ["Січ", "Лют", "Бер", "Кві"]
x_m = np.arange(len(months))
w = 0.25

compute = np.array([45, 52, 60, 75])
storage = np.array([20, 22, 25, 30])
network = np.array([15, 18, 22, 28])
totals = compute + storage + network

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 1. Grouped Bar Chart (3 групи з точним зміщенням)
axes[0, 0].bar(x_m - w, compute, width=w, label="Compute (EC2)", color='#3b82f6', edgecolor='black', linewidth=0.8)
axes[0, 0].bar(x_m, storage, width=w, label="Storage (S3)", color='#10b981', edgecolor='black', linewidth=0.8)
axes[0, 0].bar(x_m + w, network, width=w, label="Network (Egress)", color='#f59e0b', edgecolor='black', linewidth=0.8)
axes[0, 0].set_xticks(x_m)
axes[0, 0].set_xticklabels(months)
axes[0, 0].set_ylabel("Витрати ($ тис.)")
axes[0, 0].set_title("Груповий Bar Chart: детальне порівняння категорій\nЗсув: x - w, x, x + w", fontweight='bold')
axes[0, 0].legend()
axes[0, 0].grid(axis='y', linestyle=':', alpha=0.6)

# 2. Stacked Bar Chart (абсолютний бюджет через bottom)
axes[0, 1].bar(months, compute, label="Compute (EC2)", color='#3b82f6', edgecolor='black', linewidth=0.8, width=0.55)
axes[0, 1].bar(months, storage, bottom=compute, label="Storage (S3)", color='#10b981', edgecolor='black', linewidth=0.8, width=0.55)
axes[0, 1].bar(months, network, bottom=compute + storage, label="Network (Egress)", color='#f59e0b', edgecolor='black', linewidth=0.8, width=0.55)
axes[0, 1].set_ylabel("Загальний бюджет ($ тис.)")
axes[0, 1].set_title("Складений Bar Chart: загальний обсяг і частка\nНакопичення: bottom=compute, bottom=compute+storage", fontweight='bold')
axes[0, 1].legend()
axes[0, 1].grid(axis='y', linestyle=':', alpha=0.6)

# 3. 100% Normalized Stacked Bar Chart (відносні частки)
p_comp = compute / totals * 100
p_stor = storage / totals * 100
p_net = network / totals * 100

axes[1, 0].bar(months, p_comp, label="Compute (%)", color='#3b82f6', edgecolor='black', linewidth=0.8, width=0.55)
axes[1, 0].bar(months, p_stor, bottom=p_comp, label="Storage (%)", color='#10b981', edgecolor='black', linewidth=0.8, width=0.55)
axes[1, 0].bar(months, p_net, bottom=p_comp + p_stor, label="Network (%)", color='#f59e0b', edgecolor='black', linewidth=0.8, width=0.55)
axes[1, 0].set_ylabel("Частка у бюджеті (%)")
axes[1, 0].set_ylim(0, 100)
axes[1, 0].set_title("100% Нормований Stacked: зміна пропорцій компонентів\nВісь Y завжди фіксована на 100%", fontweight='bold')
axes[1, 0].legend(loc='lower right')
axes[1, 0].grid(axis='y', linestyle=':', alpha=0.6)

# 4. Складений Bar Chart із загальними підписами над стовпцями
axes[1, 1].bar(months, compute, label="Compute", color='#3b82f6', edgecolor='black', linewidth=0.8, width=0.55)
axes[1, 1].bar(months, storage, bottom=compute, label="Storage", color='#10b981', edgecolor='black', linewidth=0.8, width=0.55)
axes[1, 1].bar(months, network, bottom=compute + storage, label="Network", color='#f59e0b', edgecolor='black', linewidth=0.8, width=0.55)
for i, tot in enumerate(totals):
    axes[1, 1].text(i, tot + 2, f"${tot}k", ha='center', va='bottom', fontweight='bold', fontsize=9.5)
axes[1, 1].set_ylabel("Витрати ($ тис.)")
axes[1, 1].set_ylim(0, 155)
axes[1, 1].set_title("Stacked Bar з числовими підписами сум зверху\nax.text(i, total + offset, f'${total}k')", fontweight='bold')
axes[1, 1].legend()
axes[1, 1].grid(axis='y', linestyle=':', alpha=0.6)

plt.tight_layout()
plt.show()
Out[1]:

Pie Charts — кругові діаграми

Pie charts показують частки цілого. Але більшість Data Scientists їх уникають.

pie_chart.ipynb
Python 3 (ipykernel)
Code
In [1]:
import matplotlib.pyplot as plt

# Дані: розподіл бюджету компанії
categories = ["Зарплати", "Оренда", "Маркетинг", "R&D", "Інше"]
expenses = [50, 15, 20, 10, 5]

fig, ax = plt.subplots(figsize=(8, 8))

colors = ["#FF6B6B", "#4ECDC4", "#95E1D3", "#FFD93D", "#C7CEEA"]
explode = (0.1, 0, 0, 0, 0)  # "Витягуємо" перший сектор

wedges, texts, autotexts = ax.pie(expenses, labels=categories, autopct="%1.1f%%",
                                    startangle=90, colors=colors, explode=explode,
                                    textprops={"fontsize": 12, "weight": "bold"},
                                    wedgeprops={"edgecolor": "black", "linewidth": 1.5})

ax.set_title("Розподіл бюджету компанії", fontsize=14, fontweight="bold")

plt.tight_layout()
plt.show()
Out[1]:

Чому pie charts погані (майже завжди)

pie_vs_bar.ipynb
Python 3 (ipykernel)
Code
md

Експеримент: порівняйте дві візуалізації

Що швидше і точніше: pie chart чи bar chart?

In [1]:
import matplotlib.pyplot as plt

products = ["Продукт A", "Продукт B", "Продукт C", "Продукт D", "Продукт E"]
sales = [23, 19, 21, 18, 19]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))

# Ліворуч: Pie chart
colors = ["#FF6B6B", "#4ECDC4", "#95E1D3", "#FFD93D", "#C7CEEA"]
ax1.pie(sales, labels=products, autopct="%1.1f%%", startangle=90, colors=colors)
ax1.set_title("Pie Chart\n❓ Який продукт другий за продажами?", 
              fontsize=12, fontweight="bold")

# Праворуч: Bar chart
ax2.bar(products, sales, color=colors, edgecolor="black", linewidth=1.5)
ax2.set_title("Bar Chart\n✅ Відповідь очевидна: Продукт C", 
              fontsize=12, fontweight="bold")
ax2.set_ylabel("Продажі (тис. одиниць)")
ax2.grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.show()
Out[1]:
md

Проблеми pie charts:

  1. Людський мозок погано оцінює кути — важко порівняти 23% та 21% візуально
  2. Порівняння складне — «Який сектор більший: 2-й чи 4-й?» — потрібно вдивлятися
  3. Багато категорій = хаос — >6 секторів стають нечитабельними
  4. 3D pie charts = катастрофа — перспектива спотворює пропорції

Bar chart вирішує всі ці проблеми: довжину порівнювати легко, порядок зрозумілий, масштабується на 20+ категорій.

Коли pie chart виправданий (рідкісні випадки):
  1. Дві категорії (50/50, 60/40) — «Так» vs «Ні», «Чоловіки» vs «Жінки»
  2. Одна категорія домінує (наприклад, 80% vs 20%) — це видно одразу
  3. Презентація для нетехнічної аудиторії — pie chart «інтуїтивніший» для людей, які не працюють з даними
В усіх інших випадках → bar chart.

Повна сигнатура ax.pie() та створення сучасного Donut Chart

Кругова діаграма будується методом ax.pie(). Вона ділить круг на сектори, кутова площа яких пропорційна величинам масиву $x$. Метод повертає кортеж із трьох списків: об'єктів секторів Wedge, текстових міток категорій Text та числових міток відсотків Text (якщо задано autopct).

ax.pie(
    x,
    explode=None,
    labels=None,
    colors=None,
    autopct=None,
    pctdistance=0.6,
    shadow=False,
    labeldistance=1.1,
    startangle=0,
    radius=1,
    counterclock=True,
    wedgeprops=None,
    textprops=None,
    center=(0, 0),
    frame=False,
    rotatelabels=False,
    *,
    normalize=True,
    hatch=None,
    data=None
) -> tuple[list[Wedge], list[Text], list[Text]]
ПараметрТипЗа замовчуваннямОпис та інженерне призначення
xArrayLikeобов'язковийМасив числових значень часток або кількостей.
explodeSequence[float] | NoneNoneЗсув кожного сектора від центру (наприклад, (0.1, 0, 0) висуває перший сектор).
labelsSequence[str] | NoneNoneСписок текстових підписів категорій навколо діаграми.
colorsSequence[color] | NoneNoneКольори секторів.
autopctstr | Callable | NoneNoneФормат відображення відсотків всередині сектора (наприклад, '%1.1f%%').
pctdistancefloat0.6Радіальна відстань від центру до тексту відсотків (0 — центр, 1 — зовнішній край).
startanglefloat0Кут повороту першого сектора в градусах (рекомендується 90 для початку зверху).
wedgepropsdict | NoneNoneСловник параметрів ліній та форми. Ключовий трюк: {'width': 0.4} перетворює коло на Donut Chart!
textpropsdict | NoneNoneСловник параметрів шрифту (розмір, жирність, колір).
Donut Chart (кільцева діаграма): сучасна альтернатива Pie ChartУ дашбордах суцільні кругові діаграми часто замінюють на Donut Chart. Завдяки порожньому центру погляд менше фокусується на викривлених кутах, а вільне місце всередині використовують для розміщення загальної суми (KPI, 100% або назви метрики).Створити Donut Chart у Matplotlib надзвичайно просто:
ax.pie(values, labels=labels, wedgeprops={'width': 0.4, 'edgecolor': 'white', 'linewidth': 2})
ax.text(0, 0, "Total\n100%", ha='center', va='center', fontweight='bold', fontsize=12)
pie_parameters_donut.ipynb
Python 3 (ipykernel)
Code
md

Дослідження параметрів ax.pie() та створення Donut Chart

Порівняння стандартного кола, висування сектору (explode), кільцевої діаграми (Donut) та винесення легенди назовні

In [1]:
0.25s
import matplotlib.pyplot as plt

shares = [45, 25, 18, 12]
labels = ["Frontend (Next.js)", "Backend (Go)", "Database (PG)", "Queue (Kafka)"]
colors_pie = ["#3b82f6", "#10b981", "#f59e0b", "#8b5cf6"]

fig, axes = plt.subplots(2, 2, figsize=(13, 11))

# 1. Стандартний Pie Chart
axes[0, 0].pie(shares, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors_pie,
               wedgeprops={'edgecolor': 'black', 'linewidth': 1})
axes[0, 0].set_title("Класичний Pie Chart: autopct='%1.1f%%', startangle=90", fontweight='bold')

# 2. Pie Chart з explode (виділення головного компонента)
explode = (0.1, 0, 0, 0)
axes[0, 1].pie(shares, labels=labels, autopct='%1.1f%%', explode=explode, startangle=140, colors=colors_pie,
               wedgeprops={'edgecolor': 'black', 'linewidth': 1})
axes[0, 1].set_title("Параметр explode=(0.1, 0, 0, 0): фокус на лідері", fontweight='bold')

# 3. Donut Chart (бублик через wedgeprops width=0.4)
wedges, texts, autotexts = axes[1, 0].pie(shares, labels=labels, autopct='%1.1f%%', pctdistance=0.75, startangle=90,
                                         colors=colors_pie, wedgeprops={'width': 0.4, 'edgecolor': 'white', 'linewidth': 2})
for autotext in autotexts:
    autotext.set_color('white')
    autotext.set_fontweight('bold')
axes[1, 0].text(0, 0, "CPU\nLoad", ha='center', va='center', fontsize=13, fontweight='bold', color='#1e293b')
axes[1, 0].set_title("Donut Chart: wedgeprops={'width': 0.4}\nСучасний стиль із KPI в центрі", fontweight='bold')

# 4. Donut Chart з винесеною легендою (Dashboard-ready)
wedges, texts = axes[1, 1].pie(shares, startangle=90, colors=colors_pie,
                               wedgeprops={'width': 0.35, 'edgecolor': 'white', 'linewidth': 2})
axes[1, 1].legend(wedges, [f"{l}: {s}%" for l, s in zip(labels, shares)],
                  title="Компоненти системи", loc="center left", bbox_to_anchor=(1, 0, 0.5, 1),
                  frameon=True, facecolor='#f8fafc', fontsize=9)
axes[1, 1].text(0, 0, "100%", ha='center', va='center', fontsize=14, fontweight='bold', color='#1e293b')
axes[1, 1].set_title("Donut з окремою легендою (bbox_to_anchor):\nЧистий інтерфейс без нагромадження тексту", fontweight='bold')

plt.tight_layout()
plt.show()
Out[1]:

Сортування стовпців

Порядок категорій має значення. Завжди сортуйте, якщо немає природного порядку.

sorted_bars.ipynb
Python 3 (ipykernel)
Code
In [1]:
import matplotlib.pyplot as plt
import pandas as pd

# Дані: зарплати за спеціальностями
data = {
    "Спеціальність": ["Вчитель", "Лікар", "Інженер", "Програміст", "Юрист"],
    "Зарплата ($)": [35000, 75000, 60000, 90000, 55000]
}
df = pd.DataFrame(data)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))

# Ліворуч: без сортування (погано)
ax1.barh(df["Спеціальність"], df["Зарплата ($)"], color="gray")
ax1.set_title("❌ Без сортування\n(важко порівнювати)", fontsize=12, fontweight="bold")
ax1.set_xlabel("Зарплата ($)")

# Праворуч: відсортовано за спаданням (добре)
df_sorted = df.sort_values("Зарплата ($)", ascending=True)  # True для горизонтальних
ax2.barh(df_sorted["Спеціальність"], df_sorted["Зарплата ($)"], 
         color="steelblue", edgecolor="navy")
ax2.set_title("✅ Відсортовано\n(порівняння миттєве)", fontsize=12, fontweight="bold")
ax2.set_xlabel("Зарплата ($)")

plt.tight_layout()
plt.show()
Out[1]:
Правила сортування:
  1. Числові дані — завжди сортуйте за значенням (зростання або спадання)
  2. Часові дані — залишайте хронологічний порядок (січень, лютий, березень...)
  3. Природний порядок — зберігайте (низький/середній/високий, понеділок/вівторок...)
  4. Без природного порядку — сортуйте за алфавітом або за значенням
Мета: мінімізувати когнітивне навантаження на читача.

Практичні завдання

📝 Рівень 1: Базові навички

Завдання 1.1: Створіть вертикальний bar chart для топ-5 улюблених фруктів (придумайте дані). Додайте підписи над стовпцями.

Завдання 1.2: Побудуйте горизонтальний bar chart для часу виконання різних алгоритмів сортування (дані у мілісекундах). Відсортуйте від найшвидшого до найповільнішого.

Завдання 1.3: Створіть pie chart для розподілу часу протягом дня (сон, робота, дозвілля, тощо). Спробуйте замінити на bar chart. Що зручніше?

🧮 Рівень 2: Аналіз даних

Завдання 2.1: Завантажте датасет Titanic. Побудуйте bar chart: кількість пасажирів у кожному класі (1st, 2nd, 3rd). Додайте другий bar chart: кількість тих, хто вижив, у кожному класі.

Завдання 2.2: Побудуйте групові bar charts: порівняйте середній вік чоловіків та жінок у трьох класах Titanic.

Завдання 2.3: Створіть stacked bar chart для датасету Tips (seaborn.load_dataset("tips")): для кожного дня тижня покажіть склад — скільки було обідів (time=="Lunch") та скільки вечерь (time=="Dinner").

🚀 Рівень 3: Дослідницький проєкт

Проєкт: Порівняння типів візуалізацій

  1. Створіть датасет: продажі 8 продуктів за 3 квартали (24 значення)
  2. Візуалізуйте чотирма способами:
    • Grouped bars (3 групи стовпців для кожного продукту)
    • Stacked bars (стовпці з 3 шарами)
    • 3 окремі bar charts (по одному для кожного кварталу)
    • Heatmap (рядки = продукти, стовпці = квартали)
  3. Напишіть короткий висновок: який спосіб найкраще показує:
    • Лідера серед продуктів?
    • Динаміку зростання/спаду?
    • Загальну суму за всі квартали?

Резюме підтеми

📊 Bar Charts

  • ax.bar() — вертикальні стовпці
  • ax.barh() — горизонтальні стовпці
  • Завжди сортуйте, якщо немає природного порядку
  • Додавайте підписи над/праворуч від стовпців для точності
  • Grouped bars → порівняння груп
  • Stacked bars → склад категорій

🥧 Pie Charts (уникайте!)

  • Людський мозок погано оцінює кути
  • Важко порівняти сектори схожого розміру
  • 5 секторів = нечитабельно

  • Bar chart майже завжди кращий
  • Виправдані лише для 2 категорій або домінування однієї (80/20)

🎯 Вибір типу графіка

  • Порівняння категорій → bar chart
  • Частки цілого → bar chart (не pie!)
  • Порівняння груп → grouped bars
  • Склад категорій → stacked bars
  • Часові дані → line chart (не bar!)
Чек-лист bar chart:
  1. Відсортували категорії (за значенням або за алфавітом)
  2. Вибрали орієнтацію: вертикальну (для часових даних, малої кількості категорій) або горизонтальну (для довгих назв, багатьох категорій)
  3. Додали підписи значень над/біля стовпців
  4. Використали контрастні кольори (але не більше 3–4)
  5. Додали сітку по осі значень (alpha=0.3)
  6. Почали вісь Y з 0 (не обрізали, щоб не спотворити пропорції)

Попередня підтема: Візуалізація залежностейНаступна підтема: Композиції та стилізація — subplots та багатопанельні дашборди

Copyright © 2026