Module Écrire des fonctions avec R
20/02/2025
Ce diaporama de formation a été rédigé dans le but d’être le support visuel des formations dispensées au MASA.
Ces formations s’adressent à des agents qui ont suivi la formation R initialisation.
Champ couvert par cette formation
Ce support couvre Écrire des fonctions avec R , pour en finir avec les copiés-collés.
Ce module se décompose en plusieurs chapitres :
01 - Écrire ses propres fonctions
02 - Itération de fonctions
03 - Annexes
Ce support est orienté pour être utile aux agents du SSM MASA et se concentre sur une utilisation de R via RStudio qui est mise à disposition des agents sur la plateforme interne Cerise basée sur RStudio Workbench.
|
|
R dispose de nombreuses fonctions de base et le chargement de packages permet d’en importer d’autres.
R permet également à l’utilisateur d’écrire ses propres fonctions.
=> C’est l’équivalent des macro dans SAS
Règle courante : dès qu’on a répété le même code plus de deux fois, ça vaut le coup de l’encapsuler dans une fonction
Avantages :
Évite les erreurs de copié-collé
Facilite la lecture du code si la fonction a un nom adapté
Facilite la mise à jour du code et la correction des erreurs (on corrige une seule fois dans la fonction et non à plein d’endroits du code)
Permet de réutiliser le même code dans plusieurs scripts
Plutôt que de recopier le même code pour chaque département, on peut écrire une fonction
Une fonction exécute une série d’instructions, à partir d’éventuels objets donnés en entrée.
On crée une fonction en utilisant l’instruction function.
Elle doit être suivie :
d’une paire de parenthèses ⇒ pour indiquer les arguments
d’une paire d’accolades ⇒ pour indiquer les instructions
ma_fonction <- function(...){ ...}
Quatre éléments principaux pour créer une fonction :
Le nom de la fonction
Les arguments éventuels de la fonction
Un bloc d’instructions pour constituer le corps de la fonction
Le résultat éventuel de la fonction
Les parenthèses servent à indiquer les paramètres de la fonction (ou arguments)
Ce sont ceux qui seront passés lors de l’appel de la fonction
On peut prévoir autant d’arguments que l’on souhaite
Les arguments doivent être nommés pour être ensuite utilisés dans la fonction
Exemple : eff_milliers <- function(x, digits)
Les accolades comprennent une série d’instructions R
c’est le corps de la fonction, le code qui sera exécuté à l’appel de la fonction
On utilise dans le corps de la fonction les arguments renseignés dans les parenthèses
La fonction renvoie un résultat via l’instruction return()
L’instruction return()
arrête l’exécution de la fonction
⇒ tout ce qui est après le return() ne sera pas exécuté
return() est obligatoire
une fonction peut ne rien renvoyer : certaines fonctions accomplissent une action mais ne renvoient rien (affichage de graphique, export de fichier par exemple)
sans précision, la fonction renvoie le dernier objet appelé dans le bloc d’instructions
pour un code plus sûr et plus clair, il est recommandé d’utiliser systématiquement la fonction return() afin de préciser l’élément à renvoyer, quitte à ce que ce soit l’élément NULL
Une fonction est un objet de type function – elle apparaît dans l’environnement global dans la catégorie Functions
R traite les fonctions définies par l’utilisateur de la même façon que les autres.
Attention à ne pas nommer une fonction personnelle de la même façon qu’une fonction existante, pour ne pas l’écraser.
Identifier et écrire une fonction dans la pratique :
Code initial :
Identifier et écrire une fonction dans la pratique :
Une fonction personnelle s’utilise comme n’importe quelle autre fonction de R
Notamment, on récupère la sortie de la fonction en affectant le résultat à un objet
⇒ sinon il n’y aura que l’affichage dans la console
Dans la console :
Au moment de la définition d’une fonction, on peut indiquer une valeur par défaut qui sera prise par l’argument si l’utilisateur n’en fournit pas.
on peut utiliser la fonction sans préciser de valeur pour ce paramètre.
L’ordre et le nommage des paramètres ont une importance :
Lors de la définition d’une fonction, il vaut donc mieux placer les arguments avec une valeur par défaut en dernier : de cette façon, il est plus facile de ne pas les nommer.
Dans une fonction, on liste une série d’instructions que la fonction va effectuer
Pour des actions un peu plus complexes, on peut utiliser des conditions, en utilisant les mots-clés if et else
Par exemple, on va pouvoir exécuter certaines instructions selon la valeur prise par les arguments de la fonction.
Dans une fonction, l’environnement est temporaire
⇒ les éléments créés dans la fonction n’existent pas dans l’environnement global
Pour conserver ses fonctions, on peut les enregistrer dans un script à part.
⇒ on utilise ensuite la commande source()
pour faire appel à ce script depuis un autre script :
Pour les experts, il est possible de créer un package pour encapsuler ses fonctions.
Pour réutiliser ou partager ses fonctions, il est important de les documenter
→ le package {roxygen}
aide à générer une documentation efficace dans le corps de la fonction :
cliquer sur -> Code -> Insert Roxygen Skeleton pour faire apparaître un bloc de documentation à remplir
Les fonctions du {tidyverse} sont compliquées à utiliser dans nos propres fonctions
⇒ lorsque les variables des tableaux de données utilisées dans le corps de la fonction ne sont pas saisies directement mais proviennent d’un paramètre, cela génère des erreurs
l’opérateur {{ }} (curly curly) permet de forcer l’évaluation du paramètre
⇒ lorsque le paramètre de la fonction est une variable à laquelle on souhaite accéder, on entoure les utilisations du paramètre par des doubles accolades
Pour créer de nouvelles colonnes à partir d’un argument,
on utilise les deux opérateurs {{ }} et := (walrus operator)
⇒ on peut placer le texte que l’on souhaite comme nouveau nom de colonne
(ex : "{{var_moyenne}}_moy"
)
Lorsque l’argument est passé sous forme de chaînes de caractères, on ne peut pas utiliser l’opérateur {{ }}
⇒ le pronom .data permet d’accéder aux colonnes du tableau à partir de leur nom sous forme de chaîne de caractères
Itération de fonctions : présentation
Utilisation de across
Utilisation de across : Exemple avec mutate
Utilisation de across : Exemple avec summarise
Package purrr
{purrr} : Itérer sur un vecteur ou une liste
{purrr} : Itérer sur deux éléments
{purrr} : Généralisation
{purrr} : Manipuler des listes
{purrr} : Fonctions à effets de bord
Intérêt d’une fonction = pouvoir être utilisée plusieurs fois
Utilisation de la fonction across()
Utilisation du package {purrr}
across()
across()
across()
Objectif de across()
⇒ appliquer une même fonction à un ensemble de variables d’une table de données
across()
s’utilise à l’intérieur des fonctions dplyr comme mutate()
et summarise()
.
La syntaxe générale est la suivante :
across()
Pour désigner les variables à traiter, on utilise les mêmes « select helpers » que ceux utilisés dans la fonction select par exemple :
across()
Le traitement à effectuer correspond à une fonction ⇒ on peut la renseigner de différentes façons :
across()
avec une fonction anonyme:
définie avec (x)
across()
across()
: Exemples avec mutateacross()
: Exemples avec summariseacross()
: Exemples avec summariseObjectif de map() ⇒ appliquer une même fonction à l’ensemble des éléments d’un vecteur ou d’une liste
map() s’utilise sur un vecteur ou une liste d’objets. La syntaxe générale est la suivante :
Le traitement correspond à la fonction à appliquer ⇒ on peut la renseigner de différentes manières :
2) Avec une fonction anonyme
Les écritures suivantes sont équivalentes :
Il n’est pas nécessaire de créer une fonction en temps que telle : on peut le faire directement à l’intérieur de map()
Possibilité d’ajouter des arguments supplémentaires à la fonction à appliquer
Objectif de map2() = appliquer une même fonction sur chacun des couples d’éléments de deux listes ou vecteurs
la fonction à appliquer peut être renseignée avec une fonction anonyme
définie avec un ~ : les éléments successifs de la liste sont alors identifiés avec les mots clé .x et .y
définie avec des paramètres
Généralisation avec la fonction pmap() = permet d’appliquer une même fonction sur chacun des p-uplets d’éléments des différentes listes ou vecteurs
Les fonctions map, map2 et pmap renvoient des listes
→ on peut retrouver des data.frame plus faciles à manipuler en utilisant bind_rows(), ou la fonction reduce()
⇒ bind_rows() permet de concaténer une liste de tables
⇒ reduce() applique une fonction de façon récursive à chaque élément d’une liste ou d’un vecteur
Pour les fonctions qui ne renvoient aucun résultat, on utilise la fonction walk()
⇒ même fonctionnement que pour les fonctions map, mais spécifiques pour les fonctions à effets de bord
Exemples : impression dans la console, affichage de graphiques, export de données…
walk2()
et pwalk()
Passer un nombre indéfini de paramètres
Retourner plusieurs objets dans une fonction
Utiliser plusieurs return dans une fonction
Utilisation de across : règle de renommage
{purrr} : Contrôler le format de sortie
{purrr} : Nommer les éléments d’une liste
{purrr} : modifier le comportement d’une fonction
Il est possible de prévoir un nombre indéfini de paramètres lors de l’élaboration d’une fonction avec …
.
Cela permet d’accéder aux paramètres d’une “sous-fonction” utilisée dans le corps de la fonction créée.
Les …
permettent également de prévoir que l’utilisateur puisse passer un nombre indéfini de paramètres
L’instruction return() ne peut renvoyer qu’un seul objet ⇒ pour renvoyer plusieurs éléments dans une fonction, il faut les placer dans une liste
Il est possible de mettre plusieurs return dans le bloc d’instructions d’une fonction.
=> Dans ce cas, l’exécution s’arrête au premier return rencontré.
across()
: Règle de renommageUne règle de renommage des variables issues de across peut être donnée via l’argument .names.
On lui donne une chaîne de caractère concaténée où :
{.col}
désigne la variable à traiter
{.fn}
la fonction de traitement.
Par exemple : .names = “{.col}_{.fn}”
Si aucune règle de renommage n’est précisée, R se débrouille :
Dans un mutate, les variables d’origine sont écrasées
Dans un summarise avec un seul agrégat à calculer, conserve le nom des variables d’origine
Dans un summarise avec plusieurs agrégats, suffixe la fonction d’agrégation au nom des variables d’origine
across()
Règle de renommagePossibilité de contrôler le format de sortie avec les dérivés de la fonction map
Retour sur le calcul des évolutions départementales des populations par composition familiale :
Par défaut, les différents éléments de la liste résultats ne sont pas nommés. On ne peut accéder aux différents éléments que par index :
La fonction set_names()
du package purrr permet de nommer les éléments d’une liste en entrée :
Les fonctions de {purrr} ne renvoient un résultat que s’il n’y a aucune erreur dans l’ensemble… sinon, l’exécution est arrêtée et aucun résultat n’est retourné, même si l’erreur est à la fin !
safely() ⇒ retourne le résultat et le message d’erreur de la fonction
possibly() ⇒ retourne le résultat ou une valeur par défaut en cas d’erreur
https://juba.github.io/tidyverse/14-fonctions.html https://juba.github.io/tidyverse/17-if-boucles.html
https://thinkr.fr/controle-et-gestion-des-erreurs-dans-r/
https://juba.github.io/tidyverse/19-programmer-tidyverse.html
https://thinkr.fr/comment-creer-des-fonctions-dans-le-tidyverse-avec-la-tidyeval-et-le-stash-stash/
https://juba.github.io/tidyverse/15-dplyr-avance.html#sec-across
https://www.icem7.fr/r/across-plus-puissant-flexible-quil-ny-parait/
https://dcl-prog.stanford.edu/iteration.html
https://thinkr.fr/code-ronronne-purrr/
https://speakerdeck.com/jennybc/purrr-workshop?slide=91
https://rstudio.com/resources/cheatsheets/
Comment écrire une fonction ?