La boucle for de base
Une boucle for en R exécute un bloc de code une fois pour chaque élément d'un vecteur ou d'une liste. La variable de boucle prend tour à tour chaque valeur :
Trois éléments, trois passages. Il n'y a pas de compteur à mettre en place ni de condition à maintenir - for (fruit in fruits) se lit « pour chaque fruit dans fruits », et c'est exactement ce que ça fait. La même forme itère sur les éléments d'une liste, sur une suite de nombres (for (i in 1:10)), ou sur les noms de n'importe quoi via names().
Afficher dans une boucle demande print() ou cat()
En console, taper x affiche sa valeur. Dans un corps de boucle, non - l'affichage automatique ne se produit qu'au niveau supérieur, et une expression nue dans une boucle est évaluée puis jetée :
Donc quand une boucle « ne fait rien », c'est la première chose à vérifier. Utilise print() pour un coup d'œil rapide sur une valeur, ou cat() quand tu composes toi-même une ligne de sortie (n'oublie pas le "\n" - cat() n'en ajoute pas).
Parcourir les indices avec seq_along()
Parfois tu as besoin de la position autant que de la valeur - pour écrire dans un autre vecteur au même indice, ou pour afficher une liste numérotée. Parcours les indices avec seq_along() :
Tu verras for (i in 1:length(prices)) dans du code plus ancien, et ça abrite un vrai bug. Quand le vecteur est vide, length() vaut 0, et 1:0 ne signifie pas « aucune itération » - le deux-points compte à rebours :
Une boucle sur 1:length(empty) s'exécute deux fois, en indexant des éléments qui n'existent pas (empty[1] vaut NA, et les affectations créent des entrées fantômes). seq_along() renvoie une séquence vide pour un vecteur vide, donc le corps de la boucle ne s'exécute simplement jamais. Fais de seq_along() une habitude ; son cousin seq_len(n) fait le même travail quand tu as un compte plutôt qu'un vecteur.
Collecter les résultats : préalloue, ne fais pas grandir
Le premier réflexe naturel pour construire des résultats ressemble à ça :
squares <- c()
for (i in 1:10000) {
squares <- c(squares, i^2) # copies the ENTIRE vector every pass
}
Ça marche, mais chaque c(squares, ...) alloue un tout nouveau vecteur et y recopie tous les anciens éléments. À la fin tu as recopié environ 50 millions de nombres pour en produire 10 000. Cet anti-motif du vecteur qui grandit est la principale raison pour laquelle on dit que les boucles R sont lentes.
La correction : crée le résultat à sa taille finale d'abord, puis affecte par indice :
Pour des résultats qui ne sont pas des nombres, la même idée s'applique avec character(n), logical(n), ou vector("list", n) pour une liste d'objets quelconques. Les boucles préallouées sont parfaitement rapides.
next et break
Deux mots-clés pilotent une boucle depuis l'intérieur de son corps : next abandonne le passage courant et saute à l'élément suivant ; break sort complètement de la boucle.
Ça affiche 2 4 6 8 : les valeurs impaires sont sautées par next, et quand 10 arrive, break met fin à la boucle avant l'affichage. Utilise next pour écarter des cas tôt (ça garde le corps principal non indenté), et break quand une boucle a trouvé ce qu'elle cherchait et n'a aucune raison de continuer.
Boucles imbriquées
Un corps de boucle peut contenir une autre boucle. La boucle interne s'exécute entièrement à chaque passage de la boucle externe - la démonstration classique est une table de multiplication :
Pour chaque ligne i, la boucle interne parcourt toutes les colonnes j, et le saut de ligne de la ligne s'affiche après la fin de la boucle interne. Les boucles imbriquées vont bien à deux niveaux ; à trois ou plus, le corps demande généralement à devenir une fonction, ou le calcul entier demande à devenir un outer() ou une opération matricielle.
L'imbrication couvre aussi le parcours d'une liste de vecteurs - boucle externe sur la liste, boucle interne (ou mieux, un appel vectorisé) sur chaque élément :
Note qu'il n'y a finalement pas de boucle interne - mean() traite tout le vecteur interne en un seul appel. Cette observation se généralise, ce qui nous amène à la partie honnête.
Quand ne pas boucler
R est un langage vectorisé : ses opérations de base travaillent déjà sur des vecteurs entiers d'un coup. Une boucle qui transforme chaque élément un par un est souvent une façon plus longue d'écrire une seule ligne :
Préfère la forme vectorisée dès qu'il en existe une - elle est plus courte, plus difficile à rater, et plus rapide. Pour appliquer une fonction à chaque élément d'une liste, la famille apply (sapply, lapply, vapply) joue le même rôle.
Mais n'en fais pas un dogme « les boucles c'est mal ». Une boucle est le bon outil quand les itérations dépendent des précédentes (état courant, simulations), quand tu fais des effets de bord comme écrire des fichiers, ou simplement quand la boucle est la version que toi et tes lecteurs comprenez d'un coup d'œil. Une boucle claire vaut mieux qu'une ligne astucieuse que personne ne sait décoder.
Ce que tu retiens
for (x in v) { ... }exécute le corps une fois par élément - aucune gestion de compteur.- Dans une boucle, affiche explicitement avec
print()oucat(); les expressions nues sont jetées. - Parcours les positions avec
seq_along(v), jamais1:length(v)- ce dernier s'exécute deux fois sur un vecteur vide. - Préalloue les résultats avec
numeric(n)/vector("list", n)et affecte par indice ; faire grandir avecc()recopie tout à chaque passage. nextsaute un passage,breaksort ; préfère les opérations vectorisées quand elles existent, mais n'aie pas peur d'une boucle lisible.
Prochaine étape : les boucles qui tournent jusqu'à ce que quelque chose arrive plutôt qu'une fois par élément - while et repeat.
Questions fréquentes
Comment écrire une boucle for en R ?
for (x in v) { ... } - la variable de boucle x prend tour à tour chaque élément du vecteur (ou de la liste) v, et le corps s'exécute une fois par élément. Pour parcourir les positions plutôt que les valeurs, utilise for (i in seq_along(v)) et indexe avec v[i].
Pourquoi utiliser seq_along plutôt que 1:length(v) en R ?
Quand v est vide, length(v) vaut 0, donc 1:length(v) devient 1:0 - le vecteur à deux éléments c(1, 0) - et le corps de la boucle s'exécute deux fois sur des éléments inexistants. seq_along(v) renvoie une séquence vide pour un vecteur vide, donc la boucle s'exécute correctement zéro fois.
Pourquoi ma boucle for en R n'affiche-t-elle rien ?
L'affichage automatique de R ne se produit que pour les expressions tapées au niveau supérieur de la console. Dans un corps de boucle, une expression nue comme x est évaluée puis jetée. Enveloppe-la dans print(x), ou utilise cat(...) quand tu veux formater la sortie toi-même.
Les boucles for sont-elles lentes en R ?
La boucle elle-même va très bien - ce qui est lent, c'est de faire grandir un vecteur à l'intérieur avec result <- c(result, ...), ce qui recopie tout le vecteur à chaque passage. Préalloue avec numeric(n) ou vector("list", n) et affecte par indice, et une boucle est parfaitement performante. Cela dit, quand une opération vectorisée ou un appel de la famille apply exprime la même idée, c'est en général à la fois plus rapide et plus clair.