Sök…


Anmärkningar

För slingor är en flödeskontrollmetod för att upprepa en uppgift eller uppsättning uppgifter över en domän. Kärnstrukturen för en för slinga är

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

Var

  1. [index] är ett namn som tar exakt ett värde på [domain] över varje iteration av slingan.
  2. [domain] är en vektor med värden att itera.
  3. [body] är uppsättningen instruktioner som ska tillämpas på varje iteration.

Som ett triviellt exempel kan du överväga att använda en for-loop för att erhålla den kumulativa summan av en vektor med värden.

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

Optimera strukturen för slingor

För slingor kan vara användbart för att konceptualisera och utföra uppgifter för att upprepa. Om de inte konstrueras noggrant kan de emellertid vara mycket långsamma att utföra jämfört med den föredragna som används av apply av funktioner. Ändå finns det en handfull element som du kan inkludera i din för slingkonstruktion för att optimera slingan. I många fall kommer god konstruktion av for-loop att ge beräkningseffektivitet mycket nära den för en tillämpad funktion.

En "korrekt konstruerad" för slinga bygger på kärnstrukturen och innehåller ett uttalande som förklarar objektet som kommer att fånga varje iteration av slingan. Detta objekt bör ha både en klass och en längd deklarerad.

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

För att illustrera, låt oss skriva en slinga för att kvadratera varje värde i en numerisk vektor (detta är ett triviellt exempel för illustrationer. Det "korrekta" sättet att slutföra denna uppgift skulle vara 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
}

Återigen, märker att vi först förklarade en behållare för utgången x_squared , och gav den klassen "numerisk" med samma längd som x . Dessutom förklarade vi en "längdsäker domän" med funktionen seq_along . seq_along genererar en vektor med index för ett objekt som är lämpligt att använda i för öglor. Även om det verkar intuitivt att använda for (i in 1:length(x)) , om x har 0 längd, kommer slingan att försöka iterera över domänen 1:0 , vilket resulterar i ett fel (det 0: e indexet är odefinierat i R ).

Hållarobjekt och längdsäkra domäner hanteras internt av apply av funktioner och användare uppmuntras att apply istället för för öglor så mycket som möjligt. Men om den är korrekt konstruerad, kan en for loop ibland ge större kodklarhet med minimal förlust av effektivitet.

Vectorizing för öglor

För slingor kan ofta vara ett användbart verktyg för att konceptualisera de uppgifter som måste slutföras inom varje iteration. När slingan är helt utvecklad och konceptualiserad kan det vara fördelar med att förvandla slingan till en funktion.

I det här exemplet kommer vi att utveckla en for-loop för att beräkna medelvärdet för varje kolumn i mtcars (igen, ett triviellt exempel eftersom det skulle kunna uppnås via colMeans funktionen).

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

For-loopen kan konverteras till en tillämpningsfunktion genom att skriva om loopens kropp som en funktion.

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

Och för att jämföra resultaten:

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

Fördelarna med den vektoriserade formen är att vi kunde eliminera några kodrader. Mekaniken för att bestämma längden och typen av utgångsobjektet och iterera över en längdsäker domän hanteras för oss av tillämpningsfunktionen. Dessutom är tillämpningsfunktionen lite snabbare än slingan. Skillnaden i hastighet är ofta försumbar i mänskliga termer beroende på antalet iterationer och kroppens komplexitet.

Grundläggande för slingkonstruktion

I det här exemplet beräknar vi kvadratavvikelsen för varje kolumn i en dataram, i detta fall mtcars .

Alternativ A: heltalindex

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

squared_deviance är en lista över 11 element, som förväntat.

class(squared_deviance)
length(squared_deviance)

Alternativ B: teckenindex

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
}

Tänk om vi vill ha ett data.frame som resultat? Det finns många alternativ för att förvandla en lista till andra objekt. Men, och kanske det enklaste i detta fall kommer att vara att lagra for resulterar i en data.frame .

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

