表达谱芯片上的探针往往能够覆盖到所有人类基因,也就是说,能够同时检测所有人类基因的表达。但先前的实验表明,一个细胞中不可能所有基因都同时表达,能够同时表达的基因反而是少数。同时表达的基因约占总基因的40%左右。
由于探针与目标之间一定存在着非特异性结合,所以所有的探针均会产生信号。如果不加以过滤,认为这些探针对应的基因都表达,即不符合事实,也会对后续的分析产生影响。因此,过滤掉表达量低的探针是十分必要的。
注意,limma包的说明里面提供了两点建议。一,如果要进行探针过滤(filter),最好在进行标准化之后再过滤。二,如果要在后续分析中使用limma包,请不要进行基于方差(variance)的过滤,否则会影响方差分布,从而导致limma包处理产生糟糕的结果(poor results)。
bioconductor提供了两种过滤探针的方法,一种是使用专门进行探针过滤的genefilter包进行过滤,另一种是使用affy包中的mas5calls函数进行探针过滤。下面会详细解释如何进行探针过滤。
根据genefilter包的技术文档介绍,genefilter包设计的出发点是进行independent filtering。我们知道,检测差异基因表达是表达谱芯片最重要的功能之一。而检测差异基因表达的方法是进行对每一个基因进行统计检验。
所谓的independent filtering,意思是在进行统计检验之前,筛掉那些不能或是很可能不能通过显著性检验的探针。independent filtering不依赖于统计检验,在保持一类错误率(即假阳性率)不变的条件下,能够增加检验效能。
一个好的filtering criterion拥有下面三个特点:
1、在零假设的条件下,独立于统计检验
2、在备择假设的条件下,与统计检验相关联
3、在联合检验中不显著改变依赖结构
genefilter包中有两个常用的函数能够进行探针过滤,一个是genefilter函数,另一个是nsFilter函数。
使用genefilter函数进行探针过滤
genefilter函数需要用户指定筛选检验方式。用法如下。
genefilter(expr, flist)
expr是表达矩阵,需要用expr(ExpressionSet)
或expr(AffyBatch)
对ExpressionSet或AffyBatch进行提取。flist是包含检验方法的list,可以同时包含多个检验方法,同时进行多个检验,取交集。
举个例子。例子来源于genefilter包的技术支持文档。sample.ExpressionSet来自Biobase包。
library("Biobase") library("genefilter") data(sample.ExpressionSet) f1 <- kOverA(5, 200) ffun <- filterfun(f1) wh1 <- genefilter(exprs(sample.ExpressionSet), ffun) sum(wh1) [1] 159
f1函数的意思是指筛选出至少在5个样本中表达量超过200的基因。最终筛选到了159个基因。
再给一个例子。
f2 <- ttest(sample.ExpressionSet$type, p=0.1) wh2 <- genefilter(exprs(sample.ExpressionSet), filterfun(f2)) sum(wh2) [1] 88
f2函数的意思是进行 t.test,显著性水平 p=0.1,筛选出 t.test 中 p < 0.1 的基因,最终筛选到88个基因。
将两个筛选方法合并。
ffun_combined <- filterfun(f1, f2) wh3 <- genefilter(exprs(sample.ExpressionSet), ffun_combined) sum(wh3) [1] 35
满足f1和f2的基因只有35个。
过滤探针
sample.filter <- sample.ExpressionSet[wh3,] nrows(sample.filter) [1] 35
用户可以按照上述例子的方式,用genefilter包内整合的检验方法,构造自己的检验标准进行探针过滤。详情请见bioconductor网页的genefilter包技术支持文档。
使用nsFilter
函数进行探针过滤
虽然使用genefilter
函数能够自由地指定筛选标准,但很多时候我们并不需要对筛选标准进行改变,,而是希望使用同一套标准,或是懒得去调参数。
nsFilter
函数提供了一站式的探针过滤,能够一步对ExpressionSet
的探针进行过滤,返回一个list
。list
中的eset
为过滤后的ExpressionSet
,filter.log
为每一步过滤到多少探针的记录。nsFilter
函数也可以用于oligo包读取的对象。
使用方法如下。
nsFilter(eset, require.entrez=TRUE, remove.dupEntrez=TRUE, var.func=IQR, var.cutoff=0.5)
一般只对以下几个参数进行调整。
参数 | 注释 |
---|---|
require.entrez | 若为TRUE,过滤掉没有ENTREZ ID的探针。默认为TRUE。 |
remove.dupEntrez | 若为TRUE,当几个探针对应统同一ENTREZ ID的时候,留下 var.func 值最大的探针,其余过滤。默认为TRUE。 |
var.func | 用于过滤的统计参数。默认为IQR。 |
var.cutoff | 截断值。默认为0.5,即过滤到50%的基因。 |
这里要说明一下。由于探针过滤只能对 ExpressionSet
进行,不能对 AffyBatch
进行,因此要先进行标准化,再过滤。默认 var.cutoff = 0.5
, 即过滤掉50%的探针。
nsFilter
函数还提供了合并探针的功能,不过只针对 EntrezID。因此,如果要使用 nsFilter 函数提供的合并探针功能,就必须先对探针进行EntrezID注释。如果不使用 nsFilter
函数的合并探针功能,那先注释或后注释甚至不注释都没关系。
我喜欢用 gene symbol 注释探针而不喜欢用 EntrezID,因此也就不使用 nsFilter
函数合并重复探针的功能。而且,nsFilter
函数只能对ExpressionSet
对象进行,而我们一般不会对ExpressionSet
对象进行注释,所以合并重复探针的功能用不着。我一般习惯使用如下代码。
eset.filter <- nsFilter(eset, require.entrez=F, remove.dupEntrez=F) #不使用函数的合并探针功能 eset.filter$filter.log #查看每一步筛掉多少探针 eset.filter <- eset.filter$eset #只留下ExpressionSet对象
这样就能得到过滤掉低表达探针后的ExpressionSet
了。
如果想要使用nsFilter函数的合并探针功能,需设置require.entrez=TRUE
, remove.dupEntrez=TRUE
然后设置var.func 为自己想要用来合并探针的统计参数即可,默认为 IQR。
mas5calls
函数整合在 affy 包中。 mas5calls
函数的算法是 Wilcoxon signed rank-based gene expression presence/absence detection(不翻译反而意思更清楚)。这个算法整合与 mas5 算法中。这个函数的功能是对 AffyBatch 的探针状态进行评估,分类为”P”,”M”和”A”。我们可以只提取其中分类为”P”的探针,认为其他探针不表达;亦或是只删去分类为”A”的探针。由此过滤掉大部分表达量低的探针。
注意,由于 mas5calls
函数只能针对 AffyBatch
对象,因此只能用于标准化前的数据。而且不能用于oligo包读取的数据。
以下代码是使用 mas5calls
函数过滤探针的例子,数据来自于 affydata 包的 Dilution 数据。代码的功能是过滤掉在所有样本中均不表达的探针。
library(affy) library(affydata) data("Dilution") eset <- rma(Dilution) calls <- mas5calls(Dilution) # 获得探针的PMA信息 calls <- exprs(calls) # 提取包含探针的PMA信息的表达矩阵 head(calls) absent <- rowSums(calls == "A") # 统计每个探针在所有样本中分类为"A"的个数 absent <- which(absent == ncol(calls)) # 提取在所有样本中分类均为"A"的探针,也就是在所有样本中都不表达的探针。 rmaFiltered <- eset[-absent,] # 删去不表达的探针 nrow(exprs(eset)) # 过滤前的总探针数目 nrow(exprs(rmaFiltered)) # 过滤后的总探针数目
稍微更改一下条件。提取在所有样本中均表达的探针。代码如下。
present <- rowSums(calls == "P") # 统计每个探针在所有样本中分类为"P"的个数 present <- which(present == ncol(calls)) # 提取在所有样本中分类均为"P"的探针,也就是在所有样本中都表达的探针。 rmaFiltered <- eset[present,] # 只留下在所有样本中均表达的探针
再更改一下条件,提取至少在一个样本中表达的探针。代码如下。
present <- rowSums(calls == "P") # 统计每个探针在所有样本中分类为"P"的个数 present <- which(present != 0) # 提取至少在一个样本中表达的探针。 rmaFiltered <- eset[present,] # 留下至少在一个样本中表达的探针
大家可以按照自己的需求修改上述代码,达到过滤探针的目的。
使用 genefilter 函数过滤探针的好处在于能够自定义筛选条件,但多数情况下用不到。而使用 nsFilter 函数或 mas5calls 函数过滤探针的方法虽然固定,但操作方便。大家根据自己的需求使用相对应的函数就可以了。
mas5calls
函数只能针对AffyBatch
对象进行,因此无法用于oligo包读取的。oligo包读取的数据对象如下。可以看到无论哪种类型的对象都不能用于mas5calls
函数。
oligo包提供了paCalls
函数用于过滤探针。对于Whole Transcript arrays (Exon/Gene),可选择的method
有”DABG” (p-values for
each probe) 和 “PSDABG” (p-values for each probeset)。对于Expression arrays 来说,只能选”MAS5”。
使用oligoData包中的affyExpressionFS
数据作为例子。
首先载入数据。
library(oligo) library(oligoData) data("affyExpressionFS")
使用oligo包提供的rma
函数进行标准化。
注意,affy包中也有rma
函数。但affy包提供的rma
函数不能处理oligo包读取的对象。因此若同时加载了affy包,直接使用rma
函数可能会报错。可以选择不加载affy包,或是使用oligo::rma()
命令使用oligo包中的rma
函数。
> data.eset <- rma(affyExpressionFS) Background correcting Normalizing Calculating Expression
使用paCalls
函数。
calls <- paCalls(affyExpressionFS) #返回calls view(calls)
观察calls
的结构,发现calls
是一个list
,包含calls
和p
两个元素。calls
是包含探针PMA信息的matrix
,而p
是包含p值信息的matrix
。所谓的p值,是指探针表达量与背景相同的概率,越小说明探针与背景的差异越显著,也就是指探针表达的可能性越大。
接下来的步骤与方法2相同。详情请看方法2.
pmas <- calls$calls absent <- rowSums(pmas == "A") absent <- which(absent == ncol(pmas)) rmaFiltered <- data.eset[-absent,] nrow(data.eset) nrow(rmaFiltered)
这里提供另一个有趣的例子。GSE72154 。这个例子中paCalls
函数的返回值比较特别。下面将会详细介绍使用paCalls
函数过滤该研究。为了省时间和突出重点,芯片质量控制就不做了。
首先在ftp下载GSE72154_RAW.tar,解压缩到工作目录。然后用oligo包读取数据。
celfiles <- list.files(path = ".", pattern = ".CEL", full.names = T) data.raw <- read.celfiles(celfiles) #读取数据 sampleNames(data.raw) <- c("ler1","ler2","wrky1","wrky2") data.eset <- rma(data.raw) #标准化 calls <- paCalls(data.raw) #获得calls class(calls) #查看calls类型 head(calls) #查看calls信息
注意到,和上文不同,paCalls
函数在这个例子中返回值calls
是matrix
对象。同时要注意到,探针名称发生了改变。
data.raw和calls的探针名称一致,而data.eset的探针名称发生了改变。如果要对data.eset进行探针过滤,就势必要构建两种探针名称的对应关系。
观察calls的结构,发现这个例子中的calls,其实相当于上个例子中的calls$p,也就是包含p值的矩阵。因此,我们可以根据p值来过滤探针。
P <- apply(calls, 1, function(x) any(x < 0.05)) #选择在所有样本中至少有一个探针p值<0.05的探针名 pids <- as.numeric(names(P[P])) #将筛选后的转换探针名称转化为numeric对象,因为后续分析提取的转换探针名是numeric格式。 head(pids)
获得了符合条件”在所有样本中至少有一个探针p值<0.05”的探针名。接下来要将转换探针名称与原始探针名称对应起来。
pinfo <- getProbeInfo(data.raw) #获取转换探针名称与原始探针名称的对应关系 head(pinfo) fids <- pinfo[pinfo$fid %in% pids, 2] #获取筛选后的原始探针名称。第二列为原始探针名称。
过滤探针,返回ExpressionSet对象。
data.filter <- data.eset[rownames(data.eset) %in% fids, ] #保留符合筛选条件的探针,返回ExpressionSet对象 nrow(data.eset) #查看过滤前探针数目 nrow(data.filter) #查看过滤后探针数目
其实方法3的第一个例子也可以用第2个例子的方法来做,只是比较麻烦而已,所以如果能用第一个方法做就不要用第二个方法。如果都嫌麻烦的话,可以用nsFilter
函数来做,一步到位。
上面的例子过滤前有38408个探针,过滤后有35697个探针。只过滤了10%的探针。可以考虑更加严格的条件,比如apply(calls, 1, function(x) any(x<0.01))
(即p < 0.01), 或是 apply(calls, 1, function(x) sum(x < 0.05) > 2)
(即至少在3个样本中p < 0.05).
参考: