《R语言入门与实践》摘要

R
Author

metseq

Published

March 11, 2024

我为什么看这本书

看《R for Data Science》的时候,Hadley Wickham推荐了这本书《Hands-On Programming with R》,中文译名是《R语言入门与实践》。这本书关注的是R语言作为语言的基础,基本不涉及统计,我对R语言的了解基本是调用demo代码的时候,自己感觉的,缺少对R语言作为语言的整体了解,所以这本书是我需要的。

第1章 R基础

1.2 对象

对象,就是一个名称而已,在R中存储的数据就是一个R对象。

die <- 1:6
die
[1] 1 2 3 4 5 6

元素方式执行(element-wise execution)

die - 1
[1] 0 1 2 3 4 5
die / 2
[1] 0.5 1.0 1.5 2.0 2.5 3.0
die * die
[1]  1  4  9 16 25 36

矩阵内乘法

die %*% die
     [,1]
[1,]   91

矩阵外乘法

die %o% die
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    2    3    4    5    6
[2,]    2    4    6    8   10   12
[3,]    3    6    9   12   15   18
[4,]    4    8   12   16   20   24
[5,]    5   10   15   20   25   30
[6,]    6   12   18   24   30   36

1.3 函数

函数调用

sample(x = die, size = 1)
[1] 1

查看函数参数

args(sample)
function (x, size, replace = FALSE, prob = NULL) 
NULL

1.4 可放回抽样

sample(die, size = 10, replace = TRUE)
 [1] 1 1 1 1 1 3 6 5 4 6

1.5 编写自定义函数

roll <- function(){
  die <- 1:6
  dice <- sample(die, size = 2, replace = TRUE)
  sum(dice)
}

roll()
[1] 10

1.6 函数参数

roll2 <- function(bones = 1:6){
  dice <- sample(bones, size = 2, replace = TRUE)
  sum(dice)
}

roll2()
[1] 5

第3章 R对象

3.1 原子型向量

原子型向量就是最简单的包含数据的向量。

比如之前的`die`

die <- 1:6
die
[1] 1 2 3 4 5 6
is.vector(die)
[1] TRUE

至包含一个值的原子型向量

five <- 5
five
[1] 5
is.vector(five)
[1] TRUE
Tip

🤔R竟然把一个整数存成了长度为1的向量,理解向量是理解R语言的关键。

R可以识别6种基本类型的原子型向量:

  • 双整型(double)

  • 整型(integer)

  • 字符型(character)

  • 逻辑型(logical)

  • 复数类型(complex)

  • 原始类型(raw)

3.1.1 双整型

die <- c(1, 2, 3, 4, 5, 6)
die
[1] 1 2 3 4 5 6
typeof(die)
[1] "double"

3.1.2 整型

foo <- c(-1L, 2L, 4L)
foo
[1] -1  2  4
typeof(foo)
[1] "integer"

浮点误差

sqrt(2)^2 - 2
[1] 4.440892e-16

3.1.3 字符型

text <- c("Hello", "World")
text
[1] "Hello" "World"
typeof(text)
[1] "character"

3.1.4 逻辑型

3 > 4
[1] FALSE
logic <- c(TRUE, FALSE, TRUE)
logic
[1]  TRUE FALSE  TRUE
typeof(logic)
[1] "logical"

3.1.5 附属类型和原始类型

comp <- c(1 + 1i, 1 + 2i, 1 + 3i)
comp
[1] 1+1i 1+2i 1+3i
typeof(comp)
[1] "complex"
raw(3)
[1] 00 00 00
typeof(raw(3))
[1] "raw"

3.2 属性

属性是附加给原始型向量的额外信息,可以将属性赋予一个原子型向量(或者任意一个R对象)。属性并不会影响这个数据的取值,在显示该对象时也不会出现属性信息。你可以把属性理解为对象的元数据(metadata)。

Important

通常来说,R会选择忽略这些元数据信息,但某些R函数会检查是否附有某些特定的属性值。这些函数会根据该数据对象的特定属性信息决定是否进行某些特定的操作。

die <- 1:6
attributes(die)
NULL
die
[1] 1 2 3 4 5 6
Tip

R用NULL表示空值,意即没有任何信息。这里就是没有属性的意思。

可以自己生成一个NULL对象

bar <- 1:4
bar
[1] 1 2 3 4
bar <- NULL
bar
NULL

原子型向量最常见的三种属性时:名称(name),维度(dim)和类(class)。

3.2.1 名称属性

die <- 1:6

# 查询名称属性
names(die)
NULL
# 赋予名称属性
names(die) <- c("one", "two", "three", "four", "five", "six")
names(die)
[1] "one"   "two"   "three" "four"  "five"  "six"  
attributes(die)
$names
[1] "one"   "two"   "three" "four"  "five"  "six"  
# 删除名称属性
names(die) <- NULL
names(die)
NULL

3.2.2 维度属性

通过赋予原子型向量维度属性,将其转成一个n维数组

die
[1] 1 2 3 4 5 6
dim(die) <- c(2, 3)
die
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6

3.3 矩阵

m <- matrix(die, nrow = 2)
m
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6

3.4 数组

# 和dim效果差不多
ar <- array(1:6, dim = c(2, 3))
ar
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6

3.5 类

更改对象的维度属性不会改变其类型,但是会改变这个对象的class属性。

die <- 1:6
typeof(die)
[1] "integer"
class(die)
[1] "integer"
dim(die) <- c(2, 3)
typeof(die)
[1] "integer"
class(die)
[1] "matrix" "array" 
attributes(die)
$dim
[1] 2 3

3.5.1 日期和时间

R用一个特殊的类表示日期和时间数据。

now <- Sys.time()
now
[1] "2024-03-14 23:02:18 CST"
typeof(now)
[1] "double"
class(now)
[1] "POSIXct" "POSIXt" 
# 值是1970年1月1日零点(UTC)开始所逝去的秒数
unclass(now)
[1] 1710428538

3.5.2 因子

gender <- factor(c("male", "female", "female", "male"))

typeof(gender)
[1] "integer"
attributes(gender)
$levels
[1] "female" "male"  

$class
[1] "factor"
# 用整数存储因子,整数对于相应level的标签
unclass(gender)
[1] 2 1 1 2
attr(,"levels")
[1] "female" "male"  

3.6 强制转换

顺序是:

逻辑型 ——> 数值型 ——> 字符型

也就是如果同时存在逻辑型和数值型,会转换成数值型,以此类推。

3.7 列表

list1 <- list(100:130, "R", list(TRUE, FALSE))
list1
[[1]]
 [1] 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
[20] 119 120 121 122 123 124 125 126 127 128 129 130

[[2]]
[1] "R"

[[3]]
[[3]][[1]]
[1] TRUE

[[3]][[2]]
[1] FALSE

3.8 数据框

df <- data.frame(
  face = c("ace", "two", "six"),
  suit = c("clubs", "clubs", "clubs"),
  value = c(1, 2, 3)
)
df
  face  suit value
1  ace clubs     1
2  two clubs     2
3  six clubs     3
Tip

创建列表或向量时,也可以命名

list(face = "ace", suit = "hearts", value = 1:6)
$face
[1] "ace"

$suit
[1] "hearts"

$value
[1] 1 2 3 4 5 6
c(face = "ace", suit = "hearts", value = "one")
    face     suit    value 
   "ace" "hearts"    "one" 

第4章 R的记号体系

4.1 值的选取

要从一个数据框中提取某个值或某一组值,先写出该数据框的名称,在其后紧跟一堆中括号。中括号有两个参数,以逗号分隔。R用第一个索引参数选择行,第二个列。

`deck[ 行, 列 ]`

有6种创建索引参数的方式:

  • 正整数

  • 负整数

  • 空格

  • 逻辑值

  • 名称

4.1.1 正整数索引

deck <- read.csv('deck.csv')
head(deck)
   face   suit value
1  king spades    13
2 queen spades    12
3  jack spades    11
4   ten spades    10
5  nine spades     9
6 eight spades     8
deck[1, 1]
[1] "king"
deck[1, c(1, 2, 3)]
  face   suit value
1 king spades    13
new <- deck[1, c(1, 2, 3)]
new
  face   suit value
1 king spades    13
new[1, 3] <- '100'
new
  face   suit value
1 king spades   100
deck[1, c(1, 2, 3)]
  face   suit value
1 king spades    13

可以看到,new是一个副本,修改new并不会修改deck。

# 重复值
deck[c(1, 1), c(1, 2, 3)]
    face   suit value
1   king spades    13
1.1 king spades    13
Tip

如果从一个数据框提取两列或以上,返回一个新的数据框。如果只提取一列,返回一个向量,可以添加drop = FALSE参数,仍返回数据框。

deck[1:2, 1]
[1] "king"  "queen"
deck[1:2, 1, drop = FALSE]
   face
1  king
2 queen

4.1.2 负整数索引

R返回不包含负整数索引对应的元素。注意,和python中切片的负整数不一样。

deck[-(2:52), 1:3]
  face   suit value
1 king spades    13
-(1:6)
[1] -1 -2 -3 -4 -5 -6

4.1.3 零索引

没什么用,返回一个空对象

4.1.4 空格索引

空格代表选取该维度的所有元素。

deck[1, ]
  face   suit value
1 king spades    13

4.1.5 逻辑值索引

deck[1, c(TRUE, TRUE, FALSE)]
  face   suit
1 king spades

4.1.6 名称索引

deck[1, c("face", "suit", "value")]
  face   suit value
1 king spades    13
deck[ , "value"]
 [1] 13 12 11 10  9  8  7  6  5  4  3  2  1 13 12 11 10  9  8  7  6  5  4  3  2
[26]  1 13 12 11 10  9  8  7  6  5  4  3  2  1 13 12 11 10  9  8  7  6  5  4  3
[51]  2  1

4.4 美元符号与双中括号

使用$提取一列

deck$value
 [1] 13 12 11 10  9  8  7  6  5  4  3  2  1 13 12 11 10  9  8  7  6  5  4  3  2
[26]  1 13 12 11 10  9  8  7  6  5  4  3  2  1 13 12 11 10  9  8  7  6  5  4  3
[51]  2  1
mean(deck$value)
[1] 7
median(deck$value)
[1] 7

如果列表对象中的元素有名称,也可以用$提取。

lst <- list(number = c(1, 2), logical = TRUE, strings = c("a", "b", "c"))

lst
$number
[1] 1 2

$logical
[1] TRUE

$strings
[1] "a" "b" "c"
lst$number
[1] 1 2

用单中括号和双中括号提取列表元素

lst[1]
$number
[1] 1 2
typeof(lst[1])
[1] "list"
lst[[1]]
[1] 1 2
typeof(lst[[1]])
[1] "double"
Important

使用单中括号提取一个列表对象的自己,R返回一个列表对象。使用双中括号,R返回元素值。

忽然想到的一个提取例子:

lst[1:2]
$number
[1] 1 2

$logical
[1] TRUE
lst[[1]][2]
[1] 2

第5章 对象改值

5.1 就地改值

vec <- c(0, 0, 0, 0, 0, 0)

vec[1]
[1] 0
vec[1] <- 1000

vec
[1] 1000    0    0    0    0    0
vec[c(1, 3, 5)] <- c(1, 1, 1)

vec
[1] 1 0 1 0 1 0

创建一个原先对象中并不存在的新值。R会自动将对象的长度延伸以适应这个新值。

vec[7] <- 7
vec
[1] 1 0 1 0 1 0 7

使用$为数据集添加新变量:

deck2 <- deck

deck2$new <- 1:52

head(deck2)
   face   suit value new
1  king spades    13   1
2 queen spades    12   2
3  jack spades    11   3
4   ten spades    10   4
5  nine spades     9   5
6 eight spades     8   6

将NULL赋予一个变量,就可以删除这个变量了。

deck2$new <- NULL

head(deck2)
   face   suit value
1  king spades    13
2 queen spades    12
3  jack spades    11
4   ten spades    10
5  nine spades     9
6 eight spades     8

5.2 逻辑值取子集

vec <- 1:4
vec[c(FALSE, FALSE, TRUE, FALSE)]
[1] 3

逻辑向量和需要取子集的向量等长。

5.2.1 逻辑测试

