3 Fonction
Jusqu’ici, nous avons utilisé des fonctions incluses dans les packages, rédigées par d’autres personnes. Dans ce chapitre, nous allons voir comment créer ses propres fonctions.
3.1 Définition
La définition d’une nouvelle fonction suit la syntaxe suivante :
avec name
le nom que l’on décide de donner à la fonction, qui doit respecter les règles de nommage abordés en Section 1.2.2.2, arguments
les arguments de la fonction, et expression
le corps de la fonction. Comme on peut le remarquer, on utilise le symbole d’assignation : les fonctions sont des objets. L’appel de la fonction aura la syntaxe suivante :
Il suffit donc de rajouter des parenthèses au nom de la fonction pour l’appeler. En effet, name
désigne l’objet R
~qui contient la fonction qui est appelée à l’aide de l’expression name()
.
Par exemple, si on souhaite définir la fonction qui calcule le carré d’un nombre, voici ce que l’on peut écrire :
## [1] 4
## [1] 9
3.2 La structure d’une fonction
Les fonctions en R
, excepté les fonctions primitives du package {base
}, sont composées de trois parties :
- une liste d’arguments ;
- un corps, contenant du code exécuté lors de l’appel à la fonction ;
- un environnement, qui définit l’endroit où sont stockées les variables.
On peut accéder à ces trois parties (et les modifier) avec les fonctions formals()
pour les arguments, body()
pour le corps et environment()
pour l’environnement.
3.2.1 Le corps d’une fonction
Dans le cas le plus simple, le corps d’une fonction est constitué d’une seule instruction. Si on désire en écrire plusieurs, il est nécessaire de les entourner par des accolades, pour réaliser un regroupement. Le résultat est la valeur de la dernière commande contenue dans le corps de la fonction.
## [1] 2
Si on souhaite retourner une valeur autre part qu’à la dernière ligne, il faut utiliser la fonction return()
(utile lorsque l’on emploie des conditions, comme nous le verrons par la suite, ou pour prévenir d’une erreur).
## [1] 4
return()
en dernière ligne, dans la mesure où cela est d’une part inutile, et perturbe la lecture du code d’autre part.
Il est possible de retourner une liste, pouvant contenir autant d’objet que l’on souhaite.
# Calculer la moyenne et l'écart-type pour un vecteur
stat_des <- function(x) {
list(moyenne = mean(x), ecart_type = sd(x))
}
x <- runif(10)
stat_des(x)
## $moyenne
## [1] 0.2964024
##
## $ecart_type
## [1] 0.3340041
Il est également possible de ne pas afficher dans la console le résultat de l’appel à une fonction à l’aide de la fonction invisible()
.
stat_des_2 <- function(x) {
invisible(list(moyenne = mean(x), ecart_type = sd(x)))
}
x <- runif(10)
stat_des_2(x)
str(stat_des_2(x))
## List of 2
## $ moyenne : num 0.612
## $ ecart_type: num 0.321
## [1] 0.6124115
On peut afficher malgré tout le résultat d’une fonction retournant un résultat invisible en ayant recours aux parenthèses.
## $moyenne
## [1] 0.6124115
##
## $ecart_type
## [1] 0.3212355
Lorsque la dernière instruction est une assignation, nous sommes dans le cas d’un résultat invisible.
## [1] 4
## [1] 4
3.2.2 Les arguments d’une fonction
Dans l’exemple de la fonction carre()
que nous avons crée, nous avons renseigné un seul argument, appelé x
. Si la fonction que l’on souhaite créer nécessite plusieurs arguments, il faut les séparer par une virgule.
Considérons par exemple le problème suivant. Nous disposons d’une fonction de production \(Y(L,K, M)\), qui dépend du nombre de travailleurs \(L\) et de la quantité de capital \(K\), et du matériel \(M\), telle que \(Y(L,K, M) = L^{0.3}K^{0.5}M^{2}\). Cette fonction pourra s’écrire, en R
de la manière suivante :
3.2.2.1 Appel sans noms
En reprenant l’exemple précédent, si on nous donne \(L = 60\) et \(K = 42\) et \(M = 40\), on peut en déduire la production :
## [1] 46.28945
On peut noter que le nom des arguments n’a pas été mentionné ici. Lors de l’appel de la fonction, R
cherche d’abord s’il y a des arguments nommés afin de leur associer des valeurs. S’il n’y a pas de nom, il se basera sur la position donnée aux arguments.
## [1] 46.28945
## [1] 46.28945
3.2.2.2 Arguments effectifs
On peut, lors de la définition de la fonction, choisir de donner une valeur par défaut aux arguments. On parle d’argument formel
pour désigner les arguments de la fonction (les variables utilisées dans le corps de la fonction) et d’argument effectif
pour désigner la valeur que l’on souhaite donner à l’argument formel. Pour définir la valeur à donner à un argument formel, on utilise le symbol d’égalité. Lors de l’appel de la fonction, si l’utilisateur•rice ne définit pas explicitement une valeur, celle par défaut sera affectée.
# On propose de définir la valeur du capital à 42 par défaut
production_2 <- function(l, m, k = 42) l^(0.3) * k^(0.5) * m^(0.2)
production_2(l = 42, m = 40)
## [1] 41.59216
## [1] 9.076152
Dans l’exemple précédent, l’argument auquel nous avons donné une valeur est placé en dernier. Ce n’est pas obligatoire, mais plus pratique, si le but recherché est de ne pas avoir à saisir l’argument effectif lors de l’appel de la fonction. De plus, si l’utiliateur ne nomme pas les arguments lors de l’appel, des problèmes liés à l’ordre peuvent apparaître…
## [1] 41.59216
## Error in production_3(42, 40): argument "m" is missing, with no default
3.2.2.3 Appel avec des noms partiels
Par ailleurs, il est possible de ne pas saisir le nom complet des arguments lors de l’appel d’une fonction. En effet, on peut utiliser une abréviation du nom de l’argument. S’il existe une ambiguïté, R
retourne un message d’erreur.
## [1] 6
## Error in f("hello", 2, no = 3): argument 3 matches multiple formal arguments
...
(plus de détails sont donnés dans la Section @ref(fonctions_structure_parametres_special), il n’est pas possible d’utiliser les abréviations.
3.2.2.4 Fonctions sans arguments
On est parfois amené à créer des fonctions qui ne prennent pas d’argument. Il suffit alors de laisser la liste d’arguments formels vide.
## [1] "t" "w" "d" "p" "d" "y" "x" "x" "g" "j"
3.2.2.5 L’argument spécial ...
l’argument ...
que l’on peut voir dans certaines fonctions (essayez d’évaluer sum
par exemple) sert à indiquer que la fonction peut admettre d’autres arguments que ceux qui ont été définis. Cela sert à, dans la plupart des cas, à passer un argument à une autre fonction contenue dans le corps de la fonction que l’on appelle.
## [1] "premier" "second"
Attention toutefois, l’utilisation de ...
peut induire des soucis. En effet, un argument mal écrit sera passé à ...
et il n’y aura pas d’erreur de retournée. Par ailleurs, tous les arguments placés après ...
doivent être complètement nommés, pas abrégés.
## [1] 7
## [1] NA
## [1] NA
3.2.3 Portée des fonctions
Lorsque une fonction est appelée, le corps de cette fonction est interprété. Les variables ayant été définies dans le corps de la fonction ne vivent qu’à l’intérieur de celle-ci à moins d’avoir spécifié le contraire. On parle alors de portée
des variables. Ainsi, une variable ayant une portée locale — c’est-à-dire vivant uniquement à l’intérieur du corps de la fonction — peut avoir le même nom qu’une variable globale — c’est à dire définie dans l’espace de travail de la session —, sans pour autant désigner le même objet, ou écraser cet objet.
# Définition d'une variable globale
valeur <- 1
# Définition d'une variable locale à la fonction f
f <- function(x){
valeur <- 2
nouvelle_valeur <- 3
print(paste0("valeur vaut : ",valeur))
print(paste0("nouvelle_valeur vaut : ",valeur))
x + valeur
}
f(3)
## [1] "valeur vaut : 2"
## [1] "nouvelle_valeur vaut : 2"
## [1] 5
## [1] 1
## Error in eval(expr, envir, enclos): object 'nouvelle_valeur' not found
Sans trop rentrer trop dans les détails, il semble important de connaître quelques principes à propos de la portée des variables. Les variables sont définies dans des environnements, qui sont embriqués les uns dans les autres. Si une variable n’est pas définie dans le corps d’une fonction, R
ira chercher dans un environnement parent.
## [1] 3
Si on définit une fonction à l’intérieur d’une autre fonction, et qu’on appelle une variable non définie dans le corps de cette fonction, R
ira chercher dans l’environnement directement supérieur. S’il ne trouve pas, il ira chercher dans l’environnement encore supérieur, et ainsi de suite.
# La variable valeur n'est pas définie dans g(). R va alors chercher dans f().
valeur <- 1
f <- function(){
valeur <- 2
g <- function(x){
x + valeur
}
g(2)
}
f()
## [1] 4
# La variable valeur n'est définie ni dans g() ni dans f()
# mais dans l'environnement supérieur (global, ici)
valeur <- 1
f <- function(){
g <- function(x){
x + valeur
}
g(2)
}
f()
## [1] 3
Si on définit une variable dans le corps d’une fonction et que l’on souhaite qu’elle soit accessible dans l’environnement global, on peut utiliser le symbole <<-
, ou bien la fonction assign
(pratique peu recommandable).
## [1] 2
# En utilisant assign
rm(x)
f <- function(x){
# envir = .GlobalEnv signifique que l'on veut définir dans l'environnement global
assign(x = "x", value = x + 1, envir = .GlobalEnv)
}
f(4)
x
## [1] 5
3.3 Documentation
Lorsque l’on créé une fonction, il est important de bien la documenter afin de rendre son utilisation facilitée (par les autres mais également par soi-même).
Il existe un package ({roxygen2
}) qui permet de réaliser des fichiers de documentation des fonctions que l’on souhaite exporter au sein d’un package. Ces notes de cours ne visent pas à expliquer comment créer un package. Toutefois, il n’est pas inutile d’emprunter une technique utilisée par de nombreuses personnes qui créent des packages pour améliorer la documentation de nos fonctions dans nos codes.
L’idée est d’ajouter un commentaire roxygen
en préfixe de la définition de la fonction. Ce commentaire s’écrit à l’aide d’un croisillon suivi d’un guillemet simple : #'
. Reprenons la fonction de production, et ajoutons un commentaire roxygen
précisant :
- ce que fait la fonction
- quels sont ses arguments (
@param
) - ce qu’elle retourne (
@return
) - deux exemples d’utilisation
#' Calcul le niveau de production
#'
#' @param l montant de force de travail
#' @param k montant de capital
#' @param m montant de matériel
#' @return Le niveau de production d'une Cobb-Doublas, fonction de \code{l}, \code{k} et \code{m}
#' @examples
#' production(k = 42, l = 60, m = 40)
#' production(40, 20, 60)
production <- function(l, k, m) {
l^(0.3) * k^(0.5) * m^(0.2)
}
Plus de détails sont disponibles au chapitre 10 du livre “R Packages” d’Hadley Wickham et Jennifer Bryan.
3.4 Exercices
Exercice 1. Création de fonctions
Créer une fonction nommée
somme_n_entiers
qui retourne la somme des \(n\) premiers entiers. Son seul argument seran
; 2.Utiliser la fonctionsomme_n_entiers()
pour calculer la somme des 100 premiers entiers ;Terminer la fonction par l’assignation du résultat dans un objet nommé
res
, puis évaluer l’expression suivante :somme_n_entiers(100)
. Que peut-on constater ?Charger les données
diamonds
du package {ggplot2
} dans la sessionR
à l’aide de l’expression suivante :data(diamonds, package = "ggplot2")
Créer une fonction que l’on appellera
prix_diamant_coupe()
, qui, quand on lui fournit la valeur de la coupe du diamant sous forme de caractères (Fair
,Good
,Very Good
,Premium
, ouIdeal
), filtre le tableau de donnéesdiamonds
pour ne conserver que les observations pour lesquelles la coupe du diamant correspond à celle indiquée en argument, et retourne le prix moyen des observations de la base ainsi filtrée ;Reprendre le code de la fonction précédente, et le modifier pour retourner à présent une liste de deux éléments : (i) la moyenne des prix et (ii) l’écart-type ;
Créer la fonction
resume_diamant_coupe_couleur()
, qui pour une coupe et une couleur de diamant données, retourne une liste de deux éléments : (i) la moyenne des prix et (ii) l’écart-type pour les diamants possédant cette coupe et cette couleur (la couleur du diamant est une lettre allant deJ
pour les pires, àD
pour les meilleurs). Tester la fonction pour la coupeFair
et la couleurD
;Reprendre la fonction précédente, et lui attribuer la valeur
D
(en chaîne de caractères) comme argument effectif pour la couleur. Tester alors l’appel à la fonction en précisant :
- la coupe
Fair
et la couleurD
, - la coupe
Fair
, mais pas d’argument pour la couleur, - la coupe
Fair
et la couleurE
, - la coupe non précisée mais la couleur
E
;
Exercice 2. Création de fonctions
Supposons que les adresses e-mails des étudiant•e•s d’Aix-Marseille Université sont constituées de la manière suivante : le prénom et le nom de famille séparés par un point, le symbole arobase et le enfin le nom de domaine. Supposons de plus que les étudiant•e•s ont un seul prénom, et aucune particule au nom de famille. La syntaxe des adresses e-mail est donc comme suit : nom.prenom@etu.univ-amu.fr
.
emails <- c("marie.petit@etu.univ-amu.fr", "jean.dupont@etu.univ-amu.fr", "isabelle.martinez@etu.univ-amu.fr", "pierre.moreau@etu.univ-amu.fr")
Créer une fonction, qui à partir d’une seule adresse e-mail d’un•e étudiant•e, retourne un tibble contenant trois variables : le prénom, le nom et l’adresse e-mail de cet•te étudiant•e.