2011-06-23 17 views
7

Powiel możliwe:
Is R's apply family more than syntactic sugarJakie są zalety funkcji "Zastosuj"? Kiedy są lepsze w użyciu niż pętle "na", a kiedy nie?

Tylko to, co mówi tytuł. Może to głupie pytanie, ale rozumiem, że podczas korzystania z funkcji "zastosuj", iteracja wykonywana jest w skompilowanym kodzie zamiast w parserze R. Wydaje się to sugerować, że na przykład lapply jest szybsze niż pętla "for", jeśli istnieje wiele iteracji, a każda operacja jest stosunkowo prosta. Na przykład, jeśli pojedyncze wywołanie funkcji zawiniętej w lapply zajmuje 10 sekund i jest tylko, powiedzmy, 12 iteracji, wyobrażam sobie, że nie ma praktycznie żadnej różnicy między używaniem "dla" i "lapply".

Teraz, gdy o tym myślę, czy funkcja wewnątrz "lapply" musi zostać przeanalizowana, dlaczego powinna być DOWOLNA korzyść w zakresie wydajności z używania "lapply" zamiast "for", chyba że robisz coś, co jest tam są skompilowanymi funkcjami (takimi jak sumowanie lub mnożenie itp.)?

Z góry dziękuję!

Josh

+1

Zobacz ten wątek: http://stackoverflow.com/q/2275896/429846 –

+1

A ten: http://stackoverflow.com/q/5533246/210673 – Aaron

+1

Istnieje również wielki R Help Desk artykuł na ten temat z powrotem w maju 2008: http://promberger.info/files/rnews-vectorvsloops2008.pdf – DrewConway

Odpowiedz

12

Istnieje kilka powodów, dla których można było wolą funkcję apply rodziny nad for pętli, lub na odwrót.

Po pierwsze, for() i apply(), sapply() będą generalnie równie szybkie, jak nawzajem, jeśli zostaną wykonane poprawnie. lapply() powoduje, że więcej działa w skompilowanym kodzie wewnątrz obiektów R niż inne, więc może być szybsze niż te funkcje. Wydaje się, że przewaga prędkości jest większa, gdy akt "zapętlenia" danych jest znaczną częścią czasu obliczeniowego; w wielu codziennych zastosowaniach mało prawdopodobne jest, aby uzyskać znacznie szybciej niż z natury szybciej. W końcu wszystkie te funkcje będą wywoływać funkcje R, więc trzeba je zinterpretować, a następnie uruchomić.

for() Pętle mogą być łatwiejsze do wdrożenia, zwłaszcza jeśli pochodzą z tła programowania, w którym pętle są powszechne. Praca w pętli może być bardziej naturalna niż wymuszenie iteracyjnego obliczania na jedną z funkcji rodziny. Jednakże, aby poprawnie używać pętli for(), należy wykonać dodatkową pracę, aby skonfigurować pamięć i zarządzać ponownym podłączeniem wyjścia pętli. Funkcje apply robią to za ciebie automagicznie. Np .:

IN <- runif(10) 
OUT <- logical(length = length(IN)) 
for(i in IN) { 
    OUT[i] <- IN > 0.5 
} 

że jest głupie przykład jako > jest operatorem wektoryzowane ale chciałem coś do punktu, a mianowicie, że trzeba zarządzać wyjście. Najważniejsze jest to, że z for() pętlami, zawsze alokować wystarczającą ilość pamięci do przechowywania wyjść przed rozpoczęciem pętli. Jeśli nie wiesz, ile potrzebujesz pamięci, przydziel rozsądną porcję pamięci, a następnie sprawdź w pętli, czy wyczerpałeś tę pamięć i zrób kolejny duży fragment pamięci.

