R 语言编程— 基于tidyverse读书笔记(20220908)

1.6 控制结构

编程中的控制结构,是指分支结构和循环结构。

1.6.1 分支结构

正常程序结构与一步一步解决问题是一致的,即顺序结构,过程中可能需要对不同情形选择走不同的支路,即分支结构,是用条件语句做判断以实现分支。


分支结构示意图

R 语言中的条件语句的一般格式为:

1. 一个分支
if(条件) {
执行体
}
2. 两个分支
if(条件) {
执行体1
} else {
执行体2
}
3. 多个分支
if(条件1) {
执行体1
} else if(条件2) {
执行体2
} else {
执行体n
}

特别注意:分支的本意就是,不同分支之间不存在交叉(重叠) 。

另一种多分支的写法是用switch():

x = "b"
v = switch(x, "a"="apple", "b"="banana", "c"="cherry")
v
## [1] "banana"

例1.5 实现将百分制分数转化为五级制分数

if(score >= 90) {
res = " 优"
} else if(score >= 80) {
res = " 良"
} else if(score >= 70) {
res = " 中"
} else if(score >= 60) {
res = " 及格"
} else {
res = " 不及格"
}

‘‘条件” 是用逻辑表达式表示,必须是返回一个逻辑值TRUE 或FALSE;
多个逻辑表达式,可以通过逻辑运算符组合以表示复杂条件;
多个逻辑值的逻辑向量,可以借助函数any() 和all() 得到一个逻辑值;
函数ifelse() 可简化代码,仍以计算|x| 为例:
ifelse(x < 0, -x, x)

1.6.2 循环结构

编程中减少代码重复的两个工具,一是循环,一是函数
循环,用来处理对多个同类输入做相同事情(即迭代) ,如对向量的每个元素做相同操作,对数据框不同列做相同操作、对不同数据集做相同操作等。

循环结构示意图

R 语言有三种方式实现循环:
for 循环、while 循环、repeat 循环
apply 函数族
purrr 泛函式编程

关于跳出循环:
用关键字next 跳出本次循环,进入下次循环
用关键词break 跳出循环

1. for 循环

(1) 基本for 循环

library(tidyverse)
df = as_tibble(iris[,1:4])
用‘‘复制-粘贴” 法,计算前4 列的均值:
mean(df[[1]])
## [1] 5.843333
mean(df[[2]])
## [1] 3.057333
mean(df[[3]])
## [1] 3.758
mean(df[[4]])
## [1] 1.199333
为了避免‘‘粘贴-复制多于两次’’,改用for 循环实现:
output = vector("double", 4) # 1. 输出
    for (i in 1:4) { # 2. 迭代器
    output[i] = mean(df[[i]]) # 3. 循环体
}
output
## [1] 5.843333 3.057333 3.758000 1.199333

for 循环有三个组件:
(i) 输出:output = vector("double", 4)
在循环开始之前,最好为输出分配足够的存储空间,这样效率更高:若每循环一次,就用c() 合并一次,效率会很低下。
通常是用vector() 函数创建一个给定长度的空向量,它有两个参数:向量类型(logical, integer,double, character 等) 、向量长度。
(ii) 迭代器:i in 1:4
确定怎么循环:每次for 循环将对i 赋一个1:4 中的值,可将i 理解为代词it.
有时候会用1:length(df), 但更安全的做法是用seq_along(df) ,它能保证即使不小心遇到长度为0 的向量时,仍能正确工作。
(iii) 循环体:output[i] = mean(df[[i]])
即执行具体操作的代码,它将重复执行,每次对不同的i 值。
第1 次迭代将执行:output[1] = mean(df[[1]])
第2 次迭代将执行:output[2] = mean(df[[2]])

(2) for 循环的几种常用操作
(i) 循环模式
根据数值索引:for(i in seq_along(xs)), 迭代中使用x[i].
根据元素值:for(x in xs), 迭代中使用x.
根据名字:for(nm in names(xs)), 迭代中使用x[nm].

若要创建命名的输出,可按如下方式命名结果向量:
results = vector("list", length(x))
names(results) = names(x)
用数值索引迭代是最常用的形式,因为名字和元素都可以根据索引提取:
for (i in seq_along(x)) {
name = names(x)[i]
value = x[i]
}

