1. 前言
本节我们将开始介绍 R
中的迭代。主要介绍两种重要的迭代:
-
命令式编程:
有像
for
和while
循环一样的工具,使迭代非常的明确以及比较容易理解。但是
for
循环一般代码较长,重复的代码较多 -
函数式编程(
FP,Functional programming
):函数式编程提供了提取重复代码的工具,每个循环模式都是自己的函数。
1.1 导入
在这里我们将要介绍另一个 tidyverse
核心包 purrr
。它提供了许多强大的编程工具
library(tidyverse)
2. for 循环
假设我们有下面一个简单的 tibble
df <- tibble(
a = rnorm(10),
b = rnorm(10),
c = rnorm(10),
d = rnorm(10)
)
计算每列的中位数
> median(df$a)
[1] 0.2037405
> median(df$b)
[1] -0.2373901
> median(df$c)
[1] 0.05839867
> median(df$d)
[1] 0.08679879
这样做可真不是个好选择。记住,只要复制粘贴代码两次以上,就要考虑使用 for
循环
> output <- vector("double", ncol(df)) # 1. output
> for (i in seq_along(df)) { # 2. sequence
+ output[[i]] <- median(df[[i]]) # 3. body
+ }
> output
[1] 0.20374050 -0.23739013 0.05839867 0.08679879
对于每个循环有三个组成部分
- 输出:
output <- vector("double", length(x))
在循环开始之前,必须为输出分配足够的空间。这是很重要的,如果每次都用 c()
来动态添加会极大拖慢程序的速度
通常使用 vector()
来创建给定长度的空向量。接受两个参数:向量的类型(logical
, integer
, double
, character
等)和长度。
- 序列:
i in seq_along(df)
遍历 1,2,3,4
。你可能没见过 seq_along()
,它是一个安全版本的 1:length(l)
。
它们之间的区别是,当传入的是一个空向量时,seq_along
是正确的
> y <- vector("double", 0)
> seq_along(y)
integer(0)
> 1:length(y)
[1] 1 0
- 循环体:
output[[i]] <- median(df[[i]])
每次获取不同的 i 值,并执行同样的操作。
2.1 思考练习
- 编写循环
- 计算
mtcars
每一列的均值 - 确定
nycflights13::flights
每一列的类型 - 计算
iris
每列唯一值的数目 - 从均值为
-10
、0
、10
和100
的分布中生成10
个随机正态分布
- 将下面的代码改写为向量函数而不是
for
循环
out <- ""
for (x in letters) {
out <- stringr::str_c(out, x)
}
x <- sample(100)
sd <- 0
for (i in seq_along(x)) {
sd <- sd + (x[i] - mean(x)) ^ 2
}
sd <- sqrt(sd / (length(x) - 1))
x <- runif(100)
out <- vector("numeric", length(x))
out[1] <- x[1]
for (i in 2:length(x)) {
out[i] <- out[i - 1] + x[i]
}
3. 变异 for 循环
for
循环主要包含 4
种变体:
- 修改一个现有对象,而不是创建一个新对象
- 遍历名称或值,而不是索引
- 处理长度未知的输出
- 处理长度未知的序列
3.1 修改现有对象
df <- tibble(
a = rnorm(10),
b = rnorm(10),
c = rnorm(10),
d = rnorm(10)
)
rescale01 <- function(x) {
rng <- range(x, na.rm = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
}
df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df$d <- rescale01(df$d)
改写为 for
循环
for (i in seq_along(df)) {
df[[i]] <- rescale01(df[[i]])
}
为什么在每个 for
循环内部我都使用 [[
而不是 [
呢?因为它清楚地表明,我想处理的是单个元素。
3.2 循环模式
遍历向量主要有三种基本方式,上面讲的是最常用的方式。还有另外两种:
- 遍历向量的元素:
for (x in xs)
- 遍历向量的名称:
for (n in names(xs))
一般遍历索引是最通用的形式,可以根据索引位置提取出名称和值
for (i in seq_along(x)) {
name <- names(x)[[i]]
value <- x[[i]]
}
3.3 输出长度未知
有时您可能不知道输出结果有多长,可以使用动态添加的方式
> means <- c(0, 1, 2)
>
> output <- double()
> for (i in seq_along(means)) {
+ n <- sample(100, 1)
+ output <- c(output, rnorm(n, means[[i]]))
+ }
> str(output)
num [1:153] -0.479 0.612 1.231 1.243 0.583 ...
但是会增加程序耗时。
一个改进的方法是,将结果保存在 list
当中,循环之后再合并为一个向量。
> out <- vector("list", length(means))
> for (i in seq_along(means)) {
+ n <- sample(100, 1)
+ out[[i]] <- rnorm(n, means[[i]])
+ }
> str(out)
List of 3
$ : num -1.52
$ : num [1:30] 0.163 -0.411 0.144 0.613 2.449 ...
$ : num [1:65] 3.037 1.725 1.879 3.329 0.978 ...
> str(unlist(out))
num [1:96] -1.522 0.163 -0.411 0.144 0.613 ...
在这里,我们使用 unlist
将一个列表向量展开为单个向量。
这种模式先考虑将输出保存在更复杂的对象中,在循环结束后合并到一起。
3.4 序列长度未知
有时你可能甚至不知道序列有多长,可以考虑使用 while
循环。
例如,计算连续得到三个 H
需要多少次数
> flip <- function() sample(c("T", "H"), 1)
>
> flips <- 0
> nheads <- 0
>
> while (nheads < 3) {
+ if (flip() == "H") {
+ nheads <- nheads + 1
+ } else {
+ nheads <- 0
+ }
+ flips <- flips + 1
+ }
> flips
[1] 58
3.5 思考练习
-
如果你使用
for (nm in names(x))
遍历,但是x
没有名称时会发生什么?如果只有一些元素有名称呢?如果名字不是唯一的呢? -
编写一个函数来打印数据框中每个数字列的均值及其名称。例如,
show_mean(iris)
将打印
show_mean(iris)
#> Sepal.Length: 5.84
#> Sepal.Width: 3.06
#> Petal.Length: 3.76
#> Petal.Width: 1.20
- 下面的代码的作用是什么?它是如何工作的?
trans <- list(
disp = function(x) x * 0.0163871,
am = function(x) {
factor(x, labels = c("auto", "manual"))
}
)
for (var in names(trans)) {
mtcars[[var]] <- trans[[var]](mtcars[[var]])
}
4. for VS 函数
for
循环在 R
中可能没有在其他语言中那么重要,因为 R
是函数式编程语言。
这意味着可以在函数中将 for
循环封装起来,然后调用该函数,而不是直接使用 for
循环。
为了理解这一点的重要性,让我们考虑下面这个数据框
df <- tibble(
a = rnorm(10),
b = rnorm(10),
c = rnorm(10),
d = rnorm(10)
)
你可以使用 for
循环来计算每列的均值
> output <- vector("double", length(df))
> for (i in seq_along(df)) {
+ output[[i]] <- mean(df[[i]])
+ }
> output
[1] 0.41487173 -0.16774333 -0.05348092 0.01059490
你将会意识到,这一操作是会频繁的发生,所以我们将它封装为一个函数
col_mean <- function(df) {
output <- vector("double", length(df))
for (i in seq_along(df)) {
output[i] <- mean(df[[i]])
}
output
}
但同时,你认为计算中位数和标准差也会有所帮助,所以你复制并粘贴 col_mean()
函数,然后用 median
和 sd
替换 mean
col_median <- function(df) {
output <- vector("double", length(df))
for (i in seq_along(df)) {
output[i] <- median(df[[i]])
}
output
}
col_sd <- function(df) {
output <- vector("double", length(df))
for (i in seq_along(df)) {
output[i] <- sd(df[[i]])
}
output
}
你看,类似的代码虽然被包装为不同的函数,但是大部分代码还是复制粘贴,那我们该怎么改进呢?
考虑一下下面的简单例子
f1 <- function(x) abs(x - mean(x)) ^ 1
f2 <- function(x) abs(x - mean(x)) ^ 2
f3 <- function(x) abs(x - mean(x)) ^ 3
我们可以将这三个函数再抽象出来
f <- function(x, i) abs(x - mean(x)) ^ i
这样不仅减少了代码量,同时提高了函数的可扩展性。
现在,让我们来更改上面的三个函数
col_summary <- function(df, fun) {
out <- vector("double", length(df))
for (i in seq_along(df)) {
out[i] <- fun(df[[i]])
}
out
}
col_summary(df, median)
#> [1] -0.51850298 0.02779864 0.17295591 -0.61163819
col_summary(df, mean)
#> [1] -0.3260369 0.1356639 0.4291403 -0.2498034
将一个函数传递给另一个函数,是 R
中非常重要的思想。
在后续的章节中,将介绍并使用 purrr
包中的函数来消除常见的 for
循环。
当然 R
提供的原生的 apply()
, lapply()
, tapply()
也可以解决类似的问题,但是 purrr
更容易学习使用。
使用 purrr
中的函数而不是 for
循环的目的是为了让你将常见的列表操作分解成独立的部分
-
如何解决列表中单个元素的问题?解决该问题后,
purrr
会将您的解决方案推广到列表中的每个元素 -
如果你正在解决一个复杂的问题,你如何把它分解成多个小块,从而让你更容易解决问题。然后使用
purrr
将许多小部件通过管道组合在一起
4.1 思考练习
-
阅读
apply()
的文档。在2d
情况下,它泛化了哪两个for
循环? -
调整
col_summary()
,使其仅适用于数值列。您可能需要使用is_numeric()
函数,该函数返回一个逻辑向量,每个数值列对应TRUE
。
5. map 函数
遍历向量,并对向量中每个元素执行函数操作,最后返回结果的模式是非常普遍的。
因此, purrr
提供了一系列函数来帮你简化这些操作,每种类型的都有一个对应的函数
map()
-list
map_lgl()
- 逻辑值向量map_int()
- 整数向量map_dbl()
- double 向量map_chr()
- 字符串向量
每个函数都将一个向量作为输入,将一个函数应用于每个元素,然后返回一个与输入长度相同(名称相同)的新变量。向量的类型由 map
函数的后缀确定。
这样能够极大减少 for
循环的使用,使你的代码更加简洁和优雅。
当然并不是说 for
循环的速度很慢,我们避免使用 for
循环只是为了让代码更加的清晰,易于编写和阅读。
我们使用 map
函数来重写最后一个 for
循环,用更少的代码完成同样的工作。
因为我们的数据是双精度的,所以使用 map_dbl()
函数
> map_dbl(df, mean)
a b c d
0.41487173 -0.16774333 -0.05348092 0.01059490
> map_dbl(df, median)
a b c d
0.20374050 -0.23739013 0.05839867 0.08679879
> map_dbl(df, sd)
a b c d
1.0889674 0.8172145 0.8357361 0.7092745
与 for
循环相比,我们突出的是对每个元素执行的函数,如果我们使用管道符,这一点会更加明显
> df %>% map_dbl(mean)
a b c d
0.41487173 -0.16774333 -0.05348092 0.01059490
> df %>% map_dbl(median)
a b c d
0.20374050 -0.23739013 0.05839867 0.08679879
> df %>% map_dbl(sd)
a b c d
1.0889674 0.8172145 0.8357361 0.7092745
map_*()
和 col_summary()
之间有一些区别:
- 所有
purrr
函数都是用C
实现的。所以它们的速度更快一点,但牺牲了可读性 - 第二个参数是要应用的函数
.f
,它可以是公式、字符向量或整数向量 map_*()
使用...
来解析额外的参数并将其传递给函数.f
> map_dbl(df, mean, trim = 0.5)
a b c d
0.20374050 -0.23739013 0.05839867 0.08679879
map
函数还保留原来向量的名称
> z <- list(x = 1:3, y = 4:5)
> map_int(z, length)
x y
3 2
5.1 快捷键
有几个快捷方式可以与 .f
一起使用,以节省输入。
假设您希望将线性模型拟合到数据集中的每个组,下面的示例将 mtcars
数据集分割为三个部分(每个圆柱体的值一个),并对每个部分拟合相同的线性模型
models <- mtcars %>%
split(.$cyl) %>%
map(function(df) lm(mpg ~ wt, data = df))
这里 .
作为代词,表示当前列表元素
当我们想要提取一些汇总信息时,比如
R
2
R^2
R2。我们可以使用 summary()
获得汇总信息,然后提取出名为 r.squared
的组件。我们可以使用匿名函数的简写来完成
> models %>%
+ map(summary) %>%
+ map_dbl(~.$r.squared)
4 6 8
0.5086326 0.4645102 0.4229655
但是提取命名组件是一个常见的操作,因此 purrr
提供了一个更短的快捷方式:使用字符串。
> models %>%
+ map(summary) %>%
+ map_dbl("r.squared")
4 6 8
0.5086326 0.4645102 0.4229655
你也可以使用一个整数来按位置选择元素
> x <- list(list(1, 2, 3), list(4, 5, 6), list(7, 8, 9))
> x %>% map_dbl(2)
[1] 2 5 8
5.3 思考练习
- 使用
map
函数来实现
- 计算
mtcars
中每列的平均值 - 确定
nycflights13::flights
中每个列的类型 - 计算
iris
每一列中唯一值的数量 - 分别以
-10
、0
、10
和100
的平均值生成10
个正态随机数
-
在非
list
向量上使用 map 函数时会发生什么?map(1:5,runif)
有什么作用?为什么? -
map(-2:2, rnorm, n = 5)
有什么作用?为什么?map_dbl(-2:2, rmrm, n = 5)
有什么作用?为什么?
6. 处理错误
当您使用 map
函数来重复许多操作时,如果其中某一步出现错误,您将得到一条错误消息,并且没有输出。
这就很烦人了,为什么一次失败会阻碍你获得其他成功值? 你要如何确保一个坏苹果不会毁掉一整桶?
我们可以使用 safely
函数来处理这种情况,它接受一个函数并返回修改后的版本。
在这种情况下,修改后的函数将永远不会抛出错误,并且它总是返回一个包含两个元素的列表
result
: 结果,如果出现错误,返回NULL
error
:error
对象,如果操作成功,返回NULL
它与 try()
函数类似,但 try
有时返回原始结果,有时返回错误对象,所以处理起来比较困难
举个简单的例子来说明一下
> safe_log <- safely(log)
> str(safe_log(10))
List of 2
$ result: num 2.3
$ error : NULL
> str(safe_log("a"))
List of 2
$ result: NULL
$ error :List of 2
..$ message: chr "数学函数中用了非数值参数"
..$ call : language .Primitive("log")(x, base)
..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
函数执行成功,result
元素包含结果,error
元素为 NULL
。当函数执行失败时,result
元素为 NULL
,并且 error
元素包含错误对象
与 map
函数一起使用
> x <- list(1, 10, "a")
> y <- x %>% map(safely(log))
> str(y)
List of 3
$ :List of 2
..$ result: num 0
..$ error : NULL
$ :List of 2
..$ result: num 2.3
..$ error : NULL
$ :List of 2
..$ result: NULL
..$ error :List of 2
.. ..$ message: chr "数学函数中用了非数值参数"
.. ..$ call : language .Primitive("log")(x, base)
.. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
如果我们将其转换为包含两个元素的 list
:一个存储所有错误一个存储所有输出。会更容易使用
用 purrr::transpose()
可以很容易做到
> y <- y %>% transpose()
> str(y)
List of 2
$ result:List of 3
..$ : num 0
..$ : num 2.3
..$ : NULL
$ error :List of 3
..$ : NULL
..$ : NULL
..$ :List of 2
.. ..$ message: chr "数学函数中用了非数值参数"
.. ..$ call : language .Primitive("log")(x, base)
.. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
然后,来处理错误
> is_ok <- y$error %>% map_lgl(is_null)
> x[!is_ok]
[[1]]
[1] "a"
> y$result[is_ok] %>% flatten_dbl()
[1] 0.000000 2.302585
purrr
还提供了另外两个有用的函数:
- 类似
safely()
,possibly()
总是成功的。它比safely()
更简单,因为当出现错误时,您给它一个要返回的默认值
> x <- list(1, 10, "a")
> x %>% map_dbl(possibly(log, NA_real_))
[1] 0.000000 2.302585 NA
quietly()
与safety()
的作用类似,但不捕获错误,而是捕获打印的输出,消息和警告
> x <- list(1, -1)
> x %>% map(quietly(log)) %>% str()
List of 2
$ :List of 4
..$ result : num 0
..$ output : chr ""
..$ warnings: chr(0)
..$ messages: chr(0)
$ :List of 4
..$ result : num NaN
..$ output : chr ""
..$ warnings: chr "产生了NaNs"
..$ messages: chr(0)
7. 多参数 map
到目前为止,我们只对 map
传入了一个输入参数,但通常需要对多个相关的输入并行迭代。这就是 map2()
和 pmap()
函数的工作。
例如,假设您模拟一些不同均值的正态分布随机值。可以使用 map()
> mu <- list(5, 10, -3)
> mu %>%
+ map(rnorm, n = 5) %>%
+ str()
List of 3
$ : num [1:5] 4.58 4.8 4.08 5.72 3.21
$ : num [1:5] 8.58 11.45 10.65 9.48 10.81
$ : num [1:5] -5.02 -5.25 -2.65 -2.74 -2.84
如果您还想改变标准差怎么办?一种方法是遍历索引并传入对应索引的均值和方差。
> sigma <- list(1, 5, 10)
> seq_along(mu) %>%
+ map(~rnorm(5, mu[[.]], sigma[[.]])) %>%
+ str()
List of 3
$ : num [1:5] 5.48 3.9 4.28 5.46 4.8
$ : num [1:5] 7.913 9.208 9.664 -1.062 0.389
$ : num [1:5] 19.8 5.22 -19.1 -2.8 -6.54
但是这混淆了代码的意图,我们可以使用 map2()
来并行迭代两个向量
> map2(mu, sigma, rnorm, n = 5) %>% str()
List of 3
$ : num [1:5] 5.67 4.89 5.64 3.39 5.19
$ : num [1:5] 8.513 11.702 13.607 0.998 -0.381
$ : num [1:5] -19.4 -6.2 2.38 3.89 -16.89
map2()
生成以下一系列函数调用
注意:每次调用的参数都在函数之前。固定参数在函数之后
和 map
一样,map2
只是函数的包装
map2 <- function(x, y, f, ...) {
out <- vector("list", length(x))
for (i in seq_along(x)) {
out[[i]] <- f(x[[i]], y[[i]], ...)
}
out
}
你可能会想,是不是也有 map3
, map4
, map5
等函数呢?这种想法是不对的,像这种都会有一个可变参数列表来实现
purrr
提供了 pmap()
,它接受参数列表。例如,如果您想改变平均值、标准偏差和样本数量
> n <- list(1, 3, 5)
> args1 <- list(n, mu, sigma)
> args1 %>%
+ pmap(rnorm) %>%
+ str()
List of 3
$ : num 5.57
$ : num [1:3] 16.34 19.49 6.76
$ : num [1:5] 4.95 7.75 20 -18.65 4.66
会执行如下操作
[
如果不命名列表的元素,pmap()
将在调用函数时使用位置匹配。这容易出错,并且使代码更难阅读,所以最好命名参数
> args2 <- list(mean = mu, sd = sigma, n = n)
> args2 %>%
+ pmap(rnorm) %>%
+ str()
List of 3
$ : num 5.2
$ : num [1:3] 11.34 13.53 5.42
$ : num [1:5] 13.41 -6.24 -2.85 -12.55 -4.16
这样会产生更长但更安全的调用
因为参数的长度是一样的,所以可以将它们存储在数据框中
> params <- tribble(
+ ~mean, ~sd, ~n,
+ 5, 1, 1,
+ 10, 5, 3,
+ -3, 10, 5
+ )
> params %>%
+ pmap(rnorm)
[[1]]
[1] 3.567739
[[2]]
[1] 17.696317 17.825486 7.442771
[[3]]
[1] -8.619017 14.267712 -3.187630 -8.664932 15.264829
当代码变得非常复杂时,推荐使用数据框形式的参数,保证每列都有列名,且长度相同
7.1 调用不同的函数
进一步提高复杂性,除了更改函数的参数外,您还可以更改函数本身
f <- c("runif", "rnorm", "rpois")
param <- list(
list(min = -1, max = 1),
list(sd = 5),
list(lambda = 10)
)
这种情况下,可以使用 invoke_map()
函数
> invoke_map(f, param, n = 5) %>% str()
List of 3
$ : num [1:5] -0.8496 -0.1387 -0.0755 -0.2218 0.6228
$ : num [1:5] 3.19 -1.3 5.11 -7.96 -1.04
$ : int [1:5] 8 5 9 9 10
第一个参数是函数列表或函数名的字符向量。第二个参数是一个列表,给出了每个函数的不同参数。随后的参数会传递给每个函数
同样,您可以使用 tribble()
使创建这些配对变得更简单
sim <- tribble(
~f, ~params,
"runif", list(min = -1, max = 1),
"rnorm", list(sd = 5),
"rpois", list(lambda = 10)
)
sim %>%
mutate(sim = invoke_map(f, params, n = 10))
8. walk
walk
是 map
函数的一种替代方法,它不关心返回值,而是操作本身。
通常这样做是因为要将输出呈现到屏幕上或将文件保存到磁盘上,重要的是操作,而不是返回值。
> x <- list(1, "a", 3)
> x %>%
+ walk(print)
[1] 1
[1] "a"
[1] 3
通常相较于 walk2
和 pwalk
,walk
没那么有用。
例如,如果有绘图列表和文件名向量,可以使用 pwalk()
将每个文件保存到磁盘上的相应位置
library(ggplot2)
plots <- mtcars %>%
split(.$cyl) %>%
map(~ggplot(., aes(mpg, wt)) + geom_point())
paths <- stringr::str_c(names(plots), ".pdf")
pwalk(list(paths, plots), ggsave, path = tempdir())
9. for 循环的其他模式
purrr
提供了许多其他函数,这些函数抽象了其他类型的 for
循环。您使用它们的频率要低于 map
函数,但是了解它们很有用。
在这里我们简要说明每个函数,如果您将来看到类似的问题,希望您会想到它。然后你可以去查阅文档了解更多细节
9.1 谓词函数
许多函数返回单一的 TRUE
或 FLASE
keep()
和 discard()
分别保存谓词为真或假的输入元素
> iris %>%
+ keep(is.factor) %>%
+ str()
'data.frame': 150 obs. of 1 variable:
$ Species: Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
> iris %>%
+ discard(is.factor) %>%
+ str()
'data.frame': 150 obs. of 4 variables:
$ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
$ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
$ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
$ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
some()
和 every()
确定谓词是否对任何或所有元素都是正确的
> x <- list(1:5, letters, list(10))
>
> x %>%
+ some(is_character)
[1] TRUE
>
> x %>%
+ every(is_vector)
[1] TRUE
detect()
查找谓词为真的第一个元素;detect_index()
返回其位置
> x <- sample(10)
> x
[1] 4 3 8 10 7 2 1 9 6 5
> x %>%
+ detect(~ . > 5)
[1] 8
> x %>%
+ detect_index(~ . > 5)
[1] 3
head_while()
和 tail_while()
从向量的开头或结尾获取满足谓词为 true
时的元素,直到出现 FALSE
> x %>%
+ head_while(~ . > 5)
integer(0)
>
> x %>%
+ tail_while(~ . > 5)
integer(0)
9.2 reduce 和 accumulate
有时你想重复应该一个函数将一个复杂列表转化为一个值,可以使用 reduce
> dfs <- list(
+ age = tibble(name = "John", age = 30),
+ sex = tibble(name = c("John", "Mary"), sex = c("M", "F")),
+ trt = tibble(name = "Mary", treatment = "A")
+ )
>
> dfs %>% reduce(full_join)
Joining, by = "name"
Joining, by = "name"
# A tibble: 2 x 4
name age sex treatment
<chr> <dbl> <chr> <chr>
1 John 30 M NA
2 Mary NA F A
或者你有一列向量,想要找到它们的交集
> vs <- list(
+ c(1, 3, 5, 6, 10),
+ c(1, 2, 3, 7, 8, 10),
+ c(1, 2, 3, 4, 8, 9, 10)
+ )
>
> vs %>% reduce(intersect)
[1] 1 3 10
reduce
接受一个具有两个主要输入的函数,并将其重复引用在列表中的相邻元素,直到剩下一个元素
accumulate()
与此类似,但它保留所有中间结果,你可以用它来实现一个累加和
> (x <- sample(10))
[1] 7 1 3 10 6 5 8 4 9 2
> x %>% accumulate(`+`)
[1] 7 8 11 21 27 32 40 44 53 55
9.3 思考练习
-
使用
for
循环实现您自己的every()
版本。与purrr::every()
进行比较,你的版本缺了啥? -
创建一个增强的
col_summary()
,将汇总函数应用于数据框中的每个数字列 -
与
col_summary()
等价的基础R
函数
col_sum3 <- function(df, f) {
is_num <- sapply(df, is.numeric)
df_num <- df[, is_num]
sapply(df_num, f)
}
但是它有许多错误,对于如下输入
df <- tibble(
x = 1:3,
y = 3:1,
z = c("a", "b", "c")
)
# OK
col_sum3(df, mean)
# Has problems: don't always return numeric vector
col_sum3(df[1:2], mean)
col_sum3(df[1], mean)
col_sum3(df[0], mean)
是什么导致了错误
总结
可以在速查表中看到更多的函数及使用方法,以及函数功能的图形示例