ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

matplotlib热力图绘制指南:imshow与pcolormesh实战解析

matplotlib热力图绘制指南:imshow与pcolormesh实战解析 简介面向Python数据分析与可视化入门者这份zip压缩包聚焦基于matplotlib.pyplot的二维热力图绘制方法通过imshow函数将数据矩阵映射为不同颜色直观展示区域热度强弱同时加入颜色条与坐标轴配置帮助学习者在教学演示、科研报告或业务数据分布探查中快速应用。压缩包内共两个文件均为Python脚本大小仅约一KB小巧易用脚本包含热力图绘制示例并附带回归分析辅助脚本可作为练习素材直接运行修改便于理解从数据准备、矩阵构造到最终图像输出的完整流程。目前已有四千余人浏览学习说明该示例在可视化入门场景中有较高参考价值。资源不仅演示随机矩阵的快速绘图更展示了通过pandas读取CSV数据并利用pivot_table重构矩阵后绘制热力图的真实处理方式支持hot、viridis、coolwarm等多种色彩映射灵活切换使用户能够快捷迁移到自有数据上形成一套可复用的热力图可视化思路。1. 二维热力图把矩阵变成颜色pyplot 最小路径做信号分析时最常用的数据展示方式就是把二维数组变成热力图横轴表示时间窗或特征纵轴表示频率或通道颜色深浅对应数值大小。很多人写plt.imshow(data)只需要几秒但出图后常发现方向是反的、坐标对不上、颜色一片饱和于是开始怀疑是数据预处理的问题实际多数是绘图参数没配对。本文沿着 matplotlib 里 pyplot 包的两个主力函数imshow与pcolormesh展开把二维热力图涉及的颜色映射、坐标变换、归一化、多子图排布和导出细节一次讲透。适合 Python 环境刚跑通、想尽快交付可读图表的工程师和数据课程的学生。2. matplotlib 的 imshow 与 pcolormesh先选对绘图函数再看颜色映射2.1 二维热力图的查表上色机制热力图本质是查表上色数据矩阵里的数值映射到数组下标也就是像素位置再把数值大小通过颜色查找表转成 RGB 颜色。pyplot 包里有两个入口做这件事imshow和pcolormesh核心差异在渲染机制。imshow把二维数组当作图像矩阵渲染走的是图像处理链路像素之间可以做插值显示速度快适合均匀网格数据比如时频图、混淆矩阵、相关系数矩阵。pcolormesh则把每个数据点画成四边形网格支持非均匀的 x、y 坐标适合经纬度数据、有限元结果这类坐标不等距的场景。两者在实际项目里经常被当成同一个东西但选错了会有两类问题用imshow处理不规则网格会把图像拉伸变形用pcolormesh绘制规则矩阵反而因为要生成网格对象而更慢。我一般按数据坐标是否规则来决定规则网格直接imshow不规则网格再考虑pcolormesh。两者的行为差异可以从下表快速判断。对比项imshowpcolormesh输入要求二维数组即可需要 X、Y、Z 三个独立矩阵行列一致坐标支持仅像素坐标需 extent 换算原生支持任意非均匀坐标插值渲染支持视觉更平滑默认不做插值大数据量性能快适合视频帧级别网格生成有开销常用场景时频图、混淆矩阵经纬度网格、有限元云图从表格可以看出imshow适合数据本身就是矩阵的场景pcolormesh适合数据长在网格上的场景。实际项目里最省心的策略是手头只有二维数组时先用imshow出图确认数据形态确认无误后再按报告要求决定是否换成pcolormesh。2.2 用 imshow 跑通第一张热力图先给一个最小可运行代码把所有参数显式写出来方便对照修改import numpy as np import matplotlib.pyplot as plt # 构造一个 8 行 12 列的随机矩阵 data np.random.rand(8, 12) plt.figure(figsize(8, 5)) im plt.imshow(data, cmapviridis, aspectauto) plt.colorbar(im) plt.title(Minimal heatmap with imshow) plt.show()这段代码里cmapviridis设置颜色查找表viridis是 matplotlib 2.0 之后的默认色带对色盲友好灰度打印下也有区分度。aspectauto让单元格自动填满坐标轴不设置的话默认是equal图像会保持正方形常见结果是 8×12 的矩阵被压缩成一个竖条。plt.colorbar(im)接受imshow返回的对象把数值和颜色的对应关系画在右侧色带上。提示有的资料里写plt.colorbar()不带参数也能工作那是 matplotlib 内部自动寻找当前坐标轴上的最后一个映射对象。多子图场景下这个隐式查找容易出错建议总是把imshow或pcolormesh的返回值存下来再传给colorbar。运行后你会看到第一行数据在图像顶部这和很多人的直觉相反。因为数组第 0 行默认渲染在图像最上方而画折线图时 y 轴从下往上增长。如果希望矩阵第 0 行显示在底部可以在imshow里加originlower信号处理里的时频图通常用这个设置让低频显示在底部。2.3 pcolormesh非均匀网格的正确打开方式接着是pcolormesh的示例场景是模拟一个二维波动场import numpy as np import matplotlib.pyplot as plt # 生成网格坐标x 方向 200 点y 方向 150 点 x np.linspace(0, 4 * np.pi, 200) y np.linspace(0, 4 * np.pi, 150) X, Y np.meshgrid(x, y) Z np.sin(X) * np.cos(Y) plt.figure(figsize(8, 5)) pc plt.pcolormesh(X, Y, Z, cmapcoolwarm, shadingauto) plt.colorbar(pc) plt.show()np.meshgrid的作用是生成网格坐标矩阵X每一行相同Y每一列相同这样Z[i, j]就能对应到平面上的坐标(X[i, j], Y[i, j])。pcolormesh按坐标把每个四边形填上颜色shadingauto表示由库自动判断颜色应该落在网格中心还是网格顶点旧版本的默认行为容易在行列数不匹配时报错新版用auto可以避免这类问题。这段代码在 200×150 的网格上运行生成的图就是一张常见的二维波动热力图。性能方面pcolormesh在网格超过 10 万量级时仍能保持流畅它会把每个四边形打包成集合对象一次性绘制而类似的pcolor函数则慢很多后者在官方说明里已经不建议用于大数据量场景。3. 坐标轴对齐、中文乱码与过密刻度热力图三处高频坑位一次填平3.1 用 extent 把像素坐标映射到业务坐标imshow输入的只有矩阵x 轴和 y 轴默认是[0, N]的像素坐标。如果横轴代表时间、纵轴代表频率就需要extent参数做线性映射。比如采样率 1000 Hz、时间 2 秒、频率范围 0 到 500 Hz可以这样写import numpy as np import matplotlib.pyplot as plt data np.random.rand(64, 128) # 64 个频率点128 个时间窗 extent (0, 2, 0, 500) # (xmin, xmax, ymin, ymax) plt.figure(figsize(8, 5)) im plt.imshow(data, aspectauto, extentextent, cmapmagma, originlower) plt.xlabel(Time (s)) plt.ylabel(Frequency (Hz)) plt.colorbar(im, labelAmplitude) plt.show()extent只改变坐标轴的显示范围和刻度位置不改变数据的排列方式相当于把矩阵的行列坐标线性拉伸到指定区间。originlower在这里尤其重要频率图通常低频在下这个参数可以避免图像上下翻转带来的误读因为大多数信号处理库的矩阵第 0 行对应最低频。aspect和extent组合时需要注意aspectauto会拉伸单元格填满坐标轴而aspectequal会按照 extent 的数值比例强制图像保持纵横比。如果 x 范围是 0 到 100、y 范围是 0 到 1保持 equal 之后图像会变成一条又长又扁的色带几乎没法看所以热力图里aspectauto是多数场景的默认选择。3.2 中文乱码要改全局字体配置热力图的轴标签、色带标签经常需要中文但 matplotlib 默认字体不包含中文字形出图后就是一个个方框。常见做法是修改 rcParams 全局字体。先确认系统里是否有中文字体然后指定字体名称Windows 上一般用 SimHei 或 Microsoft YaHeiLinux 上可以安装fonts-wqy-zenhei后指定 WenQuanYi Zen Hei。配置代码import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, WenQuanYi Zen Hei] plt.rcParams[axes.unicode_minus] False # 解决负号显示为方块的问题放在绘图代码最前面即可。axes.unicode_minusFalse是配套设置因为默认的 Unicode 负号在修改字体后同样会出现渲染问题。字体名称一旦写错matplotlib 会静默回退到默认字体不会报错所以确认字体是否生效的方法是临时打印plt.rcParams[font.sans-serif]。在 Linux 服务器上跑批量绘图时还要注意中文字体文件是否已安装。没装字体时用系统包管理器安装例如 Debian 系执行apt-get install fonts-wqy-zenhei装完清除 matplotlib 的字体缓存后才能识别新字体。字体缓存目录通常是~/.cache/matplotlib删除后重启绘图会重新构建这是服务器环境最容易忽略的一步。3.3 横坐标过密时用 MaxNLocator 和旋转刻度数据量大时 x 轴刻度会挤成一团典型情况是 1000 个时间窗都标上刻度文字重叠后整条横轴变成黑色竖线。有两种应对方式一种是用MaxNLocator限制刻度数量另一种是旋转刻度文本两者可以组合使用。import numpy as np import matplotlib.pyplot as plt from matplotlib.ticker import MaxNLocator data np.random.rand(20, 200) # 宽矩阵x 轴有 200 个位置 fig, ax plt.subplots(figsize(10, 4)) im ax.imshow(data, aspectauto, cmapviridis) ax.xaxis.set_major_locator(MaxNLocator(nbins6)) plt.xticks(rotation45) plt.colorbar(im) plt.show()MaxNLocator(nbins6)的意思是在 x 轴上最多显示约 6 个刻度这个数字不是硬上限matplotlib 会根据刻度间隔取整自动调整实际显示可能多一点点。rotation45把刻度文本旋转 45 度适合刻度较少但文本较长的情况。两者同时使用先限制数量再旋转文本基本可以解决九成以上的刻度重叠问题。需要说明的是MaxNLocator的新版本里推荐使用位置参数MaxNLocator(6)老写法MaxNLocator(nbins6)仍然兼容只是部分版本会提示参数改名。升级后直接写数字最省事不会被弃用警告干扰。3.4 用 TwoSlopeNorm 修正中心对称数据的过曝imshow默认按数据的全局最小值到最大值做线性映射。如果数据是中心对称的正负值例如误差在 -10 到 10而大部分数据集中在 0 附近直接画出来会是两级饱和中间细节全被拉平。常用的处理是用TwoSlopeNorm把零值固定在色带中间让正负两段用不同色系表达import numpy as np import matplotlib.pyplot as plt from matplotlib.colors import TwoSlopeNorm data np.random.randn(30, 30) * 3 # 均值 0少量极端值 norm TwoSlopeNorm(vcenter0, vmindata.min(), vmaxdata.max()) plt.figure(figsize(7, 6)) im plt.imshow(data, cmapRdBu_r, normnorm) plt.colorbar(im) plt.show()vcenter0告诉色带映射零值是中心颜色正负两侧用冷暖色分开。vmin和vmax取数据实际范围是为了让色带完整覆盖所有数值超出部分会被强制裁剪成端点颜色。这种归一化在绘制残差图、相关系数矩阵、信号误差场时非常实用。另一个更常见的需求是把某一数值范围内的微小波动放大这就要用vmin和vmax手动框定范围例如im.set_clim(0, 1)超过范围的值按端点色处理。在批量绘制多张热力图时所有子图统一传入相同的vmin和vmax才能保证颜色在不同图之间可比这是论文拼图时常被忽略的细节。4. 信号热力图实战连续数据转矩阵排版交给多子图4.1 从一维信号构造二维热力图输入实际项目里热力图的数据很少直接是现成矩阵更多是从一维信号切窗得到的。比如一段 10 秒的振动信号按 128 个采样点切窗、64 点步长滑动就得到一个 128×N 的矩阵。构造的通用写法import numpy as np fs 1000 # 采样率 1000 Hz t np.linspace(0, 10, fs * 10) # 模拟10 Hz 基波 2 Hz 调幅 噪声 signal np.sin(2 * np.pi * 10 * t) * (1 0.5 * np.sin(2 * np.pi * 2 * t)) signal 0.2 * np.random.randn(len(t)) window_len 128 # 每窗 128 个点 step 64 # 滑动步长 64 点窗与窗重叠一半 n_windows (len(signal) - window_len) // step matrix np.zeros((window_len, n_windows)) for i in range(n_windows): matrix[:, i] signal[i * step: i * step window_len] print(matrix.shape) # (128, 155)切窗的window_len128决定纵轴长度step64决定横轴窗口数量重叠一半是比较常规的设置能保留时间连续性又不会让数据量翻倍。matrix的每一列是一段短时波形画成热力图后横轴是时间窗序号纵轴是窗口内采样点位置颜色的竖条纹变化就能直观反映幅值包络的起伏。把这个矩阵交给 pyplot就是最基础的信号热力图plt.figure(figsize(10, 5)) plt.imshow(matrix, aspectauto, cmapturbo, originlower) plt.xlabel(Window index) plt.ylabel(Sample index within window) plt.colorbar(labelAmplitude) plt.show()4.2 六张图合在一个画布上的排版方式有时需要把原始波形、频谱、热力图放在同一张图里对比用plt.subplots一次性建好坐标轴最方便。以六张图混排为例import numpy as np import matplotlib.pyplot as plt fig, axes plt.subplots(3, 2, figsize(12, 8)) for i in range(3): for j in range(2): data np.random.randn(30, 40) im axes[i, j].imshow(data, aspectauto, cmapviridis) axes[i, j].set_title(fSubplot ({i}, {j})) plt.colorbar(im, axaxes, fraction0.02, pad0.04) plt.tight_layout() plt.show()plt.subplots(3, 2)返回(fig, axes)二维数组axes[i, j]直接索引到每张子图。colorbar传axaxes表示色带高度覆盖全部六个子图避免每张图各出一条色带占用版面。fraction0.02控制色带宽度占比pad0.04控制与子图的间距。这里要小心不同子图的数据量级不一致时共享一条色带会误导比较。真实做法是先确认所有子图的值域统一或者给每个子图单独设vmin和vmax。如果各子图数据范围差异巨大建议还是每张图单独画 colorbar虽然占地方但至少不会造成视觉上的错误等值判断。不规则排版用subplots的gridspec_kw参数比如让第一行占满整宽、下面两行分两列。这类混排在大屏数据看板里很常见写法是给subplots加行列权重fig plt.figure(figsize(12, 8)) gs fig.add_gridspec(2, 2, height_ratios[1, 1.5], width_ratios[1, 1]) ax1 fig.add_subplot(gs[0, :]) ax2 fig.add_subplot(gs[1, 0]) ax3 fig.add_subplot(gs[1, 1])height_ratios让第二行整体比第一行高 50%gs[0, :]表示第一行横跨两列。热力图在跨列的长条子图里显示时记得用aspectauto否则长条会被强制拉成正方形横向细节全部丢失。4.3 颜色边界与掩膜别让异常点毁掉整张图数据里偶尔有坏点或异常值时热力图的全局色标会被拉到极端值上正常区域全部变成一个颜色。解决方式有两种一是手动设vmin/vmax截断颜色范围二是用np.ma.masked_invalid掩膜掉异常点。import numpy as np import matplotlib.pyplot as plt raw np.random.randn(50, 50) raw[3, 3] 100 # 手动制造异常点 valid np.ma.masked_invalid(raw) # 掩膜 NaN 和 inf plt.figure(figsize(7, 6)) im plt.imshow(valid, cmapviridis, vmin-3, vmax3) plt.colorbar(im) plt.show()vmin-3, vmax3直接限制色带范围异常值 100 超出上限后会被绘制成色带顶部的颜色不容易干扰正常区域的对比度。masked_invalid会把 NaN 和 inf 自动遮掉遮掉的区域在图上显示为透明结合plt.cm.viridis.set_bad(white)可以把无效区域标记为白色这在传感器数据缺失时很有用能一眼看出哪些位置没有数据。实际做信号热力图时我一般先用np.percentile确定合理的vmin和vmax例如取np.percentile(matrix, 2)和np.percentile(matrix, 98)这样可以避免 5% 的极值把色带拉得过宽又不会完全丢失真实信号峰值。直接调set_clim也能达到同样效果但 percentile 方法可复现性更强适合批量处理多段数据。5. 把 matplotlib 热力图导出成论文级图片的三个收尾技巧5.1 高分辨率导出的 dpi 与边框设置热力图用于报告时plt.savefig的参数比show更关键。常见做法是设置dpi300并用bbox_inchestight裁掉多余的留白。如果热力图横向跨度大figsize要提前设宽否则导出后字体会被拉伸变形。保存矢量图可以选择 PDF 格式文字和色带都保持清晰plt.savefig(heatmap.pdf, dpi300, bbox_inchestight)注意bbox_inchestight会重新计算画布边界colorbar 的标签有时会被裁掉一点保存后要打开文件确认。批量导出时不同子图的数据范围要保持一致否则拼进论文里颜色含义就会失真。5.2 小矩阵热力图用边缘描边提升区分度数据量不大的矩阵热力图比如 8×12每个单元格之间的边界完全靠颜色深浅区分颜色相近时很难看清。一个提升观感的小技巧是把imshow的edgecolors参数设为白色或浅灰色并配合linewidth设置边框粗细im plt.imshow(data, cmapBlues, edgecolorswhite, linewidth0.8)矩阵的每个格子都会带上细边框整体像一张彩色表格多用于混淆矩阵和分类型热力图。单元格数量超过几百个时不建议用边框会淹没颜色信息只适合小矩阵。5.3 用归一化范围验证避免导出后才发现过曝导出前验证颜色映射范围是否合理一个具体做法是打印实际的归一化边界和色带刻度print(im.norm.vmin, im.norm.vmax)如果vmin和vmax与数据的主要分布区间差距过大说明存在极端值干扰需要回到 percentile 方法重新设定范围。另一个验证方法是把 colorbar 单独截出来看确认色带两端没有大面积的纯色块。保存 PDF 之后用阅读器放大到 200% 检查文字和色带是否发虚这是论文投稿前最常被忽略的一步。本文还有配套的精品资源点击获取
返回列表