Визуализация данных в Python (Matplotlib)
Визуализация помогает быстро понять данные и результаты модели: увидеть тренд, шум, выбросы, форму распределения, ошибки метода. В этом уроке мы подробно разберем Matplotlib: из каких объектов состоит график, какие элементы у него есть (оси, деления, подписи, легенда, сетка), как управлять стилем (линии, маркеры, цвета, прозрачность), как строить несколько графиков на одном изображении и как сохранять графики в файлы для публикации.
Примечание: Meta Platforms Inc. признана экстремистскои, ее продукты запрещены на территории РФ.
Главная мысль: в Matplotlib нужно один раз четко понять "анатомию графика": Figure (вся картинка) содержит один или несколько Axes (подграфиков), а внутри каждого Axes находятся оси Axis, деления (ticks), подписи, сетка, легенда и сами данные (линии, точки, столбцы и т.д.). После этого почти любая настройка становится логичнои.
Содержание
- 1. Цели урока
- 2. Введение: Matplotlib, версия, зачем нужна визуализация
- 3. Анатомия графика: Figure, Axes, Axis, Artists и элементы оформления
- 4. Базовый график: plot, title, labels, limits, ticks, grid, legend, savefig
- 5. Стиль отображения данных: линии, маркеры, цвет, alpha, fill_between
- 6. Расширение: subplots, гистограммы, общии заголовок, сохранение серии графиков
- 7. Практика: задачи (с решениями)
- 8. Чек-лист самопроверки
1. Цели урока
- Понимать, из каких объектов состоит график Matplotlib (Figure, Axes, Axis).
- Понимать, что такое Artists и почему "почти все на рисунке" -- это объект.
- Строить линеиныи график по массивам x и y и добавлять базовое оформление.
- Управлять диапазоном осеи и делениями (ticks) осеи.
- Настраивать стиль линии и точек: linestyle, marker, color, alpha.
- Делать заливку области между кривыми (fill_between).
- Строить несколько подграфиков (subplots) и работать с axes как с массивом.
- Сохранять графики в файлы png и svg и проверять, что файл создан.
2. Введение: Matplotlib, версия, зачем нужна визуализация
Новые термины и определения
визуализация данных-- представление данных в виде графиков и диаграмм для анализа и объяснения результатов.линеиныи график-- график, где точки соединены линиеи (обычно используется для функции, временного ряда и т.д.).диаграмма-- общее название графических представлении (гистограмма, scatter, boxplot и т.д.).дискретные точки-- набор значении \((x_i, y_i)\), по которым строится изображение.рендеринг-- процесс "рисования" изображения в память или в файл.растровая графика(например PNG) -- изображение из пикселей.векторная графика(например SVG, PDF) -- изображение как набор линий/кривых/текста, которое хорошо масштабируется.DPI-- плотность пикселей для растра: сколько пикселей на 1 дюйм.
Версия Matplotlib 3.8.0
Материал ориентирован на Matplotlib 3.8.0 (дата релиза 13 сентября 2023). Если у вас другая версия, примеры в целом должны работать, но иногда может отличаться вывод предупреждении или оформление по умолчанию. Поэтому полезно проверять версию в коде.
import matplotlib
print(matplotlib.__version__)
# 3.8.0
Частая путаница
x = [0, 1, 2]
y = [0, 1, 4]
print(len(x), len(y))
# 3 3
3. Анатомия графика: Figure, Axes, Axis, Artists и элементы оформления
Figure и Axes: что где живет
Figure-- вся картинка целиком (как "лист"). В Figure может быть один или несколько подграфиков.Axes-- один подграфик (subplot) внутри Figure. На Axes вы рисуете данные и настраиваете подписи, сетку, легенду.subplot-- синоним "один Axes в сетке" (например 3 ряда и 3 столбца).
Самыи простои способ создать Figure и один Axes -- это plt.subplots().
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
print(type(fig).__name__)
# Figure
print(type(ax).__name__)
# Axes
pyplot (plt) -- модуль Matplotlib с набором удобных функций "как в MATLAB". Он умеет создавать "текущую" фигуру и "текущие" оси, и применять команды к ним.Axis, ticks, tick labels, spines, grid, legend
Axis-- ось внутри Axes (x или y). Axis отвечает за деления, подписи делении, масштаб (линеиныи, логарифмическии).tick-- деление на оси (сама отметка).tick label-- подпись деления (текст рядом с делением).major ticks-- главные деления (обычно их меньше).minor ticks-- побочные деления (обычно их больше, часто без подписеи).spines-- границы области графика (рамка вокруг осеи).grid-- сетка на графике.legend-- легенда (подписи наборов данных, обычно берутся из label).title-- заголовок графика.xlabel,ylabel-- подписи осеи x и y.
Покажем, что заголовок и подписи осеи -- это текстовые объекты, которые создаются методами Axes.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
t = ax.set_title("Title")
xl = ax.set_xlabel("X")
yl = ax.set_ylabel("Y")
print(type(t).__name__, type(xl).__name__, type(yl).__name__)
# Text Text Text
plt (stateful) и ax (OO): два стиля работы
stateful интерфейс-- стиль "черезplt": Matplotlib работает с "текущими" фигурой и осями, а команды применяются к ним (напримерplt.title).OO (object-oriented) интерфейс-- стиль "через объекты": вы храните ссылкиfigиaxи работаете с методамиax(напримерax.set_title).
Практическии вывод: для серьезных проектов и для subplots обычно удобнее OO-стиль. Он понятнее: видно, к какому именно графику относится команда.
import numpy as np
import matplotlib.pyplot as plt
x = np.array([0, 1, 2])
y = np.array([0, 1, 4])
# OO стиль
fig, ax = plt.subplots()
ax.plot(x, y, label="OO line")
ax.set_title("OO title")
ax.legend()
# Stateful стиль (работает с текущими осями)
plt.plot(x, y, label="plt line")
plt.title("plt title")
plt.legend()
print("done")
# done
Типичные ошибки
Ошибка 1: перепутать Axes и Axis
Axes это область графика, Axis это ось (x или y) внутри Axes. Если вы говорите "у меня 3 axes", это обычно означает 3 подграфика. Если говорите "x-axis", это ось внутри одного подграфика.
Ошибка 2: думать, что "отрисовка" всегда показывает окно
В скриптах и на серверах часто нет окна. Тогда правильно: сохранять в файл через savefig и проверять результат.
4. Базовый график: plot, title, labels, limits, ticks, grid, legend, savefig
Пример: y=x^2 как набор дискретных точек
Будем строить параболу:
$$ y = x^2 $$
linspace(a, b, n) (из NumPy) создает n равномерно распределенных точек на отрезке \([a,b]\). Это удобный способ получить x-координаты для графика.import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 10)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y, label="y=x^2")
ax.set_title("Grafik y=x^2 (10 tochek)")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(True)
ax.legend()
fname = "parabola_10.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(np.round(x, 3)) # Печатает в консоль массив x, где каждое число округлено до 3 знаков после запятой.
# [-3. -2.333 -1.667 -1. -0.333 0.333 1. 1.667 2.333 3. ]
print(np.round(y, 3))
# [9. 5.444 2.778 1. 0.111 0.111 1. 2.778 5.444 9. ]
print(os.path.exists(fname))
# True

