为什么使用purrr :: map而不是lapply?
我有什么理由要使用
map(<list-like-object>, function(x) <do stuff>)
代替
lapply(<list-like-object>, function(x) <do stuff>)
输出结果应该是相同的,我所做的基准似乎表明, lapply
稍微快一些(应该是map
需要评估所有的非标准评估input)。
那么有什么理由为什么这样简单的情况下,我应该考虑切换到purrr::map
? 我不是在这里问一个关于语法的提问,其他function是由咕噜咕噜等提供的,但是严格地将purrr::map
与lapply
假设为使用标准评估,即map(<list-like-object>, function(x) <do stuff>)
。 purrr::map
在性能,exception处理等方面有什么优势? 下面的评论暗示它不是,但也许有人可以详细阐述一点点?
如果你使用的唯一函数是map()
,那么不是,优点不是很大。 正如Rich Pauloo所指出的那样, map()
的主要优点是可以为常见的特殊情况编写紧凑的代码:
-
~ . + 1
~ . + 1
等价于function(x) x + 1
-
list("x", 1)
等价于function(x) x[["x"]][[1]]
。 这些帮助者比[[
– 请参阅?pluck
细节。 对于数据矩形 ,.default
参数特别有用。
但是大多数时候你并没有使用一个*apply()
/ map()
函数,你正在使用它们,而且purrr的优点是函数之间的一致性更好。 例如:
-
lapply()
的第一个参数是数据;mapply()
的第一个参数是函数。 所有地图function的第一个参数始终是数据。 -
使用
vapply()
,sapply()
和mapply()
您可以select使用USE.NAMES = FALSE
来取消输出中的名称。 但是lapply()
没有这个参数。 -
没有一致的方法将一致的parameter passing给mapper函数。 大多数函数使用
...
但是mapply()
使用MoreArgs
(你期望被称为MORE.ARGS
),Map()
,Filter()
和Reduce()
期望你创build一个新的匿名函数。 在map函数中,常量参数总是在函数名后面。 -
几乎每个purrr函数都是types稳定的:您可以仅从函数名称预测输出types。 对于
sapply()
或者是mapply()
这是不正确的。 是的,有vapply()
; 但是没有相应的mapply()
。
你可能会认为所有这些微小的区别并不重要(就像有些人认为基本R正则expression式上的stringr没有优势),但根据我的经验,在编程时它们会造成不必要的摩擦(不同的参数命令总是用于跳转我),他们使函数式编程技术更难以学习,因为除了大的想法,你还必须学习一些事情的细节。
Purrr还填充了一些基本R中不存在的便捷地图变体:
-
modify()
使用[[<-
修改“就地”来保存数据的types。 与_if
变体一起,这允许(IMO漂亮)代码像modify_if(df, is.factor, as.character)
-
map2()
允许你同时在x
和y
上映射。 这使得更容易expression像map2(models, datasets, predict)
-
imap()
允许您同时映射x
及其索引(名称或位置)。 这样可以很容易(例如)加载一个目录中的所有csv
文件,为每个filename
添加一个filename
列。dir("\\.csv$") %>% set_names() %>% map(read.csv) %>% imap(~ transform(.x, filename = .y))
-
walk()
不可见地返回其input; 当你为副作用调用一个函数时(例如,把文件写入磁盘),它是很有用的。
更不用说像safely()
和partial()
这样的其他助手了。
就我个人而言,我发现当我使用呜声时,我可以用更less的摩擦和更轻松的方式编写function代码。 它缩小了思考和实施的差距。 但你的里程可能会有所不同 没有必要使用呜呼,除非它实际上帮助你。
微基准testing
是的, map()
比lapply()
稍慢。 但是使用map()
或lapply()
的代价是由你映射的东西驱动的,而不是执行循环的开销。 下面的lapply()
表明map()
与lapply()
map()
相比的lapply()
是每个元素约40纳秒,这似乎不太可能实质上影响大多数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
这个在线箴言教程强调了在使用呜呜声时不必明确写出匿名函数的便利,它与types特定的地图函数一起使得它非常实用。
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.特定types的地图function只需许多行代码
- map_chr()
- map_lgl()
- map_int()
- map_dbl()
- map_df() – 我的最爱,返回一个数据框。
这些特定于types的映射函数都返回一个primefaces列表,而不是map()
和lapply()
自动返回的列表。 如果您正在处理内嵌有primefaces向量的嵌套列表,则可以使用这些特定于types的映射函数直接拉出向量,或者将向量强制转换为int,dbl,chr向量。 另一点便利和function。
除了便利之外,似乎乐声比地图更快。
使用便利function,如@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)
最终获胜者是….
lapply(list, `[[`, 2)
总之,如果速度是你所追求的: base :: lapply
如果简单的语法是你的果酱: purrr :: map
如果我们不考虑口味(否则应该closures这个问题)或语法一致性,风格等方面,答案是否定的,没有什么特别的理由来使用map
而不是lapply
或其他应用系列的变体,比如更严格vapply
。
PS:对那些无理压低的人,只记得OP写道:
我不是在这里问一个关于语法,咕噜声等提供的其他function的喜好或不喜欢,而是严格地将purrr :: map和lapply的比较假设为使用标准评估
如果你不考虑语法和其他function,使用map
没有特别的理由。 我自己使用purrr
,哈德利的回答我很好,但是讽刺的是,这个讽刺的是,OP没有提出任何问题。