4. 读取FASTQ文件:用Python把数据“抓”出来
拿到一个FASTQ文件,第一件事是什么?当然是把它读进去。我在刚入行时,以为读个文本文件很简单,等真上了几百万条 reads 的数据,才发现 效率 和 正确性 缺一不可。这节我就带你用 Biopython 和 pysam 这两个库,把 FASTQ 里的序列、质量和 ID 整整齐齐地取出来,再做点基本统计。
4.1 为什么不用 Python 自带 open?
你想想看,FASTQ 每 4 行为一条记录,如果用 open() 按行读,还得自己处理换行符、分隔符、质量字符串里的“@”和“+”混淆问题。我很久以前写过一次“纯手工”解析,结果一不小心把序列行里的“@”当成了标题行…… 从那以后我就老老实实用现成的库了。
4.2 用 Biopython 读取 FASTQ
Biopython 的 SeqIO.parse 是解析 FASTQ 最常用的入口。它返回一个迭代器,每条记录是一个 SeqRecord 对象,包含 id、seq 和 letter_annotations['phred_quality']。
from Bio import SeqIO
import os
fastq_path = "sample.fastq"
records = []
for record in SeqIO.parse(fastq_path, "fastq"):
records.append(record)
# 只取前 1000 条测试
if len(records) >= 1000:
break
print(f"成功读取 {len(records)} 条记录")
print(f"第一条序列长度: {len(records[0].seq)}")
print(f"第一条质量分数: {records[0].letter_annotations['phred_quality'][:10]}")
我个人习惯在遍历时加上 count 计数器,防止文件太大导致内存溢出。SeqIO.parse 返回的是延迟加载的迭代器,不转成列表就不会把整个文件塞进内存。
SeqIO.parse(gzip.open("sample.fastq.gz", "rt"), "fastq") 就可以。
4.3 用 pysam 读取 FASTQ(更贴近生产环境)
pysam 本来是处理 BAM 文件的,但它也提供了 FastxFile 类来读取 FASTQ、FASTA。如果你的下游流程需要和 BAM 文件交互,pysam 的接口和它自带的 C 扩展能带来稍快一点的速度。
import pysam
with pysam.FastxFile("sample.fastq") as fh:
for i, entry in enumerate(fh):
if i >= 1000:
break
# entry 对象包含 name, sequence, quality
print(entry.name, len(entry.sequence), entry.quality[:10])
# 注意:pysam 的 quality 是字符串,不是数值列表
对比一下:Biopython 返回的是 SeqRecord,质量是数值列表;pysam 返回的是简易命名元组,质量是原始字符串。我通常在分析流程的中间环节用 pysam,因为它不需要额外转换就能直接对接 samtools 命令。
| 特性 | Biopython SeqIO | pysam FastxFile |
|---|---|---|
| 质量格式 | list of int (Phred 分数) | str (原始 ASCII) |
| 内存占用 | 迭代器式,低(小心转列表) | 迭代器式,低 |
| 压缩支持 | 需手动用 gzip.open | 自动识别 .gz 文件 |
| 并行解析 | 可选 multiprocessing | 有内置的线程池支持 |
| 额外功能 | 丰富的序列操作 | 与 BAM/CRAM 索引交互方便 |
4.4 基本统计:给你的数据“摸个底”
读完数据,下一步就是算算序列长度分布、碱基质量分布。我常用的是在读取过程中一边读一边统计,不额外多扫一遍文件。
from Bio import SeqIO
import numpy as np
lens = []
qual_sums = []
qual_counts = []
for record in SeqIO.parse("input.fastq", "fastq"):
lens.append(len(record.seq))
quals = record.letter_annotations['phred_quality']
qual_sums.append(sum(quals))
qual_counts.append(len(quals))
lens = np.array(lens)
qual_avg = np.array(qual_sums) / np.array(qual_counts)
print(f"序列长度: min={lens.min()}, max={lens.max()}, mean={lens.mean():.1f}")
print(f"平均质量: min={qual_avg.min():.1f}, max={qual_avg.max():.1f}, mean={qual_avg.mean():.1f}")
4.5 归纳总结:解析 FASTQ 的核心流程
不管用哪个库,套路都一样:打开文件 → 迭代记录 → 提取 ID、序列、质量 → 聚合统计或逐条处理。下面这张图帮你把关系理清楚。
4.6 避坑指南
- 文件名大小写:FASTQ 可能叫 .fq 或 .fastq,Biopython 的 format 参数必须填
"fastq",不能写 "fq"。 - 质量编码偏移:绝大多数现代 FASTQ 使用 Sanger 偏移(Phred+33)。如果遇到老数据(如 Solexa),Biopython 会自动处理,pysam 需要手动设置
quality_scale="solexa"。 - 迭代不重置:迭代器是一次性的,如果先统计了长度,需要重新读取文件才能再算质量。我建议将统计逻辑合并到同一个循环里,或者把重要字段存为结构化数据(如 pandas DataFrame)。
glob.glob("*.fastq") 配合循环,再把统计结果汇总到一张 CSV 表里。我在一个 200 个样本的项目里就是这样干的,后续质控报告直接由 CSV 生成。
好了,读取并统计 FASTQ 的基本功就这些。下节课我们看看怎么把这些原始数据跟元数据(样本名、条件、批次)挂上钩——那才是真正让数据“活起来”的一步。