Matplotlib: Как выбрать цвет графика в зависимости от значения?

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

Обзор возможностей Matplotlib для настройки цветов

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

Необходимость выбора цвета в зависимости от значения данных

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

Основные методы задания цветов в Matplotlib

Прежде чем переходить к сложным случаям, вспомним базовые способы задания цвета в Matplotlib.

Использование стандартных цветовых названий

Самый простой способ — использовать предопределенные имена цветов. Matplotlib поддерживает базовые цвета (‘red’, ‘green’, ‘blue’, ‘yellow’, ‘black’, ‘white’), сокращенные обозначения (‘r’, ‘g’, ‘b’, ‘y’, ‘k’, ‘w’) и расширенный набор CSS-имен (‘orange’, ‘purple’, ‘lime’, etc.).

import matplotlib.pyplot as plt
import numpy as np

# Пример данных
x: np.ndarray = np.arange(10)
y: np.ndarray = np.random.rand(10) * 10

# Построение графика с использованием именованного цвета
plt.plot(x, y, color='crimson', marker='o')
plt.title('График с цветом crimson')
plt.show()

Задание цветов в формате HEX

Для более точного контроля цвета можно использовать шестнадцатеричный формат HEX, широко применяемый в веб-дизайне. Цвет задается как строка вида ‘#RRGGBB’, где RR, GG, BB — шестнадцатеричные значения интенсивности красного, зеленого и синего каналов соответственно.

# Построение графика с использованием HEX-цвета
plt.plot(x, y, color='#34A853', linestyle='--') # Зеленый цвет Google
plt.title('График с HEX цветом #34A853')
plt.show()

Цветовые модели RGB и RGBA

Matplotlib также поддерживает задание цвета в виде кортежей RGB или RGBA. Значения каналов (Red, Green, Blue) задаются как числа с плавающей точкой в диапазоне [0, 1]. RGBA добавляет четвертый канал — Alpha (прозрачность), также в диапазоне [0, 1], где 0 — полностью прозрачный, 1 — полностью непрозрачный.

# RGB цвет (синий)
rgb_color: tuple[float, float, float] = (0.1, 0.2, 0.8)
# RGBA цвет (полупрозрачный красный)
rgba_color: tuple[float, float, float, float] = (0.8, 0.1, 0.1, 0.6)

plt.scatter(x, y, color=rgb_color, s=100, label='RGB')
plt.scatter(x, y + 1, color=rgba_color, s=100, label='RGBA') # Смещаем для наглядности
plt.title('Графики с RGB и RGBA цветами')
plt.legend()
plt.show()

Выбор цвета графика на основе значения: простые примеры

Перейдем к основной теме: как заставить цвет элемента графика зависеть от связанных с ним данных.

Окрашивание точек на графике рассеяния в зависимости от значения координаты Z

Предположим, у нас есть данные по эффективности рекламных кампаний: затраты (X), количество кликов (Y) и коэффициент конверсии (Z). Мы хотим визуализировать зависимость кликов от затрат, при этом цвет точки должен отражать коэффициент конверсии.

import matplotlib.pyplot as plt
import numpy as np
from typing import List

# Данные рекламных кампаний (пример)
campaign_spend: np.ndarray = np.random.rand(50) * 1000 # Затраты
campaign_clicks: np.ndarray = campaign_spend * (np.random.rand(50) * 0.1 + 0.05) # Клики
conversion_rate: np.ndarray = np.random.rand(50) * 5 # Коэффициент конверсии (%)

def get_color_based_on_conversion(rate: float) -> str:
    """Возвращает цвет в зависимости от коэффициента конверсии."""
    if rate > 3.5:
        return 'green' # Высокая конверсия
    elif rate > 1.5:
        return 'orange' # Средняя конверсия
    else:
        return 'red'   # Низкая конверсия

# Получаем список цветов для каждой точки
point_colors: List[str] = [get_color_based_on_conversion(cr) for cr in conversion_rate]

