Zoeken…


Opmerkingen

For-lussen zijn een stroombesturingsmethode voor het herhalen van een taak of een reeks taken over een domein. De kernstructuur van een for-lus is

for ( [index] in [domain]){
  [body]
}

Waar

  1. [index] is een naam die exact één waarde van [domain] over elke iteratie van de lus.
  2. [domain] is een vector van waarden waarover moet worden herhaald.
  3. [body] is de set instructies die op elke iteratie moet worden toegepast.

Overweeg als een triviaal voorbeeld het gebruik van een for-lus om de cumulatieve som van een vector van waarden te verkrijgen.

x <- 1:4
cumulative_sum <- 0
for (i in x){
  cumulative_sum <- cumulative_sum + x[i]
}
cumulative_sum

Structuur van For Loops optimaliseren

For-lussen kunnen handig zijn voor het herdenken van taken en het uitvoeren van taken. Indien niet zorgvuldig geconstrueerd, kunnen ze echter zeer langzaam worden uitgevoerd in vergelijking met de voorkeur die wordt gebruikt in de van apply zijnde familie van functies. Desalniettemin zijn er een handvol elementen die u kunt opnemen in uw for-lusconstructie om de lus te optimaliseren. In veel gevallen zal een goede constructie van de for-lus rekenefficiëntie opleveren die zeer dicht bij die van een toepassingsfunctie ligt.

Een 'correct geconstrueerde' lus is gebaseerd op de kernstructuur en bevat een verklaring waarin het object wordt aangegeven dat elke iteratie van de lus vastlegt. Dit object moet zowel een klasse als een lengte hebben.

[output] <- [vector_of_length]
for ([index] in [length_safe_domain]){
  [output][index] <- [body]
}

Laten we ter illustratie een lus schrijven om elke waarde in een numerieke vector te kwadrateren (dit is slechts een triviaal voorbeeld ter illustratie. De 'juiste' manier om deze taak te x_squared <- x^2 is 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
}

Merk nogmaals op dat we eerst een bakje voor de uitvoer x_squared en de klasse "numeriek" hebben gegeven met dezelfde lengte als x . Bovendien hebben we een "lengte veilig domein" verklaard met behulp van de functie seq_along . seq_along genereert een vector van indices voor een object dat geschikt is voor gebruik in lussen. Hoewel het intuïtief lijkt te zijn om te gebruiken for (i in 1:length(x)) , als x 0 lengte heeft, probeert de lus het domein van 1:0 te herhalen, wat resulteert in een fout (de 0e index is niet gedefinieerd in R ).

Receptacle-objecten en veilige lengte-domeinen worden intern afgehandeld door de apply familie van functies en gebruikers worden aangemoedigd om de apply benadering zoveel mogelijk te gebruiken in plaats van for-loops. Echter, indien correct geconstrueerd, kan een for-lus af en toe een grotere codehelderheid bieden met minimaal verlies aan efficiëntie.

Vectoriseren voor lussen

For-lussen kunnen vaak een handig hulpmiddel zijn bij het conceptualiseren van de taken die binnen elke iteratie moeten worden voltooid. Wanneer de lus volledig is ontwikkeld en geconceptualiseerd, kan het voordelen bieden om de lus in een functie te veranderen.

In dit voorbeeld zullen we een for-lus ontwikkelen om het gemiddelde van elke kolom in de mtcars gegevensset te berekenen (nogmaals, een triviaal voorbeeld omdat dit zou kunnen worden bereikt via de functie colMeans ).

column_mean_loop <- vector("numeric", length(mtcars))
for (k in seq_along(mtcars)){
  column_mean_loop[k] <- mean(mtcars[[k]])
}

De for-lus kan worden omgezet in een toepassingsfunctie door de body van de lus als een functie te herschrijven.

col_mean_fn <- function(x) mean(x)
column_mean_apply <- vapply(mtcars, col_mean_fn, numeric(1))

En om de resultaten te vergelijken:

identical(column_mean_loop, 
          unname(column_mean_apply)) #* vapply added names to the elements
                                     #* remove them for comparison

Het voordeel van de gevectoriseerde vorm is dat we enkele regels code konden verwijderen. De mechanismen voor het bepalen van de lengte en het type van het uitvoerobject en het itereren over een veilig lengte-domein worden voor ons afgehandeld door de Apply-functie. Bovendien is de toepassingsfunctie een beetje sneller dan de lus. Het snelheidsverschil is in menselijke termen vaak te verwaarlozen, afhankelijk van het aantal iteraties en de complexiteit van het lichaam.

Basis voor lusconstructie

In dit voorbeeld berekenen we de gekwadrateerde afwijking voor elke kolom in een gegevensframe, in dit geval de mtcars .

Optie A: gehele getalindex

squared_deviance <- vector("list", length(mtcars))
for (i in seq_along(mtcars)){
  squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}

squared_deviance is een lijst met 11 elementen, zoals verwacht.

class(squared_deviance)
length(squared_deviance)

Optie B: tekenindex

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
}

Wat als we een data.frame als resultaat willen? Welnu, er zijn veel opties om een lijst om te zetten in andere objecten. In dit geval is het opslaan van de for resultaten in een data.frame misschien wel de eenvoudigste.

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

Het resultaat zal dezelfde gebeurtenis zijn, hoewel we de tekenoptie (B) gebruiken.

Optimale constructie van een For Loop

Om het effect van goed voor lusconstructie te illustreren, zullen we het gemiddelde van elke kolom op vier verschillende manieren berekenen:

  1. Een slecht geoptimaliseerde lus gebruiken
  2. Met behulp van een goed geoptimaliseerd voor lus
  3. Gebruik een *apply familie van functies *apply
  4. De functie colMeans

Elk van deze opties wordt in code weergegeven; een vergelijking van de berekeningstijd om elke optie uit te voeren zal getoond worden; en ten slotte zal een bespreking van de verschillen worden gegeven.

Slecht geoptimaliseerd voor lus

column_mean_poor <- NULL
for (i in 1:length(mtcars)){
  column_mean_poor[i] <- mean(mtcars[[i]])
}

Goed geoptimaliseerd voor lus

column_mean_optimal <- vector("numeric", length(mtcars))
for (i in seq_along(mtcars)){
  column_mean_optimal <- mean(mtcars[[i]])
}

vapply functie

column_mean_vapply <- vapply(mtcars, mean, numeric(1))

colMeans functie

column_mean_colMeans <- colMeans(mtcars)

Efficiëntie vergelijking

De resultaten van het benchmarken van deze vier benaderingen worden hieronder weergegeven (code niet weergegeven)

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

Merk op dat de geoptimaliseerde for lus de slecht geconstrueerde voor lus uitschakelde. De slecht geconstrueerde lus maakt de lengte van het uitvoerobject constant groter en bij elke verandering van de lengte evalueert R opnieuw de klasse van het object.

Een deel van deze overhead wordt verwijderd door de geoptimaliseerde voor lus door het type uitvoerobject en de lengte ervan aan te geven voordat de lus wordt gestart.

In dit voorbeeld verdubbelt het gebruik van een vapply functie echter de vapply , grotendeels omdat we R hebben verteld dat het resultaat numeriek moest zijn (als een resultaat niet numeriek was, zou een fout worden geretourneerd).

Het gebruik van de functie colMeans is iets langzamer dan de vapply functie. Dit verschil is te wijten aan enkele foutcontroles in colMeans en voornamelijk aan de as.matrix conversie (omdat mtcars een data.frame ) die niet in de vapply functie zijn uitgevoerd.

The Other Looping Constructs: while and repeat

R biedt twee extra lusconstructies, while en repeat , die typisch worden gebruikt in situaties waarin het aantal vereiste iteraties onbepaald is.


De while lus

De algemene vorm van een while lus is als volgt,

while (condition) {
    ## do something
    ## in loop body
}

waarbij de condition wordt geëvalueerd voorafgaand aan het binnengaan van het luslichaam. Als de condition TRUE , wordt de code in de body van de lus uitgevoerd en wordt dit proces herhaald totdat de condition geëvalueerd als FALSE (of een instructie break wordt bereikt; zie hieronder). In tegenstelling tot de for lus, moet, als een while lus een variabele gebruikt om incrementele iteraties uit te voeren, de variabele vooraf worden gedeclareerd en geïnitialiseerd en moet deze binnen de lus worden bijgewerkt. De volgende lussen vervullen bijvoorbeeld dezelfde taak:

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 de while lus hierboven is de lijn i <- i + 1 nodig om een oneindige lus te voorkomen.


Bovendien is het mogelijk om een while lus te beëindigen met een oproep om vanuit de body van de lus te break :

iter <- 0
while (TRUE) {
    if (runif(1) < 0.25) {
        break
    } else {
        iter <- iter + 1
    }
}
iter
#[1] 4

In dit voorbeeld is de condition altijd TRUE , dus de enige manier om de lus te beëindigen, is met een oproep om in het lichaam te break . Merk op dat de uiteindelijke waarde van iter afhangt van de status van uw PRNG wanneer dit voorbeeld wordt uitgevoerd, en bij elke uitvoering van de code (in wezen) verschillende resultaten zou moeten opleveren.


De repeat

De repeat is in wezen hetzelfde als while (TRUE) { ## something } en heeft de volgende vorm:

repeat ({
    ## do something
    ## in loop body
})

De extra {} zijn niet vereist, maar de () wel. Herschrijven van het vorige voorbeeld met repeat ,

iter <- 0
repeat ({
    if (runif(1) < 0.25) {
        break
    } else {
        iter <- iter + 1
    }
})
iter
#[1] 2 

Meer break

Het is belangrijk op te merken dat break alleen de onmiddellijk insluitende lus zal beëindigen . Dat wil zeggen, het volgende is een oneindige lus:

while (TRUE) {
    while (TRUE) {
        cat("inner loop\n")
        break
    }
    cat("outer loop\n")
}

Met een beetje creativiteit is het echter mogelijk om volledig vanuit een geneste lus te breken. Beschouw als voorbeeld de volgende uitdrukking, die in zijn huidige toestand oneindig zal doorlopen:

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))
        }
    }
}

Een mogelijkheid is om te erkennen dat, in tegenstelling tot de break , de return uitdrukking heeft wel de mogelijkheid om de controle over meerdere niveaus van het omsluiten van loops terug te keren. Aangezien return echter alleen geldig is wanneer deze binnen een functie wordt gebruikt, kunnen we break niet eenvoudigweg vervangen door return() hierboven, maar moeten we ook de gehele expressie als anonieme functie omwikkelen:

(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))
            }
        }
    }
})()

Als alternatief kunnen we een dummy-variabele ( exit ) maken voorafgaand aan de uitdrukking en deze activeren via <<- vanuit de binnenste lus wanneer we klaar zijn om te beëindigen:

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow