RNA-seq 数据预处理:从 fastq 到 count 矩阵
说真的,这步要是搞砸了,后面用 DESeq2 还是 edgeR 都白搭。我自己刚入行时就吃过这个亏——拿着漂亮的 p-value 列表兴奋半天,结果发现是上游比对参数配错了。嗯,咱们从头捋一遍,把原始测序数据变成能用的 count 矩阵。
整体流程速览
把 fastq 文件变成表达矩阵,说白了就是四步:
- 质量控制 – 看看原始数据干不干净
- 修剪/过滤 – 把接头、低质量碱基砍掉
- 比对 – 把 reads 贴到参考基因组上
- 定量 – 统计每个基因有多少 reads 落上去
每一步都有对应的主流工具。我习惯先画张图把逻辑理清楚。
第一步:质量控制与修剪
我拿到 fastq 文件后,第一件事就是跑 FASTQC。别偷懒,直接对所有样本跑一遍,再用 MultiQC 汇总看一眼整体趋势。
fastqc *.fastq.gz -o ./qc_raw
multiqc ./qc_raw -o ./multiqc_report
看过报告后,你通常会决定要不要修剪。我个人偏好 fastp,因为它快,而且能自动检测接头。这是典型命令:
fastp -i sample_R1.fastq.gz -I sample_R2.fastq.gz \
-o sample_clean_R1.fastq.gz -O sample_clean_R2.fastq.gz \
--detect_adapter_for_pe \
--trim_poly_g \
--cut_front --cut_tail --cut_mean_quality 20 \
--length_required 36
第二步:比对到参考基因组
比对这一步,选对工具很关键。我个人一直用 STAR,因为它快,而且对剪接 junction 支持好(对于真核生物是必须的)。Hisat2 也可以,但速度差一些。建索引是前提:
STAR --runMode genomeGenerate \
--genomeDir ./star_index \
--genomeFastaFiles hg38.fa \
--sjdbGTFfile hg38.gtf \
--runThreadN 16
比对命令:
STAR --genomeDir ./star_index \
--readFilesIn sample_clean_R1.fastq.gz sample_clean_R2.fastq.gz \
--readFilesCommand zcat \
--outSAMtype BAM SortedByCoordinate \
--outSAMunmapped Within \
--outSAMattributes Standard \
--runThreadN 16
--outSAMstrandField intronMotif,否则后续定量会出错。我曾经在这上面栽过,链搞反了,结果基因表达方向全反。
第三步:定量得到 count 矩阵
拿到排序后的 BAM 文件,就该数数了。我最常用 featureCounts(来自 subread 包),因为它快而且灵活。当然 htseq-count 也很经典,但速度慢一些,尤其对于大样本。
featureCounts -T 8 -p --countReadPairs \
-t exon -g gene_id \
-a hg38.gtf \
-o counts.txt \
sample1.bam sample2.bam sample3.bam
输出的 counts.txt 长这样:
Geneid sample1.bam sample2.bam sample3.bam
ENSG000001 1234 5678 9012
ENSG000002 234 345 456
ENSG000003 6789 1011 1213
这不就是我们要的 count 矩阵吗?直接拿去做 DESeq2 或 edgeR 的输入。
featureCounts 默认只统计唯一比对的 reads(
-M 参数可以改为包括多比对)。我一般保持默认——唯一比对 reads 最可靠。如果实验设计中有大量重复区域或多基因家族,那可以考虑用 -M --fraction 分配分数。
我踩过的坑(避坑指南)
- 忘记处理 adapter 残留 – 我曾经用 STAR 直接比对未修剪的 fastq,结果 junction 和 mapping rate 惨不忍睹。后来养成习惯:先跑 fastp 再进 STAR。
- 基因注释版本不一致 – 建 STAR 索引用的 GTF 和 featureCounts 用的 GTF 必须完全一致。我吃过一次闷亏:建索引用 gencode v41,定量时用了 ensembl v108,导致一堆基因 ID 对不上。
- 盲目去重 – 对于 RNA-seq,PCR 重复是生物学需要还是实验噪音?我强烈建议不要去重,除非你做的是 UMI 标记的文库。否则会严重低估低表达基因。
- 未检查链特异性 – 先跑一个样本,用
infer_experiment.py(来自 RSeQC)看看实际链特异性。我遇到过文库标记为 "unstranded" 但实际上有 95% 的 reads 落在正义链,featureCounts 默认无链模式导致基因计数翻倍。
工具选择速查表
| 环节 | 主流工具 | 我的推荐 | 一句话理由 |
|---|---|---|---|
| 质量控制 | FASTQC, MultiQC | FASTQC + MultiQC | 一个看细节,一个汇总,绝配 |
| 修剪 | fastp, Trimmomatic, cutadapt | fastp | 快,自动检测接头,输出报告清晰 |
| 比对 | STAR, HISAT2, Salmon (pseudoalignment) | STAR | 速度与准确性平衡,junction 支持好 |
| 定量 | featureCounts, htseq-count, RSEM | featureCounts | 快,内存占用少,支持多线程 |
当然,也有人用 Salmon 或 kallisto 直接做 pseudoalignment 跳过比对,但那就不是本文讨论的传统流程了。如果你做的是非常规样本(比如单细胞),那些工具更合适。
samtools flagstat 检查每个样本的比对统计,比如 mapping rate、paired reads 比例。我曾经一个样本 mapping rate 只有 60%,发现是样本污染,及时剔除了,避免了后续差异分析的偏差。
到了这一步,你已经有了一个干净的 count 矩阵。下一件事就是导入 R 开始做差异分析了。不过别急——拿到矩阵后最好再检查一下基因名的格式( Ensembl ID 还是 gene symbol?),必要时做个 基因ID-基因名 转换。我自己一般在 featureCounts 命令里就用 GTF 里的 gene_id,后续用 biomaRt 或者自带注释表转换。