初探R语言的OOP系统

前言

R语言中有两个最为基础的概念:对象函数。和之前学过的其余编程语言相比(如:c++,python),变量不会声明为某种数据类型。 变量分配有 R 对象,R 对象的数据类型便为变量的数据类型。 该文介绍R的OOP系统,R目前有四个各自独立的对象系统,分别为:S3, S4, R5/RC, R6. 类别class实际上是作为R对象的一个属性(attribute).目前大部分包是用 R3 OOP系统的,个人觉得 R4 更接近我们平时在 c, java 里的面向对象方法。

S3方法

为什么叫S3,其实很简单,S是因为R语言来源自S语言,3是版本信息。前面也提过,R内置函数中大部分函数是S3型的。举一个令人熟悉的例子,mtcars是数据包
datasets自带的一个数据集,假如我们想初步了解一下这个包中各变量的统计信息,我们很可能会使用上函数 summary(),得到结果如下:

>summary(mtcars)
>      mpg             cyl             disp             hp             drat             wt             qsec             vs               am        
 Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0   Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000   Min.   :0.0000  
 1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5   1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000   1st Qu.:0.0000  
 Median :19.20   Median :6.000   Median :196.3   Median :123.0   Median :3.695   Median :3.325   Median :17.71   Median :0.0000   Median :0.0000  
 Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7   Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375   Mean   :0.4062  
 3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0   3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000   3rd Qu.:1.0000  
 Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0   Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000   Max.   :1.0000  
      gear            carb      
 Min.   :3.000   Min.   :1.000  
 1st Qu.:3.000   1st Qu.:2.000  
 Median :4.000   Median :2.000  
 Mean   :3.688   Mean   :2.812  
 3rd Qu.:4.000   3rd Qu.:4.000  
 Max.   :5.000   Max.   :8.000  

随意拟合一个模型,再次使用函数 summary(),得到的却是不同的信息

> summary(lm(mpg ~ disp + hp, data = mtcars))

Call:
lm(formula = mpg ~ disp + hp, data = mtcars)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.7945 -2.3036 -0.8246  1.8582  6.9363 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 30.735904   1.331566  23.083  < 2e-16 ***
disp        -0.030346   0.007405  -4.098 0.000306 ***
hp          -0.024840   0.013385  -1.856 0.073679 .  
---
Signif. codes:  0***0.001**0.01*0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.127 on 29 degrees of freedom
Multiple R-squared:  0.7482,	Adjusted R-squared:  0.7309 
F-statistic: 43.09 on 2 and 29 DF,  p-value: 2.062e-09

也就是说,虽然这两个函数是同名函数,但实际上的工作却有所不同,像是C++里的 重载。实际上这两条语句的完整写法是

> summary.data.frame(mtcars)
> summary.lm(lm(mpg ~ disp + hp, data = mtcars))

字如其名,一个用于对数据框里的各列(各个变量)进行统计描述,一个对拟合的线性模型进行估计、检验等评价。
我们这时才发现,我们平时在使用 summary 功能时,压根就不需要考虑我们具体是对什么进行 summary,是 summary.data.frame 还是 summary.lm 还是其他的,不仅减轻了我们的负担,也使代码更美观(这里的美观是说将这一步的作用简明表示出来,就足矣了)
我们需要熟悉这样的形式:
g e n e r i c . m e t h o d \rm generic.method generic.method
泛型函数有很多,直接输入函数名(不带括号)可以得到相关信息, UseMethod(“as.Date”) 预示着这是个泛型函数,如下:

> as.Date
function (x, ...) 
UseMethod("as.Date")
<bytecode: 0x000001b63a781060>
<environment: namespace:base>

Methods()

The call to “UseMethod” means that this is a generic function, and it will call a specific method, a function, based on the class of the first argument. (All methods are functions; not all functions are methods.) You can list all the methods for a generic with methods(). (摘自 R for Data Science)
翻译一下,大概是说:对“UseMethod”的调用意味着这是一个泛型函数,它将基于第一个参数的类调用一个特定的方法,一个函数。(所有方法都是函数;并非所有函数都是方法。)可以使用methods()列出泛型的所有方法。
(为啥有时候查资料时,觉得很绕,是因为:它们把“泛型”和“方法”都和函数挂钩,叫成是“泛型函数”,且把方法叫做函数 😢)
为了不混淆概念,我们约定只用这两个名词: “泛型” & “方法”
methods() 的作用是查看该泛型下的所有方法

> methods("as.Date")
[1] as.Date.character   as.Date.default     as.Date.factor      as.Date.numeric     as.Date.POSIXct     as.Date.POSIXlt     as.Date.vctrs_sclr*
[8] as.Date.vctrs_vctr*
see '?methods' for accessing help and source code

对于输入的 x,泛型 as.Date() 会根据 x 的类型选择合适的方法,比如当 x 是个字符类型时,此时 as.Date 会使用其下的方法character(),也即实际上运行的是 as.Date.character(x)
使用 getS3method() 可以看到方法的具体实现:(实际上我发现大部分方法好像直接打名字也能出来源代码,不过确实有些不行,具体什么原因我也不太清楚)

> getS3method("as.Date", "default")
function (x, ...) 
{
    if (inherits(x, "Date")) 
        x
    else if (is.null(x)) 
        .Date(numeric())
    else if (is.logical(x) && all(is.na(x))) 
        .Date(as.numeric(x))
    else stop(gettextf("do not know how to convert '%s' to class %s", 
        deparse1(substitute(x)), dQuote("Date")), domain = NA)
}
<bytecode: 0x000001b639562ba0>
<environment: namespace:base>

UseMethod()

接下来我们学习如何创建自己的泛型及其下面的方法,这个例子来自文章A Simple Guide to S3 Methods
UseMethod() 用于自定义泛型,我们自定义泛型时,只需要把下面的变量名及UseMethod里的参数改一下就行了,虽然说两处地方名字可以不同,但这么做是徒劳的。

rss <- function(x) UseMethod("rss")

不同对象类型的残差平方和是不大相同的,针对 rpart,randomForest 类型我们可以定制不同的方法,因为它们所起的作用是相同的——计算RSS,所以在同一泛型下是自然合理的写法。

rss.rpart <- function(x, ···){
	result <- sum((residuals(x)**2))
    return(result)
}
rss.randomForest <- function(x, ···){
	temp <- x$y - x$predicted  
    result <- sum(temp**2)  
    return(result)
}

(这里写的是很粗糙的方法,真正写的时候要考虑各种异常情况)

在同一泛型下的不同方法中,用一个方法尤为重要——default 方法,当输入的对象类型与其他方法都不匹配时,便会考虑到default方法,一般来说,里面会考虑到很多的异常情况,并且对于各种错误操作提出有效的警告。启示使用者足够及时更正。

建议

最后一点小建议,S3面向对象方法“标识符”是 “.” 插在泛型和函数之间,其实不太好,我认为是一种设计上的考虑欠缺,像S4,它的标识符是 $ 我们平时命名函数名时其实也经常会出现点,知道了S3系统后,我现在改用下划线了。

S4方法

未完待续

参考资料:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值