Привет, Хабр!

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

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

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

Данный материал представляется собой пошаговую шпаргалку‑инструкцию с иллюстрациями того, как можно автоматизировать дизайн результатов, которые можно представить в виде матриц (матрицы корреляций, матрицы ошибок). Как результат удивить коллег и тем самым освободить себя от рутинных занятий.

Дисклеймер: данная статья не является истинной в последней инстанции. Она не гарантирует 100% полноту данных по каждому из описанных действий. Статья направлена на начинающих DS и аналитиков данных.

Если у вас есть предложение, как улучшить статью или вы заметили ошибку/опечатку, пожалуйста напишите об этом в комментариях!

Итак, приступим...


Шаг нулевой. Построение и визуализация

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

Построим матрицу ошибок используя confusion_matrix из scikit-learn. Предварительно ее отнормируем, используя атрибут normalize. Построим фундамент нашей статьи, визуализировав матрицу с использованием heatmap из seaborn.

Код
from sklearn.metrics import confusion_matrix

y_true = ... # тут вектор разметки
y_pred = ... # тут вектор предсказания
cm = confusion_matrix(y_true, y_pred,normalize='true')
sns.heatmap(cm)

самая базовая матрица
самая базовая матрица

Теперь начнем путь к созданию красивой и продающей матрицы.


Шаг первый: Аннотации и Цветовая шкала

Первый шаг к созданию красивой матрицы - это добавление аннотаций и отключение цветовой шкалы. Аннотации позволяют нам видеть точные значения в каждой ячейке матрицы. С помощью атрибутаannot методаheatmap мы можем автоматически вставлять значения в ячейки.

Код
sns.heatmap(cm, annot=True)

не очень информативно
не очень информативно

Чтобы аннотации были более читаемыми, можно настроить форматирование чисел с помощью параметра fmt.

  • Можно округлить до второго знака после запятой

Код
sns.heatmap(cm, annot=True, fmt='.2f')

намного лучше!
намного лучше!
  • Можно перевести в проценты

Код
sns.heatmap(cm, annot=True, fmt='.0%')

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

Код
sns.heatmap(cm, annot=True, fmt='.2f', cbar=False)

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


Шаг второй: Подписи и Размеры

Самый простой вариант вернуть пропорции ячейкам в матрице - использование атрибута square.

Код
sns.heatmap(cm, annot=True, square=True, fmt='.2f', cbar=False)

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

Здесь можно еще поиграться с шириной границ ячеек, за которую отвечает атрибут linewidth. При этом с атрибутом square пропорции сохраняются.

Код
sns.heatmap(cm, annot=True, square=True, 
            fmt='.2f', cbar=False, linewidth=0.1)

Более сложный вариант работы с размером - использование обертки из фигуры matplotlib для контроля размера всей матрицы. Также данный вариант необходим при добавлении названия и подписей осей.

Для контроля размера создадим объект, вызвав метод subplots и в качестве атрибута figsize подадим желаемый размер нашей матрицы. Также добавим заголовок методом title и подпишем оси X и Y методами xlabel и ylabel соответственно.

Код
#размер фигуры matplotlib figsize
figsize = (8,8)

fig, ax = plt.subplots(figsize=figsize)
plt.title('Confusion Matrix')
hmap = sns.heatmap(cm, ax=ax, annot=True, fmt='.1%', cbar=False)
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()

Для изменения названия классов в нашем случае используются параметры heatmap xticklabels и yticklabels. Переименуем i-й класс в Class i:

Код
tick_labels = [f'{i} Class' for i in range(0,10)]
figsize = (8,8)

fig, ax = plt.subplots(figsize=figsize)
plt.title('Confusion Matrix')
hmap = sns.heatmap(cm,
                   ax=ax,
                   annot=True,
                   square=True,
                   fmt='.1%',
                   cbar=False,
                   xticklabels=tick_labels,
                   yticklabels=tick_labels)
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()

Важно понимать, что названия могут быть разными по осям, но список названий по каждой из осей должен быть равен количеству значений!

Теперь, помимо размера всей матрицы, мы можем добавить возможность изменять размеры всей текстовой информации на матрице.