(ii) 将每次循环得到的结果合并为一个整体对象
这种情形,在for 循环中经常遇到。此时要尽量避免‘‘每循环一次,就做一次拼接’’,这样效率很
低。更好的做法是:先将结果保存为列表,等循环结束再将列表unlist() 或purrr::flatten_dbl()
成一个向量。

先创建空列表,再将每次循环的结果依次存入列表:
output = list() # output = NULL 也行
# output = vector("list", 3)
for(i in 1:3) {
output[[i]] = c(i, i^2)
}

另外两种类似的情形是:
生成一个长字符串。不是用str_c() 函数将上一次的迭代结果拼接到一起,而是将结果保存为字符向量,再用函数str_c(output, collapse= " ") 合并为一个单独的字符串;
生成一个大的数据框。不是依次用rbind() 函数合并每次迭代的结果,而是将结果保存为列表,再用dplyr::bind_rows(output) 函数合并成一个单独的数据框,或者直接一步到位用purrr::map_dfr()。
所以,遇到上述模式时,要先转化为更复杂的结果对象,最后再做一步合并。

2. while 循环

适用于迭代次数未知。
while 循环更简单,因为它只包含两个组件:条件、循环体:

while (condition) {
# 循环体
}

While 循环是比for 循环更一般的循环,因为for 循环总可以改写为while 循环,但while 循环不一定能改写为for 循环:

for (i in seq_along(x)) {
# 循环体
}
# 等价于
i = 1
while (i <= length(x)) {
# 循环体
i = i + 1
}

下面用while 循环实现:反复随机生成标准正态分布随机数(见1.7.2 节),若值大于1 则停止:

set.seed(123) # 设置随机种子, 让结果可重现
while(TRUE) {
x = rnorm(1)
print(x)
if(x > 1) break
}
## [1] -0.5604756
## [1] -0.2301775
## [1] 1.558708

while 循环并不常用,但在模拟时常用,特别是预先不知道迭代次数的情形。

3. repeat 循环

重复执行循环体,直到满足退出条件:

repeat{
# 循环体
if(退出条件) break
}

注意,repeat 循环至少会执行一次。
repeat 循环等价于:

while (TRUE) {
# 循环体
if(退出条件) break
}

4. apply 函数族

更建议弃用apply 函数族,直接用purrr::map 系列。

(1) apply()

apply() 函数是最常用的代替for 循环的函数。可以对矩阵、数据框、多维数组,按行或列进行循环计算,即将行或列的元素逐个传递给函数FUN 进行计算。其基本格式为:
apply(x, MARGIN, FUN, ...)
x:为数据对象(矩阵、多维数组、数据框) ;
MARGIN:1 表示按行,2 表示按列;
FUN:表示要作用的函数。

x = matrix(1:6, ncol = 3)
x
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
apply(x, 1, mean) # 按行求均值
## [1] 3 4
apply(x, 2, mean) # 按列求均值
## [1] 1.5 3.5 5.5
apply(df, 2, mean) # 对前文df 计算各列的均值
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## 5.843333 3.057333 3.758000 1.199333
(2) tapply()

该函数可以按照因子分组计算分组统计:

height = c(165, 170, 168, 172, 159)
sex = factor(c(" 男", " 女", " 男", " 男", " 女"))
tapply(height, sex, mean)
## 男女
## 168.3333 164.5000
#注意, height 与sex 是等长的,对应元素分别为同一人的身高和性别,tapply() 函数分男女两组计算了身高平均值。
(3) lapply()

lapply() 函数是一个最基础循环操作函数,用来对vector、list、data.frame 逐元、逐成分、逐列分别应用函数FUN,并返回和x 长度相同的list 对象。其基本格式为:
lapply(x, FUN, ...)
x:为数据对象(列表、数据框、向量) ;
FUN:表示要作用的函数。

lapply(df, mean) # 对前文df 计算各列的均值
# $Sepal.Length
# [1] 5.843333
#
# $Sepal.Width
# [1] 3.057333
#
# $Petal.Length
# [1] 3.758
#
# $Petal.Width
# [1] 1.199333
(4) sapply()

sapply() 函数是lapply() 的简化版本,多了一个参数simplify,若simplify=FALSE,则同lapply(),若为TRUE,则将输出的list 简化为向量或矩阵。其基本格式为:
sapply(x, FUN, simplify = TRUE, ...)

sapply(df, mean) # 对前文df 计算各列的均值
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## 5.843333 3.057333 3.758000 1.199333

5. purrr 泛函式编程

相对于apply 族,purrr 泛函式编程提供了更多的一致性、规范性和便利性,更容易记住和使用。

