Funkcje grupowania (dotknij, agreguj, agreguj) i rodzina * zastosuj


1040

Ilekroć chcę zrobić coś „mapującego” py w R, zwykle próbuję użyć funkcji w applyrodzinie.

Jednak nigdy do końca nie rozumiałem różnic między nimi - jak { sapply, lapplyitp.} Zastosuj funkcję do wejścia / zgrupowanego wejścia, jak będzie wyglądać wyjście, a nawet czym może być wejście - więc często po prostu przejrzyj je wszystkie, aż dostanę to, czego chcę.

Czy ktoś może wyjaśnić, jak korzystać z którego?

Moje obecne (prawdopodobnie niepoprawne / niepełne) zrozumienie to ...

  1. sapply(vec, f): wejście jest wektorem. wyjście jest wektorem / macierzą, w której iznajduje się element f(vec[i]), co daje macierz, jeśli fma wyjście wieloelementowe

  2. lapply(vec, f): tak samo jak sapply, ale wyjście jest listą?

  3. apply(matrix, 1/2, f): dane wejściowe to macierz. wyjście to wektor, w którym elementem ijest f (wiersz / col i macierzy)
  4. tapply(vector, grouping, f): wyjście to macierz / tablica, w której element w macierzy / macierzy jest wartością z fgrupy gwektorów i gjest wypychany do nazw wierszy / kolumn
  5. by(dataframe, grouping, f): niech gbędzie grupa. stosuje się fdo każdej kolumny grupy / ramki danych. całkiem wydrukuj grupowanie i wartość fw każdej kolumnie.
  6. aggregate(matrix, grouping, f): podobny do by, ale zamiast ładnego drukowania danych wyjściowych, agregacja umieszcza wszystko w ramce danych.

Boczne pytanie: Nadal nie nauczyłem się plyr ani przekształcania - plyrczy reshapezastąpiłbym je wszystkie całkowicie?


33
na twoje pytanie poboczne: dla wielu rzeczy plyr jest bezpośrednim zamiennikiem dla *apply()i by. plyr (przynajmniej dla mnie) wydaje się o wiele bardziej spójny, ponieważ zawsze wiem dokładnie, jakiego formatu danych oczekuje i dokładnie co wypluje. To oszczędza mi wielu kłopotów.
JD Long,

12
Polecam także dodawanie: doByoraz możliwości wyboru i zastosowania data.table.
Iterator

7
sapplyjest tylko lapplyz dodatkiem simplify2arrayna wyjściu. applydziała na wektor atomowy, ale wyjściem może być wektor lub lista. bydzieli ramki danych na sub-ramki danych, ale nie używa fosobno dla kolumn. Tylko jeśli istnieje metoda dla klasy „data.frame”, może fzostać zastosowana kolumna by. aggregatejest ogólny, więc istnieją różne metody dla różnych klas pierwszego argumentu.
IRTFM

8
Mnemoniczny: l oznacza „listę”, s oznacza „upraszczanie”, t oznacza „według typu” (każdy poziom zgrupowania jest typem)
Lutz Prechelt

W pakiecie Rfast istnieją również pewne funkcje, takie jak: eachcol.apply, apply.condition i inne, które są szybsze niż ich odpowiedniki R
Stefanos,

Odpowiedzi:


1330

R ma wiele * zastosowanych funkcji, które są prawidłowo opisane w plikach pomocy (np ?apply.). Jest ich jednak dość, że początkujący użytkownicy mogą mieć trudności z podjęciem decyzji, który z nich jest odpowiedni do ich sytuacji, a nawet z zapamiętaniem ich wszystkich. Mogą mieć ogólne przekonanie, że „powinienem tutaj użyć * funkcji zastosowania”, ale na początku może być trudno utrzymać je wszystkie prosto.

Pomimo faktu (zauważonego w innych odpowiedziach), że większość funkcjonalności rodziny * apply jest objęta niezwykle popularnym plyrpakietem, funkcje podstawowe pozostają użyteczne i warte poznania.

