Die apply
Funktionen in R bieten keine verbesserte Leistung gegenüber anderen Schleifenfunktionen (z for
. B. ). Eine Ausnahme ist lapply
die, die etwas schneller sein kann, da sie in C-Code mehr Arbeit leistet als in R ( ein Beispiel hierfür finden Sie in dieser Frage ).
Im Allgemeinen gilt jedoch die Regel, dass Sie aus Gründen der Übersichtlichkeit eine Apply-Funktion verwenden sollten, nicht aus Gründen der Leistung .
Ich möchte hinzufügen, dass Apply-Funktionen keine Nebenwirkungen haben , was ein wichtiger Unterschied bei der funktionalen Programmierung mit R ist. Dies kann durch die Verwendung von assign
oder überschrieben werden <<-
, aber das kann sehr gefährlich sein. Nebenwirkungen erschweren auch das Verständnis eines Programms, da der Status einer Variablen von der Historie abhängt.
Bearbeiten:
Nur um dies mit einem trivialen Beispiel zu betonen, das die Fibonacci-Sequenz rekursiv berechnet; Dies kann mehrmals ausgeführt werden, um eine genaue Messung zu erhalten. Der Punkt ist jedoch, dass keine der Methoden eine signifikant unterschiedliche Leistung aufweist:
> fibo <- function(n) {
+ if ( n < 2 ) n
+ else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
user system elapsed
7.48 0.00 7.52
> system.time(sapply(0:26, fibo))
user system elapsed
7.50 0.00 7.54
> system.time(lapply(0:26, fibo))
user system elapsed
7.48 0.04 7.54
> library(plyr)
> system.time(ldply(0:26, fibo))
user system elapsed
7.52 0.00 7.58
Bearbeiten 2:
In Bezug auf die Verwendung paralleler Pakete für R (z. B. rpvm, rmpi, snow) bieten diese im Allgemeinen apply
Familienfunktionen (selbst das foreach
Paket ist trotz des Namens im Wesentlichen gleichwertig). Hier ist ein einfaches Beispiel für die sapply
Funktion in snow
:
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
In diesem Beispiel wird ein Socket-Cluster verwendet, für den keine zusätzliche Software installiert werden muss. Andernfalls benötigen Sie etwas wie PVM oder MPI (siehe Tierneys Clustering-Seite ). snow
hat die folgenden Funktionen anwenden:
parLapply(cl, x, fun, ...)
parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
parApply(cl, X, MARGIN, FUN, ...)
parRapply(cl, x, fun, ...)
parCapply(cl, x, fun, ...)
Es ist sinnvoll, apply
Funktionen für die parallele Ausführung zu verwenden, da sie keine Nebenwirkungen haben . Wenn Sie einen Variablenwert innerhalb einer for
Schleife ändern , wird er global festgelegt. Auf der anderen Seite können alle apply
Funktionen sicher parallel verwendet werden, da Änderungen lokal für den Funktionsaufruf sind (es sei denn, Sie versuchen assign
oder oder <<-
in diesem Fall können Sie Nebenwirkungen einführen). Es ist unnötig zu erwähnen, dass es wichtig ist, vorsichtig mit lokalen und globalen Variablen umzugehen, insbesondere wenn es um die parallele Ausführung geht.
Bearbeiten:
Hier ist ein triviales Beispiel, um den Unterschied zwischen for
und *apply
in Bezug auf Nebenwirkungen zu demonstrieren :
> df <- 1:10
> # *apply example
> lapply(2:3, function(i) df <- df * i)
> df
[1] 1 2 3 4 5 6 7 8 9 10
> # for loop example
> for(i in 2:3) df <- df * i
> df
[1] 6 12 18 24 30 36 42 48 54 60
Beachten Sie, wie die df
in der übergeordneten Umgebung von geändert wird, for
aber nicht *apply
.
apply
Funktionsfamilie. Die Strukturierung von Programmen, die sie verwenden, ermöglicht daher die Parallelisierung zu sehr geringen Grenzkosten.snowfall
Paket anzusehen und die Beispiele in ihrer Vignette auszuprobieren.snowfall
baut auf demsnow
Paket auf und abstrahiert die Details der Parallelisierung noch weiter, was die Ausführung parallelisierterapply
Funktionen kinderleicht macht .foreach
seitdem verfügbar geworden ist und auf SO viel gefragt zu sein scheint.lapply
"etwas schneller" als einefor
Schleife ist. Dort sehe ich jedoch nichts, was darauf hindeutet. Sie erwähnen nur, dass dieslapply
schneller ist alssapply
, was aus anderen Gründen eine bekannte Tatsache ist (sapply
versucht, die Ausgabe zu vereinfachen und muss daher viele Datengrößenprüfungen und mögliche Konvertierungen durchführen). Nichts im Zusammenhang mitfor
. Vermisse ich etwasManchmal kann die Beschleunigung erheblich sein, z. B. wenn Sie for-Schleifen verschachteln müssen, um den Durchschnitt basierend auf einer Gruppierung von mehr als einem Faktor zu erhalten. Hier haben Sie zwei Ansätze, mit denen Sie genau das gleiche Ergebnis erzielen:
Beide ergeben genau das gleiche Ergebnis, nämlich eine 5 x 10-Matrix mit den Durchschnittswerten und benannten Zeilen und Spalten. Aber :
Los geht's. Was habe ich gewonnen? ;-);
quelle
*apply
ist schneller. Aber ich denke, dass der wichtigere Punkt die Nebenwirkungen sind (aktualisierte meine Antwort mit einem Beispiel).data.table
ist es noch schneller und ich denke "einfacher".library(data.table)
dt<-data.table(X,Y,Z,key=c("Y,Z"))
system.time(dt[,list(X_mean=mean(X)),by=c("Y,Z")])
tapply
ist eine spezialisierte Funktion für eine bestimmte Aufgabe, das ist für Schleife , warum es schneller als ein. Es kann nicht das tun, was eine for-Schleife kann (während reguläreapply
können). Sie vergleichen Äpfel mit Orangen.... und wie ich gerade anderswo geschrieben habe, ist vapply dein Freund! ... es ist wie sapply, aber Sie geben auch den Rückgabewerttyp an, wodurch es viel schneller wird.
Update vom 1. Januar 2020:
quelle
for
Schleifen sind auf meinem Windows 10 2-Core-Computer schneller. Ich habe dies mit5e6
Elementen gemacht - eine Schleife war 2,9 Sekunden gegenüber 3,1 Sekunden fürvapply
.Ich habe an anderer Stelle geschrieben, dass ein Beispiel wie das von Shane den Leistungsunterschied zwischen den verschiedenen Arten der Schleifensyntax nicht wirklich betont, da die gesamte Zeit innerhalb der Funktion verbracht wird, anstatt die Schleife tatsächlich zu betonen. Darüber hinaus vergleicht der Code eine for-Schleife ohne Speicher unfair mit Funktionen der Apply-Familie, die einen Wert zurückgeben. Hier ist ein etwas anderes Beispiel, das den Punkt hervorhebt.
Wenn Sie das Ergebnis speichern möchten, kann das Anwenden von Familienfunktionen viel mehr sein als syntaktischer Zucker.
(Die einfache Auflistung von z ist nur 0,2 s, daher ist das Lapply viel schneller. Das Initialisieren des z in der for-Schleife ist ziemlich schnell, da ich den Durchschnitt der letzten 5 von 6 Läufen so gebe, dass er sich außerhalb des Systems bewegt kaum etwas beeinflussen)
Eine weitere zu beachtende Sache ist jedoch, dass es einen weiteren Grund gibt, Familienfunktionen unabhängig von ihrer Leistung, Klarheit oder dem Fehlen von Nebenwirkungen anzuwenden. Eine
for
Schleife fördert normalerweise das Einfügen so viel wie möglich in die Schleife. Dies liegt daran, dass für jede Schleife Variablen eingerichtet werden müssen, um Informationen zu speichern (unter anderem mögliche Operationen). Apply-Anweisungen sind in der Regel voreingenommen. Oft möchten Sie mehrere Vorgänge mit Ihren Daten ausführen, von denen einige vektorisiert werden können, andere jedoch möglicherweise nicht. In R ist es im Gegensatz zu anderen Sprachen am besten, diese Operationen zu trennen und diejenigen auszuführen, die nicht in einer apply-Anweisung (oder einer vektorisierten Version der Funktion) vektorisiert sind, und diejenigen, die als echte Vektoroperationen vektorisiert sind. Dies beschleunigt die Leistung oft enorm.Am Beispiel von Joris Meys, in dem er eine herkömmliche for-Schleife durch eine praktische R-Funktion ersetzt, können wir damit die Effizienz des Schreibens von Code auf eine R-freundlichere Weise für eine ähnliche Beschleunigung ohne die spezielle Funktion demonstrieren.
Dies ist viel schneller als die
for
Schleife und nur ein wenig langsamer als die eingebaute optimiertetapply
Funktion. Das liegt nicht daran, dassvapply
es so viel schneller ist als,for
sondern daran, dass in jeder Iteration der Schleife nur eine Operation ausgeführt wird. In diesem Code wird alles andere vektorisiert. In der traditionellenfor
Schleife von Joris Meys treten in jeder Iteration viele (7?) Operationen auf, und es gibt einiges an Setup, nur damit es ausgeführt werden kann. Beachten Sie auch, wie viel kompakter dies ist als diefor
Version.quelle
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
und vapply ist sogar noch besser:1.19 0.00 1.19
sapply
50% langsamer alsfor
undlapply
doppelt so schnell geworden.y
an1:1e6
, nichtnumeric(1e6)
(ein Vektor von Nullen). Der Versuch, immer wieder zuzuweisenfoo(0)
,z[0]
veranschaulicht eine typischefor
Schleifenverwendung nicht gut . Die Nachricht ist ansonsten genau richtig.Wenn Funktionen über Teilmengen eines Vektors
tapply
angewendet werden , kann dies ziemlich schnell sein als eine for-Schleife. Beispiel:apply
In den meisten Situationen wird jedoch keine Geschwindigkeitssteigerung erzielt, und in einigen Fällen kann dies sogar viel langsamer sein:Aber für diese Situationen haben wir
colSums
undrowSums
:quelle
microbenchmark
viel genauer ist alssystem.time
. Wenn Sie versuchen , zu vergleichensystem.time(f3(mat))
undsystem.time(f4(mat))
Sie anderes Ergebnis fast jedes Mal zu bekommen. Manchmal kann nur ein richtiger Benchmark-Test die schnellste Funktion zeigen.