R语言S3对象的简单学习

拥抱未来

前言

最近在琢磨单细胞的分析包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),它拥有一系列的相关变量,有一系列的命令可以用来处理和获得你环境里的变量,例如assignget命令可以设置和得到一个环境里面变量的值:

> 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章节第二个方法来创建方法,

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,629评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 今天下午,很早就下班。为的是去参加赛贝线下聚会,其实很少参加线下这种,但是这次真的很想亲眼去看看,见识下世面。这就...
    俞到更好的自己阅读 455评论 0 0
  • //联系人:石虎QQ: 1224614774昵称:嗡嘛呢叭咪哄 一、变量声明 为便于下文讨论,提前创建父类Biol...
    石虎132阅读 292评论 0 9
  • 《搭接与转化》 丈夫与儿子玩文字切换,把儿子播音主持的语音作业录音切换成文字。看谁的普通话说...
    宿鸭湖吴哲阅读 180评论 0 0