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

MatplotlibMatplotlibBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

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

Советы по работе с ВМ

После запуска ВМ нажмите в левом верхнем углу, чтобы переключиться на вкладку Ноутбук и получить доступ к Jupyter Notebook для практики.

Иногда вам может потребоваться подождать несколько секунд, пока Jupyter Notebook загрузится. Валидация операций не может быть автоматизирована из-за ограничений Jupyter Notebook.

Если вы столкнетесь с проблемами во время обучения, не стесняйтесь обращаться к Labby. Оставьте отзыв после занятия, и мы оперативно решим проблему для вас.

Простая категориальная тепловая карта

Начнем с определения некоторых данных. Нам нужен двумерный список или массив, который определяет данные для цветового кодирования. Затем нам также нужны два списка или массива категорий. Самая тепловая карта представляет собой график imshow с метками, установленными на категории. Мы будем использовать функцию text для маркировки самих данных, создав Text внутри каждой ячейки, показывающей значение этой ячейки.

import matplotlib.pyplot as plt
import numpy as np

vegetables = ["cucumber", "tomato", "lettuce", "asparagus", "potato", "wheat", "barley"]
farmers = ["Farmer Joe", "Upland Bros.", "Smith Gardening", "Agrifun", "Organiculture", "BioGoods Ltd.", "Cornylee Corp."]

harvest = np.array([[0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0],
                    [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0],
                    [1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0],
                    [0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0],
                    [0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0],
                    [1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1],
                    [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3]])

fig, ax = plt.subplots()
im = ax.imshow(harvest)

## Show all ticks and label them with the respective list entries
ax.set_xticks(np.arange(len(farmers)), labels=farmers)
ax.set_yticks(np.arange(len(vegetables)), labels=vegetables)

## Rotate the tick labels and set their alignment
plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")

## Loop over data dimensions and create text annotations
for i in range(len(vegetables)):
    for j in range(len(farmers)):
        text = ax.text(j, i, harvest[i, j], ha="center", va="center", color="w")

ax.set_title("Harvest of local farmers (in tons/year)")
fig.tight_layout()
plt.show()

Использование стиля кода вспомогательной функции

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

def heatmap(data, row_labels, col_labels, ax=None, cbar_kw=None, cbarlabel="", **kwargs):
    """
    Создайте тепловую карту из двумерного массива numpy и двух списков меток.

    Параметры
    ----------
    data
        Двумерный массив numpy размером (M, N).
    row_labels
        Список или массив длиной M с метками для строк.
    col_labels
        Список или массив длиной N с метками для столбцов.
    ax
        Экземпляр `matplotlib.axes.Axes`, на который будет нарисована тепловая карта. Если не указан, используется текущая ось или создается новая. Необязательный.
    cbar_kw
        Словарь с аргументами для `matplotlib.Figure.colorbar`. Необязательный.
    cbarlabel
        Метка для цветовой шкалы. Необязательный.
    **kwargs
        Все остальные аргументы передаются в `imshow`.
    """

    if ax is None:
        ax = plt.gca()

    if cbar_kw is None:
        cbar_kw = {}

    ## Нарисуйте тепловую карту
    im = ax.imshow(data, **kwargs)

    ## Создайте цветовую шкалу
    cbar = ax.figure.colorbar(im, ax=ax, **cbar_kw)
    cbar.ax.set_ylabel(cbarlabel, rotation=-90, va="bottom")

    ## Показать все деления и подписать их соответствующими записями из списка.
    ax.set_xticks(np.arange(data.shape[1]), labels=col_labels)
    ax.set_yticks(np.arange(data.shape[0]), labels=row_labels)

    ## Пусть горизонтальные метки делений появляются сверху.
    ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)

    ## Поверните метки делений и задайте их выравнивание.
    plt.setp(ax.get_xticklabels(), rotation=-30, ha="right", rotation_mode="anchor")

    ## Отключите линии обводки и создайте белую сетку.
    ax.spines[:].set_visible(False)
    ax.set_xticks(np.arange(data.shape[1]+1)-.5, minor=True)
    ax.set_yticks(np.arange(data.shape[0]+1)-.5, minor=True)
    ax.grid(which="minor", color="w", linestyle='-', linewidth=3)
    ax.tick_params(which="minor", bottom=False, left=False)

    return im, cbar