R的7种逻辑运算符
运算符 语法 判别
> a > b a是否大于b?
>= a >= b 大于等于
< a < b 小于
<= a <= b 小于等于
== a == b 等于
!= a != b 不等于
%in% a %in% c(a, b, c) c(a, b, c)中是否包含a?

向量化的运算符

1 > 2
[1] FALSE
1 > c(0, 1, 2)
[1]  TRUE FALSE FALSE
c(1, 2, 3) == c(3, 2, 1)
[1] FALSE  TRUE FALSE
Important

%in%是唯一不进行一一对比的运算符。如果左边提供一个向量,%in%会独立测试左边向量中的元素是否出现在右边向量中。

1 %in% c(3, 4, 5)
[1] FALSE
c(1, 2) %in% c(3, 4, 5)
[1] FALSE FALSE
c(1, 2, 3) %in% c(3, 4, 5)
[1] FALSE FALSE  TRUE
c(1, 2, 3, 4) %in% c(3, 4, 5)
[1] FALSE FALSE  TRUE  TRUE

看到这里,发现作者对例子都是精挑细选的,上面的例子,左边向量长度分别从1到4,右边是3,没有变,注意比较输出结果的差异。

5.2.2 布尔运算符

布尔运算符可以将多个逻辑测试的结果整合并输出为一个TRUE或FALSE。R有六种布尔运算符。

R的布尔运算符
运算符 语法 判别
& cond1 & cond2
cond1 | cond2
! ! cond1
any any(cond1, cond2, cond3, …) 所有条件,是否至少一个为真?
all all(cond1, cond2, cond3, …) 所有条件,是否同时为真?
xor xor(cond1, cond2) cond1和cond2是否只有一个为真?
# xor很陌生,看看例子

xor(TRUE, TRUE)
[1] FALSE
xor(TRUE, FALSE)
[1] TRUE
xor(FALSE, FALSE)
[1] FALSE
xor(FALSE, TRUE)
[1] TRUE

向量化的布尔运算符

a <- c(1, 2, 3)
b <- c(1, 2, 3)
c <- c(1, 2, 4)

a == b
[1] TRUE TRUE TRUE
b == c
[1]  TRUE  TRUE FALSE
a == b & b == c
[1]  TRUE  TRUE FALSE

5.3 缺失信息

R用特殊字符NA代表“不可用”(Not Available),可用于存储缺失信息。NA具有传染性。

1 + NA
[1] NA
NA == NA
[1] NA

5.3.1 na.rm

去掉缺失信息

c(NA, 1:50)
 [1] NA  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
[26] 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
[51] 50
mean(c(NA, 1:50))
[1] NA
mean(c(NA, 1:50), na.rm = TRUE)
[1] 25.5

5.3.2 is.na

判断是否是NA

Tip

不能用==来判断NA

NA == NA
[1] NA
c(1,2, 3, NA) == NA
[1] NA NA NA NA
is.na(NA)
[1] TRUE
vec <- c(1, 2, 3, NA)

is.na(vec)
[1] FALSE FALSE FALSE  TRUE

第6章 R的环境系统

这一张作者讲的非常好,用目录树的结构来形象的比喻了R的环境系统。

建议查看原文全文。这里列出几个重要的知识点。

6.2 操作R环境

全局环境(R_GlobalEnv),最底层的环境,可以看到我创建的对象基本都在全局环境下。

ls(globalenv())
 [1] "a"      "ar"     "b"      "bar"    "c"      "comp"   "deck"   "deck2" 
 [9] "df"     "die"    "five"   "foo"    "gender" "list1"  "logic"  "lst"   
[17] "m"      "new"    "now"    "roll"   "roll2"  "text"   "vec"   

空环境,空环境没有父环境,相当于根目录了。

ls(emptyenv())
character(0)

在某个环境中,提取另一环境的变量

head(globalenv()$deck)
   face   suit value
1  king spades    13
2 queen spades    12
3  jack spades    11
4   ten spades    10
5  nine spades     9
6 eight spades     8

在某个环境中,将对象存到另一个环境

assign("new", "Hello Global", envir = globalenv())

globalenv()$new
[1] "Hello Global"

活动环境

# 当前就在R_GlobalEnv下
environment()
<environment: R_GlobalEnv>

6.3 作用域规则

在搜索对象时,R会遵循一系列的规则。这些规则被称作R的作用域规则(scoping rules)。

  1. R首先在当前的活动环境中搜索对象
  2. 在命令行中工作时,活动环境就是全局环境
  3. 当R在某个环境中没有搜索到对象时,R会进入该环境的父环境。如果还是没有,再进入父环境的父环境,以此类推

6.4 函数求值

R在每一次函数求值时,都会创建一个新环境,然后带着运行结果回到调用该函数时的环境。

函数内创建的对象的环境时单独的,所以不会覆盖已经存在的对象。

a <- "I'm in R_GlobalEnv"
a
[1] "I'm in R_GlobalEnv"
show_env <- function(){
  a <- "I'm in show_env."
  print('show_env函数内的环境:')
  print(environment())
  
  print('show_env函数的父环境:')
  print(parent.env(environment()))
  
  print('列出show_env环境下的对象')
  ls(environment())
  a
}

show_env()
[1] "show_env函数内的环境:"
<environment: 0x7f9e339a4600>
[1] "show_env函数的父环境:"
<environment: R_GlobalEnv>
[1] "列出show_env环境下的对象"
[1] "I'm in show_env."
a
[1] "I'm in R_GlobalEnv"

R会将一个函数的运行时环境与第一次创建该函数时所在的环境相连接。该函数的所有运行环境都将其作为父环境。

environment(show_env)
<environment: R_GlobalEnv>
Note

R是如何对待函数求值的?

当你调用一个函数之前,R是在活动环境中工作。我们可以将此活动环境称为调用环境(calling environment)。R会从该环境中调用函数。

调用这个函数时,R会创建一个新的运行时环境。这个环境是调用环境的子环境,也就是说调用环境是这个环境的父环境。R会将该函数的所有参数赋值到运行环境中,然后将运行时环境作为当前的活动环境。

接着,R会运行函数主体中的代码。如果代码创建了任何对象,R会将它们存储在活动环境中,也就是运行时环境。如果R调用了某些对象,遵循作用域规则搜索这些对象:首先搜索运行时环境,如果没有搜索父环境,还是没有搜索父环境的父环境,以此类推。

最后,当R完成函数运行时,它会将活动环境切换为调用环境。如果将函数运行的结果用赋值符<-赋给某个对象,那么这个新对象会存储在调用环境中。

如果对象是数据框,也是同样对待的。

df <- data.frame(
  face = c("ace", "two", "six"),
  suit = c("clubs", "clubs", "clubs"),
  value = c(1, 2, 3)
)
df
  face  suit value
1  ace clubs     1
2  two clubs     2
3  six clubs     3
test_env <- function(x){
  x$face = c("ace", "ace", "ace")
  x
}

test_env(df)
  face  suit value
1  ace clubs     1
2  ace clubs     2
3  ace clubs     3
df
  face  suit value
1  ace clubs     1
2  two clubs     2
3  six clubs     3

第7章 程序

7.1 策略

有三个简单的策略可以简化编程任务:

  1. 将复杂任务分解为一些简单的字任务
  2. 使用实例
  3. 用通俗的语言描述解决方案,然后将其转换成R代码

7.2 if语句

if语法

if (this) {
  that
}
num <- -9
if (num < 0) {
  num <- num * -1
}
num
[1] 9

7.3 else语句

else语法

if (this) {
  Plan A
} else {
  Plan B
}
a <- 3.14
dec <- a - trunc(a)
dec
[1] 0.14
if (dec >= 0.5) {
  a <- trunc(a) + 1
} else {
  a <- trunc(a)
}

a
[1] 3

else if 语法

if (this) {
  Plan A
} else if (that) {
  Plan B
} else {
  Plan C
}

7.4 查找表

有点像python里的字典

ages = c("Min" = 22, "King" = 33, "Jeo" = 34)

ages['Min']
Min 
 22 
unname(ages["Min"])
[1] 22
ages[c("Min", "Min", "Jeo")]
Min Min Jeo 
 22  22  34 

7.5 代码注释

# 我就是一行注释,用井号打头
# 注释的作用是对某些代码进行解释
# 好的注释不是直白的说代码在做什么
# 好的注释总结代码的意图,让人看了很明了
# 好吧,我承认这里注释写的有点多了

# 计算n的阶乘
fac <- function(n) {
  f = 1
  for (i in 1:n){
    f = f * i
  }
  f
}

fac(5)
[1] 120

第8章 S3

8.1 S3系统

S3指的是R自带的类系统。这个系统掌管着R如何处理具有不同类的对象。一些函数会首先查询对象的S3类,再根据其类属性作出相应的响应。

print就是这样一个函数。

num <- 1000000000
print(num)
[1] 1e+09
class(num) <- c("POSIXct", "POSIXt")
print(num)
[1] "2001-09-09 09:46:40 CST"
Note

之前觉得很神奇,原来是这么回事!

Important

R的S3系统有三个组成部分:

  1. 属性(attribute),尤其是class属性
  2. 泛型函数(generic function)
  3. 方法(method)

8.2 属性

在3.2节中,讲到了R对象都具有的属性(名称,维度,类),这些属性包含了关于这个对象的某些额外信息并且被赋予了属性名称,附加在该对象上。属性不会影响对象的实际取值,但是作为该对象的某种类型的元数据,可以被R用于控制和管理这个对象。

使用attributes函数查看属性

attributes(deck)
$names
[1] "face"  "suit"  "value"

$class
[1] "data.frame"

$row.names
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
[26] 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
[51] 51 52

R提供可很多辅助函数,可以帮忙设置和查看一些常见的属性

# 名称属性
names(deck)
[1] "face"  "suit"  "value"
# 维度属性
dim(deck)
[1] 52  3
# 类属性
class(deck)
[1] "data.frame"
# 行名属性
row.names(deck)
 [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10" "11" "12" "13" "14" "15"
[16] "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30"
[31] "31" "32" "33" "34" "35" "36" "37" "38" "39" "40" "41" "42" "43" "44" "45"
[46] "46" "47" "48" "49" "50" "51" "52"
# 或者给对象添加一个新属性
levels(deck) <- c("level 1", "level 2", "level 3")

attributes(deck)
$names
[1] "face"  "suit"  "value"

$class
[1] "data.frame"

$row.names
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
[26] 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
[51] 51 52

$levels
[1] "level 1" "level 2" "level 3"
Important

在对待属性方面,R持放任主义态度。R允许你为某个对象添加你觉得必要的属性(R会忽略大多数属性)。只有在某个函数需要找到某个属性却又找不到时,R才会抱怨。

可以利用attr函数个某个对象添加任何属性。

a <- 1:3

attributes(a)
NULL
attr(a, "symbol") <- c("one", "two", "three")

attr(a, "pinyin") <- c("yi", "er", "san")

a
[1] 1 2 3
attr(,"symbol")
[1] "one"   "two"   "three"
attr(,"pinyin")
[1] "yi"  "er"  "san"
a + 1
[1] 2 3 4
attr(,"symbol")
[1] "one"   "two"   "three"
attr(,"pinyin")
[1] "yi"  "er"  "san"

structure函数可以创建带有一组属性的R对象。该函数第一个参数是对象或者对象的取值,其余参数是属性。

structure(1:3, symbol = c("one", "two", "three"))
[1] 1 2 3
attr(,"symbol")
[1] "one"   "two"   "three"
structure(a, lalala = c("happy", "every", "day"))
[1] 1 2 3
attr(,"symbol")
[1] "one"   "two"   "three"
attr(,"pinyin")
[1] "yi"  "er"  "san"
attr(,"lalala")
[1] "happy" "every" "day"  

8.3 泛型函数

重点中的重点来了

刚才提到print函数是泛型函数。每次在控制台显示输出也是调用print函数

head(deck)
   face   suit value
1  king spades    13
2 queen spades    12
3  jack spades    11
4   ten spades    10
5  nine spades     9
6 eight spades     8
print(head(deck))
   face   suit value
1  king spades    13
2 queen spades    12
3  jack spades    11
4   ten spades    10
5  nine spades     9
6 eight spades     8
print(now)
[1] "2024-03-14 23:02:18 CST"
print(1:10)
 [1]  1  2  3  4  5  6  7  8  9 10

你可能会觉得print会查找某个对象的类属性,再根据类属性的不同,使用一个if语句分配合理的输出。作者说很好,其实print的实现方式更简单。往下看👀

8.4 方法

看看print的代码,它其实调用了一个特别的函数,叫做UseMethod。

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

head函数也是调用了UseMethod。

head
function (x, ...) 
UseMethod("head")
<bytecode: 0x7f9e3035d270>
<environment: namespace:utils>
Important

UseMethod检查你提供给print函数第一个参数的类属性,然后再将你所提供的待输出的对象交给一个新函数处理。比如提供一个POSIXct对象,就交给print.POSIXct函数;提供一个factor对象,就交给print.factor函数。

print.POSIXct和print.factor被称为print函数的方法(method)。这两个函数是普通函数,特别之处是UseMethod会调用它们处理对应的类。

print.POSIXct
function (x, tz = "", usetz = TRUE, max = NULL, ...) 
{
    if (is.null(max)) 
        max <- getOption("max.print", 9999L)
    FORM <- if (missing(tz)) 
        function(z) format(z, usetz = usetz)
    else function(z) format(z, tz = tz, usetz = usetz)
    if (max < length(x)) {
        print(FORM(x[seq_len(max)]), max = max + 1, ...)
        cat(" [ reached 'max' / getOption(\"max.print\") -- omitted", 
            length(x) - max, "entries ]\n")
    }
    else if (length(x)) 
        print(FORM(x), max = max, ...)
    else cat(class(x)[1L], "of length 0\n")
    invisible(x)
}
<bytecode: 0x7f9e345516d0>
<environment: namespace:base>
print.factor
function (x, quote = FALSE, max.levels = NULL, width = getOption("width"), 
    ...) 
{
    ord <- is.ordered(x)
    if (length(x) == 0L) 
        cat(if (ord) 
            "ordered"
        else "factor", "()\n", sep = "")
    else {
        xx <- character(length(x))
        xx[] <- as.character(x)
        keepAttrs <- setdiff(names(attributes(x)), c("levels", 
            "class"))
        attributes(xx)[keepAttrs] <- attributes(x)[keepAttrs]
        print(xx, quote = quote, ...)
    }
    maxl <- if (is.null(max.levels)) 
        TRUE
    else max.levels
    if (maxl) {
        n <- length(lev <- encodeString(levels(x), quote = ifelse(quote, 
            "\"", "")))
        colsep <- if (ord) 
            " < "
        else " "
        T0 <- "Levels: "
        if (is.logical(maxl)) 
            maxl <- {
                width <- width - (nchar(T0, "w") + 3L + 1L + 
                  3L)
                lenl <- cumsum(nchar(lev, "w") + nchar(colsep, 
                  "w"))
                if (n <= 1L || lenl[n] <= width) 
                  n
                else max(1L, which.max(lenl > width) - 1L)
            }
        drop <- n > maxl
        cat(if (drop) 
            paste(format(n), ""), T0, paste(if (drop) 
            c(lev[1L:max(1, maxl - 1)], "...", if (maxl > 1) lev[n])
        else lev, collapse = colsep), "\n", sep = "")
    }
    if (!isTRUE(val <- .valid.factor(x))) 
        warning(val)
    invisible(x)
}
<bytecode: 0x7f9e346aef98>
<environment: namespace:base>

使用methods函数可以查看泛型函数所支持的方法。

# medhods(print)的输出太长了,可以自己试试
methods(head)
[1] head.array*      head.data.frame* head.default*    head.ftable*    
[5] head.function*   head.matrix     
see '?methods' for accessing help and source code

c,+,-和<等,其工作方式也类似于泛型函数,只是它们不会调用UseMethod函数,而会调用.primitive函数。简直就是魔法。

可以自己写一个S3风格的函数,对于特定对象,做特定的处理。

a <- 1:6
class(a) <- "life"

print.life <- function(x) {
  print("I miss you!")
}

attributes(a)
$class
[1] "life"
print(a)
[1] "I miss you!"

8.5 类

可以利用R的S3系统为对象新建一个稳健的类(class)。R会以一致且合理的方式对待同属一类的对象。要想创建一个类,应该执行以下操作:

  1. 给类起一个名称
  2. 给属于该类的每个对象赋class属性
  3. 为属于该类的对象编写常用泛型函数的类方法

查看某个类的方法:

methods(class = "factor")
 [1] [             [[            [[<-          [<-           all.equal    
 [6] as.character  as.data.frame as.Date       as.list       as.logical   
[11] as.POSIXlt    as.vector     c             coerce        droplevels   
[16] format        initialize    is.na<-       length<-      levels<-     
[21] Math          Ops           plot          print         relevel      
[26] relist        rep           show          slotsFromS3   summary      
[31] Summary       xtfrm        
see '?methods' for accessing help and source code

8.7 S4和R5

S4和R5是R另外两个可以用来创建类属性行为的系统。使用难度更大,参考 Hadley Wickham 的《Advanced R Programming》。

第9章 循环

9.2 expand.grid

expand.grid可以方便快捷地写出n个向量元素的所有组合。

a <- c("one", "two", "three")
b <- c("apple", "bananna")

expand.grid(a, b)
   Var1    Var2
1   one   apple
2   two   apple
3 three   apple
4   one bananna
5   two bananna
6 three bananna

9.3 for循环

语法

for (value in that) {
  this
}
for (word in c("Today", "is", "thursday")) {
  print(word)
}
[1] "Today"
[1] "is"
[1] "thursday"
for (i in 1:6) {
  print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6

9.4 while循环

while (condition) {
  code
}
n <- 3
while (n > 0) {
  print(n)
  n <- n - 1
}
[1] 3
[1] 2
[1] 1

9.5 repeat循环

repeat会一直重复运行某段代码,直到你中止循环(按Esc),或遇到了break命令。

n <- 3
repeat {
  print(n)
  n <- n - 1
  if (n < 0){
    break
  }
}
[1] 3
[1] 2
[1] 1
[1] 0

9.6 replicate函数

replicate函数可以重复运行某段代码多少次。

replicate(10, 'best')
 [1] "best" "best" "best" "best" "best" "best" "best" "best" "best" "best"

第10章 代码提速

10.1 向量化代码

快速的R代码经常用到的三大法宝:逻辑测试,取子集和按元素方式执行。

看看下面这个例子,一个是用for循环的非向量化代码,一个是向量化代码。

# for循环
abs_loop <- function(vec) {
  for (i in 1:length(vec)) {
    if (vec[i] < 0) {
      vec[i] <- -vec[i]
    }
  }
  vec
}

# 向量化代码
abs_set <- function(vec) {
  negs <- vec < 0
  vec[negs] <- -1 * vec[negs]
  vec
}

# 生成一个很长的测试向量
long <- rep(c(-1, 1), 5000000)

# 统计代码运行的实际
system.time(abs_loop(long))
   user  system elapsed 
  0.469   0.022   0.494 
system.time(abs_set(long))
   user  system elapsed 
  0.177   0.038   0.217 

向量化代码比for循环快5倍,书中是快30倍,应该是新版的R把for循环优化了。

10.2 如何编写向量化代码

  1. 对于程序中的有序步骤,使用向量化的函数来完成
  2. 对于同类情况,使用逻辑值取子集的方式来处理

10.3 如何在R中编写快速的for循环

做两件事可以改善for循环的效率:

  1. 能放在for循环外的代码,不要放在for循环里
  2. 确保用来存储循环输出结果的对象必须具备足够的容量
system.time({
  # 这句代码是关键,给output预先分配了足够的容量
  output <- rep(NA, 1000000)
  for (i in 1:1000000) {
    output[i] <- i + 1
  }
})
   user  system elapsed 
  0.043   0.002   0.045 
system.time({
  output <-NA
  for (i in 1:1000000) {
    output[i] <- i + 1
  }
})
   user  system elapsed 
  0.230   0.061   0.291 

为什么第二段慢呢?在第二段代码中,循环每前进一步,都要为output在内存中寻找位置,以存放新的变大的output。第一段代码没有这个问题,直接放。

到这里这本书就结束了,如果你觉得不错,请在下面点赞👍或评论支持吧😄