Introduction à la programmation avec R
gilles.hunault "at" univ-angers.fr
Cours 9 - Différences entre programmation et développement
Table des matières cliquable
1. Programmation, choix, tests, déboggage et profilage
2. Programmation, spécifications et documentations
3. Mises à jour, maintenances, objets et packages
Exercices : énoncés solutions [Retour à la page principale du cours]1. Programmation, choix, tests, déboggage et profilage
Programmer, c'est écrire des programmes. Soit. Mais comment être sûr que les programmes sont justes ? Et que les autres sauront les utiliser ? Après réflexion, ce n'est pas si simple...
Prenons un exemple concret, celui de la conversion de et vers des pouces en cm : une fois que l'on sait qu'un pouce fait 2,54 cm, tout n'est pas dit. Ainsi, comment entrer les valeurs et l'unité ? Faut-il écrire une fonction qui pose une question, lit la réponse au clavier en session R, ou passer ces valeurs en paramètres, ou utiliser un formulaire Tcl/Tk, voire une interface Web ?
Admettons un instant que l'on s'en tienne au premier choix, à savoir la lecture au clavier -- ce qui sans doute pas une bonne solution, ce que l'on verra plus loin. Voici un exemple de programme. Voyez-vous l'erreur de programmation ?
########################################### # # conversion pouce/cm # # (avec une erreur facile à éviter) # ########################################### # on pose la question 1 et on lit la réponse valeur <- as.numeric(readline(prompt="quelle quantité voulez-vous convertir ? ")) # on pose la question 2 et on lit la réponse unite <- readline(prompt="en quelle unité ? ") # on convertit : si pouce on multiplie, sinon on divise facteurConv <- 2.54 if (unite=="pouces") { autreu <- "cm" ; } else { facteurConv <- 1/facteurConv ; autreu <- "pouces" ; } ; # fin si cat(valeur," ",unite," = ",(valeur*facteurConv)," ",autreu,".\n",sep="") ;Une lecture rapide du programme et le test avec un cas "idéal" comme ci-dessous :
quelle quantité voulez-vous convertir ? 10 en quelle unité ? pouces 10 pouces = 25.4 cm.ne permet pas de prouver que le programme est juste. Cela montre seulement que si on fournit les bons paramètres (en type et en valeur), le calcul est correct. Oui mais, si on saisit pouce au lieu de pouces, c'est-à-dire sans le s à la fin, que fait le programme ? Stricto sensu, pouce et pouces ne sont pas égaux donc le programme fait la conversion dans le mauvais sens.
Une version plus fiable serait donc
########################################### # # conversion pouce/cm # ########################################### # on pose la question 1 et on lit la réponse valeur <- as.numeric(readline(prompt="quelle quantité voulez-vous convertir ? ")) # on pose la question 2 et on lit la réponse unite <- readline(prompt="en quelle unité ? ") # on convertit : si pouce on multiplie, sinon on divise facteurConv <- 2.54 uniteOk <- 0 if (unite=="pouces") { autreu <- "cm" ; uniteOk <- 1 } ; if (unite=="cm") { facteurConv <- 1/facteurConv ; autreu <- "pouces" ; uniteOk <- 1 } ; # fin si if (uniteOk==0) { cat(" unité incorrecte. il faut écrire exactement \"pouces\" ou \"cm\" comme unité.\n") } else { cat(valeur," ",unite," = ",(valeur*facteurConv)," ",autreu,".\n",sep="") ; } # finsiMaintenant, pourquoi ne tester que l'unité ? Avec un utilisateur pressé, ou le clavier numérique désactivé, la saisie de la valeur peut être vide. Dans ce cas, la variable valeur contient NA car la fonction as.numeric() ne renvoie pas d'erreur.
Ecrire un programme juste, c'est à la fois penser aux erreurs possibles, les détecter et les gérer. On peut décider de boucler sur la saisie d'une valeur numérique ou quitter le programme dès la première saisie si la valeur n'est pas numérique... Programmer, c'est donc faire des choix.
Programmer, c'est aussi tester les divers cas possibles que l'on a recensé. C'est pourquoi la saisie au clavier est une mauvaise solution. Car il faudra taper les réponses à chaque fois que l'on veut vérifier que ce qu'on a écrit est correct. Une solution plus "propre" et qui correspond plus à l'esprit de R est de définir une fonction de conversion avec les deux paramètres :
convPouceCm <- function(valeur,unite) { ########################################### # # conversion pouce/cm # ########################################### if (missing(valeur) | missing(unite)) { cat("\n") cat(" syntaxe : convPouceCm(valeur,unite) avec unite = pouces ou cm\n") cat(" exemples : convPouceCm(10,\"pouces\")\n") cat(" convPouceCm(20,\"cm\")\n\n") stop() } ; # finsi # pouce on multiplie, sinon on divise facteurConv <- 2.54 uniteOk <- 0 if (unite=="pouces") { autreu <- "cm" ; uniteOk <- 1 } ; if (unite=="cm") { facteurConv <- 1/facteurConv ; autreu <- "pouces" ; uniteOk <- 1 } ; # fin si if (uniteOk==0) { cat(" unité incorrecte. il faut écrire exactement \"pouces\" ou \"cm\" comme unité.\n") } else { cat(valeur," ",unite," = ",(valeur*facteurConv)," ",autreu,".\n",sep="") ; } # finsi } # fin de fonction convPouceCmL'intérêt, c'est que maintenant on peut demander à R de tester les cas possibles juste en éxécutant le script suivant :
############################################## # # test de la fonction de conversion pouce/cm # nommée convPouceCm # ############################################# cat("# test 1 : rappel de la syntaxe si aucune valeur\n") try( convPouceCm() ) cat("# test 2 : calcul correct pour des pouces\n") convPouceCm(10,"pouces") cat("# test 3 : calcul correct pour des centimètres\n") convPouceCm(20,"cm") cat("# test 4 : comportement de la fonction si unité incorrecte...\n") convPouceCm(20,"pommes")Voici ce qu'on obtient alors :
# test 1 : rappel de la syntaxe si aucune valeur syntaxe : convPouceCm(valeur,unite) avec unite = pouces ou cm exemples : convPouceCm(10,"pouces") convPouceCm(20,"cm") Error in convPouceCm() : # test 2 : calcul correct pour des pouces 10 pouces = 25.4 cm. # test 3 : calcul correct pour des centimètres 20 cm = 7.874016 pouces. # test 4 : comportement de la fonction si unité incorrecte... unité incorrecte. il faut écrire exactement "pouces" ou "cm" comme unité.Si on veut vraiment saisir les informations au clavier, on écrira une autre fonction par exemple, saisieConversion() qui renvoie les deux valeurs. Du coup, on peut écrire convPouceCm( saisieConversion() ) pour exécuter les deux actions
# fonction de saisie pour conversion pouce/cm saisieConversion <- function() { # on pose la question 1 et on lit la réponse valeur <- as.numeric(readline(prompt="quelle quantité voulez-vous convertir ? ")) # on pose la question 2 et on lit la réponse unite <- readline(prompt="en quelle unité ? ") # on renvoie les deux valeurs return( c(valeur,unite)) } # fin de fonction saisieConversion ######################################################### convPouceCm <- function(valeur,unite) { ########################################### # # conversion pouce/cm # ########################################### if (length(valeur)==2) { # données issues de saisieConversion unite <- valeur[2] # dans cet ordre ! valeur <- as.numeric(valeur[1]) } # finsi if (missing(valeur) | missing(unite)) { cat("\n") cat(" syntaxe : convPouceCm(valeur,unite) avec unite = pouces ou cm\n") cat(" exemples : convPouceCm(10,\"pouces\")\n") cat(" convPouceCm(20,\"cm\")\n\n") stop() } ; # finsi # pouce on multiplie, sinon on divise facteurConv <- 2.54 uniteOk <- 0 if (unite=="pouces") { autreUnite <- "cm" ; uniteOk <- 1 } ; if (unite=="cm") { facteurConv <- 1/facteurConv ; autreUnite <- "pouces" ; uniteOk <- 1 } ; # fin si if (uniteOk==0) { cat(" unité incorrecte. il faut écrire exactement \"pouces\" ou \"cm\" comme unité.\n") } else { resultat <- valeur*facteurConv cat(valeur," ",unite," = ",resultat," ",autreUnite,".\n",sep="") ; } # finsi return(invisible(c(resultat,autreUnite))) } # fin de fonction convPouceCmAu passage, on pourrait améliorer la fonction convPouceCm() en prévoyant une unité par défaut, un affichage par défaut avec deux décimales, le choix de l'affichage ou non, le renvoi du résultat, ce qui permettrait de tester le programme avec lui-même :
> cnv <- convPouceCm( saisieConversion() ) quelle quantité voulez-vous convertir ? 10 en quelle unité ? pouces 10 pouces = 25.4 cm. > cnv <- convPouceCm( cnv ) 25.4 cm = 10 pouces. > cnv <- convPouceCm( cnv ) 10 pouces = 25.4 cm.Lorsque le programme écrit est d'importance, il est essentiel de pouvoir vérifier chaque calcul intermédiaire, de pouvoir suivre le détail à l'intérieur de chaque boucle, ou au contraire, de pouvoir tout exécuter jusqu'à une partie délicate. De même, si la vitesse d'exécution du programme est mauvaise, il faut pouvoir analyser le comportement du programme, savoir où R «perd du temps», ce qui le ralentit... R et Rstudio disposent de fonctions pour cela, heureusement. Voir l'exercice 1 de cette section pour plus de détails.
2. Programmation, spécifications et documentations
Ce que montre la section précédente, c'est qu'il faut bien détailler ce que l'on veut faire, bien préciser les entrées, les sorties. Autoriser p ou i ou d comme abbréviation [internationale] légale de pouces est sans doute une bonne idée car on dit inch en anglais et Daumen en allemand. Fournir le code-source de la conversion ou le rendre accessible sur Internet c'est offrir la possibilité aux autres de le lire et peut-être vous aider à l'améliorer (il vaut mieux alors mettre les commentaires en anglais, malheureusement).
Si on n'écrit pas une fonction mais un ensemble de fonctions, il est conseillé de fournir des exemples d'utilisation, un petit manuel pour rappeler les options, détailler le format des structures utilisées. Si cela met en jeu des fichiers de données, il est d'usage de fournir des exemples de tels fichiers, pour qu'une personne qui n'a pas écrit ces fonctions arrive à les utiliser et à comprendre comment elles fonctionnent juste à la lecture de l'aide.
Si c'est encore plus complexe, il faut carrément écrire un livre pour expliquer tout cela en détail, comme par exemple avec ces deux ouvrages :
Paradis Hahne 3. Mises à jour, maintenances, objets et packages
Il est fréquent qu'on ne pense pas tout de suite à tout, qu'on vienne ajouter des fonctionnalités au fur et à mesure de l'utilisation des programmes. C'est pourquoi il est important de bien gérer les numéros de version, de noter ce qui change (il faut parfois défaire ce qui a été fait et revenir à une version antérieure). Dans certains cas, notamment quand ce qu'on écrit dépend des packages de R, il faut parfois modifier le code par ce que R a changé ou par ce que le package a changé.
Les tests cités dans la section précédente doivent alors permettre de prouver que malgré les changements les programmes font comme avant et même plus qu'avant. La rétrocompatibilité et la fiabilité sont des concepts à retenir et à implémenter.
La maintenance du code peut parfois se limiter à la traduction des messages dans différentes langues, à ajouter des paramètres, mais elle peut parfois être beaucoup plus lourde, par exemple quand on vient factoriser du code, c'est-à-dire mettre des parties en commun via des sous-programmes qui couvrent plusieurs cas.
Ces quelques séances d'initiation à la programmation élementaire ne peuvent pas, bien sûr, présenter des concepts plus techniques comme les objets et l'écriture de packages. Nous renvoyons pour cela aux exercices de cettes section afin d'avoir une petite idée de ce que cela met en jeu.
4. Programmation et Interfaçage
La notion d'interface correspond à deux choses bien distinctes : les interfaces utilisateurs et les interfaces de programmation. Dans le premier cas, il s'agit de savoir comment l'utilisateur interagit avec les fonctions et les programmes (en ligne de commande, par script, par menus, par panneaux...). Dans le second cas, il s'agit de faire dialoguer entre eux les langages, d'appeler du C via R, par exemple, ou de demander à Python ou Ruby d'exécuter du R. On pourra consulter les liens ci-dessous pour voir des exemples possibles d'interface de programmation.
Au niveau des interfaces utilisateur, à part des interfaces générales avec menus comme Rkward et Rcmdr, R fournit un package nommé tcltk qui permet de développer des GUI et un package nommé shiny pour définir des interfaces web. Ainsi le code suivant
# saisie de valeur et unite en mode interface graphique library(tcltk2) tt <- tktoplevel() tktitle(tt) <- "Saisie des paramètres pour conversion" l1 <- tklabel(tt,text="quelle quantité voulez-vous convertir ?") v1 <- tclVar(10) e1 <- tkentry(tt,textvariable=v1) l2 <- tklabel(tt,text="en quelle unité ? ") v2 <- tclVar("pouces") e2 <- tkentry(tt,textvariable=v2) ok <- tkbutton(tt,text="OK",command = function() tkdestroy(tt)) tkpack(l1,e1,l2,e2,ok) tkwait.window(tt) cat("Valeurs saisies :\n") valeur <- as.numeric( tclvalue(v1) ) unite <- tclvalue(v2) cat(" valeur = ",valeur," et unité = ",unite,"\n") # appel de la fonction de conversion convPouceCm(valeur,unite)fournit une interface graphique minimale (et mal présentée) pour la saisie des deux paramètres dans la conversion pouces/cm...
Pour shiny, on écrit deux scripts : celui du serveur, nommé server.R et celui de l'interface, nommé ui.R. Voir par exemple la démonstration dans la gallerie de Shiny nommée Iris k-means clustering...
Voici le code pour notre conversion de pouces en centimètres :
library(shiny) ########################################################## shinyUI <- pageWithSidebar( ########################################################## headerPanel("Conversion de pouces en centimetres"), sidebarPanel( textInput(inputId = "pouces", label = "Nombre de pouces :", value = "") # valeur par défaut ), mainPanel( h3("Resultats :"), textOutput("sortie_texte") ) # fin de mainPanel ) # fin de pageWithSidebar ########################################################## shinyServer <- function(input, output) { ########################################################## output$sortie_texte <- renderText({ if (!nchar(input$pouces)==0) { cm <- 2.54*as.numeric(input$pouces) paste(input$pouces,'correspondent a',cm,"cm") } # fin si }) # fin de renderText } # fin de fonction ########################################################################################## shinyApp(ui = shinyUI, server = shinyServer)5. Tidyverse et conclusion
Les quelques heures passées à écrire des scripts R et à lire les corrigés des exercices vous auront sans doute convaincu(e) que :
programmer, c'est assez facile au début, pour des petits calculs ;
tout le monde peut programmer, mais bien programmer est un art ;
programmer demande de la rigueur, de la constance et de l'endurance ; en d'autres termes, programmer peut se révéler fastidieux et devenir intrinsèquement rébarbatif (pour ne pas dire ch*...) ;
programmer demande de la méthode, de la réflexion et du temps ;
programmer peut se révéler agréable car cela fait plaisir d'avoir réussi à créer quelque chose qui résoud un problème ou qui fait le travail en automatique à notre place.
R est à la fois un langage, un environnement, une communauté qui évoluent constamment. Ainsi tidyverse avec ses x %>% f %>% g ... essaie de mettre en place une approche fonctionnelle encore plus "propre" dans l'écriture du code R.
Alors, un seul mot : tous et toutes à vos papier et crayon avant de passer au clavier !
Et n'oubliez pas de visualiser vos données.
Exercices : énoncés solutions [Retour à la page principale du cours]
Retour à la page principale de (gH)