Зачем использовать purrr:: map вместо lapply?

есть ли причина, по которой я должен использовать

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

вместо

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

результат должен быть таким же, и критерии, которые я сделал, похоже, показывают, что lapply немного быстрее (это должно быть как map необходимо оценить все нестандартные оценки ввода).

Итак, есть ли причина, по которой для таких простых случаев я должен фактически рассмотреть переход на purrr::map? Я не спрашиваю здесь о том, нравится или не нравится синтаксис, другое функциональные возможности, предоставляемые purrr и т. д., но строго о сравнении purrr::map С lapply предполагая использование стандартной оценки, т. е. map(<list-like-object>, function(x) <do stuff>). Есть ли какое-либо преимущество, что purrr::map в плане производительности, обработки исключений и т. д.? В комментариях предполагают, что это не так, но, возможно, кто-то мог бы пояснить немного подробнее?

3 ответов


Если единственная функция, которую вы используете из purrr-это map(), то нет, преимущества не существенны. Как богатый очков Pauloo, основной преимущество map() - это помощники, которые позволяют писать компактный код для общих особых случаев:

  • ~ . + 1 эквивалентно function(x) x + 1

  • list("x", 1) эквивалентно function(x) x[["x"]][[1]]. Эти помощники немного более общие, чем [[ - см. ?pluck для сведения. Для сведения rectangling, в С каждого.

    dir("\.csv$") %>%
      set_names() %>%
      map(read.csv) %>%
      imap(~ transform(.x, filename = .y))
    
  • walk() возвращает свой входной сигнал незримо; и полезно когда вы вызов функции для ее побочных эффектов (т. е. запись файлов в диск.)

не говоря уже о других помощниках, таких как safely() и partial().

лично я считаю, что когда я использую purrr, я могу написать функциональный код с меньше трением и большей легкостью; он уменьшает зазор между размышляющий до идеи и ее реализации. Но ваш пробег может варьироваться; нет необходимости использовать purrr, если это действительно не поможет вам.

Microbenchmarks

да map() немного медленнее, чем lapply(). Но стоимость использования map() или lapply() управляется тем, что вы отображаете, а не накладными расходами выполнения цикла. Microbenchmark ниже предполагает, что стоимость из 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

этот онлайн учебник purrr подчеркивает удобство не нужно явно выписывать анонимные функции при использовании purrr, что вместе с функциями карты конкретного типа делает его очень функциональным.

1. purrr:: карта синтаксически намного удобнее, чем lapply

извлечь второй элемент списка

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

что, как отметил @F. Privé, то же самое as:

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 и tire либо определения пользовательской функции, либо написания анонимной функции для подмножества, удобство является одной из причин перейти к мурлыканью.

2. Тип-специфические функции карты просто много строк кода

  • map_chr()
  • map_lgl()
  • map_int()
  • map_dbl()
  • map_df () - мой любимый, возвращает фрейм данных.

каждая из этих специфичных для типа функций карты возвращает атомарный список, а не список, который map() и lapply() автоматически вернуться. Если вы имеете дело с вложенными списками, которые имеют атомарные векторы внутри, вы можете использовать эти функции карты для конкретного типа, чтобы вытащить векторы напрямую или принудить векторы в векторы int, dbl, chr. Еще один момент для удобства и функциональности.

3. Удобство в сторону, lapply быстрее, чем карта.

используя функции удобства муррр, по мере того как @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 вместо lapply или другие варианты семейства apply, такие как stricter vapply.

PS: Для тех людей, которые безвозмездно downvoting, просто помните, что OP написал:

Я не спрашиваю здесь о том, нравится или не нравится синтаксис, другой функциональные возможности, предоставляемые purrr и т. д. но строго о сравнение purrr:: map с lapply, предполагающим использование стандарта оценка

если вы не считаете синтаксис или другие функциональные возможности purrr, нет никаких особых причин использовать map. Я использую purrr Я и я в порядке с ответом Хэдли, но он по иронии судьбы идет по тем самым вещам, которые ОП заявил заранее, он не спрашивал.