plt.figure(figsize=(10, 6))
plt.scatter(campaign_spend, campaign_clicks, c=point_colors, alpha=0.7)
plt.xlabel('Затраты (у.е.)')
plt.ylabel('Количество кликов')
plt.title('Зависимость кликов от затрат (цвет = коэф. конверсии)')
plt.grid(True)
plt.show()

В этом примере мы создали функцию get_color_based_on_conversion, которая возвращает определенный цвет (‘red’, ‘orange’, ‘green’) в зависимости от значения conversion_rate. Затем мы применили эту функцию к каждому значению конверсии, чтобы получить список point_colors, который передали в параметр c функции plt.scatter.

Изменение цвета линии графика в зависимости от значения Y

Иногда требуется изменить цвет линии графика, когда ее значение пересекает определенный порог. Это сложнее, так как plt.plot по умолчанию принимает один цвет для всей линии. Решение — разбить данные на сегменты и построить каждый сегмент отдельно своим цветом.

import matplotlib.pyplot as plt
import numpy as np
from typing import Tuple

# Пример данных: Трафик веб-сайта
days: np.ndarray = np.arange(30)
traffic: np.ndarray = 1000 + 50 * np.sin(days / 3) + np.random.randn(30) * 30
traffic_threshold: float = 1000.0 # Пороговое значение

def plot_segmented_line(x: np.ndarray, y: np.ndarray, threshold: float) -> None:
    """Строит линию, меняя цвет при пересечении порога."""
    plt.figure(figsize=(12, 6))

    # Находим индексы, где значение выше или ниже порога
    above_threshold: np.ndarray = np.where(y >= threshold, y, np.nan)
    below_threshold: np.ndarray = np.where(y < threshold, y, np.nan)

    # Строим две линии: одну для значений выше порога, другую - ниже
    plt.plot(x, above_threshold, color='green', label=f'>= {threshold}')
    plt.plot(x, below_threshold, color='red', label=f'< {threshold}')

    # Добавляем горизонтальную линию порога
    plt.axhline(threshold, color='gray', linestyle='--', label='Порог')

    plt.xlabel('День')
    plt.ylabel('Трафик')
    plt.title('Динамика трафика с цветовым кодированием по порогу')
    plt.legend()
    plt.grid(True)
    plt.show()

plot_segmented_line(days, traffic, traffic_threshold)

Здесь мы использовали np.where для создания двух массивов: один содержит значения y там, где они выше или равны порогу (и np.nan в остальных местах), а другой — где они ниже порога. Построив обе линии, мы получаем эффект изменения цвета.

Использование цветовых карт (Colormaps) для отображения значений

Для плавного отображения непрерывных данных идеально подходят цветовые карты (colormaps).

Введение в цветовые карты и их типы

Цветовая карта — это предопределенный градиент цветов, который сопоставляет числовые значения (обычно нормализованные в диапазоне [0, 1]) с цветами. Matplotlib предлагает множество встроенных карт:

Реклама
  • Последовательные (Sequential): Для данных, изменяющихся от низкого к высокому (‘viridis’, ‘plasma’, ‘magma’, ‘cividis’, ‘Greys’, ‘Blues’). Идеальны для отображения интенсивности.
  • Расходящиеся (Diverging): Для данных, имеющих критическое среднее значение (например, ноль) и отклонения в обе стороны (‘coolwarm’, ‘bwr’ (blue-white-red), ‘seismic’). Хороши для визуализации разницы или корреляции.
  • Циклические (Cyclic): Для данных, имеющих циклический характер (‘twilight’, ‘hsv’).
  • Качественные (Qualitative): Набор дискретных цветов для категориальных данных (‘tab10’, ‘Set3’, ‘Paired’).

Применение цветовых карт к графикам рассеяния и контурным графикам

Цветовые карты легко применить, передав массив значений в параметр c и указав имя карты в параметре cmap.

import matplotlib.pyplot as plt
import matplotlib.cm as cm # Модуль для работы с colormaps
import numpy as np

