序列ID与元数据解读
1. 序列ID——被低估的数据入口
FASTQ 文件里,大多数人第一眼会去看碱基质量和序列。说实话,我刚开始做 NGS 分析时也一样——直接跳过 ID 行,盯着碱基和质量分数看半天。
直到有一次,一批数据在比对时出现了大量异常比对率。排查了两天,最后发现是序列 ID 中的配对信息标错了。机器把同一对 reads 标记成了 R1/R1,而不是 R1/R2。嗯,从那以后,我再也不敢跳过 ID 行。
序列 ID 其实就是这条 reads 的身份证。它里面不光有样本标识,还藏着仪器编号、跑道位置、簇坐标、配对关系、过滤状态、甚至 barcode 序列。说白了,这是一组结构化的元数据,只是被压缩成了一个字符串。
读懂了 ID,你就等于拿到了数据质量的第一手信息。
2. Illumina ID 格式全解析
目前市面上超过 70% 的 NGS 数据来自 Illumina 平台。它的 ID 格式也成了事实上的行业标准。你想想看,看懂了一套格式,就能应付大多数场景。
一个典型的 Illumina FASTQ ID 长这样:
@EAS139:136:FC706VJ:2:2104:15343:197393 1:Y:18:ATCACG
这串东西由空格分成左右两部分。前半段是核心标识,后半段是辅助信息。我习惯把前半段叫做「坐标段」,后半段叫做「生化段」。
下面这个表拆开了每个字段的含义:
| 段位 | 字段 | 示例 | 含义 |
|---|---|---|---|
| 坐标段 | 仪器 ID | EAS139 | 测序仪唯一编号 |
| 坐标段 | 流道编号 | 136 | Flowcell 的 lane 编号 |
| 坐标段 | Tile 编号 | FC706VJ | 芯片上的 tile 区域 |
| 坐标段 | X 坐标 | 2 | 簇在 tile 内的 X 位置 |
| 坐标段 | Y 坐标 | 2104 | 簇在 tile 内的 Y 位置 |
| 生化段 | 配对编号 | 1 | 1=R1, 2=R2 |
| 生化段 | 过滤状态 | Y | Y=通过过滤, N=未通过 |
| 生化段 | 控制位 | 18 | 控制碱基编号或保留字段 |
| 生化段 | 索引序列 | ATCACG | 样本 barcode 序列 |
为什么要拆这么细?因为在质控流程里,这些字段各有用途。举个例子,通过 X/Y 坐标可以判断 reads 是否集中在某个 tile 上——如果数据只来自少数几个 tile,说明上样可能不均匀。
3. 跨平台 ID 格式对比
不是所有平台都用 Illumina 那套格式。我在项目里接触过 Ion Torrent、PacBio 和 Nanopore 的数据,它们的 ID 风格差异很大。不熟悉的话,很容易在解析时出错。
| 平台 | ID 示例 | 分隔符 | 编码内容 |
|---|---|---|---|
| Illumina | @EAS139:136:FC706VJ:2:2104:15343:197393 1:Y:0:ATCACG | 冒号 / 空格 | 仪器、坐标、配对、过滤、barcode |
| Ion Torrent | @IonXpress_001_1 | 下划线 | 样本 ID、read 编号 |
| PacBio | @m54006_191218_182344/0/0_0 | 下划线 / 斜杠 | 仪器、日期时间、孔号、ZMW 编号 |
| Nanopore | @b8d5c0e2-1a3f-4d6b-8c9e-0f1a2b3c4d5e | 连字符 | UUID 唯一标识(不含生物学元数据) |
看出差别了吗?Illumina 的 ID 是「富元数据」结构,一条 ID 就包含了测序全过程的关键参数。而 Nanopore 直接用 UUID,元数据需要单独去读取 fast5 或 pod5 文件。
这个区别在实际处理中非常重要。你写解析脚本时必须先判断平台类型,再选择对应的解析策略。
4. 元数据提取与实战应用
理解结构之后,下一步就是写代码把元数据从字符串里提取出来。下面是我常用的解析方法。
Python 解析 Illumina ID:
def parse_illumina_id(read_id):
"""
解析 Illumina FASTQ ID,返回元数据字典
输入: '@EAS139:136:FC706VJ:2:2104:15343:197393 1:Y:0:ATCACG'
输出: 结构化字典
"""
# 去掉开头的 @,按空格拆成坐标段和生化段
raw = read_id.lstrip('@')
parts = raw.split(' ')
# 坐标段:用冒号拆分
coord_fields = parts[0].split(':')
meta = {
'instrument': coord_fields[0],
'flowcell': coord_fields[1],
'tile': coord_fields[2],
'x': coord_fields[3],
'y': coord_fields[4]
}
# 生化段:用冒号拆分(如果有的话)
if len(parts) > 1:
bio_fields = parts[1].split(':')
meta['pair'] = int(bio_fields[0])
meta['filtered'] = bio_fields[1] # 'Y' 或 'N'
meta['control'] = int(bio_fields[2])
meta['index'] = bio_fields[3] if len(bio_fields) > 3 else ''
return meta
# 使用示例
test_id = '@EAS139:136:FC706VJ:2:2104:15343:197393 1:Y:0:ATCACG'
result = parse_illumina_id(test_id)
print(result)
从 FASTQ 批量提取元数据统计:
# 用 awk 一行命令统计过滤状态为 N 的比例
awk -F ' ' 'NR%4==1 {
split($2, arr, ":");
if (arr[2] == "N") n_count++;
total++
} END {
print "N reads:", n_count;
print "Total reads:", total;
print "N ratio:", n_count/total;
}' sample.fastq
提取出的元数据怎么用在质控里?我总结了三个常用场景:
- 空间偏差检测:统计每个 tile 的 reads 数量,如果某些 tile 的 reads 数明显偏低,可能是上样不均匀或局部测序失败。
- 样本拆分验证:利用索引序列(barcode)统计每个样本的 reads 比例,与实验设计对比,检查是否存在交叉污染。
- 过滤状态监控:跟踪 Y/N 比例的变化趋势。如果 N 比例突然升高,说明仪器 basecall 质量在下降。
5. 常见问题与避坑指南
解析序列 ID 看起来简单,但我在实际项目中踩过不少坑。下面几个问题最常见:
- ID 被截断:有些下游工具会对 ID 做截断处理,导致信息丢失。我在做流程串联时就遇到过,BWA 输出的 BAM 里 ID 只剩前半段,后半段生化信息全没了。建议在预处理阶段就把元数据提取并保存到单独的文件里。
- 格式版本混用:同一个项目中混合了 CASAVA 1.8 之前和之后的格式。老格式里没有索引序列字段,字段数量不一样。解析脚本必须做版本检测,否则会越界报错。
- 特殊字符转义:ID 里可能包含 @、空格、冒号等特殊字符。用正则或 split 时要注意转义。我建议直接用 Python 的 split 加固定分隔符,比正则更安全。
我建议的标准化解析策略:
- 第一步:用正则匹配判断平台类型(Illumina / Ion / PacBio / Nanopore)
- 第二步:按平台选择对应的解析模板
- 第三步:对关键字段(过滤状态、配对编号、索引序列)做合法性校验
- 第四步:把解析结果写入 JSON 或 TSV,和 FASTQ 并行存储