小编典典

为什么使用 purrr::map 而不是 lapply?

all

有什么理由我应该使用

map(<list-like-object>, function(x) <do stuff>)

代替

lapply(<list-like-object>, function(x) <do stuff>)

输出应该是相同的,并且我所做的基准测试似乎表明它lapply稍微快一些(它应该是map评估所有非标准评估输入的需要)。

那么,对于这种简单的情况,我是否应该考虑切换到purrr::map. 我不是在这里询问一个人对语法、purrr
提供的其他功能等的好恶,而是严格地与使用标准评估的假设进行比较purrr::maplapplymap(<list-like-object>, function(x) <do stuff>).
purrr::map在性能、异常处理等方面有什么优势吗?下面的评论表明它没有,但也许有人可以详细说明一下?


阅读 63

收藏
2022-06-16

共1个答案

小编典典

如果您在 purrr 中使用的唯一功能是map(),那么不,优势并不显着。正如 Rich Pauloo
所指出的,它的主要优点map()是帮助程序,它允许您为常见的特殊情况编写紧凑的代码:

  • ~ . + 1相当于function(x) x + 1\(x) x + 1在 R-4.1 和更新版本中)

  • list("x", 1)相当于function(x) x[["x"]][[1]]。这些帮助器比[[- 更多信息请参阅?pluck。对于数据矩形,该 .default参数特别有用。

但是大多数时候你不是在使用单个*apply()/map() 函数,而是在使用一堆,而 purrr 的优点是函数之间的一致性更高。例如:

  • 第一个参数lapply()是数据;第一个参数 mapply()是函数。所有地图函数的第一个参数始终是数据。

  • 使用vapply(), sapply(),mapply()您可以选择使用USE.NAMES = FALSE;隐藏输出中的名称。但 lapply()没有那个论点。

  • 没有一致的方法可以将一致的参数传递给映射器函数。大多数函数使用...but mapply()uses MoreArgs(您希望被称为MORE.ARGS)和 Map(),Filter()Reduce()希望您创建一个新的匿名函数。在 map 函数中,常量参数总是在函数名之后。

  • 几乎每个 purrr 函数都是类型稳定的:您可以仅根据函数名称预测输出类型。sapply()对于or来说,这不是真的 mapply()。是的,有vapply();但mapply().

您可能认为所有这些细微的区别并不重要(就像有些人认为 stringr 与基本 R
正则表达式相比没有优势),但根据我的经验,它们在编程时会引起不必要的摩擦(不同的参数顺序总是会绊倒我起来了),而且它们使函数式编程技术更难学习,因为除了伟大的想法之外,你还必须学习一些附带的细节。

Purrr 还填充了一些基本 R 中没有的方便的地图变体:

  • modify()``[[<-保留用于“就地”修改的数据类型。结合_if变体,这允许(IMO 美丽的)代码,如modify_if(df, is.factor, as.character)

  • map2()允许您同时映射到xy。这使得表达想法变得更容易,比如 map2(models, datasets, predict)

  • imap()允许您同时映射x及其索引(名称或位置)。这使得(例如)加载 csv目录中的所有文件变得容易,为每个文件添加一filename列。

    dir("\\.csv$") %>%
    

    set_names() %>%
    map(read.csv) %>%
    imap(~ transform(.x, filename = .y))

  • walk()不可见地返回其输入;并且在您调用函数以获取其副作用(即,将文件写入磁盘)时很有用。

更不用说像safely()and之类的其他助手了partial()

就个人而言,我发现当我使用 purrr
时,我可以更轻松地编写函数式代码;它缩小了构思和实施之间的差距。但是您的里程可能会有所不同;除非确实对您有所帮助,否则无需使用 purrr。

微基准

是的,map()lapply(). 但是使用
map()or的成本lapply()取决于您所映射的内容,而不是执行循环的开销。下面的微基准表明,与每个元素map()相比的成本lapply()约为
40 ns,这似乎不太可能对大多数 R 代码产生重大影响。

library(purrr)
n <- 1e4
x <- 1:n
f <- function(x) NULL

mb <- microbenchmark::microbenchmark(
  lapply = lapply(x, f),
  map = map(x, f)
)
summary(mb, unit = "ns")$median / n
#> [1] 490.343 546.880
2022-06-16