You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

热力图掩码自定义标注:仅显示首数字问题求助

热力图仅标注最大值的解决方案

问题描述

希望在热力图中仅标注每组数据的最大值,但当前标注仅显示首数字,缩小字体也无法解决。尝试跳过seaborn的annot参数手动添加文本标注,但不清楚如何在子图中实现。

玩具数据生成代码

import numpy as np
import pandas as pd

np.random.seed(42)
n_rows = 10**6
n_ids = 1000
n_groups = 3

times = np.random.normal(12, 2.5, n_rows).round().astype(int) + np.random.choice([0,24,48,72,96,120,144], size=n_rows, p=[0.2,0.2,0.2,0.2,0.15,0.04,0.01])
timeslots= np.arange(168)

id_list = np.random.randint(low=1000, high=5000, size=1000)
ID_probabilities = np.random.normal(10, 1, n_ids-1)
ID_probabilities = ID_probabilities/ID_probabilities.sum()
final = 1 - ID_probabilities.sum()
ID_probabilities = np.append(ID_probabilities,final)
id_col = np.random.choice(id_list, size=n_rows, p=ID_probabilities)

data = pd.DataFrame(times[:,None]==timeslots, index=id_col)
n_ids = data.index.nunique()
data = data.groupby(id_col).sum()

data['grp'] = np.random.choice(range(n_groups), n_ids)

玩具数据示例

0   1   2   3   4   5   6   7   8   9   ... 159 160 161 162 163 164 165 166 167 grp
1011    0   0   0   0   0   0   2   3   15  21  ... 1   1   0   0   0   0   0   0   0   1
1016    0   0   0   0   0   0   4   3   18  41  ... 2   0   0   0   0   0   0   0   0   2
1020    0   0   0   0   0   1   1   2   6   16  ... 1   1   0   0   0   0   0   0   0   0
1024    0   0   0   0   0   0   2   3   7   13  ... 0   1   1   0   0   0   0   0   0   0
1029    0   0   0   0   0   0   1   5   3   14  ... 1   0   1   0   0   0   0   0   0   1
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
4965    0   0   0   0   0   2   4   2   10  9   ... 0   1   0   0   0   0   0   0   0   1
4984    0   0   0   0   0   1   0   6   10  12  ... 0   0   0   0   0   0   0   0   0   2
4989    0   0   0   0   0   1   3   4   7   16  ... 1   1   0   0   0   0   0   0   0   0
4995    0   0   0   0   2   0   2   2   2   23  ... 0   1   0   0   0   0   0   0   0   0
4999    0   0   0   0   0   1   1   7   9   11  ... 0   0   0   0   0   0   0   0   0   2

原代码问题分析

原代码存在两个核心问题:

  1. 标注值逻辑错误:要标注的是每小时总和的最大值,但代码错误使用了df[range(168)].max().max()(单ID的小时最大值),而非df[range(168)].sum().max();
  2. 标注渲染异常:空字符串的标注单元格可能导致seaborn渲染时出现仅显示首字符的情况。

解决方法1:修正seaborn annot参数逻辑

直接修正标注逻辑,确保值与位置匹配,同时优化渲染参数:

import seaborn as sns
import matplotlib.pyplot as plt

rows = 1 
cols = n_groups
grpr = data.groupby('grp')

actual_values = []
fig, axs = plt.subplots(rows, cols, figsize=(cols*5, rows*4), sharey=True, sharex=True)  # 放大画布避免拥挤

for grp, df in grpr:
    ax = axs[grp] if cols > 1 else axs  # 适配单/多子图索引
    hour_sums = df[range(168)].sum()
    sum_max = hour_sums.max()
    actual_values.append(sum_max)
    
    # 初始化标注数组:仅最大值位置赋值,其余为空
    annot_labels = np.full_like(hour_sums, '', dtype=str)
    annot_mask = hour_sums == sum_max
    annot_labels[annot_mask] = str(sum_max)
    
    # 绘制热力图,调整标注样式
    sns.heatmap(hour_sums.values.reshape(7,-1), 
                cbar=False, 
                annot=annot_labels.reshape(7,-1), 
                annot_kws={'rotation': 0, 'fontsize': 10, 'ha': 'center', 'va': 'center'},  # 取消旋转、居中对齐
                fmt='',
                ax=ax)
    
    ppl = df.shape[0]
    journs = int(df.sum().sum()/1000)
    ax.set_title(f'{grp}: {ppl:,} people, {journs:,}k trips')

# 统一设置轴标签和刻度
for ax in axs.flat if cols >1 else [axs]:
    ax.set(xlabel='Hour', ylabel='Day')
    ax.set_yticklabels(['M','T','W','T','F','S','S'], rotation=0)
    ax.label_outer()

plt.suptitle(f"标注已修复,实际最大值 = {actual_values}")
plt.tight_layout()
plt.show()

关键优化:

  • 修正标注值为hour_sums.max(),匹配需求逻辑;
  • 放大画布尺寸,避免标注拥挤;
  • 取消标注和y轴标签旋转,改用居中对齐提升可读性;
  • 明确指定子图ax参数,避免索引混乱。

解决方法2:手动添加文本标注(更灵活)

不依赖seaborn的annot参数,绘制热力图后手动定位最大值坐标添加文本:

import seaborn as sns
import matplotlib.pyplot as plt

rows = 1 
cols = n_groups
grpr = data.groupby('grp')

actual_values = []
fig, axs = plt.subplots(rows, cols, figsize=(cols*5, rows*4), sharey=True, sharex=True)

for grp, df in grpr:
    ax = axs[grp] if cols >1 else axs
    hour_sums = df[range(168)].sum()
    sum_max = hour_sums.max()
    actual_values.append(sum_max)
    
    # 绘制热力图,不使用annot参数
    heatmap = sns.heatmap(hour_sums.values.reshape(7,-1), 
                          cbar=False,
                          ax=ax)
    
    # 转换最大值的一维索引为二维坐标(行=星期,列=小时)
    max_idx = hour_sums.idxmax()
    row_idx = max_idx // 24
    col_idx = max_idx % 24
    
    # 获取对应单元格的中心坐标,添加文本
    cell = heatmap.get_children()[0].get_children()[row_idx*24 + col_idx]
    x = cell.get_x() + cell.get_width()/2
    y = cell.get_y() + cell.get_height()/2
    ax.text(x, y, str(sum_max), ha='center', va='center', fontsize=10, color='white')  # 白色文字更醒目
    
    ppl = df.shape[0]
    journs = int(df.sum().sum()/1000)
    ax.set_title(f'{grp}: {ppl:,} people, {journs:,}k trips')

# 统一设置轴标签和刻度
for ax in axs.flat if cols >1 else [axs]:
    ax.set(xlabel='Hour', ylabel='Day')
    ax.set_yticklabels(['M','T','W','T','F','S','S'], rotation=0)
    ax.label_outer()

plt.suptitle(f"手动标注最大值,实际最大值 = {actual_values}")
plt.tight_layout()
plt.show()

优势:

  • 完全控制标注的位置、样式、颜色;
  • 避免seaborn annot参数的潜在渲染问题;
  • 可轻松扩展为标注多个极值(如前N大值)。

内容的提问来源于stack exchange,提问作者ciaran haines

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 12:37:01