R Language
Kontrollera flödesstrukturer
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
-
[index]
är ett namn som tar exakt ett värde på[domain]
över varje iteration av slingan. -
[domain]
är en vektor med värden att itera. -
[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:
- Med hjälp av en dåligt optimerad för loop
- Med hjälp av ett väl optimerat för för loop
- Använd en
*apply
av funktioner - 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
}