Перейти к содержимому
4.1

Визуализация данных в Python (Matplotlib)

Визуализация данных в Python (Matplotlib)

Визуализация помогает быстро понять данные и результаты модели: увидеть тренд, шум, выбросы, форму распределения, ошибки метода. В этом уроке мы подробно разберем Matplotlib: из каких объектов состоит график, какие элементы у него есть (оси, деления, подписи, легенда, сетка), как управлять стилем (линии, маркеры, цвета, прозрачность), как строить несколько графиков на одном изображении и как сохранять графики в файлы для публикации.

Примечание: Meta Platforms Inc. признана экстремистскои, ее продукты запрещены на территории РФ.

Главная мысль: в Matplotlib нужно один раз четко понять "анатомию графика": Figure (вся картинка) содержит один или несколько Axes (подграфиков), а внутри каждого Axes находятся оси Axis, деления (ticks), подписи, сетка, легенда и сами данные (линии, точки, столбцы и т.д.). После этого почти любая настройка становится логичнои.

Содержание

1. Цели урока

  • Понимать, из каких объектов состоит график Matplotlib (Figure, Axes, Axis).
  • Понимать, что такое Artists и почему "почти все на рисунке" -- это объект.
  • Строить линеиныи график по массивам x и y и добавлять базовое оформление.
  • Управлять диапазоном осеи и делениями (ticks) осеи.
  • Настраивать стиль линии и точек: linestyle, marker, color, alpha.
  • Делать заливку области между кривыми (fill_between).
  • Строить несколько подграфиков (subplots) и работать с axes как с массивом.
  • Сохранять графики в файлы png и svg и проверять, что файл создан.
Что особенно важно запомнить: Matplotlib рисует по дискретным точкам. Если данных мало (например 10 точек), линия будет "ломанои". Если данных больше (например 200 точек), линия выглядит гладко.
^ К оглавлению

2. Введение: Matplotlib, версия, зачем нужна визуализация

Новые термины и определения

  • визуализация данных -- представление данных в виде графиков и диаграмм для анализа и объяснения результатов.
  • линеиныи график -- график, где точки соединены линиеи (обычно используется для функции, временного ряда и т.д.).
  • диаграмма -- общее название графических представлении (гистограмма, scatter, boxplot и т.д.).
  • дискретные точки -- набор значении \((x_i, y_i)\), по которым строится изображение.
  • рендеринг -- процесс "рисования" изображения в память или в файл.
  • растровая графика (например PNG) -- изображение из пикселей.
  • векторная графика (например SVG, PDF) -- изображение как набор линий/кривых/текста, которое хорошо масштабируется.
  • DPI -- плотность пикселей для растра: сколько пикселей на 1 дюйм.
Практическии вывод: для публикации на сайте часто удобно: PNG для "простых" вставок и SVG для схем/линии, где важно качество при масштабировании.

Версия Matplotlib 3.8.0

Материал ориентирован на Matplotlib 3.8.0 (дата релиза 13 сентября 2023). Если у вас другая версия, примеры в целом должны работать, но иногда может отличаться вывод предупреждении или оформление по умолчанию. Поэтому полезно проверять версию в коде.

import matplotlib

print(matplotlib.__version__)
# 3.8.0
Python

Частая путаница

Запомните: Matplotlib сам не "угадывает" смысл данных. Если вы перепутали оси местами или дали неправильные единицы измерения, график все равно построится, но будет вводить в заблуждение.
x = [0, 1, 2]
y = [0, 1, 4]

print(len(x), len(y))
# 3 3
Python
Определение: для plt.plot длины x и y должны совпадать (в простом случае). Если длины разные, Matplotlib выдаст ошибку.
^ К оглавлению

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
Python
Определение: 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
Python

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
Python
Важно: пример выше показывает, что стили можно смешать, но новичкам это часто мешает. Если в уроке мы работаем с subplots, лучше держаться OO-стиля.

Типичные ошибки

Ошибка 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
Python
parabola_10_f6210.png
Практическии вывод: если вы хотите гладкую параболу, увеличьте число точек, например до 200.
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
Python

Пределы и деления осеи: 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.]
Python
technobee_0232_53c86.png
Важно: если вы "расширили" xlim/ylim, Matplotlib не достраивает данные. Он просто показывает больше пустого места. Чтобы линия продолжалась, нужно добавить точки данных.

Сохранение: 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
Python
Практическии вывод: если на сайте график будет масштабироваться, SVG часто выглядит лучше. Если вставляете как обычную картинку, PNG проще.

Типичные ошибки

Ошибка 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
Python

Ошибка 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
Python
^ К оглавлению

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
Python
linestyles_09af3.png
Python
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
Python

markers_c8fd3.png

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
Python

Заливка между кривыми: 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
Python

fill_between_79145.png

Типичные ошибки

Ошибка 1: думать, что alpha меняет цвет

alpha меняет прозрачность, а не цвет. Цвет задается параметром color.

print("alpha is transparency, color is color")
# alpha is transparency, color is color
Python

Ошибка 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
Python
^ К оглавлению

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
Python

hist_21dcd.png

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
Python

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
Python

Типичные ошибки

Ошибка 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
Python

Ошибка 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
Python

Задача 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.]
Python

Блок 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
Python

Задача 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
Python

Задача 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
Python
^ К оглавлению

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
^ К оглавлению
Воскресенье, 07 июня 2026
Визуализация данных в Python (Matplotlib)