Как создать графики с общей осью X в Matplotlib?

Matplotlib – это мощная библиотека для создания статических, интерактивных и анимированных визуализаций на Python. Она предоставляет гибкий API для построения самых разнообразных графиков. Одной из распространенных задач при анализе данных является сравнение различных метрик или наборов данных, которые зависят от одной и той же переменной, например, времени или какого-либо индекса. В таких случаях бывает удобно расположить графики вертикально друг над другом, используя общую горизонтальную (X) ось.

Зачем использовать общую ось X?

Использование общей оси X при отображении нескольких графиков имеет несколько ключевых преимуществ:

Улучшение читаемости и сравнения: Когда графики используют одну и ту же шкалу по оси X, значительно проще визуально сопоставлять соответствующие точки или интервалы между разными графиками. Это особенно важно для временных рядов или данных, имеющих общий фактор.

Экономия пространства: Дублирование меток и подписей оси X для каждого графика занимает место и может сделать визуализацию перегруженной. Общая ось X требует отображения этих элементов только один раз, обычно на самом нижнем графике, освобождая пространство.

Синхронизация: При интерактивном масштабировании или панорамировании (зуммировании/перемещении) графиков с общей осью X эти действия применяются ко всем связанным графикам одновременно. Это обеспечивает согласованный просмотр данных.

Основные понятия: figure, axes, subplot

Прежде чем погрузиться в создание графиков с общей осью, важно освежить в памяти базовую иерархию объектов Matplotlib:

Figure (фигура): Это контейнер верхнего уровня, представляющий окно или страницу, на которой отображаются графики. Одна фигура может содержать один или несколько наборов координатных осей.

Axes (оси): Это фактическая область, где строятся данные. Каждые оси имеют свой набор координат (например, X и Y для 2D-графика) и могут содержать различные графические элементы, такие как линии, маркеры, подписи осей, заголовок и т.д. Часто термины "axes" и "plot" используются как синонимы, но "axes" относится к области, а "plot" – к содержимому.

Subplot (подось): Это частный случай Axes, когда Axes расположены в сетке внутри одной фигуры. plt.subplot() или plt.subplots() используются для создания таких сеток осей.

Понимание того, что sharex применяется к объектам Axes (или subplot), а не к самой фигуре, критически важно.

Краткий обзор библиотеки Matplotlib

Matplotlib является фундаментальной библиотекой для визуализации данных в экосистеме Python, особенно в области научных вычислений, анализа данных и машинного обучения. Она предоставляет как процедурный интерфейс (pyplot, часто импортируемый как plt), который напоминает MATLAB, так и объектно-ориентированный API. Для создания сложных и настраиваемых визуализаций, включая работу с несколькими осями и общей осью, объектно-ориентированный подход, использующий объекты Figure и Axes, является предпочтительным.

Создание графиков с общей осью X с помощью `plt.subplots()`

Наиболее удобный и рекомендуемый способ создания набора графиков с общей осью X – это использование функции plt.subplots().

Использование `plt.subplots(…, sharex=True)`

Функция plt.subplots() создает фигуру и набор координатных осей (или одну ось, если запрошен один график) в одной команде. Она возвращает кортеж, содержащий объект Figure и либо один объект Axes, либо массив объектов Axes (NumPy array), в зависимости от количества строк и столбцов запрошенной сетки.

Чтобы создать графики с общей осью X, достаточно передать аргумент sharex=True:

import matplotlib.pyplot as plt
import numpy as np

# Создание фигуры и набора осей с общей осью X
# nrows=2, ncols=1 означает 2 строки, 1 столбец
fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True, figsize=(10, 6))

# axes теперь является массивом объектов Axes: axes[0] и axes[1]

В этом примере axes – это одномерный массив NumPy, содержащий два объекта Axes. axes[0] соответствует верхнему графику, а axes[1] – нижнему. Поскольку sharex=True, оба этих объекта Axes будут использовать одну и ту же шкалу и диапазон по оси X.

Примеры графиков: временные ряды, сравнение данных

Рассмотрим пример с временными рядами, где мы хотим отобразить две разные метрики (например, объем продаж и количество посетителей сайта) против времени.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# Генерация примеров данных - временной ряд
dates: pd.DatetimeIndex = pd.date_range('2023-01-01', periods=100, freq='D')
# Случайные данные для двух метрик
metric_a: np.ndarray = np.random.rand(100).cumsum() + 100 # Метрика A (накопительная)
metric_b: np.ndarray = np.random.rand(100).cumsum() + 50  # Метрика B (накопительная)

# Создание фигуры и двух подосей с общей осью X
# Указываем размер фигуры для лучшей читаемости дат
fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True, figsize=(12, 8))

# Построение данных на первой (верхней) подоси
axes[0].plot(dates, metric_a, label='Метрика A', color='skyblue')
axes[0].set_title('Динамика метрик A и B по времени') # Заголовок всей группы графиков (можно на верхнем)
axes[0].set_ylabel('Значение метрики A')
axes[0].grid(True, linestyle='--', alpha=0.6)
axes[0].legend()

# Построение данных на второй (нижней) подоси
axes[1].plot(dates, metric_b, label='Метрика B', color='salmon')
axes[1].set_ylabel('Значение метрики B')
axes[1].set_xlabel('Дата') # Подпись оси X только на нижнем графике
axes[1].grid(True, linestyle='--', alpha=0.6)
axes[1].legend()

# Автоматическое форматирование меток даты для предотвращения перекрытия
fig.autofmt_xdate()

# Отображение графика
plt.tight_layout() # Автоматическая корректировка положения подосей
plt.show()

В этом примере мы видим, как легко построить два графика, выровненных по горизонтали и использующих общую ось времени. Подпись оси X ('Дата') устанавливается только на нижнем графике, что является стандартной практикой для общих осей.

Настройка отображения: подписи осей, заголовки

Как показано в примере, настройка отображения для графиков с общей осью выполняется так же, как и для одиночных графиков, но через соответствующий объект Axes из массива axes:

axes[i].plot(...): Построение данных на i-й подоси.

axes[i].set_title(...): Установка заголовка для i-й подоси. Часто общий заголовок группы графиков ставится на верхний график (axes[0].set_title(...)).

axes[i].set_ylabel(...): Установка подписи оси Y для i-й подоси. У каждого графика будет своя ось Y.

axes[i].set_xlabel(...): Установка подписи оси X. Поскольку ось X общая, подпись обычно устанавливается только на самом нижнем графике (axes[-1].set_xlabel(...) или axes[nrows-1].set_xlabel(...)).

Управление общей осью X после создания графиков

Хотя plt.subplots(sharex=True) является основным способом, иногда графики создаются по отдельности или в рамках более сложной структуры, и требуется сделать их оси X общими уже после создания.

Доступ к отдельным осям через возвращаемый объект `axes`

Как упоминалось, plt.subplots() возвращает массив объектов Axes. Доступ к ним осуществляется по индексам: axes[0], axes[1], axes[0, 0], axes[1, 2] и т.д. в зависимости от размерности сетки. Эти объекты Axes можно использовать для построения данных, настройки их внешнего вида, а также для управления совместным использованием осей.

Реклама

Использование `Axes.sharex()` для совместного использования оси X

Объекты Axes имеют методы sharex() и sharey(), которые позволяют связать оси одного объекта Axes с осями другого объекта Axes уже после их создания. Этот метод принимает другой объект Axes в качестве аргумента.

import matplotlib.pyplot as plt
import numpy as np

# Создаем два отдельных объекта Axes (можно было бы и через subplot без sharex)
fig = plt.figure(figsize=(10, 8))

# Добавляем первые оси
ax1 = fig.add_subplot(2, 1, 1)
# Добавляем вторые оси
ax2 = fig.add_subplot(2, 1, 2)

# ... Построение данных на ax1 и ax2 ...

# Теперь делаем ось X ax2 общей с осью X ax1
ax2.sharex(ax1)

# Ось X ax1 теперь также общая с осью X ax2 (связь двусторонняя)
# ax1.sharex(ax2) дал бы тот же эффект

# После связывания, можно настроить, например, скрыть метки на верхнем графике
ax1.xaxis.set_tick_params(labelbottom=False) # Скрываем метки снизу

# ... Прочие настройки и plt.show()

Использование Axes.sharex() полезно в сценариях, где структура графиков определяется более динамично или создается поэтапно.

Расширенные возможности и настройки

При работе с общей осью X возникает ряд типичных задач по настройке отображения.

Скрытие меток и подписей оси X на верхних графиках

Чтобы избежать дублирования и улучшить читаемость, метки (tick labels) и иногда подпись (label) оси X скрывают на всех графиках, кроме самого нижнего. Это можно сделать несколькими способами:

# Используя set_xticklabels([]) или set_visible(False)
axes[0].set_xticklabels([]) # Удаляет только текстовые метки, тики остаются

# Или используя tick_params
axes[0].tick_params(labelbottom=False) # Скрывает метки внизу оси X

# Скрытие всей оси (включая тики и подпись)
# axes[0].get_xaxis().set_visible(False) # Обычно не требуется для общей оси

# Если ось X имеет подпись, ее тоже можно скрыть на верхних графиках
axes[0].set_xlabel('') # Установить пустую строку для подписи

В примере с временными рядами, plt.subplots(sharex=True) по умолчанию скрывает метки оси X на верхних графиках, но явное управление полезно, если вы создаете графики другим способом или хотите выборочно контролировать отображение.

