Grupowanie funkcji (tapply, by, aggregate) i rodziny * apply

Kiedy chcę zrobić coś "map"py w R, zwykle staram się użyć funkcji z rodziny apply.

Jednak nigdy do końca nie rozumiałem różnic między nimi-jak {sapply, lapply, itd.} zastosuj funkcję do wejścia / zgrupowania wejścia, jak będzie wyglądać wyjście, a nawet jakie może być wejście -- więc często po prostu przeglądam je wszystkie, dopóki nie dostanę tego, czego chcę.

Czy ktoś może wyjaśnić jak używać który kiedy?

Mój obecny (prawdopodobnie niepoprawne/niekompletne) rozumienie jest...

  1. sapply(vec, f): Wejście jest wektorem. wyjście jest wektorem / macierzą, gdzie element i jest f(vec[i]), dając macierz, Jeśli f mA wyjście wieloelementowe

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

  3. apply(matrix, 1/2, f): Wejście jest macierzą. wyjście jest wektorem, gdzie element i jest f (wiersz / col i macierzy)
  4. tapply(vector, grouping, f): wyjście jest macierzą / tablicą, gdzie element w macierzy / tablicy jest wartością f w grupowanie g wektora i {[13] } zostaje przesunięte do nazw wierszy / col
  5. by(dataframe, grouping, f): niech g będzie zbiorem. Zastosuj f do każdej kolumny grupy / ramki danych. pretty wyświetla grupowanie i wartość f w każdej kolumnie.
  6. aggregate(matrix, grouping, f): podobne do by, ale zamiast ładnego drukowania danych wyjściowych, agregat przykleja wszystko do ramki danych.

Pytanie poboczne: nadal nie nauczyłam się plyra czy przeformułowania -- czy plyr czy reshape zastąpić te wszystkie całkowicie?

Author: zx8754, 2010-08-17

9 answers

R posiada wiele funkcji * apply, które są umiejętnie 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 dla ich sytuacji, a nawet zapamiętaniem ich wszystkich. Mogą mieć ogólne poczucie, że "powinienem używać funkcji * apply tutaj", ale może być trudno utrzymać je wszystkie prosto na początku.

Pomimo faktu (zaznaczonego w innych odpowiedziach), że duża część funkcjonalności rodziny * apply jest objęte niezwykle popularnym pakietem plyr, podstawowe funkcje pozostają użyteczne i warte poznania.

