Работа с подграфиками (subplots) в Matplotlib является стандартной задачей при визуализации данных. Часто требуется построить несколько графиков, связанных друг с другом, например, показывающих различные аспекты одних и тех же данных или сравнение метрик по разным сегментам. При этом, если подграфики имеют общую шкалу по одной из осей, бывает полезно сделать оси общими, чтобы упростить сравнение и сэкономить место.
Введение в общие оси и подграфики в Matplotlib
Что такое общие оси и зачем они нужны?
Общие оси в Matplotlib означают, что несколько подграфиков используют одну и ту же шкалу и пределы для одной из осей (X или Y). Это особенно полезно, когда подграфики отображают данные в одних и тех же единицах измерения или используют одну и ту же временную шкалу. Использование общих осей обеспечивает следующие преимущества:
- Сравнение: Позволяет легко сравнивать значения на разных графиках, так как шкала совпадает.
- Экономия места: Метки и тики осей отображаются только для крайних подграфиков, что уменьшает «визуальный шум» и позволяет увеличить полезную область для самих графиков.
- Синхронизация: Изменение пределов или масштаба на одном из общих осей автоматически применяется ко всем подграфикам, использующим эту ось.
Создание подграфиков с общими осями с помощью plt.subplots()
Функция matplotlib.pyplot.subplots() является наиболее распространенным способом создания сетки подграфиков. Она может автоматически управлять созданием общих осей.
import matplotlib.pyplot as plt
import numpy as np
from typing import Tuple, List
def create_shared_subplots(nrows: int, ncols: int) -> Tuple[plt.Figure, np.ndarray]:
"""
Создает сетку подграфиков с общими осями X и Y.
Args:
nrows: Количество строк в сетке подграфиков.
ncols: Количество столбцов в сетке подграфиков.
Returns:
Кортеж, содержащий объект Figure и массив объектов Axes.
"""
# Создаем Figure и массив подграфиков (Axes)
# sharex=True делает ось X общей для всех подграфиков в одном столбце
# sharey=True делает ось Y общей для всех подграфиков в одной строке
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, sharex=True, sharey=True)
return fig, axes
# Пример использования:
fig, axes = create_shared_subplots(nrows=2, ncols=3)
# Для демонстрации добавим тестовые данные на каждый подграфик
# Плоский список осей для удобства итерации
axes_flat: List[plt.Axes] = axes.flatten()
for i, ax in enumerate(axes_flat):
# Генерируем простые данные для каждого графика
x_data = np.linspace(0, 10, 100)
y_data = np.sin(x_data + i * np.pi/3)
ax.plot(x_data, y_data, label=f'Серия {i+1}')
ax.set_title(f'График {i+1}')
plt.show()
В приведенном примере мы создаем сетку 2×3 с общими осями X и Y. Обратите внимание, что метки тиков и подписи осей X будут видны только для нижнего ряда, а метки тиков и подписи осей Y — только для левого столбца.
Проблема отсутствующей метки оси Y для внутренних подграфиков
При использовании sharey=True Matplotlib по умолчанию отображает метку оси Y (ylabel) только для первого подграфика в строке (то есть, для крайнего левого). Подграфики, расположенные правее в той же строке, имеют общую ось Y, но их собственные метки ylabel скрываются, чтобы избежать дублирования и сэкономить место. Это логично, когда нужна только одна общая метка для всей строки. Однако, иногда требуется более явно указать, что отображается по оси Y, или добавить общую метку для всего набора подграфиков.
Основная проблема заключается в том, что вызов ax.set_ylabel() для подграфика, который не является крайним левым в строке с sharey=True, не приведет к отображению метки, так как она по умолчанию отключена.
Основные способы добавления метки оси Y для подграфиков с общими осями
Существует несколько подходов к решению проблемы отсутствующей метки оси Y, каждый со своими нюансами.
Использование fig.supylabel() для общей метки оси Y
Наиболее элегантным и рекомендованным способом добавить общую метку оси Y для всей фигуры, содержащей подграфики с общими осями, является использование метода fig.supylabel(). Этот метод добавляет метку по центру левой стороны фигуры, вне области подграфиков.
import matplotlib.pyplot as plt
import numpy as np
from typing import Tuple, List
def plot_with_suptitle_and_supylabel(nrows: int, ncols: int, suptitle_text: str, supylabel_text: str) -> None:
"""
Создает подграфики с общими осями и добавляет общий заголовок и метку оси Y для всей фигуры.
Args:
nrows: Количество строк.
ncols: Количество столбцов.
suptitle_text: Текст для общего заголовка фигуры.
supylabel_text: Текст для общей метки оси Y фигуры.
"""
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, sharex=True, sharey=True)
# Добавляем общий заголовок для всей фигуры
fig.suptitle(suptitle_text, fontsize=16)
# Добавляем общую метку для оси Y всей фигуры
fig.supylabel(supylabel_text, fontsize=12)
# Плоский список осей для итерации и добавления данных
axes_flat: List[plt.Axes] = axes.flatten()
for i, ax in enumerate(axes_flat):
x_data = np.linspace(0, 10, 100)
y_data = np.cos(x_data * (i + 1))
ax.plot(x_data, y_data)
ax.set_title(f'График {i+1}')
# Важно: при sharey=True, set_ylabel() на внутренних графиках игнорируется по умолчанию
# Если бы оси не были общими, здесь можно было бы установить индивидуальную метку
# Автоматическая корректировка расположения подграфиков для предотвращения перекрытия
fig.tight_layout(rect=[0, 0.03, 1, 0.95]) # Оставляем место для suptitle и supylabel
plt.show()
# Пример вызова функции:
plot_with_suptitle_and_supylabel(
nrows=2,
ncols=2,
suptitle_text='Пример с общей меткой оси Y',
supylabel_text='Абстрактные Единицы (А.Е.)'
)
Этот метод наиболее подходит, когда шкала по оси Y одинакова для всех подграфиков в рамках фигуры, и вы хотите иметь одну, ясно видимую метку, описывающую эту шкалу.
Установка метки только для внешнего левого подграфика
Как уже упоминалось, Matplotlib по умолчанию отображает метку ylabel только для крайнего левого подграфика в строке при sharey=True. Вы можете явно установить метку только для этого подграфика. Это полезно, когда метка относится конкретно к шкале, общей для данной строки (или всех строк, если sharey=True применяется глобально между строками).
import matplotlib.pyplot as plt
import numpy as np
from typing import Tuple
def plot_with_first_ax_ylabel(nrows: int, ncols: int, ylabel_text: str) -> None:
"""
Создает подграфики с общими осями и устанавливает метку оси Y только для первого подграфика.
Args:
nrows: Количество строк.
ncols: Количество столбцов.
ylabel_text: Текст метки для оси Y первого подграфика.
"""
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, sharex=True, sharey=True)
# Убеждаемся, что axes является 2D-массивом, даже если nrows=1 или ncols=1
if nrows == 1 and ncols == 1:
axes = np.array([[axes]])
elif nrows == 1:
axes = axes[np.newaxis, :]
elif ncols == 1:
axes = axes[:, np.newaxis]
# Добавляем данные ко всем подграфикам
for r in range(nrows):
for c in range(ncols):
ax = axes[r, c]
x_data = np.linspace(0, 10, 100)
y_data = np.tanh(x_data - 5 + r*2 + c*0.5)
ax.plot(x_data, y_data)
ax.set_title(f'График ({r+1}, {c+1})')
# Устанавливаем метку оси Y только для самого верхнего левого подграфика
# Matplotlib автоматически применяет ее к общей оси Y
axes[0, 0].set_ylabel(ylabel_text)
fig.tight_layout()
plt.show()
# Пример вызова функции:
plot_with_first_ax_ylabel(nrows=2, ncols=2, ylabel_text='Значение Параметра')
Этот подход работает корректно, потому что Matplotlib, видя sharey=True, ассоциирует set_ylabel() для axes[0, 0] с общей осью Y для всей строки (или даже всех строк, если sharey установлено глобально).
Регулировка расстояния между подграфиками для предотвращения перекрытия меток
Иногда, даже при использовании fig.supylabel() или установке метки на крайнем левом подграфике, метка может перекрываться с заголовками подграфиков или другими элементами, особенно при плотной компоновке. Для решения этой проблемы можно вручную настроить расстояние между подграфиками или использовать автоматические инструменты Matplotlib.
fig.subplots_adjust(): Позволяет вручную контролировать пространство (left, right, bottom, top, wspace, hspace).fig.tight_layout(): Автоматически регулирует параметры подграфиков для плотного размещения, предотвращая перекрытие. Часто является первым шагом для решения проблем с компоновкой.
import matplotlib.pyplot as plt
import numpy as np
def plot_with_adjusted_spacing(nrows: int, ncols: int, supylabel_text: str) -> None:
"""
Демонстрирует настройку расстояния между подграфиками.
Args:
nrows: Количество строк.
ncols: Количество столбцов.
supylabel_text: Текст общей метки оси Y.
"""
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, sharex=True, sharey=True)
# Добавляем данные и заголовки
axes_flat = axes.flatten()
for i, ax in enumerate(axes_flat):
x_data = np.linspace(0, 10, 100)
y_data = np.exp(-x_data/10) * np.sin(x_data * 2 + i)
ax.plot(x_data, y_data)
ax.set_title(f'Очень длинный заголовок графика {i+1}') # Имитация длинного заголовка
fig.supylabel(supylabel_text, fontsize=14)
# Попытка использования tight_layout()
# fig.tight_layout(rect=[0, 0.03, 1, 0.95]) # Может помочь, но не всегда достаточно при очень длинных заголовках или метках
# Ручная настройка расстояния для лучшего расположения меток и заголовков
# Оставляем больше места слева (для supylabel) и сверху (для заголовков)
fig.subplots_adjust(left=0.1, bottom=0.1, right=0.95, top=0.9, wspace=0.2, hspace=0.4)
plt.show()
# Пример вызова:
plot_with_adjusted_spacing(
nrows=2,
ncols=2,
supylabel_text='Метка оси Y с потенциальным перекрытием'
)
Ручная настройка через subplots_adjust() дает полный контроль, но требует подбора параметров. tight_layout() обычно является хорошей отправной точкой.
Продвинутые методы и примеры
Использование ax.set_ylabel() для каждого подграфика (не рекомендуется при общих осях)
Хотя технически вы можете попытаться вызвать ax.set_ylabel() для каждого подграфика, это не рекомендуется при использовании общих осей Y (sharey=True). Как было сказано, Matplotlib по умолчанию скрывает метки для внутренних подграфиков. Даже если вы попытаетесь принудительно их отобразить (что сложно и контринтуитивно), это приведет к дублированию меток, что противоречит цели использования общих осей – упрощению и экономии места. Используйте fig.supylabel() или set_ylabel() только на крайнем левом подграфике.
Применение fig.add_subplot() и ручная настройка общего ylabel
Функция fig.add_subplot() предоставляет более гибкий, но и более низкоуровневый контроль над созданием подграфиков. Вы можете создавать подграфики по одному и указывать, с какими осями они должны быть общими, используя аргументы sharex и sharey. При таком подходе проблема с меткой оси Y аналогична – метка будет отображаться только для первого подграфика, с которым разделена ось Y. Решения (fig.supylabel() или установка метки на первый созданный ax) остаются теми же.
import matplotlib.pyplot as plt
import numpy as np
def plot_with_add_subplot(nrows: int, ncols: int, ylabel_text: str) -> None:
"""
Создает подграфики с общими осями Y с использованием fig.add_subplot() и добавляет метку.
Args:
nrows: Количество строк.
ncols: Количество столбцов.
ylabel_text: Текст метки для оси Y.
"""
fig = plt.figure()
# Создаем первый подграфик и сохраняем его для использования общих осей
ax00: plt.Axes = fig.add_subplot(nrows, ncols, 1)
axes_list: List[plt.Axes] = [ax00]
# Добавляем остальные подграфики, делая их оси Y общими с ax00
for i in range(2, nrows * ncols + 1):
# index i в add_subplot начинается с 1
ax: plt.Axes = fig.add_subplot(nrows, ncols, i, sharey=ax00) # Общая ось Y с ax00
axes_list.append(ax)
# Если бы требовалась общая ось X, можно было бы добавить sharex=...
# Добавляем данные и заголовки (упрощенно)
for i, ax in enumerate(axes_list):
x_data = np.linspace(0, 10, 100)
y_data = np.sin(x_data + i)
ax.plot(x_data, y_data)
ax.set_title(f'График {i+1}')
# При sharey=ax00, только ax00 может эффективно установить ylabel
if ax == ax00:
ax.set_ylabel(ylabel_text) # Метка устанавливается на первый подграфик
fig.tight_layout()
plt.show()
# Пример вызова:
plot_with_add_subplot(nrows=2, ncols=2, ylabel_text='Данные с add_subplot')
Использование fig.add_subplot() вместе с явным указанием sharey=other_ax дает детальный контроль, но, как видно, механизм меток остается прежним – метка Y эффективна только на том Axes объекте, который «владеет» или первым использовал общую ось Y.
Решение проблем с перекрытием меток при сложных компоновках подграфиков
Для более сложных компоновок (например, с подграфиками разного размера или вложенными gridspec) проблемы с перекрытием меток могут усугубляться. В таких случаях:
- Начните с
fig.tight_layout()илиfig.subplots_adjust()для автоматической или полуавтоматической настройки. - Если автоматические средства не помогают, возможно, потребуется вручную подвинуть элементы графика. Это можно сделать, например, получив объект метки (
ax.yaxis.get_label()) и используя его методы трансформации (set_position,set_rotation,set_horizontalalignment,set_verticalalignment). Однако это достаточно низкоуровневый подход. - Как альтернатива, можно временно отключить автоматическое скрытие меток для осей (
ax.yaxis.set_tick_params(labelleft=True)), но это скорее диагностический шаг, чем решение для чистового графика с общими осями.
В большинстве типовых случаев fig.supylabel() или set_ylabel() на первом подграфике в сочетании с tight_layout() решают проблему.
Лучшие практики и советы
Выбор подходящего метода в зависимости от сложности графика
- Простые сетки подграфиков с полностью общими осями Y: Используйте
fig.supylabel()для одной общей метки для всей фигуры. Это наиболее чистое решение. - Сетки подграфиков с построчно общими осями Y (например,
sharey='row') или когда метка должна быть только у первого графика: Используйтеax.set_ylabel()только для крайнего левого подграфика (axes[0, 0]для 2D массива осей). - Сложные компоновки или необходимость точного позиционирования: Начните с
tight_layout(), при необходимости перейдите кsubplots_adjust(). В редких случаях может потребоваться ручное позиционирование меток.
Рекомендации по стилизации меток осей
Метки осей должны быть информативными и легко читаемыми. При их стилизации учитывайте следующее:
- Размер шрифта: Должен быть достаточно большим, чтобы читаться, но не настолько, чтобы доминировать над данными.
fig.supylabel()иax.set_ylabel()принимают аргументfontsize. - Цвет: Обычно используется стандартный цвет текста, но его можно изменить с помощью аргумента
color. - Единицы измерения: Всегда включайте единицы измерения в метку оси Y, если применимо (например, «Прибыль ($)», «Температура (°C)»).
Использование tight_layout() для автоматической настройки расположения подграфиков
fig.tight_layout() – ваш первый инструмент при проблемах с расположением элементов. Он пытается автоматически оптимизировать пространство, чтобы метки, заголовки и сами графики не перекрывались. При использовании fig.suptitle или fig.supylabel не забудьте указать rect аргумент, чтобы оставить для них место:
fig.tight_layout(rect=[0, 0.03, 1, 0.95]) # [left, bottom, right, top] в долях от размера фигуры
Это часто решает проблемы с перекрытием меток оси Y, особенно при использовании fig.supylabel().
Заключение
Краткое описание рассмотренных методов
Работа с метками оси Y при использовании общих осей в Matplotlib требует понимания того, как библиотека управляет осями при совместном использовании. Мы рассмотрели три основных подхода:
fig.supylabel(): Добавление одной общей метки для всей фигуры. Наиболее чистый способ для полностью общих осей.ax.set_ylabel()на первом подграфике: Установка метки только на крайний левый подграфик, где она будет видна. Подходит, когда метка относится к общей оси строки/сетки.- Настройка расстояний (
fig.subplots_adjust(),fig.tight_layout()): Непрямой метод, помогающий избежать перекрытия меток, установленных одним из первых двух способов, с другими элементами графика.
Дополнительные ресурсы для изучения Matplotlib
Для более глубокого изучения Matplotlib и продвинутых техник работы с подграфиками, осями и компоновкой, рекомендуется обратиться к официальной документации Matplotlib (https://matplotlib.org/). Документация содержит подробные описания всех функций, руководства и множество примеров. Также полезно изучить раздел «Toolkits» в документации, в частности, mpl_toolkits.axes_grid1 для создания сложных сеток осей.
Надеюсь, эта статья поможет вам уверенно добавлять метки оси Y к подграфикам с общими осями и создавать более информативные и аккуратные визуализации.