Для этого нам надо:

  • для методов ylabel, xlabel и title достаточно добавление атрибута fontsize

  • для аннотации необходимо в heatmap добавить в атрибут annot_kws (который представляет собой словарь параметров аннотации) добавить пару 'size':'value'

  • для значений на осях, необходимо переустановить подписи используя методы set_xticklabels и set_yticklabels, в которых необходимо поставить значения атрибута fontsize

Таким образом, получаем:

Код
tick_labels = [f'{i} Class' for i in range(0,10)]
figsize = (8,8)
fontsize_ticks = 9
fontsize_ax = 8
fontsize_title = 12
fontsize_annotation = 6

fig, ax = plt.subplots(figsize=figsize)
plt.title('Confusion Matrix', fontsize=fontsize_title)
hmap = sns.heatmap(cm,
                   ax=ax,
                   annot=True,
                   square=True,
                   fmt='.1%',
                   cbar=False,
                   annot_kws={'size':str(fontsize_annotation)},
                   xticklabels=tick_labels,
                   yticklabels=tick_labels)
plt.ylabel('Actual', fontsize=fontsize_ax)
plt.xlabel('Predicted', fontsize=fontsize_ax)
hmap.set_xticklabels(hm.get_xmajorticklabels(), fontsize=fontsize_ticks)
hmap.set_yticklabels(hm.get_xmajorticklabels(), fontsize=fontsize_ticks)
plt.show()

все размеры действительно разные
все размеры действительно разные

Теперь, когда мы настроили все размеры, можем перейти к изменению цвета и шрифта содержимого матрицы.


Шаг третий: Цвет и Шрифт

Цветовая палитра - это еще один важный аспект дизайна. С помощью атрибута cmap мы можем настроить цветовую гамму нашей матрицы. Какие есть, как правильно их использовать и даже как создать свою, есть в официальной документации seaborn и matplolib. Там больше картинок, поэтому заострять внимание не буду и пойдем дальше.

Для примера, возьмем непрерывную сине-зеленую палитру GnBu и применим ее к нашей матрице.

Код
tick_labels = [f'{i} Class' for i in range(0,10)]
figsize = (8,8)
ticks_size = 9
fontsize_ax = 9
fontsize_title = 12
annotation_size = 8

fig, ax = plt.subplots(figsize=figsize)
plt.title('Confusion Matrix', fontsize=fontsize_title)
hmap = sns.heatmap(cm,
                     ax=ax,
                     annot=True,
                     square=True,
                     fmt='.1%',
                     cmap='GnBu',
                     cbar=False,
                     annot_kws={'size':str(annotation_size)},
                     xticklabels=tick_labels,
                     yticklabels=tick_labels)
plt.ylabel('Actual', fontsize=fontsize_ax)
plt.xlabel('Predicted', fontsize=fontsize_ax)
hmap.set_xticklabels(hm.get_xmajorticklabels(), fontsize=ticks_size)
hmap.set_yticklabels(hm.get_ymajorticklabels(), fontsize=ticks_size)

plt.show()

Выглядит лучше!
Выглядит лучше!

Тут уже выбор на свое усмотрение, но можно даже попробовать сделать палитру, соответствующую брендбуку вашей компании.

Теперь осталось самое интересное - изменение шрифта.

Прежде всего, проверить весь доступный список шрифтов можно в Python 3.х c помощью следующих строчек кода:

Код
from matplotlib.font_manager import get_font_names

sorted(get_font_names())

Здесь методом проб и ошибок мы можем попробовать все шрифты. Либо можем поставить свой в соответствии с брендбуком. И да, мы тоже можем сделать это с помощью matplolib, для этого нужен лишь файл с шрифтами, например в популярном расширении .ttf. Для примера поставим шрифт TeX Gyre Adventor, используя всего одну строчку кода.

Код
import matplotlib

matplotlib.font_manager.fontManager.addfont('texgyreadventor.ttf')

Для применения шрифта к нашей матрице, необходимо в начале перед кодом построения вызвать метод .set() seaborn с атрибутом font.

После получения изображения матрицы рекомендуется вернуть настройки seaborn по умолчанию, используя метод .reset_orig()

Код
sns.set(font='TeX Gyre Adventor')

tick_labels = [f'{i} Class' for i in range(0,10)]
figsize = (8,8)
ticks_size = 9
fontsize_ax = 9
fontsize_title = 12
annotation_size = 8

fig, ax = plt.subplots(figsize=figsize)
plt.title('Confusion Matrix', fontsize=fontsize_title)
hmap = sns.heatmap(cm,
                   ax=ax,
                   annot=True,
                   square=True,
                   fmt='.1%',
                   cmap='GnBu',
                   cbar=False,
                   annot_kws={'size':str(annotation_size)},
                   xticklabels=tick_labels,
                   yticklabels=tick_labels)
plt.ylabel('Actual', fontsize=fontsize_ax)
plt.xlabel('Predicted', fontsize=fontsize_ax)
hmap.set_xticklabels(hm.get_xmajorticklabels(), fontsize=ticks_size)
hmap.set_yticklabels(hm.get_ymajorticklabels(), fontsize=ticks_size)

plt.show()
sns.reset_orig()

Добавив выравнивание текста по осям с помощью атрибутов ha (horizontal alignment) и va (vertical alignment) и поворот текста с помощью атрибута rotation, мы можем добиться наилучшего результата.

Код
sns.set(font='TeX Gyre Adventor')

tick_labels = [f'{i} Class' for i in range(0,10)]
figsize = (8,8)
ticks_size = 9
fontsize_ax = 9
fontsize_title = 12
annotation_size = 8

fig, ax = plt.subplots(figsize=figsize)
plt.title('Confusion Matrix', fontsize=fontsize_title)
hmap = sns.heatmap(cm,
                     ax=ax,
                     annot=True,
                     square=True,
                     fmt='.1%',
                     cmap='GnBu',
                     cbar=False,
                     annot_kws={'size':str(annotation_size)},
                     xticklabels=tick_labels,
                     yticklabels=tick_labels)
plt.ylabel('Actual', fontsize=fontsize_ax, ha='center')
plt.xlabel('Predicted', fontsize=fontsize_ax, va='center')
hmap.set_xticklabels(hm.get_xmajorticklabels(), fontsize=ticks_size, rotation=45)
hmap.set_yticklabels(hm.get_ymajorticklabels(), fontsize=ticks_size)

plt.show()
sns.reset_orig()

После того как мы настроили свою матрицу и она выглядит так, как хотели, осталось дело за малым - сохранить результат работы.


Шаг последний: Сохранение

Здесь не так много что есть сказать. Необходимо использовать функцию savefig из библиотеки Matplotlib, чтобы сохранить матрицу в различных форматах, таких как PNG или JPEG или даже PDF.

Однако при сохранении не стоит забывать о параметре dpi, который отвечает за разрешение изображения (dots per inch - точек на дюйм). Высокое разрешение (большое значение dpi) обеспечит четкость и детализацию визуализации, что особенно важно, если планируете использовать изображение в печатных материалах или презентациях. С другой стороны, слишком высокое разрешение может привести к увеличению размера файла, поэтому баланс между качеством и размером тоже имеет значение.

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

Лично я не рекомендую использовать dpi > 300.

При записи файла рекомендуется использовать метод .tight_layout(), который уменьшает отступы при сохранении.

Таким образом:

Финальный код
sns.set(font='TeX Gyre Adventor')

tick_labels = [f'{i} Class' for i in range(0,10)]
figsize = (5,5)
ticks_size = 5
fontsize_ax = 5
title = 10
annotation_size = 5

fig, ax = plt.subplots(figsize=figsize)

plt.title('Confusion Matrix', fontsize=title)

hm = sns.heatmap(cm,
                     ax=ax,
                     annot=True,
                     square=True,
                     fmt='.1%',
                     cmap = 'GnBu',
                     cbar=False,
                     annot_kws={'size':str(annotation_size)},
                     xticklabels = tick_labels,
                     yticklabels = tick_labels)

plt.ylabel('Actual', fontsize = fontsize_ax, ha='center')
plt.xlabel('Predicted', fontsize = fontsize_ax)

hm.set_xticklabels(hm.get_xmajorticklabels(), 
                   fontsize=ticks_size)
hm.set_yticklabels(hm.get_xmajorticklabels(), 
                   fontsize=ticks_size, va='center')

plt.tight_layout()
plt.savefig('name.png',dpi=300)
plt.show()
sns.reset_orig()

Заключение

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

Надеюсь статья будет кому-либо полезна.

Материал и код подготовил: Андрей Дзись

Подписывайтесь на мой телеграм канал про DS!

Комментарии (0)