Dans ce TP, nous allons aborder la création de fonction en R.

Création d’une fonction

On utilise l’opérateur function() pour déclarer sa propre fonction. Celle-ci peut prendre autant de paramètres que nécessaire. Pour retourner une valeur, on utilise l’opérateur return(). Pour créer une procédure, il suffit de déclarer une fonction qui ne retourne aucune valeur.

Classique

Voici un exemple de déclaration d’une procédure, qui affiche un texte simple (grâce à la fonction cat()).

afficheBonjour <- function() {
    cat("Bonjour\n")
}
afficheBonjour()
Bonjour

Et voici l’exemple d’une fonction, renvoyant le carré de \(\pi\).

pi2 <- function() {
    res = pi ** 2
    return(res)
}
a = pi2()
cat("Pi au carré = ", a, "\n")
Pi au carré =  9.869604 

Paramètres

On peut donc passer un paramètre, en le nommant avec les mêmes contraintes que pour un nom de variable classique.

afficheBonjour <- function(prenom) {
    cat("Bonjour", prenom, "\n")
}
afficheBonjour("FX")
Bonjour FX 

Par contre, il est absolument nécessaire de donner une valeur à chaque paramètre de la fonction définie.

afficheBonjour()
Error in cat("Bonjour", prenom, "\n"): l'argument "prenom" est manquant, avec aucune valeur par défaut

De même que pour une procédure, on peut passer des paramètres à une fonction.

puissance <- function(a, b) {
    res = a ** b
    return(res)
}
a = puissance(2, 4)
cat("a =", a, "\n")
a = 16 

On remarque ici que la variable a à l’intérieur de la fonction est indépendante de la variable a précédemment déclarée. On dit que la portée d’une variable d’une fonction est limitée à celle-ci.

Dans la suite, les exemples seront soit une procédure, soit une fonction, car le fonctionnement est généralement le même.

Paramètres nommés

Quand on déclare une fonction à plusieurs paramètres, il est possible de préciser à quel paramètre on affecte chaque valeur passée. Dans la suite, nous déclarons une fonction à 2 paramètres (nom et prenom).

  • Dans le premier appel, on procéde classiquement, comme dans les autres langages de programmation classique.
  • Dans le deuxième appel, l’ordre est le même mais on précise le nom des paramètres. Ceci est souvent utilisé pour rendre un code lisible et compréhensible par d’autres personnes.
  • Dans le troisième appel, on se permet de modifier l’ordre des paramètres de la fonction. Ceci est possible uniquement parce qu’on les nomme dans l’appel.
afficheBonjour <- function(nom, prenom) {
    cat("Bonjour ", prenom, " ", nom, "\n")
}
afficheBonjour("Jollois", "FX") # ordre par défaut
Bonjour  FX   Jollois 
afficheBonjour(nom = "Jollois", prenom = "FX") # idem mais en précisant le nom des paramètres
Bonjour  FX   Jollois 
afficheBonjour(prenom = "FX", nom = "Jollois") # en modifiant l'ordre de passage des paramètres
Bonjour  FX   Jollois 

Valeurs par défaut pour les paramètres

On peut aussi définir des valeurs par défaut aux paramètres. Ces valeurs seront celles prises par le paramètre si, lors de l’appel, la valeur de celui-ci n’est pas défini. On reprend l’exemple suivant en fixant une valeur par défaut pour le prénom.

afficheBonjour <- function(nom, prenom = "??") {
    cat("Bonjour ", prenom, " ", nom, "\n")
}
afficheBonjour("Jollois", "FX") # rien de changé par raport à avant
Bonjour  FX   Jollois 
afficheBonjour("Jollois")
Bonjour  ??   Jollois 

Passage de paramètres à d’autres fonctions (...)

Lorsqu’on définit une fonction qui en utilise une autre, on peut permettre le passage de paramètres (nommés onligatoirement) à cette sous-fonction. Par exemple, nous créons une fonction qui va afficher la moyenne d’un vecteur. Mais si ce vecteur contient des NA, la fonction mean() renverra NA. Nous allons donc permettre de passer le paramètre na.rm dans la fonction mean().

afficheMoyenne <- function(x, ...) {
    m = mean(x, ...)
    cat("Moyenne =", m, "\n")
}
afficheMoyenne(c(3, 9, 1, 7, 12))
Moyenne = 6.4 
afficheMoyenne(c(3, 9, 1, NA, 12))
Moyenne = NA 
afficheMoyenne(c(3, 9, 1, NA, 12), na.rm = TRUE)
Moyenne = 6.25 

lapply et associés

Liste

On manipule en R des listes sans le savoir, avec les data.frames.

class(mtcars)
[1] "data.frame"
is.list(mtcars)
[1] TRUE

Mais on peut aussi bien évidemment les créer ex-nihilo. Celles-ci peuvent contenir des objets de tout type, même des sous-listes.

l = list(a = "chaîne", 
         b = 12, 
         c = 1:10, 
         d = head(mtcars), 
         e = list(x = 1:10, y = log(1:10)))
length(l)
[1] 5
names(l)
[1] "a" "b" "c" "d" "e"
l[1]
$a
[1] "chaîne"
l[[1]]
[1] "chaîne"
l$a
[1] "chaîne"
l[1:2]
$a
[1] "chaîne"

$b
[1] 12
l[c("a", "c")]
$a
[1] "chaîne"

$c
 [1]  1  2  3  4  5  6  7  8  9 10
l[sapply(l, length) == 1]
$a
[1] "chaîne"

$b
[1] 12

lapply et autres

La fonction lapply() permet d’exécuter une fonction (passée en deuxième paramètre) à chaque élément d’une liste (passée en premier paramètre), ou un vecteur. Il existe aussi la fonction sapply(), qui est similaire mais qui cherche à simplifier le résultat.

lapply(l, class)
$a
[1] "character"

$b
[1] "numeric"

$c
[1] "integer"

$d
[1] "data.frame"

$e
[1] "list"
sapply(l, class)
           a            b            c            d            e 
 "character"    "numeric"    "integer" "data.frame"       "list" 

Fonction particulière

On a parfois (voire souvent) besoin d’utiliser une fonction spécifique dans les fonctions comme lapply(). On peut soit la définir avant et l’utiliser comme une autre.

infoElement <- function(e) {
    return(c(classe = class(e), longueur = length(e)))
}
lapply(l, infoElement)
$a
     classe    longueur 
"character"         "1" 

$b
   classe  longueur 
"numeric"       "1" 

$c
   classe  longueur 
"integer"      "10" 

$d
      classe     longueur 
"data.frame"         "11" 

$e
  classe longueur 
  "list"      "2" 
sapply(l, infoElement)
         a           b         c         d            e     
classe   "character" "numeric" "integer" "data.frame" "list"
longueur "1"         "1"       "10"      "11"         "2"   

Fonction anonyme

Mais puisqu’on ne l’utilise généralement que dans cette fonction, il est possible de la déclarer directement dans la fonction lapply(). On parle alors de fonction anonyme (comme en JavaScript par exemple).

sapply(l, function(e) {
    return(c(classe = class(e), longueur = length(e)))
})
         a           b         c         d            e     
classe   "character" "numeric" "integer" "data.frame" "list"
longueur "1"         "1"       "10"      "11"         "2"   

Fonctionnement invisible

On a parfois besoin d’appliquer une fonction qui ne retourne rien à une liste, par exemple pour afficher l’élément ou une partie de celui-ci. Dans l’exemple ci-dessous, on remarque que le code affiche bien chaque élément, mais renvoie aussi une liste contenant les éléments (qui est donc identique à la liste passée en paramètre). Ce comportement est dû au fait que la fonction ne renvoie pas de résultat.

lapply(l, function (e) { print(e); })
[1] "chaîne"
[1] 12
 [1]  1  2  3  4  5  6  7  8  9 10
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
$x
 [1]  1  2  3  4  5  6  7  8  9 10

