4. 数据完整性检查:缺失值检测、扫描一致性
数据完整性,说白了就是看你的质谱数据有没有「缺胳膊少腿」。我之前处理过一批临床样本的数据,刚开始跑得挺顺,结果后面发现某个时间段的谱图全是空的。一查,是采集软件中途崩了,但日志里没报错。这种事其实不少见,所以完整性检查必须放在质控流程的第一步。
核心观点:数据完整性检查就像在盖楼前检查地基,地基不牢,后面再牛的算法都白搭。缺失值和扫描不一致,是地基上最常见的两条裂缝。
4.1 缺失值检测
缺失值,就是本该有数据的地方,它没来。在质谱数据里,缺失值可能出现在三个层面:
- 扫描级缺失:某个扫描周期的信号全为空或接近零值。
- 特征级缺失:某个保留时间-质荷比组合下,没有检测到峰。
- 样本级缺失:整张谱图都丢了,或者文件损坏无法读取。
我个人的习惯是先做扫描级和样本级的检查,因为这两层最容易定位根因。
4.1.1 扫描统计指标
常用的几个指标,直接看表格:
| 指标 | 定义 | 阈值参考 |
|---|---|---|
| 空扫描占比 | 总强度低于噪音阈值的扫描数 / 总扫描数 | < 5% |
| 扫描强度变异系数 | 各扫描总强度的变异系数 | < 30% |
| 缺失特征比例 | 缺失的特征数 / 总特征数 | < 20% |
| 连续缺失窗口 | 连续缺失的最大扫描数 | < 5 个扫描 |
你可能会问:这些阈值怎么来的?嗯,这里要注意,没有万能阈值。我一般会根据仪器类型和样本基质来调。比如对于高分辨质谱,空扫描占比超过2%我就开始警觉了;但对于低分辨率的在线监测数据,5%以内也算正常。
4.1.2 常用检测方法
分三种:统计扫描法、可视化检查法、基于规则的检测法。
统计扫描法
最直接的方式,就是计算每个扫描的TIC(总离子流)强度分布。如果某个扫描的TIC低于全局TIC中位数的10%,我基本会把它标为「空扫描」。代码里可以这样做:
import numpy as np
def detect_missing_scans(tic_array, noise_fraction=0.1):
tic_median = np.median(tic_array)
threshold = tic_median * noise_fraction
missing = np.where(tic_array < threshold)[0]
return missing
missing_scans = detect_missing_scans(tic_data)
print(f"发现 {len(missing_scans)} 个空扫描")
我在项目中遇到过,某次用这个方法检测时,发现一批数据里有连续30个空扫描。当时还以为是仪器故障,结果后来发现是进样针堵了。你看,这种就是通过统计扫描法很快能锁定的问题。
可视化检查法
有时候数字不直观,我会做个热图或者折线图。把扫描序号当x轴,TIC强度当y轴,一眼就能看出哪里断流了。不过,我一般不依赖纯人工检查,它会和统计扫描法配合用。
基于规则的检测法
这个方法适合处理已知规律的数据。比如你的方法是运行5分钟,前1分钟是溶剂前沿,中间3分钟出峰,最后1分钟是冲洗——那就可以定规则:前1分钟的空扫描可以接受,但如果中间3分钟有超过2个连续空扫描,就要报警。
def rule_based_detection(tic_array, scan_times, method_config):
# method_config: {start_min, end_min, max_gap}
middle_mask = (scan_times >= method_config['start_min']) & \
(scan_times <= method_config['end_min'])
middle_tic = tic_array[middle_mask]
# 找连续空扫描序列
missing = middle_tic < (np.median(tic_array) * 0.1)
gap_lengths = np.diff(np.where(np.diff(np.concatenate(([False], missing, [False]))))[0])[::2]
if np.any(gap_lengths > method_config['max_gap']):
return True # 不合格
return False # 合格
我的经验:规则检测法的「规则」一定要在实验前就定好,边检测边调规则很容易过拟合。我曾经吃过这个亏,调出来的规则只适用于那一批数据,换批样本全废了。
4.2 扫描一致性检查
缺失值查完了,接下来要看扫描的一致性。什么叫一致性?就是你这次采集的10000个扫描,和上次采集的10000个扫描,它们的分布特征是不是高度相似的?说白了,就是看仪器在同样的实验条件下,有没有「心不在焉」。
4.2.1 需要检查哪些方面
我觉得至少要覆盖三个维度:
- 保留时间对齐度(Retention Time Alignment)—— 峰的位置有没有偏移。
- 质量轴校准稳定性(Mass Axis Stability)—— m/z 漂移在不在容忍范围内。
- 峰检测一致性(Peak Consistency)—— 同样的样本,峰数量、峰面积有没有突变。
你可以把这三个指标放进一个表格里,定期打勾,像做体检一样:
| 检查项 | 度量方式 | 合格标准 |
|---|---|---|
| 保留时间偏移 | 内标峰RT的变异系数 | CV < 1% |
| 质量轴漂移 | 已知离子m/z偏差的绝对值 | < 5 ppm |
| 峰数量变异 | 连续3针的峰数量差异 | < ±10% |
| 总峰面积波动 | 各扫描TIC的变异系数 | CV < 15% |
4.2.2 实际操作示例
假设我们有一批LC-MS数据,先加载并计算每针的TIC分布。这个示例展示了如何计算保留时间窗内的一致性:
def check_rt_consistency(peak_list, rt_window=0.1):
# peak_list: [(rt, intensity), ...]
for i in range(1, len(peak_list)):
rt_diff = abs(peak_list[i][0] - peak_list[0][0])
if rt_diff > rt_window:
return False, f"RT偏移超过 {rt_window} min"
return True, "RT一致性通过"
具体到实际项目,我会再加上一个滑动窗口来扫描整个色谱段。因为我遇到过一个情况:前几分钟的保留时间完全对得上,到了后面几分钟偏移越来越大。只用单个点检查,根本抓不到。
避坑提示:做扫描一致性检查时,别只看平均值。我记得有次跳过全程窗口检查,只取中间3分钟做统计,结果全都合格。后来发现前1分钟和后1分钟的峰面积差异巨大,粗看是没问题的,实际上数据已经不能用了。所以,要做全程逐段的切片检查,别偷懒。
4.3 数据完整性检查的整体流程
把缺失值检测和扫描一致性检查串成一条流水线,每次数据采集完就跑一遍。这个流程图我习惯拿来做日常质控的骨架:
这张图上画得很清楚:数据进来后先做文件级别的完整性检查,然后扫缺失值,再查扫描一致性,最后二分法决定能不能往下走。
我的小技巧:把流程自动化。每次采集结束后,后台自动跑一遍这个检查链,把结果写到日志里。我早期都是手动跑,但有时候忙起来就忘了。自从写了个批处理脚本老老实实跑之后,再没漏检过。
4.4 小结与注意事项
数据完整性检查是质控的第一道门。我个人总结了几条必记内容:
- 缺失值检测要区分层面:扫描级、特征级、样本级,不能混在一起看。
- 扫描一致性检查要覆盖保留时间、质量轴、峰特征三个维度。
- 阈值不是死的,要根据仪器历史和样本类型动态调整。
- 流程要自动化,不能完全依赖人工。人工的强项是诊断,不是扫描。
我曾经有一个案例就是:用了一套固定的阈值跑了半年,一直没问题。结果某次换了批血浆样本,缺失值占比飙到12%,但我的阈值是20%,直接通过了。然后下游的定量分析结果出现系统性偏差,查了两天才找到原因——样本基质抑制导致部分扫描强度被大幅压低。自那以后,我每个项目都重设阈值,不再复用之前的。
一句话记住:缺了的数据不会自己回来,不一致的信号只会误导你。数据完整性检查,是你在高分辨质谱海量数据处理前,给自己上的第一道保险。