富集分析的原理与实现

一般做完差异分析都会做这一步,目的是找到差异基因富集到的通路,进而与生物学意义联系起来。具体的统计方法很简单,这篇笔记里面的代码可以从零搭建一个富集分析工具。

后台回复20211007获取本文的测试数据和代码,以及(单细胞)转录组分析中可能用到的GO KEGG富集分析代码(这部分本文不演示)。

关于Gene Ontology (GO), KEGG这些背景就不讲了,网上很多资料。

将富集分析中的问题抽象出来,其实就是下图的“摸球”问题。

蓝色方框中的球是所有的基因【共N个】,在探究某个特定通路P时,通路里面涉及到的基因用红色表示【共M个】。绿色圆圈是一次摸球事件,用来表示做了一次差异分析得到的基因【共n个】,这些基因中,有属于通路P的(红色球)【共k个】,有不属于的(黑色球)。

用摸球问题中的语言再描述一次:袋子中共有黑球和红球N个,其中红球M个。某次抽样中,一共摸球n个,其中红球k个,问在这次摸球中,红球的占比是否显著高于袋子中红球的占比?
(以前学摸球问题/超几何分布的时候,可能只求概率,没有进一步到这个统计检验)

回答这个问题,需要求出问题中这个事件的概率以及更极端事件的概率之和,也就是p值,小于0.05或者0.01就能认为是显著了。

1. 一个通路,计算p值

接下来以一个GO term (GO_extracellular_matrix_organization)为例,计算p值。
用的通路基因集是小鼠 GO BP的基因集,差异基因集是单细胞转录组分析中一个cluster的高表达基因

library(tidyverse)
library(clusterProfiler)

gmt.df=read.gmt("Mm.c5.bp.v7.1.SYMBOL.gmt")
deg=read.table("test_deg.txt",header = T,sep = "\t",stringsAsFactors = F)
deg=deg[deg$gene %in% gmt.df$gene,]

这种情况下,前面说的几个参数的值如下:

  • 全部球的个数/全部基因数:
    N=length(unique(gmt.df$gene))
  • 全部红球的个数/通路基因集的基因数:
    one.set=gmt.df[ gmt.df$term %in% c("GO_extracellular_matrix_organization") ,] M=length(one.set$gene)
  • 摸球数/差异基因数:
    n=length(deg$gene)
  • 摸球中红球的个数/差异基因中属于这个通路的基因数:
    k=sum(deg$gene %in% one.set$gene)

N M n k的值分别为: 23210, 271, 47, 6

求p值之前,先看一下满足N M n这三个参数的超几何分布,在不同的k值之下的概率:

df1=data.frame(x=1:47,y=dhyper(x=1:47, M, N-M, n))
df1%>%ggplot(aes(x,y))+geom_point()+
  geom_line()+
  geom_vline(xintercept=k,color="red",linetype=5)+  #这里k等于6,其作为阈值
  theme_bw()+
  theme(panel.grid = element_blank())

dhyper()用来求概率,四个参数分别是:摸球中红球个数的向量,袋中红球数,袋中黑球数,摸球数

我们要计算的就是红线以及右边那些红球数对应的概率之和,如下:

> phyper(k-1,M, N-M, n, lower.tail=FALSE)
[1] 1.722659e-05

lower.tail=FALSE,计算的是P[X > x],即大于第一个参数的概率之和。上面的代码第一个参数写的是k-1,因为我们需要求k以及k右边的概率之和。

以上是对一个通路求p值


2. 多个通路,依次计算p值

如果是多个通路,需要循环操作,依次对每个通路进行富集分析。
下面的演示用到的差异基因集和GO BP基因集同上

分析哪些pathway?要满足两个条件:

  • 通路里面基因的数量满足一定要求
  • 至少和deg有基因交集

下面的代码就是对通路做过滤的

bp.stat=as.data.frame(table(gmt.df$term))
colnames(bp.stat)[1]="pathway"
bp.stat=bp.stat%>%filter(Freq >= 2 & Freq <= 2000)

tmp.df=gmt.df
tmp.df$TF=tmp.df$gene %in% deg$gene
tmp.stat=as.data.frame(tmp.df %>% dplyr::group_by(term) %>% dplyr::summarize(counts=sum(TF)))
tmp.stat=tmp.stat%>%filter(counts > 0)
keep.pw=sort(intersect(bp.stat$pathway,tmp.stat$term))

下面就是循环求p值了

N=length(unique(gmt.df$gene))
n=length(deg$gene)
term=c()
pvalue=c()

for (i in keep.pw) {
  one.set=gmt.df[ gmt.df$term %in% i ,]
  M=length(one.set$gene)
  k=sum(deg$gene %in% one.set$gene)
  one.pvalue=phyper(k-1,M, N-M, n, lower.tail=FALSE)
  term=append(term,i)
  pvalue=append(pvalue,one.pvalue)
}

my_go_res=data.frame(term=term,pvalue=pvalue)
my_go_res=my_go_res%>%arrange(pvalue)

到这会儿还没有结束,还差一个FDR


3. 计算矫正p值

FDR, False Discovery Rates。为什么要控制FDR,降低假阳性。
这里用到的是The Benjamini-Hochberg method

The Benjamini-Hochberg method

假设我们对10个通路做了富集分析,我们会先得到10个p值:

  1. 将这10个p值从小到大排序
  2. 从1到10给这些p值排序
  3. 最大的FDR adjusted p value(第10位)等于原来最大的那个p值
  4. 第9位的FDR adjusted p value等于这两个值中的较小值:
    ①前一位矫正的p值;
    ②当前未矫正的p值 * (p值总个数/当前位数)
  5. 重复第4步,直到第1位

代码如下:

fdr=c()
for (i in dim(my_go_res)[1]:1) {
  if (i==dim(my_go_res)[1]) {
    tmpfdr=my_go_res$pvalue[i]
  }else{
    tmpfdr=min(tmpfdr,my_go_res$pvalue[i] * (dim(my_go_res)[1] / i))
  }
  fdr=append(fdr,tmpfdr)
}
my_go_res$p.adj=rev(fdr)

到这儿富集分析的完整流程才算结束


4. 轮子有现成的

当然,这个算法已经非常常见了,clusterProfiler的enricher()就能够自定义基因集做富集分析。使用如下:

deg_gmt=clusterProfiler::enricher(deg$gene,TERM2GENE = gmt.df,minGSSize = 2,maxGSSize = 2000)
go_res=deg_gmt@result

和上面的结果是一模一样的。

今天的内容就到这里,后台回复20211007获取本文的测试数据和代码,以及(单细胞)转录组分析中可能用到的GO KEGG富集分析代码(这部分本文不演示)。

ref

因水平有限,有错误的地方,欢迎批评指正!

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

推荐阅读更多精彩内容