Créer une fonction

Dans R :

Règle #1 : tout ce qui existe est un objet ;

Règle #2 : tout ce qui arrive est le résultat d’un appel à une fonction ;

Règle #3 : la fonction que vous cherchez existe déjà.

Par application de la règle #1, toute fonction est un objet ; en l’occurrence un objet de classe function :

> class(seq)
[1] "function"

Conformément à la règle #2, lorsque vous affichez quelque chose dans la console ou quand vous fermez Rgui, vous faîtes appel à des fonctions (print et q respectivement).

Par dérogation à la règle #3, nous allons maintenant créer une fonction qui n’existe pas dans R.

Ce dernier point mérite qu’on s’y attarde.

Il existe des milliers de fonctions dans R et c’est bien le diable si celle dont vous avez besoin n’existe pas déjà.

La bibliothèque de fonctions principale de R est le package base. Sur la page d’aide de seq, par exemple, la mention seq {base} en haut à gauche signifie que seq est une des fonctions de base.

Pour une liste complète des fonctions inclues dans base :

> library(help = "base")

Mais lorsque vous installez R, vous n’installez par que base. Vous installez aussi utils par exemple. Pour une liste complète des packages disponibles :

> library()

Pour charger le package boot en mémoire, utilisez :

> library(boot)

Ou :

> require(boot)

Pour en savoir plus et, notamment, pour découvrir les fonctions qui viennent avec les packages installés, utilisez :

> help.start()

Mais il est possible que ça ne vous suffise pas et que vous cherchiez quelque chose de très spécial.

Dans ce cas, rendez-vous sur le CRAN (sur www.r-project.org) et considérez les milliers de packages mis à votre disposition par d’autres utilisateurs.

Pour installer un nouveau package (le package twitteR par exemple) :

> install.packages("twitteR")

Mais en désespoir de cause, si vraiment vous ne trouvez pas fonction à votre pied (ou si vous avez la flemme de chercher), vous pouvez très facilement en créer une.

La forme générale d’une fonction sous R est :

fun <- function(argument1, argument2, ...) {
 ...
 return(res)
}

Par exemple, si vous souhaitez créer une fonction qui calcule l’aire d’un disque, vous pouvez exécuter ce qui suit :

rm(list = ls()) # Ménage !
aire <- function(rayon) {
 res <- pi * rayon^2
 return(res)
}