(1) 先来理解几个概念

循环迭代
就是将函数依次应用(映射)到序列的每一个元素上,做相同的操作序列,是由一系列可以根据位置索引的元素构成,元素可以很复杂和不同类型。原子向量和列表都是序列。

泛函式编程
函数的函数称为泛函,在编程中表示函数作用在函数上,或者说函数包含其他函数作为参数。循环迭代,本质上就是将一个函数依次应用(映射)到序列的每一个元素上。表示出来不就是泛函式:map(x, f)。
purrr 泛函式编程解决循环迭代问题的逻辑是:
针对序列每个单独的元素,怎么处理它得到正确的结果,将之定义为函数,再map 到序列中的每一个元素,将得到的多个结果(每个元素作用后返回一个结果)打包到一起返回,并且可以根据想要结果返回什么类型选用map 后缀。

循环迭代返回类型的控制
map 系列函数都有后缀形式,以决定循环迭代之后返回的数据类型,这是purrr 比apply 函数族更先进和便利的一大优势。常用后缀如下:
map_chr(.x, .f): 返回字符型向量
map_lgl(.x, .f): 返回逻辑型向量
map_dbl(.x, .f): 返回实数型向量
map_int(.x, .f): 返回整数型向量
map_dfr(.x, .f): 返回数据框列表,再bind_rows 按行合并为一个数据框
map_dfc(.x, .f): 返回数据框列表,再bind_cols 按列合并为一个数据框

purrr 风格公式
在序列上做循环迭代(应用函数),经常需要自定义函数,但有些简单的函数也用function 定义一番,毕竟是麻烦和啰嗦。所以,purrr 包提供了对purrr 风格公式(匿名函数)的支持。熟悉其他语言的匿名函数的话,很自然地就能习惯。
前面说了,purrr 包实现迭代循环是用map(x, f),f 是要应用的函数,想用匿名函数来写它,它要应用在序列x 上,就是要和序列x 相关联,那么就限定用序列参数名关联好了,即将该序列参数名作为匿名函数的参数使用:
一元函数:序列参数是.x 比如,f(x) = x^ 2 + 1, 其purrr 风格公式就写为:~ .x ^ 2 + 1
二元函数:序列参数是.x, .y 比如,f(x, y) = x^2 −3y, 其purrr 风格公式就写为:~ .x ^ 2 - 3*.y
多元函数:序列参数是..1, ..2, ..3 等比如,f(x, y, z) = ln(x + y + z), 其purrr 风格公式就写为:~ log(..1 + ..2 + ..3)
所有序列参数,可以用... 代替,比如,sum(...) 同sum(..1, ..2, ..3)

(2) map(): 依次应用一元函数到一个序列的每个元素

map(.x, .f, ...)
map_*(.x, .f, ...)
.x 为序列
.f 为要应用的一元函数,或purrr 风格公式(匿名函数)
... 可设置函数.f 的其他参数

map 函数作用机制示意图

map() 返回结果列表,基本同lapply()。例如,计算前文df,每列的均值,即依次将mean() 函数,应用到第1 列,第2 列,. . . ;并控制返回结果为double 向量:

map(df, mean)
## $Sepal.Length
## [1] 5.843333
##
## $Sepal.Width
## [1] 3.057333
##
## $Petal.Length
## [1] 3.758
##
## $Petal.Width
## [1] 1.199333

说明:df 是数据框(特殊的列表) ,作为序列其元素依次是:df[[1]], df[[2]], . . . . . . 所以,map(df,mean) 相当于依次计算:mean(df[[1]]), mean(df[[2]]), . . . . . .
返回结果是double 型数值,所以更好的做法是,控制返回类型为数值向量,只需:

map_dbl(df, mean)
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## 5.843333 3.057333 3.758000 1.199333

另外,mean() 函数还有其他参数,如na.rm,若上述计算过程需要设置忽略缺失值,只需:

map_dbl(df, mean, na.rm = TRUE) # 数据不含NA, 故结果同上(略)
map_dbl(df, ~mean(.x, na.rm = TRUE)) # purrr 风格公式写法

有了map() 函数,对于自定义只接受标量的一元函数,比如f(x), 想要让它支持接受向量作为输入,根本不需要改造原函数,只需:
map_*(xs, f) # xs 表示若干个x 构成的序列

(3) map2(): 依次应用二元函数到两个序列的每对元素

map2(.x, .y .f, ...)
map2_*(.x, .y, .f, ...)
.x 为序列1
.y 为序列2
.f 为要应用的二元函数,或purrr 风格公式(匿名函数)
... 可设置函数.f 的其他参数

map2 函数作用机制示意图

例如,根据身高、体重数据计算BMI 指数

height = c(1.58, 1.76, 1.64)
weight = c(52, 73, 68)
cal_BMI = function(h, w) w / h ^ 2 # 定义计算BMI 的函数
map2_dbl(height, weight, cal_BMI)
## [1] 20.83000 23.56663 25.28257

说明:序列1 其元素为:height[[1]], height[[2]], . . . . . .
序列2 其元素为:weight[[1]], weight[[2]], . . . . . .
所以,map2_dbl(height, weight, cal_BMI) 相当于依次计算:
cal_BMI(height[[1]], weight[[1]]), cal_BMI(height[[2]], weight[[2]]), . . . . . .
更简洁的purrr 风格公式写法(省了自定义函数) :
map2_dbl(height, weight, ~ .y / .x^2)

同样,有了map2() 函数,对于自定义只接受标量的二元函数,比如f(x, y), 想要让它支持接受向量作为输入,根本不需要改造原函数,只需:
map2_*(xs, ys, f) # xs, ys 分别表示若干个x, y 构成的序列

(4) pmap(): 应用多元函数到多个序列的每组元素,可以实现对数据框逐行迭代

多个序列的长度相同,长度相同的列表不就是数据框吗。所以,pmap() 的多元迭代就是依次在数据框的每一行上进行迭代!

pmap(.l, .f, ...)
pmap_*(.l, .f, ...)
.l 为数据框,
.f 为要应用的多元函数
... 可设置函数.f 的其他参数
注: .f 是几元函数,对应数据框.l 有几列,.f 将依次在数据框.l 的每一行上进行迭代。

pmap 函数作用机制示意图

例如,分别生成不同数量不同均值、标准差的正态分布随机数。

df = tibble(
n = c(1, 3, 5),
mean = c(5, 10, -3),
sd = c(1, 5, 10))
df
## # A tibble: 3 x 3
## n mean sd
## <dbl> <dbl> <dbl>
## 1 1 5 1
## 2 3 10 5
## 3 5 -3 10
set.seed(123)
pmap(df, rnorm)
## [[1]]
## [1] 4.439524
##
## [[2]]
## [1] 8.849113 17.793542 10.352542
##
## [[3]]
## [1] -1.707123 14.150650 1.609162 -15.650612 -9.868529

说明:这里的rnorm(n, mean, sd) 是三元函数,pmap(df, rnorm) 相当于将三元函数rnorm() 依次应用到数据框df 的每一行上,即依次执行:
rnorm(1, 5, 1), rnorm(3, 10, 5), rnorm(5, -3, 10)
注意,这里df 中的列名,必须与rnorm() 函数的参数名相同(列序随便)。若要避免这种局限,可以使用purrr 风格公式写法:

pmap(df, ~ rnorm(..1, ..2, ..3)) # 或者简写为
pmap(df, ~ rnorm(...))

pmap_*() 提供了一种行化操作数据框的办法。
pmap_dbl(df, ~ mean(c(...))) # 按行求均值
## [1] 2.333333 6.000000 4.000000
pmap_chr(df, str_c, sep = "-") # 将各行拼接在一起
## [1] "1-5-1" "3-10-5" "5--3-10"

其它purrr 函数

  • imap_(.x, .f): 带索引的map_() 系列,迭代的时候既迭代元素,又迭代元素的索引(位置或名字),purrr 风格公式中用.y 表示索引;
  • invoke_map_*(.f, .x, ...): 将多个函数依次应用到序列,相当于依次执行:.f[1], .f[[2]](.x, ...), . . . . . .
  • walk 系列:walk(.l, .f, ...), walk2(.l, .f, ...), pwalk(.l, .f, ...)
    将函数依次作用到序列上,不返回结果。有些批量操作是没有或不关心返回结果的,例如批量保存数据到文件、批量绘图并保存到文件等。
  • modify 系列:modify(.x, .f, ...), modify2(.x, .y, .f, ...), modify_depth(.x,.depth, .f, ...)
    将函数.f 依次作用到序列.x,并返回修改后的序列.x
  • reduce(): 可先对序列前两个元素应用函数,再对结果与第3 个元素应用函数,再对结果与第4 个元素应用函数,. . . . . . 直到所有的元都被“reduced”
    reduce(1:100, sum) 是对1:100 求累加和;
    reduce() 可用于批量数据连接
  • accumulate(): 与reduce() 作用方式相同,不同之处是:reduce() 只返回最终的结果,而accumulate() 会返回所有中间结果。

1.7 自定义函数

编程中的函数,是用来实现某个功能,其一般形式为:
(返回值1,..., 返回值m) = 函数名(输入1, ..., 输入n)
这些输入和返回值,在函数定义时,都要有固定的类型(模具)限制,叫作形参(形式上的参数);在函数调用时,必须给它对应类型的具体数值,才能真正的去做处理,这叫作实参(实际的参数)。
所以,定义函数就好比创造一个模具,调用函数就好比用模具批量生成产品。
以前文的百分制分数转化为五级制分数为例,如果来一个百分制分数,就这样转化一次,10 个学生分数,就得写100 多行代码。所以有必要封装成一个函数。

1.7.1 自定义函数

1. 自定义函数的一般语法

R 中,自定义函数的一般格式为:

函数名= function(输入1, ..., 输入n) {
函数体
return(返回值)
}
注意,return 并不是必需的,默认函数体最后一行的值作为返回值,也就是说”return(返回值)‘‘完全可以换成” 返回值‘‘。

2. 怎么自定义一个函数

我们想要自定义一个函数,能够实现把百分制分数转化为五级制分数的功能。
基于前面函数的理解,

  • 第一步,分析输入和输出,设计函数外形
    输入有几个,分别是什么,适合用什么数据类型存放;
    输出有几个,分别是什么,适合用什么数据类型存放。
    本问题,输入有1 个:百分制分数,数值型;输出有1 个:五级制分数,字符串
    然后就可以设计自定义函数的外形:
Score_Conv = function(score) {
# 实现将一个百分制分数转化为五级分数
# 输入参数: score 为数值型, 百分制分数
# 返回值: res 为字符串型, 五级分数
...
}

函数名和变量可以随便起名,但是建议用有含义的单词。另外,为函数增加注释是一个好习惯。这些都是为了代码的可读性。

  • 第二步,梳理功能的实现过程
    拿一组本例(只有一个)具体的形参的值作为输入,比如76 分,分析怎么到达对应的五级分数‘‘良’’。这依赖于对五级分数界限的选取,选定之后做分支判断即可实现,即前文的条件语句中的示例那样。
    复杂的功能,就需要更耐心的梳理和思考甚至借助一些算法,当然也离不开逐代码片段的调试。
score = 76
if(score >= 90) {
res = " 优"
} else if(score >= 80) {
res = " 良"
} else if(score >= 70) {
res = " 中"
} else if(score >= 60) {
res = " 及格"
} else {
res = " 不及格"
}
res
## [1] "中"

拿一组具体的形参值作为输入,通过逐步调试,得到正确的返回值结果,这一步骤非常关键和有必要。

  • 第三步,将第二步的代码封装到函数体
Score_Conv = function(score) {
if(score >= 90) {
res = " 优"
} else if(score >= 80) {
res = " 良"
} else if(score >= 70) {
res = " 中"
} else if(score >= 60) {
res = " 及格"
} else {
res = " 不及格"
}
res
}

基本就是原样作为函数体放入函数,原来的变量赋值语句不需要了,只需要形参。

3. 调用函数

要调用自定义函数,必须要先加载到当前变量窗口(内存),有两种方法:
需要选中并执行函数代码,或者将函数保存为同名的Score_Conv.R 文件,注意勾选”Source on save” 再保存,然后执行
source("Score_Conv.R", encoding="UTF-8")
然后就可以调用函数了,给它一个实参76,输出结果为’’ 中’’:
Score_Conv(76)
## [1] "中"

  • 关于函数传递参数
    要调用一个函数,比如f(x, y), 首先要清楚其形参x, y 所要求的类型,假设x 要求是数值向量, y
    要求是单个逻辑值。
    那么,要调用该函数,首先需要准备与形参类型相符的实参(同名异名均可),比如
    a = c(3.56, 2.1)
    b = FALSE
    再调用函数:
    f(a, b) # 同直接给值: f(c(3.56,2.1), FALSE)
    调用函数时若不指定参数名,则默认是根据位置关联形参,即以x = a, y = b 的方式进入函数体。
    调用函数时若指定参数名,则根据参数名关联形参,位置不再重要,比如
    f(y = b, x = a) # 效果同上
4. 向量化改进

我们希望自定义函数,也能处理向量输入,即输入多个百分制分数,能一下都转化为五级分数。这也是所谓的’’ 向量化编程’’ 思维,就是要习惯用向量(矩阵)去思考、去表达。

  • 方法一:修改自定义函数
    将输入参数设计为数值向量,函数体也要相应的修改,借助循环依次处理向量中的每个元素,就相当于再套一层for 循环。
Score_Conv2 = function(score) {
n = length(score)
res = vector("character", n)
for(i in 1:n) {
if(score[i] >= 90) {
res[i] = " 优"
} else if(score[i] >= 80) {
res[i] = " 良"
} else if(score[i] >= 70) {
res[i] = " 中"
} else if(score[i] >= 60) {
res[i] = " 及格"
} else {
res[i] = " 不及格"
}
}
res
}
# 测试函数
scores = c(35, 67, 100)
Score_Conv2(scores)
## [1] "不及格" "及格" "优"
  • 方法二:借助apply 族或map 系列函数
    简单的循环语句,基本都可以改用apply 族或map 系列函数实现,其作用相当于是依次’’ 应用’’某函数,到序列的每个元素上。

也就是说,不需要修改原函数,直接就能实现向量化操作:

scores = c(35, 67, 100)
map_chr(scores, Score_Conv)
## [1] "不及格" "及格" "优"
5. 处理多个返回值

若自定义函数需要有多个返回值,R 的处理方法是,将多个返回值放入一个列表(或数据框),再返回一个列表。
例如,自定义函数,实现计算一个数值向量的均值和标准差:

MeanStd = function(x) {
mu = mean(x)
std = sqrt(sum((x-mu)^2) / (length(x)-1))
list(mu=mu, std=std)
}
# 测试函数
x = c(2, 6, 4, 9, 12)
MeanStd(x)
## $mu
## [1] 6.6
##
## $std
## [1] 3.974921
6. 默认参数值

有时候需要为输入参数设置默认值。
以前面的计算数值向量的均值和标准差的函数为例。我们知道,标准差的计算公式有两种形式,一种是总体标准差除以n, 一种是样本标准差除以n − 1
此时,没有必要写两个版本的函数,只需要再增加一个指示参数,将用的多的版本设为默认即可。

MeanStd2 = function(x, type = 1) {
mu = mean(x)
n = length(x)
if(type == 1) {
std = sqrt(sum((x - mu) ^ 2) / (n - 1))
} else {
std = sqrt(sum((x - mu) ^ 2) / n)
}
list(mu = mu, std = std)
}
# 测试函数
x = c(2, 6, 4, 9, 12)
# MeanStd2(x) # 同MeanStd(x)
MeanStd2(x, 2)
## $mu
## [1] 6.6
##
## $std
## [1] 3.555278

用type = 1 来指示表意并不明确,可以用表意更明确的字符串来指示,这就需要用到switch(),让不同的指示值= 相应的代码块,因为代码块往往是多行,需要用大括号括起来,注意分支与分支之间的逗号不能少。

MeanStd3 = function(x, type = "sample") {
mu = mean(x)
n = length(x)
switch(type,
"sample" = {
std = sqrt(sum((x - mu) ^ 2) / (n - 1))
},
"population" = {
std = sqrt(sum((x - mu) ^ 2) / n)
})
list(mu = mu, std = std)
}
MeanStd3(x)
## $mu
## [1] 6.6
##
## $std
## [1] 3.974921
MeanStd3(x, "population")
## $mu
## [1] 6.6
##
## $std
## [1] 3.555278
7. “. . . ’’ 参数

一般函数参数只接受一个对象,即使不指定参数名,也会按位置对应参数。例如

my_sum = function(x, y) {
sum(x, y)
}
my_sum(1, 2)
## [1] 3

但是,如果想对3 个数加和,怎么办?直接my_sum(1, 2, 3) 会报错。

... 是一个特殊参数,可以接受任意多个对象,并作为一个列表传递它们:

dots_sum = function(...) {
sum(...)
}
dots_sum(1)
## [1] 1
dots_sum(1, 2, 3, 4, 5)
## [1] 15
  • 几乎所有R 自带函数都在用... 这样传递参数。若参数... 后面还有其他参数,为了避免歧义调用函数时需要对其随后参数命名。

