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

  1. [index] est un nom qui prend exactement une valeur de [domain] sur chaque itération de la boucle.
  2. [domain] est un vecteur de valeurs sur lequel itérer.
  3. [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:

  1. Utiliser une boucle mal optimisée pour
  2. Utiliser un bien optimisé pour for loop
  3. Utiliser une famille de fonctions *apply
  4. 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
}


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow