Frage Gruppierungsfunktionen (tapply, by, aggregate) und die * apply-Familie


Wann immer ich etwas "map" py in R machen will, versuche ich normalerweise eine Funktion in der apply Familie.

Ich habe jedoch nie die Unterschiede zwischen ihnen verstanden - wie {sapply, lapply, usw.} wenden Sie die Funktion auf den Input / gruppierten Input an, wie der Output aussehen wird oder sogar was der Input sein kann - so gehe ich oft einfach alle durch, bis ich bekomme, was ich will.

Kann jemand erklären, wie man welchen wann benutzt?

Mein aktuelles (wahrscheinlich inkorrektes / unvollständiges) Verständnis ist ...

  1. sapply(vec, f): Eingabe ist ein Vektor. Ausgabe ist ein Vektor / Matrix, wo Element i ist f(vec[i]), geben Sie eine Matrix, wenn f hat einen Multi-Element-Ausgang

  2. lapply(vec, f): gleich wie sapply, aber die Ausgabe ist eine Liste?

  3. apply(matrix, 1/2, f): Eingabe ist eine Matrix. Ausgabe ist ein Vektor, wo Element i ist f (Zeile / Spalte der Matrix)
  4. tapply(vector, grouping, f): Ausgabe ist eine Matrix / ein Array, wobei ein Element in der Matrix / dem Array der Wert von ist f bei einer Gruppierung g des Vektors und g wird zu den Zeilen- / Spaltennamen geschoben
  5. by(dataframe, grouping, f): Lassen g eine Gruppierung sein. sich bewerben f zu jeder Spalte der Gruppe / des Datenrahmens. Ziemlich print die Gruppierung und den Wert von f an jeder Spalte.
  6. aggregate(matrix, grouping, f): ähnlich zu by, aber anstatt die Ausgabe hübsch zu drucken, speichert das Aggregat alles in einem Datenrahmen.

Nebenfrage: Ich habe noch nicht plur gelernt oder umgestalten - würde plyr oder reshape Ersetze alles vollständig?


915
2017-08-17 18:31


Ursprung


Antworten:


R hat viele * Anwendungsfunktionen, die in den Hilfedateien (z.B. ?apply). Es gibt jedoch genug von ihnen, dass die beginnenden Benutzer Schwierigkeiten haben, zu entscheiden, welcher für ihre Situation geeignet ist oder sich überhaupt an sie alle erinnert. Sie können einen allgemeinen Sinn haben, dass "ich eine * Funktion hier anwenden" sollte, aber es kann schwierig sein, sie alle zuerst auf den Punkt zu bringen.

Trotz der Tatsache (bemerkt in anderen Antworten), dass ein Großteil der Funktionalität der * apply-Familie von den extrem populären abgedeckt wird plyr Paket bleiben die Basisfunktionen nützlich und wissenswert.

Diese Antwort soll als eine Art von funktionieren Wegweiser für neue BenutzerRs, um ihnen zu helfen, sie auf die richtige * Anwendungsfunktion für ihr spezielles Problem zu lenken. Beachten Sie, das ist nicht soll die R-Dokumentation einfach erbrechen oder ersetzen! Die Hoffnung ist, dass diese Antwort Ihnen hilft, zu entscheiden, welche * Anwendung auf Ihre Situation zutrifft, und dann liegt es an Ihnen, sie weiter zu erforschen. Mit einer Ausnahme werden Leistungsunterschiede nicht behoben.

  • sich bewerben - Wenn Sie eine Funktion auf die Zeilen oder Spalten anwenden möchten einer Matrix (und höherdimensionaler Analoga); im Allgemeinen nicht für Datenrahmen ratsam, da es zuerst zu einer Matrix gezwungen wird.

    # 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
    

    Wenn Sie möchten, dass Zeile / Spalte bedeutet oder Summen für eine 2D-Matrix, sicher sein Untersuchen Sie die hochoptimierte, blitzschnelle colMeans, rowMeans, colSums, rowSums.

  • schlaflos - Wenn Sie eine Funktion auf jedes Element von a anwenden möchten Liste der Reihe nach und erhalten Sie eine Liste zurück.

    Dies ist das Arbeitspferd vieler anderer * Anwendungsfunktionen. Schälen Zurück ihren Code und Sie werden oft finden lapply unterhalb.

    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 - Wenn Sie eine Funktion auf jedes Element von a anwenden möchten Liste der Reihe nach, aber Sie wollen ein Vektor zurück, anstatt eine Liste.

    Wenn Sie sich selbst tippen unlist(lapply(...)), hör auf und überlege 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 fortgeschritteneren Anwendungen von sapply es wird versuchen, das zu erzwingen gegebenenfalls zu einem mehrdimensionalen Array führen. Zum Beispiel, wenn unsere Funktion Vektoren gleicher Länge zurückgibt, sapply werden sie als Spalten einer Matrix verwenden:

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

    Wenn unsere Funktion eine zweidimensionale Matrix zurückgibt, sapply wird im Wesentlichen die gleiche Sache machen und jede zurückgegebene Matrix als einen einzigen langen Vektor behandeln:

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

    Es sei denn wir spezifizieren simplify = "array"In diesem Fall werden die einzelnen Matrizen verwendet, um ein mehrdimensionales Array zu erstellen:

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

    Jedes dieser Verhaltensweisen hängt natürlich von unserer Funktion ab, die Vektoren oder Matrizen derselben Länge oder Dimension zurückgibt.

  • vapply - Wenn Sie verwenden möchten sapply aber vielleicht müssen Drücken Sie etwas mehr Geschwindigkeit aus Ihrem Code.

    Zum vapplyIm Grunde gibst du R ein Beispiel dafür, was für eine Sache Ihre Funktion wird zurückkehren, was einige Zeit erzwingen kann Werte, die in einen einzelnen atomaren Vektor passen.

    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 - Wenn Sie mehrere Datenstrukturen haben (z. Vektoren, Listen) und Sie möchten eine Funktion auf die 1. Elemente anwenden von jedem, und dann die zweiten Elemente von jedem, etc., das Ergebnis erzwingend zu einem Vektor / Array wie in sapply.

    Dies ist multivariat in dem Sinne, dass Ihre Funktion akzeptieren muss mehrere Argumente.

    #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
    
  • Karte - Ein Wrapper für mapply mit SIMPLIFY = FALSE, so ist es garantiert, eine Liste zurückzugeben.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
    
  • rappelig - Wenn Sie für jedes Element von a eine Funktion anwenden möchten verschachtelte Liste Struktur rekursiv.

    Um Ihnen eine Vorstellung davon zu geben, wie ungewöhnlich rapply Ich habe es vergessen, als ich diese Antwort zum ersten Mal gepostet habe! Natürlich bin ich sicher, dass viele Leute es benutzen, aber YMMV. rapply wird am besten mit einer benutzerdefinierten Funktion veranschaulicht, die angewendet werden kann:

    # 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 - Wenn Sie eine Funktion anwenden möchten Teilmengen von einem Vektor und die Teilmengen sind durch einen anderen Vektor definiert, üblicherweise durch a Faktor.

    Die schwarzen Schafe der * gelten Familie. Die Verwendung der Hilfedatei von die Phrase "Ragged Array" kann ein bisschen sein verwirrend, aber es ist tatsächlich ziemlich einfach.

    Ein Vektor:

    x <- 1:20
    

    Ein Faktor (von gleicher Länge!), Der Gruppen definiert:

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

    Addiere die Werte in x innerhalb jeder durch y:

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

    Komplexere Beispiele können behandelt werden, wo die Untergruppen definiert sind durch die einzigartigen Kombinationen einer Liste von mehreren Faktoren. tapply ist ähnlich im Geist zu den Split-Apply-kombinieren Funktionen, die sind gemeinsam in R (aggregate, by, ave, ddplyusw.) Daher ist es Status der schwarzen Schafe.


1204
2017-08-21 22:50



Auf der Randnotiz ist hier wie die verschiedenen plyr Funktionen entsprechen der Basis *apply Funktionen (vom Intro- bis zum Plyr-Dokument von der Plyr-Webseite) 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 

Eines der Ziele von plyr besteht darin, konsistente Benennungskonventionen für jede der Funktionen bereitzustellen und die Eingabe- und Ausgabedatentypen im Funktionsnamen zu codieren. Es bietet auch Konsistenz in der Ausgabe, in der Ausgabe von dlply() ist leicht befahrbar ldply() um nützliche Ausgaben usw. zu erzeugen

Konzeptionell lernen plyr ist nicht schwieriger als das Verständnis der Basis *apply Funktionen.

plyr und reshape Funktionen haben fast alle diese Funktionen in meinem täglichen Gebrauch ersetzt. Aber auch aus dem Intro zu Plyr Dokument:

Ähnliche Funktionen tapply und sweep habe keine entsprechende Funktion in plyrund bleiben nützlich. merge ist nützlich, um Zusammenfassungen mit den Originaldaten zu kombinieren.


171
2017-08-17 19:20



Von Folie 21 von http://www.slideshare.net/hadley/plyr-one-data-analytic-strategie:

apply, sapply, lapply, by, aggregate

(Hoffentlich ist das klar apply entspricht @ Hadleys aaply und aggregate entspricht @ Hadleys ddply etc. Slide 20 der gleichen Slideshare wird klären, wenn Sie es nicht von diesem Bild bekommen.)

(links ist Eingabe, oben wird ausgegeben)


118
2017-10-09 05:29



Beginnen Sie zuerst mit Jorans ausgezeichnete Antwort - zweifelhaft, irgendetwas kann das besser.

Dann können die folgenden Mnemotechniken helfen, sich an die Unterschiede zwischen den beiden zu erinnern. Während einige offensichtlich sind, andere vielleicht weniger - für diese finden Sie in Jorans Diskussionen Rechtfertigung.

Mnemonik

  • lapply ist ein Liste apply, das auf eine Liste oder einen Vektor wirkt und eine Liste zurückgibt.
  • sapply ist ein einfach  lapply (Die Funktion gibt standardmäßig einen Vektor oder eine Matrix zurück, wenn möglich)
  • vapply ist ein überprüft gelten (erlaubt die Angabe des Rückgabeobjekttyps)
  • rapply ist ein rekursiv gelten für verschachtelte Listen, d. h. Listen innerhalb von Listen
  • tapply ist ein markiert Anwenden, wo die Tags die Teilmengen identifizieren
  • apply  ist generisch: wendet eine Funktion auf die Zeilen oder Spalten einer Matrix (oder allgemeiner auf die Dimensionen eines Arrays) an

Den richtigen Hintergrund aufbauen

Wenn Sie die apply Die Familie fühlt sich immer noch etwas fremd an, dann könnte es sein, dass du einen wichtigen Standpunkt verpasst.

Diese zwei Artikel können helfen. Sie bieten den notwendigen Hintergrund, um das zu motivieren funktionale Programmiertechniken das wird von der apply Familie von Funktionen.

Benutzer von Lisp werden das Paradigma sofort erkennen. Wenn Sie mit Lisp nicht vertraut sind, sobald Sie sich mit FP vertraut gemacht haben, haben Sie einen starken Standpunkt für den Einsatz in R - und apply wird viel mehr Sinn machen.


88
2018-04-25 00:20



Da merke ich, dass (die sehr guten) Antworten dieses Postmangels fehlen by und aggregate Erklärungen. Hier ist mein Beitrag.

DURCH

Das by Funktion, wie in der Dokumentation angegeben, kann jedoch als "Wrapper" für tapply. Die kraft von by entsteht, wenn wir eine Aufgabe berechnen wollen, die tapplykann nicht umgehen. Ein Beispiel ist dieser Code:

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 

Wenn wir diese beiden Objekte drucken, ct und cb, wir "im Wesentlichen" haben die gleichen Ergebnisse und die einzigen Unterschiede sind in wie sie gezeigt werden und die verschiedenen class Attribute jeweils by zum cb und array zum ct.

Wie gesagt, die Kraft von by entsteht, wenn wir nicht verwenden können tapply; Der folgende Code ist ein Beispiel:

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

R sagt, dass Argumente die gleiche Länge haben müssen, sagen wir: "Wir wollen das berechnen summary aller Variablen in iris entlang des Faktors Species": Aber R kann das nicht tun, weil es nicht damit umgehen kann.

Mit dem by Funktion R verschicken eine spezifische Methode für data frame Klasse und dann lass das summary Funktion funktioniert auch, wenn die Länge des ersten Arguments (und auch der Typ) unterschiedlich ist.

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     

Es funktioniert tatsächlich und das Ergebnis ist sehr überraschend. Es ist ein Objekt der Klasse by das mit Species (sagen wir für jeden von ihnen) berechnet die summary jeder Variable.

Beachten Sie, dass wenn das erste Argument ein a ist data frame, die abgesetzte Funktion muss eine Methode für diese Objektklasse haben. Zum Beispiel benutzen wir diesen Code mit dem mean Funktion werden wir diesen Code haben, der überhaupt keinen Sinn hat:

 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

AGGREGAT

aggregate kann als ein anderer eine andere Art der Nutzung gesehen werden tapply wenn wir es so benutzen.

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

Die zwei unmittelbaren Unterschiede sind, dass das zweite Argument von aggregate  Muss Sei eine Liste während tapply  kann (nicht zwingend) sei eine Liste und die Ausgabe von aggregate ist ein Datenrahmen während derjenige von tapply ist ein array.

Die kraft von aggregate ist, dass es leicht Teilmengen der Daten mit behandeln kann subset Argument und dass es Methoden für ts Objekte und formula auch.

Diese Elemente machen aggregate einfacher damit zu arbeiten tapply in manchen Situationen. Hier einige Beispiele (verfügbar in der Dokumentation):

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

Das können wir auch erreichen tapply aber die Syntax ist etwas härter und die Ausgabe (unter Umständen) weniger lesbar:

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

Es gibt andere Zeiten, in denen wir nicht benutzen können by oder tapply und wir müssen es benutzen 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

Wir können das vorherige Ergebnis nicht mit erhalten tapply in einem Anruf, aber wir müssen den Mittelwert berechnen Month für jedes Element und dann kombinieren sie (auch beachten Sie, dass wir die anrufen müssen na.rm = TRUE, weil das formula Methoden der aggregate Funktion hat standardmäßig die 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

während mit by wir können einfach nicht erreichen, dass der folgende Funktionsaufruf tatsächlich einen Fehler zurückgibt (aber höchstwahrscheinlich hängt er mit der gelieferten Funktion zusammen, mean):

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

Zu anderen Zeiten sind die Ergebnisse gleich und die Unterschiede sind nur in der Klasse (und dann, wie es gezeigt / gedruckt wird und nicht nur - Beispiel, wie es Teilmenge) Objekt:

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

Der vorherige Code erreicht das gleiche Ziel und die gleichen Ergebnisse. An einigen Stellen ist das zu verwendende Werkzeug nur eine Frage des persönlichen Geschmacks und der Bedürfnisse; Die vorherigen zwei Objekte haben sehr unterschiedliche Bedürfnisse hinsichtlich der Teilmenge.


39
2017-08-28 02:28



Es gibt viele gute Antworten, die Unterschiede in den Anwendungsfällen für jede Funktion diskutieren. Keine der Antworten diskutiert die Unterschiede in der Leistung. Dies ist vernünftig, da verschiedene Funktionen verschiedene Eingaben erwarten und verschiedene Ausgaben erzeugen, wobei die meisten von ihnen jedoch ein allgemeines gemeinsames Ziel haben, nach Reihen / Gruppen zu bewerten. Meine Antwort wird sich auf die Leistung konzentrieren. Aufgrund der obigen Eingabe ist die Erzeugung aus den Vektoren im Timing enthalten, auch die applyFunktion wird nicht gemessen.

Ich habe zwei verschiedene Funktionen getestet sum und length auf einmal. Das getestete Volumen beträgt 50M am Eingang und 50K am Ausgang. Ich habe auch zwei derzeit beliebte Pakete hinzugefügt, die zu der Zeit, als die Frage gestellt wurde, nicht weit verbreitet waren. data.table und dplyr. Beides lohnt sich auf jeden Fall, wenn Sie eine gute Leistung anstreben.

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
2017-12-08 22:42



Es ist vielleicht erwähnenswert ave. ave ist tapply's freundliche Cousine. Sie gibt Ergebnisse in einem Formular zurück, das Sie direkt in Ihren Datenrahmen einfügen können.

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

Es gibt nichts im Basispaket, das funktioniert ave für ganze Datenrahmen (wie by ist wie tapply für Datenrahmen). Aber Sie können es täuschen:

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

20
2017-11-06 00:00



Trotz all der tollen Antworten gibt es 2 weitere Basisfunktionen, die es verdienen, erwähnt zu werden, das Nützliche outer Funktion und das Obskure eapply Funktion

äußere

outer ist eine sehr nützliche Funktion, die als etwas Alltägliches verborgen ist. Wenn Sie die Hilfe für lesen outer Seine Beschreibung sagt:

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

was es so erscheinen lässt, dass dies nur für Dinge der linearen Algebra nützlich ist. Es kann jedoch sehr ähnlich verwendet werden mapply Anwenden einer Funktion auf zwei Vektoren von Eingaben. Der Unterschied ist der mapply wird die Funktion auf die ersten beiden Elemente und dann die beiden anderen usw. anwenden, während outer wendet die Funktion auf jede Kombination eines Elements vom ersten Vektor und eins vom zweiten an. Beispielsweise:

 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

Ich habe das persönlich verwendet, wenn ich einen Vektor von Werten und einen Vektor von Bedingungen habe und möchte sehen, welche Werte welche Bedingungen erfüllen.

wenden Sie an

eapply ist wie lapply Mit der Ausnahme, dass keine Funktion auf jedes Element in einer Liste angewendet wird, wendet sie eine Funktion auf jedes Element in einer Umgebung an. Zum Beispiel, wenn Sie eine Liste von benutzerdefinierten Funktionen in der globalen Umgebung finden möchten:

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 

Ehrlich gesagt, benutze ich das nicht sehr, aber wenn Sie viele Pakete erstellen oder viele Umgebungen erstellen, kann es nützlich sein.


20
2018-05-16 03:59



Ich habe kürzlich das ziemlich Nützliche entdeckt sweep Funktion und füge es hier der Vollständigkeit halber hinzu:

fegen

Die Grundidee ist zu fegen durch ein Array reihen- oder spaltenweise und gibt ein modifiziertes Array zurück. Ein Beispiel wird dies verdeutlichen (Quelle: Datenspeicher):

Nehmen wir an, Sie haben eine Matrix und möchten das standardisieren es spaltenweise:

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: Für dieses einfache Beispiel kann natürlich das gleiche Ergebnis leichter erreicht werden
  apply(dataPoints, 2, scale)


5
2018-06-16 16:03