1.7.2 R 自带函数

除了自定义函数,还可以使用现成的函数:

  • 来自base R:可直接使用
  • 来自各种扩展包:需载入包,或加上包名前缀:包名:: 函数名()

这些函数的使用,可以通过? 函数名查阅其帮助,以及查阅包页面的Reference manual 和Vignettes(若有)。
下面对常用的R 自带函数做分类总结。

1. 基本数学函数
round(x, digits) # IEEE 754 标准的四舍五入, 保留n 位小数
signif(x, digits) # 四舍五入, 保留n 位有效数字
ceiling(x) # 向上取整, 例如ceiling(pi) 为4
floor(x) # 向下取整, 例如floor(pi) 为3
sign(x) # 符号函数
abs(x) # 取绝对值
sqrt(x) # 求平方根
exp(x) # e 的x 次幂
log(x, base) # 对x 取以... 为底的对数, 默认以e 为底
log2(x) # 对x 取以2 为底的对数
log10(x) # 对x 取以10 为底的对数
Re(z) # 返回复数z 的实部
Im(z) # 返回复数z 的虚部
Mod(z) # 求复数z 的模
Arg(z) # 求复数z 的辐角
Conj(z) # 求复数z 的共轭复数
2. 三角函数与双曲函数
sin(x) # 正弦函数
cos(x) # 余弦函数
tan(x) # 正切函数
asin(x) # 反正弦函数
acos(x) # 反余弦函数
atan(x) # 反正切函数
sinh(x) # 双曲正弦函数
cosh(x) # 双曲余弦函数
tanh(x) # 双曲正切函数
asinh(x) # 反双曲正弦函数
acosh(x) # 反双曲余弦函数
atanh(x) # 反双曲正切函数
3. 矩阵函数
nrow(A) # 返回矩阵A 的行数
ncol(A) # 返回矩阵A 的列数
dim(A) # 返回矩阵x 的维数(几行× 几列)
colSums(A) # 对矩阵A 的各列求和
rowSums(A) # 对矩阵A 的各行求和
colMeans(A) # 对矩阵A 的各列求均值
rowMeans(A) # 对矩阵A 的各行求均值
t(A) # 对矩阵A 转置
det(A) # 计算方阵A 的行列式
crossprod(A, B) # 计算矩阵A 与B 的内积, t(A) %*% B
outer(A, B) # 计算矩阵的外积(叉积) , A %o% B
diag(x) # 取矩阵对角线元素,或根据向量生成对角矩阵
diag(n) # 生成n 阶单位矩阵
solve(A) # 求逆矩阵(要求矩阵可逆)
solve(A, B) # 解线性方程组AX=B
ginv(A) # 求矩阵A 的广义逆(Moore-Penrose 逆)
eigen() # 返回矩阵的特征值与特征向量(列)
kronecker(A, B) # 计算矩阵A 与B 的Kronecker 积
svd(A) # 对矩阵A 做奇异值分解,A=UDV'
qr(A) # 对矩阵A 做QR 分解: A=QR, Q 为酉矩阵, R 为阶梯形矩阵
chol(A) # 对正定矩阵A 做Choleski 分解, A=P'P,P 为上三角矩阵
A[upper.tri(A)] # 提取矩阵A 的上三角矩阵
A[lower.tri(A)] # 提取矩阵A 的下三角矩阵
4. 概率函数
factorial(n) # 计算n 的阶乘
choose(n, k) # 计算组合数
gamma(x) # Gamma 函数
beta(a, b) # beta 函数
combn(x, m) # 生成x 中任取m 个元的所有组合, x 为向量或整数n
combn(4, 2)
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 1 1 1 2 2 3
## [2,] 2 3 4 3 4 4
combn(c(" 甲"," 乙"," 丙"," 丁"), 2)
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] "甲" "甲" "甲" "乙" "乙" "丙"
## [2,] "乙" "丙" "丁" "丙" "丁" "丁"

R 中,常用的概率函数有密度函数、分布函数、分位数函数、生成随机数函数,其写法为:
d = 密度函数(density)
p = 分布函数(distribution)
q = 分位数函数(quantile)
r = 生成随机数(random)

上述4 个字母+ 分布缩写,就构成通常的概率函数,例如:

