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

  1. [index] ist ein Name, der für jede Iteration der Schleife genau einen Wert von [domain] annimmt.
  2. [domain] ist ein Vektor von Werten, über den iteriert werden soll.
  3. [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:

  1. Verwendung einer schlecht optimierten Schleife
  2. Verwenden Sie eine gut für for-Schleife
  3. Verwendung einer *apply Funktionsfamilie
  4. 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
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow