Візуалізація категоріальних даних — bar charts та 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).
Погляньмо, як від трьох мінімальних рядків перейти до інформативного інженерного звіту:
Три рівні створення стовпчастої діаграми
Від сирих стовпців до професійного дашборду з автоматичними підписами значень
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()

Чому Bar Chart перевершує таблиці та кругові діаграми?
- Точність візуального сприйняття: людина найкраще порівнює довжини відрізків зі спільної базової лінії (вісь 0). Ми миттєво бачимо, що
Billingмає у 3 рази більше інцидентів, ніжNotify. - Масштабованість: стовпчаста діаграма однаково добре відображає як 3 категорії, так і 20 (особливо в горизонтальному форматі).
- Гнучкість акцентування: легке додавання кольорових акцентів, планок похибок (
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 секунди:
Квартальні продажі трьох регіонів
Один графік замінює 10 хвилин аналізу таблиці
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()

Висновки одразу видно:
- Київ — стабільне зростання з Q1 до Q4 (120 → 220)
- Львів — теж зростання, але обсяги менші (80 → 130)
- Одеса — найбільший регіон, але спад (200 → 175)
Це те, що неможливо побачити швидко в таблиці з числами!
| Bar Chart | Гістограма |
|---|---|
| Для категоріальних даних (міста, продукти) | Для неперервних даних (зріст, вік) |
| Стовпці розділені проміжками | Стовпці стикаються (bins) |
| Порядок категорій можна змінювати | Порядок bins фіксований (числова вісь) |
| Вимірює кількість, суму, середнє тощо | Вимірює частоту (кількість у інтервалі) |
Вертикальні bar charts
Базовий тип — стовпці вертикально. Зручно для порівняння кількох категорій.
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()

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
| Параметр | Тип | За замовчуванням | Опис та інженерне призначення |
|---|---|---|---|
x | float | ArrayLike | обов'язковий | Координати X (числа або список категоріальних текстових міток). |
height | float | ArrayLike | обов'язковий | Висота стовпців (числові значення вимірюваної величини). |
width | float | ArrayLike | 0.8 | Ширина стовпців. Значення width=1.0 робить стовпці впритул без проміжків. |
bottom | float | ArrayLike | 0 | Нижня координата стовпця. Використовується для побудови складених стовпців (Stacked bars). |
align | {'center', 'edge'} | 'center' | Вирівнювання відносно координати $x$: по центру або по лівому краю. |
color | color | Sequence | None (C0) | Колір заливки стовпців (можна передати список для вибіркового акцентування). |
edgecolor | color | Sequence | None | Колір обведення контуру стовпців (додає чіткості). |
linewidth | float | rcParams | Товщина лінії контуру стовпця. |
yerr | float | ArrayLike | None | Планки похибок (error bars) для відображення дисперсії, STD або довірчого інтервалу. |
capsize | float | 0 | Довжина горизонтальних засічок на планках похибок (у пунктах). |
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 — формат виведення чисел.Дослідження параметрів ax.bar(): ширина, вирівнювання, похибки та стилізація
Порівняння значень width, центровок align, планок похибок yerr та комбінування bottom із штрихуванням
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()

Горизонтальні bar charts
Коли категорій багато або назви довгі, горизонтальні стовпці зручніші.
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()

- Довгі назви категорій — «Технічна підтримка клієнтів» не поміститься вертикально
- Багато категорій (>7–8) — вертикально стане тісно
- Порівняння рангів — зручніше читати зверху вниз (топ-10, рейтинги)
- Часових даних (місяці, квартали — звичка читати зліва направо)
- Невеликої кількості категорій (3–6)
Повна сигнатура та анатомія параметрів ax.barh()
Горизонтальні стовпці будуються за допомогою ax.barh(). Головна відмінність від вертикальних полягає у рокіровці параметрів: аргумент width відповідає за горизонтальну довжину (значення величини), а аргумент height — за товщину смуги.
ax.barh(
y,
width,
height=0.8,
left=None,
*,
align='center',
data=None,
**kwargs
) -> BarContainer
| Параметр | Тип | За замовчуванням | Опис та інженерне призначення |
|---|---|---|---|
y | float | ArrayLike | обов'язковий | Координати Y (позиції на вертикальній осі або текстові мітки категорій). |
width | float | ArrayLike | обов'язковий | Горизонтальна довжина смуг (числові значення метрик). |
height | float | ArrayLike | 0.8 | Товщина смуги (вертикальний розмір кожного прямокутника). |
left | float | ArrayLike | 0 | Зсув лівого краю смуги. Аналог bottom у ax.bar: дозволяє будувати горизонтальні Stacked бари або Waterfall діаграми фаз виконання. |
align | {'center', 'edge'} | 'center' | Вирівнювання смуги відносно координати $y$. |
xerr | float | ArrayLike | None | Горизонтальні планки похибок / довірчих інтервалів. |
capsize | float | 0 | Довжина вертикальних засічок на планках похибок xerr. |
Дослідження параметрів ax.barh(): товщина, похибки, Waterfall та порогові лінії
Порівняння height, планок xerr, каскадного зсуву left та сортування з критичною лінією SLA
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()

Групові bar charts (Grouped Bars)
Порівняння кількох груп для кожної категорії.
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()

Stacked bar charts (складені стовпці)
Показують склад кожної категорії та загальну суму.
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()

- Важко порівнювати середні шари — лише нижній шар має спільну базову лінію. Верхні шари «плавають» на різних висотах.
- Не показує абсолютні значення — лише відсотки або частки. Якщо потрібні абсолютні числа → grouped bars.
- Максимум 3–4 шари — більше стає нечитабельним.
Математика позиціонування: Групові, Складені та 100% нормовані стовпці
Побудова складних категоріальних порівнянь у чистому Matplotlib базується на простій векторній арифметиці масивів NumPy:
- Групові стовпці (Grouped): щоб стовпці не накладалися, кожна група зміщується вздовж осі X на фіксований крок. Для $K$ груп із шириною стовпця $w$: $$x_k = x + \left(k - \frac{K-1}{2}\right) \cdot w$$
- Складені стовпці (Stacked): координата X залишається однаковою, але кожен новий шар опирається на суму висот попередніх шарів через параметр
bottom: $$\text{bottom}k = \sum^{k-1} \text{height}_j$$ - 100% нормовані стовпці (Normalized Stacked): абсолютні значення діляться на суму категорії $\text{totals} = \sum y_i$, що перетворює стовпці на стабільну шкалу 0–100%.
Практичне порівняння: Grouped, Stacked, 100% Normalized та сумарні підписи
Аналіз витрат на хмарну інфраструктуру за 4 місяці
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()

Pie Charts — кругові діаграми
Pie charts показують частки цілого. Але більшість Data Scientists їх уникають.
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()

Чому pie charts погані (майже завжди)
Експеримент: порівняйте дві візуалізації
Що швидше і точніше: pie chart чи bar chart?
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()

Проблеми pie charts:
- Людський мозок погано оцінює кути — важко порівняти 23% та 21% візуально
- Порівняння складне — «Який сектор більший: 2-й чи 4-й?» — потрібно вдивлятися
- Багато категорій = хаос — >6 секторів стають нечитабельними
- 3D pie charts = катастрофа — перспектива спотворює пропорції
Bar chart вирішує всі ці проблеми: довжину порівнювати легко, порядок зрозумілий, масштабується на 20+ категорій.
- Дві категорії (50/50, 60/40) — «Так» vs «Ні», «Чоловіки» vs «Жінки»
- Одна категорія домінує (наприклад, 80% vs 20%) — це видно одразу
- Презентація для нетехнічної аудиторії — pie 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]]
| Параметр | Тип | За замовчуванням | Опис та інженерне призначення |
|---|---|---|---|
x | ArrayLike | обов'язковий | Масив числових значень часток або кількостей. |
explode | Sequence[float] | None | None | Зсув кожного сектора від центру (наприклад, (0.1, 0, 0) висуває перший сектор). |
labels | Sequence[str] | None | None | Список текстових підписів категорій навколо діаграми. |
colors | Sequence[color] | None | None | Кольори секторів. |
autopct | str | Callable | None | None | Формат відображення відсотків всередині сектора (наприклад, '%1.1f%%'). |
pctdistance | float | 0.6 | Радіальна відстань від центру до тексту відсотків (0 — центр, 1 — зовнішній край). |
startangle | float | 0 | Кут повороту першого сектора в градусах (рекомендується 90 для початку зверху). |
wedgeprops | dict | None | None | Словник параметрів ліній та форми. Ключовий трюк: {'width': 0.4} перетворює коло на Donut Chart! |
textprops | dict | None | None | Словник параметрів шрифту (розмір, жирність, колір). |
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)
Дослідження параметрів ax.pie() та створення Donut Chart
Порівняння стандартного кола, висування сектору (explode), кільцевої діаграми (Donut) та винесення легенди назовні
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()

Сортування стовпців
Порядок категорій має значення. Завжди сортуйте, якщо немає природного порядку.
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()

- Числові дані — завжди сортуйте за значенням (зростання або спадання)
- Часові дані — залишайте хронологічний порядок (січень, лютий, березень...)
- Природний порядок — зберігайте (низький/середній/високий, понеділок/вівторок...)
- Без природного порядку — сортуйте за алфавітом або за значенням
Практичні завдання
📝 Рівень 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: Дослідницький проєкт
Проєкт: Порівняння типів візуалізацій
- Створіть датасет: продажі 8 продуктів за 3 квартали (24 значення)
- Візуалізуйте чотирма способами:
- Grouped bars (3 групи стовпців для кожного продукту)
- Stacked bars (стовпці з 3 шарами)
- 3 окремі bar charts (по одному для кожного кварталу)
- Heatmap (рядки = продукти, стовпці = квартали)
- Напишіть короткий висновок: який спосіб найкраще показує:
- Лідера серед продуктів?
- Динаміку зростання/спаду?
- Загальну суму за всі квартали?
Резюме підтеми
📊 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!)
- Відсортували категорії (за значенням або за алфавітом)
- Вибрали орієнтацію: вертикальну (для часових даних, малої кількості категорій) або горизонтальну (для довгих назв, багатьох категорій)
- Додали підписи значень над/біля стовпців
- Використали контрастні кольори (але не більше 3–4)
- Додали сітку по осі значень (alpha=0.3)
- Почали вісь Y з 0 (не обрізали, щоб не спотворити пропорції)
Попередня підтема: Візуалізація залежностейНаступна підтема: Композиції та стилізація — subplots та багатопанельні дашборди
Візуалізація залежностей — scatter plots та кореляція
Точкові діаграми для виявлення кореляцій, візуалізація лінійних та нелінійних залежностей, regression lines, scatter matrices
Композиції та стилізація — subplots та багатопанельні дашборди
Побудова кількох графіків на одній фігурі, GridSpec для складних компоновок, кольорові схеми, теми, стилізація для публікацій та презентацій