21

purrr 进阶

高级映射与并行迭代

Tidyverse 篇
R
library(tidyverse)

相关章节, 我们学习了如何使用map()函数迭代一个向量(或列表),并对其元素施以函数。

事实上,purrr()家族还有其它map()函数,可以在多个向量中迭代。也就说,同时接受多个向量的元素,并行计算。比如,map2()函数可以处理两个向量,而pmap()函数可以处理更多向量。

map2()

map2()函数和map()函数类似,不同在于map2()接受两个的向量,这两个向量必须是等长

图片

map()函数使用匿名函数,可以用 . 代表输入向量的每个元素。在map2()函数, .不够用,所有需要需要用 .x 代表第一个向量的元素,.y代表第二个向量的元素

R
x <- c(1, 2, 3)
y <- c(4, 5, 6)

map2(x, y, ~ .x + .y)

tibble的每一列都是向量,所以可以把map2()放在mutate()函数内部,对tibble的多列同时迭代

R
df <-
  tibble(
    a = c(1, 2, 3),
    b = c(4, 5, 6)
  )

df %>% 
  mutate(min = map2_dbl(a, b, ~min(.x, .y)))

也可以简写

R
df %>% 
  mutate(min = map2_dbl(a, b, min))

注意到,mutate()column-operation,即提取数据框一列作为向量,传递到mutate中,map2_dbl()返回的也是一个等长的向量。当然,我们可以用相关章节的row-wise的方法,实现相同功能。

R
df %>% 
  rowwise() %>% 
  mutate(min = min(a, b)) %>% 
  ungroup()

pmap()

没有map3()或者map4()函数,只有 pmap() 函数可用(p 的意思是 parallel)

pmap()函数稍微有点不一样的地方:

  • map()map2()函数,指定传递给函数f的向量,向量各自放在各自的位置上
  • pmap()需要将传递给函数的向量名,先装入一个list()中, 再传递给函数f
图片

翻转列表的图示,参数的传递关系看地更清楚。

图片

事实上,map2()pmap()的一种特殊情况

R
map2_dbl(x, y, min)
pmap_dbl(list(x, y), min)

用在tibble

tibble本质上就是list,这种结构就是pmap()所需要的,因此,直接应用函数即可。

R
tibble(
  a = c(50, 60, 70),
  b = c(10, 90, 40),
  c = c(1, 105, 200)
) %>% 
  pmap_dbl(min)

以下是两个非常优秀的图示,方便大家记忆

图片
图片

匿名函数

pmap()可以接受多个向量,因此在pmap()种使用匿名函数,就需要一种新的方法来标识每个向量。

由于向量是多个,因此不再用.x.y,而是用..1, ..2, ..3 分别代表第一个向量、第二个向量和第三个向量。

R
pmap(
  list(1:5, 5:1, 2), ~ ..1 + ..2 - ..3
  )

命名函数

R
params <- tibble::tribble(
  ~ n, ~ min, ~ max,
   1L,     0,     1,
   2L,    10,   100,
   3L,   100,  1000
)

pmap(params, ~runif(n = ..1, min = ..2, max = ..3))

如果提供给pmap().f 是命名函数,比如runif(n, min = , max = ),它有三个参数 n, min, max, 而我们输入的列表刚好也有三个同名的元素,那么他们会自动匹配,代码因此变得更加简练

R
pmap(params, runif)

当然,这里需要注意的是

  • 输入列表的元素,其个数要与函数的参数个数一致
  • 输入列表的元素,其变量名也要与函数的参数名一致

其他purrr函数

Map functions that output tibbles

接着介绍purrr宏包的其他函数。 map()家族除了返回list和atomic vector 外,map_df(), map_dfr()map_dfc()还可以返回tibble。

这个过程,好比生产线上的工人把输入的列表元素依次转换成一个个tibble,

图片

最后归集一个大的tibble。在归集成一个大的tibble的时候,有两种方式,

  • 竖着堆积,map_dfr() (r for rows)
图片
  • 并排堆放 map_dfc() (c for columns)
图片

Walk and friends

walk()函数与map()系列函数类似,但应用场景不同,map()在于执行函数操作,而walk() 保存记录数据(比如print(),write.csv(), ggsave()),常用于保存数据和生成图片。比如我们用map()生成系列图片,

R
plot_rnorm <- function(sd) {
  tibble(x = rnorm(n = 5000, mean = 0, sd = sd)) %>% 
    ggplot(aes(x)) +
    geom_histogram(bins = 40) +
    geom_vline(xintercept = 0, color = "blue")
}

plots <-
  c(5, 1, 9) %>% 
  map(plot_rnorm)

然后我们用walk()函数依次打印出来

R
plots %>% 
  walk(print)

map()函数是一定要返回列表的,但walk()看上去函数没有返回值,实际上它返回的就是它的输入,只是用户不可见而已。

图片

这样的设计很有用,尤其在管道操作中,我们可以统计中,用walk()保存中间计算的结果或者生成图片,然后若无其事地继续管道(因为walk()返回值,就是输入walk的值),保持计算的连贯。

参考

  • <https://adv-r.hadley.nz/functionals.html>
  • <https://dcl-prog.stanford.edu/purrr-extras.html>
R
pacman::p_unload(pacman::p_loaded(), character.only = TRUE)