Resultatet blir samma händelse men vi använder teckenalternativet (B).

Optimal konstruktion av en för ögla

För att illustrera effekten av bra för slingkonstruktion kommer vi att beräkna medelvärdet för varje kolumn på fyra olika sätt:

  1. Med hjälp av en dåligt optimerad för loop
  2. Med hjälp av ett väl optimerat för för loop
  3. Använd en *apply av funktioner
  4. Använda colMeans funktionen

Var och en av dessa alternativ visas i kod; en jämförelse av beräkningstiden för att utföra varje alternativ kommer att visas; och slutligen kommer en diskussion om skillnaderna att ges.

Dåligt optimerad för slinga

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

Väl optimerad för slinga

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)

Effektiv jämförelse

Resultaten av benchmarking av dessa fyra metoder visas nedan (kod visas inte)

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

Lägg märke till att den optimerade for slingan kantade ut det dåligt konstruerade för slingan. Det dåligt konstruerade för slingan ökar konstant längden på utgångsobjektet, och vid varje ändring av längden utvärderar R objektets klass.

En del av denna överbelastning tas bort av den optimerade för slingan genom att ange typen av utgångsobjekt och dess längd innan slingan startas.

I detta exempel fördubblar dock användningen av en vapply funktion beräkningseffektiviteten, till stor del för att vi berättade för R att resultatet måste vara numeriskt (om något resultat inte var numeriskt skulle ett fel returneras).

Användning av colMeans funktionen är en pekning långsammare än vapply funktionen. Denna skillnad kan hänföras till vissa felkontroller som utförs i colMeans och främst till as.matrix omvandlingen (eftersom mtcars är en data.frame ) som inte utfördes i vapply funktionen.

De andra löpande konstruktionerna: medan och upprepa

R tillhandahåller ytterligare två slingkonstruktioner, while och repeat , som vanligtvis används i situationer där antalet iterationer som krävs är obestämda.


while slingan

Den allmänna formen av en while slinga är som följer,

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

där condition utvärderas innan man går in i slingkroppen. Om condition utvärderar till TRUE , är koden insidan av slingan exekveras, och denna process upprepas tills condition utvärderar till FALSE (eller en break uttalande uppnås; se nedan). Till skillnad från den for loop, om en while slinga använder en variabel för att utföra inkrementella iterationer, variabeln måste deklareras och initieras i förväg, och måste uppdateras inom slingan. Följande slingor utför exempelvis samma uppgift:

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 

I while slingan ovan är linjen i <- i + 1 nödvändig för att förhindra en oändlig slinga.


Dessutom är det möjligt att avsluta en while slinga med ett samtal till break från insidan av slingan:

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

I detta exempel är condition alltid TRUE , så det enda sättet att avsluta slingan är med ett uppmaning att break inuti kroppen. Observera att det slutliga värdet för iter beror på statusen för din PRNG när detta exempel körs och bör ge olika resultat (i huvudsak) varje gång koden körs.


repeat slingan

repeat är väsentligen densamma som while (TRUE) { ## something } och har följande form:

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

De extra {} krävs inte, men () är. Omskrivning av föregående exempel med repeat ,

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

Mer om break

Det är viktigt att notera att break bara avslutar den omedelbart slutna slingan . Det vill säga följande är en oändlig slinga:

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

Med lite kreativitet är det dock möjligt att bryta helt från en kapslad slinga. Tänk som exempel på följande uttryck, som i sitt nuvarande tillstånd kommer att slinga oändligt:

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

En möjlighet är att inse att, till skillnad från break , den return gör uttrycket har förmågan att returnera kontroll över flera nivåer av omslutande slingor. Men eftersom return endast är giltig när den används i en funktion kan vi inte bara ersätta break med return() ovan, utan behöver också radera in hela uttrycket som en anonym funktion:

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

Alternativt kan vi skapa en dummyvariabel ( exit ) före uttrycket och aktivera den via <<- från den inre slingan när vi är redo att avsluta:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow