获取基因有效长度的N种方法

在RNAseq的下游分析中,一般都会将上游处理完得到的原始counts数转变为FPKM/RPKM或是TPM来进行后续的展示或分析,其定义和计算公式在我之前的文章中有所总结Counts FPKM RPKM TPM 的转化 - (jianshu.com)。
需要注意一点的是,在计算FPKM/RPKM和TPM时,基因长度一般指的都是基因有效长度effective length,即该基因的外显子总长度或转录本总长度,以此为标准来消除测序造成的基因长度影响才更为准确。参见生信技能树文章:基因长度之多少 | 生信菜鸟团 (bio-info-trainee.com)

那么问题来了,在计算FPKM/RPKM时,每个基因的基因有效长度数据该如何获取呢?
我总结了几种获取基因有效长度(或非冗余总外显子长度、总转录本长度)的方法,现整理如下:

一、从上游输出文件结果中获取基因有效长度

一般而言,RNA-seq得到原始counts表达矩阵最常用到的上游软件就是featureCounts和Salmon了,在这两类软件的输出结果中,除了基因(或转录本)的counts信息外,也包含了基因有效长度信息,如featureCounts输出文件的Length这一列对应的就是基因有效长度。(P.S. 之前一直以为featureCounts的Length只是单纯的基因长度,后来经过多种方法比较后发现其实Length这一列就已经是基因的有效长度了...在文章后面我也会展示这几种方法比较的结果)

因此,最方便的做法就是在下游获取counts矩阵时,将基因有效长度信息也同时提取出来用于后续的基因表达量转化。

1. 针对featureCounts的输出文件

在R中读取featureCounts的输出文件,提取Length和对应的geneid信息,再按照counts中的rowname(geneid)匹配排序,即可进行后续的TPM、FPKM值的计算了。


featurecounts输出文件格式
rm(list=ls())
options(stringsAsFactors = F) 
library(tidyverse) # ggplot2 stringer dplyr tidyr readr purrr  tibble forcats
library(data.table) #可多核读取文件

a1 <- fread('counts.txt', header = T, data.table = F)#载入counts,第一列设置为列名

### counts矩阵的构建
counts <- a1[,7:ncol(a1)] #截取样本基因表达量的counts部分作为counts 
rownames(counts) <- a1$Geneid #将基因名作为行名

### 从featurecounts 原始输出文件counts.txt中提取Geneid、Length(转录本长度),
geneid_efflen <- subset(a1,select = c("Geneid","Length"))
       colnames(geneid_efflen) <- c("geneid","efflen")  
geneid_efflen_fc <- geneid_efflen #用于之后比较

### 取出counts中geneid的对应的efflen
dim(geneid_efflen)
efflen <- geneid_efflen[match(rownames(counts),
                              geneid_efflen$geneid),
                        "efflen"]

### 计算 TPM
#TPM (Transcripts Per Kilobase Million)  每千个碱基的转录每百万映射读取的Transcripts
counts2TPM <- function(count=count, efflength=efflen){
  RPK <- count/(efflength/1000)       #每千碱基reads (“per million” scaling factor) 长度标准化
  PMSC_rpk <- sum(RPK)/1e6        #RPK的每百万缩放因子 (“per million” scaling factor ) 深度标准化
  RPK/PMSC_rpk                    
  }  
tpm <- as.data.frame(apply(counts,2,counts2TPM))
colSums(tpm)

其中geneid_efflen内容如下
geneid_efflen

2. 针对Salmon的输出文件

Salmon的输出结果以及各内容的解释如下。Salmon Output File Formats - Salmon 1.8.0 documentation
值得注意的是,salmon不仅给出了基因有效长度Length(转录本长度),还给出了EffectiveLength,即经过考虑各种因素矫正后的转录本有效长度。官方更推荐使用EffectiveLength进行后续的分析,它结果中的TPM值也是根据EffectiveLength计算的。

Salmon的输出结果

Salmon的输出结果官方解释

我们一般使用tximport导入salmon的输出文件“quant.sf”(转录本的统计结果)和转录本id与gene symbol对应关系文件,会生成以下几个数据:
"abundance" "counts" "length" "countsFromAbundance"
tximport生成的Length就是EffectiveLength,而"abundance" 就是TPM值,我们提取Length用于后续计算FPKM。注意,由于每个样本中基因的EffectiveLength有差异,我们要提取的实际为EffectiveLength的矩阵(或者可以每行EffectiveLength取均值)。

library(tximport) 

#t2s为从gtf文件中提取的transcript_id和symbol的对应关系文件
t2s <- fread("t2s_vm29_gencode.txt", data.table = F, header = F) 

##创建quant.sf所在路径  导入salmon文件处理汇总
quantdir <- file.path(getwd(),'salmon'); quantdir
files <- list.files(pattern="*quant.sf",quantdir,recursive=T); files  #显示目录下所有符合要求的文件
files <- file.path(quantdir,files);files
txi_gene <- tximport(files, type = "salmon", tx2gene = t2s)

##提取出counts/tpm表达矩阵
counts <- apply(txi_gene$counts,2,as.integer) #将counts数取整
rownames(counts) <- rownames(txi_gene$counts)
tpm <- txi_gene$abundance  ###abundance为基因的Tpm值

###提取geneid_efflen_mat
geneid_efflen_mat <- txi_gene$length  ###Length为基因的转录本有效长度

## 计算 TPM 、FPKM
  if (F) { #可直接从txi的"abundance"  中提取,不用运行
    tpm <- data.frame(rownames(counts),row.names = rownames(counts))
    for (i in 1:ncol(counts)) {
      count <- counts[,i] 
      efflength <- geneid_efflen_mat[,i]
      RPK <- count/(efflength/1000)   #每千碱基reads (reads per million) 长度标准化
      PMSC_rpk <- sum(RPK)/1e6        #RPK每百万缩放因子 (“per million” scaling factor ) 深度标准化
      tpm00 <- RPK/PMSC_rpk  
      tpm <- data.frame(tpm,tpm00)
      rm(tpm00)
    }
    tpm <- tpm[,-1];  colnames(tpm) <- colnames(counts);  head(tpm)
   
  }
  
  ## 计算 fpkm
  if(T){
    fpkm <- data.frame(rownames(counts),row.names = rownames(counts))
    for (i in 1:ncol(counts)) {
      count <- counts[,i] 
      efflength <- geneid_efflen_mat[,i]
      PMSC_counts <- sum(count)/1e6   #counts的每百万缩放因子 (“per million” scaling factor) 深度标准化
      FPM <- count/PMSC_counts        #每百万reads/Fragments (Reads/Fragments Per Million) 长度标准化
      fpkm00 <- FPM/(efflength/1000)
      fpkm <- data.frame(fpkm,fpkm00)
      rm(fpkm00)
    }
    fpkm <- fpkm[,-1];  colnames(fpkm) <- colnames(counts)
  }

如果想要提取一般意义上的基因有效长度,需要利用“quant.genes.sf”文件(基因的统计结果,需要在进行salmon时加上参数 -g ,后接gtf文件),提取Length这一列的信息。

a2 <- fread("quant.genes.sf",
            data.table = F)
geneid_efflen <- subset(a2, select = c("Name","Length"))
colnames(geneid_efflen) <- c("geneid","efflen") 
geneid_efflen_salmon <- geneid_efflen #用于之后比较

二、 从gtf文件中计算获取基因有效长度

整理了两种从gtf文件中计算获取基因有效长度的方法(非冗余外显子长度之和),参考这两篇文章:
基因长度并不是end-start - (jianshu.com)
Htseq Count To Fpkm | KeepNotes blog (bioinfo-scrounger.com)
由于处理数据量很大,代码速度运行较慢,因此在以下代码中还调用了parallel包进行多核运算处理

1. 利用GenomicFeatures包导入gtf处理

library(parallel) #并行计算  parApply parLapply parSaplly 
cl <- makeCluster(0.75*detectCores())  #设计启用计算机3/4的核

## 利用GenomicFeatures包导入gtf处理
    library(GenomicFeatures)
    txdb <- makeTxDbFromGFF("gencode.vM25.chr_patch_hapl_scaff.annotation.gtf.gz",
 format="gtf") 
    exons_gene <- exonsBy(txdb, by = "gene") ###提取基因外显子
    head(exons_gene)

    ##计算总外显子长度:用reduce去除掉重叠冗余的部分,,width统计长度,最后计算总长度
    exons_gene_lens <- parLapply(cl,exons_gene,function(x){sum(width(reduce(x)))}) 
    exons_gene_lens[1:10]
    
    ##转换为dataframe
    geneid_efflen <- data.frame(geneid=names(exons_gene_lens),
                                efflen=as.numeric(exons_gene_lens))
    geneid_efflen_gtf1 <- geneid_efflen

2.利用rtracklayer包导入gtf处理

##利用rtracklayer包import导入处理 
    gtf <- as.data.frame(rtracklayer::import("gencode.vM25.chr_patch_hapl_scaff.annotation.gtf.gz"))
    table(gtf$type)

    exon <- gtf[gtf$type=="exon",
                c("start","end","gene_id")]
    exon_bygeneid <- split(exon,exon$gene_id)   #按照每个geneid所含的exon排序成列表
    
    efflen <- parLapply(cl,exon_bygeneid,function(x){
      tmp <- apply(x,1,function(y){  y[1]:y[2]  }) #输出exon长度值的所有元素            
      length(unique(unlist(tmp))) #去重复并统计exon长度元素的数量
      }) 

    ##转换为dataframe
    geneid_efflen <- data.frame(geneid=names(efflen),
                                efflen=as.numeric(efflen))
    geneid_efflen_gtf2 <- geneid_efflen

所得结果的比较

现在就可以来进行基因有效长度之间的比较啦。
首先看看从gtf文件中获取基因有效长度的两种方法是否有差异。可以发现,仅有极少数efflen有差异,因此这两种方法可以说是几乎没什么差别了:


从gtf文件中获取efflen的比较

再比较一下geneid_efflen_gtf1和geneid_efflen_salmon,发现有一半的efflen是不匹配的?仔细一想这也是可以理解的,因为上游中salmon是对样本中的转录本进行的统计,这说明了样本中有一半的基因并未表达其全部的转录本而已:


salmon和gtf中获取的efflen比较

再将geneid_efflen_gtf1和geneid_efflen_fc进行比较,发现全都能匹配上,这说明featureCounts的Length确实就已经是我们想要的基因有效长度了(即非冗余外显子总长度):


featureCounts和gtf中获取的efflen比较

总结

  1. 获取基因有效长度的最简便方法是直接从featureCounts或salmon的输出文件中提取。
    但需要注意的是,featureCounts中基因有效长度Length即为基因的非冗余外显子总长度,而salmon中的基因有效长度Length是目标基因的转录本总长度,由于样本中只有部分基因会表达其全部类型的转录本,因此salmon中的转录本总长度会有部分小于非冗余外显子总长度。
  2. salmon输出结果中不仅给出了基因有效长度Length(转录本总长度),还给出了经过考虑各种因素矫正后的转录本有效长度EffectiveLength。Salmon官方更推荐使用EffectiveLength进行后续的分析,认为其能更好消除测序时基因长度的影响,它结果中的TPM值也是根据EffectiveLength计算的,后续分析中可以直接采用。
  3. 在没有上游原始输出文件的情况下,也可以采取直接从gtf文件中计算的方法,获取每个基因的非冗余外显子总长度得到基因有效长度。还可以保存geneid_efflen便于之后再读取:
    write.csv(geneid_efflen,file = "geneid_efflen_vm25_gencode.csv",row.names = F)

参考资料
基因长度之多少 | 生信菜鸟团 (bio-info-trainee.com)
Counts FPKM RPKM TPM 的转化 - (jianshu.com)
基因长度并不是end-start - (jianshu.com)
Htseq Count To Fpkm | KeepNotes blog (bioinfo-scrounger.com)
Salmon Output File Formats - Salmon 1.8.0 documentation

你可能感兴趣的:(获取基因有效长度的N种方法)