01 质谱数据简介

做质谱数据分析这几年,我最常被新手问到的一个问题就是:“这些文件到底是什么东西?”拿到一台质谱仪,随便跑一针样品,出来的文件后缀可能是 .raw、.mzML、.mzXML……看着一堆乱码一样的二进制文件,确实挺懵的。

别急,这一节我就带你把质谱数据的底子摸清楚。搞懂了它,后面清洗、建模、验证的路才会走得顺。

质谱仪到底干了件什么事?

说白了,质谱的核心就四个字:“称重”。但不是称你手里的东西,而是称离子在真空中的飞行能力。

我习惯把它分成三步来理解:

  1. 离子化:样品进来,被电离成带电粒子。
  2. 质量分离:根据质荷比(m/z)把这些离子分开。
  3. 检测:记录每个 m/z 对应的离子强度。

你最终拿到的,就是一大堆 (m/z, 强度) 的数据对。不同时间点扫一次,就得到一张质谱图。连续扫下去,就有了时间轴上的谱图序列——我们叫它 LC-MS 数据。

一个小比喻:你可以把质谱仪想象成一个超市收银台。每个商品(离子)都有条形码(m/z),收银员(检测器)扫一下,告诉你这个商品卖了多少件(强度)。

嗯,这里有个坑要注意:质谱数据不是单一的数值数组,而是 三维的——保留时间、质荷比、强度。你清洗数据时,这三样东西一个都不能丢。

常见的数据格式,哪个最靠谱?

说到格式,我之前踩过不少坑。2018 年我第一次处理某品牌高分辨质谱的 .raw 文件,差点崩溃——只能用厂商自己的软件打开,换一台电脑就报错。所以我现在做项目,第一件事就是问:“能转成 mzML 吗?”

来,我把三种主流格式的特点整理了一下:

格式 全称 类型 典型特点
.raw Thermo 专有格式 二进制,厂商封闭 只能用 Xcalibur、FreeStyle 打开;数据量大,通常 1 GB+
.mzXML Mass Spectrometry XML XML,开放 早起标准,结构清晰;但文件膨胀,解析慢
.mzML Mass Spectrometry Markup Language XML,开放(HUPO-PSI 标准) 当前最推荐的格式;同时支持索引化和压缩编码

我个人最推荐的是 mzML。为什么呢?

  • 标准化:HUPO-PSI 组织推动的,厂商和工具都认。
  • 可索引:支持随机访问,不用读完整个文件就能找到特定扫描。
  • 压缩友好:可以用 zlib 对 m/z 和强度数组进行压缩,文件大小能降到 .raw 的 30%~50%。

我的经验之谈:项目开始时,如果条件允许,直接让仪器导出 mzML。能省掉后面很麻烦的格式转换环节。

mzML 文件内部长什么样?

有一次我调试一个识别算法的 bug,不得不手撕 mzML 文件。打开一看,结构其实挺清晰的:

<?xml version="1.0" encoding="UTF-8"?>
<mzML xmlns="http://psi.hupo.org/ms/mzml">
  <cvList>...</cvList>
  <fileDescription>...</fileDescription>
  <referenceableParamGroupList>...</referenceableParamGroupList>
  <sampleList>...</sampleList>
  <softwareList>...</softwareList>
  <instrumentConfigurationList>...</instrumentConfigurationList>
  <dataProcessingList>...</dataProcessingList>
  <run>
    <spectrumList count="1250">
      <spectrum index="0" id="scan=1">
        <cvParam name="ms level" value="1"/>
        <cvParam name="base peak intensity" value="4590821.0"/>
        <binaryDataArrayList count="2">
          <binaryDataArray encodedLength="5632">
            <cvParam name="m/z array" unitName="m/z"/>
            <binary>BASE64_ENCODED_DATA</binary>
          </binaryDataArray>
        </binaryDataArrayList>
      </spectrum>
    </spectrumList>
  </run>
</mzML>

核心就是 <spectrum> 标签。每个 spectrum 代表一次扫描,里面记录着 m/z 和强度数据。这些数据是 Base64 编码的二进制,直接读不了,得用专门的库去解码。

注意:千万不要自己去写 Base64 解码 + 解压缩的脚本。pymzML、mzjava、OpenMS 这些库已经帮你封装好了。我曾经图省事手写过一次,结果精度对不上,排查了三天才发现是字节序搞反了。

知识体系总览

这一节的内容,我画了张图帮你把关系理清楚:

质谱数据简介 · 知识结构图 质谱数据基础知识 质谱仪工作原理 离子化 → 分离 → 检测 输出: (m/z, 强度) 对 三种常见文件格式 .raw(厂商封闭二进制) .mzXML(早期XML标准) .mzML(推荐标准,索引+压缩) mzML 内部结构 <spectrumList> <binaryDataArray> Base64 + zlib 压缩 ▸ 清洗前必须理解:数据是“三维”的(保留时间 × m/z × 强度) ▸ 格式选不对,后面所有工作都得推到重来

该不该自己做格式转换?

你可能会问:“我拿到的是 .raw,是不是得自己写个转换器?”我劝你千万别这么干。

市面上的开源工具已经很成熟了:

  • msconvert(ProteoWizard 工具包):最常用的格式转换神器。支持从 .raw 转出 mzML、mzXML,还能做峰值挑选。
  • OpenMS / TOPP tools:整个质谱数据处理流程的工具箱,FormatConverter 就是它的转换模块。
  • pymzML:Python 接口,适合写脚本批量处理。我经常用它做小规模的文件格式校验。

举个例子,一条命令行搞定:

msconvert input.raw --mzML --zlib --outdir ./converted/

这条命令会把 input.raw 转成 mzML 格式,同时开启 zlib 压缩。在我的经验里,压缩后文件体积能缩小一半以上,而且解析速度一点也没变慢。

一个小技巧:在处理大规模队列样本时,我习惯先随机抽 3~5 个文件做格式转换和解析测试,确认参数一致后,再启动全量转换。能避免因为批次间参数设置不同导致后面没法对齐的坑。

数据清洗前,你至少要确认这三件事

好,工具选好了,格式也转了。但别急着开始清洗。我踩过的坑告诉你,先做三件事:

  1. 检查文件完整性:mzML 有没有被截断?可以用 xmllint --valid 做一遍语法校验。
  2. 确认扫描数:跑了 60 分钟的梯度,预期的扫描数是 6000 吗?少了说明仪器可能中途掉线了。
  3. 看总离子流图(TIC)是否正常:TIC 曲线如果断层了,那大概率是液相那边出了问题,数据清洗再多也没用。

说到 TIC,我记得有次帮一个合作方排查数据质量,他们做了 300 个样本的蛋白质组学分析,结果鉴定到的蛋白数少得可怜。后来让我看 .raw 文件转 mzML 的过程,发现转换时默认只保留了 MS1 扫描,把 MS2 全扔了。嗯,就是这么个小参数,导致整个项目白做了一半。

所以我现在拿到任何质谱数据,第一件事就是先用 pyOpenMS 写个小脚本,快速拉一下基本信息:

import pyopenms as oms

exp = oms.MSExperiment()
oms.MzMLFile().load("sample.mzML", exp)

print("光谱数量:", exp.getSpectra().size())
print("色谱数量:", exp.getChromatograms().size())
print("MS 级别范围:", exp.getMinMSLevel(), "-", exp.getMaxMSLevel())

tic = exp.calculateTIC()
print("总离子流(TIC)曲线长度:", len(tic))

这段代码跑完,我心里就有底了。数据没问题,才敢往下做清洗和模型训练。