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. 1
  2. 0
  3. 1
  4. 1
  5. 0
  6. 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
TRUE

Est-ce que le 6e élément est inférieur à 5?

vec[6]<5
FALSE
vec[5]>=5
TRUE

On peut extraire de ce vecteur les valeurs supérieures à 6 par exemple;

vec[vec>6]
  1. 7
  2. 8
  3. 9
  4. 10
vec[vec>=6]
  1. 6
  2. 7
  3. 8
  4. 9
  5. 10
vec[!(vec>=6)]
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5

Les valeurs plus petites ou égales à 3 ou plus grandes ou égales à 6

vec[(vec>=8)|(vec<=3)]
  1. 1
  2. 2
  3. 3
  4. 8
  5. 9
  6. 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
'jeune'
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
'trop_vieux_ou_trop_jeun'

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
'Québec'

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
'autre'

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
'Ontario'
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
'Québec'

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
  1. 'Québec'
  2. 'autre'
  3. '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
  1. 'Québec'
  2. 'autre'
  3. '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 que x == y

  • d’utiliser !all.equal(x,y) plutôt que x != y

Voici un exemple;

x<-.2-.1
y<-.3-.2
x==y
FALSE

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)
TRUE

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)
../../_images/6a4dbfdc7db6c5e5050bb78218e84e8c752cafcf6c80286696ddb1d6e0b62a37.png

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)
elapsed: 12.67

Donc la première façon de faire et 9.32 fois plus lente que la deuxième!