import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 200)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y, label="y=x^2")
ax.set_title("Grafik y=x^2 (200 tochek)")
ax.grid(True)
ax.legend()
fname = "parabola_200.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(len(x))
# 200
print(os.path.exists(fname))
# True
Пределы и деления осеи: xlim/ylim, xticks/yticks
xlim,ylim-- явные границы области отображения.xticks,yticks-- явные позиции делении.rotation-- поворот подписеи делении (удобно, если подписеи много).
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 10)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y, label="y=x^2")
ax.set_xlim(-5, 5)
ax.set_ylim(-1, 12)
ax.set_xticks(np.arange(-5, 6, 1))
ax.set_yticks(np.arange(0, 13, 2))
ax.grid(True)
ax.legend()
print(ax.get_xlim())
# (-5.0, 5.0)
print(ax.get_ylim())
# (-1.0, 12.0)
print(ax.get_xticks()[:5])
# [-5. -4. -3. -2. -1.]

Сохранение: png vs svg, dpi, bbox_inches
savefig-- сохранение Figure в файл.format-- формат файла (png, svg, pdf). Обычно определяется по расширению.bbox_inches="tight"-- опция, которая пытается обрезать лишние поля вокруг рисунка.
import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 200)
y = x**2
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x, y, label="y=x^2")
ax.set_title("Save as PNG and SVG")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(True)
ax.legend()
png_name = "plot.png"
svg_name = "plot.svg"
fig.savefig(png_name, dpi=150, bbox_inches="tight")
fig.savefig(svg_name, bbox_inches="tight")
plt.close(fig)
print(os.path.exists(png_name), os.path.exists(svg_name))
# True True
Типичные ошибки
Ошибка 1: поставить label, но забыть legend
label сам по себе легенду не рисует. Нужен вызов legend().
import numpy as np
import matplotlib.pyplot as plt
x = np.array([0, 1, 2])
y = np.array([0, 1, 4])
fig, ax = plt.subplots()
ax.plot(x, y, label="line")
print("label set, but legend not called")
# label set, but legend not called
Ошибка 2: несовпадение количества делении и подписеи
Если вы задаете подписи делении вручную, число подписеи должно совпадать с числом делении.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
try:
ax.set_xticks([0, 1, 2])
ax.set_xticklabels(["a", "b"])
print("ok")
except Exception as e:
print("ERROR:", type(e).__name__)
# ERROR: ValueError
5. Стиль отображения данных: линии, маркеры, цвет, alpha, fill_between
linestyle и marker: как читать и как применять
linestyle задает вид линии. Самые часто используемые:
"-"-- сплошная":"-- пунктирная"--"-- штриховая"-."-- штрихпунктирная
marker задает символ точки. Примеры: ".", "o", "v", "x".import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 10)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y, linestyle="-", label="solid")
ax.plot(x, y + 1, linestyle=":", label="dotted")
ax.plot(x, y + 2, linestyle="--", label="dashed")
ax.plot(x, y + 3, linestyle="-.", label="dashdot")
ax.set_title("Line styles")
ax.legend()
fname = "linestyles.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(os.path.exists(fname))
# True

