Подграфики 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×2 | fig, (ax1, ax2) = plt.subplots(1, 2) |
| Сетка 2×2 | fig, ax = plt.subplots(2, 2) |
| Доступ к ячейке | ax[row, col] |
| Перебор всех ячеек | for ax in axes.flat: |
| Общая ось x | plt.subplots(2, 1, sharex=True) |
| Общая ось y | plt.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') |
Связанные главы
- Введение в Matplotlib — обзор библиотеки и установка
- Начало работы с Matplotlib — ваш первый график
- Линейные графики Matplotlib — построение непрерывных данных
- Подписи Matplotlib — подписи осей, заголовки и аннотации
- Сетка Matplotlib — добавление и стилизация линий сетки
- Столбчатые диаграммы Matplotlib — сравнение категорий
- Точечные графики Matplotlib — связи между переменными
- Гистограммы Matplotlib — распределения данных