Композиції та стилізація — subplots та багатопанельні дашборди
Композиції та стилізація — subplots та багатопанельні дашборди
🎯 Мета підтеми
- Навчитися створювати кілька графіків на одній фігурі (subplots)
- Опанувати GridSpec для складних компоновок
- Освоїти кольорові схеми та теми matplotlib
- Навчитися створювати «дашборди» для аналізу даних
- Підготувати графіки для публікацій, презентацій та звітів
🔑 Ключові терміни
- Subplots — кілька осей (Axes) на одній фігурі (Figure)
- GridSpec — гнучка сітка для нестандартних компоновок
- Colormap — кольорова палітра для безперервних даних
- Style — тема оформлення (seaborn, ggplot, dark_background)
- Tight layout — автоматичне вирівнювання для уникнення перекриття
Плавний старт: Subplots за 3 рядки коду
Коли для повноти аналізу одного графіка недостатньо, інженери об'єднують кілька панелей на одній фігурі — створюють subplots. Це дозволяє порівнювати метрики різних систем в одному візуальному контексті.
Погляньмо, як від трьох мінімальних рядків перейти до повноцінного центру моніторингу:
Три рівні створення багатопанельних композицій
Від створення сітки 1×2 у 3 рядки до інженерного дашборду з пороговими лініями SLA
import matplotlib.pyplot as plt
import numpy as np
# Синтетичні дані моніторингу сервера: 60 хвилин роботи
rng = np.random.default_rng(42)
t = np.linspace(0, 60, 60)
cpu = 35 + 20 * np.sin(t / 8) + rng.normal(0, 3, 60)
ram = 4 + 0.05 * t + rng.normal(0, 0.1, 60)
# --- РІВЕНЬ 0: Мінімальний код (3 рядки) ---
fig, (ax1, ax2) = plt.subplots(1, 2)
plt.show()
# --- РІВЕНЬ 1: Розділені осі та фізичні одиниці виміру ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(7, 4.5))
ax1.plot(t, cpu, color='#2563eb')
ax1.set_ylabel("CPU (%)")
ax1.set_title("Навантаження обчислювального вузла")
ax1.grid(True, linestyle=':', alpha=0.5)
ax2.plot(t, ram, color='#10b981')
ax2.set_xlabel("Час моніторингу (хв)")
ax2.set_ylabel("RAM (GB)")
ax2.grid(True, linestyle=':', alpha=0.5)
plt.tight_layout()
plt.show()
# --- РІВЕНЬ 2: Професійний NOC-дашборд з SLA та зонами заповнення ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 5))
fig.suptitle("NOC Оперативний моніторинг: Node Alpha", fontsize=13, fontweight='bold')
# Панель процесора
ax1.plot(t, cpu, color='#ef4444', linewidth=1.8, label="CPU Core 0")
ax1.axhline(50, color='#dc2626', linestyle='--', linewidth=1.2, label="SLA поріг (50%)")
ax1.fill_between(t, cpu, alpha=0.15, color='#ef4444')
ax1.set_ylabel("CPU (%)")
ax1.legend(loc='upper right', frameon=True, facecolor='#f8fafc')
ax1.grid(True, linestyle=':', alpha=0.5)
# Панель оперативної пам'яті
ax2.plot(t, ram, color='#3b82f6', linewidth=1.8, label="RAM Total")
ax2.fill_between(t, ram, alpha=0.15, color='#3b82f6')
ax2.set_xlabel("Час моніторингу (хв)")
ax2.set_ylabel("RAM (GB)")
ax2.legend(loc='lower right', frameon=True, facecolor='#f8fafc')
ax2.grid(True, linestyle=':', alpha=0.5)
plt.tight_layout()
plt.show()

