2. 质谱数据预处理流程:基线校正、噪声平滑、峰检测

拿到原始质谱数据之后,别急着建模分析。我刚开始搞这行的时候,就犯过这个错误——一上来就怼算法,结果基线漂移能把任何定量分析都带跑偏。质谱仪出来的信号,你想想看,相当于一个没煮过的生肉,腥味大、毛刺多,得先收拾利索了才能下锅。

预处理流程,就是把这步“收拾干净”的工作标准化。说白了,三步走:把基线拉平,把噪声抹匀,再把峰给揪出来。每一步不到位,后面的定性定量全是白费功夫。

核心原则:预处理不是越重越好,而是每步只做该做的事,别把真实的峰信号给洗没了。

质谱数据预处理流程 原始质谱信号 基线校正 (移除漂移) 噪声平滑 (滤除高频) 峰检测 (找到信号峰) 预处理后数据 参数调优循环 ⚠ 经验提醒: 基线校正在噪声平滑之前做,否则先平滑会拉平基线,导致后续校正失效。顺序很重要。

2.1 基线校正 — 把漂移拉回平地

基线漂移是质谱的老朋友。尤其是MALDI、DESI这类离子源,基质效应、电荷积累、温度变化都会让基线慢慢往上翘或往下溜。你别小看这点漂移,我见过一个代谢组学项目,就因为没做基线校正,两组样本的差异峰全是假的。

常用的方法,我按个人经验排个序:

方法 原理 优缺点
多项式拟合 用低阶多项式拟合基线走势 简单快速,但对复杂漂移效果差
非对称最小二乘法 给残差加非对称权重,自动区分峰与基线 我常用。参数调好之后很鲁棒
局部中值/最小值 滑动窗口内取最小值作为基线 容易受残留噪声影响,不推荐

我个人习惯用非对称最小二乘法。为什么?说白了,它能自动判断哪些点是“峰上的”,哪些是“基线中的”,然后只对基线部分做加权拟合。Python里有现成的库,叫 pybaselines,里边就封装了这个算法。

关于参数的经验:lam 控制平滑程度,大的 lam 会让基线更直。我一般从 1e5 开始试。 p 是非对称权重系数,通常 0.001 到 0.1 之间,峰越密集用越小的值。

举个例子,一段调用的过程可能是这样:

from pybaselines import Baseline
baseline_fitter = Baseline(x_data)
# lam 控制平滑度,p 控制非对称权重
baseline, params = baseline_fitter.asls(
    y_raw, lam=1e6, p=0.01
)
corrected = y_raw - baseline

避坑指南:我曾经拿最大值窗口去做基线校正,结果把峰顶当成了基线,整个谱图的峰都翻过来了。记住,基线是信号的下界,不是上界。非对称最小二乘法里 p 值设反了也会出同样的问题——只能认0.01,不要瞎写 0.99。

2.2 噪声平滑 — 别把细节洗没了

基线拉平之后,信号里全是高频毛刺,尤其是那些质谱级数较高的数据,你看那个波纹,跟心电图似的。平滑的目的就是把这些毛刺去掉,但不能把真实的峰给抹平了。

常用的平滑方法,我列个表对比一下:

方法 核心理念 场景推荐
Savitzky-Golay 用多项式在滑动窗口内拟合 质谱信号首选,保留峰形好
移动平均 窗口内取平均 简单但钝化峰顶,不推荐细峰
小波去噪 小波分解后阈值处理 信噪比极差时用,但复杂

我最推荐的是Savitzky-Golay平滑。为什么?它在去除噪声的同时,非常努力地保持峰的高宽和形状。你想想看,如果用移动平均,窗口大了峰顶就被削平了,峰高就不准了。Savitzky-Golay是用一个滑动窗口内的多项式拟合,相当于在每个窗口里都做一次局部回归。

参数就两个:window_length 和 polyorder。

from scipy.signal import savgol_filter

# window_length 必须是奇数
# polyorder 一般取 2 或 3
y_smoothed = savgol_filter(
    corrected, window_length=11, polyorder=3
)

