Dlaczego warto używać purrr:: map zamiast lapply?
Czy Jest jakiś powód, dla którego powinienem używać
map(<list-like-object>, function(x) <do stuff>)
Zamiast
lapply(<list-like-object>, function(x) <do stuff>)
Wynik powinien być taki sam, a benchmarki, które zrobiłem, wydają się pokazywać, że lapply
jest nieco szybszy (powinien być tak, jak map
musi ocenić wszystkie niestandardowe dane wejściowe oceny).
Czy jest jakiś powód, dla którego w tak prostych przypadkach powinienem rozważyć przejście na purrr::map
? Nie pytam tutaj o upodobania lub niechęci do składni, innych funkcjonalności dostarczanych przez purrr itp., ale ściśle o porównaniu purrr::map
z lapply
zakładając użycie standardowej oceny, tj. map(<list-like-object>, function(x) <do stuff>)
. Czy jest jakaś przewaga purrr::map
pod względem wydajności, obsługi wyjątków itp.? Poniższe komentarze sugerują, że nie, ale może ktoś mógłby rozwinąć trochę więcej?
3 answers
Jeśli jedyną funkcją używaną przez purrr jest map()
, to nie,
korzyści nie są znaczące. Jak zaznacza Rich Pauloo, głównym
zaletą map()
są helpery, które pozwalają na pisanie kompaktowych
kod typowych przypadków szczególnych:
~ . + 1
jest odpowiednikiemfunction(x) x + 1
list("x", 1)
jest równoważnefunction(x) x[["x"]][[1]]
. Te pomocnicy są nieco bardziej ogólni niż[[
- Zobacz?pluck
po szczegóły. Dla danych rectangling , the.default
argument jest szczególnie pomocne.
Ale przez większość czasu nie używasz jednego *apply()
/ map()
funkcji, używasz kilku z nich, a zaletą purrr jest
znacznie większa spójność między funkcjami. Na przykład:
Pierwszym argumentem do
lapply()
są dane; pierwszym argumentem domapply()
jest funkcją. Pierwszy argument do wszystkich funkcji map jest zawsze dane.Z
vapply()
,sapply()
, imapply()
możesz wybrać wymazywanie nazw na wyjście zUSE.NAMES = FALSE
; alelapply()
nie ma takiego argumentu.Nie ma spójnego sposobu przekazywania spójnych argumentów na funkcja mapera. Większość funkcji używa
...
, alemapply()
używaMoreArgs
(którego można się spodziewać, że będzie się nazywaćMORE.ARGS
), orazMap()
,Filter()
iReduce()
oczekuję, że stworzysz nowy funkcja anonimowa. W funkcjach map zawsze pojawia się argument stały po nazwie funkcji.Prawie każda funkcja purrr jest typu stabilna: można przewidzieć na Typ wyjścia wyłącznie z nazwy funkcji. Nie jest to prawdą dla
sapply()
lubmapply()
. Tak, jestvapply()
; ale nie ma odpowiednik dlamapply()
.
Możesz myśleć, że wszystkie te drobne rozróżnienia nie są ważne (tak jak niektórzy myślą, że nie ma przewagi stringr nad bazowych wyrażeń regularnych R), ale z mojego doświadczenia wynika, że powodują niepotrzebne tarcia podczas programowania (odmienne rozkazy argumentów zawsze używane do trip me up), i sprawiają, że funkcjonalne techniki programowania trudniejsze do ucz się, ponieważ oprócz wielkich pomysłów, Musisz również nauczyć się kilku przypadkowych szczegółów.
Purrr wypełnia również kilka przydatnych wariantów map, których nie ma w bazie R:
modify()
zachowuje typ danych używając[[<-
do modyfikacji "in miejsce". W połączeniu z wariantem_if
pozwala to na (IMO piękny) Kod jakmodify_if(df, is.factor, as.character)
map2()
pozwala na jednoczesne mapowaniex
iy
. To sprawia, że łatwiej jest wyrażać idee takie jakmap2(models, datasets, predict)
-
imap()
pozwala na jednoczesne mapowaniex
i jego indeksów (nazwiska lub stanowiska). To ułatwia (np.) załadowanie wszystkichcsv
pliki w katalogu, dodając do każdego kolumnyfilename
.dir("\\.csv$") %>% set_names() %>% map(read.csv) %>% imap(~ transform(.x, filename = .y))
walk()
zwraca jego wejście niewidocznie; i jest przydatny, gdy jesteś wywołanie funkcji dla jej efektów ubocznych (np. zapisywanie plików do dysk).
Nie wspominając o innych pomocnikach jak safely()
i partial()
.
Osobiście uważam, że kiedy używam purrr, mogę napisać kod funkcjonalny z mniejszym tarciem i większą łatwością; zmniejsza lukę między wymyślanie pomysłu i wdrażanie go. Ale przebieg może się różnić; nie ma potrzeby, aby używać purrr, chyba że rzeczywiście pomaga.
Microbenchmarks
Tak, map()
jest nieco wolniejszy niż lapply()
. Ale koszt korzystania
map()
lub lapply()
jest napędzany przez to, co mapujesz, a nie nad głową
wykonywania pętli. Na microbenchmark poniżej sugeruje, że koszt
z map()
w porównaniu do lapply()
wynosi około 40 ns na pierwiastek, co
wydaje się mało prawdopodobne, aby miało to istotny wpływ na większość kodu 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-25 19:17:59
Ten internetowy samouczek purrr podkreśla wygodę braku konieczności jawnego wypisywania anonimowych funkcji podczas korzystania z purrr, co wraz z funkcjami map specyficznymi dla typu czyni go bardzo funkcjonalnym.
1. purrr:: mapa jest składniowo o wiele wygodniejsza niż lapply
Wyodrębnij drugi element listy
map(list, 2) # and it's done like magic
Który jak zauważył @F. Privé, jest taki sam jak:
map(list, function(x) x[[2]])
Z lapply
lapply(list, 2) # doesn't work
Musimy przekazać mu funkcję anonimową
lapply(list, function(x) x[[2]]) # now it works
Lub, jak zauważył @RichScriven, możemy po prostu przekazać [[
jako argument do lapply
lapply(list, `[[`, 2) # a bit more simple syntantically
W tle, purr przyjmuje wektor liczbowy lub znakowy jako argument i używa go jako funkcji podzbiorowej. Jeśli robisz wiele podzestawów list za pomocą lapply i męczysz się albo definiowaniem niestandardowej funkcji, albo pisaniem anonimowej funkcji dla subsetting, wygoda jest jednym z powodów, aby przejść do purrr.
2. Specyficzne dla typu funkcje mapy po prostu wiele linii kodu
- map_chr ()
- map_lgl ()
- map_int ()
- map_dbl ()
- map_df () - Moje ulubione, zwraca ramkę danych.
Każda z tych funkcji map specyficznych dla typu zwraca listę atomową, a nie listę, która map()
i lapply()
zwracają automatycznie. Jeśli masz do czynienia z listami zagnieżdżonymi, które mają wektory atomowe wewnątrz, możesz użyć tych specyficznych dla danego typu funkcji map do wyciągania wektorów bezpośrednio lub zmuszania wektorów do wektorów int, dbl, chr. Kolejny punkt dla wygody i funkcjonalności.
3. Pomijając wygodę, lapply jest szybszy niż Mapa.
Używanie funkcji purrr convenience, jak zauważył @F. Privé, spowalnia nieco przetwarzanie. Ścigajmy się z każdym z 4 przypadków, które przedstawiłem powyżej.
# 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)
A zwycięzcą jest....
lapply(list, `[[`, 2)
W sumie, jeśli prędkość jest tym, czego szukasz: base::lapply
If simple syntax is your jam: purrr:: map
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-13 01:18:11
Jeśli nie weźmiemy pod uwagę aspektów smaku (inaczej to pytanie powinno być zamknięte) lub spójności składni, stylu itp., odpowiedź brzmi Nie, Nie ma specjalnego powodu, aby używać map
zamiast lapply
lub innych wariantów rodziny zastosuj, takich jak bardziej rygorystyczne vapply
.
PS: do tych ludzi bezinteresownie upominających się, tylko pamiętajcie, że OP napisał:
Nie pytam tutaj o czyjeś upodobania lub niechęci do składni, inne funkcjonalności oferowane przez purrr itp., ale ściśle o porównanie mapy purrr:: z mapą lapply przy założeniu użycia standardu ocena
Jeśli nie bierzesz pod uwagę składni ani innych funkcjonalności purrr
, nie ma specjalnego powodu, aby używać map
. Sam używam purrr
i nie mam nic przeciwko odpowiedzi Hadleya, ale jak na ironię chodzi o to, co powiedział OP z góry, o co nie pytał.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-06 18:16:03