+ - 0:00:00
Notes for current slide
Notes for next slide

量化金融与金融编程

L06 向量与函数


曾永艺

厦门大学管理学院


2023-11-10

1 / 53
2 / 53

To understand computations in R, two slogans are helpful:

  • Everything that exists is an object.

  • Everything that happens is a function call.

3 / 53

To understand computations in R, two slogans are helpful:

  • Everything that exists is an object.

  • Everything that happens is a function call.

1. 向量

  • 原子向量
  • 列表
  • 向量属性
  • 增强向量
3 / 53

To understand computations in R, two slogans are helpful:

  • Everything that exists is an object.

  • Everything that happens is a function call.

1. 向量

  • 原子向量
  • 列表
  • 向量属性
  • 增强向量

2. 函数

  • 使用函数的优点
  • 编写函数的套路
  • 函数的参数
  • 函数的返回值
  • 函数的环境
  • {{ tidyverse }} *
  • 作为一等公民的函数

* i.e., functions that embrace tidyverse.

3 / 53

1. 向量

(vectors)

4 / 53
5 / 53

1.1 原子向量 >> 逻辑向量(logical vector

逻辑向量的元素只有三种可能取值:TRUE | FALSE | NA

6 / 53

1.1 原子向量 >> 逻辑向量(logical vector

逻辑向量的元素只有三种可能取值:TRUE | FALSE | NA

逻辑向量通常由比较运算(?Comparison)生成

1:10 %% 3 == 0
#> [1] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE
6 / 53

1.1 原子向量 >> 逻辑向量(logical vector

逻辑向量的元素只有三种可能取值:TRUE | FALSE | NA

逻辑向量通常由比较运算(?Comparison)生成

1:10 %% 3 == 0
#> [1] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE

当然也可以用 c() 直接构造逻辑向量

c(TRUE, FALSE, NA, T, F) # c(): Combine Values into a Vector or List
#> [1] TRUE FALSE NA TRUE FALSE
6 / 53

1.1 原子向量 >> 逻辑向量(logical vector

逻辑向量的元素只有三种可能取值:TRUE | FALSE | NA

逻辑向量通常由比较运算(?Comparison)生成

1:10 %% 3 == 0
#> [1] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE

当然也可以用 c() 直接构造逻辑向量

c(TRUE, FALSE, NA, T, F) # c(): Combine Values into a Vector or List
#> [1] TRUE FALSE NA TRUE FALSE

强烈建议不要使用 TRUEFALSE 的缩写形式 TF

T <- FALSE; F <- 2; c(TRUE, FALSE, NA, T, F); rm(list = c("T", "F"))
#> [1] 1 0 NA 0 2
6 / 53

1.1 原子向量 >> 数值向量(numeric vector

数值向量包括整数向量(integer)和实数向量(double)

7 / 53

1.1 原子向量 >> 数值向量(numeric vector

数值向量包括整数向量(integer)和实数向量(double)

默认为实数,除非在整数后加上 L

typeof(1);typeof(1L);typeof(1.5L)
#> [1] "double"
#> [1] "integer"
#> [1] "double"

R 用双精度来存储实数向量的数值,但在很多情况下只是近似值,因此不要用 == 直接比较实数向量的取值,而用 dplyr::near()

7 / 53

1.1 原子向量 >> 数值向量(numeric vector

数值向量包括整数向量(integer)和实数向量(double)

默认为实数,除非在整数后加上 L

typeof(1);typeof(1L);typeof(1.5L)
#> [1] "double"
#> [1] "integer"
#> [1] "double"

R 用双精度来存储实数向量的数值,但在很多情况下只是近似值,因此不要用 == 直接比较实数向量的取值,而用 dplyr::near()

整数向量只有一个特殊值 NA,而实数向量则有四个特殊值 NANaNInf-Inf;用 is.*() 而不要直接用 == 检测这些特殊值

c(-1, 0, 1, NA) / 0 # 4个特殊值
#> [1] -Inf NaN Inf NA
0 Inf NA NaN
is.finite()
is.infinite()
is.na()
is.nan()
7 / 53

1.1 原子向量 >> 字符向量(charater vector

字符向量的每个元素由字符串构成,可用来表示任意数据

c("FALSE", "123L", "123.45",
"a string", "1 + 2i",
"3a 29",
"2018-11-13 10:00 CST")
#> [1] "FALSE"
#> [2] "123L"
#> [3] "123.45"
#> [4] "a string"
#> [5] "1 + 2i"
#> [6] "3a 29"
#> [7] "2018-11-13 10:00 CST"
8 / 53

1.1 原子向量 >> 字符向量(charater vector

字符向量的每个元素由字符串构成,可用来表示任意数据

c("FALSE", "123L", "123.45",
"a string", "1 + 2i",
"3a 29",
"2018-11-13 10:00 CST")
#> [1] "FALSE"
#> [2] "123L"
#> [3] "123.45"
#> [4] "a string"
#> [5] "1 + 2i"
#> [6] "3a 29"
#> [7] "2018-11-13 10:00 CST"

R 使用全局字符串池,这意味着每个字符串在内存中只存一次

x <- "This is a long string."
object.size(x)
# lobstr::obj_size(x)
#> 136 bytes
y <- rep(x, 1000)
object.size(y)
#> 8128 bytes
# 8 * 1000 + 128 B
8 / 53

1.1 原子向量 >> 类型转换

显性转换:as.logical() | as.integer() | as.double() | as.character()

9 / 53

1.1 原子向量 >> 类型转换

显性转换:as.logical() | as.integer() | as.double() | as.character()

as.integer(c("1", "2")) + c(10, 11) ## [1] 11 13
9 / 53

1.1 原子向量 >> 类型转换

显性转换:as.logical() | as.integer() | as.double() | as.character()

as.integer(c("1", "2")) + c(10, 11) ## [1] 11 13

隐性转换:当向量用于需要特定类型输入的函数时,R 会自动将向量转换为特定类型(但非总是如此)

x <- sample(1:20, 100,
replace = TRUE)
# what proportion > 10?
mean(x > 10)
#> [1] 0.52
x + "3" # 但并非总是如此
#> Error in x + "3": non-numeric argument to binary operator
9 / 53

1.1 原子向量 >> 类型转换

显性转换:as.logical() | as.integer() | as.double() | as.character()

as.integer(c("1", "2")) + c(10, 11) ## [1] 11 13

隐性转换:当向量用于需要特定类型输入的函数时,R 会自动将向量转换为特定类型(但非总是如此)

x <- sample(1:20, 100,
replace = TRUE)
# what proportion > 10?
mean(x > 10)
#> [1] 0.52
x + "3" # 但并非总是如此
#> Error in x + "3": non-numeric argument to binary operator

隐性转换:当用 c() 联结不同类型的向量时,R 会套用最复杂的类型

c(FALSE, 1L, 1.5, "a", 1 + 1i, raw(2))
#> [1] "FALSE" "1" "1.5" "a"
#> [5] "1+1i" "00" "00"
1 == "1"; -1 < FALSE; "one" < 2
#> [1] TRUE
#> [1] TRUE
#> [1] FALSE
9 / 53

1.1 原子向量 >> 类型检测


尽管 R 的 base 包提供很多类型检测函数(如 is.logical() ),但建议使用 purrr 包中用法更加一致的相关函数 *


lgl int dbl chr list
is_logical()
is_integer()
is_double()
is_numeric()
is_character()
is_atomic()
is_list()
is_vector()

*:事实上这些函数现在都 import 自 rlang

10 / 53

1.1 原子向量 >> 循环规则(recycling

R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度

11 / 53

1.1 原子向量 >> 循环规则(recycling

R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度

runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE TRUE TRUE
11 / 53

1.1 原子向量 >> 循环规则(recycling

R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度

runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE TRUE TRUE

当较长向量不是较短向量的整数倍时,R 照样会进行转换,但会有个 Warning message

1:5 + c(1, NA)
#> Warning in 1:5 + c(1, NA): longer
#> object length is not a multiple of
#> shorter object length
#> [1] 2 NA 4 NA 6
11 / 53

1.1 原子向量 >> 循环规则(recycling

R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度

runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE TRUE TRUE

当较长向量不是较短向量的整数倍时,R 照样会进行转换,但会有个 Warning message

1:5 + c(1, NA)
#> Warning in 1:5 + c(1, NA): longer
#> object length is not a multiple of
#> shorter object length
#> [1] 2 NA 4 NA 6

当向量的长短不一时(标量除外),要求更严格的 tidyverse 则会抛出 Error,停止运行

tibble(x = 1:4, y = 1:2)
#> Error in `tibble()`:
#> ! Tibble columns must have compatible sizes.
#> • Size 4: Existing data.
#> • Size 2: Column `y`.
#> ℹ Only values of size one are recycled.
11 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法一:用整数向量选取元素

12 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法一:用整数向量选取元素

x <- c("one", "two", "three", "four", "five")
x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"
12 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法一:用整数向量选取元素

x <- c("one", "two", "three", "four", "five")
x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"
x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"
12 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法一:用整数向量选取元素

x <- c("one", "two", "three", "four", "five")
x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"
x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"
x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscripts
12 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法一:用整数向量选取元素

x <- c("one", "two", "three", "four", "five")
x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"
x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"
x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscripts
x[0] # 返回空值(但非NULL)
#> character(0)
12 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法一:用整数向量选取元素

x <- c("one", "two", "three", "four", "five")
x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"
x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"
x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscripts
x[0] # 返回空值(但非NULL)
#> character(0)
x[c(1.2, 2.3, 3.4, 4.5, 5.6, 6.7)] # 想想,这会返回什么?
12 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法二:用逻辑向量选取元素,保留逻辑向量取值为 TRUE 位置的元素

x <- c(10, 3, NA, 5)
x[!is.na(x)] # 提取非缺失值
#> [1] 10 3 5
# 注意:逻辑向量取值为NA的位置也会被
# 保留下来
x[x %% 2 == 0] # 提取偶数或缺失值
#> [1] 10 NA
x[x %% 2 == 1] # 提取奇数或缺失值
#> [1] 3 NA 5
13 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法二:用逻辑向量选取元素,保留逻辑向量取值为 TRUE 位置的元素

x <- c(10, 3, NA, 5)
x[!is.na(x)] # 提取非缺失值
#> [1] 10 3 5
# 注意:逻辑向量取值为NA的位置也会被
# 保留下来
x[x %% 2 == 0] # 提取偶数或缺失值
#> [1] 10 NA
x[x %% 2 == 1] # 提取奇数或缺失值
#> [1] 3 NA 5

方法三:用字符向量选取命名向量的元素

# 命名向量:创建时直接命名
abc <- c(a = 1, b = 2, c = 3)
# 命名向量:事后再命名
abc <- 1:3
abc <- purrr::set_names(
# 也可用base包的setNames()
x = abc,
nm = c("a", "b", "c")
)
# 提取相应命名的元素
abc[c("a", "c", "b", "a", "d")]
#> a c b a <NA>
#> 1 3 2 1 NA
13 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法四:空向量表示选取全部元素

abc
#> a b c
#> 1 2 3
abc[] # 空向量
#> a b c
#> 1 2 3
14 / 53

1.1 原子向量 >> 用 [ 函数选取元素(subsetting

方法四:空向量表示选取全部元素

abc
#> a b c
#> 1 2 3
abc[] # 空向量
#> a b c
#> 1 2 3

最后,[ 有个特殊的变型 [[,后者只返回单个元素,且会丢弃元素名称。当你想要明确表达你只需要一个元素时,推荐使用 [[。

abc["a"] # abc[1]
#> a
#> 1
abc[["a"]] # abc[[1]]
#> [1] 1
14 / 53

1.2 列表 >> 基础

和原子向量不同,列表可同时包含多种类型(type)的元素(甚至嵌套下一级的列表),这使得列表很适合用来存储混合或树状结构的数据

15 / 53

1.2 列表 >> 基础

和原子向量不同,列表可同时包含多种类型(type)的元素(甚至嵌套下一级的列表),这使得列表很适合用来存储混合或树状结构的数据

a <- list(a = 1:3, b = "a string",
c = pi, d = list(-1, -5))
a # print(a)
#> $a
#> [1] 1 2 3
#>
#> $b
#> [1] "a string"
#>
#> $c
#> [1] 3.14
#>
#> $d
#> $d[[1]]
#> [1] -1
#>
#> $d[[2]]
#> [1] -5
15 / 53

1.2 列表 >> 基础

和原子向量不同,列表可同时包含多种类型(type)的元素(甚至嵌套下一级的列表),这使得列表很适合用来存储混合或树状结构的数据

a <- list(a = 1:3, b = "a string",
c = pi, d = list(-1, -5))
a # print(a)
#> $a
#> [1] 1 2 3
#>
#> $b
#> [1] "a string"
#>
#> $c
#> [1] 3.14
#>
#> $d
#> $d[[1]]
#> [1] -1
#>
#> $d[[2]]
#> [1] -5

除了直接 print() 列表对象的内容之外,更推荐使用 str() 函数 / RStudio Environment 面板来查看 简单 / 复杂 列表对象的结构

str(a)
#> List of 4
#> $ a: int [1:3] 1 2 3
#> $ b: chr "a string"
#> $ c: num 3.14
#> $ d:List of 2
#> ..$ : num -1
#> ..$ : num -5
15 / 53

1.2 列表 >> 提取列表元素(subsetting

方法一:用 [ 提取子列表,总是返回列表

# 可用 数值|字符|逻辑向量 提取,如a[1:2]
# 或a[c(TRUE, TRUE, FALSE, FALSE)]
a[c("a", "b")] %>% str()
#> List of 2
#> $ a: int [1:3] 1 2 3
#> $ b: chr "a string"
# 尽管只有一个元素,但仍是列表
a[3] %>% str()
#> List of 1
#> $ c: num 3.14
a[4] %>% str()
#> List of 1
#> $ d:List of 2
#> ..$ : num -1
#> ..$ : num -5
16 / 53

1.2 列表 >> 提取列表元素(subsetting

方法一:用 [ 提取子列表,总是返回列表

# 可用 数值|字符|逻辑向量 提取,如a[1:2]
# 或a[c(TRUE, TRUE, FALSE, FALSE)]
a[c("a", "b")] %>% str()
#> List of 2
#> $ a: int [1:3] 1 2 3
#> $ b: chr "a string"
# 尽管只有一个元素,但仍是列表
a[3] %>% str()
#> List of 1
#> $ c: num 3.14
a[4] %>% str()
#> List of 1
#> $ d:List of 2
#> ..$ : num -1
#> ..$ : num -5

方法二:用 [[ 提取单一列表元素,并从列表中移除一个层级

a[[3]] %>% str()
#> num 3.14
a[[4]] %>% str()
#> List of 2
#> $ : num -1
#> $ : num -5

方法三:$ 大致等同 [[ ,能更方便地提取一个命名列表的元素

a$a # a[["a"]]
#> [1] 1 2 3
16 / 53

1.2 列表 >> 提取列表元素(subsetting

17 / 53

1.2 列表 >> 提取列表元素(subsetting

另一种图示说明

  • 图中圆角方框代表列表,而普通方框代表原子向量
  • 子元素被包围在父元素之内
17 / 53

🙋‍♂️ Your Turn!

03:00
  1. 以嵌套集合的方式表示列表 list(1, 2, list(3, 4), list(5, c(6, 7)))

  2. 如何从上述列表中提取出 list(3, 4)

  1. myList[[3]]

18 / 53

1.3 向量属性(attributes

我们可以通过向量属性来给向量增加任意的额外元数据(metadata),而向量属性可被视作附加在向量上的命名列表

19 / 53

1.3 向量属性(attributes

我们可以通过向量属性来给向量增加任意的额外元数据(metadata),而向量属性可被视作附加在向量上的命名列表

通过 attr() 来设定或获取向量的属性;通过 attributes() 来获取全部或批量设置向量的属性信息

19 / 53

1.3 向量属性(attributes

我们可以通过向量属性来给向量增加任意的额外元数据(metadata),而向量属性可被视作附加在向量上的命名列表

通过 attr() 来设定或获取向量的属性;通过 attributes() 来获取全部或批量设置向量的属性信息

x <- 1:10
attr(x, "source")
#> NULL
attributes(x) <- list(
source = c("Wind", "GTA"),
date = Sys.Date()
)
attr(x, "cleaned_by") <- "Y.Z"
19 / 53

1.3 向量属性(attributes

我们可以通过向量属性来给向量增加任意的额外元数据(metadata),而向量属性可被视作附加在向量上的命名列表

通过 attr() 来设定或获取向量的属性;通过 attributes() 来获取全部或批量设置向量的属性信息

x <- 1:10
attr(x, "source")
#> NULL
attributes(x) <- list(
source = c("Wind", "GTA"),
date = Sys.Date()
)
attr(x, "cleaned_by") <- "Y.Z"
attributes(x)
#> $source
#> [1] "Wind" "GTA"
#>
#> $date
#> [1] "2023-11-09"
#>
#> $cleaned_by
#> [1] "Y.Z"
19 / 53

1.3 向量属性(attributes

R 中的向量除了我们之前提到过的“类型 typeof()”和“长度 length()”两个性质之外,还有三个基础属性:名称、维度和类

20 / 53

1.3 向量属性(attributes

R 中的向量除了我们之前提到过的“类型 typeof()”和“长度 length()”两个性质之外,还有三个基础属性:名称、维度和类

①名称属性(names):给向量元素命名

# 命名向量
x = c(1, 2, 3)
names(x) <- c("a", "b", "c")
x
#> a b c
#> 1 2 3
names(x) # attr(x, "names")
#> [1] "a" "b" "c"
20 / 53

1.3 向量属性(attributes

R 中的向量除了我们之前提到过的“类型 typeof()”和“长度 length()”两个性质之外,还有三个基础属性:名称、维度和类

①名称属性(names):给向量元素命名

# 命名向量
x = c(1, 2, 3)
names(x) <- c("a", "b", "c")
x
#> a b c
#> 1 2 3
names(x) # attr(x, "names")
#> [1] "a" "b" "c"

②维度属性(dimensions):将向量“改造”为矩阵或数组

x <- 1:10
dim(x) <- c(2, 5) # ?matrix
x
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 1 3 5 7 9
#> [2,] 2 4 6 8 10
class(x); dim(x) # attr(x, "dim")
#> [1] "matrix" "array"
#> [1] 2 5

------------------|----------------------------|----------------- names() | rownames(), colnames() | dimnames() length() | nrow(), ncol() | dim() c() | rbind(), cbind() | abind::abind() — | t() | aperm() is.null(dim(x)) | is.matrix() | is.array()

20 / 53

1.3 向量属性(attributes

③类属性(class):控制泛型函数(generic functions的行为方式,这是 R 语言用来实现“S3 面向对象系统”的方法

21 / 53

1.3 向量属性(attributes

③类属性(class):控制泛型函数(generic functions的行为方式,这是 R 语言用来实现“S3 面向对象系统”的方法

# as.Date()是个典型的泛型函数
# 列印as.Date()函数的源代码
as.Date
#> function (x, ...)
#> UseMethod("as.Date")
#> <bytecode: 0x000001ed6dcbef18>
#> <environment: namespace:base>
21 / 53

1.3 向量属性(attributes

③类属性(class):控制泛型函数(generic functions的行为方式,这是 R 语言用来实现“S3 面向对象系统”的方法

# as.Date()是个典型的泛型函数
# 列印as.Date()函数的源代码
as.Date
#> function (x, ...)
#> UseMethod("as.Date")
#> <bytecode: 0x000001ed6dcbef18>
#> <environment: namespace:base>

结果显示as.Date() 函数调用 UseMethod(),这就意味着 as.Date() 是个泛型函数,它将根据第一个参数 x 的类来确定该调用哪个特定方法(method)。

# 查看as.Date()泛型函数调用的全部方法
methods("as.Date")
#> [1] as.Date.character as.Date.default
#> [3] as.Date.factor as.Date.numeric
#> [5] as.Date.POSIXct as.Date.POSIXlt
#> [7] as.Date.vctrs_sclr* as.Date.vctrs_vctr*
#> see '?methods' for accessing help and source code
21 / 53

1.3 向量属性(attributes

# 查看as.Date()函数指定类(如numeric)所实现的具体方法
getS3method("as.Date", "numeric")
#> function (x, origin, ...)
#> if (missing(origin)) .Date(x) else as.Date(origin, ...) + x
#> <bytecode: 0x000001ed6c7ac3e8>
#> <environment: namespace:base>
# 由于as.Date.numeric()是导出函数,也可用as.Date.numeric直接查看其代码
22 / 53

1.3 向量属性(attributes

# 查看as.Date()函数指定类(如numeric)所实现的具体方法
getS3method("as.Date", "numeric")
#> function (x, origin, ...)
#> if (missing(origin)) .Date(x) else as.Date(origin, ...) + x
#> <bytecode: 0x000001ed6c7ac3e8>
#> <environment: namespace:base>
# 由于as.Date.numeric()是导出函数,也可用as.Date.numeric直接查看其代码


记住,泛型函数R中非常重要的概念,有很多常见的函数都是泛型函数,具体如 print()summary()mean()、[、[[ 和 $ 等。

22 / 53

1.4 增强向量(augmented vectors

增强向量①:因子向量(factors

23 / 53

1.4 增强向量(augmented vectors

增强向量①:因子向量(factors

(x <- factor(c("A", "B", "A"),
levels = c("A", "B", "C")))
#> [1] A B A
#> Levels: A B C
typeof(x) # 获取向量x的类型
#> [1] "integer"
attributes(x) # 获取向量x的属性
#> $levels
#> [1] "A" "B" "C"
#>
#> $class
#> [1] "factor"
23 / 53

1.4 增强向量(augmented vectors

增强向量①:因子向量(factors

(x <- factor(c("A", "B", "A"),
levels = c("A", "B", "C")))
#> [1] A B A
#> Levels: A B C
typeof(x) # 获取向量x的类型
#> [1] "integer"
attributes(x) # 获取向量x的属性
#> $levels
#> [1] "A" "B" "C"
#>
#> $class
#> [1] "factor"
# 获取x向量levels属性的便捷函数
levels(x)
#> [1] "A" "B" "C"
# 获取x向量class属性的便捷函数
class(x)
#> [1] "factor"
unclass(x) # 移除向量x的类属性
#> [1] 1 2 1
#> attr(,"levels")
#> [1] "A" "B" "C"
23 / 53

1.4 增强向量(augmented vectors

增强向量②:日期向量和日期-时间向量(dates and date-times

24 / 53

1.4 增强向量(augmented vectors

增强向量②:日期向量和日期-时间向量(dates and date-times

(x <- as.Date("1971-01-01"))
#> [1] "1971-01-01"
typeof(x)
#> [1] "double"
attributes(x)
#> $class
#> [1] "Date"
unclass(x) # as.integer(x)
#> [1] 365
24 / 53

1.4 增强向量(augmented vectors

增强向量②:日期向量和日期-时间向量(dates and date-times

(x <- as.Date("1971-01-01"))
#> [1] "1971-01-01"
typeof(x)
#> [1] "double"
attributes(x)
#> $class
#> [1] "Date"
unclass(x) # as.integer(x)
#> [1] 365
(x <- lubridate::ymd_hm(
"1970-01-01 01:00"))
#> [1] "1970-01-01 01:00:00 UTC"
typeof(x); as.double(x) # unclass(x)
#> [1] "double"
#> [1] 3600
attributes(x)
#> $class
#> [1] "POSIXct" "POSIXt"
#>
#> $tzone
#> [1] "UTC"
24 / 53

1.4 增强向量(augmented vectors

增强向量②:日期向量和日期-时间向量(dates and date-times

25 / 53

1.4 增强向量(augmented vectors

增强向量②:日期向量和日期-时间向量(dates and date-times

(y <- as.POSIXlt(x)) # built on top of named lists
#> [1] "1970-01-01 01:00:00 UTC"
25 / 53

1.4 增强向量(augmented vectors

增强向量②:日期向量和日期-时间向量(dates and date-times

(y <- as.POSIXlt(x)) # built on top of named lists
#> [1] "1970-01-01 01:00:00 UTC"
typeof(y)
#> [1] "list"
attributes(y)
#> $names
#> [1] "sec" "min" "hour" "mday"
#> [5] "mon" "year" "wday" "yday"
#> [9] "isdst" "zone" "gmtoff"
#>
#> $class
#> [1] "POSIXlt" "POSIXt"
#>
#> $tzone
#> [1] "UTC"
#>
#> $balanced
#> [1] TRUE
25 / 53

1.4 增强向量(augmented vectors

增强向量②:日期向量和日期-时间向量(dates and date-times

(y <- as.POSIXlt(x)) # built on top of named lists
#> [1] "1970-01-01 01:00:00 UTC"
typeof(y)
#> [1] "list"
attributes(y)
#> $names
#> [1] "sec" "min" "hour" "mday"
#> [5] "mon" "year" "wday" "yday"
#> [9] "isdst" "zone" "gmtoff"
#>
#> $class
#> [1] "POSIXlt" "POSIXt"
#>
#> $tzone
#> [1] "UTC"
#>
#> $balanced
#> [1] TRUE
unclass(y) %>% str
#> List of 11
#> $ sec : num 0
#> $ min : int 0
#> $ hour : int 1
#> $ mday : int 1
#> $ mon : int 0
#> $ year : int 70
#> $ wday : int 4
#> $ yday : int 0
#> $ isdst : int 0
#> $ zone : chr "UTC"
#> $ gmtoff: int 0
#> - attr(*, "tzone")= chr "UTC"
#> - attr(*, "balanced")= logi TRUE
y$year # 提取列表y的"year"元素
#> [1] 70
25 / 53

增强向量③:dataframetibble

27 / 53

增强向量③:dataframetibble

df <- data.frame(
x = LETTERS[1:3],
y = 0:2, `:)` = runif(3))
typeof(df)
#> [1] "list"
attributes(df)
#> $names
#> [1] "x" "y" "X.."
#>
#> $class
#> [1] "data.frame"
#>
#> $row.names
#> [1] 1 2 3
27 / 53

增强向量③:dataframetibble

df <- data.frame(
x = LETTERS[1:3],
y = 0:2, `:)` = runif(3))
typeof(df)
#> [1] "list"
attributes(df)
#> $names
#> [1] "x" "y" "X.."
#>
#> $class
#> [1] "data.frame"
#>
#> $row.names
#> [1] 1 2 3
tb <- tibble(
x = LETTERS[1:3],
y = 0:2, `:)` = runif(3))
typeof(tb)
#> [1] "list"
attributes(tb)
#> $class
#> [1] "tbl_df" "tbl" "data.frame"
#>
#> $row.names
#> [1] 1 2 3
#>
#> $names
#> [1] "x" "y" ":)"
27 / 53

增强向量③:dataframetibble *

*:dataframetibble 的底层都是个 list,每一列是 list 的一个元素,但要求每列都必须是等长的向量

28 / 53

增强向量③:dataframetibble *

*:dataframetibble 的底层都是个 list,每一列是 list 的一个元素,但要求每列都必须是等长的向量

# 看看df底层的list
unclass(df) %>% str()
#> List of 3
#> $ x : chr [1:3] "A" "B" "C"
#> $ y : int [1:3] 0 1 2
#> $ X..: num [1:3] 0.5232 0.5718 0.0529
#> - attr(*, "row.names")= int [1:3] 1 2 3
df # 列印df的内容
#> x y X..
#> 1 A 0 0.5232
#> 2 B 1 0.5718
#> 3 C 2 0.0529
28 / 53

增强向量③:dataframetibble *

*:dataframetibble 的底层都是个 list,每一列是 list 的一个元素,但要求每列都必须是等长的向量

# 看看df底层的list
unclass(df) %>% str()
#> List of 3
#> $ x : chr [1:3] "A" "B" "C"
#> $ y : int [1:3] 0 1 2
#> $ X..: num [1:3] 0.5232 0.5718 0.0529
#> - attr(*, "row.names")= int [1:3] 1 2 3
df # 列印df的内容
#> x y X..
#> 1 A 0 0.5232
#> 2 B 1 0.5718
#> 3 C 2 0.0529
# 看看tb底层的list
unclass(tb) %>% str()
#> List of 3
#> $ x : chr [1:3] "A" "B" "C"
#> $ y : int [1:3] 0 1 2
#> $ :): num [1:3] 0.525 0.239 0.675
#> - attr(*, "row.names")= int [1:3] 1 2 3
tb # 列印tb的内容
#> # A tibble: 3 × 3
#> x y `:)`
#> <chr> <int> <dbl>
#> 1 A 0 0.525
#> 2 B 1 0.239
#> 3 C 2 0.675
28 / 53

增强向量③:dataframetibble

29 / 53

增强向量③:dataframetibble

# 提取tb的第1列和第3列
tb[c(1, 3)]
#> # A tibble: 3 × 2
#> x `:)`
#> <chr> <dbl>
#> 1 A 0.525
#> 2 B 0.239
#> 3 C 0.675
29 / 53

增强向量③:dataframetibble

# 提取tb的第1列和第3列
tb[c(1, 3)]
#> # A tibble: 3 × 2
#> x `:)`
#> <chr> <dbl>
#> 1 A 0.525
#> 2 B 0.239
#> 3 C 0.675
# 提取第2列,仍结果为tibble
# 也可用tb[2]
tb["y"]
#> # A tibble: 3 × 1
#> y
#> <int>
#> 1 0
#> 2 1
#> 3 2
# 提取第2列,但结果为向量
# 也可用tb[[2]]或tb[["y"]]
tb$y
#> [1] 0 1 2
29 / 53

增强向量③:dataframetibble

# tb(df)具有类似矩阵(matrix)类的性质
# tb的维度“属性”
dim(tb)
#> [1] 3 3
30 / 53

增强向量③:dataframetibble

# tb(df)具有类似矩阵(matrix)类的性质
# tb的维度“属性”
dim(tb)
#> [1] 3 3
# 用提取子矩阵的方法提取子集
tb[c(1, 3), c("y", ":)")]
#> # A tibble: 2 × 2
#> y `:)`
#> <int> <dbl>
#> 1 0 0.525
#> 2 2 0.675
30 / 53

增强向量③:dataframetibble

# tb(df)具有类似矩阵(matrix)类的性质
# tb的维度“属性”
dim(tb)
#> [1] 3 3
# 用提取子矩阵的方法提取子集
tb[c(1, 3), c("y", ":)")]
#> # A tibble: 2 × 2
#> y `:)`
#> <int> <dbl>
#> 1 0 0.525
#> 2 2 0.675
# 用dplyr工具可能更为直观
tb %>% slice(c(1, 3)) %>%
select(y, ":)")
#> # A tibble: 2 × 2
#> y `:)`
#> <int> <dbl>
#> 1 0 0.525
#> 2 2 0.675
30 / 53

2. 函数

(functions)

31 / 53

2.1 使用函数的优点

32 / 53

2.1 使用函数的优点

和复制-粘贴-修改比,使用函数的优点

  • 清晰的函数名让代码更容易理解

  • 当要求发生变化时,你只需要在一个地方进行改动

  • 消除复制-粘贴-修改过程中可能出现的无心错误

  • 有时你必须写函数(如某些函数的参数就是函数)

来围观一个栗子    👉

32 / 53

2.1 使用函数的优点

和复制-粘贴-修改比,使用函数的优点

  • 清晰的函数名让代码更容易理解

  • 当要求发生变化时,你只需要在一个地方进行改动

  • 消除复制-粘贴-修改过程中可能出现的无心错误

  • 有时你必须写函数(如某些函数的参数就是函数)

来围观一个栗子    👉

# 甲:以下生成正态分布的随机变量并组装成df ……
set.seed(1234)
df <- tibble::tibble(
a = rnorm(10),
b = rnorm(10),
c = rnorm(10)
)
# 乙:这我明白!
# 甲:很好!那么请问,以下又是神马操作?
df$a <- (df$a - min(df$a, na.rm = TRUE)) /
(max(df$a, na.rm = TRUE) -
min(df$a, na.rm = TRUE))
df$b <- (df$b - min(df$b, na.rm = TRUE)) /
(max(df$b, na.rm = TRUE) -
min(df$a, na.rm = TRUE))
df$c <- (df$c - min(df$c, na.rm = TRUE)) /
(max(df$c, na.rm = TRUE) -
min(df$c, na.rm = TRUE))
# 乙:~~~
32 / 53

2.2 编写函数的套路

Step#1 分析重复性的代码 *1

(df$a - min(df$a, na.rm = TRUE)) /
(max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
33 / 53

2.2 编写函数的套路

Step#1 分析重复性的代码 *1

(df$a - min(df$a, na.rm = TRUE)) /
(max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))


Step#2 将函数的输入提取为临时变量 *2

x <- df$a
(x - min(x, na.rm = TRUE)) /
(max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
#> [1] 0.332 0.765 1.000 0.000 0.809 0.831 0.516 0.524 0.519 0.424

*1:可选步骤。
*2:多个输入也可类似处理。

33 / 53

2.2 编写函数的套路

Step#3   包装成函数 [ -> 改进] -> 测试

rescale01 <- function(x) {
(x - min(x, na.rm = TRUE)) /
(max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
}
rescale01(df$a) # 测试看看结果是否正确
#> [1] 0.332 0.765 1.000 0.000 0.809 0.831 0.516 0.524 0.519 0.424
34 / 53

2.2 编写函数的套路

Step#3   包装成函数 [ -> 改进] -> 测试

rescale01 <- function(x) {
(x - min(x, na.rm = TRUE)) /
(max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
}
rescale01(df$a) # 测试看看结果是否正确
#> [1] 0.332 0.765 1.000 0.000 0.809 0.831 0.516 0.524 0.519 0.424
# 改进之:使用range(),一举得到min和max
rescale01 <- function(x) {
rng <- range(x, na.rm = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
}
rescale01(df$a) # 测试看看结果是否正确
#> [1] 0.332 0.765 1.000 0.000 0.809 0.831 0.516 0.524 0.519 0.424
34 / 53

2.2 编写函数的套路

应用新编写的函数 rescale01()

df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df
#> # A tibble: 10 × 3
#> a b c
#> <dbl> <dbl> <dbl>
#> 1 0.332 0.153 0.782
#> 2 0.765 0 0.473
#> 3 1 0.0651 0.498
#> 4 0 0.311 0.943
#> 5 0.809 0.573 0.373
#> 6 0.831 0.260 0
#> # ℹ 4 more rows
35 / 53

2.2 编写函数的套路

应用新编写的函数 rescale01()

df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df
#> # A tibble: 10 × 3
#> a b c
#> <dbl> <dbl> <dbl>
#> 1 0.332 0.153 0.782
#> 2 0.765 0 0.473
#> 3 1 0.0651 0.498
#> 4 0 0.311 0.943
#> 5 0.809 0.573 0.373
#> 6 0.831 0.260 0
#> # ℹ 4 more rows

😒 但 ……
  左边的示例代码还是
  有点 WET 1
  不够 DRY 2


解决方案
  用 for 循环或函数式编程,
  这是下一讲才会讲到的内容。

1: Write Everything Twice
2: Don't Repeat Yourself

35 / 53

2.2 编写函数的套路

R 中函数的构成要素


rescale01 <- # 函数名:rescale01
function(x) # 函数参数: <- formals()
{ # 函数体:{ <- body()
rng <- range(x, na.rm = TRUE) # ...
(x - rng[1]) / (rng[2] - rng[1]) # ...
} # } + 函数环境 <- enviornment()
36 / 53

2.2 编写函数的套路

函数名

  • 明晰易懂、建议为动词

  • 推荐snake_case命名法

  • 保持一致,如统一前缀

# Long, but clear
impute_missing()
collapse_years()
# common prefix for a family
stringr::str_*()
# Never do this!
col_mins <- function(x, y) {}
rowMaxes <- function(y, x) {}
37 / 53

2.2 编写函数的套路

函数名

  • 明晰易懂、建议为动词

  • 推荐snake_case命名法

  • 保持一致,如统一前缀

# Long, but clear
impute_missing()
collapse_years()
# common prefix for a family
stringr::str_*()
# Never do this!
col_mins <- function(x, y) {}
rowMaxes <- function(y, x) {}

函数体

  • 代码缩进,形成层级结构

  • 使用空格

  • 合理使用注释

    • 说明函数目的
    • 代码分块(Ctrl+Shift+R)
  • 使用中间变量


  • 保持一致风格   👉 styler
37 / 53

2.3 函数的参数

函数的参数大致可划分为两类:

  1. 提供函数操作的(数据)对象
  2. 控制函数如何操作对象的具体细节
?t.test # args(t.test)或formals(t.test)在此不好用
# t.test(x, y = NULL,
# alternative = c("two.sided", "less", "greater"),
# mu = 0, paired = FALSE, var.equal = FALSE,
# conf.level = 0.95, ...)
38 / 53

2.3 函数的参数

函数的参数大致可划分为两类:

  1. 提供函数操作的(数据)对象
  2. 控制函数如何操作对象的具体细节
?t.test # args(t.test)或formals(t.test)在此不好用
# t.test(x, y = NULL,
# alternative = c("two.sided", "less", "greater"),
# mu = 0, paired = FALSE, var.equal = FALSE,
# conf.level = 0.95, ...)

通常把对象参数放在最前面,其余参数则按重要性排序,并设定默认值

38 / 53

2.3 函数的参数

函数的参数大致可划分为两类:

  1. 提供函数操作的(数据)对象
  2. 控制函数如何操作对象的具体细节
?t.test # args(t.test)或formals(t.test)在此不好用
# t.test(x, y = NULL,
# alternative = c("two.sided", "less", "greater"),
# mu = 0, paired = FALSE, var.equal = FALSE,
# conf.level = 0.95, ...)

通常把对象参数放在最前面,其余参数则按重要性排序,并设定默认值

参数名可采用约定俗成的名字,并与通常用法保持一致 👉

参数名 惯常含义 参数名 惯常含义
x, y, z 数据向量 i, j 行列序号
df 数据框 n 向量长度或数据框行数
w 权重向量 na.rm 是否移除缺失值
38 / 53

2.3 函数的参数

特殊参数 ...

39 / 53

2.3 函数的参数

特殊参数 ...

# 很多R函数接受任意数量的输入,此时可用...来捕捉未被匹配的参数
sum(1, 2, 3, 4, 5, NA, 7, 8, 9, 10, na.rm = TRUE)
#> [1] 49
39 / 53

2.3 函数的参数

特殊参数 ...

# 很多R函数接受任意数量的输入,此时可用...来捕捉未被匹配的参数
sum(1, 2, 3, 4, 5, NA, 7, 8, 9, 10, na.rm = TRUE)
#> [1] 49
# 也可用...将不想直接处理的参数传递至函数体内调用的底层函数,如
rule <- function(..., pad = "-") {
title <- paste0(...)
width <- getOption("width") - nchar(title) - 6
cat("## ", title, " ", stringr::str_dup(pad, width), "\n",
sep = "")
}
rule("Important output", pad = "=")
## Important output ================================================
39 / 53

2.3 函数的参数

在函数体中对重要参数的取值进行检验

wt_mean <- function(x, w, na.rm = FALSE) {
if (length(x) != length(w)) { # conditional execution
stop("`x` and `w` must be the same length", call. = FALSE)
}
stopifnot(is.logical(na.rm), length(na.rm) == 1)
if (na.rm) {
miss <- is.na(x) | is.na(w)
x <- x[!miss]
w <- w[!miss]
}
sum(w * x) / sum(w)
}
40 / 53

2.3 函数的参数

在函数体中对重要参数的取值进行检验

wt_mean <- function(x, w, na.rm = FALSE) {
if (length(x) != length(w)) { # conditional execution
stop("`x` and `w` must be the same length", call. = FALSE)
}
stopifnot(is.logical(na.rm), length(na.rm) == 1)
if (na.rm) {
miss <- is.na(x) | is.na(w)
x <- x[!miss]
w <- w[!miss]
}
sum(w * x) / sum(w)
}
wt_mean(1:6, 5:1, na.rm = "foo")
#> Error: `x` and `w` must be the same length
40 / 53

2.3 函数的参数

在函数体中对重要参数的取值进行检验

wt_mean <- function(x, w, na.rm = FALSE) {
if (length(x) != length(w)) { # conditional execution
stop("`x` and `w` must be the same length", call. = FALSE)
}
stopifnot(is.logical(na.rm), length(na.rm) == 1)
if (na.rm) {
miss <- is.na(x) | is.na(w)
x <- x[!miss]
w <- w[!miss]
}
sum(w * x) / sum(w)
}
wt_mean(1:6, 5:1, na.rm = "foo")
#> Error: `x` and `w` must be the same length
wt_mean(1:6, 6:1, na.rm = "foo")
#> Error in wt_mean(1:6, 6:1, na.rm = "foo"): is.logical(na.rm) is not TRUE
40 / 53

2.3 函数的参数

调用函数时将 实际参数 映射到 形式参数 三种方法的优先级依次为:

        名字完全匹配 >> 名字前缀部分匹配 >> 位置匹配

41 / 53

2.3 函数的参数

调用函数时将 实际参数 映射到 形式参数 三种方法的优先级依次为:

        名字完全匹配 >> 名字前缀部分匹配 >> 位置匹配

调用函数时通常可略去对象参数名,而其余参数则建议使用全名

41 / 53

2.3 函数的参数

调用函数时将 实际参数 映射到 形式参数 三种方法的优先级依次为:

        名字完全匹配 >> 名字前缀部分匹配 >> 位置匹配

调用函数时通常可略去对象参数名,而其余参数则建议使用全名

# Good
mean(1:10, na.rm = TRUE)
ggplot(mpg, aes(x = displ, y = hwy)) + geom_point()
41 / 53

2.3 函数的参数

调用函数时将 实际参数 映射到 形式参数 三种方法的优先级依次为:

        名字完全匹配 >> 名字前缀部分匹配 >> 位置匹配

调用函数时通常可略去对象参数名,而其余参数则建议使用全名

# Good
mean(1:10, na.rm = TRUE)
ggplot(mpg, aes(x = displ, y = hwy)) + geom_point()
# Bad
mean(1:10, , TRUE) # 位置匹配
mean(n = TRUE, x = 1:10) # 简写“细节”参数名
mean(, TRUE, x = 1:10) # 参数名匹配 + 位置匹配
41 / 53

2.4 函数的返回值

通常函数返回其最后一个语句求值的结果

42 / 53

2.4 函数的返回值

通常函数返回其最后一个语句求值的结果

当然你也可以用 return() 来提前返回结果,从而让代码更易读

42 / 53

2.4 函数的返回值

通常函数返回其最后一个语句求值的结果

当然你也可以用 return() 来提前返回结果,从而让代码更易读

f <- function() {
if (condition) {
# Do
# something
# that
# takes
# many
# lines
# to
# express
} else {
# return something short
}
}
42 / 53

2.4 函数的返回值

通常函数返回其最后一个语句求值的结果

当然你也可以用 return() 来提前返回结果,从而让代码更易读

f <- function() {
if (condition) {
# Do
# something
# that
# takes
# many
# lines
# to
# express
} else {
# return something short
}
}
f <- function() {
if (!condition) {
return(something_short)
}
# Do
# something
# that
# takes
# many
# lines
# to
# express
}
42 / 53

2.4 函数的返回值

编写支持管道操作 %>% 的函数

  • transformation 类型的函数:直接对输入的(数据)对象进行转化操作,生成新的(数据)对象。此类函数天然支持管道操作。
  • side-effect 类型的函数:就输入的(数据)对象完成特定任务,如赋值 <-、打印结果、作图、存入文档等。此类函数最好能用 invisible() 不可见地返回输入对象,从而支持管道操作。
43 / 53

2.4 函数的返回值

编写支持管道操作 %>% 的函数

  • transformation 类型的函数:直接对输入的(数据)对象进行转化操作,生成新的(数据)对象。此类函数天然支持管道操作。
  • side-effect 类型的函数:就输入的(数据)对象完成特定任务,如赋值 <-、打印结果、作图、存入文档等。此类函数最好能用 invisible() 不可见地返回输入对象,从而支持管道操作。
show_missings <- function(df) {
n <- sum(is.na(df))
cat("Missing values: ", n, "\n",
sep = "")
invisible(df)
}
x <- show_missings(mtcars)
#> Missing values: 0
class(x)
#> [1] "data.frame"
43 / 53

2.4 函数的返回值

编写支持管道操作 %>% 的函数

  • transformation 类型的函数:直接对输入的(数据)对象进行转化操作,生成新的(数据)对象。此类函数天然支持管道操作。
  • side-effect 类型的函数:就输入的(数据)对象完成特定任务,如赋值 <-、打印结果、作图、存入文档等。此类函数最好能用 invisible() 不可见地返回输入对象,从而支持管道操作。
show_missings <- function(df) {
n <- sum(is.na(df))
cat("Missing values: ", n, "\n",
sep = "")
invisible(df)
}
x <- show_missings(mtcars)
#> Missing values: 0
class(x)
#> [1] "data.frame"
library(dplyr)
mtcars %>%
show_missings() %>%
mutate(
mpg = ifelse(mpg < 20, NA, mpg)
) %>%
show_missings()
#> Missing values: 0
#> Missing values: 18
43 / 53

2.5 函数的环境

f <- function(x) {
x + y
}
44 / 53

2.5 函数的环境

f <- function(x) {
x + y
}

前面定义的 f 看起来像个奇怪的函数:只有一个参数 x,希望返回 x + y 的值,但却没有对 y 进行定义和赋值 😲

44 / 53

2.5 函数的环境

f <- function(x) {
x + y
}

前面定义的 f 看起来像个奇怪的函数:只有一个参数 x,希望返回 x + y 的值,但却没有对 y 进行定义和赋值 😲

但在 R 中这是个完全合法的函数,R 会根据所谓的“词法作用域”规则(lexical scoping)来查找 y 的取值,查找的结果受到函数环境的影响。在本例中,由于在函数 f 中并未定义 y,R 就会依次到创建函数 f 的环境(即封闭环境)及其父环境中查找 y 的值。

environment(f)
#> <environment: R_GlobalEnv>
44 / 53

2.5 函数的环境

f <- function(x) {
x + y
}

前面定义的 f 看起来像个奇怪的函数:只有一个参数 x,希望返回 x + y 的值,但却没有对 y 进行定义和赋值 😲

但在 R 中这是个完全合法的函数,R 会根据所谓的“词法作用域”规则(lexical scoping)来查找 y 的取值,查找的结果受到函数环境的影响。在本例中,由于在函数 f 中并未定义 y,R 就会依次到创建函数 f 的环境(即封闭环境)及其父环境中查找 y 的值。

environment(f)
#> <environment: R_GlobalEnv>
x <- 100; y <- 100; f(10)
#> [1] 110
x <- 100; y <- 1000; f(10)
#> [1] 1010
44 / 53

2.6 {{ tidyverse }}

library(tidyverse)
# 定义一个计算分组均值的函数
grouped_mean <- function(df, group_var, mean_var) {
df |>
group_by(group_var) |>
summarize(mean(mean_var))
}
# 调用函数,可惜报错啦 😲
diamonds |>
grouped_mean(cut, carat)
#> Error in `group_by()`:
#> ! Must group by variables found in `.data`.
#> ✖ Column `group_var` is not found.
46 / 53

2.6 {{ tidyverse }}

library(tidyverse)
# 定义一个计算分组均值的函数
grouped_mean <- function(df, group_var, mean_var) {
df |>
group_by(group_var) |>
summarize(mean(mean_var))
}
# 调用函数,可惜报错啦 😲
diamonds |>
grouped_mean(cut, carat)
#> Error in `group_by()`:
#> ! Must group by variables found in `.data`.
#> ✖ Column `group_var` is not found.


问题症结:为了方便交互式调用,tidyverse 中的很多函数都采用所谓的 tidy evaluation —— 让你可以直接引用数据框中的变量名。

46 / 53

2.6 {{ tidyverse }}

解决之道:我们需要某种方式告诉这些函数 不要 直接将形式参数名视为数据框中的变量名,而是将其替换为传递给它的实际参数 —— {{ }} 粉墨登场!

47 / 53

2.6 {{ tidyverse }}

解决之道:我们需要某种方式告诉这些函数 不要 直接将形式参数名视为数据框中的变量名,而是将其替换为传递给它的实际参数 —— {{ }} 粉墨登场!

library(tidyverse)
# 定义一个计算分组均值的函数
grouped_mean <- function(df,
group_var,
mean_var) {
df |>
group_by({{ group_var }}) |>
summarize(mean({{ mean_var }}))
}
# 调用函数,这次就没问题啦 😄
diamonds |>
grouped_mean(cut, log(carat))
#> # A tibble: 5 × 2
#> cut `mean(log(carat))`
#> <ord> <dbl>
#> 1 Fair -0.0629
#> 2 Good -0.307
#> 3 Very Good -0.379
#> 4 Premium -0.288
#> 5 Ideal -0.518
47 / 53

2.6 {{ tidyverse }}

解决之道:我们需要某种方式告诉这些函数 不要 直接将形式参数名视为数据框中的变量名,而是将其替换为传递给它的实际参数 —— {{ }} 粉墨登场!

library(tidyverse)
# 定义一个计算分组均值的函数
grouped_mean <- function(df,
group_var,
mean_var) {
df |>
group_by({{ group_var }}) |>
summarize(mean({{ mean_var }}))
}
# 调用函数,这次就没问题啦 😄
diamonds |>
grouped_mean(cut, log(carat))
#> # A tibble: 5 × 2
#> cut `mean(log(carat))`
#> <ord> <dbl>
#> 1 Fair -0.0629
#> 2 Good -0.307
#> 3 Very Good -0.379
#> 4 Premium -0.288
#> 5 Ideal -0.518

什么时候需要 {{ }}

  • data-masking,如:

    ?mutate
    #> Arguments
    #> .data A data frame ...
    #> ... <data-masking> ...
  • tidy-select,如:

    ?select
    #> Arguments
    #> .data A data frame ...
    #> ... <tidy-select> ...
47 / 53

2.6 {{ tidyverse }}

pick() / across() 多个变量

# 定义计算分组均值的函数
grouped_means <- function(df,
g_vars,
m_vars) {
df |>
group_by(pick({{ g_vars }})) |>
summarize(
across({{ m_vars }}, mean))
}
# 调用函数
diamonds |>
grouped_means(c(cut, color),
c(carat, price))
#> # A tibble: 35 × 4
#> # Groups: cut [5]
#> cut color carat price
#> <ord> <ord> <dbl> <dbl>
#> 1 Fair D 0.920 4291.
#> 2 Fair E 0.857 3682.
#> 3 Fair F 0.905 3827.
#> # ℹ 32 more rows
48 / 53

2.6 {{ tidyverse }}

pick() / across() 多个变量

# 定义计算分组均值的函数
grouped_means <- function(df,
g_vars,
m_vars) {
df |>
group_by(pick({{ g_vars }})) |>
summarize(
across({{ m_vars }}, mean))
}
# 调用函数
diamonds |>
grouped_means(c(cut, color),
c(carat, price))
#> # A tibble: 35 × 4
#> # Groups: cut [5]
#> cut color carat price
#> <ord> <ord> <dbl> <dbl>
#> 1 Fair D 0.920 4291.
#> 2 Fair E 0.857 3682.
#> 3 Fair F 0.905 3827.
#> # ℹ 32 more rows

直接传递参数 ...,而无需 {{ }}

# 定义计算分组均值的函数
grouped_mean <- function(.df,
...,
.mean_var) {
.df |>
group_by(...) |>
summarize(mean({{ .mean_var }}))
}
# 调用函数
diamonds |>
grouped_mean(cut, color,
.mean_var = carat)
#> # A tibble: 35 × 3
#> # Groups: cut [5]
#> cut color `mean(carat)`
#> <ord> <ord> <dbl>
#> 1 Fair D 0.920
#> 2 Fair E 0.857
#> 3 Fair F 0.905
#> # ℹ 32 more rows
48 / 53

2.6 {{ tidyverse }}

形式参数 / 环境变量为字符向量 => 使用代词 .data

# 定义计算分组均值的函数,但参数取值为字符向量
grouped_mean <- function(df,
group_var,
mean_var) {
df |>
group_by(.data[[group_var]]) |>
summarize(mean(.data[[mean_var]]))
}
# 用字符向量调用函数
diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2
#> cut `mean(.data[["carat"]])`
#> <ord> <dbl>
#> 1 Fair 1.05
#> 2 Good 0.849
#> 3 Very Good 0.806
#> # ℹ 2 more rows
49 / 53

2.6 {{ tidyverse }}

形式参数 / 环境变量为字符向量 => 使用代词 .data

# 定义计算分组均值的函数,但参数取值为字符向量
grouped_mean <- function(df,
group_var,
mean_var) {
df |>
group_by(.data[[group_var]]) |>
summarize(mean(.data[[mean_var]]))
}
# 用字符向量调用函数
diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2
#> cut `mean(.data[["carat"]])`
#> <ord> <dbl>
#> 1 Fair 1.05
#> 2 Good 0.849
#> 3 Very Good 0.806
#> # ℹ 2 more rows
# 示例数据集
xy <- tibble(
x = sample(1:2, 100, replace = TRUE),
y = sample(LETTERS[1:2], 100, TRUE))
# 字符环境变量
for (var in names(xy)) {
xy %>% count(.data[[var]]) %>% print()
}
#> # A tibble: 2 × 2
#> x n
#> <int> <int>
#> 1 1 47
#> 2 2 53
#> # A tibble: 2 × 2
#> y n
#> <chr> <int>
#> 1 A 55
#> 2 B 45
49 / 53

2.6 {{ tidyverse }}

形式参数 / 环境变量为字符向量 => 使用代词 .data

# 定义计算分组均值的函数,但参数取值为字符向量
grouped_mean <- function(df,
group_var,
mean_var) {
df |>
group_by(.data[[group_var]]) |>
summarize(mean(.data[[mean_var]]))
}
# 用字符向量调用函数
diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2
#> cut `mean(.data[["carat"]])`
#> <ord> <dbl>
#> 1 Fair 1.05
#> 2 Good 0.849
#> 3 Very Good 0.806
#> # ℹ 2 more rows
# 示例数据集
xy <- tibble(
x = sample(1:2, 100, replace = TRUE),
y = sample(LETTERS[1:2], 100, TRUE))
# 字符环境变量
for (var in names(xy)) {
xy %>% count(.data[[var]]) %>% print()
}
#> # A tibble: 2 × 2
#> x n
#> <int> <int>
#> 1 1 47
#> 2 2 53
#> # A tibble: 2 × 2
#> y n
#> <chr> <int>
#> 1 A 55
#> 2 B 45

👉 vignette("programming", package = "dplyr")

49 / 53

2.7 作为一等公民的函数

R 的函数还是所谓的 first-class 函数,适用于向量的所有操作也都适用于函数

  1. 可以将函数赋值给变量(当然也可以不 -> 匿名函数

  2. 将函数存储在列表中(函数列表

  3. 在函数内创建函数(函数工厂闭包

  4. 将函数作为参数传递给其它函数(泛函

  5. 甚至把函数作为一个函数的结果返回(函数工厂函数运算符

50 / 53

课后作业

51 / 53


复习 📖 R for Data Science, 2e 一书的以下章节:

52 / 53


复习 📖 R for Data Science, 2e 一书的以下章节:

52 / 53










本网页版讲义的制作由 R 包 {{xaringan}} 赋能!
53 / 53
2 / 53
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
oTile View: Overview of Slides
Esc Back to slideshow