Чому багатопанельні композиції незамінні в інженерії?
- Єдиний контекст порівняння: зіставлення піку процесора з одночасним зростанням пам'яті або мережевого трафіку дозволяє за секунди знайти причину системного збою.
- Усунення плутанини одиниць виміру: замість нагромадження ліній із відсотками (%) та гігабайтами (GB) на одних осях, subplots надають кожній метриці власний чистий простір.
- Економія уваги людини: структурований звіт на одній сторінці читається в рази швидше, ніж перемикання між окремими вікнами чи вкладками.
🧭 Наскрізна інженерна історія: Побудова центру оперативного моніторингу (NOC Dashboard)
Уявіть, що ви керуєте надійністю інфраструктури (Lead SRE) у хмарному провайдері. Перед запуском нового регіону дата-центру команді потрібен централізований екран моніторингу (NOC Dashboard), який у реальному часі відображатиме здоров'я кластера:
- Крок 1 (Subplots): Створити базову сітку синхронізованих панелей для ключових ресурсів сервера (CPU vs RAM).
- Крок 2 (Анатомія
plt.subplotsтаsubplot_mosaic): Опанувати гнучку адресацію підграфіків у циклі через.flatten()та декларативний синтаксис мозаїки для швидкого прототипування. - Крок 3 (Асиметричний
GridSpec): Побудувати професійний діагностичний Jointplot (основне поле розсіювання + бічні та верхні маргінальні гістограми розподілу). - Крок 4 (Спільні осі
sharex/sharey): Забезпечити безшовну часову синхронізацію: масштабування одного графіка автоматично вирівнює всі інші панелі. - Крок 5 (Теми та стилізація): Налаштувати темну тему (
dark_background) для моніторів чергової зміни та чистий стиль (seaborn-v0_8-whitegrid) для звітів керівництву. - Крок 6 (Інженерні анотації та зони
axvspan/axhspan): Підсвітити часові інтервали DDoS-атак та порогові коридори SLA (Normal, Warning, Critical).
Розгляньмо кожен із цих аналітичних інструментів послідовно.
Історія про аналітика та квартальний звіт
Аналітик Ганна готує квартальний звіт для CEO. Їй потрібно показати:
- Динаміку продажів за 12 місяців
- Розподіл клієнтів за віком
- Топ-5 продуктів за виручкою
- Кореляцію між витратами на маркетинг та продажами
Вона може створити 4 окремі файли з графіками. Але CEO хоче одну сторінку — щоб порівняти все одним поглядом.
Рішення: subplots — 4 графіки на одній фігурі.
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=42)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle("Квартальний звіт Q4 2026 — Ключові метрики",
fontsize=16, fontweight="bold")
# 1. Динаміка продажів (лінійний графік)
months = ["Січ", "Лют", "Бер", "Кві", "Тра", "Чер",
"Лип", "Сер", "Вер", "Жов", "Лис", "Гру"]
sales = [120, 135, 150, 145, 160, 180, 190, 185, 200, 220, 240, 260]
axes[0, 0].plot(months, sales, marker="o", linewidth=2.5, color="steelblue")
axes[0, 0].fill_between(range(len(months)), sales, alpha=0.3, color="steelblue")
axes[0, 0].set_title("Динаміка продажів (тис. грн)", fontweight="bold")
axes[0, 0].set_ylabel("Продажі")
axes[0, 0].grid(True, alpha=0.3)
axes[0, 0].tick_params(axis="x", rotation=45)
# 2. Розподіл клієнтів за віком (гістограма)
ages = rng.normal(35, 12, 500)
axes[0, 1].hist(ages, bins=25, color="coral", edgecolor="black", alpha=0.7)
axes[0, 1].axvline(np.mean(ages), color="red", linestyle="--", linewidth=2,
label=f"Середнє: {np.mean(ages):.1f}")
axes[0, 1].set_title("Розподіл клієнтів за віком", fontweight="bold")
axes[0, 1].set_xlabel("Вік (роки)")
axes[0, 1].set_ylabel("Кількість клієнтів")
axes[0, 1].legend()
axes[0, 1].grid(axis="y", alpha=0.3)
# 3. Топ-5 продуктів (bar chart)
products = ["Продукт A", "Продукт B", "Продукт C", "Продукт D", "Продукт E"]
revenue = [450, 380, 320, 280, 210]
axes[1, 0].barh(products, revenue, color="mediumseagreen", edgecolor="black")
axes[1, 0].set_title("Топ-5 продуктів за виручкою (тис. грн)", fontweight="bold")
axes[1, 0].set_xlabel("Виручка")
axes[1, 0].grid(axis="x", alpha=0.3)
# 4. Кореляція маркетинг — продажі (scatter)
marketing = np.array([10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65])
sales_vs_marketing = 3 * marketing + 80 + rng.normal(0, 10, 12)
axes[1, 1].scatter(marketing, sales_vs_marketing, s=100, alpha=0.7,
color="purple", edgecolor="black")
# Лінія тренду
z = np.polyfit(marketing, sales_vs_marketing, 1)
p = np.poly1d(z)
axes[1, 1].plot(marketing, p(marketing), "r--", linewidth=2,
label=f"r = {np.corrcoef(marketing, sales_vs_marketing)[0,1]:.2f}")
axes[1, 1].set_title("Маркетинг vs Продажі", fontweight="bold")
axes[1, 1].set_xlabel("Витрати на маркетинг (тис. грн)")
axes[1, 1].set_ylabel("Продажі (тис. грн)")
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Переваги композиції:
- Одна сторінка замість чотирьох окремих файлів
- Швидке порівняння — CEO бачить усі метрики одночасно
- Контекст — динаміка продажів зрозуміліша поруч з маркетингом
- Професійний вигляд — єдиний загальний заголовок, узгоджені кольори
Базові subplots — регулярна сітка
Створення сітки 2×2
import matplotlib.pyplot as plt
import numpy as np
# Створюємо сітку 2x2
rng = np.random.default_rng(seed=42)
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# axes — це масив NumPy розміром (2, 2)
print(f"Тип axes: {type(axes)}")
print(f"Форма axes: {axes.shape}")
# Звертаємося до конкретних осей через індекси
axes[0, 0].plot([1, 2, 3], [1, 4, 9], 'r-o')
axes[0, 0].set_title("Графік 1: Квадрати")
axes[0, 1].hist(rng.normal(0, 1, 500), bins=30, color="green", alpha=0.7)
axes[0, 1].set_title("Графік 2: Гістограма")
axes[1, 0].scatter(rng.random(50), rng.random(50), s=100, alpha=0.6)
axes[1, 0].set_title("Графік 3: Scatter")
axes[1, 1].bar(["A", "B", "C"], [10, 25, 15], color=["red", "blue", "green"])
axes[1, 1].set_title("Графік 4: Bar Chart")
plt.tight_layout()
plt.show()
Тип axes: <class 'numpy.ndarray'> Форма axes: (2, 2)

plt.subplots():fig, axes = plt.subplots(nrows=2, # Кількість рядків
ncols=3, # Кількість стовпців
figsize=(12,8), # Розмір фігури (ширина, висота)
sharex=False, # Спільна вісь X?
sharey=False, # Спільна вісь Y?
squeeze=True) # Якщо 1 Axes, повернути його (не масив)
Повернене значення:- Якщо
nrows=1, ncols=1→axes— один об'єкт Axes - Якщо один вимір > 1 →
axes— одновимірний масив - Якщо обидва виміри > 1 →
axes— двовимірний масив
Доступ до осей у різних конфігураціях
import matplotlib.pyplot as plt
# Конфігурація 1: один рядок, три стовпці
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# axes — одновимірний масив [ax0, ax1, ax2]
axes[0].plot([1, 2, 3])
axes[1].bar(["A", "B"], [5, 10])
axes[2].scatter([1, 2], [3, 4])
plt.tight_layout()
plt.show()
# Конфігурація 2: три рядки, один стовпець
fig, axes = plt.subplots(3, 1, figsize=(8, 10))
# axes — одновимірний масив [ax0, ax1, ax2]
axes[0].plot([1, 2, 3])
axes[1].bar(["A", "B"], [5, 10])
axes[2].scatter([1, 2], [3, 4])
plt.tight_layout()
plt.show()
# Конфігурація 3: 2x3 сітка
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
# axes — двовимірний масив (2, 3)
axes[0, 0].plot([1, 2])
axes[1, 2].bar(["X"], [7])
plt.tight_layout()
plt.show()

axes (одновимірний чи двовимірний), використовуйте .flatten():fig, axes = plt.subplots(2, 3)
axes_flat = axes.flatten() # Тепер завжди одновимірний масив [ax0, ax1, ..., ax5]
for i, ax in enumerate(axes_flat):
ax.text(0.5, 0.5, f"Графік {i+1}", ha="center", va="center", fontsize=14)
Повна сигнатура plt.subplots() та сучасний fig.subplot_mosaic()
Функція plt.subplots() є основним високорівневим фабричним методом створення фігури разом із сіткою осей:
plt.subplots(
nrows=1,
ncols=1,
*,
sharex=False,
sharey=False,
squeeze=True,
width_ratios=None,
height_ratios=None,
subplot_kw=None,
gridspec_kw=None,
**fig_kw
) -> tuple[Figure, Axes | np.ndarray]
| Параметр | Тип | За замовчуванням | Опис та інженерне призначення |
|---|---|---|---|
nrows, ncols | int | 1, 1 | Кількість рядків і стовпців у регулярній сітці. |
sharex, sharey | bool | {'all', 'row', 'col'} | False | Синхронізація меж та шкали осей X або Y між підграфіками. |
squeeze | bool | True | Якщо True, скасовує зайві виміри (повертає окремий Axes для 1×1 або 1D масив для 1×N). Якщо False, завжди повертає 2D-масив форми (nrows, ncols). |
width_ratios | Sequence[float] | None | Пропорції ширини стовпців (наприклад, [2, 1] робить перший стовпець удвічі ширшим). |
height_ratios | Sequence[float] | None | Пропорції висоти рядків (наприклад, [3, 1] для головного графіка та підвалу). |
subplot_kw | dict | None | Словник аргументів, що передаються в кожен окремий Axes (наприклад, {'projection': 'polar'}). |
gridspec_kw | dict | None | Словник додаткових параметрів для базового GridSpec (wspace, hspace). |
fig.subplot_mosaic() (Matplotlib 3.3+)Для побудови нестандартних екранів більше не обов'язково вручну вираховувати зрізи індексів gs[:, 0]! Метод subplot_mosaic() дозволяє задавати макет візуально у вигляді текстової матриці:mosaic = [
["main", "main"],
["bar", "hist"]
]
fig, ax_dict = plt.subplot_mosaic(mosaic, figsize=(10, 7))
# Звернення напряму за семантичними назвами:
ax_dict["main"].plot(t, cpu)
ax_dict["bar"].bar(cats, vals)
ax_dict["hist"].hist(errors)
. у рядку mosaic означає порожній простір (пустий слот).Дослідження компоновок: 1×3, 2×2, subplot_mosaic та ітерація flatten()
Порівняння одновимірних і двовимірних осей, декларативної мозаїки та пакетного опрацювання в циклі
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(42)
t = np.linspace(0, 20, 40)
# Приклад 1: Використання subplot_mosaic для асиметричного NOC-екрана
mosaic = [
["monitor", "monitor"],
["summary", "events"]
]
fig, ax_dict = plt.subplot_mosaic(mosaic, figsize=(11, 7))
fig.suptitle("NOC Дашборд через fig.subplot_mosaic()", fontsize=14, fontweight='bold')
# Головний графік зверху
ax_dict["monitor"].plot(t, 40 + 15 * np.sin(t/3), color='#0284c7', linewidth=2, label="Cluster Load")
ax_dict["monitor"].set_title("Головний монітор: ax_dict['monitor']", fontweight='bold')
ax_dict["monitor"].legend(loc='upper right')
ax_dict["monitor"].grid(True, linestyle=':', alpha=0.5)
# Підсумкова діаграма зліва знизу
ax_dict["summary"].bar(["Ok", "Warn", "Err"], [120, 15, 2], color=['#10b981', '#f59e0b', '#ef4444'], width=0.5)
ax_dict["summary"].set_title("Статуси: ax_dict['summary']", fontweight='bold')
ax_dict["summary"].grid(axis='y', linestyle=':', alpha=0.5)
# Гістограма справа знизу
ax_dict["events"].hist(rng.normal(25, 5, 100), bins=15, color='#8b5cf6', edgecolor='black')
ax_dict["events"].set_title("Розподіл затримок: ax_dict['events']", fontweight='bold')
ax_dict["events"].grid(True, linestyle=':', alpha=0.5)
plt.tight_layout()
plt.show()

GridSpec — нестандартні компоновки
Іноді потрібна асиметрична сітка: один великий графік зліва + два маленькі справа.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
rng = np.random.default_rng(seed=42)
fig = plt.figure(figsize=(12, 8))
gs = gridspec.GridSpec(2, 2, figure=fig, width_ratios=[2, 1], height_ratios=[1, 1])
# Лівий графік — займає два рядки
ax_left = fig.add_subplot(gs[:, 0]) # Всі рядки (:), перший стовпець (0)
x = np.linspace(0, 10, 100)
ax_left.plot(x, np.sin(x), label="sin(x)", linewidth=2)
ax_left.plot(x, np.cos(x), label="cos(x)", linewidth=2)
ax_left.set_title("Основний графік (великий)", fontsize=13, fontweight="bold")
ax_left.legend()
ax_left.grid(True, alpha=0.3)
# Правий верхній
ax_top_right = fig.add_subplot(gs[0, 1])
ax_top_right.hist(rng.normal(0, 1, 300), bins=20, color="coral", edgecolor="black")
ax_top_right.set_title("Деталь 1", fontweight="bold")
# Правий нижній
ax_bottom_right = fig.add_subplot(gs[1, 1])
ax_bottom_right.scatter(rng.random(50), rng.random(50), s=80, alpha=0.7, c="purple")
ax_bottom_right.set_title("Деталь 2", fontweight="bold")
plt.tight_layout()
plt.show()

gs = gridspec.GridSpec(nrows, ncols,
figure=fig,
width_ratios=[2, 1], # Співвідношення ширини стовпців
height_ratios=[1, 2], # Співвідношення висоти рядків
wspace=0.3, # Горизонтальний проміжок
hspace=0.4) # Вертикальний проміжок
# Додавання осей:
ax = fig.add_subplot(gs[row, col]) # Одна клітинка
ax = fig.add_subplot(gs[row1:row2, col]) # Кілька рядків
ax = fig.add_subplot(gs[:, col1:col2]) # Кілька стовпців
gs[0, 1]— рядок 0, стовпець 1gs[:, 0]— всі рядки, стовпець 0gs[1:3, :]— рядки 1–2, всі стовпці
Повна сигнатура GridSpec та архітектура інженерного Jointplot
Клас matplotlib.gridspec.GridSpec є низькорівневим координатором геометрії. Він дозволяє будувати довільні асиметричні компоновки, коли кожен підграфік займає кілька комірок віртуальної матриці.
matplotlib.gridspec.GridSpec(
nrows,
ncols,
figure=None,
left=None,
bottom=None,
right=None,
top=None,
wspace=None,
hspace=None,
width_ratios=None,
height_ratios=None
)
| Параметр | Тип | За замовчуванням | Опис та інженерне призначення |
|---|---|---|---|
nrows, ncols | int | обов'язкові | Кількість рядків і стовпців віртуальної координатної сітки. |
width_ratios | Sequence[float] | None | Співвідношення ширини стовпців (наприклад, [4, 1] для великої зони аналізу та вузької бічної панелі). |
height_ratios | Sequence[float] | None | Співвідношення висоти рядків (наприклад, [1, 4] для компактної шапки та головного полотна). |
wspace | float | None (rcParams) | Горизонтальний проміжок між сусідніми стовпцями (для злитих графіків jointplot ставлять 0.05–0.08). |
hspace | float | None (rcParams) | Вертикальний проміжок між сусідніми рядками. |
left, right, bottom, top | float | None | Межі розміщення сітки відносно країв полотна у частках від 0.0 до 1.0. |
Професійний патерн: Створення класичного Jointplot через GridSpec
Поєднання двовимірного розсіювання (Scatter) з маргінальними розподілами (Histogram) зверху та праворуч
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
rng = np.random.default_rng(42)
x = rng.normal(50, 15, 300)
y = 0.6 * x + rng.normal(20, 8, 300)
fig = plt.figure(figsize=(10, 8))
# Сітка 2x2: верхній рядок низький (height_ratio=1), лівий стовпець широкий (width_ratio=4)
gs = gridspec.GridSpec(2, 2, figure=fig, width_ratios=[4, 1], height_ratios=[1, 4],
wspace=0.08, hspace=0.08)
# 1. Центральний головний scatter plot
ax_main = fig.add_subplot(gs[1, 0])
ax_main.scatter(x, y, color='#059669', alpha=0.5, edgecolor='#064e3b', s=30)
ax_main.set_xlabel("Вхідний трафік (Mb/s)")
ax_main.set_ylabel("Затримка виконання (ms)")
ax_main.grid(True, linestyle=':', alpha=0.6)
# 2. Верхня маргінальна гістограма (спільна вісь X з ax_main)
ax_top = fig.add_subplot(gs[0, 0], sharex=ax_main)
ax_top.hist(x, bins=30, color='#34d399', edgecolor='#065f46', alpha=0.7)
ax_top.set_title("Архітектура Jointplot: Main + Marginal Distributions", fontweight='bold')
ax_top.grid(axis='y', linestyle=':', alpha=0.4)
# Приховуємо підписи осі X, щоб вони не дублювали головний графік
plt.setp(ax_top.get_xticklabels(), visible=False)
# 3. Права маргінальна гістограма (спільна вісь Y з ax_main)
ax_right = fig.add_subplot(gs[1, 1], sharey=ax_main)
ax_right.hist(y, bins=30, orientation='horizontal', color='#34d399', edgecolor='#065f46', alpha=0.7)
ax_right.grid(axis='x', linestyle=':', alpha=0.4)
# Приховуємо підписи осі Y
plt.setp(ax_right.get_yticklabels(), visible=False)
plt.show()

Спільні осі (sharex, sharey)
Коли графіки порівнюються, корисно мати спільні осі для вирівнювання масштабу.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(3, 1, figsize=(10, 10), sharex=True, sharey=True)
# Три різні функції з однаковим діапазоном X
axes[0].plot(x, np.sin(x), color="blue")
axes[0].set_title("sin(x)")
axes[0].grid(True, alpha=0.3)
axes[1].plot(x, np.sin(2*x), color="red")
axes[1].set_title("sin(2x)")
axes[1].set_ylabel("Амплітуда (спільна вісь Y)", fontsize=12, fontweight="bold")
axes[1].grid(True, alpha=0.3)
axes[2].plot(x, np.sin(3*x), color="green")
axes[2].set_title("sin(3x)")
axes[2].set_xlabel("X (спільна вісь)", fontsize=12, fontweight="bold")
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

sharex та sharey:- Автоматична синхронізація масштабу — зум на одному графіку → зум на всіх
- Економія місця — мітки осі показуються лише на крайніх графіках
- Легше порівнювати — однаковий масштаб дозволяє бачити різницю у величині
sharex— часові серії (усі мають однаковий діапазон дат)sharey— графіки з однаковими одиницями виміру- Обидва — порівняння подібних метрик (продажі в різних регіонах)
Кольорові схеми та теми
Matplotlib підтримує готові теми (styles) для швидкої зміни вигляду графіків.
import matplotlib.pyplot as plt
import numpy as np
styles = ["default", "seaborn-v0_8-darkgrid", "ggplot", "bmh"]
x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle("Порівняння стилів matplotlib", fontsize=16, fontweight="bold")
for ax, style in zip(axes.flat, styles):
with plt.style.context(style): # Тимчасово застосовуємо стиль
ax.plot(x, np.sin(x), label="sin(x)", linewidth=2)
ax.plot(x, np.cos(x), label="cos(x)", linewidth=2)
ax.set_title(f"Стиль: {style}", fontweight="bold")
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()

# Застосувати стиль глобально
plt.style.use("seaborn-v0_8-darkgrid")
# Застосувати тимчасово (context manager)
with plt.style.context("ggplot"):
plt.plot([1, 2, 3])
plt.show()
| Стиль | Опис | Коли використовувати |
|---|---|---|
default | Класичний matplotlib | Технічна документація |
seaborn-v0_8-darkgrid | Світлий фон + сітка | Статистичні звіти |
ggplot | Стиль R ggplot2 | Наукові статті |
bmh | Bayesian Methods for Hackers | Презентації |
fivethirtyeight | Стиль сайту FiveThirtyEight | Журналістика даних |
dark_background | Темний фон | Презентації в темряві |
print(plt.style.available)Кастомізація кольорових палітр
import matplotlib.pyplot as plt
import numpy as np
# Створюємо дані: 5 категорій
categories = ["A", "B", "C", "D", "E"]
values = [23, 45, 56, 78, 32]
fig, axes = plt.subplots(2, 3, figsize=(16, 9))
fig.suptitle("Кольорові палітри для категорій", fontsize=15, fontweight="bold")
# 1. Дефолтні кольори matplotlib (C0, C1, ...)
axes[0, 0].bar(categories, values, color=[f"C{i}" for i in range(5)], edgecolor="black")
axes[0, 0].set_title("Default (C0–C9)")
# 2. Tab10 (10 розрізнюваних кольорів)
colors_tab10 = plt.cm.tab10(np.linspace(0, 1, 5))
axes[0, 1].bar(categories, values, color=colors_tab10, edgecolor="black")
axes[0, 1].set_title("Tab10 (рекомендовано)")
# 3. Viridis (послідовна палітра)
colors_viridis = plt.cm.viridis(np.linspace(0, 1, 5))
axes[0, 2].bar(categories, values, color=colors_viridis, edgecolor="black")
axes[0, 2].set_title("Viridis")
# 4. Pastel (м'які кольори)
colors_pastel = plt.cm.Pastel1(np.linspace(0, 1, 5))
axes[1, 0].bar(categories, values, color=colors_pastel, edgecolor="black")
axes[1, 0].set_title("Pastel1")
# 5. Set3 (категоріальні)
colors_set3 = plt.cm.Set3(np.linspace(0, 1, 5))
axes[1, 1].bar(categories, values, color=colors_set3, edgecolor="black")
axes[1, 1].set_title("Set3")
# 6. Кастомна палітра (власні HEX кольори)
custom_colors = ["#FF6B6B", "#4ECDC4", "#95E1D3", "#FFD93D", "#C7CEEA"]
axes[1, 2].bar(categories, values, color=custom_colors, edgecolor="black")
axes[1, 2].set_title("Кастомна (HEX)")
plt.tight_layout()
plt.show()

viridis,plasma,cividis— послідовніtab10,Paired— категоріальні- Використовуйте різні стилі ліній (
linestyle) як додатковий канал
Системні налаштування plt.style та plt.rcParams для корпоративних звітів
У великих проєктах замість ручного задавання параметрів кожного графіка використовують глобальні файли конфігурації стилів або контекстні менеджери:
- Глобальне перемикання теми:
plt.style.use("seaborn-v0_8-whitegrid")— застосовує набір кольорів, сіток та шрифтів до всіх наступних графіків у сесії. - Локальне ізольоване застосування: конструкція
with plt.style.context("dark_background"):змінює стиль виключно всередині блокуwith, не впливаючи на інші візуалізації. - Глибока кастомізація через
plt.rcParams: глобальний словник параметрів Matplotlib, що керує абсолютно всіма властивостями рендерингу (розміри шрифтів, товщини ліній, роздільна здатністьfigure.dpiтощо).
# Приклад корпоративного налаштування rcParams
plt.rcParams.update({
"font.family": "sans-serif",
"font.size": 11,
"figure.titlesize": 14,
"axes.titlesize": 12,
"axes.labelsize": 11,
"axes.edgecolor": "#cbd5e1",
"axes.grid": True,
"grid.linestyle": ":",
"grid.alpha": 0.6,
"figure.dpi": 150
})
| Параметр rcParams | Тип | Приклад значення | Інженерне призначення |
|---|---|---|---|
font.family | str | 'sans-serif', 'Inter' | Сімейство шрифтів для всіх текстових написів. |
figure.dpi | float | 150 (екран), 300 (друк) | Роздільна здатність полотна у точках на дюйм (DPI). |
axes.edgecolor | color | '#cbd5e1', '#334155' | Колір рамок координатних осей (spines). |
axes.grid | bool | True | Автоматичне увімкнення сітки для всіх осей без виклику ax.grid(). |
savefig.bbox | str | 'tight' | Автоматичне обрізання зайвих полів при збереженні через fig.savefig(). |
Порівняльне дослідження стилів: Seaborn, ggplot, Dark та BMH
Вплив теми на читабельність графіків у світлих і темних інтерфейсах
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
fig, axes = plt.subplots(2, 2, figsize=(13, 9.5))
styles = [
("seaborn-v0_8-whitegrid", "Стиль Seaborn: світлий фон, чітка сітка"),
("ggplot", "Стиль ggplot2: класичний сірий фон та біла сітка"),
("dark_background", "Стиль Dark: темна тема для нічного чергування"),
("bmh", "Стиль BMH: високий контраст для аналітичних звітів")
]
for idx, (st_name, st_desc) in enumerate(styles):
ax = axes[idx // 2, idx % 2]
with plt.style.context(st_name):
ax.plot(x, y1, label="Signal A (sin)", linewidth=2)
ax.plot(x, y2, label="Signal B (cos)", linewidth=2, linestyle='--')
ax.set_title(f"Стиль: '{st_name}'\n{st_desc}", fontweight='bold')
ax.set_xlabel("Час (с)")
ax.set_ylabel("Амплітуда")
ax.legend()
plt.tight_layout()
plt.show()

Анотації та текстові підписи
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x) * np.exp(-x/10)
fig, ax = plt.subplots(figsize=(12, 7))
ax.plot(x, y, linewidth=2.5, color="darkblue")
# Знаходимо максимум
max_idx = np.argmax(y)
max_x, max_y = x[max_idx], y[max_idx]
# Анотація зі стрілкою
ax.annotate("Локальний максимум",
xy=(max_x, max_y), # Точка, на яку вказуємо
xytext=(max_x + 2, max_y + 0.3), # Розташування тексту
fontsize=12, fontweight="bold", color="darkred",
arrowprops=dict(arrowstyle="->", color="red", lw=2),
bbox=dict(boxstyle="round,pad=0.5", facecolor="yellow", alpha=0.8))
# Точка на максимумі
ax.plot(max_x, max_y, "ro", markersize=12, label=f"Max: ({max_x:.2f}, {max_y:.2f})")
# Горизонтальна лінія через максимум
ax.axhline(max_y, color="gray", linestyle="--", alpha=0.5)
# Вертикальна лінія
ax.axvline(max_x, color="gray", linestyle="--", alpha=0.5)
# Текст без стрілки
ax.text(7, -0.2, "Загасаюча амплітуда", fontsize=11,
style="italic", bbox=dict(boxstyle="round", facecolor="lightblue", alpha=0.6))
ax.set_title("Загасаюча синусоїда з анотаціями", fontsize=14, fontweight="bold")
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.legend(loc="upper right")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# 1. Простий текст
ax.text(x, y, "Текст", fontsize=12, color="red", ha="center", va="top")
# 2. Анотація зі стрілкою
ax.annotate("Пояснення", xy=(x_point, y_point), xytext=(x_text, y_text),
arrowprops=dict(arrowstyle="->", color="blue"))
# 3. Горизонтальна лінія з підписом
ax.axhline(y=value, color="gray", linestyle="--", label="Поріг")
# 4. Вертикальна лінія
ax.axvline(x=value, color="gray", linestyle=":")
# 5. Вертикальна зона (діапазон)
ax.axvspan(x1, x2, alpha=0.2, color="yellow") # Підсвічує область між x1 та x2
# 6. Горизонтальна зона
ax.axhspan(y1, y2, alpha=0.2, color="lightblue")

