purrr
是一个拓展R函数式编程能力的包。它会涉及到很多东西,在这篇文章中,我会展示在purrr
中最重要的(至少对我来说)几个函数。
用map
函数来摆脱循环
library(purrr)numbers <- list(11, 12, 13, 14)map_dbl(numbers, sqrt)
## [1] 3.316625 3.464102 3.605551 3.741657
你可能想知道为什么这可能比for循环更受欢迎?因为它更简洁,你不需要初始化任何类型的结构来保存结果。如果用google “create empty list in R”,你会发现它很普遍。然而,有了map
函数族,将不需要初始化结构。map_dbl
函数会返回一个实数原子列表(atomic list),map
函数会返回一个列表,去试一下吧。
在某些list上使用map
map_if()
#创造一个辅助函数,如果为偶数则返回TRUEis_even <- function(x){ !as.logical(x %% 2)}map_if(numbers, is_even, sqrt)
## [[1]]## [1] 11## ## [[2]]## [1] 3.464102## ## [[3]]## [1] 13## ## [[4]]## [1] 3.741657
map_at()
map_at(numbers, c(1,3), sqrt)
## [[1]]## [1] 3.316625## ## [[2]]## [1] 12## ## [[3]]## [1] 3.605551## ## [[4]]## [1] 14
map_if()
和 map_at()
比map
拥有更多的参数;如果是map_if()
,则是用一个判断函数(一个返回TRUE
或者FALSE
的函数),map_at
则是用一个位置向量。这样只有在满足某一条件时才会映射你的函数,这也是很多人google寻找的东西。
在多个参数中映射一个函数
numbers2 <- list(1, 2, 3, 4)map2(numbers, numbers2, `+`)
## [[1]]## [1] 12## ## [[2]]## [1] 14## ## [[3]]## [1] 16## ## [[4]]## [1] 18
map_2
函数可以输入2个参数来
你可以使用map_2
函数将两个列表映射到一个函数,你甚至可以使用pmap()
函数将任意数量的列表映射到任何函数。
如果出现问题,不要停止执行你的函数
possible_sqrt <- possibly(sqrt, otherwise = NA_real_)numbers_with_error <- list(1, 2, 3, "spam", 4)map(numbers_with_error, possible_sqrt)
## [[1]]## [1] 1## ## [[2]]## [1] 1.414214## ## [[3]]## [1] 1.732051## ## [[4]]## [1] NA## ## [[5]]## [1] 2
另一个很常见的问题:即使报错,也要继续执行你的循环。在大多数情况下,循环会在错误处停止,但是你想让他继续跑下去,看看哪里出错了。去google “skip error in a loop” 你会发现有很多人也想这样做。其实只要结合map()
函数与possibly()
函数就可以了。大多数解决方案可以会说使用tryCatch
函数,但我个人感觉它不太好用。
如果出现错误,不要停止执行函数并捕获错误
safe_sqrt <- safely(sqrt, otherwise = NA_real_)map(numbers_with_error, safe_sqrt)
## [[1]]## [[1]]$result## [1] 1## ## [[1]]$error## NULL## ## ## [[2]]## [[2]]$result## [1] 1.414214## ## [[2]]$error## NULL## ## ## [[3]]## [[3]]$result## [1] 1.732051## ## [[3]]$error## NULL## ## ## [[4]]## [[4]]$result## [1] NA## ## [[4]]$error#### ## ## [[5]]## [[5]]$result## [1] 2## ## [[5]]$error## NULL
safely
函数与possibly
函数很相似,但是它会在列表中返回列表。因此元素是结果和伴随错误消息的列表。如果没有错误,则返回NULL
。如果有错误,则返回错误信息。
转置一个列表
safe_result_list <- map(numbers_with_error, safe_sqrt)transpose(safe_result_list)
## $result## $result[[1]]## [1] 1## ## $result[[2]]## [1] 1.414214## ## $result[[3]]## [1] 1.732051## ## $result[[4]]## [1] NA## ## $result[[5]]## [1] 2## ## ## $error## $error[[1]]## NULL## ## $error[[2]]## NULL## ## $error[[3]]## NULL## ## $error[[4]]#### ## $error[[5]]## NULL
这里我们转置了一个列表。这意味着我们仍然返回列表中的列表,但是第一个列表里面全是results
,可通过safe_result_list$result
得到;第二个列表中全是errors
,可以通过 safe_result_list$error
得到,这是很有用的。
将函数应用到列表的更底层
transposed_list <- transpose(safe_result_list)transposed_list %>% at_depth(2, is_null)
## Warning: at_depth() is deprecated, please use `modify_depth()` instead
## $result## $result[[1]]## [1] FALSE## ## $result[[2]]## [1] FALSE## ## $result[[3]]## [1] FALSE## ## $result[[4]]## [1] FALSE## ## $result[[5]]## [1] FALSE## ## ## $error## $error[[1]]## [1] TRUE## ## $error[[2]]## [1] TRUE## ## $error[[3]]## [1] TRUE## ## $error[[4]]## [1] FALSE## ## $error[[5]]## [1] TRUE
有时候处理列表嵌套列表的数据会很棘手,特别是当我们想在子列表中应用一个函数时。但是使用at_depth()
函数将会变得很简单。
对列表元素进行命名
name_element <- c("sqrt()", "ok?")set_names(transposed_list, name_element)
## $`sqrt()`## $`sqrt()`[[1]]## [1] 1## ## $`sqrt()`[[2]]## [1] 1.414214## ## $`sqrt()`[[3]]## [1] 1.732051## ## $`sqrt()`[[4]]## [1] NA## ## $`sqrt()`[[5]]## [1] 2## ## ## $`ok?`## $`ok?`[[1]]## NULL## ## $`ok?`[[2]]## NULL## ## $`ok?`[[3]]## NULL## ## $`ok?`[[4]]#### ## $`ok?`[[5]]## NULL
对列表进行reduce操作,使之变成一个值
reduce(numbers, `*`)
## [1] 24024
下面是 accumulate()
函数:
accumulate(numbers, `*`)
## [1] 11 132 1716 24024
它会保存中间结果。
若用accumulate_right()
则为 从右向左 这个函数非常常用,你可以reduce
任何东西:
矩阵:
mat1 <- matrix(rnorm(10), nrow = 2)mat2 <- matrix(rnorm(10), nrow = 2)mat3 <- matrix(rnorm(10), nrow = 2)
list_mat <- list(mat1, mat2, mat3)reduce(list_mat, `+`)#结果等同于mat1+mat2+mat3
## [,1] [,2] [,3] [,4] [,5]## [1,] -2.48530177 1.0110049 0.4450388 1.280802 1.3413979## [2,] 0.07596679 -0.6872268 -0.6579242 1.615237 0.8231933
甚至数据框:
df1 <- as.data.frame(mat1)df2 <- as.data.frame(mat2)df3 <- as.data.frame(mat3)list_df <- list(df1, df2, df3)reduce(list_df, dplyr::full_join)
## Joining, by = c("V1", "V2", "V3", "V4", "V5")## Joining, by = c("V1", "V2", "V3", "V4", "V5")## V1 V2 V3 V4 V5## 1 -0.6264538 -0.8356286 0.32950777 0.48742905 0.5757814## 2 0.1836433 1.5952808 -0.82046838 0.73832471 -0.3053884## 3 -0.8969145 1.5878453 -0.08025176 0.70795473 1.9844739## 4 0.1848492 -1.1303757 0.13242028 -0.23969802 -0.1387870## 5 -0.9619334 0.2587882 0.19578283 0.08541773 -1.2188574## 6 -0.2925257 -1.1521319 0.03012394 1.11661021 1.2673687
希望你能喜欢这些有用的列变函数。
解释下map,reduce
举例说明,比如我们有一个函数$f(x)=x^2$,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]
上,就可以用map()
实现如下:
举例说明,比如我们有一个函数f(x)=x^2,要把这个函数作用在一个list(1, 2, 3, 4, 5, 6, 7, 8, 9)上,就可以用map()实现如下: map(list(1:9),function(x)x^2) f(x) = x * x │ │ ┌───┬───┬───┬───┼───┬───┬───┬───┐ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼[ 1 2 3 4 5 6 7 8 9 ] │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼[ 1 4 9 16 25 36 49 64 81 ]
reduce(list(x1, x2, x3, x4),f) = f(f(f(x1, x2), x3), x4)