跨物种RNA-seq标准化和差异表达R代码剖析

前言

前文跨物种RNA-seq标准化及差异表达介绍了跨物种标准化的方法,这次我们研究下其对应的R包SCBN的源代码。SCBN的用户手册地址为SCBN Tutorial

使用

假设我们对两个物种进行标准化,分别为 1 物种和 2 物种

sim_data

上图的 geneLength1 代表物种 1 每个基因的长度,count1 代表物种 1 对应基因的count数;geneLength2 代表物种 2 每个基因的长度,count2 代表物种 2 对应基因的count数

data(sim_data)
factor <- SCBN(orth_gene=sim_data, hkind=1:1000, a=0.05)

## hkind代表两个物种保守的基因在sim_data中的位置, 该例子中为第一个到第1000个是两个物种的保守基因
## α 代表p_val的阈值

运行完以后,factor 为:


我们可以简单的看一下SCBN的结构

SCBN <- function(orth_gene, hkind, a=0.05)
{
  if (all(!is.na(orth_gene)) == FALSE) {
      stop("The dataset of orthologous genes has NA values.", call.=TRUE)
  } else if (all(hkind %in% (seq_len(nrow(orth_gene)))) == FALSE){
             stop("The conserved genes are not included in dataset of
                   orthologous genes.", call.=TRUE)
  }

  scale <- MediancalcNorm(orth_gene, hkind)
  scbn_val <- Iter_optimal(scale=scale, orth_gene=orth_gene, hkind=hkind, a=a)
  list(median_val=scale, scbn_val=scbn_val)
}

不难发现它是由两个主要的函数构成,一个是 MediancalcNorm() 主要计算 median_val;另一个是 Iter_optimal() 主要计算 scbn_val

MediancalcNorm()

首先我们看一下 MediancalcNorm() 的功能:

MediancalcNorm <- function(orth_gene, hkind)
{
##对于物种 1 , 用物种 1 的count除以对应基因的长度, 再乘10^9 / (物种 1 所有基因count数之和
  RPKMh <- orth_gene[, 2]/orth_gene[, 1]*(10^9/sum(orth_gene[, 2]))
##对于物种 2 , 用物种 2 的count除以对应基因的长度, 再乘10^9 / (物种 2 所有基因count数之和
  RPKMm <- orth_gene[, 4]/orth_gene[, 3]*(10^9/sum(orth_gene[, 4]))
##挑选物种 1 和 2 的保守基因
  cRPKMh <- RPKMh[hkind]
  cRPKMm <- RPKMm[hkind]
## 标准化因子scale为两个物种保守基因矫正后count值的商
  scale <- median(cRPKMh)/median(cRPKMm)
  return(scale)
}

利用p_val判断两个物种的差异基因

计算p_val的方法页很简单,按照说明书上的方法:

data(sim_data)
orth_gene <- sim_data
## hkind 代表保守基因在sim_data的位置
hkind <- 1:1000

##scale 代表上面所计算的scbn_val
scale <- factor$scbn_val
x <- orth_gene[, 2]
y <- orth_gene[, 4]
lengthx <- orth_gene[, 1]
lengthy <- orth_gene[, 3]
n1 <- sum(x)
n2 <- sum(y)
p_value <- sageTestNew(x, y, lengthx, lengthy, n1, n2, scale)

通过上述命令就可以计算出每个基因对应的p_val来了
我们对应的看一下核心函数 sageTestNew() 的功能:

sageTestNew<-function (x, y, lengthx, lengthy, n1, n2, scale)
{
  if (any(is.na(x)) || any(is.na(y)))
      stop("missing values not allowed")
  if (any(x < 0) || any(y < 0))
      stop("x and y must be non-negative")
  if (length(x) != length(y))
      stop("x and y must have same length")

  ## x 代表物种 1 的count数
  x <- round(x)
  ## y 代表物种 2 的count数
  y <- round(y)
  ## n1(nn) 代表物种 1 总的count数
  nn <- round(n1)
  ## n2(mm) 代表物种 2 总的count数
  mm <- round(n2)

  ## size 代表物种 1 和物种 2 对应的每个基因(保守基因)count数之和(比方说保守的 gene A 在物种 1 的count数加上在物种 2 中的count数)
  size <- x + y
  pValue <- rep(1, length(size))

  ## 定义rate (scale为之前计算的scbn_val), lengthx为物种 1 每个保守基因的长度, lengthy为物种 2 对应每个保守基因的长度, 
  ## 每个基因都有一个rate
  rate <- scale*lengthx*nn/lengthy/mm
  ## 定义概率, 每一个基因都有个prob, 这个prob
  prob <- rate/(1+rate)

#接下来需要做判断, 当size大于10000时, 采用卡方检验计算p_val
  if (any(big <- size > 10000)) {
      ibig <- (seq_along(x))[big]
    ## 每个 i 代表两个物种中每一个保守的基因
      for (i in ibig)
           pValue[i] <- chisq.test(matrix(c(x[i], y[i],
                                           nn-x[i], mm-y[i]), 2, 2))$p.value
  }

## 筛选出两个物种对应基因count之和大于0小于10000的基因
  size0 <- size[size > 0 & !big]
## 筛选出两个物种对应基因count之和大于0小于10000的基因count
  x1 <- x[size > 0 & !big]
## 筛选出两个物种对应基因count之和大于0小于10000的基因概率
  prob1 <- prob[size > 0 & !big]
  pValue1 <- rep(1, length(size0))
  if (length(size0))
  ## 依次遍历每一个满足条件的基因
      for(i in seq_len(length(size0))){
        ## 对每一个基因进行二项分布检验, 计算每次实验结果为物种 1 count的
        ## size0[i] (size0[i]是一个数值) 为第 i 个基因两个物种count数总和, 可以理解为二项分布试验总的量
        ## prob1[i] 为第 i 个基因二项分布的概率
        ## 0:size0[i] 可以理解为二项分布实验的次数为 0~size0[i] 次, 物种 1 的count数为 k
          p <- dbinom(0:size0[i], prob=prob1[i], size=size0[i])
        ## 按l排序
          o <- order(p)
        ## 做累积求和, 求极端情况的概率(p_val)
          cumsump <- cumsum(p[o])[order(o)]
        ## x1[i] 为物种 1 第 i 个基因的count值
          pValue1[i] <- cumsump[x1[i] + 1]
    }

  pValue[size > 0 & !big] <- pValue1
  return(pValue)
}

对于size > 10000 的基因来说
这里作者采用的是卡方检验来完成p_val的计算,卡方检验的原理很简单,事实上就是列联表检验,其目的是检验列联表横纵遍历是否相关,如下图:


其中括号内是期望值,也就是说对于基因 1 来说物种 1 和 2 的count数应该是对半分的,也就是基因 1 在两个物种之间是没差异的,那么根据拟合优度公式计算p_val :

如果p_val不显著,代表基因 1 与物种(物种1,2)这个变量之间是没有关系的,翻译为生物学现象即为基因 1 在两个物种之间不是差异表达的;
如果p_val显著,代表基因 1 与物种(物种1,2)这个变量之间是有关系的,翻译为生物学现象即为基因 1 在两个物种之间是差异表达的;

对于size < 10000 的基因来说
这一段比较有意思的是二项分布检验:

二项分布

我们知道离散型二项分布检验相当于是在做摸球游戏,比方说 gene A,在物种 1 的count数为10(相当于10个红球),在物种 2 中的count数为20(相当于20个白球),假设抽取到物种 1 的count的概率为 1/3,那么 0:size0[i](size0[i] =30) 相当于一共做了30次实验;P{ X = k }相当于做30次实验,摸到物种 1 的count(摸到红球)次数为 k(k取值0-10次) 的概率

那么对应的P即为选取物种 1 count为 k 时的概率,那么对于某物种,当k(次数)取值越小,而对应的概率越大时,代表该物种对应基因的count数越少;那么相应的另外一个物种的count数就会很多,从而产生差异表达,那么p_val定义为当选取物种 1 的count数(k)为10的时候,累积的概率P的和 ( ∑ P{ X < k })。当p_val显著,则代表该gene在物种 1 和 2 中差异表达

举个例子

##物种 1, count数为 1 
dbinom(0:10, prob=0.1, size=10)
 [1] 0.3486784401 0.3874204890 0.1937102445 0.0573956280 0.0111602610 0.0014880348 0.0001377810 0.0000087480 0.0000003645
[10] 0.0000000090 0.0000000001

##物种 2, count数为 9
dbinom(0:10, prob=0.9, size=10)
 [1] 0.0000000001 0.0000000090 0.0000003645 0.0000087480 0.0001377810 0.0014880348 0.0111602610 0.0573956280 0.1937102445
[10] 0.3874204890 0.3486784401
> 

我们可以看到两种检验结果成相反的结果,物种1的count较少,所以 k 取值小的时候,对应的概率大;而物种2的count较多,所以 k 取值大的时候,对应的概率大

不过这里有两个疑问:

  1. 为什么作者在做卡方检验的时候不对总的count做一个文库归一化处理
  2. 在二项分布定义 prob(rate) 的时候为什么考虑的是基因长度而不是表达量

Iter_optimal()

接着我们再看下Iter_optimal()的功能,这种标准化的模式是作者自己提出来的:

Iter_optimal <- function(scale, orth_gene, hkind, a) {
  if (scale > 0.5) {
      fW1 <- seq(scale-0.5, scale + 0.5, 0.1)
  } else {
      fW1 <- seq(0.02, scale +0.5, 0.1)
  }
  n1 <- length(fW1)
  fdr1 <- rep(0, n1)
  for(j in seq_len(n1)){
      sMm1 <- sageTestNew(orth_gene[, 2], orth_gene[, 4], orth_gene[, 1],
                          orth_gene[, 3], sum(orth_gene[, 2]),
                          sum(orth_gene[, 4]), fW1[j])
      fdr1[j] <- sum(sMm1[hkind] < a)/length(hkind)
  }
  fdr11 <- abs(fdr1-a)
  counts1 <- length(which(fdr11 == min(fdr11)))
  if (counts1 %% 2 == 1) {
      fw2 <- median(fW1[which(fdr11 == min(fdr11))])
  } else {
      fw2 <- fW1[which(fdr11 == min(fdr11))][counts1/2+1]
  }
  if (fw2 > 0.5) {
      fW3 <- seq(fw2-0.5, fw2+0.5, 0.05)
  } else {
      fW3 <- seq(0.02, fw2+0.5, 0.05)
  }
  n2 <- length(fW3)
  fdr2 <- rep(0,n2)
  for(j in seq_len(n2)){
      sMm2 <- sageTestNew(orth_gene[, 2], orth_gene[, 4], orth_gene[,1],
                          orth_gene[, 3], sum(orth_gene[, 2]),
                          sum(orth_gene[, 4]), fW3[j])
      fdr2[j] <- sum(sMm2[hkind] < a)/length(hkind)
  }
  fdr22 <- abs(fdr2-a)
  counts2 <- length(which(fdr22 == min(fdr22)))
  if (counts2%%2 == 1) {
      fw4 <- median(fW3[which(fdr22 == min(fdr22))])
  } else {
      fw4 <- fW3[which(fdr22 == min(fdr22))][counts2/2+1]
  }
  if (fw4 > 0.25) {
      fW5 <- seq(fw4-0.25,fw4+0.25,0.005)
  } else {
     fW5 <- seq(0.02, fw4+0.25, 0.005)
  }
  n3 <- length(fW5)
  fdr3 <- rep(0,n3)
  for (j in seq_len(n3)) {
       sMm3 <- sageTestNew(orth_gene[, 2], orth_gene[, 4], orth_gene[, 1],
                           orth_gene[, 3], sum(orth_gene[, 2]),
                           sum(orth_gene[, 4]), fW5[j])
       fdr3[j] <- sum(sMm3[hkind] < a)/length(hkind)
  }
  fdr33 <- abs(fdr3-a)
  counts3 <- length(which(fdr33 == min(fdr33)))
  if (counts3%%2 == 1) {
      fw6 <- median(fW5[which(fdr33 == min(fdr33))])
  } else {
      fw6 <- fW5[which(fdr33 == min(fdr33))][counts3/2+1]
  }
  factor <- fw6
  return(factor)
}

其核心思想就是根据显著性阈值(α)构造一列数组

 fW1 <- seq(scale-0.5, scale + 0.5, 0.1)
## 或
 fW1 <- seq(0.02, scale +0.5, 0.1)

##计算p_val
 sMm1 <- sageTestNew(orth_gene[, 2], orth_gene[, 4], orth_gene[, 1],
                          orth_gene[, 3], sum(orth_gene[, 2]),
                          sum(orth_gene[, 4]), fW1[j])
## fW1[j] 为不同的显著性阈值

然后根据一系列方法去刷选,最后得到标准化因子 scbn_val

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容