При визуализации данных с помощью Matplotlib часто возникает необходимость отобразить несколько подграфиков (subplots), каждый из которых использует цветовую карту (colormap) для представления значений. Стандартный подход добавления colorbar к каждому подграфику приводит к дублированию шкал, что загромождает визуализацию и затрудняет сравнение данных между графиками, поскольку диапазоны цветов могут не совпадать.
Зачем нужна общая цветовая шкала?
Использование единой цветовой шкалы для группы подграфиков решает несколько задач:
- Согласованность: Гарантирует, что один и тот же цвет соответствует одному и тому же значению на всех подграфиках.
- Сравнимость: Упрощает визуальное сравнение распределения значений между различными наборами данных.
- Экономия пространства: Уменьшает визуальный шум, заменяя несколько шкал одной общей.
- Профессиональный вид: Придает визуализации более чистый и законченный вид.
Обзор основных подходов к решению проблемы
Существует несколько способов реализовать общую цветовую шкалу в Matplotlib:
- Использование
matplotlib.cm.ScalarMappable: Создание отдельного объекта, отвечающего за отображение данных в цвета, и привязкаcolorbarк нему. - Явное задание пределов
vminиvmax: Определение общих минимального и максимального значений для всех подграфиков и передача их в функции отрисовки. - Применение
matplotlib.gridspec: Использование более гибкой системы компоновки для явного выделения места под общую цветовую шкалу.
Рассмотрим каждый из этих подходов подробнее.
Способ 1: Использование ScalarMappable и colorbar
Этот метод является наиболее гибким и рекомендуемым. Он заключается в создании объекта ScalarMappable, который инкапсулирует нормализацию данных (mapping data values to the [0, 1] interval) и цветовую карту. Затем colorbar создается на основе этого объекта, а не конкретного подграфика.
Создание экземпляра ScalarMappable
Сначала необходимо определить нормализатор (matplotlib.colors.Normalize) и цветовую карту (cmap). Нормализатор обычно создается на основе глобальных минимального и максимального значений данных (vmin, vmax).
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.cm as cm
import numpy as np
from typing import List, Tuple
# Пример: Глобальные мин/макс значения для всех данных
global_vmin: float = 0.0
global_vmax: float = 100.0
# Выбор цветовой карты
cmap: mcolors.Colormap = cm.viridis
# Создание нормализатора
norm: mcolors.Normalize = mcolors.Normalize(vmin=global_vmin, vmax=global_vmax)
# Создание ScalarMappable
sm: cm.ScalarMappable = cm.ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([]) # Важно: передаем пустой массив или None
Отображение данных на подграфиках с использованием ScalarMappable
При построении графиков (например, imshow или pcolormesh) необходимо использовать ту же цветовую карту (cmap) и нормализатор (norm), которые были переданы в ScalarMappable.
# Пример данных (имитация тепловых карт эффективности рекламных кампаний)
data1: np.ndarray = np.random.rand(10, 10) * 70
data2: np.ndarray = np.random.rand(10, 10) * 100
data3: np.ndarray = np.random.rand(10, 10) * 50
data4: np.ndarray = np.random.rand(10, 10) * 90
all_data: List[np.ndarray] = [data1, data2, data3, data4]
fig, axes = plt.subplots(2, 2, figsize=(8, 8))
for ax, data in zip(axes.flat, all_data):
im = ax.imshow(data, cmap=cmap, norm=norm)
ax.set_title(f'Max Value: {data.max():.2f}')
ax.set_xticks([])
ax.set_yticks([])
Добавление colorbar для общей цветовой шкалы
Цветовая шкала добавляется с помощью fig.colorbar(), передавая ей объект ScalarMappable (sm) и указывая оси (ax), рядом с которыми она должна быть размещена.
# Добавление colorbar
# fig.colorbar(sm, ax=axes.ravel().tolist(), shrink=0.6)
# Более гибкий вариант - размещение относительно всей фигуры
cbar_ax = fig.add_axes([0.92, 0.15, 0.03, 0.7]) # [left, bottom, width, height]
fig.colorbar(sm, cax=cbar_ax)
plt.suptitle('Эффективность кампаний (Общая шкала)')
plt.show()
Пример кода с пояснениями
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.cm as cm
import numpy as np
from typing import List, Tuple, Optional
def plot_multiple_heatmaps_shared_cbar(
data_list: List[np.ndarray],
grid_shape: Tuple[int, int],
cmap_name: str = 'viridis',
global_min: Optional[float] = None,
global_max: Optional[float] = None,
figsize: Tuple[int, int] = (10, 8),
cbar_rect: List[float] = [0.92, 0.15, 0.03, 0.7]
) -> None:
"""
Отображает несколько тепловых карт с общей цветовой шкалой.
Args:
data_list: Список 2D numpy массивов для отображения.
grid_shape: Кортеж (rows, cols) для сетки подграфиков.
cmap_name: Название цветовой карты Matplotlib.
global_min: Глобальное минимальное значение для шкалы. Если None, вычисляется по данным.
global_max: Глобальное максимальное значение для шкалы. Если None, вычисляется по данным.
figsize: Размер фигуры (width, height).
cbar_rect: Позиция и размер цветовой шкалы [left, bottom, width, height].
"""
rows, cols = grid_shape
if len(data_list) > rows * cols:
raise ValueError("Количество данных превышает размер сетки")
# Определение глобальных min/max, если они не заданы
if global_min is None:
global_min = min(data.min() for data in data_list)
if global_max is None:
global_max = max(data.max() for data in data_list)
# Настройка ScalarMappable
cmap: mcolors.Colormap = cm.get_cmap(cmap_name)
norm: mcolors.Normalize = mcolors.Normalize(vmin=global_min, vmax=global_max)
sm: cm.ScalarMappable = cm.ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([]) # Обязательно для независимого colorbar
fig, axes = plt.subplots(rows, cols, figsize=figsize, squeeze=False)
# Отображение данных
for i, ax in enumerate(axes.flat):
if i < len(data_list):
im = ax.imshow(data_list[i], cmap=cmap, norm=norm)
ax.set_title(f'Dataset {i+1} (Max: {data_list[i].max():.2f})')
ax.set_xticks([])
ax.set_yticks([])
else:
ax.axis('off') # Скрыть неиспользуемые оси
# Добавление общей цветовой шкалы
cbar_ax = fig.add_axes(cbar_rect)
fig.colorbar(sm, cax=cbar_ax)
plt.suptitle(f'Сравнение данных с общей шкалой [{global_min:.1f}, {global_max:.1f}]')
plt.subplots_adjust(right=0.88, wspace=0.1, hspace=0.2) # Корректировка отступов
plt.show()
# Пример использования
data1 = np.random.rand(10, 10) * 80 # CTR % * 100
data2 = np.random.rand(10, 10) * 100
data3 = np.random.rand(10, 10) * 65
data4 = np.random.rand(10, 10) * 95
plot_multiple_heatmaps_shared_cbar([data1, data2, data3, data4], grid_shape=(2, 2))
Способ 2: Явное задание пределов цветовой шкалы (vmin, vmax)
Этот подход проще в реализации для стандартных случаев. Он основан на том, что colorbar автоматически определяет свой диапазон на основе параметров vmin и vmax, переданных в функцию отрисовки (например, imshow).
Определение минимального и максимального значений для всех подграфиков
Перед построением графиков необходимо вычислить единые минимальное (vmin) и максимальное (vmax) значения по всем наборам данных, которые будут отображаться.
import numpy as np
from typing import List
# Пример данных (конверсии по регионам)
conversions_region_a: np.ndarray = np.random.rand(5, 5) * 5 + 1 # Конверсии от 1% до 6%
conversions_region_b: np.ndarray = np.random.rand(5, 5) * 3 + 0.5 # Конверсии от 0.5% до 3.5%
all_conversions: List[np.ndarray] = [conversions_region_a, conversions_region_b]
# Вычисление глобальных min/max
global_vmin: float = min(data.min() for data in all_conversions)
global_vmax: float = max(data.max() for data in all_conversions)
print(f"Global Min: {global_vmin:.2f}, Global Max: {global_vmax:.2f}")
Передача vmin и vmax в функции построения графиков
При вызове imshow, pcolormesh, scatter (с параметром c) и других подобных функций необходимо явно указать вычисленные vmin и vmax.
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
cmap_name = 'plasma'
im1 = axes[0].imshow(conversions_region_a, cmap=cmap_name, vmin=global_vmin, vmax=global_vmax)
axes[0].set_title('Регион А')
im2 = axes[1].imshow(conversions_region_b, cmap=cmap_name, vmin=global_vmin, vmax=global_vmax)
axes[1].set_title('Регион Б')
for ax in axes:
ax.set_xticks([])
ax.set_yticks([])
Добавление colorbar
Теперь colorbar можно добавить, привязав его к любому из объектов AxesImage (результат вызова imshow), поскольку все они используют одинаковые vmin, vmax и cmap. Matplotlib автоматически создаст правильную шкалу.
# Добавляем colorbar, ссылаясь на последний AxesImage (im2), но можно и на im1
# Используем fig.colorbar для размещения относительно фигуры
cbar_ax = fig.add_axes([0.93, 0.15, 0.02, 0.7])
fig.colorbar(im2, cax=cbar_ax, label='Уровень конверсии (%)')
plt.suptitle('Сравнение конверсий по регионам')
plt.subplots_adjust(right=0.9) # Оставляем место для colorbar
plt.show()
Преимущества и недостатки данного подхода
- Преимущества:
- Простота реализации: не требует создания
ScalarMappable. - Интуитивно понятен.
- Простота реализации: не требует создания
- Недостатки:
- Требует предварительного вычисления
vminиvmaxпо всем данным. - Менее гибок, если требуется сложная настройка нормализации или цветовой карты.
- Требует предварительного вычисления
Способ 3: Использование matplotlib.gridspec для более сложной компоновки
GridSpec предоставляет более мощный контроль над расположением подграфиков и других элементов фигуры, чем стандартный plt.subplots. Это позволяет явно выделить область для colorbar.
Настройка компоновки с помощью GridSpec
GridSpec делит фигуру на сетку, и вы можете указать, какие ячейки этой сетки будет занимать каждый подграфик и colorbar.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
import matplotlib.cm as cm
import matplotlib.colors as mcolors
# Данные и параметры шкалы (как в Способе 1)
data1 = np.random.rand(10, 10) * 70
data2 = np.random.rand(10, 10) * 100
data3 = np.random.rand(10, 10) * 50
data4 = np.random.rand(10, 10) * 90
all_data = [data1, data2, data3, data4]
global_vmin, global_vmax = 0.0, 100.0
cmap = cm.viridis
norm = mcolors.Normalize(vmin=global_vmin, vmax=global_vmax)
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
fig = plt.figure(figsize=(10, 8))
# Создаем GridSpec: 2 строки, 2 колонки для графиков + 1 колонка для colorbar
# width_ratios управляет относительной шириной колонок
gs = gridspec.GridSpec(2, 3, width_ratios=[1, 1, 0.1], wspace=0.1, hspace=0.2)
# Создаем оси для подграфиков
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, 0])
ax4 = fig.add_subplot(gs[1, 1])
axes_list = [ax1, ax2, ax3, ax4]
# Создаем ось для colorbar, занимающую обе строки в последней колонке
cbar_ax = fig.add_subplot(gs[:, 2])
Размещение подграфиков и цветовой шкалы
Отрисовка данных происходит как обычно, а colorbar размещается в специально выделенной оси (cbar_ax).
# Отображение данных
for ax, data in zip(axes_list, all_data):
im = ax.imshow(data, cmap=cmap, norm=norm)
ax.set_title(f'Max: {data.max():.1f}')
ax.set_xticks([])
ax.set_yticks([])
# Добавление colorbar в выделенную ось
fig.colorbar(sm, cax=cbar_ax)
plt.suptitle('Использование GridSpec для общей шкалы')
plt.show()
Преимущества использования GridSpec
- Точный контроль: Позволяет точно позиционировать
colorbarотносительно подграфиков. - Сложные макеты: Идеально подходит для неравномерных сеток или когда нужно встроить
colorbarвнутрь макета сложным образом. - Явное разделение: Четко отделяет пространство для графиков от пространства для шкалы.
Рекомендации и заключение
Выбор подходящего метода в зависимости от задачи
ScalarMappable(Способ 1): Наиболее универсальный и рекомендуемый подход. Используйте его, если нужна гибкость, сложная нормализация или вы хотите явно управлять объектом, представляющим шкалу.- Явные
vmin/vmax(Способ 2): Хороший выбор для простых случаев, когда достаточно задать общие пределы и использовать стандартные функции отрисовки. Требует предварительного расчета пределов. GridSpec(Способ 3): Используйте, когда требуется точный контроль над компоновкой фигуры, особенно для сложных или нерегулярных макетов подграфиков.
Часто ScalarMappable используется совместно с GridSpec или ручным размещением оси для colorbar (fig.add_axes), чтобы добиться наилучшего результата.
Дополнительные возможности настройки цветовой шкалы
Объект colorbar имеет множество параметров для настройки внешнего вида:
-
label: Добавление подписи к шкале. -
orientation: Изменение ориентации (‘vertical’ или ‘horizontal’). -
shrink,aspect: Управление размером шкалы. -
ticks: Явное задание позиций делений. -
extend: Позволяет указать стрелками на шкале, что данные выходят за пределыvmin/vmax(значения ‘min’, ‘max’, ‘both’, ‘neither’). Это особенно полезно, когда нужно показать наличие выбросов, не искажая основную шкалу.# Пример использования extend norm_extended = mcolors.Normalize(vmin=10, vmax=90) sm_extended = cm.ScalarMappable(cmap=cmap, norm=norm_extended) sm_extended.set_array([]) # ... (plotting code using norm_extended) # fig.colorbar(sm_extended, cax=cbar_ax, label='Значение (с обрезкой)', extend='both')
Полезные ресурсы
Для более глубокого изучения рекомендуется обратиться к официальной документации Matplotlib, в частности к разделам, посвященным colorbar, ScalarMappable, Normalize и GridSpec. Примеры в галерее Matplotlib также могут быть очень полезны для понимания различных техник визуализации.
Создание единой цветовой шкалы — важный навык для представления сложных данных в понятной и профессиональной манере. Выбор правильного метода зависит от конкретной задачи и желаемого уровня контроля над визуализацией.