def annotate_heatmap(im, data=None, valfmt="{x:.2f}", textcolors=("black", "white"), threshold=None, **textkw):
    """
    Функция для аннотации тепловой карты.

    Параметры
    ----------
    im
        AxesImage, который нужно подписать.
    data
        Данные, используемые для аннотации. Если None, используются данные изображения. Необязательный.
    valfmt
        Формат аннотаций внутри тепловой карты. Это должно быть либо строкой форматирования, например, "$ {x:.2f}", либо `matplotlib.ticker.Formatter`. Необязательный.
    textcolors
        Пара цветов. Первый используется для значений ниже порога, второй для значений выше порога. Необязательный.
    threshold
        Значение в единицах данных, по которому применяются цвета из textcolors. Если None (по умолчанию), используется середина цветовой карты в качестве разделителя. Необязательный.
    **kwargs
        Все остальные аргументы передаются в каждый вызов `text`, используемый для создания текстовых меток.
    """

    if not isinstance(data, (list, np.ndarray)):
        data = im.get_array()

    ## Нормализуйте порог к цветовому диапазону изображения.
    if threshold is not None:
        threshold = im.norm(threshold)
    else:
        threshold = im.norm(data.max())/2.

    ## Задайте стандартное выравнивание по центру, но позволяет переопределить его textkw.
    kw = dict(horizontalalignment="center", verticalalignment="center")
    kw.update(textkw)

    ## Получите форматтер, если передана строка
    if isinstance(valfmt, str):
        valfmt = matplotlib.ticker.StrMethodFormatter(valfmt)

    ## Переберите данные и создайте `Text` для каждого "пикселя".
    ## Измените цвет текста в зависимости от данных.
    texts = []
    for i in range(data.shape[0]):
        for j in range(data.shape[1]):
            kw.update(color=textcolors[int(im.norm(data[i, j]) > threshold)])
            text = im.axes.text(j, i, valfmt(data[i, j], None), **kw)
            texts.append(text)

    return texts

Применение функции

Теперь, когда у нас есть функции, мы можем использовать их для создания тепловой карты с аннотациями. Мы создаем новый набор данных, передаем дополнительные аргументы в imshow, используем целый формат для аннотаций и задаем некоторые цвета. Мы также скрываем диагональные элементы (которые все равны 1), используя matplotlib.ticker.FuncFormatter.

data = np.random.randint(2, 100, size=(7, 7))
y = [f"Book {i}" for i in range(1, 8)]
x = [f"Store {i}" for i in list("ABCDEFG")]

fig, ax = plt.subplots()
im, _ = heatmap(data, y, x, ax=ax, vmin=0, cmap="magma_r", cbarlabel="weekly sold copies")
annotate_heatmap(im, valfmt="{x:d}", size=7, threshold=20, textcolors=("red", "white"))

def func(x, pos):
    return f"{x:.2f}".replace("0.", ".").replace("1.00", "")

annotate_heatmap(im, valfmt=matplotlib.ticker.FuncFormatter(func), size=7)

Более сложные примеры тепловых карточек

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

np.random.seed(19680801)

fig, ((ax, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(8, 6))

## Повторите вышеприведенный пример с другим размером шрифта и цветовой картой.

im, _ = heatmap(harvest, vegetables, farmers, ax=ax, cmap="Wistia", cbarlabel="harvest [t/year]")
annotate_heatmap(im, valfmt="{x:.1f}", size=7)

## Иногда даже сами данные являются категориальными. Здесь мы используем `matplotlib.colors.BoundaryNorm`, чтобы разделить данные на классы и использовать это для цветовой кодировки графика, а также для получения меток классов из массива классов.

data = np.random.randn(6, 6)
y = [f"Prod. {i}" for i in range(10, 70, 10)]
x = [f"Cycle {i}" for i in range(1, 7)]

qrates = list("ABCDEFG")
norm = matplotlib.colors.BoundaryNorm(np.linspace(-3.5, 3.5, 8), 7)
fmt = matplotlib.ticker.FuncFormatter(lambda x, pos: qrates[::-1][norm(x)])

im, _ = heatmap(data, y, x, ax=ax3, cmap=mpl.colormaps["PiYG"].resampled(7), norm=norm, cbar_kw=dict(ticks=np.arange(-3, 4), format=fmt), cbarlabel="Quality Rating")
annotate_heatmap(im, valfmt=fmt, size=9, fontweight="bold", threshold=-1, textcolors=("red", "black"))

## Мы можем красиво нарисовать матрицу корреляции. Поскольку она ограничена значениями от -1 до 1, мы используем эти значения в качестве vmin и vmax.

corr_matrix = np.corrcoef(harvest)
im, _ = heatmap(corr_matrix, vegetables, vegetables, ax=ax4, cmap="PuOr", vmin=-1, vmax=1, cbarlabel="correlation coeff.")
annotate_heatmap(im, valfmt=matplotlib.ticker.FuncFormatter(func), size=7)

plt.tight_layout()
plt.show()

Резюме

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