3. Illumina 测序平台命名规则详解:仪器ID、流道、位置、双端索引

好,咱们进入第三章。我相信你手头肯定遇到过一堆长文件名,看起来像一串乱码。别慌,Illumina的命名规则其实非常固定,它就是一把钥匙。只要掌握了这把钥匙,提取批次信息就像查字典一样简单。

我个人习惯,拿到一个FASTQ文件,第一件事不是打开看内容,而是先盯着文件名看十秒钟。为什么?因为文件名已经把样本的“出生证明”写得明明白白了——在哪台机器上测的、在芯片哪个位置、用了哪个文库。

3.1 完整文件名结构解析

Illumina的FASTQ文件命名格式,说白了就是一串用下划线拼接的字段。我拿一个真实项目的例子跟你讲:

Sample1_S1_L001_R1_001.fastq.gz

这个文件名拆开来看,各个部分的含义如下表:

字段 示例值 含义
样本名 Sample1 由用户或样本表自定义,用于区分不同样本
样本编号 S1 Illumina软件自动分配的编号,同一run内唯一
Lane编号 L001 表明该数据来自哪个lun(流道),三位数补零,比如L001
Read方向 R1 双端测序时R1代表第一端,R2代表第二端
Set Partition 001 拆分的块编号,如果单个文件过大就会拆成多个
小技巧: 我建议你把这个结构记下来。你在脚本里做批量解析时,直接用下划线分割就能拿到每个字段。我曾经有一次连续跑了两个批次,文件名结构完全一样,只是Lane编号不同,用这个规则一秒就分开。

3.2 仪器ID与Run编号

上面的最简格式其实还少了一个重要字段——完整格式里仪器ID和Run编号会作为前缀。你再看看这个:

MISEQ01_20231017_Sample1_S1_L001_R1_001.fastq.gz

嗯,这里要注意,“MISEQ01”就是仪器ID,“20231017”通常是测序开始的日期(作为Run编号)。为什么我要提这个?因为批次信息提取的核心就靠这两个字段。不同仪器之间试剂的剂量、出错概率都可能有差异,如果不区分仪器就把所有FASTQ混在一起分析,你可能会踩坑。

我把常见仪器的命名前缀列出来,你参考一下:

平台 典型仪器ID样式
MiSeq MISEQ01、M02134
NextSeq 500/550 NB501234、VH00123
HiSeq 2500/4000 HISEQ01、D00535
NovaSeq 6000 A00123、NS500256

你看,每个仪器都有自己的“身份证号”。我建议你在做批次汇总时,把仪器ID作为分组的第一层key,这样能迅速看出哪个批次的数据是同一台机器产出的。

3.3 流道(Lane)与位置信息

Illumina的测序芯片通常有多个流道,数据产出后每个流道会生成独立的FASTQ文件。比如L001、L002、L003、L004分别代表四个流道。

为什么会这样设计?你想想看,同一张芯片上不同流道的数据,如果某个流道出现气泡或者堵塞,只影响那个流道的质量。如果我在处理数据时发现某个Lane的读段比例明显偏低,我就会怀疑那个流道有问题,直接跳过不用,其他流道的数据照样能用。

有的平台还会包含Tile位置信息,不过那个通常只在中间文件或者BCL转换日志里出现。比如:

Sample1_S1_L001_R1_001.fastq.gz   # 标准文件
1101_20231017_1_1101_20231017.bcl  # BCL转换日志里会有tile编号
避坑指南: 我曾经遇到过一个项目,测序公司给的文件名里Lane编号写成了"Lane01"而不是"L001"。你直接用分割下划线的方法会取到"Lane01",导致批量脚本报错。我的处理方式是统一用正则匹配L\d{3}模式,这样无论叫L001还是Lane001都能正确提取。

3.4 双端索引(Index)与样本区分

现在的Illumina测序都走双端索引(dual index)。什么意思?就是一条读段有两个Index,一个叫i7,一个叫i5。它们被写在FASTQ文件的header里,而不是文件名里。不过,文件名通常会在样本名里体现索引信息。

你看这个示例:

SampleA_GTAGAGGA-CTCTCTAT_L001_R1_001.fastq.gz

“GTAGAGGA”是i7索引序列,“CTCTCTAT”是i5索引序列。这个组合就是这条数据的“指纹”。

为什么强调这一点?因为双端索引大大提高了多路复用(multiplexing)的准确性。以前单端索引容易串样,现在双端索引很少出现index hopping的问题。不过,我做生信分析时还是会验证一下——把FASTQ header里的索引序列和样本表做比对,确认样本没有被交换。

提取索引序列的方法其实很简单,用zcat加head就能看到:

zcat SampleA_GTAGAGGA-CTCTCTAT_L001_R1_001.fastq.gz | head -1

输出大概长这样:

@A00123:1:1101:1000:10000#GTAGAGGA+CTCTCTAT 1:N:0:GTAGAGGA+CTCTCTAT

我一般用+号前后两部分来识别索引,加号前是i7,加号后是i5。一个经验之谈:如果跑完测序发现某个样本的读段数特别少,先去看看FASTQ header里的索引序列跟你当初录入的是不是一致,经常有人写错样本表。

核心要点: 仪器ID + Run编号 + Lane编号 + 索引序列,这四个字段组合起来就是一个绝对的批次标识。只要这四个不变,数据就属于同一个批次。
Illumina FASTQ命名规则结构图 Illumina FASTQ 文件名字段分解 SampleA_GTAGAGGA-CTCTCTAT_L001_R1_001.fastq.gz 仪器ID + Run编号 例如:MISEQ01_20231017 样本名 + 索引序列 例如:SampleA_GTAGAGGA-CTCTCTAT 流道Lane L001 ~ L004 Read方向 R1/R2 + 分区 批次标识四元组 仪器ID + Run编号 + Lane编号 + 索引序列 → 确定唯一批次

3.5 实战:手写一个命名解析函数

说了这么多,不如直接上代码。我一般用Python写一个解析函数,目标就是从文件名里提取出仪器ID、Run编号、Lane、索引序列这四个关键字段。下面是我在几个项目中反复使用的一个版本:

import re

def parse_fastq_filename(filename):
    """
    解析Illumina FASTQ文件名,返回批次信息字典。
    支持两种格式:
      1. Sample_Index_L001_R1_001.fastq.gz
      2. Instrument_RunDate_Sample_Index_L001_R1_001.fastq.gz
    """
    # 去掉.gz后缀
    base = filename.replace('.fastq.gz', '')
    
    # 定义输出结构
    info = {
        'instrument': '',
        'run_date': '',
        'sample_name': '',
        'index_i7': '',
        'index_i5': '',
        'lane': '',
        'read_direction': ''
    }
    
    # 按下划线分割
    parts = base.split('_')
    
    # 判断是否包含仪器信息:第一段是字母数字混合且不是标准样本名
    # 如果第一段包含字母和数字(长度<10),认为是仪器ID
    if re.match(r'^[A-Za-z0-9]{4,8}$', parts[0]):
        info['instrument'] = parts[0]
        info['run_date'] = parts[1]
        parts = parts[2:]
    
    # 提取样本名和索引:惯例是样本名_索引i7-索引i5
    sample_field = parts[0]
    # 如果有连字符,说明样本名和索引是分开的
    if '-' in sample_field:
        # 有的样本名本身带下划线,比如Sample_01_GTAGAGGA-CTCTCTAT
        # 这里简化处理:认为最后一个带连字符的字段是索引
        # 更健壯的做法是用正则匹配索引模式
        idx_match = re.search(r'([ATCG]+)-([ATCG]+)', sample_field)
        if idx_match:
            info['index_i7'] = idx_match.group(1)
            info['index_i5'] = idx_match.group(2)
            # 剩余部分作为样本名
            info['sample_name'] = sample_field.split('_')[0] if '_' in sample_field else sample_field
        else:
            info['sample_name'] = sample_field
    else:
        info['sample_name'] = sample_field
    
    # 提取Lane编号
    for part in parts:
        if part.startswith('L') and len(part) == 4 and part[1:].isdigit():
            info['lane'] = part
            break
    
    # 提取Read方向
    for part in parts:
        if part in ['R1', 'R2']:
            info['read_direction'] = part
            break
    
    return info

# 示例用法
if __name__ == '__main__':
    test_files = [
        'SampleA_GTAGAGGA-CTCTCTAT_L001_R1_001.fastq.gz',
        'MISEQ01_20231017_SampleA_GTAGAGGA-CTCTCTAT_L001_R1_001.fastq.gz',
    ]
    for f in test_files:
        result = parse_fastq_filename(f)
        print(result)
使用建议: 把这个函数放在你的生信pipeline工具包里。每次拿到新批次的数据,先用它把所有文件名解析成结构化字典,然后导出成CSV或YAML,后续合并、质控、去重都能复用这个批次信息表。

这个函数覆盖了最常见的两种格式。实际项目中,我遇到过一些测序公司习惯在文件名里附加项目号(比如Proj001_),这时你只需要在parts切片那里多判断一下。记住核心逻辑:不管格式怎么变,仪器、Run、Lane、索引这四个字段一定在文件名里出现一次。

总结一下这章的关键:Illumina FASTQ文件命名不是乱写的。它是一份结构化的元数据。仪器ID、Run编号、Lane编号、索引序列,四者合一就是批次。你学会了拆解,后续的样本追踪、重复测序判断、批次效应分析都变得非常直接。