Ta odpowiedź ma działać jako drogowskaz dla nowych użytkowników, aby pomóc im w przekierowaniu do właściwej funkcji *apply dla konkretnego problemu. Uwaga, jest to nie przeznaczone po prostu do zwrócenia lub zastąpienia dokumentacji R! Mamy nadzieję, że ta odpowiedź pomoże Ci zdecydować ,która funkcja * zastosuj odpowiada twojej sytuacji, a następnie do ciebie należy jej zbadanie dalej. Z jednym wyjątkiem różnice w wydajności nie będą rozwiązywane.

  • Zastosuj - gdy chcesz zastosować funkcję do wierszy lub kolumn macierzy (i analogów wyższych wymiarów); zazwyczaj nie jest to wskazane dla ramek danych, ponieważ najpierw zostanie wymuszone na macierzy.

    # 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, aby wiersz / kolumna oznacza lub sumy dla macierzy 2D, upewnij się, że zbadaj wysoce zoptymalizowany, błyskawiczny colMeans, rowMeans, colSums, rowSums.

  • Lapply - gdy chcesz zastosować funkcję do każdego elementu lista po kolei i dostać listę z powrotem.

    Jest to koń pociągowy wielu innych funkcji * apply. Skórka Cofnij ich kod, a często znajdziesz lapply pod 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 - gdy chcesz zastosować funkcję do każdego elementu lista z kolei, ale chcesz wektor Z powrotem, a nie lista.

    Jeśli znajdziesz się pisząc unlist(lapply(...)), zatrzymaj się i rozważ 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 {[21] } będzie próbował zmusić wynik do tablicy wielowymiarowej, jeśli jest to właściwe. Na przykład, jeśli nasza funkcja zwróci wektory o tej samej długości, sapply użyje ich jako kolumn macierzy:

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

    Jeśli nasza funkcja zwróci macierz dwuwymiarową, sapply zrobi zasadniczo to samo, traktując każdą zwróconą macierz jako jedną długą wektor:

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

    Jeśli nie określimy simplify = "array", W takim przypadku będzie on używał poszczególnych macierzy do zbudowania wielowymiarowej tablicy:

    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 - kiedy chcesz użyć sapply, ale być może potrzebujesz wyciśnij więcej prędkości z kodu.

    Dla vapply, W zasadzie dajesz r przykład jakie rzeczy twoja funkcja powróci, co może zaoszczędzić trochę czasu wartości mieszczące się w jednym wektorze atomowym.

    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 2 elementy każdego, itp., wymuszanie wyniku do wektora / tablicy jak w sapply.

    Jest to wielowymiarowe w tym sensie, że twoja funkcja musi Akceptuj 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
    
  • Mapka - wrapper do mapply Z SIMPLIFY = FALSE, więc jest gwarantowane zwrócenie listy.

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

    Aby dać ci trochę pojęcia, jak rzadko rapply jest, zapomniałem o tym, gdy po raz pierwszy publikując tę odpowiedź! Oczywiście, jestem pewien, że wiele osób go używa, ale YMMV. rapply najlepiej zilustrować za pomocą funkcji zdefiniowanej 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 - Dla gdy chcesz zastosować funkcję do podzbiorów Z wektor i podzbiory są określone przez jakiś inny wektor, Zwykle factor.

    Czarna owca z rodziny * apply. Korzystanie z pliku pomocy fraza "ragged array" może być nieco myląca, ale w rzeczywistości jest całkiem proste.

    A wektor:

    x <- 1:20
    

    Współczynnik (tej samej długości!) definiowanie grup:

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

    Dodaj wartości w x w każdej podgrupie 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. tapply jest podobne w duchu do funkcji split-apply-combine, które są często w R (aggregate, by, ave, ddply, itd.) Stąd jego status czarnej owcy.

 1217
Author: joran,
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-10-03 19:54:11

