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


1040

Wann immer ich etwas "map" py in R machen möchte, versuche ich normalerweise, eine Funktion in der applyFamilie zu verwenden.

Ich habe jedoch die Unterschiede zwischen ihnen nie ganz verstanden - wie { sapply, lapplyusw.} die Funktion auf die Eingabe / gruppierte Eingabe anwenden, wie die Ausgabe aussehen wird oder sogar wie die Eingabe sein kann - so habe ich es oft getan Gehen Sie sie einfach alle durch, bis ich das bekomme, was ich will.

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

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

  1. sapply(vec, f): Eingabe ist ein Vektor. Die Ausgabe ist ein Vektor / eine Matrix, wobei sich das Element ibefindet. f(vec[i])Wenn Sie eine fAusgabe mit mehreren Elementen haben , erhalten Sie eine Matrix

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

  3. apply(matrix, 1/2, f): Eingabe ist eine Matrix. Ausgabe ist ein Vektor, wobei Element if ist (Zeile / Spalte i der Matrix)
  4. tapply(vector, grouping, f): output ist eine Matrix / ein Array, wobei ein Element in der Matrix / dem Array der Wert feiner Gruppierung gdes Vektors ist und gin die Zeilen- / Spaltennamen verschoben wird
  5. by(dataframe, grouping, f): Sei geine Gruppierung. gelten ffür jede Spalte der Gruppe / des Datenrahmens. Drucken Sie die Gruppierung und den Wert fjeder Spalte hübsch aus .
  6. aggregate(matrix, grouping, f): Ähnlich wie by, aber anstatt die Ausgabe hübsch zu drucken, steckt das Aggregat alles in einen Datenrahmen.

Nebenfrage: Ich habe Plyr noch nicht gelernt oder umgeformt - würde plyroder würde ich reshapeall dies komplett ersetzen?


33
zu Ihrer Nebenfrage: Für viele Dinge ist Plyr ein direkter Ersatz für *apply()und by. plyr (zumindest für mich) scheint viel konsistenter zu sein, da ich immer genau weiß, welches Datenformat es erwartet und was es genau ausspucken wird. Das erspart mir viel Ärger.
JD Long

12
Außerdem würde ich empfehlen, Folgendes hinzuzufügen: doByund die Auswahl- und Anwendungsfunktionen von data.table.
Iterator

7
sapplyist nur lapplymit der Hinzufügung von simplify2arrayauf dem Ausgang. applyZwingt zum Atomvektor, aber die Ausgabe kann ein Vektor oder eine Liste sein. byteilt Datenrahmen in Unterdatenrahmen auf, wird jedoch nicht fseparat für Spalten verwendet. Nur wenn es eine Methode für die 'data.frame'-Klasse gibt, wird diese möglicherweise fspaltenweise angewendet by. aggregateist generisch, so dass für verschiedene Klassen des ersten Arguments unterschiedliche Methoden existieren.
IRTFM

8
Mnemonik: l steht für 'Liste', s steht für 'Vereinfachen', t steht für 'Pro Typ' (jede Ebene der Gruppierung ist ein Typ)
Lutz Prechelt

Es gibt auch einige Funktionen im Paket Rfast, wie: eachcol.apply, apply.condition und mehr, die schneller sind als Rs Äquivalente
Stefanos

Antworten:


1330

R hat viele * Apply-Funktionen, die in den Hilfedateien (z ?apply. B. ) ausführlich beschrieben sind . Es gibt jedoch genug davon, so dass es für Anfänger schwierig sein kann, zu entscheiden, welche für ihre Situation geeignet ist, oder sich sogar an sie alle zu erinnern. Sie mögen allgemein den Eindruck haben, dass "ich hier eine * Apply-Funktion verwenden sollte", aber es kann schwierig sein, sie zunächst alle gerade zu halten.

Trotz der Tatsache (in anderen Antworten vermerkt), dass ein Großteil der Funktionalität der * apply-Familie durch das äußerst beliebte plyrPaket abgedeckt wird , bleiben die Basisfunktionen nützlich und wissenswert.

