FastQ 文件格式:结构解析与质量分数编码

说到测序数据,我们最先接触的就是 FastQ 文件。嗯,我刚开始做生信那会儿,拿到一个几百兆的 FastQ 文件,直接打开看,密密麻麻的字符,完全不知道它想表达什么。后来搞明白了,其实结构很简单,就四行一个记录。

四行结构,一个 read

每个 FastQ 文件里,一条 read 占 4 行,顺序固定。你想想看,这比 FASTA 多了一行质量分数,就是那把衡量数据好坏的尺子。

行号 标识符 内容说明
第1行 @ 序列标识符(read ID),包含仪器、坐标、barcode 等信息
第2行 (无) 碱基序列(A、T、C、G、N)
第3行 + 分隔符,有时跟着相同的 ID(可选)
第4行 (无) 与第2行碱基一一对应的质量分数(ASCII 字符)

这是标准格式,千万别搞错顺序。我遇到过有人把质量分数行当成了碱基序列,结果下游分析全乱套。说白了,第2行和第4行长度必须相等,否则就是坏文件。

一个小习惯:我会先用 less 看文件前几行,确认 @ 开头、+ 行、长度一致。一个简单的命令就能避开大坑。

质量分数编码的演变史

质量分数不是直接存的数字,而是用一个 ASCII 字符来表示。为什么这样做?因为纯文本文件里存二进制数字太占空间,用一个字符代表一个 Phred 值更高效。这里面涉及三种编码方式,也是我们分析时必须搞明白的「暗号」。

Sanger / Phred+33 编码

这是最通用的标准,目前绝大多数数据都用它。Phred 质量分数 Q = -10 × log₁₀(P),P 是碱基识别错误的概率。Q 值范围 0~93,但实际测序中很少超过 60。它用 ASCII 码 33~126 表示,也就是从 '!' 开始。

我早期做人类基因组重测序,拿到的是 Sanger 编码的 FastQ,直接用 seqtk 查看,字符像 'I'、'J'、'K' 这些大写字母,一看就是高质量。

转换公式: Q = ASCII值 − 33

举例:字符 '?' 的 ASCII 为 63,则 Q = 63 − 33 = 30

Illumina 1.3+ / Phred+64 编码

Illumina 早期机器(GA、GA II)使用这种编码。ASCII 偏移量是 64,范围从 '@'(ASCII 64)到 'h'(ASCII 104),对应 Q 值 0~40。我记得当时处理过一批 Solexa 数据,发现质量分数全是 '@'、'A'、'B' 之类的小写字母,跟常见的不一样。后来一查,哦,偏移量不同。

不过现在 Illumina 的新机器(HiSeq/NovaSeq)早就改用 Sanger 编码了。如果你遇到老数据,必须手动识别或者用工具转换。

Illumina 1.5+ / Phred+64 变种

这个版本有些特殊。它也是为了兼容高 Q 值,但把 0 ~ 2 这三个低质量值做了统一映射:所有 Q ≤ 2 都编码为 'B'(ASCII 66)。这样做是为了节省字符空间?我不太确定,但你在处理 1.5+ 数据时,如果看到质量分数字符全是 'B' 开头,说明低质量碱基被统一压缩了。

编码方式 ASCII 偏移量 质量分数范围 常见字符特征 典型应用
Sanger(Phred+33) 33 0~93 !'$%&'()*+,-./0123456789:;<=>?@ABCD... Illumina >= 1.8、Sanger、PacBio、ONT
Illumina 1.3+(Phred+64) 64 0~40 @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh Illumina GA / GA II
Illumina 1.5+(Phred+64 变种) 64 0~40(低质量合并) B 字符对应 Q ≤ 2,其他同上 Illumina 1.5 软件版本
曾经踩过的坑:有次 batch 数据,一半是 Sanger 编码,一半是 Illumina 1.3+,我没注意直接合并了,结果质量过滤时 Q30 阈值对应不同的 ASCII 字符,导致过滤标准不一致。后来我养成了习惯:上来先检测编码方式。

如何快速判断 FastQ 的编码方式?

手动翻看质量分数那一行的字符分布,基本能猜个八九不离十。下面是我常用的经验法则:

  1. 看第一个字符:如果是 '!',基本是 Sanger(ASCII 33)。
  2. 看有没有 '@' 或 'A':如果出现大量 '@'(ASCII 64)且没有 '!',很可能是 Illumina 1.3+。
  3. 看有没有 'B' 打头的连续低质量段:如果有,且整体偏移像 64,那就是 1.5+。
  4. 用程序统计 Q 值分布:更保险的方式是写个小脚本,把字符转成 ASCII 再减偏移量,看最大值是否超过 40。

我这里有一个快速的 Python 片段,你可以直接拿来用:

def guess_encoding(fastq_file):
    with open(fastq_file) as f:
        for i, line in enumerate(f):
            if i % 4 == 3:  # 质量分数行
                chars = line.strip()
                ascii_vals = [ord(c) for c in chars]
                min_val = min(ascii_vals)
                max_val = max(ascii_vals)
                if min_val >= 33 and max_val <= 73:
                    return "Sanger (Phred+33)"
                elif min_val >= 64 and max_val <= 104:
                    if chr(min_val) == 'B':
                        return "Illumina 1.5+ (Phred+64 variant)"
                    else:
                        return "Illumina 1.3+ (Phred+64)"
                else:
                    return "Unknown"
            if i > 100: break
    return "Could not determine"

这段代码简单实用,我放在每次质控管线的第一步。如果返回 "Unknown",我会仔细看一下原始字符的 ASCII 范围,再结合平台说明判断。

ASCII 偏移量的本质

其实说白了,偏移量就是个「起点」。Sanger 用 '!' 代表 Q=0,Illumina 1.3+ 用 '@' 代表 Q=0。你想想看,如果直接存数字 30 和 40,要两个 ASCII 字符;但存一个 '?'(63)和 'J'(74),一个字符就够了。这是用可打印字符来压缩数值。

转换公式非常简单:

  • Sanger: Q = ord(char) - 33
  • Illumina 1.3+ / 1.5+: Q = ord(char) - 64(注意 1.5+ 会把 Q≤2 统一为 0)

反过来,如果你想把 Q 值写成 FastQ,就用 chr(Q + offset)。

记忆口诀:"+33" 是从感叹号开始;"+64" 是从 @ 符号开始。英文里 '!' 比 '@' 小,所以偏移量小。

知识框架图

下面这张图把 FastQ 结构、三种编码方式、判读流程串起来了,方便你建立整体认知:

FastQ 文件格式与质量分数编码体系 FastQ 四行结构 @ 标识符行 碱基序列行 + 分隔符行 质量分数行(字符) 编码方式 Sanger:偏移33 Illumina 1.3+:偏移64 Illumina 1.5+:偏移64变种 Phred 质量分数 Q Q = -10 × log₁₀(P) ASCII字符 = Q + offset Q30 → 错误率 0.1% 判读第一步:查看第一个字符 判读第二步:统计 ASCII 范围 结果:确定编码 → 正确解析 Q 值 → 质控过滤 提示:大多数现代测序平台(Illumina ≥1.8、PacBio、ONT)使用 Sanger 编码,但老数据需仔细判断

实际项目中的注意事项

我处理过上百个批次的测序数据,每次合并不同来源的 FastQ 前,都会用上面的脚本统一检测一遍编码。这里有几个教训,你提前知道就能省很多麻烦:

经验之谈:如果同一个项目里混用了不同编码的数据,不要直接合并。建议先统一转换成 Sanger 编码。工具比如 seqtk seq -V Q64 file.fastq > file_sanger.fastq 可以帮你转换(-V 指定输入编码偏移,-Q 指定输出偏移)。我一般输出固定用 Sanger。
特别注意:有些第三方工具在输出 FastQ 时,质量分数行可能写反(例如把碱基序列写成质量分数那一行)。如果你看到 @ 行里的 ID 不是以单空格分隔,或者 + 行后面缺东西,建议用 fastQValidator 或 FastQC 报告辅助检查。千万别跳过这一步。