Na marginesie, oto jak różne funkcje plyr odpowiadają funkcjom bazowym *apply (z intro 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 plyr jest zapewnienie spójnych konwencji nazewnictwa dla każdej z funkcji, kodowanie wejściowych i wyjściowych typów danych w nazwie funkcji. Zapewnia również spójność wyjściową, ponieważ wyjście z dlply() jest łatwo przejezdne do ldply(), aby uzyskać użyteczne wyjście, itd.

Koncepcyjnie uczenie się plyr nie jest trudniejsze niż zrozumienie podstawowych funkcji *apply.

plyr i reshape Funkcje zastąpiły prawie wszystkie te funkcje w moim codziennym użytkowaniu. Ale również z Intro do dokumentu Plyr:

Powiązane funkcje tapply i sweep nie mają odpowiedniej funkcji w plyr i pozostają użyteczne. {[13] } jest przydatny do łączenia podsumowań z oryginalnymi danymi.

 172
Author: JoFrhwld,
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
2010-08-17 19:20:09

Ze slajdu 21 z http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy :

apply, sapply, lapply, by, agregat

(miejmy nadzieję, że jest jasne, że apply odpowiada aaply @Hadley i aggregate odpowiada ddply @Hadley itd. Slajd 20 tego samego slideshare wyjaśni, jeśli nie dostaniesz go z tego obrazu.)

(po lewej stronie jest wejście, na górze jest wyjście)

 120
Author: isomorphismes,
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
2012-02-15 23:42:48

Zacznij od doskonałej odpowiedzi Jorana -- wątpliwe, że wszystko może to poprawić.

Następnie następujące mnemotechniki mogą pomóc zapamiętać rozróżnienia między każdym z nich. Podczas gdy niektóre są oczywiste, inne mogą być mniej - - - dla nich znajdziesz uzasadnienie w dyskusjach Jorana.

Mnemotechnika

  • lapply jest listą, która działa na liście lub wektorze i zwraca listę.
  • sapply jest prostą lapply (funkcja domyślnie zwraca wektor lub macierz, jeśli to możliwe)
  • vapply is a verified apply (pozwala na zdefiniowanie typu obiektu zwrotnego)
  • rapply jest rekurencyjnym stosowanym do list zagnieżdżonych, czyli list wewnątrz list
  • tapply jest otagowane zastosuj, gdzie znaczniki identyfikują podzbiory
  • apply jest ogólnym : stosuje funkcję do wierszy lub kolumn macierzy (lub, bardziej ogólnie, do wymiarów array)

Budowanie WŁAŚCIWEGO tła

Jeśli Korzystanie z rodziny apply nadal wydaje ci się trochę obce, to może być tak, że brakuje Ci kluczowego punktu widzenia.

Te dwa artykuły mogą pomóc. Stanowią one niezbędne zaplecze do motywowania technik programowania funkcyjnego , które są dostarczane przez rodzinę funkcji apply.

Użytkownicy Lispu natychmiast rozpozna paradygmat. Jeśli nie jesteś zaznajomiony z Lisp, gdy już opanujesz FP, zyskasz potężny punkt widzenia do użycia w R -- i apply będzie miał o wiele więcej sensu.

  • W 2006 roku został wybrany na stanowisko Dyrektora ds.]}
  • W 2005 roku został wybrany na stanowisko dyrektora ds.]}
 89
Author: Assad Ebrahim,
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-05-23 10:31:37

Ponieważ zdałem sobie sprawę, że (bardzo doskonałe) odpowiedzi tego postu brak by i aggregate wyjaśnienia. Oto mój wkład.

BY

Funkcja by, jak podano w dokumentacji, może być jednak "opakowaniem" dla tapply. Moc by powstaje, gdy chcemy obliczyć zadanie, z którym tapply nie poradzi sobie. 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, ct i cb," zasadniczo " mamy te same wyniki i tylko różnice dotyczą sposobu ich wyświetlania oraz różnych atrybutów class, odpowiednio by dla cb i array dla ct.

Jak już powiedziałem, moc by powstaje, gdy nie możemy użyć tapply; poniższy kod jest jednym z przykładów:

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

R mówi, że argumenty muszą mieć takie same długości, powiedzmy " chcemy obliczyć summary wszystkich zmiennych w iris wzdłuż czynnika Species": ale R po prostu nie może tego zrobić, ponieważ nie wie, jak sobie z tym poradzić.

Z by funkcja R wysyła określoną metodę dla klasy data frame, a następnie niech funkcja summary Działa nawet wtedy, gdy długość pierwszego argumentu (a także Typ) są różne.

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     
To naprawdę działa, a wynik jest bardzo zaskakujący. Jest to obiekt klasy by, która wzdłuż Species (powiedzmy, dla każdej z nich) oblicza summary każdej zmiennej.

Zauważ, że jeśli pierwszym argumentem jest data frame, wywołana funkcja musi mieć metodę dla tej klasy obiektów. Na przykład my użyj tego kodu z funkcją mean będziemy mieli ten kod, który w ogóle nie ma 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

aggregate może być postrzegany jako inny inny sposób użycia tapply, jeśli używamy 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 są takie, że drugi argument aggregate must be a list while tapply może (nieobowiązkowe) być listą i że wyjście aggregate jest ramką danych, podczas gdy wyjście tapply jest array.

The power z aggregate jest to, że może obsługiwać łatwo podzbiory danych z argumentem subset oraz że ma metody dla obiektów ts i formula.

