抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

和其它编程语言一样,R 中也存在着高效执行重复任务的结构,即循环,也称迭代。在 R 中存在着两种重要的迭代方式:命令式编程函数式编程。其它编程语言中常见的如 for 循环、while 循环(或类似于这些循环)等循环结构,在 R 中同样存在,属于命令式编程。但在 R 中还存在着另外一种循环方式,即函数式编程。R 有一些包可以实现该功能,例如 purrr 包。该包是 tidyverse 包的核心 R 包之一。

for 循环

循环模式

对向量进行循环的基本方式有 3 种:

  • 使用数值索引进行循环
  • 使用元素进行循环
  • 使用名称进行循环

while 循环

映射循环

单向量映射

R 中经常会存在这样一种模式:先对向量进行循环,然后对每个元素进行处理,最后保存结果。purrr 包中存在一个函数族可以完成以上操作:

函数作用
map()输出列表
map_lgl()输出逻辑型向量
map_int()输出整型向量
map_dbl()输出双精度型向量
map_chr()输出字符型向量

映射函数的主要优势不是速度,而是简洁

具体使用方式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> df <- tibble(x = c(3, 4, 5), y = c(6, 8, 10), z = c(5, 12, 13))
> df
# # A tibble: 3 x 3
# x y z
# <dbl> <dbl> <dbl>
# 1 3 6 5
# 2 4 8 12
# 3 5 10 13
> map_dbl(df, mean)
# x y z
# 4 8 10
> map_dbl(df, median)
# x y z
# 4 8 12
> map_dbl(df, sd)
# x y z
# 1.000000 2.000000 4.358899

映射函数除了可以对向量等多元素结构中的元素进行函数处理外,还可以给用来处理的函数附带参数,例如:

1
2
3
4
5
6
7
8
> mu <- list(0, 1, 10)
> samples <- map(mu, rnorm, n = 100000)
> mean(samples[[1]])
# [1] 0.003097461
> mean(samples[[2]])
# [1] 0.9996111
> mean(samples[[3]])
# [1] 9.997856

上例会生成一个列表,其中的元素为服从均值分别为 0、1、10 的正态分布(标准差为 1,样本容量为 100000 )的随机数数列。

map() 函数等映射函数均可以附带多个参数,列在处理函数名之后

多向量映射

映射函数 map() 可以进行一个向量的迭代映射处理。对于多个向量的情况,需要使用其它的映射函数。例如可以使用 map2() 函数进行双向量的映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> mu <- list(0, 1, 10)
> sigma <- list(1, 5, 10)
> samples <- map2(mu, sigma, rnorm, n = 100000)
> mean(samples[[1]])
# [1] 0.008634069
> mean(samples[[2]])
# [1] 0.9733784
> mean(samples[[3]])
# [1] 9.963484
> sd(samples[[1]])
# [1] 1.002143
> sd(samples[[2]])
# [1] 4.991922
> sd(samples[[3]])
# [1] 10.0035

类似地,上例会生成一个列表,其中的元素为服从均值分别为 0、1、10 ,标准差分别为 1、5、10 的正态分布(样本容量为 100000 )的随机数数列。

在此基础上,若想对多向量进行迭代映射处理,可以使用 pmap() 函数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
> n <- list(200, 300, 500)
> mu <- list(0, 1, 10)
> sigma <- list(1, 5, 10)
> argslist <- list(mean = mu, sd = sigma, n = n)
> str(argslist)
List of 3
# $ mean:List of 3
# ..$ : num 0
# ..$ : num 1
# ..$ : num 10
# $ sd :List of 3
# ..$ : num 1
# ..$ : num 5
# ..$ : num 10
# $ n :List of 3
# ..$ : num 200
# ..$ : num 300
# ..$ : num 500
> samples <- pmap(argslist, rnorm)
> str(samples)
# List of 3
# $ : num [1:200] -0.1887 -0.6226 0.0414 -2.5238 0.672 ...
# $ : num [1:300] -3.6 7.88 0.23 -3.95 2.6 ...
# $ : num [1:500] -1.67 5.81 14.41 6.66 19.46 ...

pmap() 函数与 map() 等映射函数稍有区别,它接受一个多向量构成的列表作为第一参数,而不是接受多个向量作为参数

pmap() 函数的第一参数中列表的各个元素名推荐与处理函数的参数名一致,否则该函数会将列表内与处理函数的参数名不一致的名称对应的元素,按顺序赋给未与处理函数参数名保持一致的参数

多向量多函数映射

在多向量映射的基础上,还可以进一步地对多个操作函数进行映射。实现该操作的函数为 invoke_map() ,例如:

1
2
3
4
5
6
7
8
> funclist <- list(runif, rpois, rnorm)
> paramlist <- list(list(min = -1, max = 1), list(lambda = 10), list(sd = 4, mean = 5))
> samples <- invoke_map(funclist, paramlist, n = 100)
> str(samples)
# List of 3
# $ : num [1:100] 0.96 0.37 0.14 0.454 -0.669 ...
# $ : int [1:100] 5 11 11 6 13 11 6 13 8 13 ...
# $ : num [1:100] 1.45 4.18 1.97 9.28 5.32 ...

上例会生成一个列表,列表中包含三个元素。第一个元素为服从最小值为 -1,最大值为 1 ,样本容量为 100 的均匀分布的随机数据列表;第二个元素为服从期望和方差为 10 ,样本容量为 100 的泊松分布的随机数据列表;第三个元素为服从标准差为 4 ,均值为 5 ,样本容量为 100 的正态分布的随机数据列表。

与单函数映射不同,invoke_map() 函数的第二参数(即操作函数的对应参数集列表)是元素为列表的列表。即第二参数的各个元素对应着第一参数(即操作函数列表)的所需参数集,而这些参数集中的元素对应着其操作函数的所需参数

概括地讲,invoke_map() 函数的第二参数是操作函数参数集列表,其元素是操作函数参数集,其元素的元素是操作函数参数

基于上述,多函数映射的第二参数必须注意操作函数参数名不是第二参数的元素名

游走函数

当进行迭代的目的是函数的副作用而不是返回值时,应该使用游走函数而不是映射函数。实现该操作的函数为 walk() ,例如:

1
2
3
4
5
> x <- list(1, "R", TRUE)
> walk(x, print)
# [1] 1
# [1] "R"
# [1] TRUE

类似地,游走函数也存在着函数 walk2()pwalk() 函数。

以上三个函数除了完成操作函数的副作用外,这些游走函数本身也具有返回值,为其第一参数(若无进行修改的话)

针对上例,walk(x, print)x 的值是一样的

预测函数

保留元素

可以使用 keep() 函数保留符合条件的元素,例如:

1
2
3
4
5
6
7
8
9
> rep(10, 10) %>% map(sample, 5) %>% keep(function(x) mean(x) > 6)
# [[1]]
# [1] 4 9 8 7 6
#
# [[2]]
# [1] 10 8 2 7 4
#
# [[3]]
# [1] 4 9 3 6 10

丢弃元素

可以使用 discard() 函数丢弃符合条件的元素,例如:

1
2
3
4
5
6
7
8
9
10
11
12
> rep(10, 10) %>% map(sample, 5) %>% discard(function(x) mean(x) > 6)
# [[1]]
# [1] 1 3 9 6 4
#
# [[2]]
# [1] 1 4 5 8 3
#
# [[3]]
# [1] 7 4 9 5 3
#
# [[4]]
# [1] 2 1 4 10 5

列表元素预测

对列表元素可以使用以下函数进行预测:

函数作用
every()判断预测值是否对所有元素为真
some()判断预测值是否对某个元素为真
none()判断预测值是否对所有元素为假

例如:

1
2
3
4
5
6
7
8
9
> y <- list(0:10, 5.5)
> y %>% every(is.numeric)
# [1] TRUE
> y %>% every(is.integer)
# [1] FALSE
> y %>% some(is.integer)
# [1] TRUE
> y %>% none(is.character)
# [1] TRUE

查找元素

可以使用 detect() 函数找出预测为真的第一个元素,使用 detect_index() 函数返回预测为真的第一个元素的索引。例如:

1
2
3
4
5
> is_even <- function(x) x %% 2 == 0
> 3:10 %>% detect(is_even)
# [1] 4
> 3:10 %>% detect_index(is_even)
# [1] 2

可以使用 head_while()tail_while() 函数分别从列表或原子型向量的开头和结尾找出预测为真的元素,直到预测为假为止。例如:

1
2
3
4
5
6
> numlist <- c(1, 2, 3, 4, 3, 4, 5, 4, 3, 4, 3, 2, 1)
> func <- function(x) x < 4
> head_while(numlist, func)
# [1] 1 2 3
> tail_while(numlist, func)
# [1] 3 2 1

规约与累计

可以使用归约函数 reduce() 将列表中的元素按从前向后的顺序用二元函数或二元运算符进行迭代运算。例如:

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
27
28
29
30
31
32
33
34
35
36
37
> physical_properties <- list(
+ phys_density = tibble(name = "water", phys_density = 998),
+ phys_viscosity = tibble(name = "water", phys_viscosity = 0.001005),
+ phys_state = tibble(name = c("air", "water"), phys_state = c("gas", "liquid"))
+ )
> physical_properties
# $phys_density
# # A tibble: 1 x 2
# name phys_density
# <chr> <dbl>
# 1 water 998
#
# $phys_viscosity
# # A tibble: 1 x 2
# name phys_viscosity
# <chr> <dbl>
# 1 water 0.00100
#
# $phys_state
# # A tibble: 2 x 2
# name phys_state
# <chr> <chr>
# 1 air gas
# 2 water liquid
#
> reduce(physical_properties,full_join)
# Joining, by = "name"
# Joining, by = "name"
# # A tibble: 2 x 4
# name phys_density phys_viscosity phys_state
# <chr> <dbl> <dbl> <chr>
# 1 water 998 0.00100 liquid
# 2 air NA NA gas
> 1:3 %>% reduce(`+`)
# [1] 6
> 1:10 %>% reduce(`*`)
# [1] 3628800

累计函数 accumulate() 的作用与归约函数相似,区别在于它会将迭代操作过程显示出来。例如:

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
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
52
> physical_properties <- list(
+ phys_density = tibble(name = "water", phys_density = 998),
+ phys_viscosity = tibble(name = "water", phys_viscosity = 0.001005),
+ phys_state = tibble(name = c("air", "water"), phys_state = c("gas", "liquid"))
+ )
> physical_properties
# $phys_density
# # A tibble: 1 x 2
# name phys_density
# <chr> <dbl>
# 1 water 998
#
# $phys_viscosity
# # A tibble: 1 x 2
# name phys_viscosity
# <chr> <dbl>
# 1 water 0.00100
#
# $phys_state
# # A tibble: 2 x 2
# name phys_state
# <chr> <chr>
# 1 air gas
# 2 water liquid
#
> accumulate(physical_properties,full_join)
# Joining, by = "name"
# Joining, by = "name"
# $phys_density
# # A tibble: 1 x 2
# name phys_density
# <chr> <dbl>
# 1 water 998
#
# $phys_viscosity
# # A tibble: 1 x 3
# name phys_density phys_viscosity
# <chr> <dbl> <dbl>
# 1 water 998 0.00100
#
# $phys_state
# # A tibble: 2 x 4
# name phys_density phys_viscosity phys_state
# <chr> <dbl> <dbl> <chr>
# 1 water 998 0.00100 liquid
# 2 air NA NA gas
#
> 1:3 %>% accumulate(`+`)
# [1] 1 3 6
> 1:10 %>% accumulate(`*`)
# [1] 1 2 6 24 120 720 5040
# [8] 40320 362880 3628800

评论



This is a picture without description

This is a picture without description This is a picture without description This is a picture without description