Форматирование меток оси X (например, даты)

Если ось X представляет даты или время, стандартное форматирование может быть неоптимальным (например, перекрывающиеся метки). Matplotlib имеет развитые возможности для форматирования дат с помощью модуля matplotlib.dates.

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
import numpy as np

# ... (генерация данных dates, metric_a, metric_b как выше) ...

fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True, figsize=(12, 8))

# ... (построение графиков на axes[0] и axes[1] как выше) ...

# Применение форматера для оси X (на общем экземпляре оси)
# Получаем общий экземпляр оси X. Он доступен через .get_shared_x_axes() любого из связанных объектов Axes.
# Или просто работаем с осью нижнего графика, т.к. она управляет отображением.
ax = axes[1] # Работаем с осью нижнего графика

# Устанавливаем локатор и форматер для дат
ax.xaxis.set_major_locator(mdates.AutoDateLocator()) # Автоматический выбор основных тиков
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) # Формат даты ГГГГ-ММ-ДД

# Автоматическое вращение меток даты
fig.autofmt_xdate()

# ... (остальные настройки и plt.show()) ...

Метод fig.autofmt_xdate() часто сам по себе неплохо справляется с вращением меток для предотвращения перекрытия при работе с датами.

Обработка перекрывающихся меток

Помимо форматирования дат, перекрытие меток может произойти и с другими типами данных на плотной оси X. Matplotlib предоставляет различные локаторы (Locator) и форматеры (Formatter) для контроля положения и вида меток. Для не-датовых осей могут использоваться MaxNLocator (ограничивает максимальное количество тиков) или FixedLocator.

При использовании общей оси X, эти настройки применяются к общему экземпляру оси, который по умолчанию управляется нижним графиком (или любым из графиков, который не скрывает свои метки).

Использование `gridspec` для более сложной компоновки графиков

plt.subplots() отлично подходит для простых сеток. Однако, если вам нужна более сложная компоновка подосей (например, графики разного размера, расположенные не по равномерной сетке, но с необходимостью общей оси X), можно использовать модуль matplotlib.gridspec.

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

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

# Создаем фигуру
fig = plt.figure(figsize=(10, 10))

# Определяем сетку 3x1
gs = gridspec.GridSpec(3, 1, figure=fig)

# Добавляем оси в ячейки сетки
# ax1 занимает верхнюю строку
ax1 = fig.add_subplot(gs[0, 0])
# ax2 занимает среднюю строку
ax2 = fig.add_subplot(gs[1, 0], sharex=ax1) # Делаем ось X общей с ax1
# ax3 занимает нижнюю строку
ax3 = fig.add_subplot(gs[2, 0], sharex=ax1) # Делаем ось X общей с ax1

# ... Построение данных на ax1, ax2, ax3 ...

# Скрываем метки на верхних графиках
ax1.tick_params(labelbottom=False)
ax2.tick_params(labelbottom=False)

# ... Прочие настройки и plt.show()

Использование gridspec в сочетании с аргументом sharex (или методом sharex()) дает полный контроль над расположением и совместным использованием осей в сложных макетах.

Заключение

Создание графиков с общей осью X в Matplotlib является эффективным способом визуализации и сравнения данных, зависящих от одной и той же независимой переменной. Это значительно улучшает читаемость, экономит пространство и обеспечивает синхронизированное взаимодействие с графиками.

Преимущества и недостатки использования общей оси X

Преимущества:

Упрощает визуальное сравнение данных между графиками.

Сокращает дублирование элементов оси X, экономя пространство.

Обеспечивает синхронизацию масштабирования и панорамирования.

Недостатки:

Может быть менее информативно, если данные на разных графиках имеют сильно различающиеся диапазоны значений, которые трудно сравнивать по общей шкале.

Требует внимательной настройки меток и подписей для предотвращения перегруженности или их полного отсутствия на верхних графиках.

Рекомендации по применению в различных ситуациях

Общую ось X рекомендуется использовать в следующих случаях:

Временные ряды: Отображение нескольких метрик, меняющихся со временем.

Сравнение распределений: Построение гистограмм или графиков плотности для разных групп по одному и тому же диапазону значений.

Данные, зависящие от общего индекса: Любые данные, где наблюдение на позиции i в одном наборе данных логически связано с наблюдением на позиции i в другом наборе данных (например, последовательность экспериментов, шаги алгоритма).

Избегайте общей оси X, если графики отображают совершенно разные типы данных с несвязанными шкалами по оси X или если важно сохранить независимое масштабирование для каждой оси.

Дополнительные ресурсы и ссылки

Для более глубокого изучения возможностей Matplotlib по работе с сабплотами, осями и их совместным использованием, обратитесь к официальной документации Matplotlib, особенно к разделам, посвященным pyplot, axes, figure и gridspec.


Добавить комментарий