2025, Nov 05 12:01

Итоги без искажений: исключаем столбец итогов из теплокарты Plotly

Разберём, как сделать теплокарту Plotly с колонкой итогов, не искажающей цвета: исключаем grand_total из раскраски, оставляем числа в тексте, go.Heatmap.

Тепловые карты Plotly отлично подходят для визуализации сводных метрик, но часто всплывает одна ловушка, когда вы добавляете столбец итогов по строкам. Этот единственный столбец попадает в цветовую шкалу и «перебивает» остальные ячейки, из‑за чего они выглядят блеклыми и малоинформативными. Задача — оставить итоги на виду, но исключить их из раскраски.

Постановка задачи

Рассмотрим сводную таблицу pandas, где мы добавляем столбец итогов и пытаемся построить её с помощью Plotly Express. Итоги полезны как контекст, но они тоже окрашиваются и тем самым искажают шкалу.

import numpy as np
import pandas as pd
import plotly.express as px
origin_cities = ['London', 'Tokio', 'Seoul', 'Paris', 'Tashkent', 'Washington', 'Moscow']
current_cities = ['London', 'Madrid', 'Tashkent', 'Seoul', 'Paris', 'Toronto', 'Washington', 'Istanbul', 'Hanoi', 'Manilla', 'Delhi', 'Busan', 'Moscow']
src_city = np.random.choice(origin_cities, size=1000)
dst_city = np.random.choice(current_cities, size=1000)
pay = np.random.randint(1300, 6900, size=1000)
base_df = pd.DataFrame({'src_city': src_city, 'dst_city': dst_city, 'pay': pay})
wide_tbl = pd.pivot_table(
    base_df,
    index='src_city',
    columns='dst_city',
    values='pay',
    aggfunc='sum',
    fill_value=0
)
wide_tbl['grand_total'] = wide_tbl.sum(axis=1)
col_order = ['grand_total'] + [c for c in wide_tbl.columns if c != 'grand_total']
wide_tbl = wide_tbl[col_order]
px.imshow(wide_tbl, text_auto=True)

В чём проблема на самом деле

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

Решение

Лекарство — разделить то, что окрашивается, и то, что показывается как текст. Создайте один DataFrame для значений теплокарты и установите в столбце итогов значение None, чтобы он не получал цвет. Отдельно подготовьте DataFrame для текстовых подписей, чтобы итоги по‑прежнему отображались числом. Поскольку этот подход опирается на go.Heatmap, нужно передавать массивы и перевернуть порядок строк: go.Heatmap рисует первую строку массива внизу теплокарты. Так как столбец итогов становится прозрачным, отключение линий сетки помогает избежать визуального шума; при желании можно подправить фон или тему, чтобы столбец итогов читался иначе.

import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
origin_cities = ['London', 'Tokio', 'Seoul', 'Paris', 'Tashkent', 'Washington', 'Moscow']
current_cities = ['London', 'Madrid', 'Tashkent', 'Seoul', 'Paris', 'Toronto', 'Washington', 'Istanbul', 'Hanoi', 'Manilla', 'Delhi', 'Busan', 'Moscow']
np.random.seed(42)
src_city = np.random.choice(origin_cities, size=1000)
dst_city = np.random.choice(current_cities, size=1000)
pay = np.random.randint(1300, 6900, size=1000)
base_df = pd.DataFrame({'src_city': src_city, 'dst_city': dst_city, 'pay': pay})
wide_tbl = pd.pivot_table(
    base_df,
    index='src_city',
    columns='dst_city',
    values='pay',
    aggfunc='sum',
    fill_value=0
)
wide_tbl['grand_total'] = wide_tbl.sum(axis=1)
col_order = ['grand_total'] + [c for c in wide_tbl.columns if c != 'grand_total']
wide_tbl = wide_tbl[col_order]
# значения для раскраски: скрываем итоги, присваивая им None
heat_vals = wide_tbl.iloc[::-1].copy()
heat_vals['grand_total'] = None
# текст для отображения: сохраняем все значения как строки
heat_text = wide_tbl.iloc[::-1].astype(str).copy()
z_data = heat_vals.to_numpy()
text_data = heat_text.to_numpy()
y_labels = heat_text.index.values
x_labels = heat_text.columns.values
fig = go.Figure()
fig.add_trace(go.Heatmap(
    z=z_data,
    y=y_labels,
    x=x_labels,
    text=text_data,
    texttemplate="%{text:.2s}",
))
fig.update_layout(
    xaxis=dict(showgrid=False),
    yaxis=dict(showgrid=False),
)
fig.show()

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

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

Итоги

Если столбец итогов «перекашивает» вашу теплокарту Plotly, оставьте числа видимыми как текст, а из z‑значений исключите их, присваивая None. Переключитесь на go.Heatmap, передайте массивы значений и текста, переверните порядок строк для ожидаемого отображения и отключите линии сетки для более чистого вида. Небольшой рефакторинг сохранит осмысленную цветовую шкалу и покажет итоги именно там, где аналитики привыкли их видеть.

Статья основана на вопросе с StackOverflow от Ranger и ответе Derek O.