1150 Stimmen

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

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

Allerdings habe ich die Unterschiede zwischen ihnen nie ganz verstanden - wie { sapply , lapply usw.} die Funktion auf die Eingabe bzw. die gruppierte Eingabe anwenden, wie die Ausgabe aussehen wird oder sogar, was die Eingabe sein kann - also gehe ich sie oft einfach alle durch, bis ich das Gewünschte erhalte.

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

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

  1. sapply(vec, f) Eingabe ist ein Vektor. Ausgabe ist ein Vektor/Matrix, wobei Element i es f(vec[i]) und gibt Ihnen 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, dessen Element i ist f(Zeile/Spalte i der Matrix)

  4. tapply(vector, grouping, f) Ausgabe ist eine Matrix/ein Array, wobei ein Element in der Matrix/dem Array der Wert von f bei einer Gruppierung g des Vektors, und g wird an die Zeilen-/Spaltennamen angehängt

  5. by(dataframe, grouping, f) : l g eine Gruppierung sein. anwenden f zu jeder Spalte der Gruppe/des Datenrahmens. Die Gruppierung und der Wert von f an jeder Spalte.

  6. aggregate(matrix, grouping, f) : ähnlich wie by , aber anstatt die Ausgabe hübsch auszudrucken, packt Aggregate alles in einen Datenrahmen.

Nebenfrage: Ich habe immer noch nicht gelernt, plyr oder reshape - würde plyr o reshape alle diese Produkte vollständig ersetzen?

41voto

jangorecki Punkte 15157

Es gibt viele gute Antworten, die die Unterschiede in den Anwendungsfällen für jede Funktion diskutieren. In keiner der Antworten wird auf die Unterschiede in der Leistung eingegangen. Das ist vernünftig, denn verschiedene Funktionen erwarten verschiedene Eingaben und erzeugen verschiedene Ausgaben, doch die meisten von ihnen haben ein allgemeines gemeinsames Ziel, nämlich die Auswertung nach Serien/Gruppen. Meine Antwort wird sich auf die Leistung konzentrieren. Aufgrund der obigen Ausführungen wird die Erzeugung der Eingaben aus den Vektoren in die Zeitplanung einbezogen, auch die apply Funktion wird nicht gemessen.

Ich habe zwei verschiedene Funktionen getestet sum y length auf einmal. Das getestete Volumen beträgt 50M am Eingang und 50K am Ausgang. Ich habe auch zwei derzeit beliebte Pakete, die nicht weit verbreitet waren zu der Zeit, als die Frage gestellt wurde, enthalten, data.table y dplyr . B

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

33voto

John Paul Punkte 11536

Trotz all der großartigen Antworten hier, gibt es noch 2 weitere Basisfunktionen, die es verdienen, erwähnt zu werden, die nützlichen outer という関数と eapply 機能

exterior

outer ist eine sehr nützliche Funktion, die sich hinter einer eher banalen Funktion verbirgt. Wenn Sie die Hilfe zu outer heißt es in der 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 es nur für lineare Algebra nützlich ist. Es kann jedoch ähnlich verwendet werden wie mapply um eine Funktion auf zwei Vektoren von Eingaben anzuwenden. Der Unterschied besteht darin, dass mapply wendet die Funktion auf die ersten beiden Elemente und dann auf die zweiten beiden usw. an, während outer wendet die Funktion auf jede Kombination aus einem Element des ersten Vektors und einem Element des zweiten Vektors an. 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 という感じです。 lapply mit dem Unterschied, dass es eine Funktion nicht auf jedes Element in einer Liste anwendet, sondern auf jedes Element in einer Umgebung. Wenn Sie z. B. eine Liste der benutzerdefinierten Funktionen in der globalen Umgebung suchen wollen:

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 diese Funktion nicht sehr oft, aber wenn Sie viele Pakete erstellen oder viele Umgebungen erstellen, kann sie sehr nützlich sein.

27voto

特筆すべきは ave . ave es tapply freundliche Cousine. Er gibt die Ergebnisse in einer Form zurück, die 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
##   ...

Im Basispaket gibt es nichts, was so funktioniert wie ave für ganze Datenrahmen (als by という感じです。 tapply für Datenrahmen). Aber man kann es überspielen:

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

15voto

vonjd Punkte 3844

Vor kurzem entdeckte ich die recht nützliche sweep Funktion und fügen sie hier der Vollständigkeit halber hinzu:

スイープ

基本的な考え方は スイープ durch ein Array zeilen- oder spaltenweise und gibt ein geändertes Array zurück. Ein Beispiel soll dies verdeutlichen (Quelle: データキャンプ ):

Angenommen, Sie haben eine Matrix und möchten きかくをつける 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,"-")

# 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 lässt sich das gleiche Ergebnis natürlich auch einfacher erreichen durch
apply(dataPoints, 2, scale)

8voto

Sebastian Punkte 702

での 倒壊 Paket, das kürzlich auf CRAN veröffentlicht wurde, habe ich versucht, die meisten der üblichen Anwendungsfunktionen in nur 2 Funktionen zusammenzufassen:

  1. dapply (Data-Apply) wendet Funktionen auf Zeilen oder (standardmäßig) Spalten von Matrizen und data.frames an und gibt (standardmäßig) ein Objekt desselben Typs und mit denselben Attributen zurück (es sei denn, das Ergebnis der einzelnen Berechnungen ist atomar und drop = TRUE ). Die Leistung ist vergleichbar mit lapply für data.frame-Spalten, und etwa 2x schneller als apply für Matrixzeilen oder -spalten. Die Parallelität ist verfügbar über mclapply (nur für MAC).

構文です。

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

Ejemplos:

# 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 ist eine S3-Generik für Split-Apply-Combine-Berechnungen mit Vektor-, Matrix- und Data.frame-Methode. Es ist deutlich schneller als tapply , by y aggregate (よりも速い)。 plyr 大容量データで dplyr ist jedoch schneller).

構文です。

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

Ejemplos:

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

Listen von Gruppierungsvariablen können auch an g .

Apropos Leistung: Ein Hauptziel der 倒壊 ist es, die Hochleistungsprogrammierung in R zu fördern und über Split-Apply-Combine hinauszugehen. Zu diesem Zweck verfügt das Paket über einen vollständigen Satz von schnellen generischen Funktionen auf C++-Basis: fmean , fmedian , fmode , fsum , fprod , fsd , fvar , fmin , fmax , ffirst , flast , fNobs , fNdistinct , fscale , fbetween , fwithin , fHDbetween , fHDwithin , flag , fdiff y fgrowth . Sie führen gruppierte Berechnungen in einem einzigen Durchgang durch die Daten durch (d. h. keine Aufteilung und Neukombination).

構文です。

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

Ejemplos:

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 stelle ich Benchmarks zur Verfügung. Die Programmierung mit den schnellen Funktionen ist deutlich schneller als die Programmierung mit ドラえもん o データテーブル vor allem bei kleineren Daten, aber auch bei großen Daten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X