在R中,“S3方法”是什么意思?

140

由于我对R语言还比较陌生,所以不了解S3方法和对象是什么。我发现有S3和S4对象系统,并且一些人建议尽可能使用S3而不是S4(请参见Google的R风格指南http://google-styleguide.googlecode.com/svn/trunk/google-r-style.html)。然而,我不知道S3方法/对象的确切定义。

更新:截至2019年,Google的R风格指南超链接已移至此处

6个回答

92
大部分相关信息可以通过查看?S3?UseMethod来找到,但简而言之:
S3是一种方法调度方案。如果您使用R已经有一段时间,您会注意到对于许多不同类型的对象,有printpredictsummary方法。
在S3中,这是通过以下方式实现的:
  • 设置感兴趣对象的类别(例如:调用方法glm的返回值具有类别glm
  • 提供一个具有一般名称的方法(例如print),然后是一个点,然后是类名(例如:print.glm
  • 必须对此通用名称(print)进行一些准备工作才能使其工作,但如果您只是想符合现有方法名称,您不需要这样做(如果需要,请参见我之前提到的帮助文件)。
对于观察者,尤其是新创建的时髦模型拟合软件包的用户来说,能够键入predict(myfit, type="class")比键入predict.mykindoffit(myfit, type="class")更为方便。
还有很多东西需要了解,但这应该足以让您入门了。基于对象的属性(类)调度方法有很多缺点(C语言纯粹主义者可能会因此而惊恐失措),但对于许多情况,它的表现还不错。在当前版本的R中,实现了更新的方法(S4和引用类),但大多数人仍然(仅)使用S3。

56

为了让你开始使用S3,看一看median函数的代码。在命令提示符下键入median,可以发现它的主体有一行代码,即

UseMethod("median")

这意味着它是一个S3方法。换句话说,您可以为不同的S3类别拥有不同的median函数。要列出所有可能的中位数方法,请键入

methods(median) #actually not that interesting.  
在这种情况下,只有一个方法可用,默认情况下该方法被调用。您可以通过输入以下代码来查看它的代码
median.default

一个更有趣的例子是print函数,该函数有许多不同的方法。

methods(print)  #very exciting

注意,有些方法的名称旁边带有*,这意味着它们被隐藏在某个包的命名空间中。使用find找出它们所在的包。例如:

find("acf")  #it's in the stats package
stats:::print.acf

那么根据你的回答,这是否意味着median是一个S3方法,而print不是S3(也许是S4)? - Ankit Agrawal
看到 method(print),我看到了一个 print.zoo* 但是尝试使用 find("zoo") 查找它时,它会返回 character(0) 而不是包名。 - Ankit Agrawal

40

根据http://adv-r.had.co.nz/OO-essentials.html,R语言的三个面向对象系统在类和方法定义方面有所不同:

S3采用了一种称为泛型函数面向对象编程的风格。这与大多数编程语言(如Java、C++和C#)实现的消息传递面向对象不同。在消息传递中,将消息(方法)发送到对象,然后对象确定要调用哪个函数。通常,这个对象在方法调用中有一个特殊的外观,通常出现在方法/消息名称之前,例如canvas.drawRect("blue")。S3不同,虽然仍然通过方法进行计算,但是一种特殊类型的函数称为泛型函数决定要调用哪个方法,例如drawRect(canvas, "blue")。S3是一个非常休闲的系统,它没有类的正式定义。

  • S4与S3类似,但更加正式化。有两个主要区别:S4具有正式类定义,描述每个类的表示和继承,并具有用于定义泛型和方法的特殊辅助函数。S4还具有多重分派,这意味着泛型函数可以根据任意数量参数的类来选择方法,而不仅仅是一个。

  • 引用类(简称RC)与S3和S4非常不同。RC实现了消息传递面向对象,因此方法属于类而不是函数。$用于分隔对象和方法,因此方法调用看起来像canvas$drawRect("blue")。RC对象也是可变的:它们不使用R语言通常的复制-修改语义,而是原地修改。这使得它们更难推理,但允许它们解决使用S3或S4难以解决的问题。

  • 此外还有一个不太符合面向对象规范但很重要的系统:

    • 基本类型是其他OO系统底层的C级别类型。虽然基本类型主要使用C代码进行操作,但了解它们很重要,因为它们为其他OO系统提供了构建块。

    13

    我最初来到这个问题是想知道这些名称的来源。根据维基百科的这篇文章,R语言的名称来源于S编程语言的一个版本。其他答案中描述的方法分派方案来自于S并且根据版本进行了标记。


    11

    试一试

    methods(residuals)
    

    其中包括“residuals.lm”和“residuals.glm”。也就是说,当您拟合线性模型m并输入residuals(m)时,将调用residuals.lm。当您拟合广义线性模型时,将调用residuals.glm。 这有点像C++对象模型被颠倒了过来。在C++中,您定义一个基类,其中包含虚函数,派生类重写这些虚函数。 在R中,您定义一个虚拟(也称为通用)函数,然后决定哪些类将覆盖此函数(即定义方法)。请注意,执行此操作的类不需要派生自一个共同的超类。 我不会认为普遍更喜欢S3而不是S4。 S4具有更多正式化(=更多打字),这可能对某些应用程序来说太多了。但是,S4类可以像在C ++中定义类或结构体一样定义。例如,您可以指定某个类的对象由字符串和两个数字组成:

    setClass("myClass", representation(label = "character", x = "numeric", y = "numeric"))
    

    使用该类的对象调用的方法可以依赖于对象具有这些成员。这与S3类非常不同,S3类只是一堆元素的列表。

    使用S3和S4,您通过fun(object, args)调用成员函数,而不是object$fun(args)。如果您正在寻找类似后者的东西,请查看proto包。


    我非常确定,在R语言中,成员函数和方法属于对象的想法并不那么合理。方法不属于对象(函数也是对象),而是属于函数本身。 - petermeissner

    5
    这是根据Hadley Wickham(RStudio的首席科学家)在他的书籍《Advanced R, 2nd edition》(CRC Press,2019)中介绍的众多R对象系统的最新简要概述。该书有一个基于面向对象编程章节的网页版本here。请注意保留HTML标记。

    Advanced R book cover

    第一版于2015年有一个网页版本这里,对应的OO章节在这里

    面向对象系统的方法

    Hadley定义了以下内容来区分两种不同的面向对象编程方法:

    函数式面向对象编程:方法(可调用的代码片段)属于通用函数(不要与Java / C# 通用方法混淆)。将方法视为位于全局查找表中。运行时系统会根据函数名称和传递给该函数的一个或多个参数的类型(或对象类)来查找要执行的方法(这称为“方法分派”)。从语法上讲,方法调用可能看起来像普通的函数调用:myfunc(object, arg1, arg2)。如果语言支持,此调用将导致运行时查找与对(“myfunc”,typeof(object))或可能的(“myfunc”,typeof(object),typeof(arg1),typeof(arg2))相关联的方法。在R的S3中,通用函数的完整名称给出了(函数名,类)对。例如:mean.Date是计算日期平均值的方法。尝试使用methods(“mean”)列出具有函数名称mean的通用方法。函数式面向对象编程方法可以在OO先驱SmalltalkCommon Lisp Object SystemJulia中找到。Hadley指出:“与R相比,Julia的实现已经完全发展并且非常高效。” 封装的面向对象编程:方法属于对象或类,方法调用通常看起来像object.method(arg1, arg2)。这被称为封装,因为对象封装了数据(字段)和行为(方法)。将方法视为附加到对象或对象类描述的查找表中。运行时根据方法名称和可能的一个或多个参数类型查找方法。这是"C++、Java、C#"等"流行"OO语言中采用的方法。

    在两种情况下,如果支持继承(可能是),运行时可以向上遍历类层次结构,直到找到与调用查找键匹配的位置。

    如何找出R对象属于哪个系统

    library(sloop) # formerly, "pryr"
    otype(mtcars)
    #> [1] "S3"
    

    R对象系统

    S3

    • 函数式面向对象方法。
    • 根据Hadley的说法是最重要的系统。
    • 最简单、最常见。R使用的第一个OO系统。
    • 随着基础R一起提供,被广泛使用。
    • 依赖于约定而不是强制保证。
    • 参见Chambers, John M, and Trevor J Hastie. 1992. "Statistical Models in S." Wadsworth & Brooks/Cole Advanced Books & Software.
    • 详细信息请参见"Advanced R, 2nd edition"这里

    S4

    • 函数式面向对象编程方法。
    • 根据Hadley的说法,RC是第三个最重要的系统。
    • RC是S3的重写版本,因此与S3类似,但更加正式和严格:它强制您仔细考虑程序设计。适用于构建大型系统(例如Bioconductor项目)。
    • 实现在基础的“methods”包中。
    • 参见:Chambers, John M. 1998. "Programming with Data: A Guide to the S Language." Springer.
    • 有关详细信息,请参阅“Advanced R, 2nd edition”这里

    RC也称为“Reference Classes”

    • 封装的面向对象编程方法。
    • 自带基础R功能。
    • 基于S4。
    • RC对象是一种特殊类型的S4对象,也是"可变"的。即,它们可以在原地修改,而不是使用R通常的复制-修改语义。请注意,可变状态很难理解,并且是丑陋的错误源,但在某些应用程序中可以导致更高效的代码。

    R6

    • 封装的面向对象编程方法。
    • 根据哈德利的说法,是第二重要的系统。
    • 可以在R6包中找到(使用library(R6)进行安装)。
    • 类似于RC,但更轻便且快速:它不依赖于S4或methods包。建立在R环境的基础上。还具有:
      • 公共和私有方法
      • 活动绑定(字段,当访问时实际上调用一个方法)
      • 跨包工作的类继承
      • 既有类方法(属于类的代码,可以通过selfprivatesuper访问实例),也有成员函数(分配给字段的函数,但不是方法,只是函数)
    • 提供了一种标准化的逃避R的“复制修改”语义的方式。
    • 请参见软件包网站:“R6:为R提供封装的面向对象编程”
    • 详见“Advanced R, 2nd edition”此处

    其他

    还有一些其他的包,比如 R.oo(类似于RC),proto(基于原型,类似JavaScript)和Mutatr。然而,“Advanced R”指出:

    除了广泛使用的R6外,这些系统主要是理论上的兴趣。它们确实有其优点,但很少有R用户知道和理解它们,因此对于其他人阅读和贡献你的代码是困难的。

    确保您也阅读了“Advanced R, 2nd edition”权衡的章节。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接