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 ...
sapply(vec, f)
: Eingabe ist ein Vektor. Die Ausgabe ist ein Vektor/eine Matrix, wobei das Element i
f(vec[i])
ist. Dies gibt Ihnen eine Matrix, wenn f
eine Ausgabe mit mehreren Elementen hat
lapply(vec, f)
: Wie sapply
, aber die Ausgabe ist eine Liste?
apply(matrix, 1/2, f)
: Eingabe ist eine Matrix. Ausgabe ist ein Vektor, wobei Element i
f ist (Zeile/Spalte i der Matrix)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. Spaltennamenby(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.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?
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.
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
undsweep
haben inplyr
keine entsprechende Funktion und sind weiterhin nützlich.merge
ist nützlich, um Zusammenfassungen mit den Originaldaten zu kombinieren.
Aus Folie 21 von http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy :
(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)
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 Listentapply
ist eine getaggte Anwendung, bei der die Tags die Teilmengen identifizierenapply
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.
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
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.
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
## ...
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 durchapply(dataPoints, 2, scale)