博士tl;
我并不是在询问人们对语法或purrr提供的其他功能的喜欢或不喜欢。
选择与您的用例相匹配的工具,并最大限度地提高您的生产力。对于优先考虑速度的生产代码,使用*apply,对于需要小内存占用的代码,使用map。基于人体工程学,地图可能更适合大多数用户和大多数一次性任务。
方便
2021年10月更新
因为公认的答案和投票第二多的帖子都提到了语法的便利性:
R版本4.1.1及更高版本现在支持速记匿名函数\(x)和管道|>语法。要检查R版本,请使用version[['version.string']]]。
library(purrr)
library(repurrrsive)
lapply(got_chars[1:2], `[[`, 2) |>
lapply(\(.) . + 1)
#> [[1]]
#> [1] 1023
#>
#> [[2]]
#> [1] 1053
map(got_chars[1:2], 2) %>%
map(~ . + 1)
#> [[1]]
#> [1] 1023
#>
#> [[2]]
#> [1] 1053
如果您的任务涉及到对列表类对象的两次以上操作,则purrr方法的语法通常较短。
nchar(
"lapply(x, fun, y) |>
lapply(\\(.) . + 1)")
#> [1] 45
nchar(
"library(purrr)
map(x, fun) %>%
map(~ . + 1)")
#> [1] 45
考虑到一个人在其职业生涯中可能写了数万或数十万次这样的调用,这种语法长度差异可能相当于写了1或2本小说(例如,小说80000封信)。进一步考虑您的代码输入速度(每分钟约65个单词?),您的输入准确性(您是否发现您经常键入某些语法错误(\"< ?),您对函数参数的回忆,然后您可以对使用一种风格或两种风格的组合的效率进行公平的比较。
另一个考虑因素可能是你的目标受众。就我个人而言,我发现解释purrr::map如何比lapply更难,正是因为它简洁的语法。
1 |>
lapply(\(.z) .z + 1)
#> [[1]]
#> [1] 2
1 %>%
map(~ .z+ 1)
#> Error in .f(.x[[i]], ...) : object '.z' not found
but,
1 %>%
map(~ .+ 1)
#> [[1]]
#> [1] 2
速度
通常在处理类列表对象时,会执行多个操作。讨论的一个细微差别是,在大多数代码中,purrr的开销是微不足道的——处理大型列表和用例。
got_large <- rep(got_chars, 1e4) # 300 000 elements, 1.3 GB in memory
bench::mark(
base = {
lapply(got_large, `[[`, 2) |>
lapply(\(.) . * 1e5) |>
lapply(\(.) . / 1e5) |>
lapply(\(.) as.character(.))
},
purrr = {
map(got_large, 2) %>%
map(~ . * 1e5) %>%
map(~ . / 1e5) %>%
map(~ as.character(.))
}, iterations = 100,
)[c(1, 3, 4, 5, 7, 8, 9)]
# A tibble: 2 x 7
expression median `itr/sec` mem_alloc n_itr n_gc total_time
<bch:expr> <bch:tm> <dbl> <bch:byt> <int> <dbl> <bch:tm>
1 base 1.19s 0.807 9.17MB 100 301 2.06m
2 purrr 2.67s 0.363 9.15MB 100 919 4.59m
执行的动作越多,就越不一致。如果您正在编写一些用户经常使用的代码或依赖于它的包,那么速度可能是在基本R和purr之间进行选择时需要考虑的重要因素。注意,purrr的内存占用略低。
然而,有一种反对意见:如果你想要速度,就去使用较低级别的语言。