$y
 [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101
 [8] 2.0794415 2.1972246 2.3025851
$a
[1] "chaîne"

$b
[1] 12

$c
 [1]  1  2  3  4  5  6  7  8  9 10

$d
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

$e
$e$x
 [1]  1  2  3  4  5  6  7  8  9 10

$e$y
 [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101
 [8] 2.0794415 2.1972246 2.3025851

Dans ce type de cas, si on veut éviter ce comportement, on peut utiliser la fonction invisible(). Ceci va rendre invisible l’exécution du code et on ne verra donc pas la liste retournée par lapply().

invisible(lapply(l, function (e) { print(e); }))
[1] "chaîne"
[1] 12
 [1]  1  2  3  4  5  6  7  8  9 10
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
$x
 [1]  1  2  3  4  5  6  7  8  9 10

$y
 [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101
 [8] 2.0794415 2.1972246 2.3025851

Fonctions autres

Nous utilisons ici une liste de valeurs entre 0 et 9, définie ci-après.

liste = c(0, 5, 9, 1, 2, 4, 6, 7, 3)

Recherche

Il est possible de faire une recherche dans une liste (ou un vecteur) avec les fonctions Find() et Position(). Celles-ci renvoient le premier élément trouvé (ou le dernier car il est possible de partir de la droite). La fonction passée en premier paramètre doit renvoyer les valeurs TRUE ou FALSE.

On cherche par exemple ici le premier (ou dernier) élément strictement supérieur à 6 dans la liste précédemment créée.

Find(function(e) return(e > 6), liste)
[1] 9
Find(function(e) return(e > 6), liste, right = T)
[1] 7
Position(function(e) return(e > 6), liste)
[1] 3
Position(function(e) return(e > 6), liste, right = T)
[1] 8

Filtre

Pour récupérer tous les éléments d’une liste respectant une condition (grâce à la fonction passée en paramètre donc), on dispose de la fonction Filter(). Nous récupérons ici tous les éléments de la liste qui sont strictement supérieurs à 6.

Filter(function(e) return(e > 6), liste)
[1] 9 7

Réduction

On peut opérer une opération de réduction d’une liste à l’aide d’une fonction binaire (à deux paramètres donc).

Reduce(function(a, b) {
  cat("a =", a, "\t b =", b, "\t a + b =", a + b, "\n")
  return(a + b)
  }, liste)
a = 0    b = 5   a + b = 5 
a = 5    b = 9   a + b = 14 
a = 14   b = 1   a + b = 15 
a = 15   b = 2   a + b = 17 
a = 17   b = 4   a + b = 21 
a = 21   b = 6   a + b = 27 
a = 27   b = 7   a + b = 34 
a = 34   b = 3   a + b = 37 
[1] 37

Attention : Pour fonctionner correctement, la fonction doit retourner un objet utilisable dans la fonction.

Dans l’exemple ci-dessous, nous transformons mtcars en une liste de 32 éléments, chacune étant une liste nommée des caractéristiques de la voiture (avec en plus le nom de celle-ci).

mt = lapply(1:nrow(mtcars), function(i) {
        return(c(nom = rownames(mtcars)[i], as.list(mtcars[i,])))
    })
mt[[1]]
$nom
[1] "Mazda RX4"

$mpg
[1] 21

$cyl
[1] 6

$disp
[1] 160

$hp
[1] 110

$drat
[1] 3.9

$wt
[1] 2.62

$qsec
[1] 16.46

$vs
[1] 0

$am
[1] 1

$gear
[1] 4

$carb
[1] 4

Imaginons qu’on souhaite faire la somme des consommations, il nous faut créer une liste initiale avec la valeur 0 pour l’élément mpg. Ensuite, on additionne les deux valeurs qu’on stocke dans a (qui aura pour première valeur init) et on retourne celle-ci.

init = list(mpg = 0)
Reduce(function(a, b) { a$mpg = a$mpg + b$mpg; return(a)}, mt, init)
$mpg
[1] 642.9

Exercices

sur les fonctions

  1. Créer une fonction affichePays() qui prend en paramètre un nom de pays et un continent, et qui l’affiche sur une ligne :

    pays (continent)

  2. Ajouter un paramètre à cette fonction, qui sera l’année d’indépendance (avec la valeur NA par défaut) =
    • si l’année est NA, alors on affichera

    pays (continent)

    • sinon, on affichera :

    pays (continent - indépendance en XXXX)

  3. Créer une fonction avec les caractéristiques suivantes :
    • Paramètres :
      • une valeur (population d’un pays)
      • un vecteur de valeur (population de plusieurs villes)
    • Retour :
      • le ratio entre la somme des valeurs du vecteur (des populations des villes) et la première valeur (population d’un pays)
    • Par exemple :
      • f(10, c(1, 2, 3)) renverra .6

world

A partir des données présentes dans le fichier world-liste.RData, répondre aux questions suivantes. Ces données concernent les pays dans le monde (à la fin du siècle dernier), et sont représentées sous forme de liste dans l’objet Country.liste.

  1. Donner le nombre de pays représentés
  2. Calculer la population mondiale
  3. Utiliser la fonction affichePays() créée précédemment dans une fonction anonyme, pour afficher les informations des pays (un pays par ligne)
  4. Utiliser la fonction de calcul de ratio de la population pour savoir pour chaque pays le ratio entre la population des villes de celui-ci et la population du pays
    • Idéalement, créer une nouvelle liste qui ajoute cette information aux autres, pour chaque pays
  5. Identifier quels pays ont un ratio supérieur à 1 avec la nouvelle liste créée