学习R语言:面向对象编程

目录

本文内容来自《R 语言编程艺术》(The Art of R Programming),有部分修改

R 语言虽然与传统的面向对象语言 (如 C++,JAVA 和 Python) 不同,但也是一个明显的面向对象语言。

  • R 中所有要素都是对象,从数字到字符串到矩阵
  • R 支持封装 (encapsulation)
  • R 类支持多态 (polymorphic)
  • R 允许继承 (inheritance)

下面介绍 R 语言中的 S3 和 S4 类。

S3 类

R 中原始的类结构,包含一个列表,附加类名属性和调度 (dispatch) 功能

S3 泛型函数

同一个函数可以针对不同的类调用不同的操作。

实例:线性模型函数 lm() 中的 OOP

x <- c(1, 2, 3)
y <- c(1, 3, 8)
lm_out <- lm(y ~ x)
class(lm_out)
[1] "lm"
lm_out
Call:
lm(formula = y ~ x)

Coefficients:
(Intercept)            x  
       -3.0          3.5  

这里使用的是 print.lm() 方法。

去掉 lm_out 的类属性,再打印对象

unclass(lm_out)
$coefficients
(Intercept)           x 
       -3.0         3.5 

$residuals
   1    2    3 
 0.5 -1.0  0.5 

$effects
(Intercept)           x             
  -6.928203   -4.949747    1.224745 

$rank
[1] 2

$fitted.values
  1   2   3 
0.5 4.0 7.5 

$assign
[1] 0 1

$qr
$qr
  (Intercept)          x
1  -1.7320508 -3.4641016
2   0.5773503 -1.4142136
3   0.5773503  0.9659258
attr(,"assign")
[1] 0 1

$qraux
[1] 1.577350 1.258819

$pivot
[1] 1 2

$tol
[1] 1e-07

$rank
[1] 2

attr(,"class")
[1] "qr"

$df.residual
[1] 1

$xlevels
named list()

$call
lm(formula = y ~ x)

$terms
y ~ x
attr(,"variables")
list(y, x)
attr(,"factors")
  x
y 0
x 1
attr(,"term.labels")
[1] "x"
attr(,"order")
[1] 1
attr(,"intercept")
[1] 1
attr(,"response")
[1] 1
attr(,".Environment")
<environment: R_GlobalEnv>
attr(,"predvars")
list(y, x)
attr(,"dataClasses")
        y         x 
"numeric" "numeric" 

$model

寻找泛型函数的实现方法

print
function (x, ...) 
UseMethod("print")
<bytecode: 0x0000029cb2efe180>
<environment: namespace:base>

methods() 函数列出某个函数对应的所有泛型函数。

methods(print)
  [1] print.acf*                                          
  [2] print.anova*                                        
  [3] print.aov*                                          
  [4] print.aovlist*                                      
  [5] print.ar*                                           
  [6] print.Arima*                                        
  [7] print.arima0*                                       
  [8] print.AsIs                                          
  [9] print.aspell*
  ...skip...

星号标注的是不可见函数 (nonvisible function)。 可以使用 getAnywhere() 函数找到函数的命名空间。

getAnywhere(print.lm)
A single object matching ‘print.lm’ was found
It was found in the following places
  registered S3 method for print from namespace stats
  namespace:stats
with value

function (x, digits = max(3L, getOption("digits") - 3L), 
    ...) 
{
    cat("\nCall:\n", paste(deparse(x$call), sep = "\n", 
        collapse = "\n"), "\n\n", sep = "")
    if (length(coef(x))) {
        cat("Coefficients:\n")
        print.default(format(coef(x), digits = digits), print.gap = 2L, 
            quote = FALSE)
    }
    else cat("No coefficients\n")
    cat("\n")
    invisible(x)
}
<bytecode: 0x0000029cba7e8b90>
<environment: namespace:stats>

调用命名空间 stats 中的 print.lm() 函数

stats:::print.lm(lm_out)
Call:
lm(formula = y ~ x)

Coefficients:
(Intercept)            x  
       -3.0          3.5  

编写 S3 类

S3 类实例通过构建列表的方式来创建:

  • 创建列表
  • 设置类属性
j <- list(
  name="Joe",
  salary=55000,
  union=TRUE
)
class(j) <- "employee"
attributes(j)
$names
[1] "name"   "salary" "union" 

$class
[1] "employee"

使用默认的 print() 输出

j
$name
[1] "Joe"

$salary
[1] 55000

$union
[1] TRUE

attr(,"class")
[1] "employee"

employee 类型定义 print 函数

print.employee <- function(wrkr) {
  cat(wrkr$name, "\n")
  cat("salary", wrkr$salary, "\n")
  cat("union member", wrkr$union, "\n")
}

检查 employee 类的方法

methods(, "employee")
[1] print

实际测试

j
Joe 
salary 55000 
union member TRUE 

使用继承

k <- list(
  name="Kate",
  salary=68000,
  union=FALSE,
  hrsthismonth=2
)
class(k) <- c("hrlyemployee", "employee")

hrlyemployee 类继承 employee 类的 print 方法

k
Kate 
salary 68000 
union member FALSE 

S4 类

S4 类与 S3 类对比

操作S3 类S4 类
定义类在构造函数的代码中隐式定义setClass()
创建对象创建列表,设置类属性new()
引用成员变量$@
实现泛型函数 f()定义 f.classname()setMethod()
声明泛型函数UseMethod()setGeneric()

编写 S4 类

定义 employee

setClass(
  "employee",
  representation(
    name="character",
    salary="numeric",
    union="logical"
  )
)

创建 employee 类对象

joe <- new(
  "employee",
  name="Joe",
  salary=55000,
  union=TRUE
)
joe
An object of class "employee"
Slot "name":
[1] "Joe"

Slot "salary":
[1] 55000

Slot "union":
[1] TRUE

成员变量称为 slot,通过 @ 引用

joe@salary
[1] 55000

可以使用 slot() 函数通过组件名访问组件

slot(joe, "salary")
[1] 55000

可以给组件赋值

joe@salary <- 65000
joe
An object of class "employee"
Slot "name":
[1] "Joe"

Slot "salary":
[1] 65000

Slot "union":
[1] TRUE
slot(joe, "salary") <- 88000
joe
An object of class "employee"
Slot "name":
[1] "Joe"

Slot "salary":
[1] 88000

Slot "union":
[1] TRUE

访问未定义的组件会报错,但在 S3 中不会报错

joe@salry <- 48000
Error in (function (cl, name, valueClass) : ‘salry’ is not a slot in class “employee”

在 S4 类上实现泛型函数

使用 setMethod() 定义泛型函数。

S4 类中 show() 函数类似 S3 类中的 print() 函数

show(joe)
An object of class "employee"
Slot "name":
[1] "Joe"

Slot "salary":
[1] 88000

Slot "union":
[1] TRUE

employee 类定义泛型函数 show

setMethod(
  "show",
  "employee",
  function(object) {
    inorout <- ifelse(object@union, "is", "is not")
    cat(object@name, "has a salary of", object@salary,
        "and", inorout, "in the union", "\n")
  }
)
show(joe)
Joe has a salary of 88000 and is in the union 

S3 类和 S4 类对比

注:作为一名初学者,笔者尚不了解 R 语言常用的类型。 不过,类型安全对于维护大型项目来说至关重要。 S4 类有明确的类型定义,可以在 IDE 中使用代码提示功能。

对象管理

R 语言提供多种工具管理环境中的对象

  • ls()
  • rm()
  • save()
  • 查看对象结构,如 class()mode()
  • exists()

ls() 函数列出所有对象

ls()
 [1] "ct"             "cttab"          "ctu"           
 [4] "hz"             "j"              "joe"           
 [7] "k"              "lm_out"         "print.employee"
[10] "x"              "y"              "z"      

pattern 参数用于筛选

ls(pattern="e")
[1] "joe"            "print.employee"

rm() 函数删除特定对象

rm("j", "joe")

list 参数指定删除对象的名称。 下面代码删除所有对象

rm(list = ls())

save() 函数保存对象集合

save() 保存成文件,load() 从文件中加载

z <- rnorm(100000)
hz <- hist(z)
save(hz, file="hzfile")
ls()
[1] "hz" "z" 
rm(hz)
ls()
[1] "z"
load("hzfile")
ls()
[1] "hz" "z" 
plot(hz)

查看对象内部结构

  • class()mode()
  • names()attributes()
  • unclass()str()
  • edit()
ct <- read.table(
  "../data/ct.dat",
  header=TRUE,
)
ct

生成列联表

cttab <- table(ct)
cttab
          Voted.For.X.Last.Time
Vote.for.X No Yes
  No        2   0
  Not Sure  0   1
  Yes       1   1

使用 unclass() 查看对象结构

ctu <- unclass(cttab)
ctu
          Voted.For.X.Last.Time
Vote.for.X No Yes
  No        2   0
  Not Sure  0   1
  Yes       1   1
class(ctu)
[1] "matrix" "array" 

str() 以更紧凑的方式显示

str(cttab)
 'table' int [1:3, 1:2] 2 0 1 0 1 1
 - attr(*, "dimnames")=List of 2
  ..$ Vote.for.X           : chr [1:3] "No" "Not Sure" "Yes"
  ..$ Voted.For.X.Last.Time: chr [1:2] "No" "Yes"

page()edit() 可以用于查看对象内容,比如函数的具体实现。

names() 函数显示对象有哪些组件

names(hz)
[1] "breaks"   "counts"   "density"  "mids"     "xname"    "equidist"

attributes() 会给出更多信息,包括类名称

attributes(hz)
$names
[1] "breaks"   "counts"   "density"  "mids"     "xname"    "equidist"

$class
[1] "histogram"

exists() 函数

exists() 判断对象是否存在

exists("acc")
[1] FALSE

参考

学习 R 语言系列文章

快速入门

向量

矩阵和数组

列表

数据框

因子和表

编程结构

数学运算与模拟

本文代码请访问如下项目:

https://github.com/perillaroc/the-art-of-r-programming