Valid XHTML     Valid CSS2    

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

  4. Programmation et Interfaçage

  5. Tidyverse et conclusion

 
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="") ;
     } # finsi
     
     

Maintenant, 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 convPouceCm
     

L'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 convPouceCm
     

Au 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 :

non su non su
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.

          Rcpp          R py2          R in Ruby

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...

          non su

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)
     

          non su

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.

         

    non su

    non su

         

    non su

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.

     

non su

non su

 
Exercices :           énoncés            solutions           [Retour à la page principale du cours]

 

 

retour gH    Retour à la page principale de   (gH)