3. 科学计算可视化入门

在自动报告生成流程中,可视化不是“配图”,而是把计算结果转成可判读信息的关键环节。Matplotlib 是 Python 科学计算生态中最通用的绘图库,它的核心模型可以概括为三层:Figure(画布)承载一个或多个 Axes(坐标系/子图),每个 Axes 内部再由线、点、柱、文本等 Artist 对象组成。理解这三层关系,才能写出既能单独出图、又能嵌入报告模板的绘图代码。

最常用的入口是 pyplot 模块。下面这段示例用一组模拟的采样数据演示基础绘图流程,数据为教学假设,并非实测结果:

import numpy as np
import matplotlib.pyplot as plt

# 教学示例数据:某传感器 0~10 s 的模拟读数
t = np.linspace(0, 10, 200)
signal = 2.0 * np.sin(1.2 * t) + 0.3 * np.random.default_rng(7).normal(size=t.size)

fig, ax = plt.subplots(figsize=(6, 3.6), dpi=120)
ax.plot(t, signal, color="#2b7de9", linewidth=1.4, label="传感器读数")
ax.set_xlabel("时间 t / s")
ax.set_ylabel("幅值 / V")
ax.set_title("模拟信号时序")
ax.grid(alpha=0.3)
ax.legend(loc="upper right")
fig.tight_layout()
fig.savefig("signal.png", bbox_inches="tight")

这里有几个容易被忽略但直接影响报告质量的细节。fig, ax = plt.subplots() 显式拿到 Axes 对象,比直接调用 plt.plot() 更可控,尤其在需要多子图或后续定制时;figsize 与 dpi 共同决定输出像素尺寸,报告插图一般建议宽度 6 英寸、dpi 120~150,既能保证清晰度又不至于让文档体积过大;bbox_inches="tight" 会裁掉多余白边,避免图例或轴标签被截断。

图表类型的选择依据

选图的第一原则是看“数据关系”而不是看“好不好看”。连续变量随另一连续变量变化,用折线图;离散类别的数量比较,用柱状图;两个变量的分布与相关性,用散点图;单个变量自身的分布形态,用直方图;部分与整体的构成,用堆叠柱或饼图(类别不宜超过 5 个);多组数据在同一量纲下比较分布,用箱线图。下面这张图把常见图表类型与它们最适合表达的关系对应起来,可作为选图时的快速参照。

图表类型与数据关系的对应选择 先判断数据关系,再决定图形;同一份数据可因表达目的不同而换图 先问:要表达什么关系? 趋势 / 比较 / 分布 / 构成 / 相关 趋势变化 折线图 plot 类别比较 柱状图 bar 分布形态 直方图 hist 变量相关 散点图 scatter 选图检查点 ① 横轴是否为连续量? ② 类别数量是否过多(饼图 ≤ 5)? ③ 是否要比较分布而非均值? ④ 多序列是否共用同一量纲? 量纲不同时优先拆子图或使用双坐标轴,不要强行叠在同一纵轴上。

需要强调的是,图表类型不是一次定死的。同一组月度数据,若目的是看走势就用折线,若目的是比较各月高低就用柱状,若目的是看数值集中区间就用直方图。自动报告里更稳妥的做法是:在生成脚本中把“图类型”作为参数暴露出来,由报告配置决定,而不是硬编码在绘图函数里。

坐标轴定制:让图自己会说话

坐标轴是读图的第一入口。至少要处理四件事:轴标签带单位、刻度范围与密度合理、必要时使用对数轴、以及参照线。例如当数据跨越多个数量级时,ax.set_yscale("log") 能让小值区间不被压扁;当需要突出阈值时,用 ax.axhline(y=阈值, linestyle="--", color="red") 画一条水平参考线,比在正文里反复描述更直观。刻度方面,ax.set_xticks() 可显式指定位置,ax.tick_params(axis="x", rotation=30) 可缓解时间标签重叠。

中文显示是报告场景的高频问题。Matplotlib 默认字体不含中文字形,会出现方框。稳妥做法是在脚本开头统一设置:

import matplotlib
matplotlib.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"]
matplotlib.rcParams["axes.unicode_minus"] = False  # 正常显示负号

把字体配置放在绘图模块的初始化位置,而不是每张图重复设置,可以保证整份报告风格一致。

图例与多序列表达

图例的作用是建立“图形元素—数据含义”的映射。每个 plot 调用都应带 label 参数,最后统一调用 ax.legend()。位置用 loc 控制,常用 "best" 让 Matplotlib 自动避让数据,但自动避让并不总是理想,报告插图更推荐显式指定如 "upper right" 或放到图外 bbox_to_anchor=(1.02, 1)。当序列较多时,图例会挤占绘图区,此时可考虑 ncol=2 分列,或改用直接在线端标注的方式。

多序列对比还要注意颜色与线型的可区分性。同一张图里序列不超过 5 条时,用默认色循环通常够用;超过时建议同时改变线型(实线、虚线、点划线)或标记点形状,避免仅靠颜色区分,这对黑白打印和色觉障碍读者都更友好。下面给出一个双序列对比的完整片段,包含图例、参考线与轴范围控制:

fig, ax = plt.subplots(figsize=(6, 3.6))
ax.plot(t, signal, label="实测(模拟)", color="#2b7de9")
ax.plot(t, 2.0 * np.sin(1.2 * t), label="理论值", color="#f08a24", linestyle="--")
ax.axhline(0, color="#9db4cc", linewidth=0.8)
ax.set_xlim(0, 10)
ax.set_ylim(-3, 3)
ax.set_xlabel("时间 t / s")
ax.set_ylabel("幅值 / V")
ax.legend(loc="upper right", frameon=False)

最后是输出与嵌入。报告生成通常批量出图,建议统一封装一个 save_figure(fig, path) 函数,内部固定 dpi、bbox_inches="tight" 与格式(PNG 用于网页,SVG/PDF 用于打印),并在保存后调用 plt.close(fig) 释放内存。批量生成几十上百张图时,忘记关闭 Figure 会导致内存持续增长,这是自动报告脚本最常见的隐性故障之一。把“建图—定制—保存—关闭”做成固定函数,既保证风格统一,也让报告模板只需关心图片路径,而不必重复绘图细节。