it-swarm.com.de

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

Wann immer ich etwas "map" -Py in R machen möchte, versuche ich normalerweise, eine Funktion in der apply -Familie zu verwenden.

Allerdings habe ich die Unterschiede zwischen ihnen nie ganz verstanden - wie {sapply, lapply usw.} wenden Sie die Funktion auf die Eingabe/gruppierte Eingabe an, wie die Ausgabe aussehen wird, oder sogar was die Eingabe sein kann - so gehe ich oft 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 das Element if(vec[i]) ist. Dies gibt Ihnen eine Matrix, wenn f eine Ausgabe mit mehreren Elementen hat

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

  3. apply(matrix, 1/2, f): Eingabe ist eine Matrix. Ausgabe ist ein Vektor, wobei Element i f ist (Zeile/Spalte i der Matrix)
  4. tapply(vector, grouping, f): Ausgabe ist eine Matrix/ein Array, wobei ein Element in der Matrix/im Array der Wert von f in einer Gruppierung g des Vektors ist und g in die Zeile/verschoben wird. Spaltennamen
  5. by(dataframe, grouping, f): Sei g eine Gruppierung. Wenden Sie f auf jede Spalte der Gruppe/des Datenrahmens an. Drucken Sie die Gruppierung und den Wert von f an jeder Spalte hübsch aus.
  6. aggregate(matrix, grouping, f): Ähnlich wie by, aber statt die Ausgabe hübsch auszudrucken, speichert Aggregat alles in einem Datenrahmen.

Nebenfrage: Ich habe Plyr oder Umformen noch nicht gelernt - würde plyr oder reshape all dies vollständig ersetzen?

992
grautur

R hat viele * Anwendungsfunktionen, die in den Hilfedateien beschrieben sind (z. B. ?apply). Es gibt jedoch genug von ihnen, bei denen es für anfängliche Benutzer schwierig sein kann, zu entscheiden, welches für ihre Situation geeignet ist, oder sich 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 aufrechtzuerhalten.

Trotz der Tatsache (wie in anderen Antworten erwähnt), dass ein Großteil der Funktionen der * apply-Familie durch das äußerst beliebte plyr -Paket abgedeckt wird, bleiben die Basisfunktionen nützlich und wissenswert.

Diese Antwort soll als eine Art Wegweiser für neue Verwendungszwecke dienen, um sie an die richtige * Apply-Funktion für ihr spezielles Problem zu leiten. 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 * Anwendungsfunktion zu Ihrer Situation passt, und dass Sie es dann weiter erforschen müssen. Leistungsunterschiede werden mit einer Ausnahme nicht berücksichtigt.

  • apply - Wenn Sie eine Funktion auf die Zeilen oder Spalten einer Matrix (und höherdimensionale Analoga) anwenden möchten; Nicht generell empfehlenswert für Datenrahmen, da diese zuerst in eine Matrix umgewandelt 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-/Spaltenmittelwerte oder Summen für eine 2D-Matrix wünschen, sollten Sie unbedingt die hochoptimierten, blitzschnellen colMeans, rowMeans, colSums, rowSums untersuchen.

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

    Dies ist das Arbeitspferd vieler anderer * Anwendungsfunktionen. Ziehen Sie ihren Code zurück und Sie werden oft lapply darunter 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 statt einer Liste ein vector zurück möchten .

    Wenn Sie feststellen, dass Sie unlist(lapply(...)) eingeben, halten Sie an und betrachten 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 Verwendungen von sapply wird versucht, das Ergebnis gegebenenfalls in ein mehrdimensionales Array umzuwandeln. Wenn unsere Funktion beispielsweise Vektoren gleicher Länge zurückgibt, werden diese von sapply als Spalten einer Matrix verwendet:

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

    Wenn unsere Funktion eine zweidimensionale Matrix zurückgibt, tut sapply im Wesentlichen dasselbe und behandelt jede zurückgegebene Matrix als einen einzelnen langen Vektor:

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

    Es sei denn, wir geben simplify = "array" an. 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, Vektoren oder Matrizen gleicher Länge oder Dimension zurückzugeben.

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

    Für vapply geben Sie R im Grunde genommen ein Beispiel dafür, was Ihre Funktion zurückgibt, was einige Zeit sparen kann, wenn zurückgegebene Werte in einen einzelnen Atomvektor 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 (z. B. Vektoren, Listen) haben und eine Funktion auf die ersten Elemente von jedem und dann auf die zweiten Elemente von jedem anwenden möchten usw., wobei das Ergebnis wie in sapply in einen Vektor/ein Array umgewandelt wird.

    Dies ist insofern multivariat, als 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 für mapply mit SIMPLIFY = FALSE, sodass garantiert 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 - Zum rekursiven Anwenden einer Funktion auf jedes Element einer verschachtelten Liste -Struktur.

    Um Ihnen eine Vorstellung davon zu geben, wie ungewöhnlich rapply ist, habe ich es beim ersten Posten dieser Antwort vergessen! Ich bin mir sicher, dass viele Leute es benutzen, aber YMMV. rapply lässt sich am besten mit einer benutzerdefinierten Funktion veranschaulichen:

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

    Die schwarzen Schafe der Familie * bewerben sich gewissermaßen. Die Verwendung der Hilfedatei des Ausdrucks "zerlumptes Array" kann ein bisschen verwirrend sein, aber es ist 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 x innerhalb jeder durch y definierten Untergruppe:

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

    Komplexere Beispiele können behandelt werden, bei denen die Untergruppen durch die eindeutigen Kombinationen einer Liste mehrerer Faktoren definiert werden. tapply ähnelt im Prinzip den in R (aggregate, by, ave, ddply, etc.) üblichen Funktionen zum Aufteilen, Anwenden und Kombinieren. Daher sein Status als schwarzes Schaf.

1279
joran

In der Randnotiz ist hier, wie die verschiedenen plyr - Funktionen den Basisfunktionen *apply entsprechen (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 ist es, für jede der Funktionen einheitliche Namenskonventionen bereitzustellen und die Eingabe- und Ausgabedatentypen im Funktionsnamen zu kodieren. Es sorgt auch für Konsistenz bei der Ausgabe, da die Ausgabe von dlply() leicht an ldply() übergeben werden kann, um eine nützliche Ausgabe usw. zu erzeugen.

Konzeptionell ist das Lernen von plyr nicht schwieriger als das Verstehen der Basisfunktionen *apply.

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

Verwandte Funktionen tapply und sweep haben in plyr keine entsprechende Funktion und sind weiterhin nützlich. merge ist nützlich, um Zusammenfassungen mit den Originaldaten zu kombinieren.

182
JoFrhwld

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

apply, sapply, lapply, by, aggregate

(Es ist hoffentlich klar, dass apply @ Hadleys aaply und aggregate @ Hadleys ddply entspricht.) Wenn Sie nicht sehen, wird dies durch Folie 20 derselben Diashow verdeutlicht es von diesem Bild.)

(links ist die Eingabe, oben ist die Ausgabe)

129
isomorphismes

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

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

Mnemonics

  • lapply ist eine Liste , die auf eine Liste oder einen Vektor wirkt und eine Liste zurückgibt.
  • sapply ist ein einfaches lapply (Funktion liefert standardmäßig einen Vektor oder eine Matrix, wenn möglich)
  • vapply ist eine verifizierte Anwendung (ermöglicht die Vorgabe des Rückgabeobjekttyps)
  • rapply ist eine rekursive Anwendung für verschachtelte Listen, d. H. Listen innerhalb von Listen
  • tapply ist 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 erstellen

Wenn sich die Verwendung der apply -Familie für Sie noch etwas fremd anfühlt, fehlt 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 apply -Familie von Funktionen bereitgestellt werden.

Benutzer von LISP werden das Paradigma sofort erkennen. Wenn Sie nicht mit LISP vertraut sind, haben Sie, sobald Sie sich mit FP vertraut gemacht haben, eine mächtige Sichtweise für die Verwendung in R - erlangt, und apply macht viel mehr Sinn.

96
Assad Ebrahim

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 vernünftig, da verschiedene Funktionen verschiedene Eingaben erwarten und verschiedene Ausgaben erzeugen, aber die meisten von ihnen haben ein allgemeines gemeinsames Ziel, nach Serien/Gruppen zu bewerten. Meine Antwort wird sich auf die Leistung konzentrieren. Aufgrund der obigen Tatsache, dass die Eingabeerstellung aus den Vektoren im Timing enthalten ist, wird auch die Funktion apply nicht gemessen.

Ich habe zwei verschiedene Funktionen sum und length gleichzeitig getestet. Das getestete Volumen beträgt 50 MB am Eingang und 50 KB am Ausgang. Ich habe auch zwei derzeit beliebte Pakete beigefügt, die zu der Zeit, als die Frage gestellt wurde, nicht weit verbreitet waren, data.table und 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
31
jangorecki

Trotz all der tollen Antworten gibt es hier noch zwei weitere Grundfunktionen, die erwähnenswert sind: die nützliche Funktion outer und die obskure Funktion eapply

äußere

outer ist eine sehr nützliche Funktion, die eher banal versteckt ist. Wenn Sie die Hilfe zu outer lesen, lautet die Beschreibung:

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 den Anschein erweckt, dass dies nur für Dinge vom Typ der linearen Algebra nützlich ist. Es kann jedoch ähnlich wie mapply verwendet werden, um eine Funktion auf zwei Vektoren von Eingaben anzuwenden. Der Unterschied besteht darin, dass mapply die Funktion auf die ersten beiden Elemente und dann auf die zweiten beiden usw. anwendet, während outer die Funktion auf jede Kombination eines Elements aus dem ersten Vektor und eines aus dem zweiten Vektor anwendet . 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 Vektor von Werten und einen Vektor von Bedingungen habe und sehen möchte, welche Werte welche Bedingungen erfüllen.

eapply

eapply ist wie lapply, außer dass eine Funktion nicht auf jedes Element in einer Liste angewendet wird, 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.

23
John Paul

Es ist vielleicht erwähnenswert, ave. ave ist der freundliche Cousin von tapply. Es gibt Ergebnisse in einer Form zurück, die 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 für ganze Datenrahmen wie ave funktioniert (da by wie tapply für Datenrahmen ist). Aber Sie können es fudgen:

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

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

Sweep

Die Grundidee ist, sweep zeilen- oder spaltenweise durch ein Array zu gehen und ein modifiziertes Array zurückzugeben. Ein Beispiel wird dies verdeutlichen (Quelle: Datencamp ):

Angenommen, Sie haben eine Matrix und möchten standardisieren sie 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 das gleiche Ergebnis natürlich leichter erreicht werden durch
apply(dataPoints, 2, scale)

9
vonjd