... pod względem czasu wykonywania i / lub pamięci.
Jeśli tak nie jest, udowodnij to za pomocą fragmentu kodu. Zauważ, że przyspieszenie przez wektoryzację się nie liczy. Przyśpieszenie musi pochodzić z apply
( tapply
, sapply
...) sama.
Te apply
funkcje R nie zapewniają większą wydajność w porównaniu z innymi typami pętli funkcji (na przykład for
). Jedynym wyjątkiem jest sytuacja, lapply
która może być nieco szybsza, ponieważ wykonuje więcej pracy w kodzie C niż w języku R (zobacz to pytanie jako przykład ).
Generalnie jednak zasada jest taka, że należy używać funkcji zastosuj w celu zwiększenia przejrzystości, a nie wydajności .
Dodałbym do tego, że zastosowane funkcje nie mają skutków ubocznych , co jest ważnym rozróżnieniem, jeśli chodzi o programowanie funkcjonalne w R. Można to zmienić za pomocą assign
lub <<-
, ale może to być bardzo niebezpieczne. Efekty uboczne również utrudniają zrozumienie programu, ponieważ stan zmiennej zależy od historii.
Edytować:
Dla podkreślenia tego trywialnym przykładem, który rekurencyjnie oblicza ciąg Fibonacciego; można to uruchomić wiele razy, aby uzyskać dokładny pomiar, ale chodzi o to, że żadna z metod nie ma znacząco różnej wydajności:
> fibo <- function(n) {
+ if ( n < 2 ) n
+ else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
user system elapsed
7.48 0.00 7.52
> system.time(sapply(0:26, fibo))
user system elapsed
7.50 0.00 7.54
> system.time(lapply(0:26, fibo))
user system elapsed
7.48 0.04 7.54
> library(plyr)
> system.time(ldply(0:26, fibo))
user system elapsed
7.52 0.00 7.58
Edycja 2:
Jeśli chodzi o użycie pakietów równoległych dla R (np. Rpvm, rmpi, snow), generalnie zapewniają one apply
funkcje rodzinne (nawet foreach
pakiet jest zasadniczo równoważny, pomimo nazwy). Oto prosty przykład sapply
funkcji w snow
:
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
W tym przykładzie zastosowano klaster gniazd, dla którego nie trzeba instalować dodatkowego oprogramowania; w przeciwnym razie będziesz potrzebować czegoś takiego jak PVM lub MPI (patrz strona klastrowania Tierney ). snow
ma następujące funkcje:
parLapply(cl, x, fun, ...)
parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
parApply(cl, X, MARGIN, FUN, ...)
parRapply(cl, x, fun, ...)
parCapply(cl, x, fun, ...)
Sensowne jest, aby apply
funkcje były wykonywane równolegle, ponieważ nie mają one skutków ubocznych . Kiedy zmieniasz wartość zmiennej w for
pętli, jest ona ustawiana globalnie. Z drugiej strony wszystkie apply
funkcje mogą być bezpiecznie używane równolegle, ponieważ zmiany są lokalne dla wywołania funkcji (chyba że spróbujesz użyć assign
lub <<-
w takim przypadku możesz wprowadzić efekty uboczne). Nie trzeba dodawać, że należy uważać na zmienne lokalne i globalne, zwłaszcza w przypadku wykonywania równoległego.
Edytować:
Oto trywialny przykład pokazujący różnicę między skutkami ubocznymi for
i w *apply
takim zakresie:
> df <- 1:10
> # *apply example
> lapply(2:3, function(i) df <- df * i)
> df
[1] 1 2 3 4 5 6 7 8 9 10
> # for loop example
> for(i in 2:3) df <- df * i
> df
[1] 6 12 18 24 30 36 42 48 54 60
Zwróć uwagę, jak df
zmienia się środowisko w środowisku nadrzędnym, for
ale nie *apply
.
apply
języka R implementuje również zrównoleglenie poprzez rodzinę funkcji. Dlatego strukturyzacja programów tak, aby stosowały, pozwala na ich zrównoleglenie przy bardzo małym koszcie krańcowym.snowfall
opakowanie i wypróbować przykłady w ich winiecie.snowfall
kompiluje się nasnow
pakiecie i dodatkowo abstrakcyjnie wyodrębnia szczegóły równoległości, dzięki czemu wykonywanie równoległychapply
funkcji jest bardzo proste .foreach
od tego czasu stało się dostępne i wydaje się, że jest bardzo pytany w SO.lapply
jest „trochę szybszy” niżfor
pętla. Jednak nie widzę nic, co by to sugerowało. Wspominasz tylko, żelapply
jest szybszy niżsapply
, co jest dobrze znanym faktem z innych powodów (sapply
próbuje uprościć dane wyjściowe i dlatego musi wykonywać wiele sprawdzania rozmiaru danych i potencjalnych konwersji). Nic związanego zfor
. Czy coś mi brakuje?Czasami przyspieszenie może być znaczne, na przykład gdy trzeba zagnieżdżać pętle for, aby uzyskać średnią na podstawie grupowania więcej niż jednego czynnika. Tutaj masz dwa podejścia, które dają dokładnie ten sam wynik:
Obie dają dokładnie ten sam wynik, będący macierzą 5 x 10 ze średnimi oraz nazwanymi wierszami i kolumnami. Ale :
Proszę bardzo. Co ja wygrałem? ;-)
źródło
*apply
jest szybszy. Ale myślę, że ważniejszy punkt to skutki uboczne (zaktualizowałem moją odpowiedź przykładem).data.table
jest jeszcze szybsze i myślę, że „łatwiejsze”.library(data.table)
dt<-data.table(X,Y,Z,key=c("Y,Z"))
system.time(dt[,list(X_mean=mean(X)),by=c("Y,Z")])
tapply
jest wyspecjalizowaną funkcję dla określonego zadania, to dlaczego to szybciej niż pętli for. Nie może zrobić tego, co może zrobić pętla for (podczas gdy zwykłaapply
może). Porównujesz jabłka z pomarańczami.... i jak właśnie napisałem w innym miejscu, vapply jest twoim przyjacielem! ... to jak sapply, ale określasz również typ zwracanej wartości, co znacznie przyspiesza.
Aktualizacja z 1 stycznia 2020 r .:
źródło
for
pętle są szybsze na moim komputerze z 2-rdzeniowym systemem Windows 10. Zrobiłem to z5e6
elementami - pętla wynosiła 2,9 sekundy vs. 3,1 sekundy dlavapply
.W innym miejscu napisałem, że przykład taki jak Shane tak naprawdę nie podkreśla różnicy w wydajności między różnymi rodzajami składni zapętlonej, ponieważ cały czas spędza się w funkcji, a nie na obciążaniu pętli. Ponadto kod niesprawiedliwie porównuje pętlę for bez pamięci z funkcjami rodziny Apply, które zwracają wartość. Oto nieco inny przykład, który podkreśla tę kwestię.
Jeśli planujesz zapisać wynik, zastosowanie funkcji rodziny może być dużo więcej niż tylko cukrem syntaktycznym.
(Prosta nie na liście z wynosi tylko 0,2 s, więc okrążenie jest znacznie szybsze. Inicjalizacja z w pętli for jest dość szybka, ponieważ podaję średnią z ostatnich 5 z 6 biegów, więc poruszanie się poza systemem. prawie nie wpływają na rzeczy)
Należy jednak zauważyć, że istnieje jeszcze jeden powód, dla którego warto stosować funkcje rodzinne niezależnie od ich wydajności, przejrzystości lub braku skutków ubocznych. ZA
for
Pętla zwykle promuje wprowadzenie jak najwięcej wewnątrz pętli. Dzieje się tak, ponieważ każda pętla wymaga ustawienia zmiennych do przechowywania informacji (wśród innych możliwych operacji). Instrukcje Zastosuj są zwykle stronnicze w drugą stronę. Często chcesz wykonać wiele operacji na danych, z których kilka można wektoryzować, ale niektóre mogą nie być w stanie tego zrobić. W R, w przeciwieństwie do innych języków, najlepiej jest oddzielić te operacje i uruchomić te, które nie są wektoryzowane w instrukcji Apply (lub wektoryzowanej wersji funkcji) i te, które są wektoryzowane jako prawdziwe operacje wektorowe. To często ogromnie przyspiesza wydajność.Biorąc przykład Jorisa Meysa, w którym zastępuje tradycyjną pętlę for poręczną funkcją R, możemy jej użyć, aby pokazać efektywność pisania kodu w bardziej przyjazny dla języka R sposób przy podobnym przyspieszeniu bez wyspecjalizowanej funkcji.
To kończy się znacznie szybciej niż
for
pętla i tylko trochę wolniej niż wbudowanatapply
funkcja zoptymalizowana . Nie dlatego, żevapply
jest o wiele szybszy niż,for
ale dlatego, że wykonuje tylko jedną operację w każdej iteracji pętli. W tym kodzie wszystko inne jest wektoryzowane. W tradycyjnejfor
pętli Joris Meys wiele (7?) Operacji jest wykonywanych w każdej iteracji i jest sporo konfiguracji tylko po to, aby je wykonać. Zwróć także uwagę, o ile bardziej kompaktowy jest to format niżfor
wersja.źródło
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
, a vapply jest jeszcze lepszy:1.19 0.00 1.19
sapply
50% wolniejszyfor
ilapply
dwukrotnie szybszy.y
do1:1e6
, a nienumeric(1e6)
(wektorem zer). Starając się przeznaczyćfoo(0)
doz[0]
kółko ma również nie przedstawiają typowefor
użycie pętli. Poza tym wiadomość jest na miejscu.Stosowanie funkcji na podzbiorach wektora
tapply
może być znacznie szybsze niż pętla for. Przykład:apply
jednak w większości sytuacji nie zapewnia żadnego wzrostu prędkości, aw niektórych przypadkach może być nawet dużo wolniejsza:Ale w takich sytuacjach mamy
colSums
irowSums
:źródło
microbenchmark
jest on znacznie dokładniejszy niżsystem.time
. Jeśli próbujesz porównaćsystem.time(f3(mat))
isystem.time(f4(mat))
dostaniesz inny wynik prawie za każdym razem. Czasami tylko właściwy test porównawczy jest w stanie pokazać najszybszą funkcję.