Diese Antwort soll als eine Art Wegweiser für neue Benutzer dienen, um sie auf die richtige * Apply-Funktion für ihr bestimmtes Problem hinzuweisen. Beachten Sie, dass dies nicht dazu gedacht ist, die R-Dokumentation einfach wieder zu erbrechen oder zu ersetzen! Die Hoffnung ist, dass diese Antwort Ihnen hilft, zu entscheiden, welche * Apply-Funktion zu Ihrer Situation passt, und dann liegt es an Ihnen, sie weiter zu erforschen. Mit einer Ausnahme werden Leistungsunterschiede nicht berücksichtigt.

  • anwenden - Wenn Sie eine Funktion auf die Zeilen oder Spalten einer Matrix (und höherdimensionale Analoga) anwenden möchten; Im Allgemeinen nicht empfehlenswert für Datenrahmen, da diese zuerst zu einer Matrix gezwungen werden.

    # 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 Zeilen / Spalten - Mittel oder Summen für eine 2D - Matrix möchten, müssen Sie die hochoptimierte, blitzschnell untersuchen colMeans, rowMeans, colSums, rowSums.

  • lapply - Wenn Sie nacheinander eine Funktion auf jedes Element einer Liste anwenden und eine Liste zurückerhalten möchten.

    Dies ist das Arbeitstier vieler anderer * Apply-Funktionen. Ziehen Sie ihren Code zurück und Sie werden ihn oft lapplydarunter finden .

    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 nacheinander eine Funktion auf jedes Element einer Liste anwenden möchten, aber einen Vektor anstelle einer Liste zurückhaben möchten .

    Wenn Sie tippen unlist(lapply(...)), hören Sie auf und überlegen Sie 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 

    Bei fortgeschritteneren Anwendungen sapplywird versucht, das Ergebnis gegebenenfalls in ein mehrdimensionales Array zu zwingen. Wenn unsere Funktion beispielsweise Vektoren gleicher Länge zurückgibt, sapplywerden sie als Spalten einer Matrix verwendet:

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

    Wenn unsere Funktion eine zweidimensionale Matrix zurückgibt, sapplywird sie im Wesentlichen dasselbe tun und jede zurückgegebene Matrix als einen einzelnen langen Vektor behandeln:

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

    Sofern nicht simplify = "array"anders angegeben , werden in diesem Fall 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 davon ab, dass unsere Funktion Vektoren oder Matrizen gleicher Länge oder Dimension zurückgibt.

  • vapply - Wenn Sie verwenden möchten, aber möglicherweise etwas mehr Geschwindigkeit aus Ihrem Code herausholen müssen.sapply

    Für vapplygeben R Sie im Grunde ein Beispiel , welche Art von Sache zurückkehren Ihre Funktion, die einige Zeit Nötigung zurückgegebenen Werte speichern können in einem einzigen Atom-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. B. Vektoren, Listen) und eine Funktion auf die ersten Elemente von jedem und dann auf die zweiten Elemente von jedem usw. anwenden möchten, um das Ergebnis wie in einem Vektor / Array zu erzwingen sapply.

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

    #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
  • Map - Ein Wrapper, mapplymit dem SIMPLIFY = FALSEgarantiert wird, dass eine Liste zurückgegeben wird.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
  • rapply - Wenn Sie eine Funktion rekursiv auf jedes Element einer verschachtelten Listenstruktur anwenden möchten .

    Um Ihnen eine Vorstellung davon zu geben, wie ungewöhnlich es rapplyist, habe ich es beim ersten Posten dieser Antwort vergessen! Natürlich bin ich sicher, dass viele Leute es benutzen, aber YMMV. rapplywird am besten mit einer benutzerdefinierten Funktion veranschaulicht, die angewendet werden soll:

    # 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 auf Teilmengen eines Vektors anwenden möchten und die Teilmengen durch einen anderen Vektor definiert werden, normalerweise einen Faktor.

    Die schwarzen Schafe der * bewerben Familie. Die Verwendung des Ausdrucks "zerlumptes Array" in der Hilfedatei kann etwas verwirrend sein , ist aber eigentlich recht einfach.

    Ein Vektor:

    x <- 1:20

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

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

    Addieren Sie die Werte in xjeder Untergruppe, die definiert ist durch y:

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

    Komplexere Beispiele können behandelt werden, wenn die Untergruppen durch die eindeutigen Kombinationen einer Liste mehrerer Faktoren definiert sind. tapplyähnlich ist die Split-apply-Mähdrescherfunktionen im Geist , die gemeinsam in R ( aggregate, by, ave, ddplyStatus, etc.) Daher ihr schwarze Schafe.


32
Glauben Sie, Sie werden feststellen, dass dies byrein gespalten ist und aggregatesich tapplyin ihren Kernen befindet. Ich denke, schwarze Schafe sind ein ausgezeichneter Stoff.
IRTFM

21
Fantastische Antwort! Dies sollte Teil der offiziellen R-Dokumentation sein :). Ein kleiner Vorschlag: Vielleicht ein paar Aufzählungszeichen hinzufügen aggregateund byauch? (Ich verstehe sie schließlich nach Ihrer Beschreibung!, Aber sie sind ziemlich häufig, so dass es nützlich sein könnte, einige spezifische Beispiele für diese beiden Funktionen herauszulösen und zu haben.)
grautur

3
@grautur Ich habe aktiv Dinge aus dieser Antwort herausgeschnitten, um zu vermeiden, dass sie (a) zu lang ist und (b) die Dokumentation neu schreibt. Ich entschied , dass während aggregate, byetc. basiert auf * an Funktionen, wie du sie mit anderem Ansatz genug von einem Benutzer Perspektive ist , dass sie sollten in einer separaten Antwort zusammengefasst werden. Ich kann das versuchen, wenn ich Zeit habe, oder vielleicht schlägt mich jemand anderes und verdient meine Gegenstimme.
Joran

4
auch ?Mapals Verwandter vonmapply
baptiste

3
@jsanders - Dem würde ich überhaupt nicht zustimmen. data.frames sind ein absolut zentraler Bestandteil von R und werden als listObjekt häufig mit lapplybesonders manipuliert . Sie dienen auch als Container zum Gruppieren von Vektoren / Faktoren vieler Typen in einem herkömmlichen rechteckigen Datensatz. Während data.tableund plyrmöglicherweise eine bestimmte Art von Syntax hinzugefügt wird, die einige möglicherweise als komfortabler empfinden, erweitern und wirken sie auf data.frames.
E-Mail

191

Nebenbei bemerkt, hier ist, wie die verschiedenen plyrFunktionen den Basisfunktionen entsprechen *apply(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 plyrist es, konsistente Namenskonventionen für jede der Funktionen bereitzustellen und die Eingabe- und Ausgabedatentypen im Funktionsnamen zu codieren. Es bietet auch Konsistenz in der Ausgabe, da die Ausgabe von dlply()leicht passierbar ist ldply(), um nützliche Ausgabe usw. zu erzeugen.

Konzeptionell ist das Lernen plyrnicht schwieriger als das Verstehen der *applyBasisfunktionen.

plyrund reshapeFunktionen haben fast alle diese Funktionen in meinem täglichen Gebrauch ersetzt. Aber auch aus dem Intro to Plyr-Dokument:

Verwandte Funktionen tapplyund sweephaben keine entsprechende Funktion in plyrund bleiben nützlich. mergeist nützlich, um Zusammenfassungen mit den Originaldaten zu kombinieren.


13
Als ich anfing, R von Grund auf neu zu lernen, fand ich Plyr VIEL einfacher zu lernen als die *apply()Funktionsfamilie. Für mich ddply()war das sehr intuitiv, da ich mit SQL-Aggregationsfunktionen vertraut war. ddply()wurde mein Hammer für die Lösung vieler Probleme, von denen einige mit anderen Befehlen besser hätten gelöst werden können.
JD Long

1
Ich denke, ich habe herausgefunden, dass das Konzept hinter plyrFunktionen Funktionen ähnelt. *applyWenn Sie also eine ausführen können, können Sie die andere ausführen, aber plyrFunktionen sind leichter zu merken. Aber ich stimme dem ddply()Hammer voll und ganz zu !
JoFrhwld

1
Das Plyr-Paket verfügt über die join()Funktion, die ähnliche Aufgaben wie das Zusammenführen ausführt. Vielleicht ist es mehr auf den Punkt, es im Zusammenhang mit Plyr zu erwähnen.
Marbel

Vergessen wir nicht arm, vergesseneapply
JDL

Gute Antwort im Allgemeinen, aber ich denke, es spielt den Nutzen vapplyund die Nachteile von herunter sapply. Ein Hauptvorteil von vapplyist, dass es den Ausgabetyp und die Ausgabelänge erzwingt, sodass Sie entweder die genaue erwartete Ausgabe oder einen informativen Fehler erhalten. Auf der anderen Seite sapplywird versucht, die Ausgabe nach Regeln zu vereinfachen, die nicht immer offensichtlich sind, und ansonsten auf eine Liste zurückzugreifen. Versuchen Sie beispielsweise, die Art der Ausgabe vorherzusagen, die dadurch erzeugt wird : sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1)). Was ist mit sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)?
Alexey Shiklomanov


100

Beginnen Sie zuerst mit Jorans hervorragender Antwort - zweifelhaft, dass irgendetwas das verbessern kann.

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

Mnemonik

  • lapplyist eine Liste , die auf eine Liste oder einen Vektor einwirkt und eine Liste zurückgibt.
  • sapplyist eine einfache lapply (Funktion gibt standardmäßig einen Vektor oder eine Matrix zurück, wenn möglich)
  • vapplyist eine verifizierte Anwendung (ermöglicht die Vorgabe des Rückgabeobjekttyps)
  • rapplyist eine rekursive Anwendung für verschachtelte Listen, dh Listen innerhalb von Listen
  • tapplyist eine getaggte Anwendung, bei der die Tags die Teilmengen identifizieren
  • apply ist generisch : Wendet eine Funktion auf die Zeilen oder Spalten einer Matrix an (oder allgemeiner auf die Dimensionen eines Arrays).

Den richtigen Hintergrund schaffen

Wenn sich die Nutzung der applyFamilie für Sie immer noch etwas fremd anfühlt, fehlt Ihnen möglicherweise eine wichtige Sichtweise.

Diese beiden Artikel können helfen. Sie liefern den notwendigen Hintergrund, um die funktionalen Programmiertechniken zu motivieren , die von der applyFunktionsfamilie bereitgestellt werden .

Benutzer von Lisp werden das Paradigma sofort erkennen. Wenn Sie mit Lisp nicht vertraut sind, haben Sie, sobald Sie sich mit FP vertraut gemacht haben, eine leistungsstarke Sichtweise für die Verwendung in R gewonnen - und dies applywird viel sinnvoller sein.


51

Da habe ich festgestellt, dass (die sehr ausgezeichneten) Antworten dieses Beitrags fehlen byund aggregateErklärungen. Hier ist mein Beitrag.

DURCH

Die byin der Dokumentation angegebene Funktion kann jedoch als "Wrapper" für tapply. Die Kraft von byentsteht, wenn wir eine Aufgabe berechnen wollen, tapplydie nicht erledigt werden kann. 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 ctund cb"im Wesentlichen" die gleichen Ergebnisse erzielen und die einzigen Unterschiede darin bestehen, wie sie angezeigt werden und in welchen unterschiedlichen classAttributen byfür cbund arrayfür ct.

Wie ich schon sagte, byentsteht die Kraft von , wenn wir nicht nutzen 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 die summaryaller Variablen irisentlang des Faktors berechnen Species": aber R kann das einfach nicht, weil es nicht weiß, wie es zu handhaben ist.

Mit der byFunktion R wird eine bestimmte Methode für die data frameKlasse ausgelöst und die summaryFunktion dann auch dann funktionieren gelassen , wenn die Länge des ersten Arguments (und auch der Typ) unterschiedlich sind.

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 in der Tat und das Ergebnis ist sehr überraschend. Es ist ein Objekt der Klasse by, das zusammen Species(etwa für jedes von ihnen) das summaryvon jeder Variablen berechnet .

Beachten Sie, dass data framedie ausgelöste Funktion eine Methode für diese Objektklasse haben muss , wenn das erste Argument a ist . Zum Beispiel verwenden wir diesen Code mit der meanFunktion, dass 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

aggregatekann als eine andere Art der Verwendung angesehen werden, tapplywenn wir es so verwenden.

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 beiden unmittelbaren Unterschiede sind , dass das zweite Argument der aggregate muss eine Liste sein , während tapply kann (nicht zwingend) eine Liste sein und dass der Ausgang aggregateist ein Datenrahmen , während der eine von tapplyeinem ist array.

Die Stärke von aggregateist, dass es leicht Teilmengen der Daten mit subsetArgumenten verarbeiten kann und dass es Methoden für tsObjekte und formulaauch hat.

Diese Elemente aggregateerleichtern tapplyin einigen Situationen die Arbeit damit . Hier einige Beispiele (in der Dokumentation verfügbar):

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

Wir können dasselbe erreichen, tapplyaber die Syntax ist etwas schwieriger und die Ausgabe (unter bestimmten 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 verwenden können byoder tapplymüssen 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 tapplyeinem Aufruf erhalten, aber wir müssen den Mittelwert Monthfür jedes Element berechnen und dann kombinieren (beachten Sie auch, dass wir das aufrufen müssen na.rm = TRUE, da die formulaMethoden der aggregateFunktion standardmäßig das haben 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 bywir dies einfach nicht erreichen können, gibt der folgende Funktionsaufruf einen Fehler zurück (aber höchstwahrscheinlich hängt er mit der bereitgestellten Funktion zusammen mean):

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

In anderen Fällen sind die Ergebnisse gleich und die Unterschiede liegen nur in der Klasse (und dann, wie es angezeigt / gedruckt wird und nicht nur - Beispiel, wie man es untergibt) 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 Tool nur eine Frage des persönlichen Geschmacks und der persönlichen Bedürfnisse. Die beiden vorherigen Objekte haben sehr unterschiedliche Anforderungen an die Teilmenge.


Wie ich bereits sagte, entsteht die Kraft von by, wenn wir tapply nicht verwenden können. Der folgende Code ist ein Beispiel: Dies sind die Wörter, die Sie oben verwendet haben. Und Sie haben ein Beispiel für die Berechnung der Zusammenfassung gegeben. Nehmen wir an, die zusammenfassende Statistik kann nur so berechnet werden, dass sie gereinigt werden muss: z. B. ist data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))dies eine Verwendung von 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

Es gibt viele gute Antworten, die Unterschiede in den Anwendungsfällen für jede Funktion diskutieren. In keiner der Antworten werden die Leistungsunterschiede erörtert. Dies ist sinnvoll, da verschiedene Funktionen unterschiedliche Eingaben erwarten und unterschiedliche Ausgaben erzeugen. Die meisten von ihnen haben jedoch ein allgemeines gemeinsames Ziel, sie nach Serien / Gruppen zu bewerten. Meine Antwort wird sich auf die Leistung konzentrieren. Aufgrund der obigen Angaben ist die Eingabeerstellung aus den Vektoren im Timing enthalten, auch die applyFunktion wird nicht gemessen.

Ich habe zwei verschiedene Funktionen getestet sumund lengthauf einmal. Das getestete Volumen beträgt 50 M am Eingang und 50 K am Ausgang. Ich habe auch zwei derzeit beliebte Pakete aufgenommen, die zu dem Zeitpunkt, als die Frage gestellt wurde, nicht weit verbreitet waren, data.tableund dplyr. Beides ist auf jeden Fall einen Blick wert, 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

Ist es normal, dass dplyr niedriger ist als die applt-Funktionen?
Mostafa

1
@ DimitriPetrenko Ich glaube nicht, nicht sicher, warum es hier ist. Es ist am besten, mit Ihren eigenen Daten zu testen, da viele Faktoren ins Spiel kommen.
Jangorecki

28

Trotz all der tollen Antworten hier gibt es zwei weitere Basisfunktionen, die es zu erwähnen gilt, die nützliche outerFunktion und die obskure eapplyFunktion

äußere

outerist eine sehr nützliche Funktion, die als weltlichere verborgen ist. Wenn Sie die Hilfe für outerdie Beschreibung lesen, heißt es:

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, ist nur für Dinge vom Typ lineare Algebra nützlich. Es kann jedoch ähnlich verwendet werden mapply, um eine Funktion auf zwei Vektoren von Eingaben anzuwenden. Der Unterschied besteht darin, dass mapplydie Funktion auf die ersten beiden Elemente und dann auf die zweiten beiden usw. angewendet outerwird , während die Funktion auf jede Kombination eines Elements aus dem ersten Vektor und eines aus dem zweiten angewendet wird. Zum Beispiel:

 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 persönlich habe dies verwendet, wenn ich einen Wertevektor und einen Vektor von Bedingungen habe und sehen möchte, welche Werte welche Bedingungen erfüllen.

eifrig

eapplyist so lapply, als würde eine Funktion nicht auf jedes Element in einer Liste angewendet, sondern auf jedes Element in einer Umgebung. Wenn Sie beispielsweise eine Liste benutzerdefinierter Funktionen in der globalen Umgebung suchen 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 oft, aber wenn Sie viele Pakete erstellen oder viele Umgebungen erstellen, kann es nützlich sein.


25

Es ist vielleicht erwähnenswert ave. aveist tapplyder freundliche Cousin. Es gibt Ergebnisse in einem Formular zurück, das Sie direkt wieder 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
##   ...

Das Basispaket enthält nichts, was avefür ganze Datenrahmen funktioniert ( bywie tapplyfür Datenrahmen). Aber Sie können es fummeln:

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

Ich habe kürzlich die ziemlich nützliche sweepFunktion entdeckt und der Vollständigkeit halber hier hinzugefügt:

fegen

Die Grundidee besteht darin , ein Array zeilen- oder spaltenweise zu durchlaufen und ein modifiziertes Array zurückzugeben. Ein Beispiel wird dies verdeutlichen (Quelle: Datencamp ):

Angenommen, Sie haben eine Matrix und möchten diese spaltenweise standardisieren :

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 das gleiche Ergebnis natürlich leichter erzielt werden durch
apply(dataPoints, 2, scale)


1
Hat das etwas mit Gruppierung zu tun?
Frank

2
@Frank: Um ehrlich zu sein, ist der Titel dieses Beitrags ziemlich irreführend: Wenn Sie die Frage selbst lesen, geht es um "die Familie bewerben". sweepist eine Funktion höherer Ordnung wie alle anderen hier erwähnten, zum Beispiel apply, sapply, lapplyalso die gleiche Frage über die akzeptierte Antwort mit über 1.000 upvotes und die Beispiele darin gegeben gestellt werden könnten. Schauen Sie sich einfach das dort gegebene Beispiel an apply.
vonjd

2
Sweep hat einen irreführenden Namen, irreführende Standardeinstellungen und einen irreführenden Parameternamen :). Es ist für mich einfacher, dies so zu verstehen: 1) STATS ist ein Vektor oder ein einzelner Wert, der wiederholt wird, um eine Matrix mit der gleichen Größe wie die erste Eingabe zu bilden. 2) FUN wird auf die erste Eingabe und diese neue Matrix angewendet. Vielleicht besser illustriert durch : sweep(matrix(1:6,nrow=2),2,7:9,list). Es ist normalerweise effizienter als applyweil wo applySchleifen sweepvektorisierte Funktionen verwenden können.
Moody_Mudskipper

2

In dem kürzlich auf CRAN veröffentlichten Collapse- Paket habe ich versucht, die meisten gängigen Apply-Funktionen in nur zwei Funktionen zu komprimieren:

  1. dapply(Data-Apply) wendet Funktionen auf Zeilen oder (Standard-) Spalten von Matrizen und data.frames an und (Standard) gibt ein Objekt desselben Typs und mit denselben Attributen zurück (es sei denn, das Ergebnis jeder Berechnung ist atomar und drop = TRUE). Die Leistung ist vergleichbar mitlapply von data.frame-Spalten und etwa zweimal schneller als applybei Matrixzeilen oder -spalten . Parallelität ist über mclapply(nur für MAC) verfügbar .

Syntax:

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

Beispiele:

# 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. BYist ein S3-Generikum für Split-Apply-Combine-Computing mit Vektor-, Matrix- und Data.frame-Methode. Es ist deutlich schneller als tapply, byundaggregate (ein auch schneller als plyrauf große Daten dplyrschneller obwohl).

Syntax:

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

Beispiele:

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

Es können auch Listen mit Gruppierungsvariablen bereitgestellt werden g.

Apropos Leistung: Ein Hauptziel des Zusammenbruchs besteht darin, die Hochleistungsprogrammierung in R zu fördern und über das Split-Apply-Combine-Kombinieren hinauszugehen. Zu diesem Zweck hat das Paket eine ganze Reihe von C ++ basierten schnell generische Funktionen: fmean, fmedian, fmode, fsum, fprod, fsd, fvar, fmin, fmax, ffirst, flast, fNobs, fNdistinct, fscale, fbetween, fwithin, fHDbetween, fHDwithin, flag, fdiffund fgrowth. Sie führen gruppierte Berechnungen in einem einzigen Durchgang durch die Daten durch (dh keine Aufteilung und Rekombination).

Syntax:

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

Beispiele:

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

In den Paketvignetten gebe ich Benchmarks an. Das Programmieren mit den schnellen Funktionen ist erheblich schneller als das Programmieren mit dplyr oder data.table , insbesondere bei kleineren Daten, aber auch bei großen Datenmengen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.