Restart_6: Programmation

Lise Vaudor

21/09/2021

Structures conditionnelles

Les instructions conditionnelles (if ou if else) permettent d’exécuter (ou non) certaines commandes en fonction de conditions spécifiées par l’utilisateur.

Voici la structure d’une instruction conditionnelle if:

if(condition){
  ...
  ...
  ...
}

Et if else:

if(condition){
  ...
}else{
  ...
}

Exemples:

Temperature <- 21
if(Temperature<15){
  print("il fait frisquet aujourd'hui!")
}

Il ne se passe rien, car la condition n’étant pas remplie la commande print(... n’a pas été exécutée

Temperature <- 12
if(Temperature<15){
  print("il fait frisquet aujourd'hui!")
}
[1] "il fait frisquet aujourd'hui!"

Ici au contraire, la condition (Temperature <15) était remplie et la commande print(... a bien été exécutée

Boucles for

Les boucles for permettent d’exécuter des instructions de manière itérative (ou répétée).

Voici la structure d’une instruction instruction for:

for(compteur in sequence){
  ...
  ...
  ...
}

Par exemple:

for(i in 1:5){
  print(paste("On en est à",i,"!"))
}
[1] "On en est à 1 !"
[1] "On en est à 2 !"
[1] "On en est à 3 !"
[1] "On en est à 4 !"
[1] "On en est à 5 !"
vars=c("Ozone","Solar.R","Temp")
for(i in 1:3){
  p <- ggplot(air, aes_string(x=vars[i]))+
    geom_histogram(fill="skyblue")
  plot(p)
}

Fonctions: Ecrire ses propres fonctions

L’utilisateur peut créer lui-même ses fonctions, par exemple s’il pense répéter plusieurs fois un même type de traitement.

Une fonction s’écrit de la manière suivante:

mafonction <-function(argument1,argument2){
  ...
  ...
  resultat <- ...
  return(resultat)
}

Par exemple, la fonction

Tconversion  <-function(x){
    reponse=(x-32)/1.8
    return(reponse)
}

convertit les températures en degrés Fahrenheit, en températures en degrés Celsius. On peut par exemple la tester de cette manière:

Tconversion(451)
[1] 232.7778

Quelques exemples en plus de fonctions, structure if et for…

Tconversion <- function(x, type="FtoC"){
  if(type=="FtoC"){
    resultat <- (x-32)/1.8
  }
  if(type=="CtoF"){
    resultat <- 1.8*x+32
  }
  if(type!="FtoC" & type!="CtoF"){
    resultat <- "Pas compris!"
  }
  return(resultat)
}

Testons cette fonction:

Tconversion(451)
[1] 232.7778
Tconversion(451, type="FtoC")
[1] 232.7778
Tconversion(232, type="CtoF")
[1] 449.6
Tconversion(232, type="kekek!")
[1] "Pas compris!"

Fonctions: inputs, output, side effects

Voilà comment on peut voir une fonction…

Ici je considère une fonction .f() qui a un input principal x et des inputs secondaires (...).

Classiquement, on va produire l’output en appelant la fonction avec pour arguments l’input x et les inputs secondaires: output=.f(x,...).

Par exemple:

x=c(33,NA,2,15,7,4,5)
moyenne=mean(x,na.rm=TRUE)
Ici, j’ai produit l’output moyenne en appelant la fonction mean(), avec pour argument principal x et pour argument secondaire na.rm=TRUE.

Itération et outputs: map()

Imaginons maintenant que je souhaite appeler la fonction mean() de manière répétée sur plusieurs éléments d’une liste:

myX=list(c(1,6),
         c(33,NA,2,15,7,4,5),
         c(3))

Je pourrais choisir de le faire via une boucle for:

moyennes=vector("list",length=3)
for (i in 1:length(myX)){
  moyennes[i]=mean(myX[[i]],na.rm=TRUE)
}
print(moyennes)
[[1]]
[1] 3.5

[[2]]
[1] 11

[[3]]
[1] 3

OU BIEN je peux choisir de le faire avec la fonction map() du package purrr

library(purrr)
moyennes=map(myX,mean, na.rm=TRUE)
print(moyennes)
[[1]]
[1] 3.5

[[2]]
[1] 11

[[3]]
[1] 3

Itération et outputs: map()

Ainsi, en utilisant map(), j’ai en quelque sorte transformé ma petite fonction/usine mean() en lui adjoignant une ‘rampe d’approvisionnement’:

Mon argument principal, x, devient ainsi une liste d’éléments utilisés comme input pour la fonction mean(). Mon argument secondaire, na.rm=TRUE, est en revanche le même pour toutes les itérations.

L’output moyennes est par défaut également une liste.

Itération et outputs: map()

Notez que l’on aurait pu ici demander explicitement à ce que le résultat nous soit renvoyé non pas comme une liste, mais comme un vecteur de valeurs numériques de type “double”:

moyennes=map_dbl(myX,mean,na.rm=TRUE)
print(moyennes)
[1]  3.5 11.0  3.0

Selon le type d’output renvoyé par la fonction, il peut ainsi être assez pratique d’utiliser les fonctions

  • map_dbl() (double)
  • map_lgl (logique)
  • map_int() (entier)
  • etc.

Itération et effets: walk()

Considérons maintenant les effets secondaires, en prenant pour exemple une fonction dont l’utilité première n’est pas de renvoyer un output, mais plutôt d’afficher quelque chose dans la console:

print_moyenne=function(x){
  print(paste("la moyenne est de",
              mean(x,na.rm=TRUE)))
  return(NULL)
}

Reprenons notre exemple myX:

myX=list(c(1,6),
         c(33,NA,2,15,7,4,5),
         c(3))

La fonction “walk()” permet d’itérer les “effets secondaires” d’une fonction… ici 3 messages/nuages de fumée différents:

walk(myX,print_moyenne)
[1] "la moyenne est de 3.5"
[1] "la moyenne est de 11"
[1] "la moyenne est de 3"

2 arguments principaux: map2()

Considérons maintenant une fonction à laquelle on voudrait adjoindre “deux rampes d’approvisionnement”. On va prendre pour exemple la fonction cor() qui calcule un coefficient de corrélation linéaire entre deux vecteurs:

cor(c(1,5,6,9),c(0.3,0.8,0.9,1.2))
[1] 0.9976344
myX=list(c(2,5,6,7,1,0,1,1),
       c(5,1,6,NA,2),
       c(2,5,8,6))
myY=list(c(5,8,9,7,22,1,9,9),
       c(2,8,9,5,4),
       c(8,9,8,7))

On veut itérer la fonction cor() en considérant chaque élément de x ET de y (le i-ième élément de x correspondant au i-ième élément de y…). On peut faire cela en considérant la fonction map2().

map2(myX,myY,cor)
[[1]]
[1] -0.07264618

[[2]]
[1] NA

[[3]]
[1] -0.1632993

p arguments principaux: pmap()

Enfin, on peut généraliser ce principe à \(p>2\) arguments principaux:

Dans ce cas, les p listes d’arguments sont fournis comme une liste, i.e. on passe à pmap() un argument .l qui est une liste de p éléments qui sont eux-mêmes des listes

Echapper aux erreurs: safely()

Jusqu’ici, tout va bien: j’ai choisi pour commencer des exemples d’application simples, où tout se déroule comme sur des roulettes.

Mais avec les fonctions de purrr comme avec une boucle for, il est particulièrement problématique qu’une des itérations génère une erreur, car même si cette erreur ne concerne qu’un élément parmi peut-être beaucoup d’autres, son occurrence stoppe l’exécution de toutes les itérations.

Considérons ainsi l’exemple suivant:

myX=list(c(2,5,6,7,1,0,1,1),
         c())
myY=list(c(5,8,9,7,22,1,9,9),
         c())
map2(myX,myY,cor)
Error in .f(.x[[i]], .y[[i]], ...): supply both 'x' and 'y' or a matrix-like 'x'

Aïe… La fonction cor() n’accepte pas que ses arguments x ou y soient vides et génère ainsi une erreur sur la deuxième itération. On n’obtient donc de résultat ni pour cette itération, ni pour les autres…

Echapper aux erreurs: safely()

Pour remédier à cela, outre modifier la fonction cor() pour qu’elle puisse s’adapter à ce cas particulier, il est possible d’utiliser la fonction safely():

map2(myX,myY,safely(cor))
[[1]]
[[1]]$result
[1] -0.07264618

[[1]]$error
NULL


[[2]]
[[2]]$result
NULL

[[2]]$error
<simpleError in .f(...): supply both 'x' and 'y' or a matrix-like 'x'>

Dans ce cas, pour chaque élément d’input, on obtient un élément d’output qui contient deux choses:

  • un élément result (qui correspond au résultat souhaité et est dans ce cas vide pour le troisième élément, problématique)
  • un élément error (qui correspond à l’éventuel message d’erreur généré et est dans ce cas vide pour les deux premiers éléments, qui ne génèrent pas d’erreur)

Manipulation des listes

Reprenons ainsi le résultat généré précédemment:

mycors=map2(myX,myY,safely(cor))

On peut récupérer un élément de cette liste de la manière suivante (attention, des crochets simples récupéreraient une liste de longueur 1 contenant l’objet issu de cor(), et non l’objet issu de cor() directement):

mycors[[1]]
$result
[1] -0.07264618

$error
NULL

On peut ensuite accéder aux éléments de mycors[[1]] soit en utilisant leur nom (ici result ou error) soit en utilisant leur index (ici 1 ou 2)

mycors[[1]]$result # idem: mycors[[1]][1]
[1] -0.07264618

… et si, pour chacun des 3 éléments de mycors, je veux récupérer l’élément result, eh bien, là encore, la fonction map() peut me servir:

map(mycors,"result")
[[1]]
[1] -0.07264618

[[2]]
NULL

Attention, notez bien la différence de syntaxe:

map(x,"pouetpouet")

cherche à extraire un élément appelé “pouetpouet” des différents éléments de la liste x.

En revanche

map(x,pouetpouet)

cherche à appliquer une fonction appelée pouetpouet aux différents éléments de la liste x.

On récapitule!

On a parlé de quoi, déjà?
  • Structures conditionnelles (if)
  • Boucles for
  • Ecriture de fonctions
  • Itération avec purrr
    • itération par map(), map2(), map_dbl(), etc.
    • pas d’arrêt lié aux erreurs: safely()
    • extraction d’éléments de listes via leur position ou leur nom