Ta odpowiedź ma służyć jako drogowskaz dla nowych useRs, aby pomóc im skierować je do właściwej * funkcji zastosowania dla ich konkretnego problemu. Uwaga: nie ma to na celu po prostu regurgitacji ani wymiany dokumentacji R! Mamy nadzieję, że ta odpowiedź pomoże Ci zdecydować, która * funkcja zastosowania pasuje do Twojej sytuacji, a następnie od Ciebie zależy, czy ją zbadasz. Z jednym wyjątkiem różnice wydajności nie zostaną uwzględnione.

  • Apply - Kiedy chcesz zastosować funkcję do wierszy lub kolumn macierzy (i analogów o wyższych wymiarach); generalnie nie jest wskazane dla ramek danych, ponieważ najpierw wymusi macierz.

    # Two dimensional matrix
    M <- matrix(seq(1,16), 4, 4)
    
    # apply min to rows
    apply(M, 1, min)
    [1] 1 2 3 4
    
    # apply max to columns
    apply(M, 2, max)
    [1]  4  8 12 16
    
    # 3 dimensional array
    M <- array( seq(32), dim = c(4,4,2))
    
    # Apply sum across each M[*, , ] - i.e Sum across 2nd and 3rd dimension
    apply(M, 1, sum)
    # Result is one-dimensional
    [1] 120 128 136 144
    
    # Apply sum across each M[*, *, ] - i.e Sum across 3rd dimension
    apply(M, c(1,2), sum)
    # Result is two-dimensional
         [,1] [,2] [,3] [,4]
    [1,]   18   26   34   42
    [2,]   20   28   36   44
    [3,]   22   30   38   46
    [4,]   24   32   40   48
    

    Jeśli chcesz środki wiersz / kolumna lub kwoty na matrycy 2D, należy zbadać wysoce zoptymalizowane, błyskawicznym colMeans, rowMeans, colSums, rowSums.

  • lapply - Kiedy chcesz zastosować funkcję do każdego elementu listy po kolei i odzyskać listę.

    Jest to koń roboczy wielu innych * funkcji stosujących. Odklej ich kod, a często znajdziesz go lapplypod spodem.

    x <- list(a = 1, b = 1:3, c = 10:100) 
    lapply(x, FUN = length) 
    $a 
    [1] 1
    $b 
    [1] 3
    $c 
    [1] 91
    lapply(x, FUN = sum) 
    $a 
    [1] 1
    $b 
    [1] 6
    $c 
    [1] 5005
    
  • sapply - Kiedy chcesz zastosować funkcję do każdego elementu listy po kolei, ale chcesz wektora z powrotem, a nie listy.

    Jeśli zaczniesz pisać unlist(lapply(...)), przestań i zastanów się sapply.

    x <- list(a = 1, b = 1:3, c = 10:100)
    # Compare with above; a named vector, not a list 
    sapply(x, FUN = length)  
    a  b  c   
    1  3 91
    
    sapply(x, FUN = sum)   
    a    b    c    
    1    6 5005 
    

    W bardziej zaawansowanych zastosowaniach sapplyspróbuje przymusić wynik do tablicy wielowymiarowej, jeśli to właściwe. Na przykład, jeśli nasza funkcja zwraca wektory o tej samej długości, sapplyużyje ich jako kolumn macierzy:

    sapply(1:5,function(x) rnorm(3,x))

    Jeśli nasza funkcja zwróci macierz dwuwymiarową, sapplyzrobi zasadniczo to samo, traktując każdą zwróconą macierz jako pojedynczy długi wektor:

    sapply(1:5,function(x) matrix(x,2,2))

    O ile nie określimy simplify = "array", w którym to przypadku wykorzysta poszczególne macierze do zbudowania tablicy wielowymiarowej:

    sapply(1:5,function(x) matrix(x,2,2), simplify = "array")

    Każde z tych zachowań jest oczywiście zależne od naszej funkcji zwracającej wektory lub macierze o tej samej długości lub wymiarze.

  • vapply - gdy chcesz użyć, sapplyale być może trzeba wycisnąć trochę więcej kodu z kodu.

    Bo w vapplyzasadzie podajesz R przykład tego, co zwróci twoja funkcja, co może zaoszczędzić trochę czasu, zmuszając zwrócone wartości do dopasowania do pojedynczego wektora atomowego.

    x <- list(a = 1, b = 1:3, c = 10:100)
    #Note that since the advantage here is mainly speed, this
    # example is only for illustration. We're telling R that
    # everything returned by length() should be an integer of 
    # length 1. 
    vapply(x, FUN = length, FUN.VALUE = 0L) 
    a  b  c  
    1  3 91
    
  • mapply - Gdy masz kilka struktur danych (np. wektory, listy) i chcesz zastosować funkcję do 1. elementów każdego, a następnie do 2. elementów każdego itd., zmuszając wynik do wektora / tablicy, jak w sapply.

    Jest to wielowymiarowe w tym sensie, że twoja funkcja musi akceptować wiele argumentów.

    #Sums the 1st elements, the 2nd elements, etc. 
    mapply(sum, 1:5, 1:5, 1:5) 
    [1]  3  6  9 12 15
    #To do rep(1,4), rep(2,3), etc.
    mapply(rep, 1:4, 4:1)   
    [[1]]
    [1] 1 1 1 1
    
    [[2]]
    [1] 2 2 2
    
    [[3]]
    [1] 3 3
    
    [[4]]
    [1] 4
  • Mapa - opakowanie do mapplyz SIMPLIFY = FALSE, więc na pewno zwróci listę.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
  • rapply - Gdy chcesz zastosować funkcję do każdego elementu zagnieżdżonej struktury listy , rekurencyjnie.

    Aby dać ci pojęcie o tym, jak niezwykłe rapplyjest, zapomniałem o tym, publikując tę ​​odpowiedź po raz pierwszy! Oczywiście jestem pewien, że wiele osób go używa, ale YMMV. rapplynajlepiej ilustruje funkcja zdefiniowana przez użytkownika do zastosowania:

    # Append ! to string, otherwise increment
    myFun <- function(x){
        if(is.character(x)){
          return(paste(x,"!",sep=""))
        }
        else{
          return(x + 1)
        }
    }
    
    #A nested list structure
    l <- list(a = list(a1 = "Boo", b1 = 2, c1 = "Eeek"), 
              b = 3, c = "Yikes", 
              d = list(a2 = 1, b2 = list(a3 = "Hey", b3 = 5)))
    
    
    # Result is named vector, coerced to character          
    rapply(l, myFun)
    
    # Result is a nested list like l, with values altered
    rapply(l, myFun, how="replace")
  • tapply - Gdy chcesz zastosować funkcję do podzbiorów wektora, a podzbiory są zdefiniowane przez inny wektor, zwykle czynnik.

    Czarne owce * stosują rodzinę. Użycie wyrażenia „tablica obdarta” w pliku pomocy może być nieco mylące , ale w rzeczywistości jest dość proste.

    Wektor:

    x <- 1:20

    Współczynnik (o tej samej długości!) Definiujący grupy:

    y <- factor(rep(letters[1:5], each = 4))

    Dodaj wartości w xramach każdej podgrupy zdefiniowanej przez y:

    tapply(x, y, sum)  
     a  b  c  d  e  
    10 26 42 58 74 

    Bardziej złożone przykłady mogą być obsługiwane, gdy podgrupy są zdefiniowane przez unikalne kombinacje listy kilku czynników. tapplyjest napisane w duchu podobnym do split-apply-łączą funkcje, które są powszechne w R ( aggregate, by, ave, ddply, itd.) Stąd jego status czarna owca.


32
Uwierz, że przekonasz się, że byjest to czysto podzielone lap i aggregatejest tapplyw ich rdzeniu. Myślę, że czarne owce są doskonałym materiałem.
IRTFM,

21
Fantastyczna odpowiedź! To powinno być częścią oficjalnej dokumentacji R. :). Jedna drobna sugestia: może dodać kilka kul przy użyciu aggregatei byrównież? (W końcu rozumiem je po twoim opisie !, ale są dość powszechne, więc może być użyteczne
rozróżnienie i podanie

3
@grautur Aktywnie przycinałem rzeczy z tej odpowiedzi, aby uniknąć (a) zbyt długiego i (b) ponownego napisania dokumentacji. Uznałem, że chwilę aggregate, byitp są w oparciu o * Stosowanie funkcji, sposób podejście ich użyciu jest inna tyle z punktu widzenia użytkowników, że powinny one być zebrane w osobnej odpowiedzi. Mogę spróbować, jeśli będę miał czas, a może ktoś inny mnie do tego pobije i zdobędzie moje poparcie.
joran

4
również ?Mapjako krewnymapply
baptiste

3
@ jsanders - w ogóle się z tym nie zgodzę. data.frames są absolutnie centralną częścią R i jako listobiekt są często manipulowane przy użyciu lapplyszczególnie. Działają również jako pojemniki do grupowania wektorów / czynników wielu typów razem w tradycyjny prostokątny zestaw danych. Chociaż data.tablei plyrmoże dodać pewien typ składni, który niektórzy mogą uważać za wygodniejszy, rozszerzają i działają data.frameodpowiednio na s.
thelatemail

191

Na marginesie, oto jak różne plyrfunkcje odpowiadają funkcjom podstawowym *apply(od dokumentu wprowadzającego do dokumentu plyr ze strony internetowej plyr http://had.co.nz/plyr/ )

Base function   Input   Output   plyr function 
---------------------------------------
aggregate        d       d       ddply + colwise 
apply            a       a/l     aaply / alply 
by               d       l       dlply 
lapply           l       l       llply  
mapply           a       a/l     maply / mlply 
replicate        r       a/l     raply / rlply 
sapply           l       a       laply 

Jednym z celów plyrjest zapewnienie spójnych konwencji nazewnictwa dla każdej funkcji, kodując typy danych wejściowych i wyjściowych w nazwie funkcji. Zapewnia również spójność danych wyjściowych, ponieważ dane wyjściowe dlply()są łatwo przejezdne w ldply()celu uzyskania użytecznych danych wyjściowych itp.

Koncepcyjnie nauka plyrnie jest trudniejsza niż zrozumienie podstawowych *applyfunkcji.

plyra reshapefunkcje zastąpiły prawie wszystkie te funkcje w moim codziennym użytkowaniu. Ale także z dokumentu Wprowadzenie do Plyra:

Powiązane funkcje tapplyi sweepnie mają odpowiadającej im funkcji plyri pozostają użyteczne. mergejest przydatny do łączenia podsumowań z oryginalnymi danymi.


13
Kiedy zacząłem uczyć się R od zera, okazało się, że Plyr DUŻO jest łatwiejszy do nauczenia niż *apply()rodzina funkcji. Dla mnie ddply()było bardzo intuicyjne, ponieważ znałem funkcje agregujące SQL. ddply()stał się moim młotem do rozwiązywania wielu problemów, z których niektóre można by lepiej rozwiązać za pomocą innych poleceń.
JD Long,

1
Wydaje mi się, że pomyślałem, że pojęcie plyrfunkcji jest podobne do *applyfunkcji, więc jeśli możesz zrobić jedną, możesz zrobić drugą, ale plyrfunkcje są łatwiejsze do zapamiętania. Ale całkowicie zgadzam się co do ddply()młotka!
JoFrhwld,

1
Pakiet plyr ma join()funkcję, która wykonuje zadania podobne do scalania. Być może bardziej chodzi o to, aby wspomnieć o tym w kontekście plyr.
marbel

Nie zapominajmy o biednych, zapomnianycheapply
JDL

Ogólnie rzecz biorąc, świetna odpowiedź, ale myślę, że lekceważy użyteczność vapplyi wady sapply. Główną zaletą vapplyjest to, że wymusza typ wyjścia i jego długość, dzięki czemu uzyskasz dokładnie oczekiwany wynik lub błąd informacyjny. Z drugiej strony sapplypostara się uprościć wyniki, przestrzegając reguł, które nie zawsze są oczywiste, i w przeciwnym razie wróci do listy. Na przykład, spróbuj przewidzieć typ wyjścia będzie produkować: sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1)). Co sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)?
Alexey Shiklomanov


