当前位置: 首页 > 工具软件 > Purrr > 使用案例 >

purrr鲜为人知的技巧

后安民
2023-12-01

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)
 类似资料: