R Language
Structures d'écoulement de contrôle
Recherche…
Remarques
Les boucles for sont une méthode de contrôle de flux permettant de répéter une tâche ou un ensemble de tâches sur un domaine. La structure de base d'une boucle for est
for ( [index] in [domain]){
[body]
}
Où
-
[index]
est un nom qui prend exactement une valeur de[domain]
sur chaque itération de la boucle. -
[domain]
est un vecteur de valeurs sur lequel itérer. -
[body]
est l'ensemble des instructions à appliquer à chaque itération.
À titre d'exemple trivial, considérez l'utilisation d'une boucle for pour obtenir la somme cumulée d'un vecteur de valeurs.
x <- 1:4
cumulative_sum <- 0
for (i in x){
cumulative_sum <- cumulative_sum + x[i]
}
cumulative_sum
Optimisation de la structure des boucles For
Les boucles for peuvent être utiles pour conceptualiser et exécuter des tâches à répéter. S'ils ne sont pas construits avec soin, ils peuvent cependant être très lents à exécuter par rapport aux fonctions préférées de la famille de fonctions apply
. Néanmoins, vous pouvez inclure quelques éléments dans votre construction en boucle pour optimiser la boucle. Dans de nombreux cas, une bonne construction de la boucle for produira une efficacité de calcul très proche de celle d'une fonction d'application.
Une boucle "correctement construite" est construite sur la structure de base et inclut une déclaration déclarant l'objet qui capturera chaque itération de la boucle. Cet objet doit avoir une classe et une longueur déclarées.
[output] <- [vector_of_length]
for ([index] in [length_safe_domain]){
[output][index] <- [body]
}
Pour illustrer notre propos, écrivons une boucle pour mettre chaque valeur en carré dans un vecteur numérique (ceci est un exemple trivial à des fins d'illustration uniquement. La manière correcte de remplir cette tâche serait 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
}
Encore une fois, notez que nous avons d'abord déclaré un réceptacle pour la sortie x_squared
, et lui x_squared
donné la classe "numeric" avec la même longueur que x
. De plus, nous avons déclaré un "domaine sécurisé" en utilisant la fonction seq_along
. seq_along
génère un vecteur d'indices pour un objet pouvant être utilisé dans des boucles. Bien que cela semble intuitif à utiliser for (i in 1:length(x))
, si x
a 0 longueur, la boucle essaiera d'itérer sur le domaine de 1:0
, entraînant une erreur (le 0ème index n'est pas défini dans R). ).
Les objets réceptacle et les domaines sécurisés en longueur sont gérés en interne par la famille de fonctions apply
et les utilisateurs sont encouragés à adopter l'approche apply
à la place des boucles autant que possible. Cependant, si elle est correctement construite, une boucle for peut parfois fournir une plus grande clarté du code avec une perte d'efficacité minimale.
Vectoriser pour les boucles
Les boucles for peuvent souvent être un outil utile pour conceptualiser les tâches à accomplir dans chaque itération. Lorsque la boucle est complètement développée et conceptualisée, il peut être avantageux de transformer la boucle en une fonction.
Dans cet exemple, nous allons développer une boucle for pour calculer la moyenne de chaque colonne du mtcars
données mtcars
(encore une fois, un exemple trivial car cela pourrait être accompli via la fonction colMeans
).
column_mean_loop <- vector("numeric", length(mtcars))
for (k in seq_along(mtcars)){
column_mean_loop[k] <- mean(mtcars[[k]])
}
La boucle for peut être convertie en fonction apply en réécrivant le corps de la boucle en tant que fonction.
col_mean_fn <- function(x) mean(x)
column_mean_apply <- vapply(mtcars, col_mean_fn, numeric(1))
Et pour comparer les résultats:
identical(column_mean_loop,
unname(column_mean_apply)) #* vapply added names to the elements
#* remove them for comparison
Les avantages de la forme vectorisée sont que nous avons pu éliminer quelques lignes de code. Les mécanismes de détermination de la longueur et du type de l’objet de sortie et de la itération sur un domaine sûr de longueur sont gérés pour nous par la fonction d’application. De plus, la fonction Apply est un peu plus rapide que la boucle. La différence de vitesse est souvent négligeable sur le plan humain en fonction du nombre d'itérations et de la complexité du corps.
Basic For Loop Construction
Dans cet exemple, nous allons calculer la déviation au carré pour chaque colonne dans une trame de données, dans ce cas les mtcars
.
Option A: index entier
squared_deviance <- vector("list", length(mtcars))
for (i in seq_along(mtcars)){
squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}
squared_deviance
est une liste de 11 éléments, comme prévu.
class(squared_deviance)
length(squared_deviance)
Option B: index des caractères
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
}
Et si on veut un data.frame
en conséquence? Eh bien, il existe de nombreuses options pour transformer une liste en d'autres objets. Cependant, et peut-être le plus simple dans ce cas, sera de stocker le for
résultats dans un 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
Le résultat sera le même événement même si nous utilisons l'option de caractère (B).
Construction optimale d'une boucle For
Pour illustrer l'effet de la construction sur la boucle, nous allons calculer la moyenne de chaque colonne de quatre manières différentes:
- Utiliser une boucle mal optimisée pour
- Utiliser un bien optimisé pour for loop
- Utiliser une famille de fonctions
*apply
- Utiliser la fonction
colMeans
Chacune de ces options sera affichée dans le code; une comparaison du temps de calcul pour exécuter chaque option sera affichée; et enfin une discussion des différences sera donnée.
Mal optimisé pour la boucle
column_mean_poor <- NULL
for (i in 1:length(mtcars)){
column_mean_poor[i] <- mean(mtcars[[i]])
}
Bien optimisé pour la boucle
column_mean_optimal <- vector("numeric", length(mtcars))
for (i in seq_along(mtcars)){
column_mean_optimal <- mean(mtcars[[i]])
}
vapply
Fonction
column_mean_vapply <- vapply(mtcars, mean, numeric(1))
Fonction colMeans
column_mean_colMeans <- colMeans(mtcars)
Comparaison d'efficacité
Les résultats de l'analyse comparative de ces quatre approches sont présentés ci-dessous (code non affiché)
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
Notez que la boucle optimisée for
boucle dépasse la boucle mal construite. La boucle mal construite augmente constamment la longueur de l'objet de sortie et à chaque changement de longueur, R réévalue la classe de l'objet.
Une partie de cette charge est supprimée par la boucle optimisée en déclarant le type d'objet de sortie et sa longueur avant de démarrer la boucle.
Dans cet exemple, cependant, l'utilisation d'une fonction vapply
double l'efficacité du calcul, en grande partie parce que nous avons dit à R que le résultat devait être numérique (si l'un des résultats n'était pas numérique, une erreur serait renvoyée).
L'utilisation de la fonction colMeans
est un contact plus lent que la fonction vapply
. Cette différence est attribuable à certaines vérifications d’erreur effectuées dans colMeans
et principalement à la conversion as.matrix
(car mtcars
est un data.frame
) qui n’a pas été effectuée dans la fonction vapply
.
Les autres constructions en boucle: while et repeat
R fournit deux constructions en boucle supplémentaires, while
et repeat
, qui sont généralement utilisées dans les situations où le nombre d'itérations requis est indéterminé.
Le while
en boucle
La forme générale d'un while
en boucle est la suivante,
while (condition) {
## do something
## in loop body
}
où la condition
est évaluée avant d'entrer dans le corps de la boucle. Si la condition
évaluée à TRUE
, le code à l'intérieur du corps de la boucle est exécuté et ce processus se répète jusqu'à ce que la condition
évaluée à FALSE
(ou une instruction de break
est atteinte, voir ci-dessous). Contrairement à la for
la boucle, si while
boucle utilise une variable pour effectuer des itérations supplémentaires, la variable doit être déclarée et initialisé à l' avance, et doit être mis à jour dans le corps de la boucle. Par exemple, les boucles suivantes accomplissent la même tâche:
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
Dans le while
en boucle au- dessus, la ligne i <- i + 1
est nécessaire pour éviter une boucle infinie.
De plus, il est possible de mettre fin à une while
boucle avec un appel à break
à l' intérieur du corps de la boucle:
iter <- 0
while (TRUE) {
if (runif(1) < 0.25) {
break
} else {
iter <- iter + 1
}
}
iter
#[1] 4
Dans cet exemple, la condition
est toujours TRUE
, la seule façon de mettre fin à la boucle est avec un appel à break
l' intérieur du corps. Notez que la valeur finale de iter
dépendra de l'état de votre PRNG lors de l'exécution de cet exemple et devrait produire différents résultats (essentiellement) à chaque exécution du code.
La boucle de repeat
La construction de repeat
est essentiellement la même que while (TRUE) { ## something }
, et a la forme suivante:
repeat ({
## do something
## in loop body
})
Les extra {}
ne sont pas obligatoires, mais les ()
sont. Réécrire l'exemple précédent en utilisant repeat
,
iter <- 0
repeat ({
if (runif(1) < 0.25) {
break
} else {
iter <- iter + 1
}
})
iter
#[1] 2
Plus sur la break
Il est important de noter que la break
ne mettra fin à la boucle qui se termine immédiatement . Autrement dit, ce qui suit est une boucle infinie:
while (TRUE) {
while (TRUE) {
cat("inner loop\n")
break
}
cat("outer loop\n")
}
Avec un peu de créativité, cependant, il est possible de rompre entièrement dans une boucle imbriquée. À titre d'exemple, considérons l'expression suivante, qui, dans son état actuel, va boucler indéfiniment:
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))
}
}
}
Une possibilité est de reconnaître que, contrairement à la break
, le return
d' expression a la capacité de rendre le contrôle sur plusieurs niveaux de boucles enserrant. Cependant, comme return
n'est valide que lorsqu'il est utilisé dans une fonction, nous ne pouvons pas simplement remplacer break
avec return()
ci-dessus, mais nous devons également envelopper l'intégralité de l'expression en tant que fonction anonyme:
(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))
}
}
}
})()
Alternativement, nous pouvons créer une variable factice ( exit
) avant l'expression et l'activer via <<-
depuis la boucle interne lorsque nous sommes prêts à terminer:
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
}