Głównym powodem, dla którego warto korzystać z jednej z rodzin funkcji apply jest bardziej elegancki, czytelny kod. Zamiast zarządzać pamięcią wyjściową i konfigurowaniem pętli (jak pokazano powyżej), możemy pozwolić R obsłużyć to i zwięźle poprosić R, aby uruchomił funkcję na podzbiorach naszych danych.Prędkość zwykle nie wchodzi w decyzję, przynajmniej dla mnie. Używam tej funkcji, która najlepiej pasuje do sytuacji i da w wyniku prosty, łatwy do zrozumienia kod, ponieważ znacznie częściej tracę więcej czasu niż oszczędzam, wybierając zawsze najszybszą funkcję, jeśli nie pamiętam, co to jest kod Robiąc dzień, tydzień lub więcej później!

Rodzina apply nadaje się do operacji skalarnych lub wektorowych. Pętla for() często umożliwia wykonywanie wielu iterowanych operacji przy użyciu tego samego indeksu i. Na przykład, napisałem kod, który używa pętli for(), aby wykonać k -fold lub sprawdzanie poprawności krzyżowej na obiektach. Prawdopodobnie nigdy bym tego nie zrobił z jedną z rodziny apply, ponieważ każda iteracja CV wymaga wielu operacji, dostępu do wielu obiektów w bieżącej ramce i wypełnia kilka obiektów wyjściowych, które przechowują wynik iteracji.

Co do ostatniego punktu, o dlaczego lapply() może ewentualnie być szybsze że for() lub apply(), trzeba zdać sobie sprawę, że „pętla” mogą być wykonywane w interpretowany kod R lub kod skompilowany. Tak, oba wciąż będą wywoływać funkcje R, które muszą być interpretowane, ale jeśli robisz pętlę i wywołujesz bezpośrednio ze skompilowanego kodu C (np. lapply()), to tam, gdzie wzrost wydajności może pochodzić z ponad apply(), powiedzmy, który sprowadza się do pętla for() w rzeczywistym kodzie R. Zobacz źródło dla apply(), aby zobaczyć, że jest owinięcie wokół for() pętli, a następnie spojrzeć na kod dla lapply(), który jest:

> lapply 
function (X, FUN, ...) 
{ 
    FUN <- match.fun(FUN) 
    if (!is.vector(X) || is.object(X)) 
     X <- as.list(X) 
    .Internal(lapply(X, FUN)) 
} 
<environment: namespace:base> 

i należy zrozumieć, dlaczego nie może być różnica w prędkości między lapply() oraz for() i inne funkcje rodziny. .Internal() jest jednym ze sposobów wywoływania skompilowanego kodu C używanego przez sam R. Oprócz manipulacji i sprawdzenia poprawności na FUN, całe obliczenie odbywa się w C, wywołując funkcję R FUN. Porównaj to ze źródłem dla apply().

+0

Nice ! W podobnych kwestiach udało się również dodać nowe informacje. Dzięki, nauczyłem się kilku nowych rzeczy. – Aaron

+0

Świetnie, dzięki. Chciałbym wyjaśnić jedną rzecz dotyczącą potencjalnego przyrostu wydajności. Kod, który jest wykonywany wewnątrz wywołania lapply(), nadal będzie zajmował tyle samo czasu w iteracji, prawda?Tak więc byłoby poprawne stwierdzając, że w sytuacji, gdy każda iteracja sama w sobie przyjmuje rzędu kilku sekund lub więcej, że zysk wydajności nad stosuje() lub dla() byłoby pomijalne? – Josh

3

Od Burnsa R Inferno (pdf), p25:

użytkowania wyraźne for pętli, gdy każdy iteracja jest nietrywialne zadanie. Lecz prosta pętla może być wyraźniej wyrażona i zwarto wyrażona przy użyciu funkcji apply . Istnieje co najmniej jeden Wyjątkiem od tej reguły ... jeśli wynik będzie być lista i niektóre składniki może być NULL, a następnie do pętli kłopot (Big Trouble) i lapply daje oczekiwaną odpowiedź.

+0

Nie zgadzam się całkowicie. Jeśli iteracja jest nietrywialnym zadaniem, lepiej jest przenieść ten blok kodu do odpowiedniej funkcji, a następnie wywołać go za pomocą * apply(). – geoffjentry