Raggruppare le funzioni (tapply, by, aggregate) e la famiglia * apply


1041

Ogni volta che voglio fare qualcosa di "mappa" in R, di solito cerco di usare una funzione in applyfamiglia.

Tuttavia, non ho mai capito bene le differenze tra loro - in che modo { sapply, lapplyecc.} Applicare la funzione all'input / input raggruppato, come apparirà l'output o anche come può essere l'input, quindi spesso esaminali tutti finché non ottengo ciò che voglio.

Qualcuno può spiegare come utilizzare quale quando?

La mia comprensione attuale (probabilmente errata / incompleta) è ...

  1. sapply(vec, f): input è un vettore. l'output è un vettore / matrice, dove l'elemento iè f(vec[i]), che ti dà una matrice se fha un output multi-elemento

  2. lapply(vec, f): uguale a sapply, ma l'output è un elenco?

  3. apply(matrix, 1/2, f): input è una matrice. l'output è un vettore, dove elemento iè f (riga / colonna della matrice)
  4. tapply(vector, grouping, f): output è una matrice / matrice, in cui un elemento nella matrice / matrice è il valore di fin un raggruppamento gdel vettore e gviene inviato ai nomi di riga / col
  5. by(dataframe, grouping, f): lascia che gsia un raggruppamento. si applicano fa ciascuna colonna del gruppo / frame di dati. stampa piuttosto il raggruppamento e il valore di fogni colonna.
  6. aggregate(matrix, grouping, f): simile a by, ma invece di stampare piuttosto l'output, aggregate inserisce tutto in un frame di dati.

Domanda a margine: non ho ancora imparato plyr o rimodellare - sostituirli plyro reshapesostituirli interamente?


33
alla tua domanda secondaria: per molte cose plyr è un sostituto diretto di *apply()e by. plyr (almeno per me) sembra molto più coerente in quanto so sempre esattamente quale formato di dati si aspetta e esattamente cosa sputerà. Questo mi risparmia molta seccatura.
JD Long,

12
Inoltre, consiglierei di aggiungere: doBye le funzionalità di selezione e applicazione di data.table.
Iteratore,

7
sapplyè solo lapplycon l'aggiunta di simplify2arraysull'output. applycostringe a vettore atomico, ma l'output può essere vettore o elenco. bysuddivide i frame di dati in sub-frame di dati, ma non li utilizza fseparatamente nelle colonne. Solo se esiste un metodo per la classe 'data.frame' potrebbe fessere applicato a livello di colonna by. aggregateè generico, quindi esistono metodi diversi per le diverse classi del primo argomento.
IRTFM,

8
Mnemonico: l sta per "lista", s sta per "semplificazione", t è per "per tipo" (ogni livello del raggruppamento è un tipo)
Lutz Prechelt

Esistono anche alcune funzioni nel pacchetto Rfast, come: eachcol.apply, apply.condition e altro, che sono più veloci degli equivalenti di R
Stefanos,

Risposte:


1330

R ha molte * funzioni di applicazione che sono abilmente descritte nei file della guida (ad es ?apply.). Ce ne sono abbastanza, tuttavia, che l'uso iniziale può avere difficoltà a decidere quale è appropriato per la loro situazione o anche a ricordarli tutti. Potrebbero avere un senso generale che "dovrei usare una funzione * apply qui", ma all'inizio può essere difficile tenerli tutti dritti.

Nonostante (notato in altre risposte) che gran parte delle funzionalità della famiglia * apply siano coperte dal plyrpacchetto estremamente popolare , le funzioni di base rimangono utili e vale la pena conoscerle.

