前言
最近在琢磨单细胞的分析包Seurat,其对象type是一个S4 object,value显示为S4 object of class Seurat,class则是Seurat,这对一个R一知半解的我而言是有点混乱的,由于目前对S4 object一无所知,所以目前的学习策略就是从S3 object学起,了解其发展,达到最后理解的作用。
我们可以看一下Seurat包里面示例数据的class:
> summary(pancreas)
Length Class Mode
1 Seurat S4
> class(pancreas)
[1] "Seurat"
attr(,"package")
[1] "Seurat"
学习资源来自:
R tutorial
A Simple Guide to S3 Methods
The S3 object system
正文
1.1 The basic idea
首先,我们要知道,在R里面,万物皆是对象(object),在R语言里面创建的对象都会有一个属性(attribute),一个对象常见的属性就是类别(class),你可以通过class命令来设置class的属性,需要注意的是,class是一个向量(vector),是可以拓展继承的,同时允许用户定义其顺序:
> bubba <- c(1,2,3)
> bubba
[1] 1 2 3
>
> class(bubba)
[1] "numeric"
>
> class(bubba) <- append(class(bubba),"Flamboyancy")
> class(bubba)
[1] "numeric" "Flamboyancy"
为一类class定义一个S3 method可以通过UseMethod命令来实现,该命令会告诉R寻找一个函数(function),该函数前缀对匹配当前的函数(即代码行的函数),同时该函数后缀匹配数据的class(或者classes),换句话来说,一系列的函数可以被定义,同时函数可以用来处理对应class的数据。
所以,第一步就是创建一个类函数(generic function),UseMethod函数就是在告诉R去寻找该类函数下属的函数,寻找的方式根据两部分,一部分是函数的name,一部分是对象的class(或者classes),寻找到的该函数由两部分组成,通过"."连接,前部分对应函数name,后一部分对应对象的class。
一定没理解我上一段说的乱七八糟的话吧,直接看代码,然后再回过头来理解。
> bubba <- list(first="one", second="two", third="third")
> class(bubba)
[1] "list"
> GetFirst <- function(x){
+ UseMethod("GetFirst",x)
+ }
> GetFirst.list <- function(x){
+ return(x$first)
+ }
> GetFirst(bubba)
[1] "one"
> class(bubba) <- "Flamboyancy"
> GetFirst(bubba)
Error in UseMethod("GetFirst", x) :
no applicable method for 'GetFirst' applied to an object of class "Flamboyancy"
> GetFirst.Flamboyancy <- function(x){
+ return(x$first)
+ }
> GetFirst(bubba)
[1] "one"
1.2 Memory management
过多的面对对象的方法导致了一个很常见的问题:我该使用哪一个方法?S3类和S4类相比,S3类更灵活些,而S4 class的限制更多一些。
我们的学习对象是S3类,在我们进一步研究其之前,我们需要来讨论一下内存环境(memory environment),他会极大影响S3类导致你的代码完全不兼容,同时也赋予了S3类的灵活性。一个环境可以被理解为一个局部作用域(loca scope),它拥有一系列的相关变量,有一系列的命令可以用来处理和获得你环境里的变量,例如assign和get命令可以设置和得到一个环境里面变量的值:
> rm(list = ls(envir = e))
> ls()
character(0)
> rm(list = ls())
> ls()
character(0)
> e <- environment()
> e
<environment: R_GlobalEnv>
> assign("bubba",3,e)
> bubba
[1] 3
> get("bubba",e)
[1] 3
> rm(list = ls(envir = e))
> ls()
character(0)
环境可以被创造并且被包含在别的环境里面与此同时创造成一个层次结构,有一系列的命令可以帮助你处理不同的环境,使用命令help(enviroment),但是我们这里并不多追究细节因为了解这么多就足够我们学习S3类了。
1.3 Creating an S3 class
在章节里面我们可以讨论过创建S3类的最简单的方法了,在章节里面我们可以讨论过创建S3类的最简单的方法了,我们现在拓展这个想法,同时了解一下如何创建一个函数根据给定的类返回一个对象,基本的理念就是创建一个list包含相应的变量,同时这个list的类被定义,该list被返回。
可以有两种方法完成这样的操作:
- Stright forward approach
- Local environment approach
学习时间截至:2018-11-28
1.3.1 Stright forward approach
<p id = "build"></p>
第一种方法就是Straight Forward Approach,相对来说是非常常见的一种S3创建方法:
NorthAmerican <- function(eatsBreakfast=TRUE,myFavorite="cereal")
{
me <- list(
hasBreakfast = eatsBreakfast,
favoriteBreakfast = myFavorite
)
## 给类命名
class(me) <- append(class(me),"NorthAmerican")
return(me)
}
一旦这行代码被执行了,一个新的函数也就被定义了,叫做NorthAmerican,一个新的有定义类的对象可以通过这个函数创建:
> bubba <- NorthAmerican()
> bubba
$`hasBreakfast`
[1] TRUE
$favoriteBreakfast
[1] "cereal"
attr(,"class")
[1] "list" "NorthAmerican"
> bubba$hasBreakfast
[1] TRUE
> louise <- NorthAmerican(eatsBreakfast=TRUE,myFavorite="fried eggs")
> louise
$`hasBreakfast`
[1] TRUE
$favoriteBreakfast
[1] "fried eggs"
attr(,"class")
[1] "list" "NorthAmerican"
1.3.2 Local environment approach
另一个方法是local environment approach,它可以利用一个函数的本地环境去获取变量,这个方法看起来更像是面对对象的方法。
这个方法依赖于函数被定义时创建的局部作用域(local scope),一个新的环境可以使用environment命令来生成同时保存在list里面,这个局部作用域里面的变量就可以使用这个环境来确定和选取。
下面的示例需要理解的地方会更多一下,但是当我们随后演示往这一类对象里面添加方法的时候会理解会简单一些:
> rm(list = ls())
> NordAmericain <- function(eatsBreakfast=TRUE,myFavorite="cereal")
+ {
+
+ ## Get the environment for this
+ ## instance of the function.
+ thisEnv <- environment()
+
+ hasBreakfast <- eatsBreakfast
+ favoriteBreakfast <- myFavorite
+
+ ## Create the list used to represent an
+ ## object for this class
+ me <- list(
+
+ ## Define the environment where this list is defined so
+ ## that I can refer to it later.
+ thisEnv = thisEnv,
+
+ ## The Methods for this class normally go here but are discussed
+ ## below. A simple placeholder is here to give you a teaser....
+ getEnv = function()
+ {
+ return(get("thisEnv",thisEnv))
+ }
+
+ )
+
+ ## Define the value of the list within the current environment.
+ assign('this',me,envir=thisEnv)
+
+ ## Set the name for the class
+ class(me) <- append(class(me),"NordAmericain")
+ return(me)
+ }
> bubba <- NordAmericain()
> bubba
$`thisEnv`
<environment: 0x00000000061e00a8>
$getEnv
function ()
{
return(get("thisEnv", thisEnv))
}
<environment: 0x00000000061e00a8>
attr(,"class")
[1] "list" "NordAmericain"
> get(x = "bubba", envir = bubba$getEnv())
$`thisEnv`
<environment: 0x00000000061e00a8>
$getEnv
function ()
{
return(get("thisEnv", thisEnv))
}
<environment: 0x00000000061e00a8>
attr(,"class")
[1] "list" "NordAmericain"
> get("bubba")
$`thisEnv`
<environment: 0x00000000061e00a8>
$getEnv
function ()
{
return(get("thisEnv", thisEnv))
}
<environment: 0x00000000061e00a8>
attr(,"class")
[1] "list" "NordAmericain"
> get("hasBreakfast",bubba$getEnv())
[1] TRUE
> get("hasBreakfast")
Error in get("hasBreakfast") : object 'hasBreakfast' not found
> bubba$getEnv()
<environment: 0x00000000061e00a8>
类(class)被定义以后,环境可以通过getEnv()函数简单的提取到:
> bubba <- NordAmericain()
> get("hasBreakfast",bubba$getEnv())
[1] TRUE
> get("favoriteBreakfast",bubba$getEnv())
[1] "cereal"
需要注意的是,这个方法也有一个缺点,就是它依赖于环境,当你复制了一个对象的时候,你同时也复制了这个环境,你所做出的改动会影响到所有复制。
> bubba <- NordAmericain(myFavorite="oatmeal")
> get("favoriteBreakfast",bubba$getEnv())
[1] "oatmeal"
> louise <- bubba
> assign("favoriteBreakfast","toast",louise$getEnv())
> get("favoriteBreakfast",louise$getEnv())
[1] "toast"
> get("favoriteBreakfast",bubba$getEnv())
[1] "toast"
1.4 Creating methods
那我们现在学习如何创建和一个类(class)相关的方法(methods)。依旧分为两种方法来讲述,同时会结合上面提到的两种方法来做,如果你希望学习的直接粗暴些,使用第一种方法比较合适。
1.4.1 Straight forward approach
第一种方法就是在类外定义函数对类内变量进行修改,通过genric way来定义函数,随后再定义对应的类。R语言会帮你决定根据对应的类属性使用对应的函数(也就是函数的后缀)。
在示例里,我们定义对应上面class的函数,我们假设NorthAmerican函数就像之前我们定义的那样,第一个示例里面我们希望可以创建一个函数,为一个对象设置hasBreakfast的值,这个函数的名称为setBreakfast。
第一步就是建立函数,然后使用UseMethod命令告诉R去搜索正确的函数进行处理,如果我们传递一个属性为NorthAmerican的对象,正确的处理函数就应该是 setHasBreakfast.NorthAmerican,注意我们同时还需要创建一个默认函数setHasBreakfast.default来处理类属性不一致的对象:
setHasBreakfast <- function(elObjeto, newValue)
{
print("Calling the base setHasBreakfast function")
UseMethod("setHasBreakfast",elObjeto)
print("Note this is not executed!")
}
setHasBreakfast.default <- function(elObjeto, newValue)
{
print("You screwed up. I do not know how to handle this object.")
return(elObjeto)
}
setHasBreakfast.NorthAmerican <- function(elObjeto, newValue)
{
print("In setHasBreakfast.NorthAmerican and setting the value")
elObjeto$hasBreakfast <- newValue
return(elObjeto)
}
需要注意的是,在函数里直接改变变量不会存储到原始对象,所以你必须函数里返回更改后的对象才可以:
> bubba <- NorthAmerican()
> bubba$hasBreakfast
[1] TRUE
> bubba <- setHasBreakfast(bubba,FALSE)
[1] "Calling the base setHasBreakfast function"
[1] "In setHasBreakfast.NorthAmerican and setting the value"
> bubba$hasBreakfast
[1] FALSE
> bubba <- setHasBreakfast(bubba,"No type checking sucker!")
[1] "Calling the base setHasBreakfast function"
[1] "In setHasBreakfast.NorthAmerican and setting the value"
> bubba$hasBreakfast
[1] "No type checking sucker!"
如果正确的函数无法被找到,默认的方法就会被调用:
> someNumbers <- 1:4
> someNumbers
[1] 1 2 3 4
> someNumbers <- setHasBreakfast(someNumbers,"what?")
[1] "Calling the base setHasBreakfast function"
[1] "You screwed up. I do not know how to handle this object."
> someNumbers
[1] 1 2 3 4
更新时间截至:2018-12-28
1.4.2 Local environment approach
如果你使用的是1.3章节第二个方法来创建方法,