W3docs

Подграфики Matplotlib — Полное руководство

Создавайте одно- и многопанельные фигуры с подграфиками Matplotlib: plt.subplots(), GridSpec, общие оси, размеры и примеры сохранения.

Подграфики позволяют размещать несколько диаграмм внутри одной фигуры Matplotlib. Вместо того чтобы создавать отдельные окна для каждого графика, можно расположить их в сетке — рядом, стопкой или в произвольном макете — чтобы читатель мог сравнивать данные с первого взгляда. В этой главе рассматривается всё — от простой двухпанельной фигуры до гибких сеточных макетов с общими осями и объединением ячеек.

Перед началом установите Matplotlib, если вы этого ещё не сделали:

pip install matplotlib numpy

Если вы только начинаете работать с библиотекой, сначала прочитайте главы Введение в Matplotlib и Начало работы.

Самый быстрый способ: plt.subplots()

plt.subplots(nrows, ncols) — стандартная точка входа. Она возвращает объект Figure и массив объектов Axes.

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))

x = np.linspace(0, 2 * np.pi, 200)

axes[0].plot(x, np.sin(x), color='steelblue')
axes[0].set_title('Sine')
axes[0].set_xlabel('x')
axes[0].set_ylabel('sin(x)')

axes[1].plot(x, np.cos(x), color='darkorange')
axes[1].set_title('Cosine')
axes[1].set_xlabel('x')
axes[1].set_ylabel('cos(x)')

plt.tight_layout()
plt.show()

Ключевые моменты:

  • figsize=(width, height) задаёт размер фигуры в дюймах. Значение по умолчанию (6.4, 4.8) зачастую слишком мало для нескольких панелей.
  • plt.tight_layout() автоматически регулирует отступы, чтобы заголовки и подписи не перекрывались. Вызывайте его непосредственно перед show() или savefig().
  • Когда ncols=1 и nrows=1 (значение по умолчанию), axes является одиночным объектом Axes, а не массивом.

Создание сетки 2×2

Передайте оба параметра nrows и ncols, чтобы получить двумерный массив NumPy из объектов Axes. Обращайтесь к нему с помощью [row, col], где оба индекса отсчитываются с нуля.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)

fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(10, 8))

# Top-left
ax[0, 0].plot(x, np.sin(x), color='steelblue')
ax[0, 0].set_title('sin(x)')

# Top-right
ax[0, 1].plot(x, np.cos(x), color='darkorange')
ax[0, 1].set_title('cos(x)')

# Bottom-left  — clamp tan to avoid ±∞ spikes
y_tan = np.clip(np.tan(x), -10, 10)
ax[1, 0].plot(x, y_tan, color='seagreen')
ax[1, 0].set_title('tan(x) [clipped]')

# Bottom-right
ax[1, 1].plot(x, np.exp(x / np.pi), color='tomato')
ax[1, 1].set_title('exp(x/π)')

plt.suptitle('Trigonometric & Exponential Functions', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

plt.suptitle() добавляет общий заголовок фигуры над всеми подграфиками. Смещение y=1.02 поднимает его выше заголовков панелей верхнего ряда.

Перебор осей в цикле

При работе с большой сеткой удобнее перебирать уплощённый массив осей в цикле, чем обращаться к каждой ячейке вручную:

import matplotlib.pyplot as plt
import numpy as np

functions = [np.sin, np.cos, np.tan, np.arctan]
names     = ['sin', 'cos', 'tan', 'arctan']
colors    = ['steelblue', 'darkorange', 'seagreen', 'tomato']

x = np.linspace(-np.pi, np.pi, 300)

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

for ax, fn, name, color in zip(axes.flat, functions, names, colors):
    y = fn(x)
    y = np.clip(y, -5, 5)          # keep tan from dominating the scale
    ax.plot(x, y, color=color)
    ax.set_title(name)
    ax.axhline(0, color='black', linewidth=0.6, linestyle='--')
    ax.axvline(0, color='black', linewidth=0.6, linestyle='--')

plt.tight_layout()
plt.show()

axes.flat возвращает одномерный итератор независимо от формы сетки, поэтому один и тот же цикл работает для сеток 2×2, 3×3 и любых других.

Общие оси

Когда панели представляют одну и ту же величину (одинаковый диапазон x или одинаковый масштаб y), свяжите их оси так, чтобы прокрутка или масштабирование одной обновляло все:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 4 * np.pi, 400)

# sharex=True links horizontal axes; sharey=True links vertical axes
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 6), sharex=True)

ax1.plot(x, np.sin(x), color='steelblue')
ax1.set_ylabel('sin(x)')
ax1.set_title('Shared x-axis example')

ax2.plot(x, np.sin(2 * x), color='darkorange')
ax2.set_ylabel('sin(2x)')
ax2.set_xlabel('x (radians)')

# Hide redundant x-tick labels on the top panel
ax1.tick_params(labelbottom=False)

plt.tight_layout()
plt.show()

sharex=True убирает дублирующиеся подписи оси x с верхних панелей и выравнивает все панели. Используйте sharey=True аналогично, когда масштаб y должен совпадать по столбцам.

Прямая распаковка осей

Если макет небольшой и известен заранее, можно сразу распаковать возвращаемый массив в именованные переменные:

import matplotlib.pyplot as plt
import numpy as np

# 1 row, 3 columns → unpack into three named axes
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(13, 4))

x = np.linspace(0, 10, 300)

ax1.plot(x, x ** 0.5,  color='steelblue',  label='√x')
ax1.set_title('Square Root')
ax1.legend()

ax2.plot(x, np.log1p(x), color='darkorange', label='ln(1+x)')
ax2.set_title('Natural Log')
ax2.legend()

ax3.plot(x, x,           color='seagreen',  label='x')
ax3.set_title('Linear')
ax3.legend()

plt.tight_layout()
plt.show()

Это удобнее, чем axes[0], axes[1], axes[2] для коротких макетов. Для двумерной сетки с двумя строками используйте вложенную распаковку: (ax1, ax2), (ax3, ax4) = axes.

Произвольные макеты с GridSpec

plt.subplots() создаёт только сетки, где все ячейки одинакового размера. Для неравномерных макетов — широкая обзорная панель вверху с детальными панелями снизу — используйте matplotlib.gridspec.GridSpec:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np

x = np.linspace(0, 2 * np.pi, 300)

fig = plt.figure(figsize=(10, 8))
gs  = gridspec.GridSpec(nrows=2, ncols=3, figure=fig)

# Top row: one wide panel spanning all three columns
ax_top = fig.add_subplot(gs[0, :])
ax_top.plot(x, np.sin(x), color='steelblue', linewidth=2)
ax_top.set_title('Overview — sin(x)')

# Bottom row: three equal panels
ax_bl = fig.add_subplot(gs[1, 0])
ax_bl.plot(x, np.sin(x),     color='steelblue')
ax_bl.set_title('sin(x)')

ax_bm = fig.add_subplot(gs[1, 1])
ax_bm.plot(x, np.cos(x),     color='darkorange')
ax_bm.set_title('cos(x)')

ax_br = fig.add_subplot(gs[1, 2])
ax_br.plot(x, np.sin(x) * np.cos(x), color='seagreen')
ax_br.set_title('sin(x)·cos(x)')

plt.tight_layout()
plt.show()

Синтаксис срезов gs[row, col] аналогичен срезам NumPy. gs[0, :] охватывает все три столбца; gs[1, 0] берёт только первую ячейку в строке 1.

Управление высотой строк и шириной столбцов

GridSpec принимает параметры height_ratios и width_ratios, чтобы сделать одни строки или столбцы крупнее других:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np

fig = plt.figure(figsize=(9, 7))
gs  = gridspec.GridSpec(
    2, 2,
    height_ratios=[3, 1],   # top row is 3× taller than bottom row
    width_ratios=[2, 1],    # left column is 2× wider than right column
    hspace=0.4,
    wspace=0.3,
)

x = np.linspace(0, 4 * np.pi, 400)

ax1 = fig.add_subplot(gs[0, 0])
ax1.plot(x, np.sin(x), color='steelblue')
ax1.set_title('Main (tall + wide)')

ax2 = fig.add_subplot(gs[0, 1])
ax2.plot(x, np.cos(x), color='darkorange')
ax2.set_title('Side (tall + narrow)')

ax3 = fig.add_subplot(gs[1, 0])
ax3.plot(x, np.sin(x) ** 2, color='seagreen')
ax3.set_title('Bottom-left (short + wide)')

