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

量化金融与金融编程

L06 向量与函数


曾永艺

厦门大学管理学院


2023-11-10

1 / 40

To understand computations in R, two slogans are helpful:

  • Everything that exists is an object.

  • Everything that happens is a function call.

2 / 40

To understand computations in R, two slogans are helpful:

  • Everything that exists is an object.

  • Everything that happens is a function call.

1. 向量

  • 原子向量
  • 列表
  • 向量属性
  • 增强向量
2 / 40

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.

2 / 40

1. 向量

(vectors)

3 / 40
4 / 40

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

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

5 / 40

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

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

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

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

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
5 / 40

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
5 / 40

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

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

6 / 40

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

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

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

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

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

6 / 40

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()
6 / 40

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"
7 / 40

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
7 / 40

1.1 原子向量 >> 类型转换

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

8 / 40

1.1 原子向量 >> 类型转换

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

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

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.47
x + "3" # 但并非总是如此
#> Error in x + "3": non-numeric argument to binary operator
8 / 40

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.47
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
8 / 40

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

9 / 40

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

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

10 / 40

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

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

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

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

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

runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE FALSE 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
10 / 40

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

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

runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE FALSE 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.
10 / 40

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

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

11 / 40

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"
11 / 40

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"
11 / 40

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
11 / 40

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)
11 / 40

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)] # 想想,这会返回什么?
11 / 40

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
12 / 40

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
12 / 40

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

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

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

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
13 / 40

1.2 列表 >> 基础

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

14 / 40

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
14 / 40

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
14 / 40

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
15 / 40

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
15 / 40

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

16 / 40

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

另一种图示说明

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

1.3 向量属性(attributes

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

17 / 40

1.3 向量属性(attributes

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

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

17 / 40

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"
17 / 40

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-06"
#>
#> $cleaned_by
#> [1] "Y.Z"
17 / 40

1.3 向量属性(attributes

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

18 / 40

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"
18 / 40

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()

18 / 40

1.3 向量属性(attributes

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

19 / 40

1.3 向量属性(attributes

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

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

1.3 向量属性(attributes

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

# as.Date()是个典型的泛型函数
# 列印as.Date()函数的源代码
as.Date
#> function (x, ...)
#> UseMethod("as.Date")
#> <bytecode: 0x00000280f20921c0>
#> <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
19 / 40

1.3 向量属性(attributes

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

1.3 向量属性(attributes

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


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

20 / 40

1.4 增强向量(augmented vectors

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

21 / 40

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"
21 / 40

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"
21 / 40

1.4 增强向量(augmented vectors

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

22 / 40

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
22 / 40

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"
22 / 40

1.4 增强向量(augmented vectors

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

23 / 40

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"
23 / 40

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
23 / 40

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
23 / 40

1.4 增强向量(augmented vectors

增强向量③:dataframetibble

24 / 40

1.4 增强向量(augmented vectors

增强向量③: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
24 / 40

1.4 增强向量(augmented vectors

增强向量③: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" ":)"
24 / 40

1.4 增强向量(augmented vectors

增强向量③:dataframetibble *

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

25 / 40

1.4 增强向量(augmented vectors

增强向量③: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.409 0.456 0.905
#> - attr(*, "row.names")= int [1:3] 1 2 3
df # 列印df的内容
#> x y X..
#> 1 A 0 0.409
#> 2 B 1 0.456
#> 3 C 2 0.905
25 / 40

1.4 增强向量(augmented vectors

增强向量③: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.409 0.456 0.905
#> - attr(*, "row.names")= int [1:3] 1 2 3
df # 列印df的内容
#> x y X..
#> 1 A 0 0.409
#> 2 B 1 0.456
#> 3 C 2 0.905
# 看看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.267 0.315 0.35
#> - attr(*, "row.names")= int [1:3] 1 2 3
tb # 列印tb的内容
#> # A tibble: 3 × 3
#> x y `:)`
#> <chr> <int> <dbl>
#> 1 A 0 0.267
#> 2 B 1 0.315
#> 3 C 2 0.350
25 / 40

1.4 增强向量(augmented vectors

增强向量③:dataframetibble

26 / 40

1.4 增强向量(augmented vectors

增强向量③:dataframetibble

# 提取tb的第1列和第3列
tb[c(1, 3)]
#> # A tibble: 3 × 2
#> x `:)`
#> <chr> <dbl>
#> 1 A 0.267
#> 2 B 0.315
#> 3 C 0.350
26 / 40

1.4 增强向量(augmented vectors

增强向量③:dataframetibble

# 提取tb的第1列和第3列
tb[c(1, 3)]
#> # A tibble: 3 × 2
#> x `:)`
#> <chr> <dbl>
#> 1 A 0.267
#> 2 B 0.315
#> 3 C 0.350
# 提取第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
26 / 40

1.4 增强向量(augmented vectors

增强向量③:dataframetibble

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

1.4 增强向量(augmented vectors

增强向量③: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.267
#> 2 2 0.350
27 / 40

1.4 增强向量(augmented vectors

增强向量③: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.267
#> 2 2 0.350
# 用dplyr工具可能更为直观
tb %>% slice(c(1, 3)) %>%
select(y, ":)")
#> # A tibble: 2 × 2
#> y `:)`
#> <int> <dbl>
#> 1 0 0.267
#> 2 2 0.350
27 / 40

2. 函数

(functions)

28 / 40

2.1 使用函数的优点

29 / 40

2.1 使用函数的优点

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

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

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

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

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

来围观一个栗子    👉

29 / 40

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))
# 乙:~~~
29 / 40

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))
30 / 40

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:多个输入也可类似处理。

30 / 40

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
31 / 40

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
31 / 40

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
32 / 40

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

32 / 40

2.2 编写函数的套路

R 中函数的构成要素


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

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) {}
34 / 40

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
34 / 40

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, ...)
35 / 40

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, ...)

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

35 / 40

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 是否移除缺失值
35 / 40

2.3 函数的参数

特殊参数 ...

36 / 40

2.3 函数的参数

特殊参数 ...

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

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 ================================================
36 / 40

2.3 函数的参数

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

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

37 / 40

2.3 函数的参数

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

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

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

37 / 40

2.3 函数的参数

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

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

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

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

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) # 参数名匹配 + 位置匹配
37 / 40

2.4 函数的返回值

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

38 / 40

2.4 函数的返回值

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

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

38 / 40

2.4 函数的返回值

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

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

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

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
}
38 / 40

2.4 函数的返回值

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

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

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"
39 / 40

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
39 / 40










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

To understand computations in R, two slogans are helpful:

  • Everything that exists is an object.

  • Everything that happens is a function call.

2 / 40
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