Structure de contrôle#
Dans cette section du cours, nous verrons deux sortes de structures de contrôle. Nous verrons comment contrôler notre code avec des conditions et des boucles.
Instruction de condition#
On se rappelle que les valeurs booléennes prennent les valeurs TRUE
ou FALSE
. Numériquement, ces mêmes valeurs prennent les valeurs 0
ou 1
. Créons d’abord un vecteur appelé vec_boo
vec_boo<-c(T,F,T,T,F,T)
Convertissons ce vecteur en valeurs numériques
as.numeric(vec_boo)
- 1
- 0
- 1
- 1
- 0
- 1
On peut faire des tests logiques afin de comparer deux ou plusieurs valeurs.
Pour ce faire, cérons un vecteur vec
vec<-1:10
Regardons si le premier élément est inférieur à 5 par exemple;
vec[1]<5
Est-ce que le 6e élément est inférieur à 5?
vec[6]<5
vec[5]>=5
On peut extraire de ce vecteur les valeurs supérieures à 6 par exemple;
vec[vec>6]
- 7
- 8
- 9
- 10
vec[vec>=6]
- 6
- 7
- 8
- 9
- 10
vec[!(vec>=6)]
- 1
- 2
- 3
- 4
- 5
Les valeurs plus petites ou égales à 3 ou
plus grandes ou égales à 6
vec[(vec>=8)|(vec<=3)]
- 1
- 2
- 3
- 8
- 9
- 10
Mais pourquoi on fait ça? On vient de voir qu’il n’est pas toujours nécessaire d’utiliser des if
. Je tiens à vous montrer qu’il est préférable d’éviter les if
lorsque possible, cela améliore grandement le temps d’exécution.
Toutefois, dans d’autres situations, par exemple lorsqu’on veut afficher un caractère quelconque lorsqu’une condition donnée est respectée. Regardons un exemple;
if(vec[1]>3){print("cet élément est plus grand que 3")} else {print("cet élément n'est pas plus grand que 3")}
[1] "cet élément n'est pas plus grand que 3"
Exemple d’un if
impriqué
age<-30
if(age>18 && age<=35){cat_age<-"jeune"}else {cat_age<-"trop_vieux_ou_trop_jeun"}
cat_age
age<-18
if(age>18 && age<=35){cat_age<-"jeune"}else if (age>35 && age<=55){cat_age<-"jeune"} else {cat_age<-"trop_vieux_ou_trop_jeun"}
cat_age
Regardons une version vectorisée de ces conditions
Ville_1<-"Montréal"
if(Ville_1=='Montréal'){
Province_1<-"Québec"
} else {
Province_1<-"autre"
}
Province_1
Maintenant si l’on assigne Toronto à Ville_1, on obtient;
Ville_1<-"Toronto"
if(Ville_1=='Montréal'){
Province_1<-"Québec"
} else {
Province_1<-"autre"
}
Province_1
Ajoutons une autre condition;
Ville_1<-"Toronto"
if(Ville_1=='Montréal'){
Province_1<-"Québec"
} else if (Ville_1=='Toronto'){
Province_1<-"Ontario" }else {Province_1<-"autre"}
Province_1
Ville_1<-"Montréal"
if(Ville_1=='Montréal'){
Province_1<-"Québec"
} else if (Ville_1=='Toronto'){
Province_1<-"Ontario" }else {Province_1<-"autre"}
Province_1
ifelse#
Il est très conseillé de toujours vectoriser lorsqu’on travaille avec R
.
Ville<-c("Montréal","Vancover","Toronto")
province<-ifelse(Ville=="Toronto","Ontario",ifelse(Ville=="Montréal","Québec","autre"))
province
- 'Québec'
- 'autre'
- 'Ontario'
L’expression ifelse
est la suivante: ifelse(<condition>,<expressionSiVrai>,<expressionSiFaux>)
Ville<-c("Montréal","Laval","Brossard")
province<-ifelse(Ville=="Toronto","Ontario",ifelse(Ville=="Montréal","Québec","autre"))
province
- 'Québec'
- 'autre'
- 'autre'
all.equal()#
Lors de l’utilisation de l’instruction if()
, il est préférable a:
d’utiliser
all.equal(x,y)
plutôt quex == y
d’utiliser
!all.equal(x,y)
plutôt quex != y
Voici un exemple;
x<-.2-.1
y<-.3-.2
x==y
Pourtant on sait que c’est vrai! La fonction all.equal
permet de régler ce problème, car elle contient un paramètre de tolérance de précision.
all.equal(x,y)
Instruction de boucle#
Une boucle est une structure de contrôle qui permet d’exécuter une commande quelconque plusieurs fois de suite. R
possède trois instructions de boucle; for
, while
et repeat
.
for loop#
La boucle de type for
permet de faire une itération à la fois et exécuter une portion du code à chacune des itérations. La syntaxe de cette instruction est la suivante:
for (i in vecteur) {instruction}
Par exemple;
for (i in 1:5){
print(paste("le chiffre:", i, "dans la boucle"))
}
[1] "le chiffre: 1 dans la boucle"
[1] "le chiffre: 2 dans la boucle"
[1] "le chiffre: 3 dans la boucle"
[1] "le chiffre: 4 dans la boucle"
[1] "le chiffre: 5 dans la boucle"
À chacune des itérations, on peut insérer un nombre illimité de codes qu’on voudrait exécuter (plusieurs lignes de codes et non qu’une seule ligne). Par exemple, à chaque itération on calcule le carré de l’élément d’un vecteur.
for (i in 1:5){
i_carr<-i**2
print(paste("le chiffre:", i, "dans la boucle et son carré est:", i_carr))
}
[1] "le chiffre: 1 dans la boucle et son carré est: 1"
[1] "le chiffre: 2 dans la boucle et son carré est: 4"
[1] "le chiffre: 3 dans la boucle et son carré est: 9"
[1] "le chiffre: 4 dans la boucle et son carré est: 16"
[1] "le chiffre: 5 dans la boucle et son carré est: 25"
On peut aussi inclure des conditions if
for (i in 1:5){
i_carr<-i**2
print(paste("le chiffre:", i, "dans la boucle et son carré est:", i_carr))
if (i==5)
{print("fin de la boucle")}
}
[1] "le chiffre: 1 dans la boucle et son carré est: 1"
[1] "le chiffre: 2 dans la boucle et son carré est: 4"
[1] "le chiffre: 3 dans la boucle et son carré est: 9"
[1] "le chiffre: 4 dans la boucle et son carré est: 16"
[1] "le chiffre: 5 dans la boucle et son carré est: 25"
[1] "fin de la boucle"
next#
L’instruction next
permet d’amener le curseur d’exécution du programme au départ de la boucle. On donne alors l’ordre au programme de sauter cette étape et de passer à la suivante. Dans l’exemple suivant, on ordonne au programme de passer à l’étape suivante si la valeur de l’élément courant dans la boucle est égale à 2.
for (i in 1:5){
if (i==2){next}
print(paste("le chiffre:", i, "dans la boucle"))
}
[1] "le chiffre: 1 dans la boucle"
[1] "le chiffre: 3 dans la boucle"
[1] "le chiffre: 4 dans la boucle"
[1] "le chiffre: 5 dans la boucle"
break#
L’instruction break
permet de sortir immédiatement de la boucle si la condition est respectée. Dans l’exemple suivant, on demande de sortir de la boucle si la valeur de l’élément courant est égale à 4.
for (i in 1:5){
if (i==4){break}
print(paste("le chiffre:", i, "dans la boucle"))
}
[1] "le chiffre: 1 dans la boucle"
[1] "le chiffre: 2 dans la boucle"
[1] "le chiffre: 3 dans la boucle"
while loop#
Dans ce type de boucle, le code continue de s’exécuter tant que la condition donnée est vraie. L’exécution du code s’arrête seulement si la condition devient fausse.
i=0
while (i<5){
i<-i+1
print(paste("le chiffre:", i, "dans la boucle"))
}
[1] "le chiffre: 1 dans la boucle"
[1] "le chiffre: 2 dans la boucle"
[1] "le chiffre: 3 dans la boucle"
[1] "le chiffre: 4 dans la boucle"
[1] "le chiffre: 5 dans la boucle"
Dans ce genre de boucle, il arrive que le code continue de s’exécuter jusqu’à ce qu’on force l’arrêt. Par exemple le code suivant:
# i=1
# while (i<5){
# print(paste(i, Sys.time()))
# }
On aurait pu éviter cette boucle infinie en ajoutant l’instruction break
. Par exemple, sortir de la boucle s’il s’écoule 3 secondes entre le passage sur le premier élément et l’élément courant.
# i=1
# t_0<-Sys.time()
# while (i<2){
# if((Sys.time()-t_0)>3){break}
# print(paste(i, Sys.time()))
# }
repeat#
La boucle repeat
permet de répéter une instruction jusqu’à ce qu’un arrêt (break
) est donné
n=1
repeat{
n<-n+1
print(mean(rnorm(100)))
if (n>=7) {break}
}
[1] -0.04360991
[1] -0.07170522
[1] -0.08003594
[1] 0.1722577
[1] 0.04813118
[1] 0.1500783
append#
Nous avons appris à faire des boucles, mais comment peut-on garder en mémoire ou dans un objet l’information obtenue de chaque itération? Par exemple, à chaque itération, nous désirons faire un calcul et conserver le résultat. Regardons un exemple ou l’on voudrait calculer la moyenne de 100 variables aléatoires générées selon une distribution normale centrée réduite. On voudrait alors conserver le résultat (la moyenne calculée dans ce cas) qui a été calculé à chaque itération.
Nous créons un objet vide au départ, qu’on appelle moyennes
(au pluriel), et dans lequel nous insérons la moyenne calculée à chaque itération.
moyennes<-NULL
for (i in 1:1000){
mu<-mean(rnorm(100))
moyennes<-rbind(moyennes, mu)
}
plot(moyennes)
Toutefois, cette manière n’est pas la plus optimale en termes de temps de calcul. pour le prouver, utilisons la fonction system.time()
afin de mesurer le temps d’exécution de la boucle précédente, mais cette fois sur 20000 valeurs.
moyennes<-NULL
a<-system.time(
for (i in 1:20000){
mu<-mean(rnorm(100))
moyennes<-rbind(moyennes, mu)
}
)
a
user system elapsed
1.106 0.197 1.305
Dimensionner pour accélérer#
Au lieu de créer un objet NULL
de taille et de type indéfini et d’y insérer le calcul via rbind
, on peut créer un vecteur vide de même taille que le nombre de valeurs du résultat produit.
moyennes<-numeric(length = 20000)
b<-system.time(
for (i in 1:20000){
mu<-mean(rnorm(100))
moyennes[i] <-mu
}
)
b
user system elapsed
0.097 0.006 0.103
Ce qu’on vient de faire est génial! au lieu d’ajouter le résultat calculé à l’objet vide à chaque itération, nous assignons plutôt le résultat à l’index courant du vecteur vide.
Comparons les temps écoulés entre les deux manières de faire:
round(a[3]/b[3],2)
Donc la première façon de faire et 9.32 fois plus lente que la deuxième!