Valid XHTML     Valid CSS2    

Introduction à la programmation avec R

                gilles.hunault "at" univ-angers.fr

Cours 5 - Sous-programmes : les fonctions

 

Table des matières cliquable

  1. Définition des fonctions nommées et anonymes

  2. Définition et utilisation des paramètres

  3. Tests et utilisation des paramètres

  4. L'ellipse notée ...

  5. Quelques exemples de fonctions

  6. Spécificités du langage R

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

1. Définition des fonctions nommées et anonymes

Pour définir une fonction nommée, il faut réaliser une affectation avec comme partie gauche le nom de la fonction à créer, puis utiliser le mot function, mettre des parenthèses, ajouter des paramètres s'il y en a besoin avec éventuellement des valeurs par défaut, puis écrire le corps de la fonction entre accolades et mettre return avec des parenthèses pour indiquer l'objet renvoyé, qui peut être une simple valeur ou quelque chose de plus complexe. Par exemple, pour inventer une fonction qui calcule le carré d'une valeur, on devrait écrire


     carre <- function(x) { return(x*x) }     
          

Il est possible de raccourcir le corps de la fonction en omettant les accolades et en mettant comme dernière (ou comme seule) instruction le calcul à renvoyer. Ainsi, on pourrait écrire


     carre <- function(x) x*x     
          

Une écriture aussi concise est utile dans le cas d'une fonction intégrée à un appel de fonction ou pour une fonction anonyme (concept détaillé un peu plus bas dans cette section), mais cette écriture est dangereuse et «fainéante». Il vaut mieux lui préférer le code suivant :


     carre <- function(x) {     
          
      # calcule le carré de son argument     
          
      y <- x*x     
      return(y)     
          
     } # fin de fonction carre     
          

Nous reviendrons sur ces écritures, mais un grand principe de la programmation robuste est qu'il faut préférer la lisibilité, la facilité de relecture et la maintenance du code à la facilité d'écriture. Il faut viser l'écriture de programmes robustes et lisibles et non pas de programmes vite tapés sur le clavier.

Pour se servir de cette fonction carre() il suffit de l'appeler là où on utiliserait le carré des valeurs. Cela peut donc être dans le cadre d'un calcul simple ou d'un affichage interactif (en console), dans le cadre d'une instruction d'affectation etc. Il n'est pas obligatoire ici de nommer le paramètre au niveau de l'appel, mais il vaudra mieux le faire quand il y en aura plus d'un. On peut aussi utiliser juste le nom de la fonction en tant que paramètre quand cela est permis. Le calcul effectué par notre fonction étant "vectoriel", c'est-à-dire applicable à une structure, notre fonction est elle-même vectorielle. Voici des exemples d'utilisation :


     ## définition de la fonction     
          
      carre <- function(x) {     
          
      # calcule le carré de son argument     
          
      y <- x*x     
          
      return(y)     
          
     } # fin de fonction carre     
          
     # ------------------------------------------------------     
          
     # calcul interactif     
          
      carre(5)  # suffisant, mais carre(x=5) est OK aussi     
          
      # affichage en interactif     
          
      cat("le carré de 8 est ",sprintf("%05d",carre(x=8)),"\n")  # noter %05d au lieu de %5d     
          
      # à l'intérieur d'une affectation     
          
      lesCarresPlusUn <- 1 + carre(1:10)     
          
      # en tant que paramètre     
          
      lapply(X=list(a=1,b=3,c=5),FUN=carre)     
          

Et leurs résultats :


     [1] 25     
          
     le carré de 8 est  00064     
          
     # non affiché : [1]   2   5  10  17  26  37  50  65  82 101     
          
     $a     
     [1] 1     
          
     $b     
     [1] 9     
          
     $c     
     [1] 25     
          
          

Imaginons maintenant que nous voulions écrire une fonction pvalt() qui doit renvoyer la p-value du test t de Student via l'appel de la fonction t.test(). Il faut commencer par comprendre l'objet renvoyé par la fonction t.test(), puis trouver comment extraire la composante voulue, pour ensuite écrire la fonction et enfin vérifier qu'elle renvoie bien ce qu'on voulait :


      # toutes les données iris     
          
      data(iris) # chargement "fainéant" (lazy loading) du data.frame iris     
          
      # juste les deux premières colonnes et les dix premières lignes     
          
      lesd <- iris[ (1:10) , (1:2) ] # plus "propre" que iris[1:10,1:2]     
          
      # effectuons le test et essayons de comprendre où est mémorisée     
      # la p-value     
          
      tt <- t.test(lesd)     
          
      print(tt)  # après lecture, la p-value est donc  8.008e-15     
          
      # quelques essais pour comprendre ce que renvoie la fonction t.test()     
          
      print(class(tt))     
      print(is.list(tt))     
      print(names(tt))     
      print(tt$p.value) # c'est bien cela     
          
      # définissons rapidement la fonction demandée     
          
      pvalt <- function(test) { return( test$p.value ) }     
          
      # vérifions que c'est bien ce qu'il faut :     
          
      identical( tt$p.value, pvalt(tt) ) # doit renvoyer TRUE     
          

          
     >  # toutes les données iris     
     >     
     >  data(iris) # chargement "fainéant" (lazy loading) du data.frame iris     
          
     >  # juste les deux premières colonnes et les dix premières lignes     
     >     
     >  lesd <- iris[ (1:10) , (1:2) ] # plus "propre" que iris[1:10,1:2]     
          
     >  # effectuons le test et essayons de comprendre où est mémorisée     
     >  # la p-value     
     >     
     >  tt <- t.test(lesd)     
          
     >  print(tt)  # la p-value est donc  8.008e-15     
          
     	One Sample t-test     
          
     data:  lesd     
     t = 21.5729, df = 19, p-value = 8.008e-15     
     alternative hypothesis: true mean is not equal to 0     
     95 percent confidence interval:     
      3.688668 4.481332     
     sample estimates:     
     mean of x     
         4.085     
          
          
     >  print(class(tt))     
     [1] "htest"     
          
     >  print(is.list(tt))     
     [1] TRUE     
          
     >  print(names(tt))     
     [1] "statistic"   "parameter"   "p.value"     "conf.int"    "estimate"    "null.value"  "alternative" "method"      "data.name"     
          
     >  print(tt$p.value) # c'est bien cela     
     [1] 8.007948e-15     
          
     >  # définissons la fonction demandée     
     >     
     >  pvalt <- function(test) { return( test$p.value ) }     
          
     >  # vérifions que c'est bien ce qu'il faut :     
     >     
     >  identical( tt$p.value, pvalt(tt) ) # doit renvoyer TRUE     
     [1] TRUE     
          

Une fonction anonyme correspond à la seule partie droite de définition de la fonction. Comme on l'utilise sans affectation, elle n'a pas de nom. Une telle fonction s'utilise généralement comme paramètre dans un appel de fonction. Ainsi, pour réaliser le calcul de carrés dans une liste, on peut écrire, sans passer par une fonction carre() :


      lapply(X=list(a=1,b=3,c=5),FUN=function(x) x*x)     
          

Si on se rend compte qu'on utilise plusieurs fois la même fonction anonyme, il est conseillé d'en faire une fonction nommée. Cela gagne du temps et de la maintenance de code.

2. Définition et utilisation des paramètres

Pour définir les paramètres d'une fonction, on doit impérativement les nommer. Par contre, il est facultatif de leur donner une valeur par défaut avec =. Mettre l'ellipse dans la définition des paramètres signifie que la fonction peut accepter (et transmettre) d'autres paramètres que ceux explicitement fournis. Voir la section 4 pour le détail sur l'ellipse.

Utiliser les paramètres dans un ordre ou dans un autre peut être plus ou moins facile à mémoriser ou à comprendre. On pourra s'en rendre compte si on compare les choix de l'ordre des paramètres pour le calcul de la somme des colonnes d'une matrice via la fonction apply() dans les codes R suivants :


     ## calculs des sommes par colonne avec la fonction apply
     
     # pas besoin de nommer les paramètres, il s'agit de l'ordre
     # utilisé dans la définition de la fonction apply :
     
       apply(m,2,sum)
     
     # mieux, utilisation du nom des paramètres :
     
       apply(X=m,MARGIN=2,FUN=sum)
     
     # acceptable et reconnu par R :
     
       apply(X=m,M=2,F=sum)
     
     # sans doute la meilleure solution car elle suit
     # l'ordre somme colonnes matrice :
     
       apply(FUN=sum,MARGIN=2,X=m)
     

3. Tests et utilisation des paramètres

On peut tester si un paramètre est présent (ou plutôt absent) avec missing(paramètre). La liste des paramètres est fournie par formals(fonction) mais on peut aussi utiliser args(fonction) pour connaitre l'entête de la fonction ; le texte de la fonction est donné par body(fonction). Voici quelques exemples qui montrent que R est un langage assez ouvert puisqu'on peut consulter le code source du langage :


     > class(mean)
       "function"
     
     > args(mean)
       function (x, ...)
       NULL
     
     > formals(mean)
       $x
     
       $...
     
     > args(var)
       function (x, y = NULL, na.rm = FALSE, use)
       NULL
     
     > args(plot)
       function (x, y, ...)
       NULL
     
     > args(legend)
       function (x, y = NULL, legend, fill = NULL, col = par("col"),
           border = "black", lty, lwd, pch, angle = 45, density = NULL,
           bty = "o", bg = par("bg"), box.lwd = par("lwd"), box.lty = par("lty"),
           box.col = par("fg"), pt.bg = NA, cex = 1, pt.cex = cex, pt.lwd = lwd,
           xjust = 0, yjust = 1, x.intersp = 1, y.intersp = 1, adj = c(0,
               0.5), text.width = NULL, text.col = par("col"), text.font = NULL,
           merge = do.lines && has.pch, trace = FALSE, plot = TRUE,
           ncol = 1, horiz = FALSE, title = NULL, inset = 0, xpd, title.col = text.col,
           title.adj = 0.5, seg.len = 2)
       NULL
     
     

Il est classique de tester le type (la classe des paramètres) avec une fonction is.* et de quitter le programme avec stop ou stopifnot. Voici là encore quelques exemples issus du propre code de R :


     > colMeans
     
     function (x, na.rm = FALSE, dims = 1L)
     {
         if (is.data.frame(x))
             x <- as.matrix(x)
         if (!is.array(x) || length(dn <- dim(x)) < 2L)
             stop("'x' must be an array of at least two dimensions")
         if (dims < 1L || dims > length(dn) - 1L)
             stop("invalid 'dims'")
         n <- prod(dn[1L:dims])
         dn <- dn[-(1L:dims)]
         z <- if (is.complex(x))
             .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) *
                 .Internal(colMeans(Im(x), n, prod(dn), na.rm))
         else .Internal(colMeans(x, n, prod(dn), na.rm))
         if (length(dn) > 1L) {
             dim(z) <- dn
             dimnames(z) <- dimnames(x)[-(1L:dims)]
         }
         else names(z) <- dimnames(x)[[dims + 1]]
         z
     }
     <bytecode: 0x3809668>
     <environment: namespace:base>
     
     > var
       function (x, y = NULL, na.rm = FALSE, use)
       {
           if (missing(use))
               use <- if (na.rm)
                   "na.or.complete"
               else "everything"
           na.method <- pmatch(use, c("all.obs", "complete.obs", "pairwise.complete.obs",
               "everything", "na.or.complete"))
           if (is.na(na.method))
               stop("invalid 'use' argument")
           if (is.data.frame(x))
               x <- as.matrix(x)
           else stopifnot(is.atomic(x))
           if (is.data.frame(y))
               y <- as.matrix(y)
           else stopifnot(is.atomic(y))
           .Call(C_cov, x, y, na.method, FALSE)
       }
       <bytecode: 0x46f7258>
       <environment: namespace:stats>
     

