Навіщо використовувати purrr :: map замість lapply?


172

Чи є якась причина, чому я повинен використовувати

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, оскільки весь цей дрібницький матеріал є лише оболонкою поверх нього. У якийсь момент ця оболонка зламається.
Hong Ooi

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() функцію, ви використовуєте купу їх, і перевага purrr полягає в набагато більшій узгодженості між функціями. Наприклад:

  • Перший аргумент lapply()- це дані; Перший аргумент mapply()- це функція. Перший аргумент для всіх функцій карти - це завжди дані.

  • З vapply(), sapply()іmapply() ви можете вибрати для придушення імен на виході з USE.NAMES = FALSE; але lapply()не має такого аргументу.

  • Не існує послідовного способу передачі послідовних аргументів функції картографа. Більшість функцій використовують, ...але 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, якщо це насправді не допоможе вам.

Мікроблоки

Так, 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 () надає мені стовпчик символів із назви файлів, які я хочу. Чи є причина не використовувати його там?
лікарГ

2
Так, краще використовувати mutate(), я просто хотів простий приклад, без інших деп.
Хадлі

Чи не має специфіки типу відображатися десь у цій відповіді? map_*це те, що мене завантажувало purrrв багатьох сценаріях. Це допомогло мені з деякими аспектами «управління потоком» мого коду ( stopifnot(is.data.frame(x))).
о.

2
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(...)) і так далі

The map_<type> функції також мають корисне якість, якщо вони не можуть повернути атомний вектор зазначеного типу, вони зазнають невдачі. Це корисно при визначенні строгого потоку управління, коли ви хочете, щоб функція вийшла з ладу, якщо вона [якось] генерує неправильний тип об'єкта.

3. Зручність убік, lapply[трохи] швидше, ніжmap

Використання 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)

введіть тут опис зображення

А переможець -….

lapply(list, `[[`, 2)

Підсумовуючи, якщо швидкість сировини - це те, що ви шукаєте: base::lapply (хоча це не те, що набагато швидше)

Для простого синтаксису та виразності: purrr::map


Цей чудовий purrrпідручник підкреслює зручність відсутності явного виписування анонімних функцій під час використання purrrта переваги функцій, характерних для типу map.


2
Зауважте, що якщо ви використовуєте function(x) x[[2]]замість просто 2, це буде менш повільно. Весь цей додатковий час пов'язаний з перевірками, які lapplyне роблять.
F. Privé

17
Вам не потрібні "анонімні" функції. [[є функцією. Можна зробити lapply(list, "[[", 3).
Rich Scriven

@RichScriven, що має сенс. Це спрощує синтаксис для використання lapply над purrr.
Багатий Паулуо

37

Якщо ми не розглядаємо аспекти смаку (інакше це питання слід закрити) або послідовність синтаксису, стиль тощо, відповідь "ні", немає особливих причин використовувати mapзамість lapplyінших варіантів сімейства застосувань, наприклад, суворішого vapply.

PS: Тим людям, які безоплатно звертаються, просто пам’ятайте, що ОП писав:

Я не запитую тут про власні симпатії чи не подобається синтаксис, інші функціональні можливості, які надає purrr і т.д., але строго про порівняння purrr :: map зі скромним припущенням, використовуючи стандартне оцінювання

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.