Questa risposta ha lo scopo di agire come una sorta di segnavia per nuovi utilizzi per aiutarli a indirizzare alla corretta * applicare la funzione per il loro problema specifico. Nota, questo non intende semplicemente rigurgitare o sostituire la documentazione R! La speranza è che questa risposta ti aiuti a decidere quale funzione * applicare si adatta alla tua situazione e quindi spetta a te ricercarla ulteriormente. Con una sola eccezione, le differenze di prestazioni non verranno affrontate.

  • applica : quando si desidera applicare una funzione alle righe o alle colonne di una matrice (e analoghi di dimensioni superiori); non è generalmente consigliabile per i frame di dati in quanto verrà forzato prima su una matrice.

    # 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

    Se si desidera che i mezzi riga / colonna o somme per una matrice 2D, assicurarsi di indagare il altamente ottimizzato, fulminea colMeans, rowMeans, colSums, rowSums.

  • lapply - Quando si desidera applicare una funzione a ciascun elemento di un elenco a turno e ottenere di nuovo un elenco.

    Questo è il cavallo di battaglia di molte altre funzioni * apply. Rimuovi il loro codice e spesso troverai lapplysotto.

    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 - Quando si desidera applicare una funzione a ciascun elemento di un elenco a turno, ma si desidera un vettore indietro, piuttosto che un elenco.

    Se ti ritrovi a scrivere unlist(lapply(...)), fermati e considera 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 

    In usi più avanzati di sapplyesso tenterà di forzare il risultato in un array multidimensionale, se appropriato. Ad esempio, se la nostra funzione restituisce vettori della stessa lunghezza, sapplyli utilizzerà come colonne di una matrice:

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

    Se la nostra funzione restituisce una matrice bidimensionale, sapplyfarà essenzialmente la stessa cosa, trattando ciascuna matrice restituita come un singolo vettore lungo:

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

    Se non specificato simplify = "array", nel qual caso utilizzerà le singole matrici per creare un array multidimensionale:

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

    Ognuno di questi comportamenti è ovviamente subordinato alla nostra funzione di restituire vettori o matrici della stessa lunghezza o dimensione.

  • vapply - Quando vuoi usare sapplyma forse devi spremere un po 'più di velocità dal tuo codice.

    Perché vapply, in pratica, dai a R un esempio di quale tipo di cosa restituirà la tua funzione, il che può far risparmiare tempo costringendo i valori restituiti a rientrare in un singolo vettore atomico.

    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 - Per quando hai diverse strutture di dati (ad es. vettori, elenchi) e vuoi applicare una funzione ai primi elementi di ciascuno, e poi ai secondi elementi di ciascuno, ecc., forzando il risultato su un vettore / array come in sapply.

    Questo è multivariato, nel senso che la tua funzione deve accettare più argomenti.

    #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
  • Mappa : un wrapper mapplycon SIMPLIFY = FALSE, quindi è garantito che restituisca un elenco.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
  • rapply - Per quando si desidera applicare una funzione a ciascun elemento di una struttura di elenco nidificata , in modo ricorsivo.

    Per darti un'idea di quanto sia raro rapply, me ne sono dimenticato quando ho pubblicato questa risposta per la prima volta! Ovviamente, sono sicuro che molte persone lo usano, ma YMMV. rapplyè meglio illustrato con una funzione definita dall'utente per applicare:

    # 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 - Per quando si desidera applicare una funzione ai sottoinsiemi di un vettore e i sottoinsiemi sono definiti da qualche altro vettore, generalmente un fattore.

    Le pecore nere della famiglia * applicano, in qualche modo. L'uso del file di aiuto della frase "array sfilacciato" può essere un po ' confuso , ma in realtà è abbastanza semplice.

    Un vettore:

    x <- 1:20

    Un fattore (della stessa lunghezza!) Che definisce i gruppi:

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

    Aggiungi i valori xall'interno di ciascun sottogruppo definito da y:

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

    È possibile gestire esempi più complessi in cui i sottogruppi sono definiti dalle combinazioni univoche di un elenco di diversi fattori. tapplyè simile nello spirito alla split-applica-combinare le funzioni comuni a R ( aggregate, by, ave, ddply, ecc) Quindi lo stato pecora nera.


32
Credi che scoprirai che byè puro spaccatura ed aggregateè tapplyal loro centro. Penso che le pecore nere producano un tessuto eccellente.
IRTFM,

