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


173

Есть ли причина, почему я должен использовать

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с точки зрения производительности, обработки исключений и т. Д.? Комментарии ниже предполагают, что это не так, но, возможно, кто-то мог бы уточнить немного больше?


8
Для простых случаев использования лучше придерживаться базы R и избегать зависимостей. Если вы уже загрузить tidyverseхотя, вы можете извлечь выгоду из трубы %>%и анонимных функций ~ .x + 1синтаксиса
Aurèle

49
Это в значительной степени вопрос стиля. Вы должны знать, что делают базовые R-функции, потому что все эти мелочи - просто оболочка над ними. В какой-то момент эта оболочка сломается.
Хонг Оои

9
~{}сокращение лямбда (с или без {}печатей сделка для меня для простого purrr::map(). Приведение типов в действие purrr::map_…()удобны и менее тупы, чем vapply(). purrr::map_df()это супер дорогая функция, но она также упрощает код. Нет ничего плохого в том, чтобы придерживаться базы R [lsv]apply(), хотя .
hrbrmstr

4
Спасибо за вопрос - такие вещи я тоже смотрел. Я использую R уже более 10 лет, и определенно не использую и не буду использовать purrrвещи. Моя точка зрения заключается в следующем: tidyverseотлично подходит для анализа / интерактивных / отчетов, а не для программирования. Если вам нужно использовать lapplyили mapвы программируете, и однажды вы можете создать пакет. Тогда чем меньше зависимостей, тем лучше. Плюс: я иногда вижу людей, использующих mapдовольно туманный синтаксис после. И теперь, когда я вижу тестирование производительности: если вы привыкли к applyсемье: придерживайтесь этого.
Эрик Лекутр

4
Тим, ты написал: «Я не спрашиваю здесь о чьих-то симпатиях или антипатиях о синтаксисе, других функциях, предоставляемых purrr и т. Д., Но строго о сравнении purrr :: map с лакомым предположением, используя стандартную оценку», и ответ, который ты принял, - тот, который повторяет то, что ты сказал, ты не хотел, чтобы люди шли.
Карлос

Ответы:


232

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

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

  • list("x", 1)эквивалентно function(x) x[["x"]][[1]]. Эти помощники являются более общими, чем [[- см. ?pluckПодробности. Для rectangling данных , то .defaultаргумент особенно полезно.

Но большую часть времени вы не используете одну *apply()/ map() функцию, вы используете их несколько, и преимущество мурлыканья заключается в гораздо большей согласованности между функциями. Например:

  • Первый аргумент lapply()- это данные; Первый аргумент mapply()- это функция. Первым аргументом для всех функций карты всегда являются данные.

  • С помощью vapply(), sapply()и mapply()вы можете подавить имена на выходе с помощью USE.NAMES = FALSE; но lapply()не имеет этого аргумента.

  • Не существует единого способа передачи непротиворечивых аргументов в функцию mapper. Большинство функций используют, ...но mapply()используют MoreArgs(которые вы ожидаете вызвать MORE.ARGS) Map(), Filter()и Reduce()ожидаете , что вы создадите новую анонимную функцию. В функциях карты константный аргумент всегда идет после имени функции.

  • Почти каждая функция purrr является стабильной по типу: вы можете предсказать тип вывода исключительно из имени функции. Это не верно для sapply()или mapply(). Да, есть vapply(); но нет эквивалента для mapply().

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

Purrr также заполняет некоторые удобные варианты карт, которые отсутствуют в базе R:

  • modify()сохраняет тип данных, используемых [[<-для изменения «на месте». В сочетании с _ifвариантом это позволяет (красивый IMO) код, такой какmodify_if(df, is.factor, as.character)

  • map2()позволяет отображать одновременно поверх xи y. Это облегчает выражение идей, таких как 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, если это не поможет вам.

Microbenchmarks

Да, map()немного медленнее, чем 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

2
Вы имели в виду использовать transform () в этом примере? Как в базе R transform (), или я что-то упустил? transform () дает вам имя файла как фактор, который генерирует предупреждения, когда вы (естественно) хотите связать строки вместе. mutate () дает мне символьный столбец имен файлов, которые я хочу. Есть ли причина не использовать его там?
doctorG

2
Да, лучше использовать mutate(), я просто хотел простой пример без других deps.
хэдли

Разве специфичность типа не должна отображаться где-то в этом ответе? map_*это то, что заставило меня загрузиться purrrво многих сценариях. Это помогло мне с некоторыми аспектами «потока управления» моего кода ( stopifnot(is.data.frame(x))).
о.

3
ggplot и data.table великолепны, но нужен ли нам новый пакет для каждой функции в R?
ADN BPS

58

Сравнение purrrи lapplyсводится к удобству и скорости .


1. purrr::mapсинтаксически удобнее, чем лапы

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

map(list, 2)  

который как @F. Приве указал, так же, как:

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

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

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

  • map_chr()
  • map_lgl()
  • map_int()
  • map_dbl()
  • map_df()

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

Эти map_<type>функции также имеют полезное качество, если они не могут вернуть атомный вектор указанного типа, они терпят неудачу. Это полезно при определении строгого потока управления, когда вы хотите, чтобы функция не работала, если она [каким-то образом] генерирует неправильный тип объекта.

3. Удобство в стороне, lapply[немного] быстрее, чемmap

Используя purrrудобные функции, как @F. Приве отметил, что немного замедляет обработку. Давайте рассмотрим каждый из четырех случаев, которые я представил выше.

# 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


Этот превосходный purrrучебник подчеркивает удобство отсутствия необходимости явной записи анонимных функций при использовании purrrи преимущества mapфункций, специфичных для типа .


2
Обратите внимание, что если вы используете function(x) x[[2]]вместо просто 2, это будет менее медленно. Все это дополнительное время связано с проверками, которые lapplyне делают.
Ф.

17
Вам не «нужны» анонимные функции. [[это функция. Вы можете сделать lapply(list, "[[", 3).
Rich

@RichScriven это имеет смысл. Это упрощает синтаксис для использования lapply over purrr.
Рич Пауло

37

Если мы не рассматриваем аспекты вкуса (в противном случае этот вопрос должен быть закрыт) или согласованности синтаксиса, стиля и т. Д., Ответ - нет, особой причины для использования нет. map вместо lapplyили других вариантов семейства применений, таких как более строгие vapply.

PS: Для тех людей, безвозмездно понижающих голосование, просто помните, что ОП писал:

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.