On peut utiliser les paramètres dans l'ordre de leur définition sans les nommer, ou les utiliser dans un autre ordre, à condition de les nommer. R accepte des abbréviations non ambigues des noms de paramètre :


     # 1. définition de f
     
          f <- function(xLong=-1,xEncorePlusLong) {
     
            if (missing(xEncorePlusLong)) {
              cat("\nCalcul impossible : il faut donner deux paramètres, \n")
              cat(" à savoir xLong et xEncorePlusLong.\n")
            } else {
              cat("la somme de ",xLong," et de ", xEncorePlusLong," est ",xLong+xEncorePlusLong,"\n")
            } # fin si
     
          } # fin de fonction f
     
     # 2. utilisations de f
     
          formals(f)                   # liste des paramètres
     
          args(f)                      # entête
     
          f()                          # sans paramètre
     
          f(xLong=2,xEncorePlusLong=3) # utilisation stricte des paramètres (nommés)
     
          f(xEncorePlusLong=3,xLong=2) # utilisation des paramètres nommés (sans ordre)
     
          f(2,3)                       # utilisation par position
     
          f(xE=3)                      # utilisation d'abbréviation de la valeur par défaut
     
          f(2,xE=3)                    # utilisation d'abbréviation non ambigue
     
          f(2,x=3)                     # utilisation ambigue non autorisée
     

     
     > # 1. définition de f
     
          > f <- function(xLong=-1,xEncorePlusLong) {
     
                 if (missing(xEncorePlusLong)) {
                   cat("\nCalcul impossible : il faut donner deux paramètres, \n")
                   cat(" à savoir xLong et xEncorePlusLong.\n")
                 } else {
                   cat("la somme de ",xLong," et de ", xEncorePlusLong," est ",xLong+xEncorePlusLong,"\n")
                 } # fin si
     
          } # fin de fonction f
     
     > # 2. utilisations de f
     
          > formals(f)                   # liste des paramètres
          $xLong
          -1
     
          $xEncorePlusLong
     
     
     
          > args(f)                      # entête
          function (xLong = -1, xEncorePlusLong)
          NULL
     
          > f()                          # sans paramètre
     
          Calcul impossible : il faut donner deux paramètres,
           à savoir xLong et xEncorePlusLong.
     
          > f(xLong=2,xEncorePlusLong=3) # utilisation stricte des paramètres (nommés)
          la somme de  2  et de  3  est  5
     
          > f(xEncorePlusLong=3,xLong=2) # utilisation des paramètres nommés (sans ordre)
          la somme de  2  et de  3  est  5
     
          > f(2,3)                       # utilisation par position
          la somme de  2  et de  3  est  5
     
          > f(xE=3)                      # utilisation d'abbréviation de la valeur par défaut
          la somme de  -1  et de  3  est  2
     
          > f(2,xE=3)                    # utilisation d'abbréviation non ambigue
          la somme de  2  et de  3  est  5
     
          > f(2,x=3)                     # utilisation ambigue non autorisée
          Erreur dans f(2, x = 3) :
            l'argument 2 correspond à plusieurs arguments formels
     

4. L'ellipse notée ...

Comme le montrent les exemples précédents, un argument possible pour une fonction est l'ellipse, qui se note ... (trois points qui se suivent) et dont l'aide est ici. Cette notation, qui se révèle très pratique à l'usage, permet de ne pas spécifier tous les paramètres à utiliser et de laisser le soin à R de les transmettre. L'ellipse sert beaucoup lorsqu'on écrit une fonction de tracé graphique et pour implémenter des fonctions génériques. Les paramètres qu'on définit sont les plus usuels et l'ellipse permet de passer les moins fréquents.

Imaginons par exemple que l'on veuille interfacer la fonction boxplot() avec un titre et une unité obligatoires. Voici la trame d'une telle fonction :


     boxplot_UN <- function(titre,x,unite) {
     
        if (missing(titre)) {
          cat(" syntaxe : boxplot_UN(titre,x,unite) \n")
          cat(" titre et unite sont obligatoires.\n\n")
          return(invisible(NULL))
        } # fin si
     
        if (missing(unite)) {
           stop("vous devez donner l'unité utilisée.\n")
        } # fin si
     
        # suite du code de boxplot_UN
     
     } # fin de fonction boxplot_UN
     
     ###############################################################
     
     > boxplot_UN()
       syntaxe : boxplot_UN(titre,x,unite)
       titre et unite sont obligatoires.
     
     > boxplot_UN("oui",1:10)
       Erreur dans boxplot_UN("oui", 1:10) : vous devez donner l'unité utilisée.
     
     > boxplot_UN("oui",1:10,"kilos")
       # OK...
     

Il est clair qu'avec une telle fonction on ne peut pas modifier les bornes xlim et ylim du tracé, par exemple. Au lieu de les rajouter comme arguments, le mieux est d'ajouter l'ellipse. Dès lors, l'utilisateur a accès à toutes les options de boxplot :


     boxplot_DEUX <- function(titre,x,unite,...) {
     
        if (missing(titre)) {
          cat(" syntaxe : boxplot_DEUX(titre,x,unite,...) \n")
          cat(" titre et unite sont obligatoires.\n\n")
          return(invisible(NULL))
        } # fin si
     
        if (missing(unite)) {
           stop("vous devez donner l'unité utilisée.\n")
        } # fin si
     
        # suite du code de boxplot_DEUX
     
     } # fin de fonction boxplot_DEUX
     
     ###############################################################
     
     > boxplot_UN("oui",1:10,"k",ylim=c(0,10))
       Erreur dans boxplot_UN("oui", 1:10, "k", ylim = c(0, 10)) :
       argument inutilisé (ylim = c(0, 10))
     
     > boxplot_DEUX("oui",1:10,"k",ylim=c(0,10))
       # OK...
     

5. Quelques exemples de fonctions

Afin de vous entrainer à bien "parler fonction en R", voici quatres exemples qui mettent en pratique tout ce que nous venons de décrire. Nous montrerons au passage comment "progresser" dans l'écriture d'une fonction.

5.1 La fonction moyenne()

Pour implémenter une fonction nommée moyenne() -- aussi bizarre que cela puisse paraitre à une ou un néophyte, cette définition est plus rapide que la "vraie" fonction mean() de R pour des petits vecteurs numériques -- le code minimal est le suivant :


     moyenne <- function(x) sum(x)/length(x)
     

Cet exemple de définition est suffisant dans l'environnement de R, en mode interactif, si on est pressé de tester la fonction.

Toutefois, il vaut mieux mettre un commentaire pour détailler ce que fait la fonction et utiliser explicitement le mot return() pour indiquer ce qui est renvoyé :


     # la fonction moyenne, version 2, plus explicite
     
     moyenne <- function(x) {
     
      # calcul de la moyenne arithmétique
     
      return( sum(x)/length(x) )
     
     } # fin de fonction moyenne
     

Après réflexion, vous pourrez vous rendre compte que l'argument de moyenne() doit être numérique, car sinon la fonction sum() échoue. De même, s'il y a des NA dans les valeurs, la fonction sum() échoue aussi. D'où une version 3, plus stable et plus complète pour la moyenne, avec au passage une séparation du calcul du renvoi de la valeur calculée :


     # la fonction moyenne, version 3 et complète
     
     moyenne <- function(x,oteNA=FALSE) {
     
      # calcul de la moyenne arithmétique
      # après suppression éventuelle des valeurs NA
     
      stopifnot(is.numeric(x))
     
      if (oteNA) { x <- na.omit(x) }
     
      # calcul
     
      valMoy <- sum(x)/length(x)
     
      # renvoi
     
      return( valMoy )
     
     } # fin de fonction moyenne
     

Vous aurez compris, au vu de cette progression, qu'écrire une fonction ne se fait pas forcément du premier jet. Le nom et le choix de la valeur par défaut du paramètre oteNA pour supprimer les valeurs NA devrait aussi être détaillé. Nous avons ici pris le parti de reproduire la valeur par défaut de la fonction mean(), mais sans utiliser le même nom de paramètre que R utilise, à savoir na.omit, ce qui est un petit peu maladroit (volontairement).

5.2 La fonction médiane()

Forts de notre expérience avec la fonction moyenne() prévédente, nous pourrions être tentés d'écrire de suite un "beau" code "stable et complet" pour la fonction mediane() comme ceci :


     # la fonction mediane, version 1
     
     mediane <- function(x,na.rm=FALSE) {
     
      # calcul de la médiane
      # après suppression éventuelle des valeurs NA
     
      # vérifions que x est numérique
     
      stopifnot(is.numeric(x))
     
      # supprimons les valeurs NA si demandé
     
      if (na.rm) { x <- na.omit(x) }
     
      # tri des valeurs
     
      x <- sort(x)
     
      # avec un nombre impair de termes, la médiane est
      # au milieu de la liste des valeurs, sinon c'est
      # la moyenne arithmétique des deux valeurs au centre
      # de la liste
     
      lng  <- length(x)
      lng2 <- lng/2
      if ((lng %% 2)==1) { # cas impair
         return( x[ 1 + round(lng2) ] )
      } else {
         return( mean(x[ c(lng2,lng2+1) ]) )
      } # fin si
     
     } # fin de fonction mediane
     

C'est sans doute une "belle" fonction, mais elle est incomplète, parce que la médiane peut se calculer sur des vecteurs de caractères avec un nombre pair de termes.

On remarquera que nous avons désormais écrit pour le paramètre na.rm au lieu de oteNA comme pour la fonction moyenne() précédente. C'est sans doute mieux, car cela aide à se rappeler comment on écrit cela dans les autres fonctions standards de R.

Programmer, c'est aussi évoluer, changer d'avis, à condition de le dire et de rester cohérent avec les autres fonctions de R !

5.3 La fonction decritColonne()

R fournit de nombreuses fonctions pour décrire une variable quantitative comme le poids, ou la taille. Il y a les fonctions élémentaires comme mean(), sd() et des fonctions plus complètes comme summary() ou fivenum().

Toutes ces fonctions ont un gros défaut : elles n'indiquent pas l'unité. Ainsi voir une taille de 5,6 pour des adultes peut apparaitre comme une erreur pour un français. Avec l'indication 5,6 pieds, ce serait mieux car vu qu'un pied fait 30,47 centimètres la valeur 5,6 pieds correspond approximativement à 170,6 cm, ce qui montre bien qu'il ne s'agit pas d'une erreur.

Nous nous proposons ici d'ajouter une unité à la description statistique classique et de considérer ce paramètre comme obligatoire.

Nous allons nous focaliser sur les descripteurs de tendance centrale que sont la moyenne et la médiane, et sur les valeurs de controle -- il faudrait bien sûr ajouter des indicateurs de dispersion absolue et relative, comme l'écart-type, la distance interquartile, le coefficient de variation... pour être plus complet -- que sont le minimum et le maximum afin de rester dans des calculs simples et compréhensibles.

Nous ne fournissons pas non plus de code pour représenter graphiquement les données et ces indicateurs mais ce serait indispensable s'il s'agissait d'écrire une "vraie" fonction statistique de description d'une variable quantitative dont on connait l'unité.

Comme programmer, c'est se rappeler des programmes déjà écrits, nous refuserons bien évidemment d'appliquer la fonction à des données qui ne sont pas numériques.

Voici un code possible pour cette fonction :


     # une description quantitative avec unité obligatoire
     # après suppression éventuelle automatique des valeurs NA
     
     decritColonne <- function(x,unite="") {
     
      stopifnot(is.numeric(x))
     
      if (unite=="") {
        stop(" le paramètre unite est obligatoire.\n")
      } # fin si
     
      nbNA  <- sum(is.na(x)) # gestion des NA
      nbOrg <- length(x)
      x     <- na.omit(x)
      nbVal <- length(x)
     
      # préparation des colonnes pour la mise en forme
     
      nomCalcs <- c("nbOrg","nbNA","nbVal","min","moyenne","médiane","max")
      nbCalc   <- length(nomCalcs)
      unites   <- c( rep("valeurs",3), rep(unite,nbCalc-3) )
     
      # calculs
     
      calcs <- c( nbOrg, nbNA, nbVal, min(x),mean(x),median(x),max(x) )
      resDf <- data.frame( round(calcs),unites,row.names=nomCalcs)
      names(resDf)[1] <- "resultats"
     
      # affichage
     
      print(resDf,quote=FALSE)
     
      # renvoi invisible (mais "caisse queue sait" ?)
     
      return( invisible(resDf) )
     
     } # fin de fonction decritColonne
     

