第三章 原始质谱数据可视化:谱图、基峰图、总离子流图
拿到质谱数据后,第一件事是什么?我个人的习惯——先看一眼数据长什么样。可视化不是秀图,而是帮你快速发现异常、把握整体质量、判断后续处理方向。这一章我们专门聊三个最基础也最常用的图:质谱谱图、基峰色谱图和总离子流色谱图。
3.1 质谱谱图(Mass Spectrum)
质谱谱图就是某一时刻(或一段保留时间窗口内)的质荷比(m/z)vs 离子强度的二维图。说白了,它告诉你“现在这个时间点,样品里有哪些质量的离子,各自有多少”。
我在第一次做代谢组学项目时,拿到 .mzML 文件就直接跑机器学习,结果模型一团糟。后来回过头看一眼原始谱图才发现——有一批样本的基线噪声高得离谱,质谱仪参数根本不对。你看,可视化能救命。
绘制方法(Python + pyOpenMS)
常用的库是 pyOpenMS + matplotlib,也可以直接用 pymzml。下面给出一个简单的片段,读入单个扫描并画质谱图:
import numpy as np
import matplotlib.pyplot as plt
import pyopenms as oms
# 从 mzML 文件中读取
exp = oms.MSExperiment()
oms.MzMLFile().load("sample.mzML", exp)
# 取第 10 个扫描(索引从 0 开始)
spec = exp[9]
mz_array, intensity_array = spec.get_peaks()
plt.figure(figsize=(10, 4))
plt.vlines(mz_array, 0, intensity_array, color='#3498db', linewidth=0.8)
plt.xlabel("m/z"); plt.ylabel("Intensity")
plt.title(f"MS Spectrum at RT = {spec.getRT():.2f} s")
plt.tight_layout(); plt.show()
3.2 基峰色谱图(Base Peak Chromatogram)
基峰图(BPC)的思路很简单:对于色谱上的每个时间点(每个扫描),只取强度最高的那个离子的强度值,然后把所有时间点的这个“最强值”连成一条线。
为什么不用总离子流图?我记得有一次做 LC-MS 数据,TIC 看起来信号很平稳,但 BPC 却暴露出几个关键色谱峰被高背景信号淹没了。你想想看,如果机器偶尔有高噪声 spike,TIC 会把它累积进去,而 BPC 只取最强信号,反而能更清晰地反映真实色谱峰。
从 mzML 中提取 BPC
def compute_bpc(exp):
rts, bpc_intensities = [], []
for spec in exp:
if spec.getMSLevel() == 1: # 只取一级谱
_, ints = spec.get_peaks()
if len(ints) > 0:
rts.append(spec.getRT())
bpc_intensities.append(np.max(ints))
return np.array(rts), np.array(bpc_intensities)
rts, bpc = compute_bpc(exp)
plt.plot(rts, bpc, color='#2ecc71', linewidth=0.7)
plt.xlabel("Retention Time (s)")
plt.ylabel("Base Peak Intensity")
plt.title("Base Peak Chromatogram")
plt.show()
if np.max(ints) > 0,问题解决。
3.3 总离子流色谱图(Total Ion Current Chromatogram)
TIC 是把每个扫描的所有离子强度加起来,反映总离子流随时间的变化。它是最基础的全局视图,就像监控摄像头的全景模式。
TIC 有三个常见用途:
- 快速评估运行质量——比如溶剂前沿、柱流失、信号衰减
- 归一化基线——在定量时用 TIC 做强度校正
- 发现跑样异常——如果 TIC 突然掉为零,可能是喷雾堵塞或电压崩溃
提取与绘制
def compute_tic(exp):
rts, tic = [], []
for spec in exp:
if spec.getMSLevel() == 1:
_, ints = spec.get_peaks()
rts.append(spec.getRT())
tic.append(np.sum(ints))
return np.array(rts), np.array(tic)
rts, tic = compute_tic(exp)
plt.plot(rts, tic, color='#9b59b6', linewidth=0.7)
plt.xlabel("Retention Time (s)")
plt.ylabel("Total Ion Current")
plt.title("Total Ion Current Chromatogram")
plt.show()
三种图对比一览
| 图谱类型 | 横轴 | 纵轴 | 核心信息 | 典型用途 |
|---|---|---|---|---|
| 质谱谱图 (MS) | m/z | 强度 | 单个时间点的质荷比分布 | 化合物鉴定、碎片分析 |
| 基峰色谱图 (BPC) | 保留时间 | 最强离子强度 | 每个扫描的最大离子 | 色谱峰检测、峰对齐 |
| 总离子流图 (TIC) | 保留时间 | 所有离子强度之和 | 全局信号总量 | 质量控制、基线归一化 |
可视化之前的一个预处理动作
不管你要画哪种图,第一步几乎都是:确定你读进来的数据格式正确。我用 pyOpenMS 时经常踩的坑——忘记设置 log 级别,或者 mzML 文件里的 MS level 标记不对。所以我习惯在可视化前先打印一些元信息,像这样:
print(f"Number of scans: {len(exp)}")
print(f"First scan MS level: {exp[0].getMSLevel()}")
print(f"First scan RT: {exp[0].getRT():.2f} s")
print(f"First scan m/z range: {exp[0].getMZRange()[0]:.4f} - {exp[0].getMZRange()[1]:.4f}")
这一小段代码能省掉你后面 80% 的 debug 时间。我个人每次加载数据都会跑一遍,相当于给数据“号个脉”。
组合视图:多面板图
实际工作中,我经常把 TIC、BPC 和某个质谱谱图放在同一张图里,以便在时间维和质谱维之间快速跳转。例如:
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
# ax1: TIC
ax1.plot(rts, tic, color='#9b59b6')
ax1.set_ylabel("TIC")
# ax2: BPC
ax2.plot(rts, bpc, color='#2ecc71')
ax2.set_ylabel("BPC")
# ax3: 选择保留时间 120s 处的质谱谱图
target_rt = 120
closest_idx = np.argmin(np.abs(rts - target_rt))
spec = exp[closest_idx] # 需要保证是 MS1
mz, ints = spec.get_peaks()
ax3.vlines(mz, 0, ints, color='#e74c3c', linewidth=0.6)
ax3.set_xlabel("m/z")
ax3.set_ylabel("Intensity")
ax3.set_xlim(100, 400) # 限制显示范围,避免太密
plt.tight_layout()
plt.show()
这样的组合图,在你调整预处理参数(比如保留时间窗口、m/z 容差)时特别有用。我看到哪个峰在 BPC 上比较突出,就迅速点开对应时间的质谱谱图,看看那团峰里到底有哪些离子——这也是我筛选候选特征最常用的方法之一。
本章小结
三种可视化——质谱谱图、BPC、TIC——构成了观察原始数据的三棱镜。谱图看细节,BPC 抓主峰,TIC 管全局。记住一个原则:在动手建特征之前,先画图。我曾因为跳过这一步吃过苦头,希望你不用再踩同一个坑。