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

时间:2017-07-14 10:45:37

标签: r purrr

我有什么理由使用

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

而不是

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

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

为什么有这么简单的情况我应该考虑切换到purrr::map?我不是在这里询问关于语法,purrr等提供的其他功能的喜欢或不喜欢,而是严格关于purrr::maplapply的比较,假设使用标准评估,即{ {1}}。 map(<list-like-object>, function(x) <do stuff>)在性能,异常处理等方面是否有任何优势?下面的评论表明它没有,但也许有人可以详细说明一点?

3 个答案:

答案 0 :(得分:197)

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

  • ~ . + 1相当于function(x) x + 1

  • list("x", 1)相当于function(x) x[["x"]][[1]]。这些 帮助者比[[更为通用 - 有关详细信息,请参阅?pluck。 对于data rectangling.default参数特别有帮助。

但大部分时间你都没有使用单*apply() / map() 功能,你正在使用它们,而purrr的优点是 功能之间的一致性更高。例如:

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

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

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

  • 几乎每个purrr函数都是类型稳定的:你可以预测 输出类型仅来自函数名称。事实并非如此 sapply()mapply()。是的,有vapply();但没有 相当于mapply()

您可能认为所有这些微小的区别并不重要 (就像有些人认为串起来没有优势一样 基本R正则表达式),但根据我的经验,他们造成不必要的 编程时的摩擦(不同的参数顺序总是习惯于 让我感到高兴,他们使函数式编程技术更难 学习因为以及伟大的想法,你还必须学习一堆 附带细节。

Purrr还填写了基础R中缺少的一些方便的地图变体:

  • modify()使用[[<-修改“in”来保留数据类型 放置“。与_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()partial()

就个人而言,我发现当我使用purrr时,我可以编写功能代码 摩擦力更小,更轻松;它缩小了之间的差距 思考一个想法并实施它。但你的里程可能会有所不同 没有必要使用purrr,除非它真的帮助你。

微基准

是的,map()略慢于lapply()。但使用成本 map()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

答案 1 :(得分:44)

This online purrr tutorial强调了方便性,在使用purrr时不必显式写出匿名函数,这与特定于类型的地图函数一起使其非常实用。

1。 purrr::map在语法上比lapply

更方便

提取列表的第二个元素

map(list, 2)  # and it's done like magic

作为@F。 Privé指出,与:

相同
map(list, function(x) x[[2]])

lapply

lapply(list, 2) # doesn't work

我们需要传递匿名函数

lapply(list, function(x) x[[2]])  # now it works

或@RichScriven指出,我们只需将[[作为参数传递给lapply

lapply(list, `[[`, 2)  # a bit more simple syntantically

在后台,purr将数字或字符向量作为参数,并将其用于子集。如果发现自己将函数应用于许多,使用lapply的列表,并且厌倦了定义自定义函数或编写匿名函数,那么便利是转向purrr的一个原因。

2。特定于类型的映射函数只需多行代码

  • map_chr()
  • map_lgl()
  • map_int()
  • map_dbl()
  • map_df() - 我最喜欢的,返回一个数据框。

这些特定于类型的映射函数中的每一个都返回一个原子列表,而不是map()lapply()自动返回的列表。如果您正在处理具有原子向量的嵌套列表,您可以使用这些特定于类型的映射函数直接提取向量,或将向量强制转换为int,dbl,chr向量。方便和功能的另一点。

3。除了方便,lapplymap快。

使用purrr的便利功能,如@F。 Privé指出减慢了处理速度。让我们对上面提到的4个案例中的每一个进行比赛。

# devtools::install_github("jennybc/repurrrsive")
library(repurrrsive)
library(purrr)
library(microbenchmark)
library(ggplot2)

mbm <- microbenchmark(
lapply       = lapply(got_chars[1:4], function(x) x[[2]]),
lapply_2     = lapply(got_chars[1:4], `[[`, 2),
map_shortcut = map(got_chars[1:4], 2),
map          = map(got_chars[1:4], function(x) x[[2]]),
times        = 100
)
autoplot(mbm)

enter image description here

获胜者是......

lapply(list, `[[`, 2)

总而言之,如果你正在追求速度: base::lapply

如果您遇到简单的语法: purrr::map

答案 2 :(得分:25)

如果我们不考虑品味的方面(否则这个问题应该被关闭)或语法一致性,风格等,答案是否定的,没有特别的理由使用map代替lapply或申请系列的其他变体,例如更严格的vapply

PS:对于那些无偿贬低的人,只记得OP写道:

  

我不是在这里询问有关语法的喜欢或不喜欢,   purrr等提供的其他功能,但严格来说   假设使用标准,purrr :: map与lapply的比较   评价

如果您不考虑purrr的语法或其他功能,那么使用map没有特别的理由。我自己使用purrr而且我对Hadley的回答很好,但具有讽刺意味的是,OP先前提到的并没有提出要求的事情。