当你拿到RNA-seq测序数据时,通常会得到两种类型的表达矩阵:原始计数(raw counts)和标准化后的表达量(如FPKM)。原始计数直接反映了比对到每个基因上的reads数,但它存在两个明显的局限性:一是没有考虑基因长度的影响,二是没有考虑测序深度(即样本间测序量的差异)。这就好比比较两个图书馆的藏书量,如果不考虑每本书的厚度和图书馆的总藏书规模,直接比较两馆的"书的本数"是没有意义的。
FPKM(Fragments Per Kilobase per Million)就是为了解决这些问题而设计的标准化指标。它的计算公式可以拆解为三个部分:
在实际应用中,我们常常会遇到只有raw counts数据的情况。这时就需要自己动手计算FPKM值。我刚开始接触这个转换过程时,常常被各种标准化方法搞得晕头转向,直到理解了它们背后的生物学意义才豁然开朗。比如,为什么要用基因长度做标准化?因为较长的基因在随机打断过程中会产生更多片段,如果不做长度校正就会高估其表达量。
biomaRt是连接Ensembl数据库的桥梁,它允许我们直接通过R获取基因注释信息。安装过程很简单:
r复制if (!require("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("biomaRt")
library(biomaRt)
我第一次使用时遇到个坑:有时候默认的Ensembl服务器可能连接不稳定。这时可以指定镜像:
r复制ensembl <- useMart("ensembl", host = "https://asia.ensembl.org")
假设我们有一个CSV格式的counts矩阵,行是基因ID,列是样本:
r复制counts_data <- read.csv("GSE169758_counts.csv", header=TRUE, row.names=1)
head(counts_data)
检查数据质量很重要。我通常会先看看数据的基本统计:
r复制summary(colSums(counts_data)) # 检查各样本的总reads数
summary(rowSums(counts_data)) # 检查各基因的总表达量
如果发现某些样本的总reads数异常低,可能需要考虑是否纳入分析。同样,表达量极低的基因(比如在所有样本中counts都<10)也可以考虑过滤掉,以减少噪音。
选择正确的数据集很关键。对于人类数据:
r复制ensembl <- useDataset("hsapiens_gene_ensembl", mart=ensembl)
如果是小鼠数据,则使用:
r复制ensembl <- useDataset("mmusculus_gene_ensembl", mart=ensembl)
我们需要获取每个基因的转录本长度信息。这里有个技巧:一个基因可能有多个转录本,通常选择最长的转录本作为代表:
r复制gene_info <- getBM(
attributes = c('ensembl_gene_id', 'start_position',
'end_position', 'ensembl_transcript_id',
'transcript_length'),
mart = ensembl)
对获取的数据进行排序和去重:
r复制# 按基因ID和转录本长度降序排列
gene_info <- gene_info[order(gene_info$ensembl_gene_id,
-gene_info$transcript_length),]
# 保留每个基因最长的转录本
gene_lengths <- gene_info[!duplicated(gene_info$ensembl_gene_id),
c("ensembl_gene_id", "transcript_length")]
colnames(gene_lengths) <- c("gene_id", "length")
首先确保counts矩阵中的基因都能在gene_lengths中找到对应:
r复制common_genes <- intersect(rownames(counts_data), gene_lengths$gene_id)
counts_filtered <- counts_data[common_genes, ]
lengths_filtered <- gene_lengths[match(common_genes, gene_lengths$gene_id), "length"]
文库大小(library size)即每个样本的总reads数:
r复制lib_sizes <- colSums(counts_filtered)
FPKM的计算公式为:
[ \text{FPKM} = \frac{\text{reads count} \times 10^9}{\text{gene length} \times \text{library size}} ]
R实现代码:
r复制fpkm_matrix <- apply(counts_filtered, 2, function(x) {
(x * 1e9) / (lengths_filtered * sum(x))
})
检查计算结果:
r复制head(fpkm_matrix[,1:3])
summary(fpkm_matrix)
保存结果:
r复制write.csv(fpkm_matrix, "fpkm_results.csv", quote=FALSE)
使用org.Hs.eg.db包(小鼠则用org.Mm.eg.db):
r复制BiocManager::install("org.Hs.eg.db")
library(org.Hs.eg.db)
r复制ensembl_to_symbol <- toTable(org.Hs.egSYMBOL)
ensembl_to_entrez <- toTable(org.Hs.egENSEMBL)
r复制# 准备待注释的数据
fpkm_df <- as.data.frame(fpkm_matrix)
fpkm_df$ensembl_id <- rownames(fpkm_df)
# 分步合并
annotated <- merge(fpkm_df, ensembl_to_entrez, by="ensembl_id", all.x=TRUE)
annotated <- merge(annotated, ensembl_to_symbol, by="gene_id", all.x=TRUE)
# 整理最终结果
annotated <- annotated[!duplicated(annotated$ensembl_id), ]
rownames(annotated) <- annotated$ensembl_id
annotated <- annotated[rownames(fpkm_df), ] # 保持原始顺序
有时候多个Ensembl ID会对应同一个gene symbol,或者某些ID没有对应的symbol。我的处理经验是:
r复制# 去除完全重复的行
annotated <- annotated[!duplicated(annotated$ensembl_id), ]
# 处理没有symbol的基因
annotated$symbol[is.na(annotated$symbol)] <- annotated$ensembl_id[is.na(annotated$symbol)]
以下是将所有步骤整合在一起的完整代码:
r复制# 加载包
library(biomaRt)
library(org.Hs.eg.db)
# 1. 导入counts数据
counts_data <- read.csv("GSE169758_counts.csv", header=TRUE, row.names=1)
# 2. 获取基因长度
ensembl <- useMart("ensembl")
ensembl <- useDataset("hsapiens_gene_ensembl", mart=ensembl)
gene_info <- getBM(
attributes = c('ensembl_gene_id', 'transcript_length'),
filters = 'ensembl_gene_id',
values = rownames(counts_data),
mart = ensembl)
# 取最长转录本
gene_lengths <- aggregate(transcript_length ~ ensembl_gene_id,
data=gene_info, max)
# 3. 计算FPKM
common_genes <- intersect(rownames(counts_data), gene_lengths$ensembl_gene_id)
counts_filtered <- counts_data[common_genes, ]
lengths_filtered <- gene_lengths[match(common_genes, gene_lengths$ensembl_gene_id), "transcript_length"]
fpkm_matrix <- apply(counts_filtered, 2, function(x) {
(x * 1e9) / (lengths_filtered * sum(x))
})
# 4. 基因注释
fpkm_df <- as.data.frame(fpkm_matrix)
fpkm_df$ensembl_id <- rownames(fpkm_df)
ensembl_to_symbol <- toTable(org.Hs.egSYMBOL)
ensembl_to_entrez <- toTable(org.Hs.egENSEMBL)
annotated <- merge(fpkm_df, ensembl_to_entrez, by.x="ensembl_id",
by.y="ensembl_id", all.x=TRUE)
annotated <- merge(annotated, ensembl_to_symbol, by="gene_id", all.x=TRUE)
# 5. 保存结果
write.csv(annotated, "annotated_fpkm_results.csv", row.names=FALSE)
在实际操作中,我遇到过不少问题,这里分享几个典型的:
问题1:biomaRt连接失败
问题2:基因ID不匹配
问题3:FPKM计算结果异常
问题4:部分基因没有symbol注释
经过多次实践,我总结出一些提升效率的技巧:
批量处理多个数据集:可以编写函数将整个过程封装起来,方便处理多个项目
并行计算加速:对于大型数据集,可以使用parallel包加速FPKM计算
r复制library(parallel)
cl <- makeCluster(4) # 使用4个核心
fpkm_matrix <- parApply(cl, counts_filtered, 2, function(x) {
(x * 1e9) / (lengths_filtered * sum(x))
})
stopCluster(cl)
r复制hist(log10(lengths_filtered), main="Gene length distribution")
boxplot(log2(fpkm_matrix+1), main="FPKM distribution across samples")
使用最新注释:定期更新biomaRt连接或本地注释包,确保使用最新的基因注释信息
结果验证:将计算结果与已知结果(如GEO中的FPKM矩阵)进行对比验证
记得第一次成功完成整个流程时,那种成就感让我印象深刻。虽然过程中会遇到各种问题,但每次解决问题的过程都是宝贵的学习经验。希望这篇指南能帮助你顺利实现从counts到FPKM的转换!