调参思路:window_length 从 5 开始试,逐渐增大,直到噪声明显减少为止。如果你发现峰开始变矮或变宽,那就是窗口开太大了。polyorder 我固定用 3,低于 2 平滑力量不够,高于 3 容易过拟合噪声本身。

嗯,这里要注意一点。我曾经为了把噪声压得更干净,把窗口拉到 21 点,结果两个挨得近的峰被直接“熔”成了一个峰。那是我做脂质组学分类项目的时候,少了一个关键峰,整个模型全废了。所以平滑的黄金法则是:用最小的窗口达到可接受的噪声水平。

2.3 峰检测 — 揪出真正的信号峰

数据磨好了,基线平了,噪声小了,接下来是重头戏:找峰。一个质谱峰有三个核心特征:位置(m/z)、强度(高度)、宽度(半高宽)。峰检测的目标就是把这三样东西准确算出来。

常见的检测策略:

  • 阈值法:设定信噪比阈值(S/N > 3),高于阈值的视为峰。最基础也最常用。
  • 导数法:一阶导数为零找极值,二阶导数判断峰型。对重叠峰分辨力好。
  • 峰形拟合法:用高斯或洛伦兹函数拟合每个峰。精准但计算量大。
  • 连续小波变换:把峰当作小波基的匹配,适合信噪比低的数据。

我实际项目中用得最多的是连续小波变换 + 阈值过滤。原因是它不像导数法那么敏感噪声,还能同时给出峰宽信息。Python 的 scipy.signal.find_peaks_cwt 早期版本有这功能,但现在我更推荐 scipy.signal.find_peaks 配合 prominence 参数使用。

from scipy.signal import find_peaks

# prominence 控制峰与周围最低点的突出高度
peaks, properties = find_peaks(
    y_smoothed,
    height=500,        # 最小峰高
    prominence=100,    # 最小突出度
    width=3,           # 最小峰宽(点数)
    distance=10        # 峰之间的最小间隔
)

# 输出峰位置(m/z索引)
mz_values = x_data[peaks]
intensities = y_smoothed[peaks]

prominence 是最有用的参数——它能过滤掉那些局部高但全图看很弱的“伪峰”。我见过很多人只设 height 不设 prominence,结果被基线残留的鼓包坑得一塌糊涂。

你可能会问:信噪比阈值设多少合适?我个人习惯说,信噪比 > 3 是检出的下限,但真实的做定量分析时,我至少要 S/N > 10 才放心。特别是低丰度峰,你信噪比只有 3,稍微一点噪声扰动就可能量变 30% 以上,根本不可复现。

避坑指南:我曾经做蛋白组学标记定量项目,把所有 S/N > 3 的峰都收进来做结果,结果有将近 20% 的“峰”是噪声脉冲。后来我加了一个 最小峰宽过滤(至少 3 个连续点大于阈值),假阳性立刻降到了 5% 以下。

2.4 三步流程的常见问题与参数调优

这三步不是孤立执行的。基线校正的参数会影响后续平滑和峰检测的效果。我建议你建立一个小型的参数扫描脚本,用一组已知正确峰的标注数据做验证,选最优的组合。别全靠直觉,直觉是靠不住的。

总结一下最常见的坑:

  • 顺序搞反:先平滑后基线校正 = 基线漂移被平滑固化,校正困难。
  • 平滑过度:两个真实峰被融成一个,丢失分辨力。
  • 基线校正过度:把真实信号的低谷部分当成了基线减掉,导致信号变负。
  • 峰检测阈值过低:假阳性满天飞,后续分析全白做。

我个人的工作流是:先跑一组默认参数看效果,然后用可视化检查——画一张叠加图,原始信号、基线、校正后信号一起看。眼睛是最快的筛查工具。等肉眼觉得差不多了,再用一组已知标准品的数据跑定量验证。

推荐工具:除了上面说的 Python 库,开源的 OpenMS 和 MZmine 3 里也集成了这些流程。不想写代码的时候,用 MZmine 的图形界面调参数更直观。