Te elementy ułatwiają aggregate pracę z tym tapply w 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 z tapply, ale składnia jest nieco trudniejsza, a wyjście (w niektórych okolicznościach) mniej czytelne:

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ć by lub {[14] } i musimy 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ć poprzedniego wyniku za pomocą tapply w jednym wywołaniu, ale musimy obliczyć średnią wzdłuż Month dla każdego elementu, a następnie połączyć je (należy również pamiętać, że musimy wywołać na.rm = TRUE, ponieważ metody formula funkcji aggregate mają 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 by po prostu nie możemy tego osiągnąć w rzeczywistości poniższe wywołanie funkcji zwraca błąd (ale najprawdopodobniej jest to związane z dostarczonym funkcja, mean):

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

Innym razem wyniki są takie same, a różnice są tylko w klasie (a następnie w jaki sposób jest wyświetlany/drukowany i nie tylko -- przykład, jak go podzestawić) 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 punktach to, jakiego narzędzia użyć, jest tylko kwestią osobistych gustów i potrzeb; dwa poprzednie obiekty mają bardzo różne potrzeby pod względem podzbioru.

 41
Author: SabDeM,
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
2015-08-28 10:03:08

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 rozsądne, ponieważ różne funkcje oczekują różnych danych wejściowych i wytwarzają różne dane wyjściowe, ale większość z nich ma ogólny wspólny cel do oceny według serii/grup. Moja odpowiedź będzie skupiać się na wydajności. Ze względu na to, że tworzenie danych wejściowych z wektorów jest uwzględniane w czasie, również funkcja apply nie jest mierzona.

Przetestowałem dwie różne funkcje sum i length na raz. Testowana głośność to 50M na wejściu i 50k na wyjściu. Dodałem również dwa obecnie popularne pakiety, które nie były powszechnie używane w czasie zadawania pytań, data.table i dplyr. Oba są zdecydowanie warto szukać, jeśli dążysz do 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
 30
Author: jangorecki,
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
2015-12-08 22:50:30

Być może warto wspomnieć ave. To przyjacielski kuzyn. Zwraca wyniki w formie, którą można podłączyć 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 by działało jak ave dla całych ramek danych (tak jak by jest jak tapply dla ramek danych). Ale można 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
## ...
 21
Author: ,
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
2014-11-06 00:00:25

Pomimo wszystkich wspaniałych odpowiedzi tutaj, są jeszcze 2 funkcje podstawowe, które zasługują na wzmiankę, użyteczna outer Funkcja i niejasne eapply funkcja

Zewnętrzne

outer to bardzo przydatna funkcja ukryta jako bardziej przyziemna. Jeśli czytasz pomoc dla outer jej opis 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 jednak użyć podobnie jak mapply, aby zastosować funkcję do dwóch wektorów wejścia. Różnica polega na tym, że mapply zastosuje funkcję do dwóch pierwszych elementów, a następnie do dwóch drugich itd., podczas gdy outer zastosuje 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

Osobiście użyłem tego, gdy mam wektor wartości i Wektor warunków i chcę zobaczyć, które wartości spełniają które Warunki.

Eapply

eapply jest jak lapply z tym, że zamiast zastosowanie funkcji do każdego elementu na liście, to 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 często, ale jeśli budujesz wiele pakietów lub tworzysz wiele środowisk, może się to przydać.

 21
Author: John Paul,
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
2016-06-03 13:37:53

Niedawno odkryłem dość przydatną funkcję sweep i dodałem ją tutaj dla kompletności:

Sweep

Podstawową ideą jest zamiatanie przez tablicę w wierszu lub kolumnie i zwracanie zmodyfikowanej tablicy. Przykład to wyjaśni (Źródło: datacamp):

Powiedzmy, że masz macierz i chcesz standaryzować to:

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

NB: dla tego prostego przykładu ten sam wynik można oczywiście osiągnąć więcej łatwo przez
apply(dataPoints, 2, scale)

 8
Author: vonjd,
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-06-16 16:03:27