如何添加正负值对数缩放的双颜色条(Matplotlib)
解决Matplotlib正负值对数刻度热力图的颜色条重叠问题及优化方案
一、修正原代码的颜色条重叠问题
原代码使用twinx()创建重复轴是导致颜色条布局混乱的核心原因,实际上无需额外轴,直接在同一轴上叠加正负值热力图,再通过工具精确控制颜色条位置即可解决:
import numpy as np import matplotlib.pyplot as plt import matplotlib.colors from mpl_toolkits.axes_grid1 import make_axes_locatable # 生成示例数据 data = np.random.randn(10, 10) # 用nan替代0,避免对数刻度下的0值报错,同时让非目标区域自动透明 datap = np.where(data > 0, data, np.nan) datan = np.where(data < 0, np.abs(data), np.nan) # 计算有效绝对值的极值(排除0) abs_data = np.abs(data[np.abs(data) > 0]) vmin = abs_data.min() vmax = abs_data.max() # 创建画布和主轴 fig, ax = plt.subplots(figsize=(8, 6)) # 叠加绘制正负值热力图 heatmap_p = ax.imshow(datap, cmap='Reds', norm=matplotlib.colors.LogNorm(vmin=vmin, vmax=vmax)) heatmap_n = ax.imshow(datan, cmap='Blues_r', norm=matplotlib.colors.LogNorm(vmin=vmin, vmax=vmax)) # 使用make_axes_locatable工具在主轴右侧创建独立的颜色条轴,避免重叠 divider = make_axes_locatable(ax) # 第一个颜色条(正值) cax_p = divider.append_axes("right", size="5%", pad=0.1) cbar_p = fig.colorbar(heatmap_p, cax=cax_p, label='正值(对数刻度)') # 第二个颜色条(负值绝对值) cax_n = divider.append_axes("right", size="5%", pad=0.2) cbar_n = fig.colorbar(heatmap_n, cax=cax_n, label='负值绝对值(对数刻度)') ax.set_title('正负值对数热力图') plt.show()
二、更优的可视化方案:自定义双色对数颜色映射
如果不想用两个颜色条,可以创建统一的双色对数映射,将负值映射到蓝色系、正值映射到红色系,中间0值用中性色填充,仅需一个颜色条即可清晰区分正负与数值大小:
import numpy as np import matplotlib.pyplot as plt import matplotlib.colors # 生成示例数据 data = np.random.randn(10, 10) abs_max = np.abs(data).max() # 自定义归一化类:对正负值分别做对数缩放后映射到颜色区间 class SymLogNormCustom(matplotlib.colors.Normalize): def __init__(self, vmin=None, vmax=None, clip=False): self.vmin = vmin if vmin is not None else -abs_max self.vmax = vmax if vmax is not None else abs_max self.abs_max = max(abs(self.vmin), self.vmax) super().__init__(vmin=self.vmin, vmax=self.vmax, clip=clip) def __call__(self, value, clip=None): # 正值:对数缩放后映射到0.5-1区间 pos = np.where(value > 0, np.log10(value + 1e-10)/np.log10(self.abs_max + 1e-10), 0) pos = 0.5 + 0.5 * pos # 负值:取绝对值对数缩放后映射到0-0.5区间 neg = np.where(value < 0, np.log10(np.abs(value) + 1e-10)/np.log10(self.abs_max + 1e-10), 0) neg = 0.5 - 0.5 * neg # 0值设为中间色(0.5) zero = np.where(value == 0, 0.5, 0) return np.ma.masked_array(pos + neg + zero) # 创建蓝色→白色→红色的双色映射 cmap = matplotlib.colors.LinearSegmentedColormap.from_list( 'BlueRed', ['darkblue', 'white', 'darkred'], N=256 ) # 绘图 fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(data, cmap=cmap, norm=SymLogNormCustom(vmin=-abs_max, vmax=abs_max)) cbar = fig.colorbar(im, ax=ax, label='数值(正负对数刻度)') # 自定义颜色条刻度,清晰展示对数区间 ticks = [-abs_max, -abs_max/10, -abs_max/100, 0, abs_max/100, abs_max/10, abs_max] cbar.set_ticks(ticks) cbar.set_ticklabels([f'-{abs_max:.1f}', f'-{abs_max/10:.1f}', f'-{abs_max/100:.1f}', '0', f'{abs_max/100:.1f}', f'{abs_max/10:.1f}', f'{abs_max:.1f}']) ax.set_title('正负值统一对数热力图') plt.show()
内容的提问来源于stack exchange,提问作者Tanus Szabo
相关产品推荐
相关产品推荐

