Das größte Problem und die Wurzel der Ineffektivität ist die Indizierung von data.frame. Ich meine all diese Zeilen, in denen Sie sie verwenden temp[,]
.
Versuchen Sie dies so weit wie möglich zu vermeiden. Ich habe Ihre Funktion übernommen, die Indizierung geändert und hier version_A
dayloop2_A <- function(temp){
res <- numeric(nrow(temp))
for (i in 1:nrow(temp)){
res[i] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
res[i] <- temp[i,9] + res[i-1]
} else {
res[i] <- temp[i,9]
}
} else {
res[i] <- temp[i,9]
}
}
temp$`Kumm.` <- res
return(temp)
}
Wie Sie sehen können, erstelle ich einen Vektor, res
der Ergebnisse sammelt. Am Ende füge ich es hinzu data.frame
und ich muss mich nicht mit Namen anlegen. Wie ist es besser?
Ich führe jede Funktion für data.frame
mit nrow
1.000 bis 10.000 mal 1.000 aus und messe die Zeit mitsystem.time
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
Ergebnis ist
Sie können sehen, dass Ihre Version exponentiell von abhängt nrow(X)
. Die modifizierte Version hat eine lineare Beziehung, und ein einfaches lm
Modell sagt voraus, dass die Berechnung für 850.000 Zeilen 6 Minuten und 10 Sekunden dauert.
Kraft der Vektorisierung
Wie Shane und Calimo in ihren Antworten angeben, ist die Vektorisierung ein Schlüssel zu einer besseren Leistung. Von Ihrem Code aus können Sie sich außerhalb der Schleife bewegen:
- Konditionierung
- Initialisierung der Ergebnisse (welche sind
temp[i,9]
)
Dies führt zu diesem Code
dayloop2_B <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in 1:nrow(temp)) {
if (cond[i]) res[i] <- temp[i,9] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Vergleichen Sie das Ergebnis für diese Funktionen, diesmal für nrow
10.000 bis 100.000 mal 10.000.
Tuning das abgestimmte
Eine weitere Optimierung besteht darin, eine Schleifenindizierung temp[i,9]
auf zu ändern res[i]
(die in der i-ten Schleifeniteration genau gleich ist). Es ist wieder ein Unterschied zwischen der Indizierung eines Vektors und der Indizierung eines data.frame
.
Zweitens: Wenn Sie sich die Schleife ansehen, sehen Sie, dass keine Schleife über alle Schleifen erforderlich ist i
, sondern nur für diejenigen, die dem Zustand entsprechen.
Auf geht's
dayloop2_D <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in (1:nrow(temp))[cond]) {
res[i] <- res[i] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Die Leistung, die Sie in hohem Maße erzielen, hängt von einer Datenstruktur ab. Genau - auf Prozent der TRUE
Werte im Zustand. Für meine simulierten Daten dauert die Rechenzeit 850.000 Zeilen unter einer Sekunde.
Wenn du willst, dass du weiter gehen kannst, sehe ich mindestens zwei Dinge, die getan werden können:
- Schreiben Sie einen
C
Code, um bedingte Cumsum zu machen
Wenn Sie wissen, dass in Ihrer Datenmaximalsequenz nicht groß ist, können Sie die Schleife in "vektorisiert" ändern, während
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
Der für Simulationen und Abbildungen verwendete Code ist auf GitHub verfügbar .
res = c(1,2,3,4)
undcond
ist allesTRUE
, dann sollte das Endergebnis sein :1
,3
(Ursache1+2
),6
(Ursache zweite ist jetzt3
und dritte ist3
auch),10
(6+4
). Doing einfache Summierung du hast1
,3
,5
,7
.Allgemeine Strategien zur Beschleunigung des R-Codes
Stellen Sie zunächst fest, wo sich der langsame Teil wirklich befindet. Code, der nicht langsam ausgeführt wird, muss nicht optimiert werden. Bei kleinen Codemengen kann es funktionieren, einfach darüber nachzudenken. Wenn dies fehlschlägt, können RProf und ähnliche Profiling-Tools hilfreich sein.
Wenn Sie den Engpass herausgefunden haben, überlegen Sie sich effizientere Algorithmen, um das zu tun, was Sie wollen. Berechnungen sollten nach Möglichkeit nur einmal ausgeführt werden.
Mit mehr effizienten Funktionen kann zu moderaten oder großen Geschwindigkeitsgewinnen führen.
paste0
Erzeugt zum Beispiel einen kleinen Effizienzgewinn, aber.colSums()
seine Verwandten produzieren etwas ausgeprägtere Gewinne.mean
ist besonders langsam .Dann können Sie einige besonders häufige Probleme vermeiden :
cbind
wird dich sehr schnell verlangsamen.Versuchen Sie eine bessere Vektorisierung , die oft, aber nicht immer helfen kann. In dieser Hinsicht bieten inhärent vektorisierte Befehle wie
ifelse
,diff
und dergleichen mehr Verbesserungen als dieapply
Befehlsfamilie (die über eine gut geschriebene Schleife nur einen geringen bis keinen Geschwindigkeitsschub bietet).Sie können auch versuchen , R-Funktionen weitere Informationen bereitzustellen . Verwenden Sie beispielsweise
vapply
anstelle vonsapply
und geben Sie an,colClasses
wenn Sie textbasierte Daten einlesen . Die Geschwindigkeitsgewinne variieren je nachdem, wie viele Vermutungen Sie eliminieren.Betrachten Sie als nächstes optimierte Pakete : Das
data.table
Paket kann massive Geschwindigkeitsgewinne erzielen, wenn seine Verwendung möglich ist, bei der Datenmanipulation und beim Lesen großer Datenmengen (fread
).Versuchen Sie als nächstes, die Geschwindigkeit durch effizientere Mittel zum Aufrufen von R zu steigern :
Ra
undjit
Pakete im Konzert für die Just-in-Time - Kompilierung (Dirk hat ein Beispiel in dieser Präsentation ).Und wenn Sie mit all dem noch nicht so schnell sind, wie Sie es benötigen, müssen Sie möglicherweise für das langsame Code-Snippet zu einer schnelleren Sprache wechseln . Die Kombination von
Rcpp
undinline
hier macht das Ersetzen nur des langsamsten Teils des Algorithmus durch C ++ - Code besonders einfach. Hier ist zum Beispiel mein erster Versuch, dies zu tun , und es werden sogar hochoptimierte R-Lösungen weggeblasen.Wenn Sie nach all dem immer noch Probleme haben, brauchen Sie nur mehr Rechenleistung. Untersuchen Sie Parallelisierung ( http://cran.r-project.org/web/views/HighPerformanceComputing.html ) oder sogar GPU-basierte Lösungen (
gpu-tools
) an.Links zu anderen Anleitungen
quelle
Wenn Sie
for
Schleifen verwenden, codieren Sie R höchstwahrscheinlich so, als wäre es C oder Java oder etwas anderes. R-Code, der richtig vektorisiert ist, ist extrem schnell.Nehmen Sie zum Beispiel diese zwei einfachen Codebits, um eine Liste von 10.000 Ganzzahlen nacheinander zu generieren:
Das erste Codebeispiel ist, wie man eine Schleife unter Verwendung eines traditionellen Codierungsparadigmas codiert. Der Vorgang dauert 28 Sekunden
Sie können eine fast 100-fache Verbesserung erzielen, indem Sie einfach Speicher vorab zuweisen:
Bei Verwendung der Basis-R-Vektoroperation unter Verwendung des Doppelpunktoperators erfolgt
:
diese Operation jedoch praktisch augenblicklich:quelle
a[i]
sich nichts ändert. Hatsystem.time({a <- NULL; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- 1:1e5; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- NULL; a <- 2*(1:1e5)})
aber ein ähnliches Ergebnis.rep(1, 1e5)
- die Timings sind identisch.Dies könnte viel schneller gemacht werden, indem die Schleifen mithilfe von Indizes oder verschachtelten
ifelse()
Anweisungen übersprungen werden .quelle
i
hängt dieser -th-Wert von -th ab,i-1
sodass sie nicht so festgelegt werden können, wie Sie es tun (mitwhich()-1
).Ich mag es nicht, Code neu zu schreiben ... Natürlich sind auch ifelse und lapply bessere Optionen, aber manchmal ist es schwierig, dies anzupassen.
Häufig verwende ich data.frames wie Listen wie
df$var[i]
Hier ist ein erfundenes Beispiel:
data.frame version:
Listenversion:
17x mal schneller, um eine Liste von Vektoren als einen data.frame zu verwenden.
Irgendwelche Kommentare dazu, warum intern data.frames in dieser Hinsicht so langsam sind? Man könnte denken, sie funktionieren wie Listen ...
Für noch schnelleren Code tun Sie dies
class(d)='list'
anstelle vond=as.list(d)
undclass(d)='data.frame'
quelle
[<-.data.frame
irgendwie aufgerufen wird, wenn Sie dies tun,d$foo[i] = mark
und möglicherweise bei jeder<-
Änderung eine neue Kopie des Vektors des möglicherweise gesamten data.frame erstellt . Es wäre eine interessante Frage zu SO.df$var[i]
Notation dieselbe[<-.data.frame
Funktion? Mir ist aufgefallen, dass es in der Tat ziemlich lang ist. Wenn nicht, welche Funktion wird verwendet?d$foo[i]=mark
nach grob übersetztd <- `$<-`(d, 'foo', `[<-`(d$foo, i, mark))
, aber mit einigen temporären Variablen.Als Ari am Ende seiner Antwort erwähnt, das
Rcpp
undinline
machen Pakete es unglaublich einfach , schnell Dinge zu machen. Versuchen Sie als Beispiel dieseninline
Code (Warnung: nicht getestet):Es gibt ein ähnliches Verfahren für
#include
Dinge, bei denen Sie nur einen Parameter übergebenzu cxxfunction, as
include=inc
. Das wirklich Coole daran ist, dass es die gesamte Verknüpfung und Kompilierung für Sie übernimmt, sodass das Prototyping sehr schnell ist.Haftungsausschluss: Ich bin nicht ganz sicher, ob die Klasse von tmp eine numerische und keine numerische Matrix oder etwas anderes sein sollte. Aber ich bin mir größtenteils sicher.
Bearbeiten: Wenn Sie danach noch mehr Geschwindigkeit benötigen, ist OpenMP eine Parallelisierungsfunktion
C++
. Ich habe nicht versucht, es von zu verwendeninline
, aber es sollte funktionieren. Die Idee wäre, im Fall vonn
Kernen eine Schleifeniteration vonk
durchführen zu lassenk % n
. Eine passende Einführung finden Sie in Matloffs The Art of R Programming , das hier verfügbar ist in Kapitel 16, Rückgriff auf C .quelle
Die Antworten hier sind großartig. Ein kleiner Aspekt, der nicht behandelt wird, ist, dass die Frage lautet: " Mein PC funktioniert noch (ca. 10 Stunden) und ich habe keine Ahnung von der Laufzeit ". Ich habe bei der Entwicklung immer den folgenden Code in Schleifen eingefügt, um ein Gefühl dafür zu bekommen, wie sich Änderungen auf die Geschwindigkeit auswirken, und um zu überwachen, wie lange es dauern wird, bis der Vorgang abgeschlossen ist.
Funktioniert auch mit lapply.
Wenn die Funktion innerhalb der Schleife recht schnell ist, die Anzahl der Schleifen jedoch groß ist, sollten Sie nur gelegentlich drucken, da das Drucken auf die Konsole selbst einen Overhead hat. z.B
quelle
cat(sprintf("\nNow running... %40s, %s/%s \n", nm[i], i, n))
da ich normalerweise über benannte Dinge (mit Namen innm
) schleife .In R können Sie die Schleifenverarbeitung häufig mithilfe der
apply
Familienfunktionen beschleunigen (in Ihrem Fall wahrscheinlichreplicate
). Schauen Sie sich das anplyr
Paket an, das Fortschrittsbalken enthält.Eine andere Möglichkeit besteht darin, Schleifen vollständig zu vermeiden und durch vektorisierte Arithmetik zu ersetzen. Ich bin mir nicht sicher, was Sie genau tun, aber Sie können Ihre Funktion wahrscheinlich auf alle Zeilen gleichzeitig anwenden:
Dies wird viel schneller sein, und dann können Sie die Zeilen mit Ihrer Bedingung filtern:
Vektorisierte Arithmetik erfordert mehr Zeit und das Nachdenken über das Problem, aber dann können Sie manchmal mehrere Größenordnungen in der Ausführungszeit sparen.
quelle
Die Verarbeitung mit
data.table
ist eine praktikable Option:Wenn Sie die möglichen Vorteile der Zustandsfilterung ignorieren, ist dies sehr schnell. Wenn Sie die Berechnung für die Teilmenge der Daten durchführen können, ist dies natürlich hilfreich.
quelle