(Tout ce que vous écrirez devant un # sera considéré comme des commentaires et donc, ne sera pas exécuté par R.)

Dans la console :

> aire <- function(rayon) {
+ res <- pi * rayon^2
+ return(res)
+ }
>

Vous indique que la fonction a bien été chargée en mémoire.

D’ailleurs :

> ls()
[1] "aire"
> class(aire)
[1] "function"

Vous pouvez donc l’utiliser :

> aire(10)
[1] 314.1593

Si vous reprenez l’exercice que je vous avais proposé la dernière fois, nous avons vu que, pour reproduire ce que fait diff sur un vecteur x, nous pouvions écrire :

n <- length(x)
dx <- x[2:n] - x[1:(n-1)]

Vous pouvez utiliser ces deux lignes de code pour en faire une fonction :

mydiff <- function(x) {
 n <- length(x)
 res <- x[2:n] - x[1:(n-1)]
 return(res)
}

Qui nous donne :

> mydiff(rivers)
  [1]  -415     5    67   132   -74  1009 -1324   330   135  -270     6
 [12]   -56    35   555    36  -704   127   -39   710  -400   -95   945
 [23]  -610   403  -353  -540    57  -121    -6   245   195  -330  -140
 [34]    77   -97    35   585  -640   420  -370   (etc…)

Une chose que nous pourrions reprocher à diff, c’est de nous renvoyer un vecteur plus court que celui que nous lui donnons en argument. C’est logique puisque qu’on ne peut calculer que n-1 différences entre n éléments successifs mais nous pourrions, par exemple, aimer que le premier élément du résultat soit vide (un NA).

Avec :

mydiff <- function(x) {
 n <- length(x)
 res <- c(NA, x[2:n] - x[1:(n-1)])
 return(res)
}

On obtiendra :

> mydiff(rivers)
  [1]    NA  -415     5    67   132   -74  1009 -1324   330   135  -270
 [12]     6   -56    35   555    36  -704   127   -39   710  -400   -95
 [23]   945  -610   403  -353  -540    57  -121    -6   245   195  -330
 [34]  -140    77   -97    35   585  -640   420  -370   (etc…)

Puisque nous y sommes, pourquoi se limiter à la différence entre les éléments i et i+1 ? Nous pourrions tout à faire coder quelque chose de plus générique qui calcule les différences entre les éléments i et i+h :

mydiff <- function(x, h) {
 n <- length(x)
 res <- c(rep(NA, h), x[(h+1):n] - x[1:(n-h)])
 return(res)
}

Dans la console :

> mydiff(rivers, 2)
  [1]    NA    NA  -410    72   199    58   935  -315  -994   465  -135
 [12]  -264   -50   -21   590   591  -668  -577    88   671   310  -495
 [23]   850   335  -207    50  -893  -483   -64  -127   239   440  -135
 [34]  -470   -63   -20   -62   620   -55   (etc…)

Cela dit, la plupart du temps, nous utiliserons h = 1. pour gagner du temps, nous allons utiliser ça comme valeur par défaut de h :

mydiff <- function(x, h = 1) {
 n <- length(x)
 res <- c(rep(NA, h), x[(h+1):n] - x[1:(n-h)])
 return(res)
}

Un problème qui peut se poser à ce stade, c’est que quelqu’un utilise une valeur pour h qui ne soit pas comprise entre 1 et length(x) :

> mydiff(rivers, -1)
Erreur dans rep(NA, h) : argument 'times' incorrect

Prenons les devants et ajoutons un stop :

mydiff <- function(x, h = 1) {
 if(! h %in% 1:length(x)) stop("gruik !")
 n <- length(x)
 res <- c(rep(NA, h), x[(h+1):n] - x[1:(n-h)])
 return(res)
}

(Notez le ! ceci %in% cela qui se lit ceci n’est pas dans cela.)

De telle sorte que :

> mydiff(rivers, -1)
Erreur dans mydiff(rivers, -1) : gruik !

(Notez le message d'erreur parfaitement explicite.)

Enfin, diff présente l’avantage de travailler aussi sur les matrices (par colonnes) ; nous aimerions reproduire ça :

mydiff <- function(x, h = 1) {
 if(! h %in% 1:length(x)) stop("message d'erreur")
 if(is.matrix(x)) {
  res <- matrix(NA, nrow(x), ncol(x))
  for(i in 1:ncol(x)) {
   res[, i] <- mydiff(x[, i], h = h)
  }
 } else {
  n <- length(x)
  res <- c(rep(NA, h), x[(h+1):n] - x[1:(n-h)])
 }
 return(res)
}

Voilà l’idée :

Si x n’est pas une matrice (si c’est un vecteur), on applique mydiff normalement.

En revanche, si x est une matrice, on créé une matrice vide res de mêmes dimensions puis, avec une boucle for, on remplit res en rappelant mydiff sur chaque colonne (puisque chaque x[, i] est un vecteur sauf si x[, i, drop = FALSE] vous vous souvenez ?).

C’est ce qu’on appelle une fonction récursive : elle se rappelle elle-même.

> test <- matrix(rivers[1:15], 5, 3)
> mydiff(test, 2)
     [,1] [,2] [,3]
[1,]   NA   NA   NA
[2,]   NA   NA   NA
[3,] -410 -315  -50
[4,]   72 -994  -21
[5,]  199  465  590

Fort de ce qui précède, vous pouvez déjà faire pas mal de choses et nous allons donc en rester là pour le moment.

Avant de vous donner du travail, je voudrais néanmoins passer quelques instants sur un petit détail technique qui a son importance.

Pour bien voir, videz la mémoire et rechargez une version de mydiff (peu importe laquelle) :

rm(list = ls())
mydiff <- function(x, h = 1) {
 n <- length(x)
 res <- c(rep(NA, h), x[(h+1):n] - x[1:(n-h)])
 return(res)
}

Et exécutez :

> test <- rivers[1:15]
> mydiff(test, 2)

Et maintenant, inspectons notre espace de travail :

> ls()
[1] "mydiff" "test"

Rien ne vous choque ?

Rien ne vous étonne ?

Où sont n et res ?

(Neo, le retour.)

Lorsque je vous ai présenté cette notion d’espace de travail, je l'ai fait par analogie avec un bureau sur lequel on dépose des objets. Ici, nous avons bien deux objets posés sur notre bureau : mydiff et test mais, bizarrement, pas n et res.

La raison en est que quand R exécute un appel à une fonction — ici mydiff — il créé une boîte sur notre bureau dans laquelle il stocke tous les objets qui sont créés à l’intérieur de votre fonction puis, à la fin de l’exécution, il détruit la boîte (et ce qu'il y a dedans). Cette boîte s’appelle un environnement. D'ailleurs votre bureau (a.k.a. espace de travail) est aussi un environnement qui s'appelle .GlobalEnv.

Sans rentrer dans les détails de ces choses un brin complexes, je vais juste vous montrer un rapide exemple :

rm(list = ls())
x <- 1:5

Créons une boîte :

> boite <- new.env()
> class(boite)
[1] "environment"
> ls()
[1] "boite" "x"

Dans notre boîte, rangeons un vecteur y :

boite$y <- 5:1

À ce stade, sur notre bureau, on a toujours :

> ls()
[1] "boite" "x"

Mais, à l’intérieur de la boîte :

> ls(boite)
[1] "y"
> boite$y
[1] 5 4 3 2 1

Supprimons la boîte (et son contenu avec) :

> rm(list = "e")
> ls()
[1] "x"

Voilà. En gros, c’est pour ça que res et n ont disparu…

N’ayez aucune inquiétude : l’exercice qui vient ne vous demandera pas d’avoir compris ce qui suit la photo de Neo.

Aucun commentaire:

Enregistrer un commentaire