读《R语言面向对象编程》
R主要面向统计计算,为数据科学家青睐,代码量一般不会很大,使用面向过程的编程方式就可以很好的完成编程任务。而且还是有RStudio这样的交互操作集成开发环境,所以大部分的R语言用户对R语言是不是面向对象的很疑惑,虽然我们都知道在R中一切皆对象,比如Seurat对象。其实用过Seurat的话,会感受到它既有S3 面向对象结构,又有S4对象结构。阅读源码也验证了这一点:其实Seurat这个R包用的主要是S3面向对象结构,但是在创建数据对象的时候用的是S4类。如下:
Seurat <- setClass(
Class = 'Seurat',
slots = c(
assays = 'list',
meta.data = 'data.frame',
active.assay = 'character',
active.ident = 'factor',
graphs = 'list',
neighbors = 'list',
reductions = 'list',
images = 'list',
project.name = 'character',
misc = 'list',
version = 'package_version',
commands = 'list',
tools = 'list'
)
)
那么,R语言中的面向对象到底是怎样的呢?这个问题需要答案。面向对象对很多程序员来说都不陌生,不管是真的会用还是讲笑话的时候听过。在提到这个名词的时候总是和一些听不懂的词汇联系到一起,如抽象,封装,继承,多态等。但是面向对象却是写给人看的。在面向对象的程序设计中,对象(object)是最基本的元素,不过对象指的是具体的实例,在对象之上还有一个类(class)的概念。
在R语言的中文世界里,R语言面向对象编程的知识很少被提及,大部分的R语言书籍是偏应用的。有的话也散见于《R语言核心技术手册》《高级R语言编程指南》《R语言编程艺术》等,很少有专门来讲这一节的。这与R语言的用户大多不是程序员不无关系,也与R语言的面向对象结构发展曲折有一定关系。在R中有四种面向对象结构。
徐静2018-08-06但是正当我们苦于没有系统的R语言面向对象资料的时候,我们发现了这本在线书:R语言面向对象编程。里面有对这四种类型的详细介绍,而且还有可执行的实例代码,这无疑会加快一般用户对R语言面向对象的理解。
这本书并不厚,代码也很简单,我们就不再这里一一演示了。就着Seurat的对象结构,我们来演示S3和S4的实现,这为我们理解Seurat的源码也是有所裨益的。
- S3方法
zgetEMB3 <- function(object,ebm='pca',...) UseMethod("zgetEMB3")
zgetEMB3.Seurat<- function(object,ebm='pca',...){
(object@reductions[[ebm]]@cell.embeddings[,1:2])
}
> head(zgetEMB3(pbmc_small,ebm = 'tsne'))
tSNE_1 tSNE_2
ATGCCAGAACGACT 0.8675977 -8.1007483
CATGGCCTGTGCAT -7.3925306 -8.7717451
GAACCTGATGAACC -28.2064258 0.2410102
TGACTGGATTCTCA 16.3480689 -11.1633255
AGTCAGACTGCACA 1.9113998 -11.1929311
TCTGATACACGTGT 3.1475998 -9.9369312
> head(zgetEMB3.Seurat(pbmc_small))
PC_1 PC_2
ATGCCAGAACGACT -0.77403708 -0.8996461
CATGGCCTGTGCAT -0.02602702 -0.3466795
GAACCTGATGAACC -0.45650250 0.1795811
TGACTGGATTCTCA -0.81163243 -1.3795340
AGTCAGACTGCACA -0.77403708 -0.8996461
TCTGATACACGTGT -0.77403708 -0.8996461
> showMethods(zgetEMB3)
Function "zgetEMB3":
<not an S4 generic function>
> getMethod("zgetEMB3","Seurat")
Error in getMethod("zgetEMB3", "Seurat") :
no generic function found for 'zgetEMB3'
> getS3method("zgetEMB3","Seurat")
function(object,ebm='pca',...){
(object@reductions[[ebm]]@cell.embeddings[,1:2])
}
<bytecode: 0x00000196ab91f060>
> ftype(zgetEMB3)
[1] "s3" "generic"
> otype(zgetEMB3)
[1] "base"
>
- S4 方法
setGeneric("zgetEMB4",function(obj,...){
standardGeneric("zgetEMB4")
})
setMethod("zgetEMB4","Seurat",function(obj,ebm='pca',...){
(obj@reductions[[ebm]]@cell.embeddings[,1:2])
})
> head( zgetEMB4(pbmc_small,'tsne'))
tSNE_1 tSNE_2
ATGCCAGAACGACT 0.8675977 -8.1007483
CATGGCCTGTGCAT -7.3925306 -8.7717451
GAACCTGATGAACC -28.2064258 0.2410102
TGACTGGATTCTCA 16.3480689 -11.1633255
AGTCAGACTGCACA 1.9113998 -11.1929311
TCTGATACACGTGT 3.1475998 -9.9369312
> head(zgetEMB4(pbmc_small,'pca'))
PC_1 PC_2
ATGCCAGAACGACT -0.77403708 -0.8996461
CATGGCCTGTGCAT -0.02602702 -0.3466795
GAACCTGATGAACC -0.45650250 0.1795811
TGACTGGATTCTCA -0.81163243 -1.3795340
AGTCAGACTGCACA -0.77403708 -0.8996461
TCTGATACACGTGT -0.77403708 -0.8996461
> head(zgetEMB4(pbmc_small))
PC_1 PC_2
ATGCCAGAACGACT -0.77403708 -0.8996461
CATGGCCTGTGCAT -0.02602702 -0.3466795
GAACCTGATGAACC -0.45650250 0.1795811
TGACTGGATTCTCA -0.81163243 -1.3795340
AGTCAGACTGCACA -0.77403708 -0.8996461
TCTGATACACGTGT -0.77403708 -0.8996461
> getMethod("zgetEMB4","Seurat")
Method Definition:
function (obj, ...)
{
.local <- function (obj, ebm = "pca", ...)
{
(obj@reductions[[ebm]]@cell.embeddings[, 1:2])
}
.local(obj, ...)
}
<bytecode: 0x00000196abc3d068>
Signatures:
obj
target "Seurat"
defined "Seurat"
> getS3method("zgetEMB4","Seurat")
Error in getS3method("zgetEMB4", "Seurat") :
no function 'zgetEMB4' could be found
In addition: Warning message:
In findGeneric(f, envir) :
'zgetEMB4' is a formal generic function; S3 methods will not likely be found
> showMethods(zgetEMB4)
Function: zgetEMB4 (package .GlobalEnv)
obj="Seurat"
> ftype(zgetEMB4)
[1] "function"
> otype(zgetEMB4)
[1] "S4"
查看目前Seurat的方法:
methods(class="Seurat")
[1] $ $<- [ [[
[5] [[<- AddMetaData as.CellDataSet as.loom
[9] as.SingleCellExperiment Command DefaultAssay DefaultAssay<-
[13] dim dimnames droplevels Embeddings
[17] FindClusters FindMarkers FindNeighbors FindSpatiallyVariableFeatures
[21] FindVariableFeatures GetAssay GetAssayData GetImage
[25] GetTissueCoordinates HVFInfo Idents Idents<-
[29] Key levels levels<- Loadings
[33] merge Misc Misc<- names
[37] NormalizeData OldWhichCells Project Project<-
[41] RenameCells RenameIdents ReorderIdent RunALRA
[45] RunCCA RunICA RunLSI RunPCA
[49] RunTSNE RunUMAP ScaleData ScoreJackStraw
[53] SetAssayData SetIdent show SpatiallyVariableFeatures
[57] StashIdent Stdev subset SubsetData
[61] SVFInfo Tool Tool<- VariableFeatures
[65] VariableFeatures<- WhichCells zgetEMB3 zgetEMB4
see '?methods' for accessing help and source code
可以看到我们创建的 zgetEMB3 和 zgetEMB4 已经在其中了。S4对象系统具有明显的结构化特征,更适合面向对象的程序设计。S3对象简答,具有动态性,结构化特征不明显,S4对象结构化,功能强大。Seurat 目前的结构是结合这两者,但是开发者文档写的很清楚:作者是建议使用S3的。而Bioconductor社区以S4对象作为基础框架,只接受S4定义的R包。这也某种程度上解释了为什么我们从来不没有在Bioconductor上安装过Seurat。loomR是基于R6面向对象类型实现的。
另外,在读Seurat源码的时候我们发现了下面的语法,作为思考题:这两种函数定义的方式有什么不同,说说其中的缘由。
#' @rdname DefaultAssay
#' @export
#' @method DefaultAssay Assay
#'
DefaultAssay.Assay <- function(object, ...) {
object <- UpdateSlots(object = object)
return(slot(object = object, name = 'assay.orig'))
}
#' @export
#' @method DefaultAssay<- Assay
#'
"DefaultAssay<-.Assay" <- function(object, ..., value) {
object <- UpdateSlots(object = object)
return(slot(object = object, name = 'assay.used'))
object <- UpdateSlots(object = object)
slot(object = object, name = 'assay.orig') <- value
return(object)
}
在线电子书地址,见所附第一条链接
https://dataxujing.github.io/R_oop/index.html
https://github.com/satijalab/seurat/wiki/S3-methods
https://github.com/satijalab/seurat/issues/990
https://www.tutorialspoint.com/r/r_functions.htm
https://adv-r.hadley.nz/functions.html
https://stat.ethz.ch/R-manual/R-patched/library/methods/html/Methods_for_S3.html
https://satijalab.org/loomR/loomR_tutorial.html
https://satijalab.org/seurat/install.html