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 stworzyłem, wydają się wskazywać, że lapply
jest nieco szybszy (powinno być tak samo, jak map
trzeba, aby ocenić wszystkie niestandardowe dane wejściowe do oceny).
Czy jest więc jakiś powód, dla którego w tak prostych przypadkach powinienem rozważyć przejście na purrr::map
? Nie pytam tutaj o swoje upodobania lub antypatie dotyczące składni, innych funkcjonalności, które zapewnia mruczenie itp., A stricte o porównanie purrr::map
z lapply
założeniem, że stosuje się ocenę standardową, tj map(<list-like-object>, function(x) <do stuff>)
. Czy jest jakaś korzyść purrr::map
pod względem wydajności, obsługi wyjątków itp.? Poniższe komentarze sugerują, że tak nie jest, ale może ktoś mógłby rozwinąć trochę więcej?
tidyverse
, możesz skorzystać ze składni%>%
~ .x + 1
~{}
skrót lambda (z{}
pieczęciami lub bez, to dla mnie sprawa dla zwykłegopurrr::map()
. Egzekwowanie typówpurrr::map_…()
jest poręczne i mniej rozwlekłe niżvapply()
.purrr::map_df()
jest bardzo kosztowną funkcją, ale także upraszcza kod. Nie ma absolutnie nic złego w trzymaniu się bazy R[lsv]apply()
, chociaż ,purrr
rzeczy. Chodzi mi o to:tidyverse
jest fantastyczny do analiz / interaktywnych / raportów, a nie do programowania. Jeśli musisz używaćlapply
lubmap
programujesz i pewnego dnia możesz skończyć z utworzeniem pakietu. Im mniej zależności, tym lepiej. Plus: czasami widzę ludzi używającychmap
później dość niejasnej składni. A teraz, gdy widzę testy wydajności: jeśli jesteś przyzwyczajony doapply
rodziny: trzymaj się tego.Odpowiedzi:
Jeśli jedyną funkcją, której używasz w mruczeniu, jest
map()
, to nie, korzyści nie są znaczące. Jak podkreśla Rich Pauloo, główną zaletą programumap()
są pomocniki, które pozwalają pisać zwarty kod dla typowych, specjalnych przypadków:~ . + 1
jest równafunction(x) x + 1
list("x", 1)
jest równoważnefunction(x) x[["x"]][[1]]
. Te pomocniki są nieco bardziej ogólne niż[[
- zobacz?pluck
szczegóły. Dla rectangling danych The.default
argument jest szczególnie pomocne.Ale przez większość czasu nie używasz pojedynczej funkcji
*apply()
/map()
, używasz ich kilku, a zaletą mruczenia jest znacznie większa spójność między funkcjami. Na przykład:Pierwszym argumentem
lapply()
są dane; pierwszym argumentemmapply()
jest funkcja. Pierwszym argumentem wszystkich funkcji mapowania są zawsze dane.Z
vapply()
,sapply()
imapply()
można wybrać do tłumienia nazwisk na wyjściu zUSE.NAMES = FALSE
; alelapply()
nie ma tego argumentu.Nie ma spójnego sposobu przekazywania spójnych argumentów do funkcji mapowania. Większość funkcji korzystać
...
, alemapply()
zastosowaniaMoreArgs
(co można oczekiwać, aby nazwaćMORE.ARGS
), iMap()
,Filter()
iReduce()
oczekiwać, aby utworzyć nową funkcję anonimową. W funkcjach odwzorowujących stały argument zawsze występuje po nazwie funkcji.Prawie każda funkcja mruczenia jest stabilna: typ wyniku można przewidzieć wyłącznie na podstawie nazwy funkcji. Nie dotyczy to
sapply()
lubmapply()
. Tak, jestvapply()
; ale nie ma odpowiednika dlamapply()
.Możesz pomyśleć, że wszystkie te pomniejsze rozróżnienia nie są ważne (tak jak niektórzy myślą, że nie ma żadnej przewagi nad wyrażeniami regularnymi typu R), ale z mojego doświadczenia wynika, że powodują one niepotrzebne tarcia podczas programowania (różne porządki argumentów zawsze używane ja w górę) i utrudniają naukę technik programowania funkcjonalnego, ponieważ oprócz wielkich pomysłów trzeba też nauczyć się wielu przypadkowych szczegółów.
Mruczenie wypełnia również kilka przydatnych wariantów map, których nie ma w podstawowym R:
modify()
zachowuje typ danych przy użyciu[[<-
do modyfikacji „w miejscu”. W połączeniu z_if
wariantem pozwala to na (piękny IMO) kod taki jakmodify_if(df, is.factor, as.character)
map2()
pozwala na jednoczesne mapowanie nadx
iy
. Ułatwia to wyrażanie pomysłów, takich jakmap2(models, datasets, predict)
imap()
umożliwia jednoczesne mapowanie nadx
i jego indeksów (nazw lub pozycji). Ułatwia to (np.) Ładowanie wszystkichcsv
plików w katalogu, dodającfilename
do każdego kolumnę.walk()
zwraca swoje dane wejściowe niewidocznie; i jest przydatne, gdy wywołujesz funkcję ze względu na jej skutki uboczne (np. zapisywanie plików na dysku).Nie wspominając o innych pomocnikach, takich jak
safely()
ipartial()
.Osobiście uważam, że kiedy używam mruczenia, mogę pisać funkcjonalny kod z mniejszym tarciem i większą łatwością; zmniejsza dystans między wymyśleniem pomysłu a jego wdrożeniem. Ale Twój przebieg może się różnić; nie ma potrzeby używania mruczenia, chyba że faktycznie ci to pomaga.
Microbenchmarks
Tak,
map()
jest nieco wolniejszy niżlapply()
. Ale koszt używaniamap()
lublapply()
jest zależny od tego, co mapujesz, a nie od kosztów ogólnych związanych z wykonywaniem pętli. Poniższy mikropunkt sugeruje, że koszt wmap()
porównaniu dolapply()
wynosi około 40 ns na element, co wydaje się mało prawdopodobne, aby miało istotny wpływ na większość kodu R.źródło
mutate()
, chciałem tylko prostego przykładu bez innych deps.map_*
jest tym, co sprawiło, że ładowałem siępurrr
w wielu skryptach. Pomogło mi to z niektórymi aspektami „przepływu kontroli” w moim kodzie (stopifnot(is.data.frame(x))
).Porównywanie
purrr
ilapply
sprowadza się do wygody i szybkości .1.
purrr::map
jest wygodniejszy syntaktycznie niż lapplywyodrębnij drugi element listy
który jako @F. Privé wskazał, jest tym samym, co:
z
lapply
musimy przekazać anonimową funkcję ...
... lub jak wskazał @RichScriven, przekazujemy
[[
jako argument dolapply
Jeśli więc stosujesz funkcje do wielu list przy użyciu
lapply
i męczysz się definiowaniem funkcji niestandardowej lub pisaniem funkcji anonimowej, wygoda jest jednym z powodów do faworyzowaniapurrr
.2. Mapa specyficzna dla typu działa po prostu w wielu wierszach kodu
map_chr()
map_lgl()
map_int()
map_dbl()
map_df()
Każda z tych funkcji mapowania specyficznych dla typu zwraca wektor, a nie listy zwracane przez
map()
ilapply()
. Jeśli masz do czynienia z zagnieżdżonymi listami wektorów, możesz użyć tych funkcji mapowania specyficznych dla typu, aby bezpośrednio wyciągnąć wektory i przekształcić wektory bezpośrednio do wektorów int, dbl, chr. Wersja bazowa R będzie wyglądać podobnieas.numeric(sapply(...))
,as.character(sapply(...))
itpTe
map_<type>
funkcje mają również użyteczną jakość, jeśli nie mogą powrócić wektor atomowy wskazanego typu, one niepowodzeniem. Jest to przydatne podczas definiowania ścisłej kontroli przepływu, gdy chcesz, aby funkcja zakończyła się niepowodzeniem, jeśli [w jakiś sposób] generuje niewłaściwy typ obiektu.3. Pomijając wygodę,
lapply
jest [nieco] szybszy niżmap
Korzystanie
purrr
z wygodnych funkcji, takich jak @F. Privé zwrócił nieco uwagę na spowolnienie przetwarzania. Wyścig z każdym z 4 przypadków, które przedstawiłem powyżej.A zwycięzcą jest....
Podsumowując, jeśli szukasz surowej prędkości:
base::lapply
(chociaż nie jest to dużo szybsze)Aby uzyskać prostą składnię i wyrazistość:
purrr::map
Ten doskonały
purrr
samouczek podkreśla wygodę wynikającą z braku konieczności jawnego pisania anonimowych funkcji podczas używaniapurrr
oraz zaletymap
funkcji specyficznych dla typu .źródło
function(x) x[[2]]
zamiast just2
, będzie to mniej wolne. Cały ten dodatkowy czas wynika z kontroli, które sięlapply
nie sprawdzają .[[
jest funkcją. Możesz to zrobićlapply(list, "[[", 3)
.Jeśli nie weźmiemy pod uwagę aspektów gustu (w przeciwnym razie pytanie to powinno zostać zamknięte) lub spójności składni, stylu itp., Odpowiedź brzmi nie, nie ma specjalnego powodu, aby używać
map
zamiastlapply
lub innych wariantów rodziny stosowanej, takich jak bardziej rygorystycznevapply
.PS: Dla tych ludzi, którzy niesłusznie przegłosowali, pamiętajcie tylko, że OP napisał:
Jeśli nie bierzesz pod uwagę składni ani innych funkcji
purrr
, nie ma specjalnego powodu, aby używaćmap
. Używampurrr
siebie i nie przeszkadza mi odpowiedź Hadleya, ale ironicznie omawia ona te same rzeczy, które OP stwierdził z góry, o które nie pytał.źródło