Повна сигнатура ax.annotate() та інженерне зонування (axvspan / axhspan)
Метод ax.annotate() надає найбільш прецизійний контроль над інженерними підписами, дозволяючи зв'язувати пояснювальний текст із конкретною точкою даних за допомогою векторних стрілок із кастомною кривизною.
ax.annotate(
text,
xy,
xytext=None,
xycoords='data',
textcoords=None,
arrowprops=None,
annotation_clip=None,
**kwargs
) -> Annotation
| Параметр | Тип | За замовчуванням | Опис та інженерне призначення |
|---|---|---|---|
text | str | обов'язковий | Текст пояснення або числового значення. |
xy | tuple[float, float] | обов'язковий | Цільова точка, на яку вказує стрілка (наприклад, координати локального максимуму). |
xytext | tuple[float, float] | None (xy) | Координати розташування тексту. Якщо задано, від xytext до xy малюється стрілка. |
xycoords | str | Transform | 'data' | Система координат для точки xy: 'data' (шкала графіка), 'axes fraction' (від 0 до 1 відносно осі). |
textcoords | str | Transform | xycoords | Система координат для тексту: 'offset points' (зсув у пунктах від цілі) або 'data'. |
arrowprops | dict | None | None | Словник параметрів стрілки: arrowstyle ('->', 'fancy'), connectionstyle ('arc3,rad=0.3'), color. |
bbox | dict | None | None | Словник рамки навколо тексту: boxstyle ('round', 'sawtooth'), facecolor, alpha. |
ax.axvspan(t_start, t_end)— вертикальна зона: маркує часовий інтервал аварії, релізу або нічного резервного копіювання.ax.axhspan(val_min, val_max)— горизонтальна зона: наочно показує допустимі діапазони метрики (зелена зона стабільності, жовта зона ризику, червона зона відмови).ax.axvline(x)/ax.axhline(y)— точкові граничні лінії регламенту.
Дослідження анотацій: стилі стрілок, кривизна, хронологія інциденту та зони SLA
Порівняння arrowstyle, connectionstyle, виділення вікна атаки через axvspan та коридорів через axhspan
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(42)
time_hours = np.linspace(0, 24, 120)
latency = 40 + 5 * np.sin(time_hours / 3) + rng.normal(0, 3, 120)
# Моделюємо стрибок затримки під час інциденту з 14:00 по 18:00
mask_incident = (time_hours >= 14) & (time_hours <= 18)
latency[mask_incident] += 120 + 40 * np.sin((time_hours[mask_incident] - 14) * 0.8)
fig, axes = plt.subplots(1, 2, figsize=(14, 5.5))
# 1. Хронологія інциденту з вертикальним зонуванням
axes[0].plot(time_hours, latency, color='#0f172a', linewidth=1.8, label="Latency P99")
axes[0].axvspan(14, 18, color='#fca5a5', alpha=0.35, label="Вікно інциденту (DDoS)")
axes[0].axvline(14, color='#dc2626', linestyle='--', linewidth=1.5)
axes[0].axvline(18, color='#16a34a', linestyle='--', linewidth=1.5)
# Додаємо анотацію зі стрілкою та рамкою
axes[0].annotate("Пікове навантаження: 215 ms\nПочаток міграції трафіку",
xy=(16, 215), xytext=(8, 230),
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=-0.2", color='#dc2626', lw=2),
fontweight='bold', fontsize=9, color='#7f1d1d',
bbox=dict(boxstyle="round,pad=0.4", facecolor='#fef2f2', edgecolor='#dc2626'))
axes[0].set_title("Хронологія збою: ax.axvspan() та ax.annotate()", fontweight='bold')
axes[0].set_xlabel("Час доби (години)")
axes[0].set_ylabel("Затримка (ms)")
axes[0].set_ylim(20, 260)
axes[0].legend(loc='upper left')
axes[0].grid(True, linestyle=':', alpha=0.5)
# 2. Коридори статусу системи через axhspan
axes[1].plot(time_hours, latency, color='#0f172a', linewidth=1.8)
axes[1].axhspan(0, 80, color='#bbf7d0', alpha=0.3, label="Нормальний стан SLA (< 80 ms)")
axes[1].axhspan(80, 150, color='#fef08a', alpha=0.3, label="Попередження Warning (80–150 ms)")
axes[1].axhspan(150, 260, color='#fecaca', alpha=0.3, label="Критична деградація Critical (> 150 ms)")
axes[1].axhline(80, color='#16a34a', linestyle=':', linewidth=1.5)
axes[1].axhline(150, color='#dc2626', linestyle=':', linewidth=1.5)
axes[1].set_title("Статусні коридори працездатності: ax.axhspan()", fontweight='bold')
axes[1].set_xlabel("Час доби (години)")
axes[1].set_ylabel("Затримка (ms)")
axes[1].set_ylim(20, 260)
axes[1].legend(loc='upper left')
axes[1].grid(True, linestyle=':', alpha=0.5)
plt.tight_layout()
plt.show()