# Данные: X, Y координаты и значение Z
x_coords: np.ndarray = np.random.rand(100)
y_coords: np.ndarray = np.random.rand(100)
z_values: np.ndarray = x_coords * y_coords # Пример значения от 0 до ~1

plt.figure(figsize=(8, 6))
scatter = plt.scatter(x_coords, y_coords, c=z_values, cmap='viridis', s=50)

# Добавляем цветовую шкалу (colorbar)
plt.colorbar(scatter, label='Значение Z (x * y)')

plt.xlabel('X координата')
plt.ylabel('Y координата')
plt.title('График рассеяния с цветовой картой viridis')
plt.show()

Нормализация данных для эффективного использования цветовых карт

Цветовые карты обычно ожидают данные в диапазоне [0, 1]. Если ваши данные выходят за эти пределы, Matplotlib автоматически нормализует их. Однако для явного контроля можно использовать класс matplotlib.colors.Normalize.

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

# Данные с произвольным диапазоном
data_values: np.ndarray = np.random.randn(100) * 50 + 100 # от ~ -50 до ~ 250
x_coords: np.ndarray = np.arange(100)

# Создаем нормализатор
# Можно задать vmin и vmax явно, или они будут вычислены по данным
normalizer = mcolors.Normalize(vmin=np.min(data_values), vmax=np.max(data_values))
# Или для расходящейся карты, центрируем относительно нуля:
# normalizer = mcolors.TwoSlopeNorm(vmin=np.min(data_values), vcenter=0, vmax=np.max(data_values))

plt.figure(figsize=(10, 6))
scatter = plt.scatter(x_coords, data_values, c=data_values, cmap='coolwarm', norm=normalizer)
plt.colorbar(scatter, label='Нормализованное значение')
plt.title('График с явно нормализованными данными и картой coolwarm')
plt.show()

Нормализация гарантирует, что весь диапазон цветов карты используется для отображения диапазона ваших данных.

Продвинутые техники и примеры

Рассмотрим более сложные сценарии управления цветом.

Создание пользовательских цветовых карт

Если стандартные карты не подходят (например, нужно использовать корпоративные цвета), можно создать свою карту с помощью LinearSegmentedColormap (для градиентов) или ListedColormap (для дискретных цветов).

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

# Создание пользовательской карты из списка цветов (дискретная)
custom_colors: list[str] = ['#E63946', '#F1FAEE', '#A8DADC', '#457B9D', '#1D3557'] # Пример палитры
custom_cmap_listed = mcolors.ListedColormap(custom_colors, name='custom_listed')

# Создание пользовательской карты с градиентами (сегментированная)
# Формат: словарь с ключами 'red', 'green', 'blue'
# Значения - списки кортежей (x, y0, y1), где x - позиция [0,1], y0/y1 - значения цвета на этой позиции
cdict = {
    'red':   [(0.0,  0.8, 0.8),  # Красный в начале (позиция 0.0) = 0.8
              (0.5,  1.0, 1.0),  # Красный в середине (0.5) = 1.0
              (1.0,  0.2, 0.2)], # Красный в конце (1.0) = 0.2
    'green': [(0.0,  0.2, 0.2),
              (0.5,  1.0, 1.0),
              (1.0,  0.2, 0.2)],
    'blue':  [(0.0,  0.2, 0.2),
              (0.5,  0.5, 0.5),
              (1.0,  0.8, 0.8)]
}
custom_cmap_segmented = mcolors.LinearSegmentedColormap('custom_segmented', cdict)

# Пример использования
x: np.ndarray = np.arange(100)
y: np.ndarray = np.sin(x / 10)
z: np.ndarray = np.cos(x / 10)

plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.scatter(x, y, c=z, cmap=custom_cmap_listed, s=50)
plt.title('Listed Colormap')

plt.subplot(1, 2, 2)
plt.scatter(x, y, c=z, cmap=custom_cmap_segmented, s=50)
plt.title('LinearSegmented Colormap')

plt.tight_layout()
plt.show()

Использование нескольких цветовых карт на одном графике

Хотя это может усложнить восприятие, иногда требуется использовать разные цветовые карты для разных наборов данных на одном графике. Это достигается путем построения каждого набора данных с собственной настройкой cmap и norm.

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

# Данные 1
x1, y1 = np.random.rand(50), np.random.rand(50)
c1 = x1 + y1 # Значения для первой карты

# Данные 2
x2, y2 = np.random.rand(50) + 1, np.random.rand(50)
c2 = x2 - y2 # Значения для второй карты

plt.figure(figsize=(10, 7))

# Нормализаторы для каждого набора данных
norm1 = mcolors.Normalize(vmin=np.min(c1), vmax=np.max(c1))
norm2 = mcolors.Normalize(vmin=np.min(c2), vmax=np.max(c2))

# График 1
sc1 = plt.scatter(x1, y1, c=c1, cmap='Blues', norm=norm1, s=60, label='Набор 1 (Blues)')
plt.colorbar(sc1, label='Значение C1 (x+y)', orientation='horizontal', pad=0.1)

# График 2
sc2 = plt.scatter(x2, y2, c=c2, cmap='Oranges', norm=norm2, s=60, marker='^', label='Набор 2 (Oranges)')
plt.colorbar(sc2, label='Значение C2 (x-y)', orientation='vertical', pad=0.05)

plt.title('Два набора данных с разными цветовыми картами')
plt.legend()
plt.show()

Важно аккуратно размещать colorbar’ы, чтобы они не перекрывались и были понятны.

Применение условных выражений для выбора цвета

Помимо простых функций или np.where, можно использовать более сложные условные конструкции, например, list comprehensions с тернарными операторами или np.select, для выбора цвета на основе нескольких условий.

import matplotlib.pyplot as plt
import numpy as np

# Пример: Результаты A/B теста
# variant: 0=A, 1=B
# conversion: True/False
# significance: p-value
variants: np.ndarray = np.random.randint(0, 2, size=100)
conversions: np.ndarray = np.random.rand(100) > 0.6
p_values: np.ndarray = np.random.rand(100) * 0.2

x_axis = np.arange(100)

def select_ab_color(variant: int, conversion: bool, p_value: float) -> str:
    """Выбирает цвет точки по результатам A/B теста."""
    if p_value < 0.05: # Статистически значимо
        if variant == 1 and conversion: return 'darkgreen' # Успешный вариант B
        if variant == 0 and conversion: return 'lime'      # Успешный вариант A
        if variant == 1 and not conversion: return 'darkred' # Неуспешный вариант B
        if variant == 0 and not conversion: return 'salmon'    # Неуспешный вариант A
    else: # Незначимо
        if conversion: return 'blue' # Конверсия есть, но неясно из-за чего
        else: return 'gray'         # Нет конверсии

colors: list[str] = [select_ab_color(v, c, p) 
                     for v, c, p in zip(variants, conversions, p_values)]

plt.figure(figsize=(12, 6))
plt.scatter(x_axis, p_values, c=colors, s=50, alpha=0.8)
plt.xlabel('Номер наблюдения')
plt.ylabel('P-value')
plt.title('Результаты A/B теста (цвет зависит от варианта, конверсии и значимости)')
# Добавить легенду вручную, если нужно
import matplotlib.patches as mpatches
legend_elements = [
    mpatches.Patch(color='darkgreen', label='B, Конверсия, p<0.05'),
    mpatches.Patch(color='lime', label='A, Конверсия, p<0.05'),
    mpatches.Patch(color='darkred', label='B, Нет конверсии, p<0.05'),
    mpatches.Patch(color='salmon', label='A, Нет конверсии, p<0.05'),
    mpatches.Patch(color='blue', label='Конверсия, p>=0.05'),
    mpatches.Patch(color='gray', label='Нет конверсии, p>=0.05'),
]
plt.legend(handles=legend_elements, bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout(rect=[0, 0, 0.85, 1]) # Оставить место для легенды
plt.show()

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


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