import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 10)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y, marker=".", linestyle="", label="dot")
ax.plot(x, y + 1, marker="o", linestyle="", label="circle")
ax.plot(x, y + 2, marker="v", linestyle="", label="triangle")
ax.plot(x, y + 3, marker="x", linestyle="", label="x")
ax.set_title("Markers without lines")
ax.legend()
fname = "markers.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(os.path.exists(fname))
# True
color, linewidth, markersize, alpha
color-- цвет линии/точек/заливки (например "black", "red", "gray").linewidth-- толщина линии.markersize-- размер маркера.alpha-- прозрачность (0..1).
import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 30)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y, color="black", linewidth=2.0, label="black line")
ax.plot(x, y, color="red", alpha=0.3, label="red transparent")
ax.plot(x, y, marker="o", markersize=3, linestyle="", label="markers")
ax.set_title("Color, linewidth, alpha")
ax.legend()
fname = "style_params.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(os.path.exists(fname))
# True
Заливка между кривыми: fill_between
fill_between(x, y1, y2) заливает область между кривыми \(y_1(x)\) и \(y_2(x)\).import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 100)
y1 = x**2
y2 = x**2 + 3
fig, ax = plt.subplots()
ax.plot(x, y1, color="black", label="y1=x^2")
ax.plot(x, y2, color="gray", linestyle="--", label="y2=x^2+3")
ax.fill_between(x, y1, y2, color="gray", alpha=0.3, label="filled")
ax.set_title("fill_between")
ax.legend()
fname = "fill_between.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(os.path.exists(fname))
# True
Типичные ошибки
Ошибка 1: думать, что alpha меняет цвет
alpha меняет прозрачность, а не цвет. Цвет задается параметром color.
print("alpha is transparency, color is color")
# alpha is transparency, color is color
Ошибка 2: fill_between при разной длине массивов
x, y1, y2 должны быть одинаковой длины. Иначе будет ошибка.
import numpy as np
import matplotlib.pyplot as plt
x = np.array([0, 1, 2])
y1 = np.array([0, 1, 4])
y2 = np.array([0, 1]) # короче
fig, ax = plt.subplots()
try:
ax.fill_between(x, y1, y2)
print("ok")
except Exception as e:
print("ERROR:", type(e).__name__)
# ERROR: ValueError
6. Расширение: subplots, гистограммы, общии заголовок, сохранение серии графиков
Гистограмма и bins: что показывают столбцы
гистограмма-- столбчатая диаграмма, показывающая, сколько значении попало в интервалы.бин (bin)-- интервал значении, в которыи мы "собираем" данные.bins-- число интервалов (столбцов).нормальное распределение-- распределение вида "колокол". Обычно обозначают \(N(\mu,\sigma^2)\).mu-- среднее \(\mu\).sigma-- стандартное отклонение \(\sigma\).
Формула плотности нормального распределения (для общего понимания):
$$ f(x) = \frac{1}{\sigma\sqrt{2\pi}}\exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right) $$
rng (генератор) из numpy.random.default_rng дает воспроизводимые псевдослучайные числа, если вы задали seed.import os
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(0)
data = rng.normal(loc=0.0, scale=1.0, size=100)
fig, ax = plt.subplots()
# hist возвращает: counts, bin_edges, patches
counts, bin_edges, patches = ax.hist(data, bins=10)
ax.set_title("Histogram: normal(0,1), n=100")
ax.set_xlim(-4, 4)
fname = "hist.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(np.round(data[:5], 6))
# [ 0.12573 -0.132105 0.640423 0.1049 -0.535669]
print(counts.astype(int))
# [ 5 8 16 16 18 15 9 7 4 2]
print(len(bin_edges))
# 11
print(os.path.exists(fname))
# True
plt.subplots: сетка 3x3 и массив axes
plt.subplots(r, c)-- создает Figure и сетку Axes размером r строк на c столбцов.axes-- массив Axes. Для (3,3) обращаться нужно какaxes[i, j].suptitle-- общий заголовок для всеи Figure (над всеми подграфиками).
import os
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(1)
def data_gen(center, sigma, size=200):
return rng.normal(loc=center, scale=sigma, size=size)
centers = [-2, 0, 2]
sigmas = [1, 2, 3]
fig, axes = plt.subplots(3, 3, figsize=(10, 10))
fig.suptitle("Histograms: normal(mu, sigma), size=200")
for i, center in enumerate(centers):
for j, sigma in enumerate(sigmas):
ax = axes[i, j]
data = data_gen(center, sigma)
ax.hist(data, bins=20)
ax.set_xlim(-10, 10)
ax.set_title(f"mu={center}, sigma={sigma}")
fname = "grid_hist.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(type(axes).__name__)
# ndarray
print(axes.shape)
# (3, 3)
print(os.path.exists(fname))
# True
layout: tight_layout и constrained_layout
layout-- расположение элементов (оси, подписи, заголовки) так, чтобы они не перекрывали друг друга.tight_layout-- попытка автоматически подогнать отступы между графиками.constrained_layout-- более современный механизм автоматическои раскладки (в некоторых случаях работает лучше).
Если подписи осеи или заголовки "обрезаются" при сохранении, два самых простых решения:
- использовать
fig.savefig(..., bbox_inches="tight") - или включить
constrained_layout=Trueпри создании subplots
import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 10, 50)
y = np.sin(x)
fig, ax = plt.subplots(constrained_layout=True, figsize=(6, 4))
ax.plot(x, y, label="sin(x)")
ax.set_title("Long title: this is a long long title")
ax.set_xlabel("Very long X label")
ax.set_ylabel("Very long Y label")
ax.legend()
fname = "layout.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(os.path.exists(fname))
# True
Типичные ошибки
Ошибка 1: axes при subplots(1,1) и subplots(3,3) имеют разныи тип
Если subplots(1,1), axes это один Axes. Если subplots(3,3), axes это массив Axes. Новички часто пишут axes.plot(...) и получают ошибку, потому что axes -- массив.
import matplotlib.pyplot as plt
fig1, ax1 = plt.subplots(1, 1)
fig2, axes2 = plt.subplots(2, 2)
print(type(ax1).__name__)
# Axes
print(type(axes2).__name__)
# ndarray
Ошибка 2: забыть закрывать фигуры при генерации многих файлов
Если вы создаете десятки или сотни графиков, закрывайте фигуру: plt.close(fig). Иначе может расти потребление памяти.
7. Практика: задачи (с решениями)
Блок 1: базовая линия и сохранение
Задача 1: построить y=x^2 и сохранить в PNG
Постройте график \(y=x^2\) на [-3,3] с 200 точками. Добавьте title, xlabel, ylabel, grid, legend. Сохраните в parabola.png и выведите True/False, что файл создан.
import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 200)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y, label="y=x^2")
ax.set_title("Parabola")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(True)
ax.legend()
fname = "task_parabola.png"
fig.savefig(fname, dpi=150, bbox_inches="tight")
plt.close(fig)
print(os.path.exists(fname))
# True
Задача 2: настроить пределы и деления
Сделайте xlim(-5,5), ylim(-1,12). Установите xticks от -5 до 5 с шагом 1 и выведите первые 5 делении.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_xlim(-5, 5)
ax.set_ylim(-1, 12)
ax.set_xticks(np.arange(-5, 6, 1))
print(ax.get_xticks()[:5])
# [-5. -4. -3. -2. -1.]
Блок 2: стиль, гистограмма, subplots
Задача 3: 4 линии с разными linestyle
Нарисуйте 4 линии с linestyle "-", ":", "--", "-.". Сохраните рисунок и выведите True.
import os
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 10)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y, linestyle="-", label="solid")
ax.plot(x, y + 1, linestyle=":", label="dotted")
ax.plot(x, y + 2, linestyle="--", label="dashed")
ax.plot(x, y + 3, linestyle="-.", label="dashdot")
ax.legend()
fname = "task_linestyle.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(os.path.exists(fname))
# True
Задача 4: гистограмма и counts
Сгенерируйте 100 чисел из normal(0,1). Постройте hist(bins=10), распечатайте counts и длину массива границ bin_edges.
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(0)
data = rng.normal(0.0, 1.0, size=100)
fig, ax = plt.subplots()
counts, bin_edges, patches = ax.hist(data, bins=10)
print(counts.astype(int))
# [ 5 8 16 16 18 15 9 7 4 2]
print(len(bin_edges))
# 11
Задача 5: subplots 2x2 и axes.shape
Создайте сетку 2x2, выведите axes.shape, сохраните файл.
import os
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(6, 6))
fig.suptitle("2x2")
fname = "task_grid.png"
fig.savefig(fname, dpi=150)
plt.close(fig)
print(axes.shape)
# (2, 2)
print(os.path.exists(fname))
# True
8. Чек-лист самопроверки
Отметьте пункты, которые вы реально понимаете и можете повторить без подсказок.
| +/- | Навык | Проверка |
|---|---|---|
| Понимаю Figure и Axes | Могу объяснить: Figure это вся картинка, Axes это один подграфик | |
| Не путаю Axes и Axis | Могу объяснить: Axis это ось x или y с делениями внутри Axes | |
| Понимаю ticks | Могу объяснить: tick это деление, tick label это подпись деления | |
| Строю plot | Могу построить график по массивам x и y | |
| Добавляю оформление | Могу сделать title, xlabel, ylabel, grid, legend | |
| Управляю диапазоном | Могу применить xlim/ylim и объяснить, что данные не "достраиваются" | |
| Управляю делениями | Могу задать xticks/yticks и понимаю риск несовпадения labels | |
| Настраиваю стиль | Могу использовать linestyle, marker, color, alpha | |
| Делаю заливку | Могу использовать fill_between(x, y1, y2) | |
| Понимаю гистограмму | Могу объяснить, что bins это число интервалов и что значит counts | |
| Работаю с subplots | Могу создать сетку и обратиться к axes[i, j] | |
| Сохраняю графики | Могу использовать savefig и проверить os.path.exists |