Практичні завдання
📝 Рівень 1: Базові навички
Завдання 1.1: Створіть сітку 2×3 з 6 різними типами графіків (line, scatter, bar, hist, box, pie). Додайте загальний заголовок.
Завдання 1.2: Побудуйте 3 графіки один під одним зі спільною віссю X (часові дані за рік).
Завдання 1.3: Використайте GridSpec: один великий графік зверху + два маленькі знизу (у співвідношенні 2:1 по висоті).
🧮 Рівень 2: Аналіз даних
Завдання 2.1: Завантажте датасет Iris. Створіть дашборд 2×2:
- Scatter: sepal_length vs sepal_width (колір = species)
- Hist: розподіл petal_length
- Box plot: порівняння sepal_width між видами
- Bar chart: середня petal_length для кожного виду
Завдання 2.2: Застосуйте 4 різні стилі (default, seaborn, ggplot, dark_background) до одного графіка. Порівняйте результати.
Завдання 2.3: Побудуйте scatter plot з анотаціями: позначте топ-3 викиди стрілками та підписами.
🚀 Рівень 3: Дослідницький проєкт
Проєкт: Створення повноцінного дашборду
Завантажте датасет Titanic. Створіть композицію 3×3 (або використайте GridSpec для складнішої компоновки):
- Загальний заголовок фігури: «Аналіз пасажирів Титаніка»
- Графік 1 (великий, займає 2×2): Scatter matrix для змінних
age,fare,pclass - Графік 2: Bar chart — виживання за класами
- Графік 3: Pie chart — співвідношення чоловіків/жінок
- Графік 4: Hist — розподіл віку
- Графік 5: Box plot — ціна квитка за класами
Вимоги:
- Єдина кольорова схема для всіх графіків
- Всі графіки мають заголовки, підписи осей
- Використайте
tight_layout()абоconstrained_layout=True - Додайте хоча б 2 анотації (наприклад, позначте середній вік)
- Збережіть у форматі PNG (300 dpi) та PDF
Резюме підтеми
🎨 Subplots
plt.subplots(nrows, ncols)→ регулярна сітка- GridSpec → асиметричні композиції
sharex,sharey→ спільні осі для порівнянняtight_layout()→ автоматичне вирівнювання- Оптимально: 3–6 графіків на одній фігурі
🎨 Стилізація
plt.style.use("seaborn")→ готові теми- Кольорові палітри:
tab10,viridis,Pastel1 - Colorblind-safe:
viridis,plasma,tab10 - Анотації:
ax.annotate(),ax.text() - Zones:
ax.axvspan(),ax.axhspan()
🎯 Дашборди
- Один заголовок для всієї фігури:
fig.suptitle() - Єдина кольорова схема для узгодженості
- Логічна компоновка: головне → великим, деталі → малим
- Збереження: PNG (веб, звіти), PDF (друк, публікації)
- Роздільна здатність: 150 dpi (екран), 300 dpi (друк)
- Загальний заголовок фігури (
fig.suptitle()) - Кожен графік має власний заголовок
- Підписи осей з одиницями виміру
- Єдина кольорова схема (максимум 3–4 кольори)
- Сітка (
grid(True, alpha=0.3)) де доречно -
tight_layout()абоconstrained_layout=True - Легенди з чіткими підписами
- Розмір шрифтів достатній (≥10pt для осей, ≥12pt для заголовків)
- Анотації для ключових точок
- Збережено у правильному форматі та роздільній здатності
Попередня підтема: Візуалізація категоріальних данихПовернутися до огляду модуля: Візуалізація даних за допомогою matplotlib
Візуалізація категоріальних даних — bar charts та pie charts
Стовпчасті діаграми для порівняння категорій, горизонтальні та вертикальні bar charts, групові та stacked bars, pie charts та коли їх уникати
Регресійна задача та лінійна регресія з однією змінною
Що таке регресія, чим вона відрізняється від класифікації, рівняння прямої та перша модель передбачення ціни квартири