2025, Dec 11 09:02

Общие подписи осей для ConfusionMatrixDisplay в matplotlib

Убираем дубликаты подписей осей у ConfusionMatrixDisplay в matplotlib: очищаем локальные подписи, добавляем fig.supxlabel/fig.supylabel, настраиваем tick_params

Когда вы размещаете несколько графиков scikit-learn ConfusionMatrixDisplay в сетке подграфиков matplotlib, каждая панель рисует собственные подписи осей. Из‑за этого общие подписи, добавленные через fig.supxlabel и fig.supylabel, выглядят как дубликаты, а не как действительно совместно используемые. К тому же параметры rcParams для размеров подписей делений могут не повлиять на то, как ConfusionMatrixDisplay выводит текст, в отличие от ваших ожиданий. Цель проста: четыре матрицы ошибок, одна общая «Predicted label» внизу, одна общая «True label» слева и читабельные размеры подписей.

Пример, иллюстрирующий проблему

Фрагмент ниже строит сетку 2×2 из матриц ошибок с заголовками. Он пытается управлять размером подписей делений через rc и полагаться на значения по умолчанию для каждой оси, из‑за чего подписи осей повторяются вокруг всех четырёх панелей.

import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay, confusion_matrix

# Предполагается, что truth_vec и bin_preds уже определены и согласованы
# truth_vec: одномерная array-like структура с истинными метками
# bin_preds: словарь с ключами 'NSflow', 'capacity', 'cost', 'efficiency' и одномерными array-like предсказаниями

mpl.rc('xtick', labelsize=6)
mpl.rc('ytick', labelsize=6)

chart, panels = plt.subplots(2, 2, figsize=(8, 8), sharex=True, sharey=True)
chart.suptitle('Confusion Matrix')

panels[0, 0].set_title('NS Flow', fontsize=8)
panels[0, 1].set_title('Capacity', fontsize=8)
panels[1, 0].set_title('Cost', fontsize=8)
panels[1, 1].set_title('Efficiency', fontsize=8)

ConfusionMatrixDisplay(
    confusion_matrix=confusion_matrix(truth_vec, bin_preds['NSflow']),
    display_labels=[False, True]
).plot(ax=panels[0, 0])

ConfusionMatrixDisplay(
    confusion_matrix=confusion_matrix(truth_vec, bin_preds['capacity']),
    display_labels=[False, True]
).plot(ax=panels[0, 1])

ConfusionMatrixDisplay(
    confusion_matrix=confusion_matrix(truth_vec, bin_preds['cost']),
    display_labels=[False, True]
).plot(ax=panels[1, 0])

ConfusionMatrixDisplay(
    confusion_matrix=confusion_matrix(truth_vec, bin_preds['efficiency']),
    display_labels=[False, True]
).plot(ax=panels[1, 1])

plt.show()

Что происходит на самом деле

ConfusionMatrixDisplay.plot записывает подписи осей прямо на каждую ось, где рисует. Методы fig.supxlabel и fig.supylabel не переопределяют и не убирают эти локальные подписи; они лишь добавляют к фигуре общие надписи. В итоге у вас получается пять подписей: одна общая и четыре локальные копии. Аналогично, полагаться только на rcParams для размеров подписей делений недостаточно — то, что уже установил ConfusionMatrixDisplay, так просто не изменится; нужно настраивать каждую ось явно.

Решение: очистить локальные подписи и добавить общие

Обходной путь прост. Сначала отрисуйте каждую матрицу ошибок, но очистите её локальные подписи по x и y. Затем добавьте общие подписи через fig.supxlabel и fig.supylabel. Если нужно уменьшить подписи делений, вызовите tick_params для каждой оси. Ниже — полный пример, который делает именно это и заодно исключает лишние цветовые шкалы для каждой панели.

import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay, confusion_matrix

# Предполагается, что truth_vec и bin_preds уже определены и согласованы
# truth_vec: одномерная array-like структура с истинными метками
# bin_preds: словарь с ключами 'NSflow', 'capacity', 'cost', 'efficiency' и одномерными array-like предсказаниями

chart, panels = plt.subplots(2, 2, figsize=(8, 8), sharex=True, sharey=True)
chart.suptitle('Confusion Matrix', fontsize=14)

panel_headers = ['NS Flow', 'Capacity', 'Cost', 'Efficiency']
pred_fields = ['NSflow', 'capacity', 'cost', 'efficiency']

for idx, cell_ax in enumerate(panels.flat):
    cm_artist = ConfusionMatrixDisplay(
        confusion_matrix=confusion_matrix(truth_vec, bin_preds[pred_fields[idx]]),
        display_labels=[False, True]
    )
    cm_artist.plot(ax=cell_ax, colorbar=False)

    cell_ax.set_title(panel_headers[idx], fontsize=10)
    cell_ax.set_xlabel('')
    cell_ax.set_ylabel('')
    cell_ax.tick_params(axis='both', labelsize=8)

chart.supxlabel('Predicted label', fontsize=12)
chart.supylabel('True label', fontsize=12)

plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()

Почему эта тонкость важна

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

Итоги

Если вам нужны общие подписи осей для нескольких ConfusionMatrixDisplay, дайте каждой панели отрисовать матрицу, затем уберите локальные подписи по x и y и добавьте глобальные через fig.supxlabel и fig.supylabel. Для размеров подписей используйте точечную настройку через tick_params после построения. Так вы получите чистые, сопоставимые матрицы ошибок — без дублированных подписей и с контролируемой типографикой.