R Language
Kontrollieren Sie Flussstrukturen
Suche…
Bemerkungen
Bei Schleifen handelt es sich um eine Flusssteuerungsmethode zum Wiederholen einer Aufgabe oder einer Gruppe von Aufgaben über eine Domäne. Die Kernstruktur einer for-Schleife ist
for ( [index] in [domain]){
[body]
}
Woher
-
[index]
ist ein Name, der für jede Iteration der Schleife genau einen Wert von[domain]
annimmt. -
[domain]
ist ein Vektor von Werten, über den iteriert werden soll. -
[body]
ist der Satz von Anweisungen, die bei jeder Iteration anzuwenden sind.
Betrachten Sie als triviales Beispiel die Verwendung einer for-Schleife, um die kumulative Summe eines Wertevektors zu erhalten.
x <- 1:4
cumulative_sum <- 0
for (i in x){
cumulative_sum <- cumulative_sum + x[i]
}
cumulative_sum
Struktur von for-Schleifen optimieren
Für Schleifen kann es nützlich sein, Aufgaben zu konzeptionieren und auszuführen, die wiederholt werden sollen. Wenn sie nicht sorgfältig erstellt werden, können sie im Vergleich zu den bevorzugten Funktionen der apply
sehr langsam ausgeführt werden. Nichtsdestotrotz gibt es eine Handvoll Elemente, die Sie in Ihre for-Schleife-Konstruktion integrieren können, um die Schleife zu optimieren. In vielen Fällen führt eine gute Konstruktion der for-Schleife zu einer Recheneffizienz, die der einer Anwendungsfunktion sehr nahe kommt.
Eine ordnungsgemäß erstellte for-Schleife baut auf der Kernstruktur auf und enthält eine Anweisung, die das Objekt deklariert, das jede Iteration der Schleife erfasst. Dieses Objekt sollte sowohl eine Klasse als auch eine Länge deklariert haben.
[output] <- [vector_of_length]
for ([index] in [length_safe_domain]){
[output][index] <- [body]
}
Lassen Sie uns zur Veranschaulichung eine Schleife schreiben, um jeden Wert in einem numerischen Vektor zu x_squared <- x^2
(dies ist ein triviales Beispiel, das nur zur Veranschaulichung dient. Die "richtige" Art, diese Aufgabe zu x_squared <- x^2
wäre x_squared <- x^2
).
x <- 1:100
x_squared <- vector("numeric", length = length(x))
for (i in seq_along(x)){
x_squared[i] <- x[i]^2
}
x_squared
wieder, dass wir zuerst einen Behälter für die Ausgabe x_squared
haben und ihm die Klasse "numerisch" mit der gleichen Länge wie x
. Außerdem haben wir mit der Funktion seq_along
eine "length safe domain" seq_along
. seq_along
erzeugt einen seq_along
für ein Objekt, das für for-Schleifen geeignet ist. Die Verwendung for (i in 1:length(x))
erscheint intuitiv. Wenn x
die Länge 0 hat, versucht die Schleife, die Domäne von 1:0
zu durchlaufen, was zu einem Fehler führt (der 0. Index ist in R nicht definiert ).
Behälterobjekte und längensichere Domänen werden intern von der apply
der Funktionen gehandhabt, und die Benutzer werden aufgefordert, den apply
so weit wie möglich anstelle von for-Schleifen zu übernehmen. Bei richtiger Konstruktion kann eine for-Schleife jedoch gelegentlich eine größere Code-Klarheit bei minimalem Effizienzverlust bieten.
Vektorisieren für Loops
Schleifen können häufig ein nützliches Werkzeug sein, um die Aufgaben zu konzipieren, die in jeder Iteration ausgeführt werden müssen. Wenn die Schleife vollständig entwickelt und konzipiert ist, kann es vorteilhaft sein, aus der Schleife eine Funktion zu machen.
In diesem Beispiel werden wir eine for-Schleife entwickeln, um den Mittelwert jeder Spalte im mtcars
Dataset zu berechnen (wiederum ein triviales Beispiel, wie es mit der Funktion colMeans
).
column_mean_loop <- vector("numeric", length(mtcars))
for (k in seq_along(mtcars)){
column_mean_loop[k] <- mean(mtcars[[k]])
}
Die for-Schleife kann in eine Apply-Funktion umgewandelt werden, indem der Hauptteil der Schleife als Funktion umgeschrieben wird.
col_mean_fn <- function(x) mean(x)
column_mean_apply <- vapply(mtcars, col_mean_fn, numeric(1))
Und um die Ergebnisse zu vergleichen:
identical(column_mean_loop,
unname(column_mean_apply)) #* vapply added names to the elements
#* remove them for comparison
Die Vorteile der vektorisierten Form sind, dass wir einige Codezeilen entfernen konnten. Die Mechanik der Bestimmung der Länge und des Typs des Ausgabeobjekts und des Iterierens über eine Länge sichere Domäne wird von der Apply-Funktion für uns behandelt. Außerdem ist die Apply-Funktion etwas schneller als die Schleife. Der Geschwindigkeitsunterschied ist in menschlicher Hinsicht oft vernachlässigbar, abhängig von der Anzahl der Iterationen und der Komplexität des Körpers.
Grundlegend für die Schleifenkonstruktion
In diesem Beispiel berechnen wir die quadratische Abweichung für jede Spalte in einem mtcars
, in diesem Fall die mtcars
.
Option A: ganzzahliger Index
squared_deviance <- vector("list", length(mtcars))
for (i in seq_along(mtcars)){
squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}
squared_deviance
ist wie erwartet eine Liste mit 11 Elementen.
class(squared_deviance)
length(squared_deviance)
Option B: Zeichenindex
squared_deviance <- vector("list", length(mtcars))
Squared_deviance <- setNames(squared_deviance, names(mtcars))
for (k in names(mtcars)){
squared_deviance[[k]] <- (mtcars[[k]] - mean(mtcars[[k]]))^2
}
Was ist, wenn wir als Ergebnis einen data.frame
haben wollen? Nun, es gibt viele Möglichkeiten, eine Liste in andere Objekte umzuwandeln. In diesem Fall ist es jedoch am einfachsten, die for
Ergebnisse in einem data.frame
zu speichern.
squared_deviance <- mtcars #copy the original
squared_deviance[TRUE]<-NA #replace with NA or do squared_deviance[,]<-NA
for (i in seq_along(mtcars)){
squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}
dim(squared_deviance)
[1] 32 11
Das Ergebnis wird dasselbe Ereignis sein, obwohl wir die Zeichenoption (B) verwenden.
Optimale Konstruktion einer for-Schleife
Um den Effekt von good for loop zu veranschaulichen, berechnen wir den Mittelwert jeder Spalte auf vier verschiedene Arten:
- Verwendung einer schlecht optimierten Schleife
- Verwenden Sie eine gut für for-Schleife
- Verwendung einer
*apply
Funktionsfamilie - Verwenden der
colMeans
Funktion
Jede dieser Optionen wird im Code angezeigt. Ein Vergleich der Rechenzeit zum Ausführen jeder Option wird angezeigt. und schließlich wird eine Diskussion der Unterschiede gegeben.
Schlecht für Schleifen optimiert
column_mean_poor <- NULL
for (i in 1:length(mtcars)){
column_mean_poor[i] <- mean(mtcars[[i]])
}
Gut für die Schleife optimiert
column_mean_optimal <- vector("numeric", length(mtcars))
for (i in seq_along(mtcars)){
column_mean_optimal <- mean(mtcars[[i]])
}
vapply
Funktion
column_mean_vapply <- vapply(mtcars, mean, numeric(1))
colMeans
Funktion
column_mean_colMeans <- colMeans(mtcars)
Effizienzvergleich
Die Ergebnisse des Benchmarking dieser vier Ansätze sind unten dargestellt (Code wird nicht angezeigt)
Unit: microseconds
expr min lq mean median uq max neval cld
poor 240.986 262.0820 287.1125 275.8160 307.2485 442.609 100 d
optimal 220.313 237.4455 258.8426 247.0735 280.9130 362.469 100 c
vapply 107.042 109.7320 124.4715 113.4130 132.6695 202.473 100 a
colMeans 155.183 161.6955 180.2067 175.0045 194.2605 259.958 100 b
Beachten Sie, dass die for
Schleifen optimierte Schleife die schlecht konstruierte Schleife überschneidet. Die schlecht konstruierte Schleife erhöht ständig die Länge des Ausgabeobjekts, und bei jeder Änderung der Länge wertet R die Klasse des Objekts neu aus.
Ein Teil dieser zusätzlichen Belastung wird durch die optimierte for-Schleife beseitigt, indem der Typ des Ausgabeobjekts und seine Länge vor dem Start der Schleife deklariert werden.
In diesem Beispiel verdoppelt jedoch die Verwendung einer vapply
Funktion die Recheneffizienz, hauptsächlich weil wir R gesagt haben, dass das Ergebnis numerisch sein müsste (wenn ein Ergebnis nicht numerisch wäre, würde ein Fehler zurückgegeben werden).
Die Verwendung der colMeans
Funktion ist etwas langsamer als die vapply
Funktion. Dieser Unterschied ist auf einige colMeans
in colMeans
und hauptsächlich auf die as.matrix
Konvertierung (da mtcars
ein data.frame
) zurückzuführen, die nicht in der vapply
Funktion ausgeführt wurde.
Die anderen Looping-Konstrukte: während und wiederholen
R stellt zwei zusätzliche Schleifenkonstrukte while
und repeat
bereit, die normalerweise in Situationen verwendet werden, in denen die Anzahl der erforderlichen Iterationen unbestimmt ist.
Die while
Schleife
Die allgemeine Form einer while
Schleife lautet wie folgt:
while (condition) {
## do something
## in loop body
}
wobei der condition
vor dem Eintritt in den Schleifenkörper bewertet wird. Wenn die condition
den TRUE
condition
, wird der Code innerhalb des Schleifenkörpers ausgeführt, und dieser Vorgang wird wiederholt, bis die condition
den FALSE
(oder eine break
Anweisung erreicht wird; siehe unten). Im Gegensatz zur for
Schleife muss, wenn eine while
Schleife eine Variable zum Durchführen von inkrementellen Iterationen verwendet, die Variable vorab deklariert und initialisiert und innerhalb des Schleifenkörpers aktualisiert werden. Die folgenden Schleifen führen beispielsweise dieselbe Aufgabe aus:
for (i in 0:4) {
cat(i, "\n")
}
# 0
# 1
# 2
# 3
# 4
i <- 0
while (i < 5) {
cat(i, "\n")
i <- i + 1
}
# 0
# 1
# 2
# 3
# 4
In der while
Schleife oben ist die Linie i <- i + 1
erforderlich, um eine Endlosschleife zu verhindern.
Darüber hinaus ist es möglich, eine while
Schleife mit einem Aufruf zu beenden, break
den Loop-Rumpf zu unterbrechen:
iter <- 0
while (TRUE) {
if (runif(1) < 0.25) {
break
} else {
iter <- iter + 1
}
}
iter
#[1] 4
In diesem Beispiel ist condition
immer TRUE
. Die einzige Möglichkeit, die Schleife zu beenden, ist die Aufforderung, den Body zu break
. Beachten Sie, dass der endgültige iter
Wert beim Ausführen dieses Beispiels vom Status Ihres PRNG abhängt und bei jeder Ausführung des Codes (im Wesentlichen) zu unterschiedlichen Ergebnissen führen sollte.
Die repeat
Das repeat
ist im Wesentlichen dasselbe wie while (TRUE) { ## something }
und hat die folgende Form:
repeat ({
## do something
## in loop body
})
Die zusätzlichen {}
sind nicht erforderlich, aber die ()
sind. Umschreiben des vorherigen Beispiels mit repeat
,
iter <- 0
repeat ({
if (runif(1) < 0.25) {
break
} else {
iter <- iter + 1
}
})
iter
#[1] 2
Mehr zur break
Es ist wichtig zu wissen, dass break
nur die unmittelbar einschließende Schleife beendet . Das heißt, das Folgende ist eine Endlosschleife:
while (TRUE) {
while (TRUE) {
cat("inner loop\n")
break
}
cat("outer loop\n")
}
Mit etwas Kreativität ist es jedoch möglich, innerhalb einer verschachtelten Schleife vollständig zu brechen. Betrachten Sie als Beispiel den folgenden Ausdruck, der in seinem aktuellen Zustand endlos durchlaufen wird:
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
break
} else {
cat(sprintf("x is %.5f\n", x))
}
}
}
Eine Möglichkeit ist , zu erkennen , dass, im Gegensatz zu break
, die return
Ausdruck die Fähigkeit hat Kontrolle über mehrere Ebenen der umschließenden Schlaufen zurückzukehren. Da return
jedoch nur gültig ist, wenn es innerhalb einer Funktion verwendet wird, können wir break
nicht einfach durch return()
ersetzen, sondern müssen den gesamten Ausdruck als anonyme Funktion einschließen:
(function() {
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
return()
} else {
cat(sprintf("x is %.5f\n", x))
}
}
}
})()
Alternativ können wir eine Dummy-Variable ( exit
) vor dem Ausdruck erstellen und über <<-
aus der inneren Schleife aktivieren, wenn wir fertig sind:
exit <- FALSE
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
exit <<- TRUE
break
} else {
cat(sprintf("x is %.5f\n", x))
}
}
if (exit) break
}