Le choix du nom des variables et des fonctions est important. Ici, decritColonne() est sans doute maladroit et decritQuantitative() aurait été plus approprié. Toutefois, il est possible que l'utilisation principale de cette fonction soit d'être appelée par une fonction plus générale, nommée par exemple decritColonnes() -- donc avec un s en plus à la fin -- et là, le nom de la fonction se justifie tout à fait.

Voici un exemple d'utilisation :


     > data(iris) ; decritColonne(iris$Sepal.Length,"cm")
     
             resultats  unites
     nbOrg         150 valeurs
     nbNA            0 valeurs
     nbVal         150 valeurs
     min             4      cm
     moyenne         6      cm
     médiane         6      cm
     max             8      cm
     

5.4 La fonction plotCouleur()

Il est très courant d'avoir à coloriser des points dans un tracé, par exemple pour repérer les hommes et les femmes dans une étude épidémiologique. Nous présentons ici une fonction dont la syntaxe est plotCouleur(x,y,facteur,couleurs). Ainsi, pour tracer le poids en fonction de l'age avec la couleur bleue pour les garçons et rouge pour les filles, on réaliserait l'appel plotCouleur(age,poids,sexe,c("blue","red")) ou, si astucieusement on dispose d'une liste de couleurs par défaut, l'appel peut se résumer à plotCouleur(age,poids,sexe). Voici la fonction :


     plotCouleur <- function(x,y,facteur,couleurs,...) {
     
       if (missing(couleurs)) {
         couleurs <- c("blue","red","green","black","yellow")
       } # finsi
     
       # conversion silencieuse en facteur
       # (permet d'utiliser une liste de nombres comme facteur)
     
       if (!is.factor(facteur)) {
         facteur <- factor(facteur)
       } # fin si
     
       # vérification du nombre de couleurs
     
       nbMod <- nlevels(facteur)
       if (nbMod>length(couleurs)) {
         stop(paste("vous devez fournir",nbMod,"couleurs pour le tracé.\n"))
       } # finsi
     
       # couleurs à utiliser
     
       couls <- couleurs[ as.numeric(facteur) ]
     
       # tracé
     
       plot(x,y,col=couls,pch=19,...)
     
     } # fin de fonction plotCouleur
     

Vous aurez remarqué, bien sûr, que nous avons utilisé l'ellipse. Attention toutefois : R n'autorise pas de spécifier plusieurs fois le même paramètre. Ainsi, puisque nous utilisons déjà le paramètre pch, l'appel suivant est incorrect


     > plotCouleur(age,taille,sexe,c("green","pink"),pch=17)
       Error in localWindow(xlim, ylim, log, asp, ...) :
       argument formel "pch" correspondant à plusieurs arguments fournis
     

6. Spécificités du langage R

R est très différent des autres langages par rapport aux fonctions :

  • les fonctions sont nativement vectorielles si les calculs utilisés sont vectoriels, ou si on y fait appel à d'autres fonctions vectorielles.

  • les fonctions sont des objets comme les autres et peuvent être passés en paramètres :

    
         ## utilisation de fonctions en paramètres
         
         f <- function(x) { return(paste("calcul de f(",x,")\n")) }
         g <- function(x) { return(paste("calcul de g(",x,")\n")) }
         
         condition <- TRUE # affichera : calcul de f( 3 )
         
         ifelse( condition, f, g ) ( 3 )
         
    
  • les paramètres sont nommés et ils peuvent avoir des valeurs par défaut, ce qui fournit une très grande souplesse d'utilisation (et un casse-tête pour les mémoriser).

Par contre ce nommage n'est pas normalisé et ne peut pas l'être, compte tenu des structures de données de R :


     > args(apply)
       function (X, MARGIN, FUN, ...)
       NULL
     
     > args(tapply)
       function (X, INDEX, FUN = NULL, ..., simplify = TRUE)
       NULL
     
     > args(mapply)
       function (FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE)
       NULL
     
     > args(vapply)
       function (X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE)
       NULL
     
     > args(lapply)
       function (X, FUN, ...)
       NULL
     
     > args(rapply)
       function (object, f, classes = "ANY", deflt = NULL, how = c("unlist","replace", "list"), ...)
       NULL
     
     

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

 

 

retour gH    Retour à la page principale de   (gH)