dnorm(3, 0, 2) # 正态分布N(0, 4) 在3 处的密度值
## [1] 0.0647588
pnorm(1:3, 1, 2) # N(1,4) 分布在1,2,3 处的分布函数值
## [1] 0.5000000 0.6914625 0.8413447
# 命中率为0.02, 独立射击400 次, 至少击中两次的概率
1 - sum(dbinom(0:1, 400, 0.02))
## [1] 0.9971655
pnorm(2, 1, 2) - pnorm(0, 1, 2) # X~N(1, 4), 求P{0<X<=2}
## [1] 0.3829249
qnorm(1-0.025,0,1) # N(0,1) 的上0.025 分位数
## [1] 1.959964

生成随机数:

set.seed(123) # 设置随机种子, 以重现随机结果
rnorm(5, 0, 1) # 生成5 个服从N(0,1) 分布的随机数
## [1] -0.56047565 -0.23017749 1.55870831 0.07050839 0.12928774
常用概率分布及缩写

随机抽样:

sample() 函数,用来从向量中重复或非重复地随机抽样,基本格式为:
sample(x, size, replace = FALSE, prob)
x:向量或整数;
size:设置抽样次数;
replace:设置是否重复抽样;
prob:设定抽样权重。

set.seed(2020)
sample(c(" 正"," 反"), 10, replace=TRUE) # 模拟抛10 次硬币
## [1] "反" "反" "正" "反" "反" "正" "正" "反" "反" "反"
sample(1:10, 10, replace=FALSE) # 随机生成1~10 的某排列
## [1] 1 8 9 2 7 5 6 3 4 10
5. 统计函数
min(x) # 求最小值
cummin(x) # 求累计最小值
max(x) # 求最大值
cummax(x) # 求累计最大值
range(x) # 求x 的范围:[最小值,最大值] (向量)
sum(x) # 求和
cumsum(x) # 求累计和
prod(x) # 求积
cumprod(x) # 求累计积
mean(x) # 求平均值
median(x) # 求中位数
quantile(x, pr) # 求分位数, x 为数值向量, pr 为概率值
sd(x) # 求标准差
var(x) # 求方差
cov(x) # 求协方差
cor(x) # 求相关系数
scale(x, center=TRUE, scale=FALSE) # 对数据做中心化: 减去均值
scale(x, center=TRUE, scale=TRUE) # 对数据做标准化

自定义极差标准化函数:

rescale = function(x, type=1) {
# type=1 正向指标, type=2 负向指标
rng = range(x, na.rm = TRUE)
if (type == 1) {
(x - rng[1]) / (rng[2] - rng[1])
} else {
(rng[2] - x) / (rng[2] - rng[1])
}
}
x = c(1, 2, 3, NA, 5)
rescale(x)
## [1] 0.00 0.25 0.50 NA 1.00
rescale(x, 2)
## [1] 1.00 0.75 0.50 NA 0.00
6. 时间序列函数

lag() 函数,用来计算时间序列的滞后,基本格式为:
lag(x, k, ...)
x:为数值向量/矩阵或一元/多元时间序列;
k:为滞后阶数,默认为1.

diff() 函数,用来计算时间序列的差分,基本格式为:
diff(x, lag = 1, difference = 1, ...)
x:为数值向量/矩阵;
lag:为滞后阶数,默认为1;
difference:为差分阶数,默认为1.

Yt 的j 阶滞后为Yt−j :

x = ts(1:8, frequency = 4, start = 2015)
x
## Qtr1 Qtr2 Qtr3 Qtr4
## 2015 1 2 3 4
## 2016 5 6 7 8
stats::lag(x, 4) # 避免被dplyr::lag() 覆盖
## Qtr1 Qtr2 Qtr3 Qtr4
## 2014 1 2 3 4
## 2015 5 6 7 8

Yt 的一阶差分为ΔYt = Yt − Yt−1, 二阶差分为Δ2Yt = ΔYt − ΔYt−1, . . .

x = c(1, 3, 6, 8, 10)
x
## [1] 1 3 6 8 10
diff(x, differences = 1)
## [1] 2 3 2 2
diff(x, differences = 2)
## [1] 1 -1 0
diff(x, lag = 2, differences = 1)
## [1] 5 5 4
7. 其他函数
unique(x, ...) # 返回唯一值, 即去掉重复元素或观测
duplicated(x, ...) # 判断元素或观测是否重复(多余), 返回逻辑值向量
anyDuplicated(x, ...) # 返回重复元素或观测的索引
rle(x) # 统计向量中连续相同值的长度
inverse.rle(x) # rle() 的反向版本, x 为list(lengths, values)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容