100

Najpierw zacznij od doskonałej odpowiedzi Jorana - wątpliwe, że coś może to poprawić.

Następnie następujące mnemoniki mogą pomóc zapamiętać różnice między nimi. Chociaż niektóre z nich są oczywiste, inne mogą być mniej - --- znajdziesz w nich uzasadnienie w dyskusjach Jorana.

Mnemonika

  • lapplyto zastosowanie listy, która działa na listę lub wektor i zwraca listę.
  • sapplyjest prosty lapply (domyślnie funkcja zwraca wektor lub macierz, jeśli to możliwe)
  • vapplyjest zweryfikowanym zastosowaniem (pozwala na wstępne określenie typu obiektu zwrotnego)
  • rapplyto rekurencyjne zastosowanie do list zagnieżdżonych, tj. list w listach
  • tapplyjest oznaczony stosuj, gdy znaczniki identyfikują podzbiory
  • apply jest ogólny : stosuje funkcję do wierszy lub kolumn macierzy (lub bardziej ogólnie do wymiarów tablicy)

Budowanie właściwego tła

Jeśli korzystanie z applyrodziny nadal wydaje Ci się trochę obce, być może brakuje Ci kluczowego punktu widzenia.

Te dwa artykuły mogą pomóc. Stanowią one niezbędne tło do motywowania technik programowania funkcjonalnego zapewnianych przez applyrodzinę funkcji.

Użytkownicy Lisp natychmiast rozpoznają ten paradygmat. Jeśli nie jesteś zaznajomiony z Lispem, po obejrzeniu FP zdobędziesz potężny punkt widzenia do użycia w R - i applynabierzesz dużo więcej sensu.


51

Ponieważ zdałem sobie sprawę, że (bardzo doskonałe) odpowiedzi na ten post brak byi aggregatewyjaśnienia. Oto mój wkład.

PRZEZ

byFunkcji, jak podano w dokumentacji może być jednak jako „Okładki” tapply. Siła bypowstaje, gdy chcemy obliczyć zadanie, tapplyktórego nie można obsłużyć. Jednym z przykładów jest ten kod:

ct <- tapply(iris$Sepal.Width , iris$Species , summary )
cb <- by(iris$Sepal.Width , iris$Species , summary )

 cb
iris$Species: setosa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.300   3.200   3.400   3.428   3.675   4.400 
-------------------------------------------------------------- 
iris$Species: versicolor
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.525   2.800   2.770   3.000   3.400 
-------------------------------------------------------------- 
iris$Species: virginica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   2.800   3.000   2.974   3.175   3.800 


ct
$setosa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.300   3.200   3.400   3.428   3.675   4.400 

$versicolor
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.525   2.800   2.770   3.000   3.400 

$virginica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   2.800   3.000   2.974   3.175   3.800 

Jeśli wydrukujemy te dwa obiekty cti cb„zasadniczo” uzyskamy te same wyniki, a jedyne różnice dotyczą sposobu ich wyświetlania i różnych classatrybutów odpowiednio bydla cbi arraydla ct.

Jak powiedziałem, moc bypowstaje, gdy nie możemy użyć tapply; poniższy kod jest jednym przykładem:

 tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) : 
  arguments must have same length

R mówi, że argumenty muszą mieć tę samą długość, powiedz „chcemy obliczyć summarywszystkie zmienne iriswzdłuż współczynnika Species”: ale R po prostu nie może tego zrobić, ponieważ nie wie, jak sobie z tym poradzić.

Dzięki byfunkcji R wywołujemy określoną metodę dla data frameklasy, a następnie pozwalamy tej summaryfunkcji działać, nawet jeśli długość pierwszego argumentu (i także typu) jest różna.

bywork <- by(iris, iris$Species, summary )

bywork
iris$Species: setosa
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200   versicolor: 0  
 Median :5.000   Median :3.400   Median :1.500   Median :0.200   virginica : 0  
 Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246                  
 3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300                  
 Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600                  
-------------------------------------------------------------- 
iris$Species: versicolor
  Sepal.Length    Sepal.Width     Petal.Length   Petal.Width          Species  
 Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000   setosa    : 0  
 1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200   versicolor:50  
 Median :5.900   Median :2.800   Median :4.35   Median :1.300   virginica : 0  
 Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326                  
 3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500                  
 Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800                  
-------------------------------------------------------------- 
iris$Species: virginica
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400   setosa    : 0  
 1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800   versicolor: 0  
 Median :6.500   Median :3.000   Median :5.550   Median :2.000   virginica :50  
 Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026                  
 3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300                  
 Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500     

naprawdę działa, a wynik jest bardzo zaskakujący. Jest to obiekt klasy, byktóry wraz Species(powiedzmy dla każdej z nich) oblicza wartość summarykażdej zmiennej.

Zauważ, że jeśli pierwszym argumentem jest a data frame, wywoływana funkcja musi mieć metodę dla tej klasy obiektów. Na przykład, czy używamy tego kodu z meanfunkcją, będziemy mieli ten kod, który nie ma żadnego sensu:

 by(iris, iris$Species, mean)
iris$Species: setosa
[1] NA
------------------------------------------- 
iris$Species: versicolor
[1] NA
------------------------------------------- 
iris$Species: virginica
[1] NA
Warning messages:
1: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA
2: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA
3: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA

AGREGAT

aggregatemoże być postrzegany jako inny sposób użycia, tapplyjeśli wykorzystamy go w taki sposób.

at <- tapply(iris$Sepal.Length , iris$Species , mean)
ag <- aggregate(iris$Sepal.Length , list(iris$Species), mean)

 at
    setosa versicolor  virginica 
     5.006      5.936      6.588 
 ag
     Group.1     x
1     setosa 5.006
2 versicolor 5.936
3  virginica 6.588

Dwie bezpośrednie różnice polegają na tym, że drugim argumentem aggregate musi być lista, podczas gdy tapply może (nie jest obowiązkowa) być listą i że wyjściem aggregatejest ramka danych, podczas gdy jednym z tapplyjest array.

Siła aggregatepolega na tym, że może on łatwo obsługiwać podzbiory danych za pomocą subsetargumentów oraz że posiada metody dla tsobiektów, a formulatakże.

Elementy te aggregateułatwiają pracę z tym tapplyw niektórych sytuacjach. Oto kilka przykładów (dostępnych w dokumentacji):

ag <- aggregate(len ~ ., data = ToothGrowth, mean)

 ag
  supp dose   len
1   OJ  0.5 13.23
2   VC  0.5  7.98
3   OJ  1.0 22.70
4   VC  1.0 16.77
5   OJ  2.0 26.06
6   VC  2.0 26.14

Możemy osiągnąć to samo, tapplyale składnia jest nieco trudniejsza, a wynik (w niektórych okolicznościach) mniej czytelny:

att <- tapply(ToothGrowth$len, list(ToothGrowth$dose, ToothGrowth$supp), mean)

 att
       OJ    VC
0.5 13.23  7.98
1   22.70 16.77
2   26.06 26.14

Są inne czasy, kiedy nie możemy użyć bylub tapplymusimy użyć aggregate.

 ag1 <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, mean)

 ag1
  Month    Ozone     Temp
1     5 23.61538 66.73077
2     6 29.44444 78.22222
3     7 59.11538 83.88462
4     8 59.96154 83.96154
5     9 31.44828 76.89655

Nie możemy uzyskać wynik z poprzednią tapplyw jednej rozmowy, ale musimy obliczyć średnią wraz Monthz poszczególnych elementów, a następnie połączyć je (również pamiętać, że musimy nazwać na.rm = TRUE, ponieważ formulasposoby aggregatefunkcjonowania ma domyślnie na.action = na.omit):

ta1 <- tapply(airquality$Ozone, airquality$Month, mean, na.rm = TRUE)
ta2 <- tapply(airquality$Temp, airquality$Month, mean, na.rm = TRUE)

 cbind(ta1, ta2)
       ta1      ta2
5 23.61538 65.54839
6 29.44444 79.10000
7 59.11538 83.90323
8 59.96154 83.96774
9 31.44828 76.90000

podczas gdy z bypo prostu nie możemy tego osiągnąć, w rzeczywistości następujące wywołanie funkcji zwraca błąd (ale najprawdopodobniej jest to związane z podaną funkcją mean):

by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)

Innym razem wyniki są takie same, a różnice występują tylko w klasie (a następnie w jaki sposób jest ona wyświetlana / drukowana i nie tylko - przykład, jak ją podzielić) obiekt:

byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)

Poprzedni kod osiąga ten sam cel i wyniki, w niektórych momentach narzędzie, które należy zastosować, jest tylko kwestią osobistych upodobań i potrzeb; poprzednie dwa obiekty mają bardzo różne potrzeby pod względem podzbiorów.


Jak już powiedziałem, moc by powstaje, gdy nie możemy użyć tapply; poniższy kod jest jednym przykładem: TO SŁOWA, KTÓRE UŻYWASZ POWYŻEJ. Podałeś przykład obliczenia podsumowania. Powiedzmy, że statystyki podsumowujące można obliczyć tylko, że trzeba będzie je wyczyścić: np. data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))Jest to użycie tapply . With the right splitting there is nothing you cant do with tapply . The only thing is it returns a matrix. Please be careful by saying we cant use tapply`
Onyambu

35

Istnieje wiele świetnych odpowiedzi, które omawiają różnice w przypadkach użycia dla każdej funkcji. Żadna z odpowiedzi nie omawia różnic w wydajności. Jest to uzasadnione, ponieważ różne funkcje oczekują różnych danych wejściowych i generują różne dane wyjściowe, jednak większość z nich ma ogólny wspólny cel do oceny według serii / grup. Moja odpowiedź będzie koncentrować się na wydajności. Z uwagi na powyższe tworzenie danych wejściowych z wektorów jest uwzględnione w taktowaniu, również applyfunkcja nie jest mierzona.

Ja testowałem dwie różne funkcje sumi lengthnaraz. Testowana objętość wynosi 50 M na wejściu i 50 K na wyjściu. Dołączyłem również dwa obecnie popularne pakiety, które nie były powszechnie używane w czasie, gdy zadawano pytanie, data.tableoraz dplyr. Oba zdecydowanie warto sprawdzić, jeśli zależy Ci na dobrej wydajności.

library(dplyr)
library(data.table)
set.seed(123)
n = 5e7
k = 5e5
x = runif(n)
grp = sample(k, n, TRUE)

timing = list()

# sapply
timing[["sapply"]] = system.time({
    lt = split(x, grp)
    r.sapply = sapply(lt, function(x) list(sum(x), length(x)), simplify = FALSE)
})

# lapply
timing[["lapply"]] = system.time({
    lt = split(x, grp)
    r.lapply = lapply(lt, function(x) list(sum(x), length(x)))
})

# tapply
timing[["tapply"]] = system.time(
    r.tapply <- tapply(x, list(grp), function(x) list(sum(x), length(x)))
)

# by
timing[["by"]] = system.time(
    r.by <- by(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)

# aggregate
timing[["aggregate"]] = system.time(
    r.aggregate <- aggregate(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)

# dplyr
timing[["dplyr"]] = system.time({
    df = data_frame(x, grp)
    r.dplyr = summarise(group_by(df, grp), sum(x), n())
})

# data.table
timing[["data.table"]] = system.time({
    dt = setnames(setDT(list(x, grp)), c("x","grp"))
    r.data.table = dt[, .(sum(x), .N), grp]
})

# all output size match to group count
sapply(list(sapply=r.sapply, lapply=r.lapply, tapply=r.tapply, by=r.by, aggregate=r.aggregate, dplyr=r.dplyr, data.table=r.data.table), 
       function(x) (if(is.data.frame(x)) nrow else length)(x)==k)
#    sapply     lapply     tapply         by  aggregate      dplyr data.table 
#      TRUE       TRUE       TRUE       TRUE       TRUE       TRUE       TRUE 

# print timings
as.data.table(sapply(timing, `[[`, "elapsed"), keep.rownames = TRUE
              )[,.(fun = V1, elapsed = V2)
                ][order(-elapsed)]
#          fun elapsed
#1:  aggregate 109.139
#2:         by  25.738
#3:      dplyr  18.978
#4:     tapply  17.006
#5:     lapply  11.524
#6:     sapply  11.326
#7: data.table   2.686

Czy to normalne, że dplyr jest niższy niż funkcje applt?
Mostafa

1
@DimitriPetrenko Nie sądzę, nie jestem pewien, dlaczego tu jest. Najlepiej przetestować na własnych danych, ponieważ w grę wchodzi wiele czynników.
jangorecki

28

Pomimo wszystkich świetnych odpowiedzi tutaj są jeszcze 2 podstawowe funkcje, które zasługują na wzmiankę, użyteczna outerfunkcja i niejasna eapplyfunkcja

zewnętrzny

outerjest bardzo przydatną funkcją ukrytą jako bardziej przyziemna. Jeśli czytasz pomoc dotyczącą outerjej opisu, mówi:

The outer product of the arrays X and Y is the array A with dimension  
c(dim(X), dim(Y)) where element A[c(arrayindex.x, arrayindex.y)] =   
FUN(X[arrayindex.x], Y[arrayindex.y], ...).

co sprawia, że ​​wydaje się, że jest to przydatne tylko dla rzeczy typu algebry liniowej. Można go jednak używać podobnie mapplydo zastosowania funkcji do dwóch wektorów danych wejściowych. Różnica polega na tym, mapplyże zastosuje funkcję do pierwszych dwóch elementów, a następnie do dwóch drugich itd., Natomiast outerzastosuje funkcję do każdej kombinacji jednego elementu z pierwszego wektora i jednego z drugiego. Na przykład:

 A<-c(1,3,5,7,9)
 B<-c(0,3,6,9,12)

mapply(FUN=pmax, A, B)

> mapply(FUN=pmax, A, B)
[1]  1  3  6  9 12

outer(A,B, pmax)

 > outer(A,B, pmax)
      [,1] [,2] [,3] [,4] [,5]
 [1,]    1    3    6    9   12
 [2,]    3    3    6    9   12
 [3,]    5    5    6    9   12
 [4,]    7    7    7    9   12
 [5,]    9    9    9    9   12

Użyłem tego osobiście, gdy mam wektor wartości i wektor warunków i chcę zobaczyć, które wartości spełniają które warunki.

eapply

eapplyprzypomina to, lapplyże zamiast stosować funkcję do każdego elementu na liście, stosuje funkcję do każdego elementu w środowisku. Na przykład, jeśli chcesz znaleźć listę funkcji zdefiniowanych przez użytkownika w środowisku globalnym:

A<-c(1,3,5,7,9)
B<-c(0,3,6,9,12)
C<-list(x=1, y=2)
D<-function(x){x+1}

> eapply(.GlobalEnv, is.function)
$A
[1] FALSE

$B
[1] FALSE

$C
[1] FALSE

$D
[1] TRUE 

Szczerze mówiąc, nie używam tego zbyt wiele, ale jeśli budujesz wiele pakietów lub tworzysz wiele środowisk, może się to przydać.


25

Być może warto wspomnieć ave. avejest tapplyprzyjaznym kuzynem. Zwraca wyniki w postaci, którą można podłączyć bezpośrednio z powrotem do ramki danych.

dfr <- data.frame(a=1:20, f=rep(LETTERS[1:5], each=4))
means <- tapply(dfr$a, dfr$f, mean)
##  A    B    C    D    E 
## 2.5  6.5 10.5 14.5 18.5 

## great, but putting it back in the data frame is another line:

dfr$m <- means[dfr$f]

dfr$m2 <- ave(dfr$a, dfr$f, FUN=mean) # NB argument name FUN is needed!
dfr
##   a f    m   m2
##   1 A  2.5  2.5
##   2 A  2.5  2.5
##   3 A  2.5  2.5
##   4 A  2.5  2.5
##   5 B  6.5  6.5
##   6 B  6.5  6.5
##   7 B  6.5  6.5
##   ...

W pakiecie podstawowym nie ma nic, co działałoby jak w aveprzypadku całych ramek danych (tak byjak w tapplyprzypadku ramek danych). Ale możesz to zrobić:

dfr$foo <- ave(1:nrow(dfr), dfr$f, FUN=function(x) {
    x <- dfr[x,]
    sum(x$m*x$m2)
})
dfr
##     a f    m   m2    foo
## 1   1 A  2.5  2.5    25
## 2   2 A  2.5  2.5    25
## 3   3 A  2.5  2.5    25
## ...

12

Niedawno odkryłem dość przydatną sweepfunkcję i dodałem ją tutaj ze względu na kompletność:

zamiatać

Podstawową ideą jest przeszukanie tablicy pod kątem wierszy lub kolumn i zwrócenie zmodyfikowanej tablicy. Przykład wyjaśni to (źródło: datacamp ):

Załóżmy, że masz macierz i chcesz ją ujednolicić pod względem kolumn:

dataPoints <- matrix(4:15, nrow = 4)

# Find means per column with `apply()`
dataPoints_means <- apply(dataPoints, 2, mean)

# Find standard deviation with `apply()`
dataPoints_sdev <- apply(dataPoints, 2, sd)

# Center the points 
dataPoints_Trans1 <- sweep(dataPoints, 2, dataPoints_means,"-")
print(dataPoints_Trans1)
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5
# Return the result
dataPoints_Trans1
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5
# Normalize
dataPoints_Trans2 <- sweep(dataPoints_Trans1, 2, dataPoints_sdev, "/")

# Return the result
dataPoints_Trans2
##            [,1]       [,2]       [,3]
## [1,] -1.1618950 -1.1618950 -1.1618950
## [2,] -0.3872983 -0.3872983 -0.3872983
## [3,]  0.3872983  0.3872983  0.3872983
## [4,]  1.1618950  1.1618950  1.1618950

Uwaga: w tym prostym przykładzie ten sam wynik można oczywiście łatwiej osiągnąć
apply(dataPoints, 2, scale)


1
Czy to jest związane z grupowaniem?
Frank

2
@Frank: Cóż, szczerze mówiąc, tytuł tego posta jest raczej mylący: kiedy czytasz samo pytanie, chodzi o „zastosuj rodzinę”. sweepJest to funkcja wyższego rzędu jak wszystkie inne wymienione tu, na przykład apply, sapply, lapplywięc to samo pytanie można zadać o akceptowanej odpowiedzi z ponad 1000 upvotes i przykłady podane w nim. Wystarczy spojrzeć na podany applytam przykład .
vonjd

2
sweep ma mylącą nazwę, mylące wartości domyślne i mylącą nazwę parametru :). Łatwiej mi to zrozumieć w ten sposób: 1) STATYSTYKA jest wektorem lub pojedynczą wartością, która zostanie powtórzona w celu utworzenia macierzy tego samego rozmiaru co pierwsze wejście, 2) FUN zostanie zastosowana na pierwszym wejściu i tej nowej macierzy. Może lepiej zilustrowany przez: sweep(matrix(1:6,nrow=2),2,7:9,list). Jest to zwykle bardziej wydajne niż applydlatego, że gdzie applypętle sweepmogą używać funkcji wektoryzowanych.
Moody_Mudskipper

2

W pakiecie zwinięcia wydanym niedawno w CRAN, próbowałem skompresować większość typowych funkcji „zastosuj” do zaledwie 2 funkcji:

  1. dapply(Zastosuj dane) stosuje funkcje do wierszy lub (domyślnych) kolumn macierzy i danych. Ramki i (domyślnie) zwraca obiekt tego samego typu i o tych samych atrybutach (chyba że wynikiem każdego obliczenia jest atomowy i drop = TRUE). Wydajność jest porównywalna z lapplykolumnami data.frame i około 2x szybsza niż w applyprzypadku wierszy lub kolumn macierzy. Równoległość jest dostępna przez mclapply(tylko dla MAC).

Składnia:

dapply(X, FUN, ..., MARGIN = 2, parallel = FALSE, mc.cores = 1L, 
       return = c("same", "matrix", "data.frame"), drop = TRUE)

Przykłady:

# Apply to columns:
dapply(mtcars, log)
dapply(mtcars, sum)
dapply(mtcars, quantile)
# Apply to rows:
dapply(mtcars, sum, MARGIN = 1)
dapply(mtcars, quantile, MARGIN = 1)
# Return as matrix:
dapply(mtcars, quantile, return = "matrix")
dapply(mtcars, quantile, MARGIN = 1, return = "matrix")
# Same for matrices ...
  1. BYjest generyczną wersją S3 do obliczeń typu dzielenie, zastosowanie, łączenie z wykorzystaniem metody wektorowej, macierzowej i data.frame. Jest to znacznie szybsze niż tapply, bya aggregate(an również szybciej niż plyrna dużych danych dplyrjest szybsza choć).

Składnia:

BY(X, g, FUN, ..., use.g.names = TRUE, sort = TRUE,
   expand.wide = FALSE, parallel = FALSE, mc.cores = 1L,
   return = c("same", "matrix", "data.frame", "list"))

Przykłady:

# Vectors:
BY(iris$Sepal.Length, iris$Species, sum)
BY(iris$Sepal.Length, iris$Species, quantile)
BY(iris$Sepal.Length, iris$Species, quantile, expand.wide = TRUE) # This returns a matrix 
# Data.frames
BY(iris[-5], iris$Species, sum)
BY(iris[-5], iris$Species, quantile)
BY(iris[-5], iris$Species, quantile, expand.wide = TRUE) # This returns a wider data.frame
BY(iris[-5], iris$Species, quantile, return = "matrix") # This returns a matrix
# Same for matrices ...

Można również dostarczyć listy zmiennych grupujących g.

Mówiąc o wydajności: Głównym celem załamania jest wspieranie wysokowydajnego programowania w języku R i całkowite wyjście poza dzielenie, stosowanie, łączenie. W tym celu pakiet ma pełny zestaw funkcji C ++ opartych szybko Generic: fmean, fmedian, fmode, fsum, fprod, fsd, fvar, fmin, fmax, ffirst, flast, fNobs, fNdistinct, fscale, fbetween, fwithin, fHDbetween, fHDwithin, flag, fdiffi fgrowth. Wykonują zgrupowane obliczenia w jednym przejściu przez dane (tj. Bez podziału i ponownego łączenia).

Składnia:

fFUN(x, g = NULL, [w = NULL,] TRA = NULL, [na.rm = TRUE,] use.g.names = TRUE, drop = TRUE)

Przykłady:

v <- iris$Sepal.Length
f <- iris$Species

# Vectors
fmean(v)             # mean
fmean(v, f)          # grouped mean
fsd(v, f)            # grouped standard deviation
fsd(v, f, TRA = "/") # grouped scaling
fscale(v, f)         # grouped standardizing (scaling and centering)
fwithin(v, f)        # grouped demeaning

w <- abs(rnorm(nrow(iris)))
fmean(v, w = w)      # Weighted mean
fmean(v, f, w)       # Weighted grouped mean
fsd(v, f, w)         # Weighted grouped standard-deviation
fsd(v, f, w, "/")    # Weighted grouped scaling
fscale(v, f, w)      # Weighted grouped standardizing
fwithin(v, f, w)     # Weighted grouped demeaning

# Same using data.frames...
fmean(iris[-5], f)                # grouped mean
fscale(iris[-5], f)               # grouped standardizing
fwithin(iris[-5], f)              # grouped demeaning

# Same with matrices ...

W pakiecie winiety podaję punkty odniesienia. Programowanie za pomocą szybkich funkcji jest znacznie szybsze niż programowanie za pomocą dplyr lub data.tabela , szczególnie na mniejszych danych, ale także na dużych danych.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.