21
Risposta fantastica! Questo dovrebbe far parte della documentazione ufficiale R :). Un piccolo suggerimento: forse aggiungi alcuni punti sull'uso aggregatee bypure? (Finalmente li capisco dopo la tua descrizione !, ma sono piuttosto comuni, quindi potrebbe essere utile separarli e avere alcuni esempi specifici per quelle due funzioni.)
Grautur

3
@grautur Stavo attivamente eliminando le cose da questa risposta per evitare che fosse (a) troppo lungo e (b) una riscrittura della documentazione. Ho deciso che mentre aggregate, byecc. Si basano su * applicare le funzioni, il modo in cui ti avvicini al loro utilizzo è abbastanza diverso dal punto di vista degli utenti che dovrebbero essere riassunti in una risposta separata. Potrei tentare di farlo se avrò tempo, o forse qualcun altro mi batterà e guadagnerà il mio voto.
joran,

4
inoltre, ?Mapcome parente dimapply
battista il

3
@jsanders - Non sarei affatto d'accordo. data.frames sono una parte assolutamente centrale di R e poiché un listoggetto viene frequentemente manipolato usando in lapplyparticolare. Agiscono anche come contenitori per raggruppare vettori / fattori di molti tipi in un set di dati rettangolare tradizionale. Mentre data.tablee plyrpotrebbero aggiungere un certo tipo di sintassi che alcuni potrebbero trovare più comodi, si stanno estendendo e agendo data.framerispettivamente su s.
thelatemail

191

Nella nota a margine , ecco come le varie plyrfunzioni corrispondono alle *applyfunzioni di base (dall'introduzione al documento plyr dalla pagina web 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 

Uno degli obiettivi di plyrè fornire convenzioni di denominazione coerenti per ciascuna delle funzioni, codificando i tipi di dati di input e output nel nome della funzione. Fornisce inoltre coerenza nell'output, in quanto l'output dlply()è facilmente passabile ldply()per produrre output utili, ecc.

Concettualmente, l'apprendimento plyrnon è più difficile della comprensione delle *applyfunzioni di base .

plyre le reshapefunzioni hanno sostituito quasi tutte queste funzioni nel mio uso quotidiano. Ma, anche dal documento Intro a Plyr:

Funzioni correlate tapplye sweepnon hanno alcuna funzione corrispondente plyr, e rimangono utile. mergeè utile per combinare i riepiloghi con i dati originali.


13
Quando ho iniziato a studiare la R da zero, ho trovato Plyr MOLTO più facile da imparare rispetto alla *apply()famiglia di funzioni. Per me, è ddply()stato molto intuitivo poiché conoscevo le funzioni di aggregazione SQL. ddply()divenne il mio martello per risolvere molti problemi, alcuni dei quali avrebbero potuto essere risolti meglio con altri comandi.
JD Long,

1
Immagino di aver capito che il concetto alla base delle plyrfunzioni è simile alle *applyfunzioni, quindi se puoi farne uno, puoi fare l'altro, ma le plyrfunzioni sono più facili da ricordare. Ma sono totalmente d'accordo sul ddply()martello!
JoFrhwld,

1
Il pacchetto plyr ha la join()funzione che esegue attività simili all'unione . Forse è più che importante menzionarlo nel contesto di plyr.
Marbel

Non dimentichiamo il povero, dimenticatoeapply
JDL

Ottima risposta in generale, ma penso che minimizzi l'utilità vapplye gli aspetti negativi di sapply. Un grande vantaggio vapplyè che impone il tipo e la lunghezza dell'output, quindi si otterrà l'output previsto esatto o un errore informativo. D'altra parte, sapplycercherà di semplificare l'output seguendo regole che non sono sempre ovvie, e altrimenti torneremo a un elenco. Per esempio, cercare di prevedere il tipo di uscita questo produrrà: sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1)). Che dire sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)?
Alexey Shiklomanov,

133

Dalla diapositiva 21 di http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy :

applicare, sapply, lapply, by, aggregate

(Speriamo che sia chiaro che applycorrisponde a @ Hadley aaplye aggregatecorrisponde a @ Hadley ddplyecc. La diapositiva 20 della stessa slidelist chiarirà se non la ottieni da questa immagine.)

(a sinistra viene inserito, in alto viene emesso)


4
c'è un refuso nella diapositiva? La cella in alto a sinistra dovrebbe essere aaply
JHowIX

100

Per prima cosa inizia con l'eccellente risposta di Joran : ogni cosa può migliorare.

Quindi i seguenti mnemonici possono aiutare a ricordare le distinzioni tra ciascuno. Mentre alcuni sono ovvi, altri potrebbero esserlo di meno --- per questi troverai giustificazione nelle discussioni di Joran.

mnemonics

  • lapplyè un elenco applicato che agisce su un elenco o vettore e restituisce un elenco.
  • sapplyè una funzione semplice lapply (per impostazione predefinita la funzione restituisce un vettore o una matrice quando possibile)
  • vapplyè un'applicazione verificata (consente di specificare il tipo di oggetto restituito)
  • rapply è un ricorsiva per elenchi nidificati, ovvero elenchi all'interno di elenchi
  • tapply è un tag applicato dove i tag identificano i sottoinsiemi
  • apply è generico : applica una funzione alle righe o alle colonne di una matrice (o, più in generale, alle dimensioni di un array)

Costruire lo sfondo giusto

Se si utilizza il apply famiglia ti sembra ancora un po 'estraneo, allora potresti non avere un punto di vista chiave.

Questi due articoli possono aiutare. Forniscono il background necessario per motivare le tecniche di programmazione funzionale fornite daapply famiglia di funzioni.

Gli utenti di Lisp riconosceranno immediatamente il paradigma. Se non hai familiarità con Lisp, una volta che hai la testa intorno a FP, avrai acquisito un potente punto di vista per l'uso in R - e applyavrai molto più senso.


51

Da quando ho capito che (l'eccellente) risposte a questo post mancano bye aggregatespiegazioni. Ecco il mio contributo.

DI

La byfunzione, come indicato nella documentazione, può essere però un "wrapper" per tapply. Il potere di bysorge quando vogliamo calcolare un compito che tapplynon è in grado di gestire. Un esempio è questo codice:

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 

Se stampiamo questi due oggetti cte cb"essenzialmente" otteniamo gli stessi risultati e le uniche differenze sono nel modo in cui sono mostrati e nei diversi classattributi, rispettivamente byper cbe arrayperct .

Come ho già detto, il potere di bysorge quando non possiamo usarlotapply ; il seguente codice è un esempio:

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

R dice che gli argomenti devono avere le stesse lunghezze, ad esempio "vogliamo calcolare il summary di tutte le variabili irislungo il fattoreSpecies ": ma R semplicemente non può farlo perché non sa come gestirlo.

Con la byfunzione R invia un metodo specifico per la data frameclasse e poi lascia chesummary funzione funzioni anche se la lunghezza del primo argomento (e anche il tipo) sono diverse.

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     

funziona davvero e il risultato è molto sorprendente. È un oggetto di classe byche insiemeSpecies (diciamo, per ciascuno di essi) calcola il valore summarydi ciascuna variabile.

Notare che se il primo argomento è a data frame, la funzione spedita deve avere un metodo per quella classe di oggetti. Ad esempio, usiamo questo codice con la meanfunzione che avremo questo codice che non ha alcun senso:

 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

AGGREGATO

aggregatepuò essere visto come un altro modo diverso di utilizzarlo tapplyse lo utilizziamo in questo modo.

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

Le due differenze immediate sono che il secondo argomento di aggregate deve essere un elenco mentre tapply può (non obbligatorio) essere un elenco e che l'output di aggregateè un frame di dati mentre quello ditapply è unarray .

Il potere di aggregateè che può gestire facilmente sottoinsiemi di dati con subsetargomento e che ha metodi per tsoggetti eformula pure.

Questi elementi aggregatefacilitano il lavoro tapplyin alcune situazioni. Ecco alcuni esempi (disponibili nella documentazione):

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

Possiamo ottenere lo stesso con tapplyma la sintassi è leggermente più difficile e l'output (in alcune circostanze) meno leggibile:

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

Ci sono altre volte in cui non possiamo usare byo tapplye dobbiamo usare 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

Non possiamo ottenere il risultato precedente con tapplyin una chiamata, ma dobbiamo calcolare la media insieme Monthper ogni elemento e quindi combinarli (nota anche che dobbiamo chiamare il na.rm = TRUE, perché i formulametodi della aggregatefunzione hanno di default il 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

mentre con bynon possiamo proprio conseguire che in effetti la seguente chiamata di funzione restituisce un errore (ma molto probabilmente è correlata alla funzione fornita mean):

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

Altre volte i risultati sono gli stessi e le differenze sono solo nell'oggetto class (e quindi come viene mostrato / stampato e non solo - esempio, come sottoinsediarlo):

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

Il codice precedente raggiunge lo stesso obiettivo e gli stessi risultati, in alcuni punti quale strumento usare è solo una questione di gusti e bisogni personali; i due oggetti precedenti hanno esigenze molto diverse in termini di subsetting.


Come ho già detto, il potere di by nasce quando non possiamo usare tapply; il seguente codice è un esempio: QUESTE SONO LE PAROLE CHE HAI USATO SOPRA. E hai fornito un esempio di calcolo del riepilogo. Bene diciamo che le statistiche riassuntive possono essere calcolate solo che dovranno essere data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))ripulite : ad esempio questo è un uso di 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

Ci sono molte grandi risposte che discutono le differenze nei casi d'uso per ciascuna funzione. Nessuna delle risposte discute le differenze nelle prestazioni. Ciò è ragionevole perché varie funzioni prevedono vari input e producono vari output, ma la maggior parte di essi ha un obiettivo comune generale da valutare per serie / gruppi. La mia risposta si concentrerà sulle prestazioni. A causa di quanto sopra la creazione di input dai vettori è inclusa nel timing, anche ilapply funzione non viene misurata.

Ho testato due diverse funzioni sume lengthcontemporaneamente. Il volume testato è di 50 M in ingresso e 50 K in uscita. Ho anche incluso due pacchetti attualmente popolari che non erano ampiamente utilizzati al momento in cui è stata posta la domanda, data.tablee dplyr. Entrambi vale sicuramente la pena guardare se si punta a buone prestazioni.

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

È normale che dplyr sia inferiore alle funzioni applt?
Mostafa,

1
@DimitriPetrenko Non credo, non so perché sia ​​qui. È meglio testare i tuoi dati, poiché ci sono molti fattori che entrano in gioco.
jangorecki,

28

Nonostante tutte le ottime risposte qui, ci sono altre 2 funzioni di base che meritano di essere menzionate, la outerfunzione utile e la eapplyfunzione oscura

esterno

outerè una funzione molto utile nascosta come più banale. Se leggi l'aiuto per la outersua descrizione dice:

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], ...).

il che fa sembrare che questo sia utile solo per cose di algebra lineare. Tuttavia, può essere usato in modo molto simile mapplyper applicare una funzione a due vettori di input. La differenza è che mapplyapplicherà la funzione ai primi due elementi e poi ai secondi due ecc., Mentre outerapplicherà la funzione a ogni combinazione di un elemento dal primo vettore e uno dal secondo. Per esempio:

 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

L'ho usato personalmente quando ho un vettore di valori e un vettore di condizioni e desidero vedere quali valori soddisfano quali condizioni.

eapply

eapplyè come lapplytranne che piuttosto che applicare una funzione a tutti gli elementi in un elenco, applica una funzione a tutti gli elementi in un ambiente. Ad esempio, se si desidera trovare un elenco di funzioni definite dall'utente nell'ambiente globale:

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 

Francamente non lo uso molto, ma se stai costruendo molti pacchetti o crei molti ambienti, potrebbe tornare utile.


25

Vale forse la pena menzionarlo ave. aveè tapplycugino amichevole. Restituisce i risultati in un modulo che è possibile ricollegare direttamente nel frame di dati.

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
##   ...

Non c'è nulla nel pacchetto di base che avefunzioni come per interi frame di dati ( bycome tapplyper i frame di dati). Ma puoi sfumarlo:

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

