3. 海洋温度剖面可视化
CTD(Conductivity-Temperature-Depth)观测得到的是随深度变化的温度序列,海洋学中最经典的表达方式不是把深度当作横轴,而是把温度放在横轴、深度放在纵轴并令其向下增大。这一约定与海洋的真实垂向结构一致:海面在图的顶部,海底在图的底部,读者一眼就能把曲线形态与水体分层对应起来。本节围绕这一核心约定,讲清楚如何用 matplotlib 画出可直接用于报告和论文的温度剖面图,并叠加温跃层标注与多站点对比。
剖面图的坐标约定与数据准备
先明确参照系,全文与图示保持一致:横轴为温度(℃),向右增大;纵轴为压强或深度,向下增大,即通过 ax.invert_yaxis() 或设置 ylim 反向实现。CTD 原始数据通常以压强 dbar 记录,1 dbar 约等于 1 m,教学示例中可直接以深度 m 近似处理,但在正式成果中应注明“深度由压强换算”或直接标注压强单位。
假设我们有一份示例数据(以下数值为教学构造,非实测):某近岸站表层温度 26.8 ℃,随深度增加在 15~45 m 区间快速下降至 12.5 ℃,45 m 以下缓慢降至 200 m 处的 8.2 ℃。数据以三列形式存放:深度、温度、盐度。绘图前先做两件事:按深度升序排序,剔除缺失值(CTD 下放与回收阶段常出现重复深度,需按时间或压强去重)。
import numpy as np
import matplotlib.pyplot as plt
# 示例数据:depth(m), temp(℃)
depth = np.array([0, 5, 10, 15, 20, 25, 30, 35, 40, 45,
60, 80, 100, 150, 200])
temp = np.array([26.8, 26.5, 25.9, 24.1, 21.0, 18.2, 16.0, 14.2,
13.0, 12.5, 11.6, 10.4, 9.6, 8.8, 8.2])
order = np.argsort(depth)
depth, temp = depth[order], temp[order]
绘制基础剖面并标注温跃层
温跃层是温度垂向梯度显著的水层,工程上常用“温度梯度超过某一阈值”来界定其上下界。对上述示例,可先计算相邻层的梯度 dT/dz,再取绝对值大于 0.2 ℃/m 的连续深度区间作为温跃层。计算后得到上界约 15 m、下界约 45 m,最大梯度出现在 20~25 m 之间。把这一区间用 axhspan 填充浅色带,并在带内标注“温跃层 15–45 m,最大梯度 0.56 ℃/m”,读者无需读表即可定位关键水层。
grad = np.abs(np.gradient(temp, depth))
mask = grad > 0.2
z_top, z_bot = depth[mask].min(), depth[mask].max()
i_max = np.argmax(grad)
fig, ax = plt.subplots(figsize=(4.2, 6.0), dpi=300)
ax.plot(temp, depth, color="#1f6fb4", lw=1.8, label="S1 站")
ax.axhspan(z_top, z_bot, color="#ffd9a0", alpha=0.45, zorder=0)
ax.annotate(f"温跃层 {z_top:.0f}–{z_bot:.0f} m\n最大梯度 {grad[i_max]:.2f} ℃/m",
xy=(temp[i_max], depth[i_max]),
xytext=(temp[i_max] + 3, depth[i_max] - 25),
arrowprops=dict(arrowstyle="->", color="#c46a00"),
fontsize=9, color="#8a4b00")
ax.invert_yaxis()
ax.set_xlabel("温度 (℃)")
ax.set_ylabel("深度 (m)")
ax.grid(ls=":", alpha=0.5)
ax.legend(loc="lower right", frameon=False)
标注位置需要一点经验:箭头指向最大梯度点,文字放在曲线右侧的空白区,避免压住曲线;若站点温度整体偏低、右侧空间不足,可把文字移到左侧并调整 ha。温跃层色带使用低饱和度暖色,与蓝色曲线形成对比但不喧宾夺主。
多站点对比:同一坐标系下的分层差异
单站剖面只能说明一个位置的水体结构,而海洋监测报告往往需要比较近岸站、陆架站与外侧站的层化强弱。做法是在同一坐标系中叠加多条曲线,用颜色区分站点,并保持深度轴一致。示例中构造三个站点:S1 近岸站(表层 26.8 ℃、跃层 15–45 m)、S2 陆架站(表层 25.2 ℃、跃层 25–60 m、最大梯度 0.38 ℃/m)、S3 外侧站(表层 24.0 ℃、跃层 40–90 m、最大梯度 0.22 ℃/m)。三条曲线的跃层深度依次加深、梯度依次减弱,正好对应从近岸到外海的层化演变。
stations = {
"S1 近岸": (temp, depth, "#1f6fb4"),
"S2 陆架": (temp2, depth2, "#2a9d8f"),
"S3 外侧": (temp3, depth3, "#8e5bb5"),
}
for name, (t, z, c) in stations.items():
ax.plot(t, z, color=c, lw=1.8, label=name)
ax.legend(loc="lower right", frameon=False, fontsize=9)
多站点图的关键是可辨识性:颜色之外再配合线型(实线、虚线、点划线)可兼顾黑白打印;若曲线在深层重叠,可只对跃层区间做局部放大插图(inset_axes),而不是把整幅图的深度范围压缩。对比时还应统一温度量程,否则各站曲线的相对位置会产生误导。
出版级输出的细节控制
“出版级”不是指分辨率数字越大越好,而是指图在目标尺寸下文字可读、线宽适中、无多余装饰。建议:图幅按最终排版宽度设定(单栏约 8.5 cm、双栏约 17 cm),dpi=300 以上;字号不小于 8 pt;去掉上、右边框(spines),网格用浅色点线;图例不加边框。保存时用 bbox_inches="tight" 裁掉多余白边,矢量格式优先存 PDF/SVG,位图存 PNG。
for s in ["top", "right"]:
ax.spines[s].set_visible(False)
fig.savefig("ctd_profile.png", dpi=300, bbox_inches="tight")
fig.savefig("ctd_profile.pdf", bbox_inches="tight")
invert_yaxis(),海面会跑到图底部,整幅图的水体分层含义完全颠倒,这是剖面图最常见的错误。