ax4 = fig.add_subplot(gs[1, 1])
ax4.plot(x, np.cos(x) ** 2, color='tomato')
ax4.set_title('Bottom-right (short + narrow)')

plt.suptitle('Proportional Grid Layout', fontsize=13)
plt.show()

hspace и wspace управляют вертикальным и горизонтальным расстоянием между подграфиками (как доля от средней высоты/ширины подграфика).

Задание общего размера фигуры

Параметр figsize всегда относится ко всей фигуре целиком, а не к отдельным подграфикам. Примерные ориентиры:

МакетРекомендуемый figsize
1 строка × 2 столбца(10, 4)
1 строка × 3 столбца(13, 4)
2 строки × 2 столбца(10, 8)
3 строки × 3 столбца(13, 11)

Вы всегда можете скорректировать значения под свои данные; это лишь отправные точки, оставляющие место для заголовков и подписей.

Добавление заголовков и подписей

Каждый объект Axes имеет собственный заголовок и подписи осей. Вся фигура целиком может иметь общий заголовок:

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
x = np.linspace(0, 5, 100)

for ax, power, color in zip(axes, [2, 3], ['steelblue', 'darkorange']):
    ax.plot(x, x ** power, color=color)
    ax.set_title(f'$x^{power}$')      # LaTeX in title
    ax.set_xlabel('x')
    ax.set_ylabel(f'$x^{power}$')
    ax.grid(True, linestyle='--', alpha=0.5)

plt.suptitle('Power Functions', fontsize=14)
plt.tight_layout()
plt.show()

Сохранение многопанельной фигуры

Вызовите plt.savefig() до plt.show() (вызов show() первым очищает фигуру):

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(1, 3, figsize=(13, 4))
x = np.linspace(0, 2 * np.pi, 300)

axes[0].plot(x, np.sin(x),        color='steelblue')
axes[0].set_title('sin(x)')

axes[1].plot(x, np.cos(x),        color='darkorange')
axes[1].set_title('cos(x)')

axes[2].plot(x, np.sin(x) + np.cos(x), color='seagreen')
axes[2].set_title('sin(x) + cos(x)')

plt.tight_layout()
plt.savefig('trig_panels.png', dpi=150, bbox_inches='tight')
plt.show()

bbox_inches='tight' обрезает лишние отступы вокруг фигуры — удобно при встраивании изображения в документ или веб-страницу.

Частые ошибки

Забытый tight_layout()

Без tight_layout() заголовки подграфиков и подписи осей соседних панелей нередко перекрываются. Всегда вызывайте его перед show() или savefig().

Неверные размерности индексов

plt.subplots(2, 3) возвращает двумерный массив; plt.subplots(1, 3) — одномерный. Попытка использовать axes[0, 0] с одномерным массивом вызовет IndexError. Используйте axes[0] либо передавайте squeeze=False, чтобы всегда получать двумерный массив:

fig, axes = plt.subplots(1, 3, squeeze=False)
# axes is now shape (1, 3) — always index with [row, col]
axes[0, 0].plot(...)
axes[0, 1].plot(...)
axes[0, 2].plot(...)

Обрезка suptitle

plt.suptitle() может обрезаться функцией tight_layout(). Исправьте это, передав rect в tight_layout:

plt.tight_layout(rect=[0, 0, 1, 0.95])   # leave 5% at top for suptitle

Или используйте вместо этого fig.subplots_adjust(top=0.90).

Краткий справочник

ЗадачаКод
Сетка 1×2fig, (ax1, ax2) = plt.subplots(1, 2)
Сетка 2×2fig, ax = plt.subplots(2, 2)
Доступ к ячейкеax[row, col]
Перебор всех ячеекfor ax in axes.flat:
Общая ось xplt.subplots(2, 1, sharex=True)
Общая ось yplt.subplots(1, 2, sharey=True)
Всегда двумерный массивplt.subplots(..., squeeze=False)
Произвольный макетgs = GridSpec(rows, cols); fig.add_subplot(gs[r, c])
Объединение столбцовfig.add_subplot(gs[0, :])
Соотношение высот строкGridSpec(2, 1, height_ratios=[3, 1])
Заголовок фигурыplt.suptitle('Title')
Сохранение фигурыplt.savefig('out.png', dpi=150, bbox_inches='tight')

Связанные главы

Was this page helpful?