Fonctions R 2021
Fonctions R 2021
2021-03-24
2 Arguments en entrée 5
2.1 Valeurs par défaut des arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.1 Arguments acceptant seulement quelques chaînes de caractères spécifiques . . . . . . . 8
2.2 Appel d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Argument ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.1 Utilité 1 : recevoir en entrée un nombre indéterminé de valeurs à traiter . . . . . . . . 10
2.3.2 Utilité 2 : passer des arguments à une autre fonction . . . . . . . . . . . . . . . . . . . 12
6 Programmation fonctionnelle 23
7 Résumé 25
Références 27
Note préliminaire : Lors de leur dernière mise à jour, ces notes ont été révisées en utilisant R version 4.0.3.
Lorsqu’un bout de code R est susceptible d’être utilisé à répétition (p. ex. pour faire un même calcul sur des
données différentes), il est préférable d’en faire une fonction R. Les fonctions permettent de :
• rédiger du code plus clair et plus court, donc plus facile à comprendre et à partager ;
• diminuer les risques de faire des erreurs ;
1
• sauver du temps à long terme.
Bref, faire des fonctions est une bonne pratique de programmation en R.
Comme il a déjà été mentionné dans les notes sur les concepts de base en R, une fonction R est un bout de
code qui produit un certain résultat, lorsqu’exécuté. Pour exécuter le code composant une fonction, celle-ci
doit être appelée. Lorsqu’elle est appelée, la fonction prend des valeurs en entrée, qui sont assignées à des
arguments. Le code de la fonction réfère à ces arguments de façon à ce que l’appel de la fonction provoque un
traitement des valeurs fournies en entrée. En fin de compte, la fonction génère un résultat, qui est la plupart
du temps retourné dans un objet en sortie. Ce résultat peut aussi être un effet de bord (p. ex. la production
d’un graphique, l’écriture dans un fichier externe, etc.). Voici une représentation schématique de l’exécution
d’un appel à une fonction R
Dans ce gabarit de code, les éléments encadrés des signes < et > sont des bouts à remplacer par ce qui
convient pour la tâche à accomplir. Les éléments <arg_1>, <arg_2> et <arg_3> représentent les arguments
de la fonction (aussi appelés paramètres dans d’autres langages informatiques), soit les objets qui peuvent
être fournis en entrée à la fonction (qui ne sont pas nécessairement au nombre de trois).
Voici une fonction qui reprend un exemple présenté dans les notes sur les structures de contrôle en R. Elle
calcule des statistiques descriptives simples selon le type des éléments du vecteur donné en entrée.
stats_desc <- function(x) {
if ([Link](x)) {
stats <- c(min = min(x), moy = mean(x), max = max(x))
} else if ([Link](x) || [Link](x)) {
stats <- table(x)
} else {
stats <- NA
}
return(stats)
}
Après avoir soumis le code de création de cette fonction dans la console, la fonction se retrouve dans
l’environnement de travail. Il est alors possible de l’appeler.
stats_desc(x = iris$Species)
## x
## setosa versicolor virginica
## 50 50 50
Nous pourrions ajouter un argument à cette fonction. Par exemple, nous pourrions offrir l’option d’une sortie
présentée sous la forme d’une matrice plutôt que d’un vecteur.
2
stats_desc <- function(x, sortie_matrice) {
# Calcul
if ([Link](x)) {
stats <- c(min = min(x), moy = mean(x), max = max(x))
} else if ([Link](x) || [Link](x)) {
stats <- table(x, dnn = NULL)
} else {
stats <- NA
}
# Production de la sortie
if (sortie_matrice) {
stats <- [Link](stats)
colnames(stats) <- if ([Link](x) || [Link](x)) "frequence" else "stat"
}
return(stats)
}
L’argument dnn = NULL a aussi été ajouté dans l’appel à la fonction table afin de retirer le nom de la
dimension (ici x) dans la sortie produite par la fonction. Nous pouvons maintenant appeler la fonction comme
suit.
stats_desc(x = iris$Species, sortie_matrice = TRUE)
## frequence
## setosa 50
## versicolor 50
## virginica 50
## $x
##
##
## $sortie_matrice
• le corps de la fonction, soit les instructions qui la constituent.
body(stats_desc)
## {
## if ([Link](x)) {
## stats <- c(min = min(x), moy = mean(x), max = max(x))
## }
## else if ([Link](x) || [Link](x)) {
## stats <- table(x, dnn = NULL)
## }
3
## else {
## stats <- NA
## }
## if (sortie_matrice) {
## stats <- [Link](stats)
## colnames(stats) <- if ([Link](x) || [Link](x))
## "frequence"
## else "stat"
## }
## return(stats)
## }
• l’environnement englobant de la fonction (défini plus loin).
environment(stats_desc)
## <environment: 0x0000017c65a6bed8>
4
FUN = function(x) c(min = min(x), moy = mean(x), max = max(x))
)
2 Arguments en entrée
Les arguments d’une fonction sont définis en énumérant leurs noms entre les parenthèses après le mot-clé
function.
<nom_fonction> <- function(<arg_1>, <arg_2>, <arg_3>) {
<instructions> # formant le corps de la fonction
}
Il n’y a pas de restrictions quant au nombre d’arguments que peut posséder une fonction. Exceptionnellement,
une fonction peut même ne posséder aucun argument. C’est le cas par exemple de la fonction getwd et de la
fonction suivante.
HelloWorld <- function() cat("Hello World !")
Comme nous le savons déjà, pour appeler une fonction sans fournir d’arguments, il faut tout de même utiliser
les parenthèses.
HelloWorld()
## Hello World !
Omettre les parenthèses retourne le code source de la fonction.
HelloWorld
5
stats <- c(min = min(x), moy = mean(x), max = max(x))
} else if ([Link](x) || [Link](x)) {
stats <- table(x, dnn = NULL)
} else {
stats <- NA
}
# Production de la sortie
if (sortie_matrice) {
stats <- [Link](stats)
colnames(stats) <- if ([Link](x) || [Link](x)) "frequence" else "stat"
}
return(stats)
}
## $x
##
##
## $sortie_matrice
## [1] FALSE
Les arguments qui ne possèdent pas de valeur par défaut sont obligatoires. Si une fonction est appelée sans
donner de valeur en entrée à un argument obligatoire, une erreur est produite.
stats_desc(sortie_matrice = FALSE)
## [1] 4 1 3 2 5
Allons voir le corps de la fonction pour comprendre ce qui se passe.
6
body(sample)
## {
## if (length(x) == 1L && [Link](x) && [Link](x) && x >=
## 1) {
## if (missing(size))
## size <- x
## [Link](x, size, replace, prob)
## }
## else {
## if (missing(size))
## size <- length(x)
## x[[Link](length(x), size, replace, prob)]
## }
## }
La fonction sample fait appel à la fonction missing pour tester si une valeur a été fournie en entrée à size.
Si ce n’est pas le cas, une valeur est assignée à size.
Cette façon de faire n’est pas idéale. Il est plus clair pour l’utilisateur de voir les valeurs par défaut dans la
liste des arguments. Le comportement par défaut de la fonction sample si l’argument size n’est pas fourni
est tout de même expliqué dans la section intitulée « Details » de la fiche d’aide de la fonction sample).
## function (n, size = n, replace = FALSE, prob = NULL, useHash = (!replace &&
## [Link](prob) && size <= n/2 && n > 1e+07))
## NULL
La valeur par défaut de l’argument size est n, qui est le nom du premier argument de la fonction. Ainsi, par
défaut, l’argument size de [Link] prend la même valeur que ce qui a été fourni à l’argument n.
Nous pouvons également constater que la valeur par défaut de l’argument useHash est (!replace &&
[Link](prob) && size <= n/2 && n > 1e+07)), soit une expression faisant intervenir les valeurs prises
par tous les autres arguments de la fonction.
Ainsi, la fonction sample aurait pu être programmée différemment. La liste de ses arguments aurait pu
contenir la valeur par défaut suivante pour l’argument size : if (length(x) == 1L && [Link](x) &&
[Link](x) && x >= 1) x else length(x). Peut-être que les auteurs de la fonction ont trouvé cette
expression trop lourde pour la mettre dans la liste des arguments.
7
Si un utilisateur appelle la fonction cut sans fournir de valeur à l’argument labels, alors celui-ci prend
d’abord la valeur NULL. Cependant, la valeur de l’objet labels est ensuite modifié dans le corps de la fonction
par des instructions ayant l’allure suivante.
if ([Link](labels)) {
<instructions> # comportant une nouvelle assignation de valeur à labels
}
Ainsi la fonction [Link] est utilisée pour vérifier si une valeur a été fournie en entrée lors de l’appel de la
fonction. Cette façon de faire est une solution de rechange l’utilisation de missing en omettant une valeur
par défaut dans la liste des arguments. Elle comporte cependant l’avantage d’indiquer plus clairement dans la
liste des arguments que l’argument n’est pas obligatoire.
La fonction [Link] vérifie que la valeur donnée en entrée à un argument est bien une valeur acceptée
ou retourne le premier élément du vecteur de valeurs possibles si aucune valeur n’a été donnée en entrée à
l’argument.
Nous devrions reproduire cette façon de faire dans nos propres fonctions qui possèdent un argument du même
type que l’argument useNA de la fonction table. Par exemple, remplaçons l’argument sortie_matrice de
notre fonction stats_desc par l’argument format_sortie comme suit.
stats_desc <- function(x, format_sortie = c("vecteur", "matrice", "liste")) {
# Calcul
if ([Link](x)) {
stats <- c(min = min(x), moy = mean(x), max = max(x))
} else if ([Link](x) || [Link](x)) {
stats <- table(x, dnn = NULL)
} else {
stats <- NA
}
# Production de la sortie
format_sortie <- [Link](format_sortie)
8
if (format_sortie == "matrice") {
stats <- [Link](stats)
colnames(stats) <- if ([Link](x) || [Link](x)) "frequence" else "stat"
} else if (format_sortie == "liste") {
stats <- [Link](stats)
}
return(stats)
}
9
Le nom partiel opt a été reconnu comme représentant l’argument option. Un nom partiel pouvant représenter
plus d’un argument génère cependant une erreur. C’est le cas du nom partiel par dans l’exemple suivant, qui
pourrait référer à l’argument param ou encore à l’argument parametre.
test_appel(1, par = 2, option = 3, 4)
## Error in test_appel(1, par = 2, option = 3, 4): argument 2 matches multiple formal arguments
Une bonne pratique de programmation en R est d’utiliser l’assignation de valeurs aux arguments par
positionnement seulement pour les premiers arguments, ceux les plus souvent utilisés. Les arguments moins
communs devraient être nommés, en utilisant leurs noms complets, afin de conserver un code facile à
comprendre.
## [[1]]
## min moy max
## 4.300000 5.843333 7.900000
##
## [[2]]
## min moy max
## 0.100000 1.199333 2.500000
##
## [[3]]
## setosa versicolor virginica
## 50 50 50
Il est possible d’attribuer des noms aux valeurs attrapées par le .... Pour la fonction stats_desc_multi,
ces noms deviennent les noms des éléments de la liste retournée en sortie.
stats_desc_multi(
[Link] = iris$[Link],
[Link] = iris$[Link],
Species = iris$Species
)
## $[Link]
## min moy max
## 4.300000 5.843333 7.900000
##
10
## $[Link]
## min moy max
## 0.100000 1.199333 2.500000
##
## $Species
## setosa versicolor virginica
## 50 50 50
Le corps d’une fonction possédant l’argument ... dans le but de recevoir autant de valeurs à traiter que désiré
doit contenir une instruction pour récupérer les objets. Dans le corps de la fonction stats_desc_multi, ils
sont récupérés par l’instruction list(...). Les valeurs passées en entrée via ... peuvent aussi être référées
dans le corps de la fonction par les noms ..1, ..2, ..3 et ainsi de suite.
test_trois_points <- function(...) {
cat("le premier argument prend la valeur", ..1, "\n")
cat("le deuxième argument prend la valeur", ..2, "\n")
}
test_trois_points("a", 1, TRUE)
11
## l'argument 1 prend la valeur a
## l'argument 2 prend la valeur 1
## l'argument 3 prend la valeur TRUE
En résumé, lorsque l’argument ... sert à recevoir en entrée un nombre indéterminé de valeurs à traiter, les
instructions suivantes peuvent être utiles pour manipuler, dans le corps de la fonction, les valeurs attrapées
par ... :
• list(...) ;
• ..1, ..2, ..3 et ainsi de suite ;
• ...elt(n) où n est un entier ;
• ...length().
12
Tous les arguments d’une fonction doivent avoir des noms distincts. Par conséquent, une fonction ne peut
pas contenir plus d’un argument ..., ce qui l’empêche d’exploiter simultanément les deux utilités de cet
argument.
Il serait intéressant par exemple que la fonction stats_desc_multi puisse à la fois traiter un nombre
indéterminé de variables fournies en entrée et passer un nombre indéterminé d’arguments à la fonction
stats_desc, qu’elle appelle. Cependant, nous ne pourrions pas utiliser deux arguments ... pour permettre
cela.
Une fonction mentionnée dans les notes sur le prétraitement de données, la fonction ave, souffre de cette
limite. L’argument ... que cette fonction possède permet à l’utilisateur de spécifier un nombre quelconque
de variables de regroupement. Il n’est donc pas possible de passer des arguments à la fonction fournie à
l’argument FUN. Par exemple, l’instruction suivante ne permet pas de calculer des moyennes tronquées de
20% des observations le plus grandes et de 20% des observations les plus petites.
ave(x = Orange$circumference, Orange$Tree, FUN = mean, trim = 0.2)
## [1] 104.4 104.4 104.4 104.4 104.4 104.4 104.4 142.2 142.2 142.2 142.2 142.2 142.2 142.2
## [15] 97.6 97.6 97.6 97.6 97.6 97.6 97.6 145.8 145.8 145.8 145.8 145.8 145.8 145.8
## [29] 114.2 114.2 114.2 114.2 114.2 114.2 114.2
13
sortie_cat <- cat("ceci est un test\n")
## NULL
Nous constatons que l’objet sortie_cat prend la valeur NULL. Ne rien retourner en sortie signifie en réalité,
pour une fonction R, retourner l’objet spécial NULL.
Les fonctions test_appel et test_trois_points ne produisent aucune sortie puisqu’elles retournent implici-
tement ce qu’un appel à la fonction cat retourne, c’est à dire rien. Elles provoquent cependant une impression
comme effet de bord.
sortie_test <- test_trois_points("oui")
## NULL
La fonction stats_desc utilise la fonction return à la dernière ligne du corps de la fonction pour retourner
explicitement l’objet stats. Cependant, le corps de la fonction stats_desc_multi ne contient aucun appel
à la fonction return. Celle-ci retourne donc le résultat de lapply(X = args, FUN = stats_desc), car il
s’agit de la dernière expression évaluée dans le corps de la fonction. Modifions cette dernière fonction pour
expérimenter l’utilisation de la fonction return.
stats_desc_multi <- function(...) {
args <- list(...)
return(args)
cat("Est-ce que cette instruction est évaluée ?")
lapply(X = args, FUN = stats_desc)
}
stats_desc_multi(rating = attitude$rating, complaints = attitude$complaints)
## $rating
## [1] 43 63 71 61 81 43 58 71 72 67 64 67 69 68 77 81 74 65 65 50 50 64 53 40 63 66 78 48 85 82
##
## $complaints
## [1] 51 64 70 63 78 55 67 75 82 61 53 60 62 83 77 90 85 60 70 58 40 61 66 37 54 77 75 57 85 82
Cette version de la fonction stats_desc_multi retourne la liste des arguments fournis en entrée plutôt que
le résultat de l’appel à lapply à cause de la présence de l’instruction return(args). Les instructions suivant
l’appel à la fonction return n’ont même pas été évaluées puisque l’impression demandée par l’appel à la
fonction cat ajouté au corps de la fonction n’a pas été produite.
14
stats_desc_multi(rating = attitude$rating, complaints = attitude$complaints)
## $stats
## $stats$rating
## min moy max
## 40.00000 64.63333 85.00000
##
## $stats$complaints
## min moy max
## 37.0 66.6 90.0
##
##
## $call
## stats_desc_multi(rating = attitude$rating, complaints = attitude$complaints)
Pour faciliter la réutilisation des résultats, il est souhaitable de toujours nommer les éléments d’une liste
retournée en sortie.
15
80
70
rating
60
50
40
50 60 70 80
raises
## NULL
Nous savons déjà cependant que d’autres fonctions graphiques retournent un objet en plus de produire un
graphique. Il est donc possible pour une fonction R de produire à la fois un effet de bord et une sortie.
sortie_boxplot <- boxplot(x = attitude$rating, xlab = "rating")
80
70
60
50
40
rating
sortie_boxplot
## $stats
## [,1]
## [1,] 40.0
## [2,] 58.0
16
## [3,] 65.5
## [4,] 72.0
## [5,] 85.0
##
## $n
## [1] 30
##
## $conf
## [,1]
## [1,] 61.46146
## [2,] 69.53854
##
## $out
## numeric(0)
##
## $group
## numeric(0)
##
## $names
## [1] ""
Un autre exemple d’effet de bord est l’écriture dans un fichier externe. Par exemple, la fonction
[Link] ne retourne rien dans l’environnement de travail de la session R, mais enregistre des données
dans un fichier externe, sur le disque de l’ordinateur.
Finalement, toute interaction avec l’environnement de travail ou la session R peut être considérée
comme un effet de bord. Les fonctions suivantes sont toutes des exemples de fonctions ayant des effets de
bord :
• library : charge un package, ce qui modifie le chemin de recherche de R ;
• setwd : modifie le répertoire de travail ;
• options : modifie les options de la session R ;
• par : modifie les paramètres graphiques ;
• etc.
17
4.1 Passage d’arguments par valeur
Lors de l’appel de la plupart des fonctions R, lorsqu’un objet est assigné à un argument, une copie de cet objet
est créée et l’évaluation des instructions du corps de la fonction affecte cette copie et non l’objet d’origine. Ce
mécanisme est appelé en informatique le « passage d’arguments par valeur ».
Un autre mécanisme possible de passage d’arguments est le « passage par référence ». Avec ce type de passage,
les objets passés ne sont pas recopiés et les instructions du corps d’une fonction peuvent modifier l’objet
d’origine. En R, ce type de passage est très rare. Une exception notable à cette observation est le package
[Link], qui utilise le passage par référence pour certaines de ses fonctions (notamment l’opérateur :=,
les fonctions setorder et setcolorder), comme nous l’avons mentionné dans les notes sur le prétraitement
de données en R.
Illustrons ici le passage d’argument de loin le plus usuel en R : le passage d’arguments par valeur. Supposons
que notre environnement de travail comporte un objet nommé x contenant le nombre 5.
x <- 5
x
## [1] 5
Créons une simple fonction R qui ajoute une unité à des nombres.
ajoute_1 <- function(x) x + 1
Maintenant, appelons cette fonction en lui donnant en entrée l’objet x de notre environnement de travail.
ajoute_1(x = x)
## [1] 6
La fonction retourne le résultat de x + 1, soit 6. Mais est-ce que l’objet x a pour autant changé ?
x
## [1] 5
Non. Il contient toujours la valeur 5.
Remarquez qu’ici le nom x a été utilisé pour deux entités distinctes :
• un objet dans notre environnement de travail,
• un argument de la fonction ajoute_1.
Dans l’instruction ajoute_1(x = x), nous avons assigné la valeur contenue dans l’objet x à l’argument
portant le même nom.
Comment pourrions-nous modifier l’objet x de notre environnement de travail à l’aide de la fonction ajoute_1 ?
Il faudrait assigner le résultat de l’instruction ajoute_1(x = x) au nom x comme suit.
x <- ajoute_1(x = x)
En fait, cette commande écrase l’ancien objet x par un nouveau, contenant la valeur retournée par
ajoute_1(x = x).
x
## [1] 6
18
Mais comment R trouve-t-il la valeur des objets appelés à l’intérieur d’une fonction, qui ne sont ni des
arguments ni des variables locales ?
Chaque langage de programmation suit une certaine règle pour résoudre ce problème. Les deux règles les
plus courantes sont l’utilisation d’une portée lexicale (en anglais lexical scoping) ou encore d’une portée
dynamique (en anglais dynamic scoping).
Avec une portée lexicale, si un objet appelé n’est pas trouvé dans l’environnement d’évaluation de l’appel
à une fonction, le programme va le chercher dans l’environnement d’où la fonction a été créée, nommé
environnement englobant (en anglais enclosing environment). Avec une portée dynamique, le programme
va plutôt le chercher dans l’environnement d’où la fonction a été appelée, nommé environnement d’appel
(en anglais calling environment).
R utilise la portée lexicale.
Voici un petit exemple pour illustrer la portée lexicale.
a <- 1
b <- 2
f <- function(x) {
a*x + b
}
## [1] 7
Les objets nommés a et b ne se retrouvaient pas dans l’environnement d’exécution de la fonction. Alors R a
cherché leurs valeurs dans l’environnement englobant de la fonction f, qui est ici l’environnement de travail.
environment(f)
## <environment: 0x0000017c65a6bed8>
Il a trouvé a = 1 et b = 2. La fonction environment retourne l’environnement englobant d’une fonction.
Modifions maintenant l’exemple comme suit.
g <- function(x) {
a <- 3
b <- 4
f(x = x)
}
## [1] 7
La fonction g est appelée dans l’environnement de travail. Elle appelle elle-même f. L’environnement d’appel
de f est donc l’environnement d’exécution de g. Par contre, l’environnement englobant de f n’a pas changé.
Il est encore l’environnement de travail, car c’est dans cet environnement que la fonction a été définie.
environment(f)
## <environment: 0x0000017c65a6bed8>
La portée lexicale permet de s’assurer que le fonctionnement de l’évaluation de l’appel à une fonction ne
dépende pas du contexte dans lequel la fonction est appelée. Il dépend seulement de l’environnement d’où la
fonction a été créée.
19
Si la portée en R était par défaut dynamique, g(x = 5) aurait retourné la valeur 19.
Et si f était créée à l’intérieur de la fonction g ?
g <- function(x) {
f <- function(x) {
a*x + b
}
a <- 3
b <- 4
f(x = x)
}
## [1] 19
L’environnement englobant de f est maintenant l’environnement d’exécution de g, car f a été défini dans le
corps de la fonction g.
Notons que l’environnement englobant des fonctions disponibles en R autres que celles que nous avons créées
en cours de session est l’espace de noms du package d’où provient la fonction. Par exemple, l’environnement
englobant de la fonction mean est l’espace de noms du package base. Nous verrons ce qu’est un espace de
noms dans les notes sur l’utilisation de packages R.
environment(mean)
## <environment: namespace:base>
20
Figure 1 – Exemple de chemin de recherche lors de l’évaluation de l’appel à une fonction R
nous partageons nos fonctions avec une autre personne, nous ne contrôlons pas le contenu de l’environnement
de travail pendant la session R de cette personne.
Ces recommandations s’appliquent au code dans le corps d’une fonction, mais aussi aux instructions définissant
les valeurs par défaut des arguments. Nous avons appris que ces instructions sont évaluées dans le corps de
la fonction. Elles peuvent donc contenir sans problème d’autres arguments de la fonction. Cependant, nous
devrions éviter d’utiliser des objets provenant de l’environnement de travail dans ces instructions.
1. Planifier le travail :
• entrée = un vecteur de nombres (= 1 seul argument)
• sortie = le dénombrement (une seule valeur)
21
• utiliser l’opérateur modulo pour tester si un nombre est impair
• nous pourrions travailler de façon vectorielle ou encore utiliser une boucle sur les éléments du vecteur
Ce vecteur contient 3 nombres entiers impairs. C’est le résultat que nous visons obtenir.
Code le plus simple qui me vient en tête :
sum(x %% 2 == 1)
## [1] 3
Nous obtenons bien 3. Ça marche pour les mini-données test.
Ce code est équivalent à la boucle suivante :
k <- 0
for (n in x) {
if (n %% 2 == 1) {
k <- k + 1
}
}
k
## [1] 3
4. Documenter la fonction :
Option 1 : Documentation en commentaire dans le corps de la fonction.
compte_impair_vectoriel <- function(x) {
# Fonction qui compte combien de nombres entiers impairs contient un vecteur numérique
# Argument en entrée : x = vecteur numérique
# Sortie : le nombre de nombres entiers impairs dans x
sum(x %% 2 == 1)
}
22
# Fonction qui compte combien de nombres entiers impairs contient un vecteur numérique
# Argument en entrée : x = vecteur numérique
# Sortie : le nombre de nombres entiers impairs dans x
compte_impair_boucle <- function(x) {
k <- 0
for (n in x) {
if (n %% 2 == 1) {
k <- k + 1
}
}
k
}
Options supplémentaires : Nous verrons d’autres options dans le cours sur les packages.
6 Programmation fonctionnelle
Maintenant que vous savez écrire des fonctions en R, vous pouvez exploiter tout le potentiel de la program-
mation fonctionnelle, paradigme de programmation exploité par R. En fait, nous avons déjà parlé de ce
paradigme dans ce cours. L’utilisation de fonctions de la famille des apply est une forme de programmation
fonctionnelle. Contentons nous ici de parler de cet aspect de la programmation fonctionnelle : les fonctions de
haut niveau qui prennent d’autres fonctions en entrée, comme les fonctions de la famille des apply.
Nous avons déjà donné dans les notes sur les structures de contrôle un exemple de boucle for remplacé par
un appel à une fonction de la famille des apply. En fait, pratiquement n’importe quelle boucle for en R
peut être remplacée par un appel à une fonction de la famille des apply une fois que nous savons comment
23
créer de nouvelles fonctions. Par exemple, reprenons l’exemple de boucle suivant, aussi tiré des notes sur les
structures de contrôle.
modeles <- vector(length = ncol(attitude) - 1, mode = "list")
names(modeles) <- setdiff(names(attitude), "rating")
Il s’agit d’une boucle ajustant plusieurs modèles de régression linéaire simple avec les variables du jeu de
données attitude. Modifions un peu cet exemple pour conserver des modèles uniquement les coefficients de
détermination, non ajustés et ajustés.
R2 <- matrix(NA, nrow = 2, ncol = ncol(attitude) - 1)
colnames(R2) <- setdiff(names(attitude), "rating")
rownames(R2) <- c("[Link]", "[Link]")
R2
Cette fonction prend en entrée le nom d’une variable provenant de attitude, mais autre que la variable
réponse rating. Nous pouvons itérer sur tous les noms de variables possibles comme suit :
sapply(
X = setdiff(names(attitude), "rating"),
FUN = R2_reg_rating_vs_var
)
24
purrr.
7 Résumé
Syntaxe générale d’une fonction
<nom_fonction> <- function(<arg_1>, <arg_2>, <arg_3>) {
<instructions> # formant le corps de la fonction
}
Argument ...
Nous pouvons insérer l’argument ... dans la liste des arguments des fonctions que nous créons. Dans le corps
de la fonction, le traitement de cet argument dépend de son utilité.
• Pour prendre en entrée un nombre indéterminé de valeurs à traiter :
– Le corps de la fonction doit contenir des instructions pour récupérer les éléments attrapées :
∗ list(...) ou ;
∗ ..1, ..2, ..3 et ainsi de suite ou ;
∗ ...elt(n) où n est un entier.
(...length() retourne le nombre d’éléments attrapés par ...)
• Pour permettre le passage d’arguments à une autre fonction :
– Dans le corps de la fonction, les appels à la ou aux fonctions auxquelles nous souhaitons permettre
le passage d’arguments doivent contenir l’argument ....
25
Résultat produit
Une fonction retourne en sortie :
• ce que retourne l’expression donnée en argument à la fonction return dans le corps de la fonction
(retour explicite) ;
• ou, en l’absence d’appel à la fonction return, ce que retourne la dernière expression évaluée dans le
corps de la fonction (retour implicite).
La sortie d’une fonction est toujours composée d’un seul objet. Pour retourner plusieurs objets, il faut les
combiner dans un seul objet (typiquement dans une liste).
La fonction [Link] permet d’obtenir une copie de l’appel de la fonction.
Une fonction peut également produire un ou des effets de bord : une impression, la production d’un
graphique, l’écriture dans un fichier externe, une interaction avec l’environnement de travail ou la session R,
etc.
Portée lexicale
Comment R trouve-t-il la valeur des objets appelés dans les instructions du corps d’une fonction qui ne sont
ni des arguments ni des variables locales ?
Il les cherche dans l’environnement englobant de la fonction = environnement dans lequel la fonction a
été créée. R utilise donc une portée lexicale.
À ne pas confondre : R ne cherche pas dans l’environnement d’appel = environnement dans lequel la
fonction est appelée (à moins que l’environnement englobant soit le même que l’environnement d’appel).
26
• les objets créés dans la fonction (variables locales),
• les objets se trouvant dans des packages chargés,
• les objets dans l’environnement englobant (si nous comprenons bien le concept de portée lexicale).
Ne pas utiliser les objets de l’environnement de travail, car le contenu de cet environnement est
constamment modifié.
Références
Référence citée dans le texte :
[1] Matloff, N. (2011). The Art of R Programming : A Tour of Statistical Software Design. No Starch
Press. Sections 1.3 et 7.4
Références supplémentaires :
• Wickham, H. (2019). Advanced R. 2e édition. Chapman and Hall/CRC.
– Fonctions : Chapitre 6 [Link]
– Environnements : Chapitre 7 [Link]
• Fanara, C. (2019). Tutoriel web intitulé « A Tutorial on Using Functions in R ! ». [Link]
[Link]/community/tutorials/functions-in-r-a-tutorial
• Passage d’arguments par valeur versus par référence : [Link]
[Link]
• Pour en apprendre davantage concernant la programmation fonctionnelle :
– [Link]
– [Link]
– [Link]
– [Link]
27