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 软件版本 |
如何快速判断 FastQ 的编码方式?
手动翻看质量分数那一行的字符分布,基本能猜个八九不离十。下面是我常用的经验法则:
- 看第一个字符:如果是 '!',基本是 Sanger(ASCII 33)。
- 看有没有 '@' 或 'A':如果出现大量 '@'(ASCII 64)且没有 '!',很可能是 Illumina 1.3+。
- 看有没有 'B' 打头的连续低质量段:如果有,且整体偏移像 64,那就是 1.5+。
- 用程序统计 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 前,都会用上面的脚本统一检测一遍编码。这里有几个教训,你提前知道就能省很多麻烦:
seqtk seq -V Q64 file.fastq > file_sanger.fastq 可以帮你转换(-V 指定输入编码偏移,-Q 指定输出偏移)。我一般输出固定用 Sanger。
fastQValidator 或 FastQC 报告辅助检查。千万别跳过这一步。