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()
#创造一个辅助函数,如果为偶数则返回TRUE
is_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
## <simpleError in sqrt(x = x): non-numeric argument to mathematical function>
##
##
## [[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]]
## <simpleError in sqrt(x = x): non-numeric argument to mathematical function>
##
## $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]]
## <simpleError in sqrt(x = x): non-numeric argument to mathematical function>
##
## $`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)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。