2. RNA-seq数据获取与预处理:原始测序数据到表达矩阵
RNA-seq的下机数据是一堆FASTQ文件,看起来就像天书。但说白了,我们要的只是一个表格——基因表达矩阵。怎么从原始测序数据一步步走到这个矩阵?这里面有不少坑。
我在刚接触RNA-seq时也踩过不少雷。有一回跑完整个流程才发现,原来某个样本的测序质量根本不过关,白算了三天。所以这一章,我把整个流程拆开来讲,每一步都告诉你:为什么要做、怎么做、容易在哪儿翻车。
2.1 整体流程概览
从FASTQ到表达矩阵,核心就四步:质量控制、比对、定量、汇总。听起来简单,但每一步都有选择。你想想看,光是比对软件就有STAR、HISAT2、Bowtie2好几个,选哪个?默认参数够用吗?
下面这张图把这四步串起来了,建议你先看一遍,心里有个全局感。
2.2 数据获取:从哪找RNA-seq数据?
大部分公开RNA-seq数据都集中在两个库里:GEO(Gene Expression Omnibus)和TCGA(The Cancer Genome Atlas)。我个人习惯优先去TCGA拿肿瘤数据,因为它样本量大、临床信息全。
但要注意,TCGA的数据是经过处理的(比如HTSeq计数),而GEO上很多是原始FASTQ。你需要看清楚描述。
数据下载工具推荐:
- TCGA: gdc-client(官方工具)或 TCGAbiolinks(R包)
- GEO: fastq-dump(SRA Toolkit)或 prefetch + fasterq-dump
举个例子,用SRA Toolkit下载FASTQ的命令长这样:
# 安装SRA Toolkit后,下载单个SRA条目
prefetch SRR12345678
fasterq-dump --split-files SRR12345678
小心磁盘爆炸。 我一个项目32个肿瘤样本,原始FASTQ就占了将近1.2TB。下载前一定算好空间,用 --split-files 把双端拆开。
2.3 质量控制:别让垃圾数据污染结果
拿到FASTQ第一件事,不是直接比对,而是看质量。FastQC是行业标准。跑起来很快:
# 对每个FASTQ跑FastQC
fastqc sample1_R1.fastq.gz sample1_R2.fastq.gz -o qc_reports/
跑完后你会拿到一个HTML报告。重点关注这几项:
| 指标 | 正常范围 | 翻车信号 |
|---|---|---|
| Per base sequence quality | 大部分区域 Q > 30 | 末尾 Q < 20,或者整体偏低 |
| Per sequence GC content | 接近理论分布(单峰) | 双峰或明显偏移 → 可能有污染 |
| Adapter Content | < 1% | > 5% → 需要去接头 |
| Overrepresented sequences | < 0.1% | 大量重复 → 可能是PCR重复或污染 |
如果发现质量不好怎么办?Trimmomatic或cutadapt是常用的清洗工具。我一般这样处理:
# Trimmomatic 双端数据清洗示例
java -jar trimmomatic-0.39.jar PE \
sample1_R1.fastq.gz sample1_R2.fastq.gz \
sample1_R1_clean.fastq.gz sample1_R1_unpaired.fastq.gz \
sample1_R2_clean.fastq.gz sample1_R2_unpaired.fastq.gz \
ILLUMINACLIP:adapters.fa:2:30:10 \
LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36
一个小技巧: 跑完FastQC后,用MultiQC把所有样本的报告汇总成一个页面,一眼就能看出哪个样本有问题。
multiqc qc_reports/ -o multiqc_report/
我曾经遇到过一个项目,32个样本里有3个的GC含量分布明显异常。后来查实验记录才发现,那3个样本的文库构建批次不同,引入了污染。如果不做QC直接往下走,那3个样本的差异表达基因会全是假阳性。
2.4 序列比对:把读段贴回基因组
清洗完FASTQ,下一步是把每一条read"贴"到参考基因组上。这一步叫比对(alignment)。
目前最主流的工具是STAR和HISAT2。我个人偏爱STAR,因为它速度极快,而且对剪接 Junction 的支持很成熟。但STAR比较吃内存——人类基因组索引大概需要30GB RAM。
STAR的用法分两步:建索引 + 比对。
# 第一步:建立基因组索引(只需做一次)
STAR --runMode genomeGenerate \
--genomeDir star_index/ \
--genomeFastaFiles hg38.fa \
--sjdbGTFfile hg38.gtf \
--sjdbOverhang 100 \
--runThreadN 16
# 第二步:比对每个样本
STAR --genomeDir star_index/ \
--readFilesIn sample1_R1_clean.fastq.gz sample1_R2_clean.fastq.gz \
--readFilesCommand gunzip -c \
--outFileNamePrefix sample1_ \
--outSAMtype BAM SortedByCoordinate \
--runThreadN 16
比对完成后,记得检查这几个指标:
- Uniquely mapped reads % → 正常应 > 70%
- Multimapped reads % → 一般 < 10%
- % of reads mapped to multiple loci → 过高说明基因组重复区域多或数据质量差
STAR会在输出日志里报告这些数字。我一般是拿到BAM后先看一眼Log.final.out,确认比对率没问题再继续。
注意GTF版本要匹配。 我见过有人用hg38的基因组配了GRCh37的GTF,结果比对率直接掉到50%以下。基因组版本和注释版本必须一致,最好从同一来源下载(比如GENCODE或Ensembl)。
2.5 基因定量:从BAM到Counts矩阵
有了比对好的BAM文件,下一步就是数一数每个基因上有多少条read。这一步叫定量(quantification)。
最常用的工具是featureCounts(来自subread包)和htseq-count。featureCounts速度更快、内存更省,我基本只用它。
用法很简单:
# featureCounts 定量
featureCounts -T 8 \
-a hg38.gtf \
-o counts.txt \
-t exon \
-g gene_id \
sample1.bam sample2.bam sample3.bam
输出文件counts.txt长这样:
Geneid Sample1.bam Sample2.bam Sample3.bam
ENSG000001.1 1234 1098 1456
ENSG000002.2 56 78 92
ENSG000003.3 0 2 1
这就是最原始的表达矩阵——行是基因,列是样本,单元格是整数counts。
如果你想得到TPM或FPKM:
- 从GTF里拿到每个基因的长度(exon总长度)
- 用R或Python做归一化
我个人习惯在后续分析中直接用counts(DESeq2要求输入整数counts),需要TPM时再算。
2.6 表达矩阵的常见格式与选择
拿到原始counts后,你可能会看到三种表达量单位。它们之间的区别一定要搞清楚:
| 单位 | 含义 | 适用场景 |
|---|---|---|
| Counts | 比对到该基因的read原始数量 | 差异表达分析(DESeq2 / edgeR) |
| FPKM / RPKM | Fragments Per Kilobase of transcript per Million mapped reads | 基因内比较(已被TPM取代) |
| TPM | Transcripts Per Million | 样本间比较、可视化、聚类 |
简单说:如果你要做差异表达,用counts;如果要做热图或聚类,用TPM。我一般会同时保留counts和TPM两份矩阵。
2.7 整个流程的自动化
当你手上有几十个样本,一个个跑STAR和featureCounts会疯掉。我建议用Snakemake或Nextflow来编排流程。下面是一个极简的Snakemake示例:
rule all:
input:
"results/counts.txt"
rule star_align:
input:
"data/{sample}_R1.fastq.gz",
"data/{sample}_R2.fastq.gz"
output:
"results/bam/{sample}.bam"
shell:
"STAR --genomeDir star_index/"
" --readFilesIn {input}"
" --outFileNamePrefix results/bam/{sample}_"
" --outSAMtype BAM SortedByCoordinate"
" --runThreadN 16"
rule feature_counts:
input:
expand("results/bam/{sample}.bam", sample=SAMPLES)
output:
"results/counts.txt"
shell:
"featureCounts -T 8 -a hg38.gtf"
" -o {output} {input}"
把这个写进Snakefile,一行命令snakemake -j 8就能跑完整个流程。中途断了还能断点续跑——这是我最喜欢Snakemake的地方。
我个人的标准流程总结:
- 下载FASTQ → 用SRA Toolkit或gdc-client
- QC → FastQC + MultiQC,必要时Trimmomatic清洗
- 比对 → STAR(人类基因组用hg38 + GENCODE GTF)
- 定量 → featureCounts,输出原始counts矩阵
- 归一化 → 根据下游需求转为TPM或保留counts
到这里,你就拿到了最核心的输入——基因表达矩阵。下一章我们会在这个矩阵上做质量评估和批次校正。但现在,先确保你的矩阵是干净、可靠的。
最后啰嗦一句: 所有中间文件(FASTQ、BAM、counts)的命名规则一定要统一。我吃过亏——样本名一会用"Tumor1"一会用"T01",后期合并矩阵时对不上号,白花了半天改名字。提前定好命名规范,能省很多事。