热力图掩码自定义标注:仅显示首数字问题求助
热力图仅标注最大值的解决方案
问题描述
希望在热力图中仅标注每组数据的最大值,但当前标注仅显示首数字,缩小字体也无法解决。尝试跳过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
原代码问题分析
原代码存在两个核心问题:
- 标注值逻辑错误:要标注的是每小时总和的最大值,但代码错误使用了
df[range(168)].max().max()(单ID的小时最大值),而非df[range(168)].sum().max(); - 标注渲染异常:空字符串的标注单元格可能导致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
相关产品推荐
相关产品推荐