Di recente ho scoperto il piuttosto utile sweep funzione e l'ho aggiunta qui per completezza:

spazzare

L'idea di base è quella di spazzare attraverso una matrice di riga o colonna-saggio e restituire una matrice modificata. Un esempio lo chiarirà (fonte: datacamp ):

Diciamo che hai una matrice e vuoi standardizzarla per colonna:

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: per questo semplice esempio lo stesso risultato può ovviamente essere raggiunto più facilmente con
apply(dataPoints, 2, scale)


1
Questo è legato al raggruppamento?
Frank,

2
@Frank: Beh, ad essere sincero con te il titolo di questo post è piuttosto fuorviante: quando leggi la domanda stessa si tratta di "la famiglia di applicare". sweepè una funzione di ordine superiore come tutte le altre citate qui, ad es apply. sapply, lapplyQuindi la stessa domanda potrebbe essere posta sulla risposta accettata con oltre 1.000 voti e gli esempi ivi riportati. Dai un'occhiata all'esempio fornito applylì.
vonjd,

2
sweep ha un nome fuorviante, valori predefiniti fuorvianti e nome del parametro fuorviante :). È più facile per me capirlo in questo modo: 1) STATS è un vettore o un valore singolo che verrà ripetuto per formare una matrice della stessa dimensione del primo input, 2) FUN verrà applicato al 1 ° input e questa nuova matrice. Forse meglio illustrato da: sweep(matrix(1:6,nrow=2),2,7:9,list). Di solito è più efficiente rispetto a applyquando i applyloop, sweepè in grado di utilizzare funzioni vettorializzate.
Moody_Mudskipper

2

Nel pacchetto di compressione recentemente rilasciato su CRAN, ho tentato di comprimere la maggior parte delle funzionalità di applicazione comuni in sole 2 funzioni:

  1. dapply(Applica dati) applica funzioni alle righe o alle colonne (predefinite) di matrici e data.frames e (impostazione predefinita) restituisce un oggetto dello stesso tipo e con gli stessi attributi (a meno che il risultato di ciascun calcolo non sia atomico e drop = TRUE). Le prestazioni sono paragonabili a quelle lapplyper le colonne data.frame e circa 2 volte più veloci rispetto applyalle colonne o alle colonne della matrice. Il parallelismo è disponibile via mclapply(solo per MAC).

Sintassi:

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

Esempi:

# 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. BYè un generico S3 per il calcolo split-apply-combina con metodo vector, matrix e data.frame. È significativamente più veloce di tapply, bye aggregate(anche più veloce di plyr, su dati di grandi dimensioni dplyrè però più veloce).

Sintassi:

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"))

Esempi:

# 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 ...

È inoltre possibile fornire un elenco di variabili di raggruppamento g.

Parlando di prestazioni: un obiettivo principale di collasso è promuovere la programmazione ad alte prestazioni in R e andare oltre la divisione-applicazione-combinazione tutto insieme. A questo scopo il pacchetto ha una serie completa di C ++ basato funzioni generiche veloce: fmean, fmedian, fmode, fsum, fprod, fsd, fvar, fmin, fmax, ffirst, flast, fNobs, fNdistinct, fscale, fbetween, fwithin, fHDbetween, fHDwithin, flag,fdiff e fgrowth. Eseguono calcoli raggruppati in un unico passaggio attraverso i dati (cioè senza divisione e ricombinazione).

Sintassi:

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

Esempi:

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 ...

Nelle vignette del pacchetto fornisco parametri di riferimento. La programmazione con le funzioni veloci è significativamente più veloce della programmazione con dplyr o data.table , specialmente su dati più piccoli, ma anche su dati di grandi dimensioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.