Guide pratique R



Le “Guide pratique R” est un manuel expliquant l’utilisation du langage de programmation R pour la manipulation des données.

1 Environnement de travail

On trouvera ici l’environnement permettant de faire tourner des scripts R.

1.1 Cerise

L’accès à R peut se réaliser via Cerise, la plateforme de stockage et de traitement des données.
Cerise permet l’accès aux ressources déposées sur le serveur, la sauvegarde, le partage de code et le travail simultané. C’est l’usage recommandé.

1.1.1 Version de R

La version de R par défaut est la 4.4.1. La version précédente (4.2.3) est toujours disponible. Il est recommandé d’utiliser la nouvelle version de R.

Pour changer de version, aller en haut à droite et cocher la version souhaitée.

1.1.2 Sessions R

Il est possible d’avoir plusieurs sessions R ouvertes. On peut choisir la version de R utilisée au lancement de la session, en allant en haut à droite.

On peut aussi créer une nouvelle session, en cliquant sur le ‘+’ en haut à droite, via :

puis choisir de l’associer à un projet ou à un dossier :

‘~’ (obtenu par AltGr+2 puis espace) permet d’indiquer le répertoire racine dans Cerise, relatif au répertoire personnel de l’utilisateur (correspond à ~/CERISE/00-Espace-Personnel/prenom.nom/).

On peut préciser le chemin du dossier en cliquant sur ‘Parcourir’, puis cliquer sur ‘Choisissez’

Puis choisir la version de R à travers la fenêtre :

et enfin cliquer sur ‘Start’.

https://orion.agriculture/confluence/display/CER/Bon+usage+des+sessions+RStudio

1.1.3 Comment récupérer un chemin pour l’utiliser dans un programme R

Dans la fenêtre de parcours des dossiers (fenêtre en bas à droite dans RStudio Server), choisir More
puis Set as Working Directory.

# setwd("~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R")

Le chemin peut-être aussi copié depuis la console (fenêtre en bas à gauche).

1.1.4 Comment se déplacer rapidement vers un autre répertoire

Pour l’utilisateur muni de ce chemin, il convient de cliquer sur les trois petits points (fenêtre en bas à droite dans RStudio Server)

Dans la fenêtre qui apparaît, on colle le chemin vers le dossier d’intérêt, par exemple
~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R

~ (obtenu par AltGr+2 puis espace) permet d’indiquer le répertoire racine dans Cerise, relatif au répertoire personnel de l’utilisateur (correspond à ~/CERISE/00-Espace-Personnel/prenom.nom/).

On clique enfin sur OK.

Pour vérifier le répertoire de travail, on peut exécuter la fonction getwd().
Un répertoire de travail (ou working directory en anglais) est un répertoire qui sera pris comme référence par RStudio.

Sous RStudio, le répertoire de travail est également affiché dans le quadrant inférieur gauche, en gris, en dessous de l’onglet ‘Console’.

> setwd("~/CERISE")
> getwd()
# donne [1] "/var/data/nfs/CERISE"

1.1.5 Comment transférer des fichiers entre son poste de travail et Cerise

1.1.5.1 Comment télécharger un fichier depuis Cerise

Une fois sur l’interface Rstudio de Cerise, il faut naviguer vers le dossier contenant le fichier à traiter.

Pour cela, il faut utiliser le navigateur de fichier, dans la partie bas droite de l’écran.

Il faut naviguer dans les dossiers jusqu’au fichier et ensuite sélectionner celui-ci pour pouvoir le télécharger, puis aller dans More > Export

1.1.5.2 Comment téléverser un fichier vers Cerise

Sur votre PC en local, faire un zip qui contient les fichiers à uploader (même si le fichier est unique). Cela permet d’affecter les “bons droits” sur ces fichiers (notamment qu’ils soient modifiables par d’autres agents). Son contenu sera ensuite automatiquement dézippé sous Cerise.

Dans le cas d’un fichier ayant vocation à être déposé sur Cerise, il faut utiliser le navigateur de fichier de RStudio, dans la partie bas droite de l’écran.

Une fois placé dans le dossier de dépôt du fichier, choisir Upload.

Puis récupérer le fichier sur son poste de travail en utilisant Parcourir, puis OK.

1.2 R Studio

RStudio est un environnement de développement intégré (EDI), qui propose des outils facilitant l’écriture de scripts et l’usage de R au quotidien. Un autre EDI apprécié est Visual Studio Code, qui peut être utilisé en local sur son poste de travail.

L’interface RStudio est composée de différents panneaux, incluant :
- une fenêtre Source (Editeur), permettant la vue des scripts et des données (en haut à gauche),
- une console contenant le journal d’exécution (la log) (en bas à gauche),
- l’espace de travail (Environnement) et l’historique des commandes (en haut à droite),
- une fenêtre Services, contenant un navigateur offrant une vue des répertoires et fichiers, un onglet de visualisation des graphiques, un onglet de gestion des packages et de l’aide (en bas à droite).

1.2.1 Faire tourner des programmes via l’environnement de développement intégré R Studio

Exécution en interactif dans la session courante de l’utilisateur.

A partir du programme ouvert dans l’IDE Rstudio, l’utilisateur exécute le programme manuellement :

Menu Code/ Run
Bouton Run
Combinaison de touche Ctrl-A suivie de Ctrl-Entrée

Une fois l’exécution commencée, la console présente à l’utilisateur les informations du déroulé du programme.

Dans ce mode, l’utilisateur doit attendre que le programme se termine pour à nouveau relancer un programme s’il souhaite rester dans la même session. Les objets de l’environnement d’exécution évoluent également tout au long du déroulé du programme exécuté, et les ressources de la session sont mobilisées par l’exécution courante.

1.2.2 Raccourcis RStudio

Pour modifier le raccourci pour le pipe natif : menu Outils (Tools) -> Options Globales (Global Options), cliquer sur l’icône Code à gauche et cocher la case à cocher pour Utiliser l’opérateur pipe natif |>.

Pour gagner du temps, il est important d’utiliser les raccourcis pour les commandes répétitives.

# (`AltGr` + `3`) pour commenter son code

`Ctrl` + `Shift` + `M` : insère le pipe `|>`
 Plusieurs verbes (ou fonctions) peuvent facilement être combinés  
 en utilisant l'opérateur `|>` (pipe) qui permet d'enchaîner les instructions.
Penser à aérer son code, notamment en revenant à la ligne, après un |>
 x |> 
  f |> 
    g |> 
      h  
est équivalent à h(g(f(x)))

`Alt` + `-` : insère la flèche d'assignation <-  
`Alt`+ `6` (Windows) : insére l'opérateur assignation `<-`
`Ctrl`+`Entrée` : exécute le code sélectionné 
(ou la commande ou se trouve le curseur si rien n'a été sélectionné)
Ctrl + A : tout sélectionner
Ctrl + C/X/V : copier/couper/coller classique
`Ctrl` + `Z` : annule la dernière modification
`Ctrl` + `F` : ouvre le formulaire de chercher/remplacer dans le script
`Ctrl` + `Shift` + `F` : recherche une expression dans plusieurs fichiers
`Ctrl` + `I` : indente automatiquement le code sélectionné
(ou la commande ou se trouve le curseur si rien n'a été sélectionné)
Ctrl + Shift + C : bascule en commentaire le code sélectionné 
(ou la ligne où se trouve le curseur si rien n'a été sélectionné)
`Ctrl` + `S` : enregistre le fichier courant (équivalent de la disquette)
`Tab` (touche de tabulation) ou Ctrl + Espace : ouvre les propositions d'autocomplétion   
(pour les noms de fonctions ou d'objets, et pour les chemins à l'intérieur de guillemets)

F1 : sur une fonction R, affiche l'aide de la fonction 
(si le package est chargé).  On peut aussi utiliser ?nom_fonction (ou ??nom_fonction).  
La documentation de la fonction apparaîtra dans le panneau en bas à droite de RStudio.
F2 : sur une fonction, affiche le code de la fonction 
(sur une fonction personnalisée uniquement 
si le script contenant la définition de la fonction est ouvert)

Ctrl + ↑ : dans la console, affiche l'historique des commandes exécutées (et permet de les relancer)
Ctrl + L : vide la console

`Ctrl` + `+=` : Zoom positif (Menu View, Zoom In)
`Ctrl` + `0à` : Annulation du zoom (Menu View, Actual Size)
`Zoom négatif` (Menu View, Zoom Out)

Ctrl + Alt + Shift + W : ferme tous les fichiers sauf le fichier courant

---- : permet de créer une section dans un script (pour structurer le code)
Ctrl + Shift + R : ajout de section dans un script (ou via le menu Code > Insert Section)
Ctrl + Alt + I : insère un chunk R (dans une syntaxe markdown uniquement)

https://orion.agriculture/confluence/display/CER/Ergonomie+et+raccourcis+dans+RStudio

https://book.utilitr.org/03_Fiches_thematiques/Fiche_rprojects.html#utiliser-les-fonctionnalit%C3%A9s-de-rstudio

1.2.3 Comment créer un nouveau répertoire

Dans le cadran en bas à droite, aller dans le menu Files, choisir New Folder.

dir.create("nouveau_dossier")

1.2.4 Comment créer un nouveau script R

Dans le menu File, choisir New File, R script

Un script correspond à un programme en R incluant des lignes de code.

Le fait de structurer ses analyses sous forme de scripts (suite d’instructions effectuant les différentes opérations d’une analyse) présente de nombreux avantages :

  • le script conserve l’ensemble des étapes d’une analyse, de l’importation des données à leur analyse en passant par les manipulations et les recodages.
  • on peut à tout moment revenir en arrière et corriger ou modifier ce qui a été fait.
  • il est très rapide de réexécuter une suite d’opérations complexes.
  • on peut très facilement mettre à jour les résultats en cas de modification des données sources.
  • le script garantit, sous certaines conditions, la reproductibilité des résultats obtenus.
# création d'un .R :
file.create("nouveau_pgm.R")

On peut enregistrer le script à tout moment dans un fichier avec l’extension .R, en cliquant sur l’icône de disquette ou en choissant File puis Save.

Pour exécuter une commande saisie dans un script, il suffit de positionner le curseur sur la ligne de la commande en question, et de cliquer sur le bouton Run (ou Exécuter) dans la barre d’outils juste au-dessus de la zone d’édition du script. On peut aussi utiliser le raccourci clavier Ctrl + Entrée.

Les commentaires sont un élément très important d’un script. Il s’agit de texte libre, ignoré par R, et qui permet de décrire les étapes du script, sa logique, les raisons pour lesquelles on a procédé de telle ou telle manière… Il est primordial de documenter ses scripts à l’aide de commentaires, car il est très facile de ne plus se retrouver dans un programme qu’on a produit soi-même, même après une courte interruption.

Pour ajouter un commentaire, il suffit de le faire précéder d’un ou plusieurs symboles #. En effet, dès que R rencontre ce caractère, il ignore tout ce qui se trouve derrière, jusqu’à la fin de la ligne.

1.2.5 Comment copier un fichier ou un dossier via R

library(fs)
 
# pour les fichiers
# fs::file_copy("chemin/vers/le/fichier_a_copier.csv", "chemin/vers/le/dossier_cible/")
 
# pour les dossiers
# fs::dir_copy

## Exemple de copie d'un dossier ou d'un fichier via R

# pour les dossiers
chemin_path <- "~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R/"
entree_path1 <- "Christophe/entree"
entree_path2 <- "Christophe/pgm"
entree_path3 <- "Christophe/sortie"
sortie_path1 <- "temporaire"

fs::dir_copy(path = path(chemin_path, entree_path1),
  new_path = path(chemin_path, sortie_path1))
dir_copy(path = path(chemin_path, entree_path2),
  new_path = path(chemin_path, sortie_path1),
  overwrite = TRUE)
dir_copy(path = path(chemin_path, entree_path3),
  new_path = path(chemin_path, sortie_path1))

# pour les fichiers
# fs::file_copy("chemin/vers/le/fichier_a_copier.csv", "chemin/vers/le/dossier_cible/")
chemin_fichier_entree <- "~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R/Christophe/pgm/"
entree_fichier <- "manipulations.R"
chemin_fichier_sortie <- "~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R/temporaire/pgm/"

fs::file_copy(path = path(chemin_fichier_entree, entree_fichier),
  new_path = path(chemin_fichier_sortie),
  overwrite = TRUE)

# renommer un fichier grâce à la fonction file.rename() :
# file.rename(from = "nouveau_fichier.csv", to = "mon_fichier.csv")
# file.rename(from = "nouveau_pgm.R", to = "nouveau_pgm_2.R")

1.2.6 La Console

La zone en bas à gauche se nomme la Console.

La Console doit normalement finir par une ligne commençant par le caractère > et sur laquelle devrait se trouver le curseur. Cette ligne est appelée l’invite de commande (ou prompt en anglais). Elle signifie que R est disponible et en attente de la prochaine instruction.

Quand on est dans la console, on peut utiliser les flèches vers le haut et vers le bas du clavier pour naviguer dans l’historique des commandes tapées précédemment. On peut à tout moment modifier la commande affichée, et l’exécuter en appuyant sur Entrée.

Enfin, il peut arriver qu’on saisisse une commande de manière incomplète : oubli d’une parenthèse, faute de frappe, etc. Dans ce cas, R remplace l’invite de commande habituel par un signe +.

4 *
+

Cela signifie qu’il “attend la suite”. On peut alors soit compléter la commande sur cette nouvelle ligne et appuyer sur Entrée, soit, si on est perdu, tout annuler et revenir à l’invite de commandes normal en appuyant sur Esc ou Échap.

Pour effacer le contenu de la console : cliquer sur l’icone “balai” à droite, ou menu Edit > Clear console ou Ctrl + L

1.2.7 L’espace de travail - le ‘workspace’

L’espace de travail (ou ‘workspace’) est l’environnement de la session R. Il comprend tous les objets créés durant une session de travail sous R. Par exemple, lorsqu’on importe des données, elles deviennent un objet de type data.frame et sont intégrées à l’environnement global. De même lorsqu’on crée un objet, il vient s’inclure dans l’environnement.

Ces différents objets (éléments en mémoire) sont alors visibles dans l’onglet ‘Environnement’ de la fenêtre en haut à droite.

Pour conserver le résultat d’une opération, on peut le stocker dans un objet à l’aide de l’opérateur d’assignation <-. Cette “flèche” stocke ce qu’il y a à sa droite dans un objet dont le nom est indiqué à sa gauche.

x <- 2

Si on exécute une commande comportant juste le nom d’un objet, R affiche son contenu.

x

Les noms d’objets peuvent contenir des lettres, des chiffres, les symboles . et _. Ils ne peuvent pas commencer par un chiffre. Attention, R fait la différence entre minuscules et majuscules dans les noms d’objets, ce qui signifie que x et X seront deux objets différents, tout comme resultat et Resultat.

De manière générale, il est préférable d’éviter les majuscules (pour les risques d’erreur) et les caractères accentués (pour des questions d’encodage) dans les noms d’objets.

De même, il faut essayer de trouver un équilibre entre clarté du nom (comprendre à quoi sert l’objet, ce qu’il contient) et sa longueur. Par exemple, on préfèrera comme nom d’objet taille_conj1 à taille_du_conjoint_numero_1 (trop long) ou à t1 (pas assez explicite).

Quand on assigne une nouvelle valeur à un objet déjà existant, la valeur précédente est perdue. Les objets n’ont pas de mémoire.

De la même manière, assigner un objet à un autre ne crée pas de “lien” entre les deux. Cela copie juste la valeur de l’objet de droite dans celui de gauche.

Les objets peuvent contenir tout un tas d’informations, comme des nombres, ou des chaînes de caractères (du texte), qu’on délimite avec des guillemets simples ou doubles (' ou ").

Pour lister les objets de l’environnement courant, il faut utiliser la fonction ls().

R va accumuler en mémoire un certain nombre d’objets (listes, vecteurs, fonctions…) Pour tous les effacer pour repartir d’une mémoire vierge (au lancement d’un script par exemple), on peut utiliser la commande ‘rm(list = ls())’, suivie de ‘gc()’

On peut aussi cliquer sur l’icône “balai” en haut à droite de la fenêtre de l’environnement, pour vider les éléments en mémoire.

La fonction rm (remove) permet d’effacer l’objet de son choix.

La commande rm(list = ls()) détruit les pointeurs vers les morceaux de mémoire utilisés. La commande gc() permet ensuite de libérer manuellement la mémoire occupée par des objets inutilisés (à lancer après la suppression d’un objet volumineux).

Le but principal de gc() est de montrer un rapport sur l’utilisation de la mémoire. Comme effet secondaire, l’appel de gc (garbage collection) déclenche le processus de collecte des déchets, en effaçant la mémoire que R n’utilise plus.

ls()

# Effacer ce qui se trouve dans l'espace de travail
rm(list = ls())

gc()

La console permet aussi d’acccéder à l’historique des commandes (Menu History).

1.2.8 Ne pas sauvegarder le fichier “.RData” - Désactiver la sauvegarde de l’espace de travail

Il est recommandé de configurer RStudio pour ne pas sauvegarder l’espace de travail (workspace), c’est-à-dire l’ensemble des objets qui existent actuellement dans l’environnement, dans un fichier nommé .RData.

Menu R-studio → Tools → Global Options → General, partie Workspace → Save workspace to .RData on exit : mettre Never

Il est déconseillé d’enregistrer l’environnement de session, avec notamment la présence de tables.

Il est également recommandé de désactiver la restauration automatique de ces fichiers. Pour cela il faut aller dans le menu Tools > Global Options, dans l’onglet General, partie Workspace décocher la case Restore .RData into workspace at startup.

Options d’enregistrement de l’espace de travail
Options d’enregistrement de l’espace de travail

1.2.9 Parenthèses et autres

Dans RStudio, les caractères marchant par paires (parenthèses, guillemets, accolades, crochets) sont automatisés. En sélectionnant du code et en appuyant sur le caractère ouvrant correspondant, RStudio place automatiquement les caractères au début et à la fin du code sélectionné. En plaçant le curseur après un caractère de ce type (par exemple une parenthèse fermante), RStudio va surligner l’autre caractère correspondant (par exemple la parenthèse ouvrante correspondante), permettant de visualiser les paires.

Modifier la couleur des parenthèses pour faciliter la lecture du code.

Aller dans le même menu “Tools” (Outils) > “Global Options” (Options globales).
Sélectionner “Code” dans la barre latérale de gauche, puis choisir l’onglet “Display” (Afficher).
Cocher l’option “Use rainbow parentheses” (Parenthèses arc-en-ciel) en bas pour colorer les parenthèses en fonction de leur niveau d’imbrication.
Cela changera la couleur des parenthèses, des accolades et des crochets pour qu’ils correspondent entre eux.

Cocher “Highlight R function calls” (Mettre en évidence les appels de fonction R) pour colorer les noms de fonction.

1.2.10 Encodage

Dans Tools > Global options > Code > Saving, choisir le Default text encoding UTF-8 pour les caractères spéciaux. Sans encoding UTF-8, les caractères spéciaux (accents…) risquent d’être formatés de manière peu compréhensible

1.2.11 Chemins

Les anti-slash windows (\) doivent être remplacés par des slash(/).

1.2.12 Choix de l’affichage du séparateur décimal

# séparateur décimal ,
options(OutDec = ",")

# options(encoding = "UTF-8", OutDec = ',')

à placer en début de programme

1.2.13 Travailler dans des projets

Il est recommandé de travailler avec les projets Rstudio. Un projet correspond à un dossier, dans lequel on regroupe l’ensemble des fichiers constitutifs d’une analyse (données, scripts, documentation).

Utiliser des projets procure plusieurs avantages :

  • RStudio lance automatiquement R dans le dossier du projet et facilite ainsi grandement l’accès aux fichiers de données à importer (plus besoin de taper le chemin d’accès complet). De même, si vous déplacez votre dossier sur votre disque, le projet continuera à fonctionner.
  • L’onglet Files de la zone en bas à droite de l’interface de RStudio permet de naviguer facilement dans les fichiers du projet.
  • Cela permet la portabilité : le répertoire de travail par défaut d’un projet est le répertoire où est ce projet.

Pour créer un projet : - Cliquer sur Project en haut à droite puis New Project. _ ou bien faire File > New Project.

On peut créer un projet RStudio :

  1. Dans un nouveau dossier (New Directory) :
    • Pour un projet tout nouveau.
  2. Dans un dossier existant (Existing Directory) :
    • Pour organiser des codes existants sous forme de projet.

L’étape d’après consiste à créer ou sélectionner le dossier, cocher la case en bas Open in new session, puis cliquer sur Create project.

La création d’un projet RStudio se traduit par :

  • un fichier .Rproj est créé dans le dossier principal. Ce fichier sert à deux choses :
    • Il centralise les options du projet ;
    • Il sert de raccourci pour ouvrir le projet.
  • le projet est chargé dans RStudio.

Une fois que le projet est créé, le nom du projet apparaît dans la barre de projet (tout en haut à droite).

Menu projets
Menu projets

On peut accéder aux derniers projets ouverts avec le raccourci en haut à droite. Cela permet de passer facilement d’un projet à un autre.

En local, si ce n’est pas le cas, dans la barre de menu en haut, cliquer sur View, puis sélectionner Show Toolbar pour afficher la barre d’outils.

À la création du projet, et chaque fois que vous l’ouvrirez, une nouvelle session R est lancée dans la fenêtre Console avec le dossier du projet comme répertoire de travail (on peut le vérifier avec la fonction getwd()), et l’onglet Files affiche les fichiers contenus dans ce dossier.

On peut aussi ouvrir un projet en sélectionnant File puis Open Project… et en allant sélectionner le fichier .Rproj qui se trouve dans le dossier du projet à ouvrir.

1.2.14 Travailler avec un projet RStudio

Lorsqu’on ouvre un projet RStudio (en double-cliquant sur le fichier .Rproj par exemple) :

  • Une nouvelle session R est ouverte ;
  • L’historique des commandes .Rhistory du projet est chargé (s’il existe) dans le cadre History ;
  • Le répertoire de travail working directory est par défaut le dossier-maître du projet (le dossier dans lequel se situe le fichier .Rproj).

Le répertoire de travail se situant à la racine du dossier créé, cela signifie que lors de l’importation, le chemin d’accès aux données sera automatiquement fixé à partir de cet endroit. De même, pour l’export, les fichiers seront également enregistrés à partir de la racine du dossier.

Les projets permettent de ne pas avoir à spécifier un chemin complet vers un fichier (comme D:\\...\\data\\donnees.xlsx) mais un chemin relatif au dossier du projet (juste data/donnees.xlsx si le fichier se trouve trouve dans un sous-dossier data.)

Il est conseillé pour chaque projet de créer une arborescence avec les répertoires ‘entree’, ‘pgm’, ‘sortie’.

library(fs)

#  menu File|New Project Exemple_projet

# pour créer un dossier
# dir.create("nouveau_dossier")
dir.create("entree")
dir.create("pgm")
dir.create("sortie")

On peut ensuite utiliser les chemins relatifs pour l’import et l’export.

# Chargement du chemin
chemin  <- "./entree/" 

# sauvegarde données
# Chargement du chemin 
chemin_sortie <- "./sortie/"

1.2.15 Script R - entête de section

On peut utiliser quatre tirets - (ou #) de suite pour créer un entête de section.

Par exemple :

# Chargement du fichier de données ---- 

Cela permet de passer rapidement d’une section à l’autre et de masquer des sections.

RStudio affiche alors dans la marge de gauche du script un petit triangle noir qui permet de replier ou déplier le contenu de la section :

Section de script dépliée
Section de script dépliée
Section de script repliée
Section de script repliée

De plus, en cliquant sur l’icône Show document outline (la plus à droite de la barre d’outils de la fenêtre du script), ou en utilisant le raccourci clavier Ctrl+Maj+O, RStudio affiche une “table des matières” automatiquement mise à jour qui liste les sections existantes et permet de naviguer facilement dans le script :

Liste dynamique des sections
Liste dynamique des sections

1.2.16 Forcer la NON utilisation de la notation scientifique

On préfére voir les nombres affichés dans leur forme décimale.

Avec la notation scientifique;
0.00000000123 est affiché comme 1.23e-9 ;
1 230 000 est affiché comme 1.23e6.

Pour désactiver la notation scientifique, on va utiliser la valeur 100 au niveau de l’argument scipen :

  # options par défaut 
  # options("digits" = 7, scipen = 0) 
  # 1.520e+10 5.498e+08 7.220e-05 
options(scipen = 100) # Désactive la notation scientifique

options(scipen = n) : Si on définit scipen à une valeur n, R affichera les nombres en notation scientifique si leur exponentielle est supérieure ou égale à n ou inférieure ou égale à -n.

1.2.17 Répartir son travail entre plusieurs scripts

Pour éviter d’avoir un script trop long, il peut être intéressant d’isoler certaines parties d’un script, par exemple pour pouvoir les mutualiser. On peut alors répartir les étapes d’une analyse entre plusieurs scripts.

On peut par exempe regrouper des recodages dans un script à part (nommé, par exemple, recodages.R).

Pour exécuter ces recodages, on peut utiliser la fonction source : celle-ci prend en paramètre un nom de fichier .R, et quand on l’exécute elle va exécuter l’ensemble du code contenu dans ce fichier. Dans ce cas, rien ne s’affiche dans la console lors de l’exécution.

Ainsi, un début de script analyse.R pourra ressembler à ceci :

# Analyse des données Histoire de vie 2003

# Chargement des données ----

library(questionr)

data(hdv2003)
source("recodages.R")

# Analyse de l'âge ----

hist(hdv2003$age)

(...)


2 Les packages

Les packages sont des regroupements de fonctions permettant de faciliter l’écriture de scripts R.

2.1 Installation des packages

2.1.1 Installation standard

L’installation est à réaliser une seule fois pour une version de R donnée (comme on le fait pour installer un programme sur son PC). Installer un package va télécharger l’ensemble des fichiers nécessaires depuis l’une des machines du CRAN, puis installer tout ça sur le disque dur de l’ordinateur.

Il est recommandé d’utiliser la fonction install.packages pour installer et mettre à jour les packages.

Par exemple, pour installer le package questionr on peut exécuter la commande :

install.packages("questionr")

Lors de l’ouverture d’un script, RStudio tente de détecter automatiquement si les packages utilisés dans celui-ci sont installés. Dans le cas contraire, il affiche un bandeau en haut du script proposant d’installer les packages manquants : ▲ _package_ --- required but is not installed. Install Don't show again

À l’ouverture, si un package est manquant, on a la mention en haut du fichier de code indiquant qu’un package (ou plusieurs) n’est pas installé.

Il est préférable de ne pas installer des packages en cliquant sur le bouton Install du bandeau. En effet, garder la trace des installations dans un script avec l’utilisation de la fonction install.packages permet de faciliter les réinstallations de packages, lors de changements de version de R notamment.

Les packages R sont spécifiques à la version de R avec laquelle ils ont été installés. La principale conséquence pratique de cette caractéristique est qu’il faut réinstaller les packages lors d’un changement de version de R.

Cette réinstallation est facilitée en utilisant un script installation_packages.R avec une ligne install.packages pour chaque package utilisé.

Il peut arriver qu’un programme génère une erreur parce qu’on utilise un package dans une version trop ancienne. Pour mettre à jour un package, il suffit de le réinstaller avec install.packages.

Des problèmes d’installation ?

Ce n’est pas parce que vous avez des messages/ des warnings/ du rouge dans votre console que vous avez eu un problème d’installation. Les problèmes d’installation sont relativement souvent liés à un problème dans l’installation des dépendances. Une dépendance est un package dont a besoin le package qu’on est en train d’installer et qui peut donc s’installer en même temps.
Dans ce cas cela peut être une bonne idée d’installer la dépendance “qui coince” en premier, puis retenter l’installation du package.

2.1.2 Installer une version plus récente que la version compilée (le fichier binaire)

L’argument type de install.packages permet de choisir une méthode alternative à la valeur par défaut, "win.binary". Utiliser type = "source" permet par exemple de récupérer une version plus récente d’un package.

install.packages("nom_du_package", type = "source")

2.1.3 Installer depuis un fichier

Il est aussi possible d’installer un package depuis une archive compressée (fichiers “.zip” pour les binaires Windows ou “.tar.gz” pour les packages source). Pour indiquer que la source est un fichier local, il est obligatoire de spécifier repos = NULL. repos (pour repository) correspond au dépôt, au lieu de stockage des packages.

install.packages("chemin/en/local/package1_x.y.z.zip", repos = NULL)

Si le fichier est de type “source” (extension .tar.gz), Rtools peut s’avérer nécessaire.

2.1.4 Modifier la configuration pour pouvoir installer les packages via internet

R sur son poste local

En cas par exemple de messages de style :

> install.packages("haven")
Warning in install.packages :
  impossible d'accéder à l'index de l'entrepôt https://cran.rstudio.com/src/contrib:
  impossible d'ouvrir l'URL 'https://cran.rstudio.com/src/contrib/PACKAGES'

Préciser l’adresse du proxy permettant l’accès à internet, pour télécharger les packages.

Mettre cette adresse (“…proxy…:8080”) dans les variables d’environnement http_proxy et https_proxy.

Ouvrir le fichier ~/.Renviron avec la commande :

file.edit("~/.Renviron")

Indiquer les lignes suivantes dans le fichier ~/.Renviron (en remplaçant “…proxy…:8080” par l’adresse du proxy) :

http_proxy="...proxy...:8080"
https_proxy="...proxy...:8080"

Redémarrer RStudio après avoir modifié le fichier .Renviron pour que les changements prennent effet.

Tester la connexion à un site internet en mettant dans la console RStudio :

url <- "https://cran.r-project.org"
readLines(url, n = 10)

Pour l’installation des packages, on obtient alors

> install.packages("haven")
essai de l'URL 'https://cran.rstudio.com/bin/windows/contrib/4.3/haven_2.5.4.zip'
Content type 'application/zip' length 765340 bytes (747 KB)
downloaded 747 KB

le package ‘haven’ a été décompressé et les sommes MD5 ont été vérifiées avec succés

Les packages binaires téléchargés sont dans
    C:\Users\prenom.nom\AppData\Local\Temp\RtmpkjoM8c\downloaded_packages

Pour connaitre l’emplacement du fichier .Renviron, lancer dans la console RStudio

# Pour obtenir le chemin du répertoire personnel
Sys.getenv("HOME")

# [1] "C:/Users/prenom.nom/Documents"

Le fichier avec un point .Renviron sert pour la personnalisation utilisateur des variables d’environnement.

Il est aussi possible de modifier le fichier Renviron.site pour l’ensemble des utilisateurs du PC (par exemple en salle de formation).

Ouvrir le fichier R_HOME/etc/Renviron.site avec la commande suivante :

# ouvrir le fichier Renviron.site situé dans le dossier R_HOME/etc/ 
# où R_HOME est le dossier d'installation de R
file.edit(file.path(R.home(), "etc", "Renviron.site"))

Indiquer les lignes suivantes dans le fichier Renviron.site (en remplaçant “…proxy…:8080” par l’adresse du proxy) :

http_proxy="...proxy...:8080"
https_proxy="...proxy...:8080"

Redémarrer RStudio après avoir modifié le fichier Renviron.site pour que les changements prennent effet.

Le fichier Renviron.site est un fichier de configuration système qui permet de définir des variables d’environnement communes pour tous les utilisateurs de R du PC.

Pour connaitre l’emplacement du fichier Renviron.site, lancer dans la console RStudio

# Pour obtenir le répertoire d'installation de R (R_HOME)
R.home()
# [1] "C:/Program Files/R/R-x.y.z"

Le fichier de configuration d’environnement global Renviron.site se trouve généralement dans le dossier etc/ de l’installation R.

Le fichier .Renviron de l’utilisateur courant surcharge le fichier Renviron.site, qui est lu en premier lors du démarrage de R.

2.1.5 Installation de Rtools en cas de difficulté sur son poste de travail

R sur son poste local

Installer une seule fois Rtools (outils de compilation pour R) à partir de https://cran.r-project.org/bin/windows/Rtools. Par exemple https://cran.r-project.org/bin/windows/Rtools/rtools40.html

Il convient de télécharger rtools40-x86_64.exe vers D:\user\R\packages puis de lancer rtools40-x86_64.exe. Comme répertoire d’installation, choisir D:\user\R\rtools40. Décocher l’ensemble des options par défaut, notamment les options qui écrivent dans le registre.

Après l’installation, mettre la location des outils make (bash, make, etc) dans le PATH.

write('PATH = "${RTOOLS40_HOME}\\usr\\bin;${PATH}"', file = "~/.Renviron", append = TRUE)

Redémarrer R et verifier que make peut être trouvé, ce qui devrait montrer le chemin (path) de l’installation de Rtools.

Sys.which("make")

donne “D:\user\R\rtools40\usr\bin\make.exe”,
avant on avait par exemple “C:\R\rtools40\usr\bin\make.exe”

2.1.6 Avoir des informations sur les packages installés

# pour connaitre les chemins vers lesquels pointe R pour chercher des packages installés
.libPaths()
# "/var/data/nfs/CERISE/00-Espace-Personnel/prenom.nom/R/x86_64-pc-linux-gnu-library/4.4"

# éléments du répertoire :
list.files("~/CERISE/00-Espace-Personnel/prenom.nom/R/x86_64-pc-linux-gnu-library/4.4")

View(installed.packages()) # affiche tous les packages installés 

row.names(x = installed.packages(priority = c("base", "recommended")))
row.names(x = installed.packages(
                        lib.loc = c("~/CERISE/00-Espace-Personnel/prenom.nom/R/x86_64-pc-linux-gnu-library/4.4")))

2.1.7 Installation de packages sur Cerise avec difficulté

Les dernières versions de certains packages ne peuvent parfois pas s’installer sur CERISE à cause de librairies du système d’exploitation absentes ou anciennes. Parfois aussi (mais pas toujours), installer une version précédente du package permet de contourner ce problème.

Voir le guide Installation de packages https://orion.agriculture/confluence/display/CER/Installation+de+packages

Il faut d’abord installer remotes :

install.packages("remotes")

# Tidyverse
remotes::install_version("tidyverse", "1.3.0")

# arrow
remotes::install_version("arrow", version = "3.0.0")

# btb
remotes::install_version("btb", version = "0.1.30.3")

# COGugaison
# Téléchargement de https://github.com/antuki/COGugaison/archive/refs/heads/master.zip
 
# → COGugaison-master.zip, on dézippe on enlève "-master", on re-zippe puis on upload dans Cerise.

devtools::install_local("/mon/chemin/vers/le/COGugaison.zip", INSTALL_opts = c("--no-lock"))

# FactoMineR
remotes::install_version("nloptr", version = "1.2.0")
remotes::install_version("FactoMineR", version = "2.0")

# Hmisc
remotes::install_version("Hmisc", version = "4.6-0")

# sf
remotes::install_version("sf", version = "0.9-0")

#raster
remotes::install_version("raster", version = "3.0-12")

# gstat
remotes::install_version("gstat", version = "2.0-6")

Installation de versions plus anciennes, en précisant un numéro de version

remotes::install_version("sf", ">= 0.7-0", dependencies = TRUE, type = "source")
# Warning: unable to access index for repository https://forge.agriculture.rie.gouv.fr/artifactory/ssp-cran-prod-local/src/contrib:
#   cannot open URL 'https://forge.agriculture.rie.gouv.fr/artifactory/ssp-cran-prod-local/src/contrib/PACKAGES'
# Downloading package from url: https://forge.agriculture.rie.gouv.fr/artifactory/cran-r-prod-remote/src/contrib/sf_1.0-14.tar.gz

Exemple d’installation d’une ancienne version de package en plusieurs étapes

# suppression d'un package
remove.packages(pkgs = "cli", 
                lib = c("~/CERISE/00-Espace-Personnel/prenom.nom/R/x86_64-pc-linux-gnu-library/4.4"))

# suppression de l'ensemble des packages pour repartir de zéro
remove.packages(pkgs = row.names(
  x = installed.packages(lib.loc = c("~/CERISE/00-Espace-Personnel/prenom.nom/R/x86_64-pc-linux-gnu-library/4.4"))),
                lib = c("~/CERISE/00-Espace-Personnel/prenom.nom/R/x86_64-pc-linux-gnu-library/4.4"))

row.names(x = installed.packages(
  lib.loc = c("~/CERISE/00-Espace-Personnel/prenom.nom/R/x86_64-pc-linux-gnu-library/4.4")))
# NULL
  
# install.packages("tidyverse") 
# ERROR: dependency ‘ragg’ is not available for package ‘tidyverse’
# * removing ‘/var/data/nfs/CERISE/00-Espace-Personnel/prenom.nom/R/x86_64-pc-linux-gnu-library/4.4/tidyverse’
# Warning in install.packages :
#   installation of package ‘tidyverse’ had non-zero exit status

# installation d'une version plus ancienne tidyverse_1.3.2.tar.gz   2022-07-18
# https://cran.r-project.org/src/contrib/Archive/tidyverse/
# installation packages préalables
install.packages(c("broom", "dbplyr", "dtplyr", "googledrive", "googlesheets4", "httr", 
                    "modelr", "reprex", "rvest"))
install.packages(
  "~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R/Christophe/packages/tidyverse_1.3.2.tar.gz",
                 repos = NULL, type = "source")
# Error in loadNamespace(j <- i[[1L]], c(lib.loc, .libPaths()), versionCheck = vI[[j]]) : 
# namespace ‘forcats’ 0.5.0 is being loaded, but >= 0.5.1 is required
# namespace ‘ggplot2’ 3.3.2 is being loaded, but >= 3.3.5 is required
# namespace ‘haven’ 2.3.1 is being loaded, but >= 2.4.3 is required
# namespace ‘hms’ 0.5.3 is already loaded, but >= 1.1.1 is required
# namespace ‘jsonlite’ 1.7.1 is being loaded, but >= 1.7.2 is required
# namespace ‘lubridate’ 1.7.9 is being loaded, but >= 1.8.0 is required
# namespace ‘readr’ 1.3.1 is being loaded, but >= 2.1.1 is required
# namespace ‘xml2’ 1.3.2 is already loaded, but >= 1.3.3 is required
install.packages("forcats")
install.packages("ggplot2")
install.packages("haven")
install.packages("hms")
install.packages("jsonlite")
install.packages("lubridate")
install.packages("readr")
install.packages("xml2")
install.packages("readr")
install.packages(
  "~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R/Christophe/packages/tidyverse_1.3.2.tar.gz", 
                 repos = NULL, type = "source")
# * DONE (tidyverse)

2.2 Utilisation des packages

Une fois le package installé, il faut le “charger” avant de pouvoir utiliser les fonctions qu’il propose. En début du code, on commence par charger le package nom_du_package avec la commande library(nom_du_package). Le chargement consiste à indiquer à R que l’on souhaite utiliser le package dans la session courante. C’est une opération qu’il faut réaliser à chaque fois que l’on ouvre une session R.

Ainsi, on regroupe en général en début de script toute une série d’appels à library qui permettent de charger tous les packages utilisés dans le script.

On peut utiliser nom_package::nom_fonction pour préciser explicitement de quel package on utilise la fonction nom_fonction.
L’utilisation de l’opérateur :: permet de ne pas charger le package en mémoire, ce qui peut être utile si on utilise une seule fonction d’un package.
Cela permet aussi d’éviter les conflits de noms de fonctions entre les packages.

# Charger les packages nécessaires
# Packages souvent utilisés
library(dplyr)
library(openxlsx)
library(janitor) # pour nettoyer et préparer des données ('make_clean_names')
library(tidyr) # pour pivot
library(fs) # pour path

On peut voir si le package est chargé dans la fenêtre Services (en bas à droite).
Dans l’onglet Packages, on a la liste des package installés.
Si la case devant le nom du package est cochée, c’est qu’il est chargé en mémoire.



3 Import / export

3.1 Importer

La première étape d’une chaîne de traitement est d’accéder aux données à traiter. Il convient d’indiquer dans le script de préparation des données comment le fichier est arrivé, soit en codant les instructions de téléchargement, soit en ajoutant des commentaires qui permettront plus tard de reconstituer le jeu de données utilisé.

  • Il est conseillé d’importer uniquement les colonnes dont on a besoin. Dans le cas où on ne sait pas quelles sont les colonnes dont on a besoin, il est conseillé de commencer par importer un petit nombre de lignes (par exemple 1 000 ou 10 000) afin d’étudier les données et de choisir ensuite les colonnes à importer.

  • Il est conseillé de nettoyer les noms de colonnes après l’import. janitor::make_clean_names permet notamment d’obtenir des noms de variables propres et cohérents : tout en minuscules, sans espaces ni caractères spéciaux. Elle remplace les caractères non autorisés par des underscores (traits de soulignement _).

df <- tibble::as_tibble(df, .name_repair = janitor::make_clean_names)

Il faut indiquer le chemin vers le fichier qu’on souhaite importer entre quotes (““).

Utiliser fs::path() pour construire un chemin de fichier. Cette fonction est utilisée pour combiner plusieurs composants du chemin de fichier, tels que les répertoires et le nom du fichier, en un seul chemin de fichier complet.

# Charger le package
library(fs)
# Gestion des extensions en utilisant le paramètre .ext.
chemin <- path("dossier1", "dossier2", "fichier", .ext = "xlsx")

repertoire_ra_2020 <- "CERISE/03-Espace-de-Diffusion/030_Structures_exploitations"
fichier_ra_exploit_2020 <- "RA2020_EXPLOITATIONS_240112.rds"

chemin_fichier <- path(repertoire_ra_2020, fichier_ra_exploit_2020)

exploit_2020 <- readRDS(file = chemin_fichier)

Le package fs offre des fonctionnalités pour manipuler les fichiers et les répertoires, comme : dir_ls() : Lister les fichiers dans un répertoire. file_move() : Déplacer des fichiers. file_copy() : Copier des fichiers. file_delete() : Supprimer des fichiers. dir_create() : Créer des répertoires.

3.1.1 Importer des fichiers RDS

library(dplyr)
library(janitor) # pour 'make_clean_names' pour standardiser la syntaxe des noms de colonnes
library(fs) # pour path

# import fichiers
chemin_geographie <- "~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R/
Christophe/Geographie/sortie/"

fichier_geographie_dep <- "dep_reg_lib.RDS"
fichier_geographie_reg <- "reg_lib.RDS"

geographie_dep <- readRDS(file = path(chemin_geographie, fichier_geographie_dep)) |> 
  as_tibble(.name_repair = make_clean_names)

geographie_reg <- readRDS(file = path(chemin_geographie, fichier_geographie_reg)) |> 
  as_tibble(.name_repair = make_clean_names)

# Chargement des données
# recensement agricole 2020 PDL DONNEES INDIVIDUELLES
chemin_pdl_20 <- "S:/Fichiers source/RA 2020_déf/BASES_RA2020_data_def_15avril2022/RDS/"
annee_ra_2020            <- 2020

exploit_pdl_2020 <- readRDS(file = path(chemin_pdl_20,
                                        "RA2020_EXPLOITATIONS_R52_220415_modif_27juin2022.rds")) |> 
  as_tibble(.name_repair = make_clean_names)

pa_pdl_2020 <- readRDS(file = path(chemin_pdl_20,"RA2020_PRODANIM_R52_220415.rds")) |> 
 as_tibble(.name_repair = make_clean_names)

mo_pdl_2020 <- readRDS(file = path(chemin_pdl_20,"RA2020_MO_CHEF_COEXPL_R52_220415.rds")) |> 
  as_tibble(.name_repair = make_clean_names)

# RA2020 DONNEES INDIVIDUELLES France
chemin_fr_20 <- "D:/user/RA2020/RA2020_source/RDS/"

exploit_dep_2020 <- readRDS(file = path(chemin_fr_20,"RA2020_EXPLOITATIONS_220415.rds")) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  filter(siege_dep %in% c("44", "49", "53", "72", "85"))

pa_fr_2020 <- readRDS(file = path(chemin_fr_20, "RA2020_PRODANIM_220415.rds")) |> 
 as_tibble(.name_repair = make_clean_names) 

3.1.2 Importer des fichiers xlsx

L’utilisateur souhaite importer dans R des données issues de tableurs (extensionxlsx).

  • Il est recommandé d’utiliser la fonction read.xlsx() du package openxlsx pour importer des fichiers xlsx.

Voici les principaux arguments et options de read.xlsx() :

Argument Valeur par défaut Fonction
xlsxFile Aucune Chemin d’accès vers un objet classeur ou une url vers un fichier xlsx à importer
sheet 1 Onglet à importer. Soit le nom de l’onglet, soit un numéro de l’onglet
startRow 1 Ligne à partir de laquelle les données sont importées. Les lignes vides en haut d’un fichier sont toujours ignorées, quelle que soit la valeur de startRow
colNames TRUE Si TRUE, la première ligne de données sera utilisée comme nom de colonnes
library(dplyr)
library(openxlsx)
library(janitor) # pour 'make_clean_names'
library(fs) # pour path

# import fichiers
chemin_entree <- "S:/ETUDES/En cours/memento_Christophe/entree/SAA/"
fichier_entree <- "cd2022-5_SAA_2021_provisoire_resultats_LIB.xlsx"

eff_volailles <- read.xlsx(xlsxFile = path(chemin_entree, fichier_entree), 
                         sheet = "Eff. volailles", colNames = TRUE, startRow = 1) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  filter(region %in% c("52  -  Pays de la Loire","98  -  Total France métropolitaine"))


chemin <- "S:/ETUDES/En cours/memento_Christophe/entree/AgenceBio/"
fichier_nat <- "Export Productions Bio - National.xlsx"
an <- "2021"

agri_bio_vegetal_fr <- read.xlsx(xlsxFile = path(chemin,fichier_nat), 
                                 sheet = "Productions végétales", colNames = TRUE, startRow = 1) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  filter(annee %in% c(an) & territoire %in% c("National","Pays de la Loire"))

# fichier annuel présent sur Cerise 
# 03-Espace-de-Diffusion/070_Production_lait/7020_Enqu_Annuelle_Laitiere/
EAL_2021_resultats_definitifs_septembre_2022

chemin_entree_2021 <- "~/CERISE/03-Espace-de-Diffusion/070_Production_lait/
7020_Enqu_Annuelle_Laitiere/EAL_2021_resultats_definitifs_septembre_2022/"

fichier_entree_liv_reg_dep <- "LIVRAISON_REGNDEP_2020_2021.xlsx"

laits_reg_dep_2021 <- read.xlsx(xlsxFile = path(chemin_entree_2021, fichier_entree_liv_reg_dep), 
                                sheet = "Sheet 1", colNames = TRUE, startRow = 1) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  mutate(reg_dep_prod = paste0(regnprod,sep = "_", depprod),
         dep_prod = substr(depprod, 2, 3))  

Pour récupérer le nom des différents onglets, on peut utiliser la fonction openxlsx::getSheetNames.

https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_tableurs.html#importer-un-fichier-xlsx-avec-le-package-openxlsx

3.1.3 Importer des fichiers xls

L’utilisateur souhaite importer dans R des données issues de tableurs (extension type xls).

  • Il est recommandé d’utiliser la fonction read_xls() du package readxl pour importer des fichiers xls.

Voici les principaux arguments et options de read_xls() :

Argument Valeur par défaut Fonction
path Aucune Chemin d’accès au fichier xls à importer
sheet NULL Onglet à importer Soit le nom de l’onglet, soit la position de l’onglet. Par défaut, sélectionne le premier onglet du fichier
col_names TRUE TRUE pour utiliser la première ligne comme noms de colonne, FALSE pour obtenir les noms par défaut ou un vecteur de caractères donnant un nom à chaque colonne
col_types NULL Préciser le type des colonnes. Si col_types = NULL, readxl essaie de deviner le type des colonnes. Voir ?readxl::read_xls pour l’usage de cette option
na “” Vecteur de chaînes de caractères à interpréter comme des valeurs manquantes
skip 0 Nombre de lignes à ignorer avant d’importer les données
n_max Inf Nombre maximum de lignes de données à lire
library(dplyr)
library(readxl)
library(fs) # pour path

# Chargement du chemin 
chemin <- "S:/ETUDES/En cours/memento_Christophe/entree/volailles_qualite/Qualite_volailles_2021/"
fichier <- "Abattages_signes_qualite_2021_modif.xls"

volailles_pdl_espece_siqo <- read_xls(path = path(chemin, fichier), 
                                      sheet = "res_nouvelle_region_modif", 
                                      skip = 9) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  filter(espece %in% c("siqo", "PAYS DE LA LOIRE"))
  • Définir le type des colonnes : le paramètre col_types permet de définir explicitement le type des colonnes et d’ignorer les colonnes qu’on ne souhaite pas importer. Pour cela, on passe au paramètre col_types un vecteur précisant le type parmi les possibilités suivantes :

    • "skip" : ignorer la colonne (qui ne sera pas importée) ;
    • "guess" : le type de la variable est devinée par rapport à ses modalités ;
    • "list" : crée une liste ;
    • "logical pour une variable booléenne, "numeric" pour une variable numérique, "date" pour une date et "text" pour une variable caractère.

    Le type de la variable sera appliqué aux colonnes dans l’ordre défini par le vecteur. Exemple : c("text","text","numeric","guess","skip","logical"). Dans le cas où on souhaite définir le même type pour toutes les colonnes, il suffit de préciser une seule fois le type attendu (exemple col_types = "text").

  • Gestion des NA : il est possible de préciser les valeurs qu’on souhaite considérer comme des NA avec le paramètre na. Par défaut, la valeur "" est considérée comme NA.

https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_tableurs.html#importer-un-fichier-xls-avec-le-package-readxl

3.1.4 Importer des fichiers ods

L’utilisateur souhaite importer dans R des données issues de tableurs (extension ods).

  • Il est recommandé d’utiliser la fonction read_ods du package readODS pour importer des fichiers ods.

Voici les principaux arguments et options de read_ods() :

Argument Valeur par défaut Fonction
path Aucune Le chemin du fichier ods à importer
sheet 1 Onglet à importer. Soit le nom de l’onglet, soit le numéro de l’onglet (utiliser de préférence le nom de l’onglet)
col_names TRUE Indique si la première ligne de l’onglet contient les noms des variables
col_types NULL NULL pour laisser R deviner le type des variables à partir de l’onglet ou se reporter à readr::type_convert pour spécifier le type des variables
na "" Vecteur donnant les chaîne de caractères interprétées comme des valeurs manquantes. Par défaut, read_ods convertit les cellules vides en données manquantes
skip 0 Le nombre de lignes du fichier de données à ignorer avant de commencer à importer les données
library(readODS)
library(fs) # pour path

# Chargement du 3ème onglet
mesDonnees <- readODS::read_ods(path = chemin_ods, sheet = "Sheet3", skip = 5)

# Chargement du chemin 
chemin_entree <- "S:/ETUDES/En cours/Fiche filière volailles/conjoncture volailles/2022_s1/entree/"
fichier_entree <- "COMEXOEUFS_France.ods"

oeufs <- read_ods(path = path(chemin_entree, fichier_entree), sheet = "Données", skip = 0)

# Chargement du chemin 
chemin  <- "~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R/Christophe/Volailles/entree/" 
fichier_cotation <- "Cotations_modif.ods"

# Import + ajout annee mois
vol_oeufs_cot <- read_ods(path = path(chemin, fichier_cotation), 
                   sheet = "VolaillesOeufsLapinsMensuel", skip = 2) |> 
  as_tibble(.name_repair = make_clean_names) 

3.1.5 Importer des fichiers csv

L’utilisateur souhaite importer dans R des données stockées sous forme de fichiers plats (formats .txt, .csv, .tsv).

Le package readr propose plusieurs fonctions adaptées pour importer des fichiers plats de taille limitée (moins de 1 Go) :

  • read_csv() : lecture d’un csv délimité par des virgules, avec un point comme marqueur décimal
  • read_csv2() : lecture d’un csv séparé par des points-virgules, avec une virgule comme marqueur décimal
  • read_delim() : fonction plus générale et paramétrable, pour lire des fichiers délimités.

Il faut charger le package readr pour utiliser ces fonctions :

library(readr)
library(dplyr)
library(readr)
library(janitor)
library(fs) # pour path

# Définition d'un opérateur `%not_in%` pour plus de clarté dans les filtres
`%not_in%` <- purrr::negate(`%in%`)

# source : https://agreste.agriculture.gouv.fr/agreste-web/disaron/COMPT0005_NRP/detail/
# Comptes régionaux de l'agriculture : productions et subventions sur les produits
# Fichier de données secrétisées FDS_COMPT0005_NRP.zip

# import fichiers

# Chargement du chemin 
chemin <- "S:/ETUDES/En cours/memento_Christophe/entree/volailles_abattage/ABATVOL_2021/"
chemin_entree <- "./entree/comptes_prod/"

fichier_entree <- "DIFFABATVOL_2021.csv"
fichier_entree_prod <- "FDS_COMPT0005_NRP_2020.txt"

volailles_pdl <- read_csv2(file = path(chemin, fichier_entree)) |>  
  as_tibble(.name_repair = make_clean_names) |> 
  filter(reg %in% c("52")) |>
  mutate(siret = as.character(siret))

# Encodage du fichier .csv trouvé avec la fonction guess_encoding du package 'readr'
guess_encoding(file = path(chemin_entree, fichier_entree_eff_avant_2010),
               n_max = 1000)

# A tibble: 1 × 2
#   encoding   confidence
#   <chr>           <dbl>
# 1 ISO-8859-1       0.77

# locale(encoding = )
# Encodage par défaut. Cela n'affecte que la façon dont le fichier est lu
# readr convertit toujours la sortie en UTF-8.
eff_volailles_avant_2010 <- read_csv2(file = path(chemin_entree, fichier_entree_eff_avant_2010),
                                      locale = locale(encoding = "ISO-8859-1")) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  filter(reg %not_in% c("01 : Guadeloupe","02 : Martinique","03 : Guyane","04 : La Réunion"),
         annee %in% 2000:2010)

# lecture d’un txt séparé par des points-virgules, avec un point comme marqueur décimal
comptes_prod <- read_delim(file = path(chemin_entree, fichier_entree_prod),
                           delim = ";",
                           locale = locale(decimal_mark = ".")) |>  
  as_tibble(.name_repair = make_clean_names) |> 
  rename(libelle_produits = libelle_produits_compt0005_nrp_dim1,
         libelle_indic_n027 = libelle_indicateurs_n027) 

La fonction read_delim() de readr permet d’importer les données d’un fichier csv.

read_csv(), read_csv2() et read_tsv() sont des implémentations pré-renseignées de read_delim pour lire des fichiers plats avec séparateurs (caractère délimiteur de colonne) , ; et tabulaire.

Chaque fonction dispose de plusieurs arguments, parmi lesquels :

  • col_names indique si la première ligne contient le nom des colonnes (TRUE par défaut)
  • col_types permet de spécifier manuellement le type des colonnes si readr ne les identifie pas correctement
  • na est un vecteur de chaînes de caractères indiquant les valeurs devant être considérées comme manquantes. Ce vecteur vaut c("", "NA") par défaut

Pour spécifier le type des colonnes, on peut préciser le type à utiliser par défaut puis faire la liste des variables ayant un type différent de cette valeur par défaut :

exploitations <- read_csv2("mes_donnees_exploitations.csv",
        col_types = cols(.default = col_character(), 
                         SAU = col_double(),
                         UGB = col_double()))

On spécifie le type des variables à l’aide des fonctions suivantes : - col_character() pour une variable de type caractère - col_double() pour une variable de type numérique (décimal) - col_integer() pour une variable de type entier - col_logical() pour une variable de type booléen (TRUE ou FALSE) - col_date() pour une variable de type date (jour, mois, année) - col_factor() pour une variable de type facteur (variable qualitative, avec un nombre prédéfini et limité de modalités)

https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_fichiers_plats.html

https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_fichiers_plats.html#utiliser-la-fonction-read_delim

3.1.6 Importer des fichiers SPSS (.sav)

L’utilisateur souhaite importer dans R des données stockées sous forme de fichiers SPSS.

La méthode recommandée est d’utiliser la fonction read_spss() du package haven.

# Charger les packages
library(haven)
library(fs) # pour path

# Importer un fichier SPSS
tableau <- haven::read_spss(path(chemin, fichier_entree))

# Importer un fichier SPSS
fichier_entree_mensuel <- "COLLECTE_TOUS_LAITS.sav"

laits_mensuel_1 <- haven::read_spss(path(chemin_entree_mensuel, fichier_entree_mensuel)) |> 
  as_tibble(.name_repair = make_clean_names) 

3.1.7 Importer des fichiers SAS

L’utilisateur souhaite importer dans R des données stockées sous forme de tables SAS.

La méthode recommandée est d’utiliser la fonction read_sas() du package haven.

# Charger le package haven
library(haven)

# Importer une table SAS.
# Utilisation de la table de passage des codes communes depuis 1943
# vers les codes communes en vigueur en 2022,
comdepuis1943_com2022 <- read_sas("S:/ETUDES/En cours/memento_Christophe/entree/geographie/geo2022/fcodcomm.sas7bdat")

3.1.8 Importer des objets RData

Parfois des données sont enregistrées au format RData. Ce format propre à R permet d’enregistrer plusieurs objets R, quel que soit leur type, dans un même fichier.

Pour enregistrer des objets, on utiliser la fonction save en lui fournissant la liste des objets à sauvegarder et le nom du fichier :

save(df, rp2018, tab, file = "fichier.RData")

Pour charger des objets préalablement enregistrés, utiliser load :

load("fichier.RData")

Les objets df, rp2018 et tab sont importés directement dans l’environnement en cours avec leur nom d’origine. Si d’autres objets du même nom existent déjà, ils sont écrasés sans avertissement.

3.1.9 Importer des fichiers avec en-tête sur 2 lignes

Dans le cas où les noms de colonnes sont répartis sur deux lignes d’en-tête au lieu d’une, on peut importer les données en concaténant le contenu des deux lignes d’en-tête.

# import ----
# Chargement du chemin 
chemin_entree <- "~/CERISE/03-Espace-de-Diffusion/060_Productions_viandes_oeufs/6060_Qualite_volailles/"

chemin_2023 <- "Qualite_volailles_2023/" 

fichier_entree_2023 <- "Abattages_signes_qualite_2023.xls"

entetes_vol_2023 <- read_xls(path = path(chemin_entree,
                                           chemin_2023,
                                           fichier_entree_2023), 
                             sheet = "res_nouvelle_region", 
                             skip = 23,
                             n_max = 5,
                             col_names = FALSE) |> 
  as_tibble(.name_repair = make_clean_names) 

# remplacer x3 à x9 par "Poulets (y c coquelets)"...
entetes_vol_2023_ligne1 <- entetes_vol_2023 |> 
  mutate(across(x3:x9, ~ "n11_Poulets"),
         across(x10:x16, ~ "n12_Chapons"),
         across(x17:x23, ~ "n13_Poules_reforme_oeufs_cons"),
         across(x24:x30, ~ "n14_Coqs_poules_reforme_reproducteurs"),
         across(x31:x37, ~ "n20_Dindes"),
         across(x38:x44, ~ "n30_Pintades"),
         across(x45:x51, ~ "n41_Canards_rotir"),
         across(x52:x58, ~ "n42_Canards_gras"),
         across(x59:x65, ~ "n51_Oies_rotir"),
         across(x66:x72, ~ "n52_Oies_grasses"),
         across(x73:x79, ~ "n80_Lapins"),
         across(x80:x86, ~ "n60_Pigeons"),
         across(x87:x93, ~ "n70_Cailles"))

# Dans le fichier excel, les noms de colonnes sont répartis sur deux lignes d’en-tête au lieu d’une. 
# L’objectif est d’importer les données de ce tableau en concaténant le contenu des deux lignes d’en-tête.
# On concatène le contenu des deux lignes d’en-tête à l’aide de la fonction paste()
# Remarque : la fonction slice() permet de filtrer une table par un numéro de ligne
entetes_vol_2023_2lignes <- paste(slice(entetes_vol_2023_ligne1, 1),
                                  slice(entetes_vol_2023, 3),
                                  sep = "_siqo_")

# On restreint les lignes à lire avec skip et n_max
# La fonction setNames() permet de renommer l’ensemble des colonnes d’un data frame 
#à partir d’un vecteur de nouveaux noms.
vol_2023 <- read_xls(path = path(chemin_entree,
                                   chemin_2023,
                                   fichier_entree_2023), 
                     sheet = "res_nouvelle_region", 
                     skip = 23,
                     n_max = 17,
                     col_names = FALSE) |> 
  setNames(entetes_vol_2023_2lignes) |> 
  as_tibble(.name_repair = make_clean_names) 

vol_2023 <- vol_2023 |> 
  rename(region=na_siqo_na_2) |> 
  select(-na_siqo_na,
         -starts_with("na_"))

# Supprimer les 4 premières lignes
# sélectionne toutes les lignes sauf les 4 premières.
# Le signe `-` devant `(1:4)` indique que nous voulons exclure ces lignes.
vol_2023 <- vol_2023  |> 
  slice(-(1:4))
# import ----
chemin_entree <- "./entree/conj_bulletin/"
fichier_entree <- "Aviculture.xls"

# On restreint les lignes à lire avec skip et n_max
vol <- read_xls(path = path(chemin_entree, fichier_entree), 
                sheet = "VolaillesOeufsLapins", 
                skip = 1054,
                n_max = 146,
                col_names = TRUE) |> 
  as_tibble(.name_repair = make_clean_names) 

str(vol)

# supprimer les 3 premières lignes 
# 4:n() crée un vecteur des indices à partir de la quatrième ligne jusqu’à la fin du dataframe.
# vol <- slice(vol, 4:n())

# Supprimer les 3 premières lignes
# sélectionne toutes les lignes sauf les 3 premières.
# Le signe `-` devant `(1:3)` indique que nous voulons exclure ces lignes.
vol <- vol  |> 
  slice(-(1:3))

# Supprimer les lignes où il n'y a que des NA 
# Supprimer les lignes avec des NA dans toutes les colonnes
# Supprimer les lignes où toutes les modalités sont NA
# avec remove_empty() du package janitor
vol <- vol |> 
  remove_empty(which = "rows")

# On renomme la modalité pour les poulets qui est sur 2 lignes
# Gallus (poulets, poules de
#         réforme et chapons)
vol2 <- vol |> 
  rename(especes = rubriques,
         code_especes = n) |> 
  mutate(especes = case_when(
    especes == "Gallus (poulets, poules de" ~ "Poulets",
    especes == "réforme et chapons)" ~ "Poulets", 
    .default = especes
  ))

# Remplacer les NA dans les colonnes désirées par la valeur de la cellule précédente 
# Pour gérer les cellules fusionnées d'excel en R, on utilise la fonction `fill` du package `tidyr`
# Cette fonction remplace les valeurs NA 
# par la valeur précédente (de la ligne précédente) dans la même colonne.
# Cette fonction remplit les valeurs `NA` avec la dernière valeur non-`NA` précédente.
# La fonction `fill` fonctionne de haut en bas par défaut (spécifié par `.direction = "down"`).
vol2 <- vol2 |> 
  fill(especes,
       .direction = "down") |> 
  select(-unite) 

names(vol2)

3.1.10 Importer des fichiers de façon récurrente

Parfois une fonction n’a pas pour objectif de renvoyer un résultat mais d’accomplir une action (générer un graphique, afficher un message, enregistrer un fichier…), comme ici importer des fichiers.
Dans ce cas la fonction peut ne pas inclure d’instruction return().

library(dplyr)
library(openxlsx)
library(janitor) # pour 'make_clean_names'
library(readr)
library(purrr) # pour map

# import fichiers
chemin_entree_avant_2010 <- "~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R/Volailles/entree/saa/"

# fichier_entree_eff_2000 <- "FDS_SAANR_8_2000.csv"

# Liste des années de 2000 à 2010
annees <- 2000:2010

# Ce code utilise la fonction map() de la bibliothèque purrr pour itérer sur la liste annees
# Utilisation de map pour appliquer une fonction à chaque année
# La fonction map permet d'appliquer une fonction à chaque élément d'une liste et de renvoyer une nouvelle liste résultante.
list_eff_annees <- map(annees, function(annee) {
  fichier_entree_eff_annee <- path("FDS_SAANR_8_", annee, ".csv")

  df <- read_csv2(file = path(chemin_entree_avant_2010, fichier_entree_eff_annee)) |> 
    as_tibble(.name_repair = make_clean_names)
  
  return(df)
})

# Combiner la liste des dataframes en un seul
# Regrouper l'ensemble des lignes dans un seul dataframe
# list_rbind() est une fonction de purrr qui combine les éléments d'une liste en une seule liste.
# list_rbind() accepte uniquement une liste en entrée.
eff_volailles_2000_2010 <- list_rbind(list_eff_annees)

# En R, si une fonction ne contient pas d’instruction return(), 
# elle renvoie par défaut la dernière expression évaluée dans la fonction. 
# Cependant, l’utilisation de return() rend le code plus lisible et explicite, 
# en indiquant clairement quel est le résultat renvoyé par la fonction.
# fichiers agrégés ----
chemin_entree_agreg <- "S:/Fichiers source/Agence_BIO/2023/"

# Affectation des fichiers d'entrée en fonction des codes de région
fichier_entree_11 <- "Export Productions Bio - Région - Île-de-France.xlsx"
fichier_entree_24 <- "Export Productions Bio - Région - Centre-Val de Loire.xlsx"
fichier_entree_27 <- "Export Productions Bio - Région - Bourgogne-Franche-Comté.xlsx"
fichier_entree_28 <- "Export Productions Bio - Région - Normandie.xlsx"
fichier_entree_32 <- "Export Productions Bio - Région - Hauts-de-France.xlsx"
fichier_entree_44 <- "Export Productions Bio - Région - Grand Est.xlsx"
fichier_entree_52 <- "Export Productions Bio - Région - Pays de la Loire.xlsx"
fichier_entree_53 <- "Export Productions Bio - Région - Bretagne.xlsx"
fichier_entree_75 <- "Export Productions Bio - Région - Nouvelle-Aquitaine.xlsx"
fichier_entree_76 <- "Export Productions Bio - Région - Occitanie.xlsx"
fichier_entree_84 <- "Export Productions Bio - Région - Auvergne-Rhône-Alpes.xlsx"
fichier_entree_93 <- "Export Productions Bio - Région - Provence-Alpes-Côte d'Azur.xlsx"
fichier_entree_94 <- "Export Productions Bio - Région - Corse.xlsx"

# fonction pour l'ensemble des fichiers régionaux
# Créer une liste des fichiers d'entrée
fichiers_entree <- list(
  fichier_entree_11, fichier_entree_24, fichier_entree_27,
  fichier_entree_28, fichier_entree_32, fichier_entree_44,
  fichier_entree_52, fichier_entree_53, fichier_entree_75,
  fichier_entree_76, fichier_entree_84, fichier_entree_93,
  fichier_entree_94
)

# Créer une liste des codes région
codes_region <- c(11, 24, 27, 28, 32, 44, 52, 53, 75, 76, 84, 93, 94)

# Créer une fonction pour lire et transformer les données
# Ajouter la colonne 'reg' avec la valeur de 'territoire' pour les lignes où 'echelle_geographique' vaut "Régional"
# Remplir les valeurs manquantes de 'reg' avec la dernière valeur non manquante
# Supprimer les lignes où 'echelle_geographique' vaut "Régional"
# Renommer la colonne 'territoire' en 'dep'
# Supprimer la colonne 'echelle_geographique'
lire_et_transformer <- function(fichier, feuille) {
  read.xlsx(
    xlsxFile = path(chemin_entree_agreg, fichier),
    sheet = feuille,
    colNames = TRUE, startRow = 1
  ) |>
    as_tibble(.name_repair = make_clean_names) |>
    mutate(
      reg = if_else(echelle_geographique == "Régional", territoire, NA),
      .after = echelle_geographique
    ) |>
    fill(reg) |>
    filter(echelle_geographique != "Régional") |>
    rename(dep = territoire) |>
    select(-echelle_geographique)
}

# Lire et transformer les fichiers avec la fonction map de purrr
bio_anim_list <- map(
  fichiers_entree,
  \(x) lire_et_transformer(x, feuille = "Productions animales")
)

# Nommer les éléments de la liste avec les codes région
names(bio_anim_list) <- paste0("bio_anim_", codes_region)

# Afficher les premières lignes pour vérifier
map(bio_anim_list, head)

# On peut accéder aux données d'une région spécifique en utilisant son code,
# par exemple : bio_anim_list$bio_anim_11 pour l'Île-de-France.

# Regrouper l'ensemble des lignes dans un seul dataframe
# list_rbind() est une fonction de purrr qui combine les éléments d'une liste en une seule liste.
# list_rbind() accepte uniquement une liste en entrée.
bio_anim_fm <- list_rbind(bio_anim_list) |>
  # ajout colonne numéro de ligne
  # creation d'une variable identifiant reprenant le numéro de ligne
  mutate(ident_anim1 = row_number(), .before = 1)

# Traitement de tous les fichiers en une seule étape
# On souhaite combiner les résultats verticalement, en empilant les lignes de chaque tableau de données.
bio_anim_fm2 <- map(
  fichiers_entree,
  \(x) lire_et_transformer(x, feuille = "Productions animales")
) |>
  list_rbind() |>
  mutate(ident_anim1 = row_number(), .before = 1)

# comparaison des deux méthodes
identical(bio_anim_fm, bio_anim_fm2)

3.2 Exporter

3.2.1 Exporter dans des fichiers RDS

Pour exporter un fichier au format rds (le format R), on peut utiliser la fonction saveRDS : saveRDS(x, “mon_fichier.rds”)

# Chargement du chemin 
chemin_sortie <- "S:/ETUDES/En cours/Fiche filière volailles/conjoncture volailles/2022_s1/sortie/"

fichier_sortie_rds <- "oeufs_cot_det_2022_2021_an_mois.RDS"

saveRDS(object = oeufs_cot_det_2022_2021_an_mois, file = path(chemin_sortie,fichier_sortie_rds))

3.2.2 Exporter dans des fichiers xlsx

Pour exporter un tableau de données dans R vers un fichier au format xlsx, on peut utiliser write.xlsx du package openxlsx. Les arguments principaux de write.xlsx sont :
data : le tableau de données à exporter
file : le chemin et le nom du fichier de sortie, y compris l’extension .xlsx.
sheetName (facultatif) :le nom de la feuille Excel dans laquelle écrire les données. Par défaut, “Sheet1”.
overwrite (facultatif) : indique s’il faut écraser un fichier existant. Par défaut, FALSE.
col.names (facultatif) : si TRUE, les noms de colonnes sont exportés. Par défaut, TRUE.

On peut exporter plusieurs tableaux de données dans un même fichier Excel en utilisant write.xlsx avec une liste de tableaux de données. Écrire une liste de data frames dans un fichier xlsx dans des feuilles individuelles en utilisant les noms de la liste comme noms de feuilles.

# Export simple
write.xlsx(donnees, "export/fichier.xlsx")

# Export multi-feuilles
# Liste de tableaux de données
liste_tableaux <- list(
  'table1' = df1,
  'table2' = df2
)

# Exporter la liste de tableaux de données dans un fichier Excel avec plusieurs feuilles
write.xlsx(liste_tableaux, file = "fichier_multi_feuilles.xlsx")
library(openxlsx)
library(fs) # pour path

# export 
# Chargement du chemin 
chemin_sortie <- "S:/ETUDES/En cours/memento_Christophe/sortie/"

fichier_sortie <- "abattoirs_volaille_pdl_2021.xlsx"

write.xlsx(volailles_sum,
           file = path(chemin_sortie, fichier_sortie),
           sheetName = "abattoirs_volailles")

# export dans plusieurs feuilles
chemin_sortie <- "S:/ETUDES/En cours/memento_Christophe/sortie/"

fichier_sortie <- "agri_bio_2021.xlsx"

# définir les noms de feuilles pour chaque tableau (data frame)
liste_tableaux <- list('tab1' = agri_bio_vegetal_tab1_final, 
                       'tab2' = agri_bio_vegetal_tab2_final,
                       'tab3' = agri_bio_animal_tab3_final)

# exporter chaque tableau vers des feuilles séparées dans le même fichier Excel
write.xlsx(liste_tableaux, file = path(chemin_sortie, fichier_sortie))

https://www.statology.org/r-export-to-excel-multiple-sheets/

3.2.3 Exporter dans des fichiers ods

Le package readODS permet d’exporter une data.frame dans un fichier au format ods :

library(readODS)
write_ods(donnees, 
          path = "chemin/fichier.ods",
          sheet = "nom_onglet",
          append = TRUE)

3.2.4 Exporter dans des fichiers csv

Pour exporter un tableau de données dans R vers un fichier au format texte délimité csv (comma separated values), on peut utiliser write_csv ou write_csv2 avec séparateur de champ virgule ou point-virgule respectivement.

Il est possible d’exporter les données dans les formats de données « plats » grâce à la fonction write_delim du package readr. Une syntaxe standard est la suivante :

# Avec point-virgule comme séparateur de champ
write_csv2(donnees, "export/donnees.csv")

# Configuration avancée
write_delim(donnees,                         # La table de donnée à exporter 
            path = "fichier_export.csv",     # Le chemin et le nom du fichier à exporter
            delim = ";",                     # Le séparateur de champ (le point-virgule ici)
            fileEncoding = "UTF-8",          # L'encodage du fichier
            row.names = FALSE,               # Indique que les numéros de ligne ne seront pas exportés
            na = "") # Les valeurs manquantes notées NA dans R seront remplacées par un champ vide


4 Manipulation de variables (colonnes)

4.1 Exploration des variables

La fonction names retourne les noms des colonnes du tableau, c’est-à-dire la liste des variables.

La fonction str renvoie un descriptif détaillé de la structure du tableau. Elle liste les différentes variables, indique leur type et affiche les premières valeurs.

À noter que sous RStudio, on peut afficher à tout moment la structure d’un objet en cliquant sur l’icône de triangle sur fond bleu à gauche du nom de l’objet dans l’onglet Environment.

Structure d’un objet
Structure d’un objet

La librairie dplyr propose une fonction glimpse qui permet de visualiser rapidement et de manière condensée le contenu d’un tableau de données.
Cette fonction permet de connaître le nombre de lignes et colonnes, le type données dans les colonnes, ainsi que les premières observations.

La fonction unique supprime toutes les valeurs en double dans un vecteur, qu’il s’agisse de nombres ou de chaînes de caractères.

La fonction sort permet de trier le tableau selon la valeur de l’effectif.

nrow() et ncol() donnent respectivement le nombre de lignes et de colonnes de la table. On peut aussi obtenir les deux informations en une seule instruction avec dim().

La fonction summary génère des statistiques résumant les données contenues dans les colonnes. Elle est souvent utilisée pour les variables numériques, mais elle peut aussi être utilisée pour les variables catégorielles. Pour les colonnes numériques, on a accès à des informations clés sur la distribution : minimum/maximum, quartiles, médiane et moyenne. Cela permet de contrôler les ordres de grandeur et la cohérence des informations. La fonction summary fournit le nombre de valeurs manquantes s’il y en a.

Pour les colonnes texte, la fonction table permet de récupérer le nombre d’occurrences de chaque valeur distincte.

library(dplyr) # pour glimpse
library(labelled) # pour look_for
library(questionr) # pour describe
library(fs) # pour path

# noms des variables
names(exploit_2020)

# liste des variables séparées par une virgule
paste0(names(exploit_2020), collapse = ",")

# liste des variables séparées par une virgule (et un espace)
paste0(names(exploit_2020), collapse = ", ")

# nom, type et extrait des variables
str(exploit_2020)
str(exploit_2020, list.len = Inf)

glimpse(exploit_2020)
look_for(exploit_2020)
describe(exploit_2020)

# modalités d'une variable caractère ou numérique
unique(exploit_2020$siege_dep)

# modalités triées d'une variable caractère ou numérique
sort(unique(exploit_2020$siege_dep))

# modalités d'une variable séparées par une virgule
paste0(unique(exploit_2020$siege_dep), collapse = ",")

# modalités d'une variable (entre guillemets) séparées par une virgule
# Lorsqu'on imprime le résultat dans la console, il affiche le caractère d'échappement \ devant "
# pour indiquer que le guillemet fait partie de la chaîne. C’est pourquoi on voit \"NR01\" au lieu de "NR01".
# pour voir le résultat sans le caractère d'échappement \, 
#on peut utiliser la fonction cat() pour imprimer le résultat.
cat(paste0(unique(eff_volailles_2000_2010$dep), collapse = '","'))

# modalités d'un facteur
levels(exploit_2020$siege_reg)

# 6 premières lignes
head(volailles_pdl)
# Les 4 premières lignes du data.frame
head(volailles_pdl, 4)

# 6 dernières lignes
tail(volailles_pdl)

# Nombre d'observations
nrow(volailles_pdl)
# Nombre de variables
ncol(volailles_pdl)
# Nombre d'observations et de variables (nombre de lignes et de colonnes)
dim(volailles_pdl)

# Description des variables avec des statistiques descriptives
summary(volailles_pdl)

# Fréquence des modalités d'une variable
# Nombre d'occurences des valeurs uniques dans la colonne dep
table(volailles_pdl$dep) |> 
  sort(decreasing = TRUE) # tri décroissant

# Valeurs manquantes
# On retourne toutes les lignes du dataframe qui contiennent au moins une valeur manquante (NA)
# dans n'importe quelle colonne.
# La commande filtre les lignes avec au moins une valeur NA
# if_any() # Si au moins une colonne..
volailles_pdl  |> 
 filter(if_any(everything(), is.na))

Afficher les valeurs et manipuler les variables

  • Pour afficher la table, plusieurs façons : “clic” dans l’environnement Rstudio, View(base), print(base), base.

  • Pour accéder à une variable : fonction pull()

Par exemple :

str(pull(base, DEP))

4.2 Création de variables

On assigne un contenu à une variable au moyen de <- La flèche d’assignation peut être écrite avec le raccourci clavier alt + “-” (tiret du 6 ou signe ‘moins’ du pavé numérique).

ATTENTION : un nom de variable ne peut pas commencer par un chiffre.

ma_variable <- 2
ma_variable <- "Toulouse"
ma_variable <- c("Toulouse", "Nantes", "Strasbourg")
ma_variable <- 1:10

paste0() pour concaténer des variables separate() pour séparer des variables

On souhaite créer de nouvelles variables

4.2.1 mutate() pour ajouter et modifier des variables

La fonction mutate() permet de créer/modifier une variable (ou plusieurs).

table_sortie <- mutate(table_entree,
                        nouvelle_variable = definition_variable)

# Par exemple pour créer une variable :
base <- mutate(base, 
               log_SUPERF = log(SUPERF),
               code_prelevement_caract = as.character(code_prelevement))

La fonction mutate() permet également de modifier une variable. Dans ce cas la syntaxe est la même que ci-dessus, mais les noms d’entrée et de sortie sont les mêmes :

library(janitor) # pour round_half_up

# Par exemple pour modifier une variable existante :
base <- mutate(base, 
               log_SUPERF = 100 * log_SUPERF,
               code_prelevement = as.character(code_prelevement))

df <- mutate(df, 
             densite = P14_POP / SUPERF,
             tx_natal = 1000 * NAISD15 / P14_POP,
             tx_mort = 1000 * DECESD15 / P14_POP)

# ajout d'une variable avec mutate
agri_bio_vegetal_fr_tab1 <- agri_bio_vegetal_fr |>   
  filter(groupe_de_productions %in% c("Toutes productions")) |> 
  select(annee, territoire, nombre_de_producteurs,
         surface_bio_et_en_conversion_en_ha, surface_en_conversion_en_ha, surface_bio_en_ha,
         part_bio_de_la_sau_en_percent) |>  
  mutate(part_bio_sau_plus_percent = paste0(part_bio_de_la_sau_en_percent," %"))

# ajout d'une variable avec une modalité déterminée
volailles_fr <- read.xlsx(xlsxFile = path(chemin, fichier), 
                           sheet = "res-France", colNames = TRUE, startRow = 1) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  mutate(reg = "France") |> 
  select(reg, espece, total_poids)

# ajout d'une variable par calcul
# passage d'ares en hectares
sau_pbs_2010_2020 <- exploit_2010 |> 
  mutate(sau_tot = sau / 100, annee = annee_ra_2010) 

# calcul solde
oeufs <- oeufs |>   
            mutate(solde = Exportation_Nombre - Importation_Nombre)

# on peut préciser l'emplacement de la nouvelle colonne avec .before ou .after
groupe_produits_laitiers_2021_France <- produits_laitiers_2021_2 |>
  group_by(code_produit, libellé_produit) |>
  summarise(production_2020 = round_half_up(sum(finiqte1, na.rm = TRUE), digits = 0),
    production_2021 = round_half_up(sum(finiqte, na.rm = TRUE), digits = 0),
    établissements_2020 = sum(nbfiniqte1, na.rm = TRUE),
    établissements_2021 = sum(nbfiniqte, na.rm = TRUE)) |> 
  ungroup() |> 
  mutate(territoire="France", .before = 1)

# lors d'un mutate avec un group_by, 
# il est conseillé d'ajouter le suffixe correspondant au nom de variable
# par exemple _reg
# VOLAILLEFIL   Élevez-vous des volailles ?
exploit_dont_volailles_2020 <- exploit_dont_volailles_2020 |> 
  mutate(nb_exploit_volailles_France = sum(volaillefil, na.rm = TRUE),
         part_exploit_volailles_France = nb_exploit_volailles_France / n() * 100) |> 
  group_by(reg) |> 
  mutate(nb_exploit_volailles_reg = sum(volaillefil, na.rm = TRUE),
         part_exploit_volailles_reg = nb_exploit_volailles_reg / n() * 100) |> 
  ungroup() 

nb_exploit_dont_volailles_2020 <- exploit_dont_volailles_2020 |> 
  distinct(reg, nb_exploit_volailles_France, part_exploit_volailles_France,
           nb_exploit_volailles_reg, part_exploit_volailles_reg)

nb_exploit_dont_volailles_2020_pdl <- exploit_dont_volailles_2020 |> 
  filter(reg == "52") |> 
  group_by(dep) |> 
  mutate(nb_exploit_volailles_dep = sum(volaillefil, na.rm = TRUE),
         part_exploit_volailles_dep = nb_exploit_volailles_dep / n() * 100)

# ajout niveau dep, reg et fm
recolte_bois_dep_reg <- recolte_bois_depart |>  
  group_by(categorie) |> 
  mutate(valeur_fm = sum(valeur)) |>
  ungroup() |> 
  group_by(reg,categorie) |> 
  mutate(valeur_reg = sum(valeur)) |>
  ungroup() |>  
  rename(valeur_dep = valeur) |> 
  select(dep, reg, categorie,
         valeur_fm,
         valeur_reg,
         valeur_dep,
         libdep, libreg)

# nombre des exploitants par groupe
repart_diplome_max_groupe1 <-  mo_2020_diplome_max |> 
  mutate(n_tot = n()) |>
  group_by(dipl_max_groupe1) |> 
  summarise(nb = n(), percent = n() / n_tot * 100) |> 
  distinct() |> 
  adorn_totals("row", name = "Ensemble")

# utilisation de la fonction last, qui permet de récupérer la valeur de la dernière ligne
# à manier avec précaution (ne pas modifier l'ordre au préalable par exemple)
exploit_otex_dep_reg_fm <- exploit_otex_pdl |> 
  left_join(exploit_otex_dep,
            join_by(otex_regroup) |> 
  adorn_totals(where = "row",
               name = "Ensemble") |> 
  mutate(a01_part_otex_fm = a01_n_exploit_fm / last(a01_n_exploit_fm) * 100,
         b02_part_otex_reg = b02_n_exploit_reg / last(b02_n_exploit_reg) * 100,
         c03_part_otex_85 = dep_85 / last(dep_85) * 100,
         d04_part_otex_72 = dep_72 / last(dep_72) * 100,
         e05_part_otex_53 = dep_53 / last(dep_53) * 100,
         f06_part_otex_49 = dep_49 / last(dep_49) * 100,
         g07_part_otex_44 = dep_44 / last(dep_44) * 100) |> 
  mutate(across(contains("part"), \(x) round_half_up(x, digits = 3)))

# sau irriguées
exploit_anim_produit_grand_pdl <- exploit_anim_produit_grand_pdl |> 
  group_by(seuil_produit) |> 
  mutate(indic_10_sau_reg = sum(sau_tot),
         sau2_reg = sum(rowSums(across(contains("cultsur")))),
         sau3_reg = sum(across(contains("cultsur"))),
         indic_11_sau_irrig_reg = sum(across(contains("irrisur"))),
         .after=nom_dossier) |> 
  ungroup()

La fonction mutate() permet de créer de nouvelles colonnes ou de modifier des colonnes existantes. Il est possible d’utiliser toutes sortes de fonctions à l’intérieur d’une étape mutate(). Le code suivant crée une variable NB_EQUIP_3PLUS qui vaut TRUE si le nombre d’équipement est supérieur ou égal à 3, et FALSE sinon.

bpe_ens_2018_tbl |> 
  mutate(NB_EQUIP_3PLUS = (NB_EQUIP >= 3))

Pour créer une nouvelle variable, on utilise un nom de variable qui n’existe pas encore dans la table. Pour modifier une variable qui existe déjà, on utilise directement le nom de cette variable.

On peut créer plusieurs nouvelles colonnes en une seule commande, et les expressions successives peuvent prendre en compte les résultats des calculs précédents. L’exemple suivant convertit d’abord la durée en heures dans une variable duree_h et la distance en kilomètres dans une variable distance_km, puis utilise ces nouvelles colonnes pour calculer la vitesse en km/h.

flights <- mutate(
    flights,
    duree_h = air_time / 60,
    distance_km = distance / 0.62137,
    vitesse = distance_km / duree_h
)

select(flights, air_time, duree_h, distance, distance_km, vitesse)

Voici quelques utilisations fréquentes de mutate() :

Action Code
Calculer une somme cumulée mutate(NB_EQUIP_CUM = cumsum(NB_EQUIP, na.rm = TRUE))
Calculer un total mutate(NB_EQUIP_TOT = sum(NB_EQUIP, na.rm = TRUE))
Sommer deux variables mutate(NB_EQUIP_DOUBLE = NB_EQUIP + NB_EQUIP)
Extraire une sous-chaine de caractères mutate(CATEGORIE_EQ = str_sub(TYPEQU, 1L, 1L))
# Calcul du nb d'exploitations ayant des bovins
exploit_prod_anim_pdl <- exploit_prod_anim_pdl |> 
  mutate(n_exploit_ayant_bovins = sum(cheptq_110000 > 0, na.rm = TRUE),
         n_exploit_ayant_vaches = sum(cheptq_111000 > 0, na.rm = TRUE),
         n_exploit_ayant_gros_bovins = sum(gros_bovins > 0, na.rm = TRUE),
         n_exploit_ayant_veaux = sum(veaux > 0, na.rm = TRUE),
         n_exploit_ayant_veaux_25min = sum(veaux >= 25, na.rm = TRUE),
         .after = nom_dossier) 
# Pour s'assurer que benef_p2 vaut 1 si au moins une des conditions est vraie,
# on utilise une condition logique combinée avec | (ou logique)
# et on convertit le résultat en numérique (TRUE devient 1)
# pour obtenir 1 si au moins une des conditions est vraie, sinon 0.
beneficiaires_pdl_2020 <- beneficiaires_2020 |>
  filter(nreg == "52") |>
  mutate(
    benef_p2 = as.numeric((benef_bio_maec > 0) |
                            (benef_ichn_aras > 0)),
    benef_p1_p2 = as.numeric((benef_p1 > 0) |
                               (benef_bio_maec > 0) |
                               (benef_ichn_aras > 0)),
    .after = benef_p1
  ) |>
  group_by(dep) |>
  mutate(
    nb_beneficiaires_p1_dep = sum(benef_p1, na.rm = TRUE),
    nb_beneficiaires_p2_dep = sum(benef_p2, na.rm = TRUE),
    nb_beneficiaires_p1_p2_dep = sum(benef_p1_p2, na.rm = TRUE),
    .after = benef_p1_p2
  ) |>
  ungroup() |>
  mutate(
    nb_beneficiaires_p1_reg = sum(benef_p1, na.rm = TRUE),
    nb_beneficiaires_p2_reg = sum(benef_p2, na.rm = TRUE),
    nb_beneficiaires_p1_p2_reg = sum(benef_p1_p2, na.rm = TRUE),
    .after = benef_p1_p2
  )

4.2.1.1 L’argument .keep

L’argument .keep dans mutate() permet de contrôler quelles colonnes du dataframe d’origine sont conservées dans le résultat.

L’argument .keep donne un contrôle fin sur les colonnes qui doivent être conservées après l’opération mutate(). Il peut prendre plusieurs valeurs :
“all” (par défaut) : Conserve toutes les colonnes originales + les nouvelles.
“used” : Conserve les colonnes qui ont été utilisées pour créer les nouvelles colonnes + les nouvelles.
“unused” : Conserve les colonnes qui n’ont pas été utilisées pour créer les nouvelles colonnes + les nouvelles.
“none” : Ne conserve aucune des colonnes originales, seulement les nouvelles.

Le paramètre .keep = “none” indique à mutate() de supprimer toutes les colonnes existantes sauf celles explicitement mentionnées. On ne conserve que les nouvelles colonnes qu’on vient de créer. Toutes les colonnes qui existaient déjà dans le tableau de données sont supprimées.

# Données d'exemple
ventes <- tibble(
  produit = c("A", "B", "C"),
  prix = c(10, 15, 20),
  quantite = c(5, 3, 8),
  region = c("Nord", "Sud", "Est")
)

# Calcul avec .keep = "none"
ventes |> 
  mutate(
    chiffre_affaires = prix * quantite,
    profit = chiffre_affaires * 0.2,
    .keep = "none"
  )
# Résultat : seulement chiffre_affaires et profit

# Comparé à .keep = "all" (défaut)
ventes |> 
  mutate(
    chiffre_affaires = prix * quantite,
    profit = chiffre_affaires * 0.2
  )
# Résultat : produit, prix, quantite, region, chiffre_affaires, profit
# calcul des ratios import / export par secteur ----
# Ajouter une ligne part export/import pour chaque secteur
# On considère le ratio non pas comme une variable, mais comme une nouvelle observation.
# Créer une nouvelle ligne avec le rapport export/import pour les secteurs AZ et C1
ratios_az_c1 <- produits_long_az_c1_det_reg |>
   # Filtrer pour garder seulement export et import (pas solde)
   filter(flux %in% c("export", "import")) |>
   # Pivoter pour avoir export et import en colonnes
   pivot_wider(
      id_cols = a_17,
      names_from = flux,
      values_from = starts_with("an_")
      # values_from = c(
      #    an_2021_sem1, an_2021_sem2, an_2022_sem1, an_2022_sem2,
      #    an_2023_sem1, an_2023_sem2, an_2024_sem1, an_2024_sem2,
      #    an_2025_sem1)
   )

# Calculer les deux ratios (export/import et import/export) pour chaque période
# Le paramètre .keep = "none" indique à mutate() de supprimer toutes les colonnes existantes
# sauf celles explicitement mentionnées
# On ne conserve que les nouvelles colonnes qu'on vient de créer.
# Toutes les colonnes qui existaient déjà dans le tableau de données sont supprimées.
ratios_export_sur_import <- ratios_az_c1 |>
   mutate(
      a_17 = a_17,
      flux = "export_sur_import",
      an_2021_sem1 = an_2021_sem1_export / an_2021_sem1_import * 100,
      an_2021_sem2 = an_2021_sem2_export / an_2021_sem2_import * 100,
      an_2022_sem1 = an_2022_sem1_export / an_2022_sem1_import * 100,
      an_2022_sem2 = an_2022_sem2_export / an_2022_sem2_import * 100,
      an_2023_sem1 = an_2023_sem1_export / an_2023_sem1_import * 100,
      an_2023_sem2 = an_2023_sem2_export / an_2023_sem2_import * 100,
      an_2024_sem1 = an_2024_sem1_export / an_2024_sem1_import * 100,
      an_2024_sem2 = an_2024_sem2_export / an_2024_sem2_import * 100,
      an_2025_sem1 = an_2025_sem1_export / an_2025_sem1_import * 100,
      .keep = "none"
   ) |>
   # Arrondir les valeurs
   mutate(across(starts_with("an_"), \(x) round_half_up(x, digits = 2)))

ratios_import_sur_export <- ratios_az_c1 |>
   mutate(
      a_17 = a_17,
      flux = "import_sur_export",
      an_2021_sem1 = an_2021_sem1_import / an_2021_sem1_export * 100,
      an_2021_sem2 = an_2021_sem2_import / an_2021_sem2_export * 100,
      an_2022_sem1 = an_2022_sem1_import / an_2022_sem1_export * 100,
      an_2022_sem2 = an_2022_sem2_import / an_2022_sem2_export * 100,
      an_2023_sem1 = an_2023_sem1_import / an_2023_sem1_export * 100,
      an_2023_sem2 = an_2023_sem2_import / an_2023_sem2_export * 100,
      an_2024_sem1 = an_2024_sem1_import / an_2024_sem1_export * 100,
      an_2024_sem2 = an_2024_sem2_import / an_2024_sem2_export * 100,
      an_2025_sem1 = an_2025_sem1_import / an_2025_sem1_export * 100,
      .keep = "none"
   ) |>
   mutate(across(starts_with("an_"), \(x) round_half_up(x, digits = 2)))

# Ajouter ces deux types de ratios au tableau original
produits_az_c1_reg_ratios_imp_exp <- bind_rows(
   produits_long_az_c1_det_reg,
   ratios_export_sur_import,
   ratios_import_sur_export
)

4.2.2 separate : scinder une colonne en plusieurs

La fonction separate est utile lorsque nous avons une seule colonne contenant des valeurs composées que nous souhaitons diviser en plusieurs colonnes distinctes. Elle prend trois arguments principaux :

  • le nom de la colonne à scinder ;
  • un vecteur indiquant les noms des nouvelles variables à créer ;
  • le séparateur sep indique à quel endroit la variable doit être scindée. Par défaut separate scinde au niveau des caractères non-alphanumérique (espace, symbole, etc.). Si l’on indique un nombre entier n, alors la colonne est scindée après le n-ième caractère.

Voici un exemple qui utilise la table des communes du Code Officiel Géographique. Dans cette table, la colonne com (code commune Insee) contient deux informations : le numéro du département et le numéro de la commune.

cog_com_2019_tbl <- doremifasolData::cog_com_2019 |> as_tibble()
cog_com_2019_tbl

Voici comment on peut utiliser separate pour scinder com en deux nouvelles colonnes code_dep et code_com. La colonne com disparait, car par défaut separate supprime la colonne scindée. Si on souhaite la conserver, il faut ajouter l’option remove = FALSE.

library(dplyr)
library(tidyr)

cog_com_2019_tbl |> 
  separate(com, c("code_dep", "code_com"), sep = 2)

# séparation d'une variable date ("22-09-2022") en "jour","mois","annee"
oeufs_cot_det <- oeufs_cot_det |> 
  separate(DATE,sep="-", into = c("jour","mois","annee"))

# on divise les noms de colonnes en "indicateur" et "territoire"
# on transpose le niveau dep en colonnes
agreg_anim_produit_grand_dep_reg <- agreg_anim_produit_grand_dep_pdl_long |>
  separate(indicateur_territoire, into = c("indicateur", "territoire"), sep = "_dep_") |>
  arrange(territoire) |> 
  pivot_wider(names_from = territoire, 
              values_from = donnees_territoire) |>
  arrange(indicateur) 

4.2.3 unite : regrouper plusieurs colonnes en une seule

La fonction unite permet est de réaliser l’opération inverse de separate : regrouper plusieurs colonnes en une seule. Elle prend trois arguments principaux :

  • le nom de la colonne à créer ;
  • un vecteur indiquant les noms des variables à regrouper ;
  • le séparateur sep qui indique quel séparateur doit être introduit entre les variables regroupées (par défaut, unite utilise le caractère _).

Voici un exemple où l’on regroupe le code commune Insee et le nom officiel de la commune, avec ” - ” comme séparateur. Les colonnes com et ncc disparaissent, car par défaut unite supprime les colonnes regroupées. Si on souhaite les conserver, il faut ajouter l’option remove = FALSE.

cog_com_2019_tbl |> 
  unite(code_et_nom, c("com", "ncc"), sep = " - ") 

On souhaite reconstruire une colonne code_insee qui indique le code Insee de la commune, et qui s’obtient en concaténant le code du département et celui de la commune.

df |>  
  unite(
      code_insee, 
      code_departement, code_commune, 
      sep = "", 
      remove = FALSE
  )

4.2.4 Ajout d’une colonne “numéro de ligne”

row.names

# ajout colonne numéro de ligne
# creation d'une variable identifiant reprenant le numéro de ligne 
produits_laitiers <- read.xlsx(xlsxFile = path(chemin_entree, fichier_entree), 
                               sheet = "produits laitiers", colNames = TRUE, 
                               startRow = 12) |> 
  as_tibble(.name_repair = make_clean_names) 

produits_laitiers <- produits_laitiers |> 
  mutate(ident = row.names(produits_laitiers), .before = 1)

# filtre en fonction du numéro de ligne
# avec l'identifiant “numéro de ligne”
# Ajout d’une colonne “numéro de ligne”
# creation d'une variable identifiant reprenant le numéro de ligne 
vab_agri_2020_2 <- vab_agri_2020 |> 
  mutate(ident = row.names(vab_agri_2020), .before = 1) |> 
  filter(ident %in% 1:15) |> 
  mutate(total = as.numeric(total)) |> 
  left_join(geographie_reg,
            join_by(libreg))

4.3 Sélectionner des variables

  • select() : sélectionner des variables par leur nom

La fonction select() permet de sélectionner les variables voulues.

  • sélection par liste blanche
table_sortie <- select(table_entree, variable1, variable2, ..., variableN)
  • sélection par liste noire (supprimer). Si on fait précéder le nom d’un -, la colonne est éliminée plutôt que sélectionnée.
table_sortie <- select(table_entree, -variable1, -variable2, ..., -variableN)

Par exemple :

base_select <- select(base, codgeo, libgeo, p14_pop)
base_select <- select(base, -codgeo)
agri_bio_vegetal_tab1_final <- agri_bio_vegetal_tab1_final |> 
  select(c("annee", "vegetal", "Loire-Atlantique", "Maine-et-Loire", "Mayenne", "Sarthe", "Vendée",
           "Pays de la Loire", "National"))

agri_bio_vegetal_fr_tab1 <- agri_bio_vegetal_fr |>   
  filter(groupe_de_productions %in% c("Toutes productions")) |> 
  select(annee, territoire, nombre_de_producteurs,
         surface_bio_et_en_conversion_en_ha, surface_en_conversion_en_ha, surface_bio_en_ha,
         part_bio_de_la_sau_en_percent) |>  
  mutate(part_bio_sau_plus_percent = path(part_bio_de_la_sau_en_percent, " %"))

Une sélection négative est aussi possible avec ‘select(-variable_a_enlever)’.

# on supprime la 1re colonne
eff_bovins_2024 <- eff_bovins_2024 |> 
  as_tibble(.name_repair = make_clean_names) |> 
  select(-1)

# on supprime la dernière colonne
test <- eff_bovins_2024 |> 
  select(-last_col())

# conversion en caractère
# Quand on passe une fonction comme argument à une autre fonction, on utilise la notation sans les parenthèses.
agri_bio_vegetal_fr_tab1_car <- agri_bio_vegetal_fr_tab1 |> 
  mutate(across(c(nombre_de_producteurs, surface_bio_et_en_conversion_en_ha,
                  surface_en_conversion_en_ha, surface_bio_en_ha,
                  part_bio_de_la_sau_en_percent), 
                as.character)) |> 
  select(-c(part_bio_de_la_sau_en_percent))

select() possède ce qu’on appelle des helpers qui permettent de gagner du temps dans l’écriture d’une sélection.

Exemple : sélectionner toutes les variables qui commencent par “code_” :

prelevementb <- select(prelevement, starts_with("code_"))

Exemple : enlever de la sélection les variables dont les noms contiennent “type_reg” :

# A partir du tableau transposé, ajout du total régional
bio_pdl_animaux_large_2 <- bio_pdl_animaux_large |> 
  adorn_totals(where = "row", name = "Pays de la Loire") |> 
  select(-c(contains("type_reg")))

Exemple : sélectionner les variables dont les noms sont contenus dans un vecteur de chaînes de caractères :

mes_variables <- c("code_prelevement", "code_intervenant", "code_reseau", "date_prelevement")
prelevementb <- select(prelevement, one_of(mes_variables))

La fonction select() permet de sélectionner des variables par leur nom, ou par une condition sur leur nom.

  • Avec une liste de noms de variables. Le code suivant sélectionne le code commune, le type d’équipement et le nombre d’équipement dans la base permanente des équipements :
  bpe_ens_2018_tbl |> 
    select(depcom, typequ, nb_equip)
  • Avec une condition logique. par exemple, la fonction starts_with("dep") permet de sélectionner toutes les variables dont le nom commence par “dep”.
  bpe_ens_2018_tbl |> 
    select(starts_with("dep"))

bio_pdl_dep_ab <- bio_pdl_dep_large2 |>
  select(dep,c(contains("ab_")))

Le tableau suivant donne la liste des conditions utilisables avec select() :

Fonction Signification
select(c()) les colonnes citées dans le vecteur
select(starts_with("...") dont le nom commence par “…”
select(ends_with("...") dont le nom se termine par “…”
select(contains("...") dont le nom contient “…”
select(matches("...") vérifie une expression régulière
select(colonne1:colonne2) une étendue de colonnes dans la table
select(where()) les colonnes vérifiant une condition
select(all_of(...)) sélectionne les colonnes listées dans un vecteur en paramètre (absolument)
select(any_of(...)) identique à all_of(), mais éventuellement (sans erreur si la colonne n’existe pas)
select(everything()) toutes les colonnes (utile pour mettre une nouvelle colonne devant les autres)

La syntaxe colonne1:colonne2 permet de sélectionner toutes les colonnes situées entre colonne1 et colonne2 incluses. À noter que cette opération est un peu plus “fragile” que les autres, car si l’ordre des colonnes change elle peut renvoyer un résultat différent.

On peut ainsi indiquer une plage de colonnes (soit par les noms de variables, soit par les numeros de colonne, par exemple ci-après on pourrait indiquer 1:4 au lieu de haircolor:weight)

data <- data  |> 
  select(catdata, haircolor:weight)
bio_anim_dep_pdl_part_reg_export <- bio_anim2_dep_pdl_an |>
  filter(echelle_geographique == "Département") |>
  select(
    echelle_geographique, territoire, type_de_production, groupe_de_productions,
    rang_nb_tetes_2023,
    rang_nombre_deleveurs_2023,
    rang_part_du_bio_en_percent_2023,
    ends_with("part_reg"),
    starts_with("part_du_bio")
  )

bio_anim_nb_tetes_dep_pdl_part_reg <- bio_anim_dep_pdl_part_reg_export |>
  select(
    echelle_geographique, territoire, type_de_production, groupe_de_productions,
    rang_nb_tetes_2023,
    starts_with("nb_tetes_")
  ) |>
  rename_with(
    \(x) str_replace(x, "nb_tetes_", "an_"),
    .cols = starts_with("nb_tetes_")
  ) |>
  mutate(indicateur = "nb_tetes", .after = "groupe_de_productions")
  • Il est possible de renommer des colonnes directement avec select. On l’utilise en lui passant des paramètres de la forme nouveau_nom = ancien_nom.
  bpe_ens_2018_tbl |> 
    select(dept = dep, depcom)

La fonction everything() permet de conserver toutes les colonnes. On l’utilisera lorsqu’on spécifie les colonnes dans un ordre souhaité, puis qu’on garde toutes les autres dans leur ordre d’origine. Cela évite de renseigner toutes les colonnes.

# continent en premier puis les autres telles qu'elles existaient
population_monde |> 
  select(continent, everything())

Attention : si on élimine des colonnes, il faut le faire après la fonction everything(), car sinon, elle remettra les colonnes éliminées.

# pays et pop en premier, toutes les autres ensuite, puis on retire continent et pib_par_habitant
population_monde |> 
  select(pays, pop, everything(), -continent, -pib_par_habitant)

La fonction where(), combinée avec le verbe select(), permet de sélectionner une colonne selon un “prédicat”, c’est-à-dire un test. Dans l’exemple ci-dessous, on n’affiche que les colonnes dont le format est de type numérique (à l’aide de la fonction is.numeric() qui teste justement ce type-là). L’expression est simplifiée en n’utilisant pas les parenthèses de is.numeric().

# selection des colonnes de type numérique
population_monde |> 
  select(where(is.numeric))

Une autre syntaxe consiste à écrire les tests comme une fonction, après le symbole (x). La variable x correspond à ce qui est à gauche du “pipe”.
On peut rajouter des conditions à l’aide des opérateurs booléens doublés && et || qui correspondent au ET et OU pour des colonnes entières.

# on selectionne les colonnes de type numérique et dont la moyenne est inférieure à 60
population_monde |> 
  select(where(\(x) is.numeric(x) && mean(x, na.rm = TRUE) < 60))

https://tidyselect.r-lib.org/reference/language.html

4.4 Renommer des colonnes (variables)

  • rename() : renommer des variables ;

La fonction rename() permet de renommer une variable (ou plusieurs).

base <- rename(base, nouveau_nom = ancien_nom)

# Exemple
base_rename <- rename(base, ZONE_EMPLOI = ZE)

# renommage 2 lignes intitulés
volailles_pdl_siqo2 <- volailles_pdl_siqo |>   
  rename(espece_poulets_coquelets_siqo_label_rouge = label_rouge_2,
         espece_poulets_coquelets_siqo_aoc_aop = aoc_aop_3,       
         espece_poulets_coquelets_siqo_agri_bio = agri_bio_4,       
         espece_poulets_coquelets_siqo_aut_signe_off = aut_signe_off_5 
        )

La syntaxe est la suivante : rename(data, nouveau_nom = ancien nom). Voici un exemple :

bpe_ens_2018_tbl |> 
  rename(code_commune = DEPCOM)

Si les noms de colonnes comportent des espaces ou des caractères spéciaux, on peut les entourer de guillemets (") ou d’apostrophes (quotes) inverses (`) :

tmp <- rename(
    flights,
    "retard départ" = dep_delay,
    "retard arrivée" = arr_delay
)
select(tmp, `retard départ`, `retard arrivée`)

La fonction rename_with() permet de renommer un groupe de colonnes avec une fonction.
La syntaxe est la suivante : rename_with(data, nom_fonction, selection_colonnes). Voici un exemple qui met en minuscules tous les noms de colonnes :

bpe_ens_2018_tbl |> 
  rename_with(tolower)

Par défaut, rename_with() applique la fonction de renommage à l’ensemble des colonnes du tableau. Il est cependant possible de lui indiquer de ne renommer que certaines de ces colonnes. Pour cela, on peut lui ajouter un argument supplémentaire nommé .cols.

# on renomme _reg en _dep_ensemble
# on renomme toutes les colonnes contenant "_reg" à la fin par "_dep_ensemble"
# ensuite en enlevant le suffixe _dep, il restera 44,49,53,72,85,ensemble
agreg_anim_produit_grand_dep_pdl_b <- agreg_anim_produit_grand_dep_pdl_a |>
  rename_with(
    \(x) str_replace(x, "_reg", "_dep_ensemble"),
    .cols = contains("reg")
  )

4.5 Conversion d’une variable caractère en numérique

On utilise la fonction ‘as.numeric’.

agri_bio_vegetal_fr <- agri_bio_vegetal_fr |> 
# Quand on passe une fonction comme argument à une autre fonction, on utilise la notation sans les parenthèses.
  mutate(across(c(surface_bio_et_en_conversion_en_ha,
                  surface_en_conversion_en_ha,surface_bio_en_ha,
                  part_bio_de_la_sau_en_percent),
                as.numeric)) 

En cas de lignes vides, cela crée des valeurs NA (not available) et produit un messsage d’avertissement dans la console. C’est un message non bloquant. Warning messages: 1: Problem with mutate() input ..1. NAs introduits lors de la conversion automatique Input ..1 is across(...).

On utilise la fonction as.numeric, combinée à across pour l’appliquer à un ensemble de variables.

# import fichiers
chemin_entree <- "S:/ETUDES/En cours/memento_Christophe/entree/BAEA/"
fichier_entree <- "baea_diff_2020_reg_52_Pays_de_la_Loire.xlsx"

otex <- read.xlsx(xlsxFile = path(chemin_entree, fichier_entree), 
                         sheet = "ETP - OTEX",colNames = TRUE,startRow = 7) |> 
  # Quand on passe une fonction comme argument à une autre fonction, on utilise la notation sans les parenthèses.
  mutate(across(-c("Orientation.technico-économique.(OTEX)"), as.numeric)) |> 
  rename(otex_lib = "Orientation.technico-économique.(OTEX)") |> 
  mutate(otex_code = substr(otex_lib,1,4))

# Pourcentage en colonne
produits_a17_percent <- produits_a17_large |> 
   mutate(an_2021_imports_percent_col = an_2021_imports / sum(an_2021_imports) * 100,
          an_2022_imports_percent_col = an_2022_imports / sum(an_2022_imports) * 100,
          an_2021_exports_percent_col = an_2021_exports / sum(an_2021_exports) * 100,
          an_2022_exports_percent_col = an_2022_exports / sum(an_2022_exports) * 100,
          an_2021_I_sem1_percent_col = an_2021_I_sem1 / sum(an_2021_I_sem1) * 100,
          an_2021_I_sem2_percent_col = an_2021_I_sem2 / sum(an_2021_I_sem2) * 100,
          an_2022_I_sem1_percent_col = an_2022_I_sem1 / sum(an_2022_I_sem1) * 100,
          an_2022_I_sem2_percent_col = an_2022_I_sem2 / sum(an_2022_I_sem2) * 100,
          an_2021_E_sem1_percent_col = an_2021_E_sem1 / sum(an_2021_E_sem1) * 100,
          an_2021_E_sem2_percent_col = an_2021_E_sem2 / sum(an_2021_E_sem2) * 100,
          an_2022_E_sem1_percent_col = an_2022_E_sem1 / sum(an_2022_E_sem1) * 100,
          an_2022_E_sem2_percent_col = an_2022_E_sem2 / sum(an_2022_E_sem2) * 100,
          .after=a17_lib_detail) |> 
   mutate(across(where(is.numeric), \(x) round_half_up(x, digits = 3)))

# type.convert pour transformer en numérique poids_produit_en_tec (caractère) qui a comme séparateur décimal ","
viande_volaille_tec_2021 <- read.xlsx(xlsxFile = path(chemin, "cd2022-5_SAA_2021_provisoire_resultats_LIB.xlsx"), 
                         sheet = "Volailles finies produites", colNames = TRUE, startRow = 1) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  filter(region %in% c("52  -  Pays de la Loire","98  -  Total France métropolitaine")) |> 
  mutate(an_2021 = type.convert(poids_produit_en_tec, dec = ","),
         reg = case_when(
           region %in% c("52  -  Pays de la Loire") ~ "pdl",
           region %in% c("98  -  Total France métropolitaine") ~ "fm")) |> 
  select(c(reg, libelle_culture, an_2021)) 
library(stringr) 

# Convertir les colonnes commençant par "surf" en numérique
bio_producteurs_vege_pdl <- bio_producteurs_vege_pdl |>
  select(ident, dep, numerobio, numeroclientoc,
    libellegroupe, libellessgroupe, production,
    surfab, surfc1, surfc2, surfc3, surfc123, surfbiototale) |> 
  # Convertir en numérique avec un séparateur décimal spécifique (",")
  # Remplacer la virgule par un point avant de faire la conversion en numérique
    mutate(across(starts_with("surf"), \(x) str_replace(x, ",", "."))) |> 
    mutate(across(starts_with("surf"), as.numeric))
  # Avec type.convert 
  # mutate(across(starts_with("surf"), \(x) type.convert(x, dec = ",")))

4.6 Conversion d’une variable numérique en caractère

On utilise la fonction ‘as.character’.

library(janitor) # pour round_half_up

# conversion en caractère
# Quand on passe une fonction comme argument à une autre fonction, on utilise la notation sans les parenthèses.
agri_bio_vegetal_fr_tab1_car <- agri_bio_vegetal_fr_tab1 |> 
  mutate(across(c(nombre_de_producteurs,surface_bio_et_en_conversion_en_ha,
                  surface_en_conversion_en_ha,surface_bio_en_ha,
                  part_bio_de_la_sau_en_percent), as.character)) |> 
  select(-c(part_bio_de_la_sau_en_percent))

4.7 Recodification de variables

Parfois, on veut créer une nouvelle variable en partant des valeurs d’une ou plusieurs autres variables.

4.7.1 avec case_match()

La fonction case_match() permet de recoder les valeurs d’une variable en fonction de correspondances exactes ou de conditions.
Il faut utiliser ~ (AltGr + 2) pour associer les valeurs de correspondance avec leurs résultats.
Pour mettre une valeur par défaut, on utilise l’argument de la fonction .default =. Le .default est un paramètre nommé de la fonction. Comme pour tout argument, on l’assigne avec = (par exemple, .default = “Autre”).

library(dplyr)

# valeur_originale ~ nouvelle_valeur signifie : 
# Si la valeur est valeur_originale, la remplacer par nouvelle_valeur.

data <- data |>
  mutate(nouvelle_variable = case_match(
    ancienne_variable,
    valeur1 ~ nouvelle_valeur1,
    valeur2 ~ nouvelle_valeur2,
    .default = valeur_defaut # (optionnel) valeur par défaut si aucune correspondance
  ))

# ajout date en nombre
lait_chevre3 <- lait_chevre2  |>
  mutate(code_mois = case_match(mois,
                          "Janvier" ~ "01",
                          "Février" ~ "02",
                          "Mars" ~ "03", 
                          "Avril" ~ "04",
                          "Mai" ~ "05",
                          "Juin" ~ "06",
                          "Juillet" ~ "07",
                          "Août" ~ "08",
                          "Septembre" ~ "09", 
                          "Octobre" ~ "10",
                          "Novembre" ~ "11",
                          "Décembre" ~ "12"))

# ajout date en lettres
laits_mensuel_chevre_2 <- laits_mensuel_chevre_1  |>
  mutate(mois = case_match(code_mois,
                          "01" ~ "Janvier",
                          "02" ~ "Février", 
                          "03" ~ "Mars",
                          "04" ~ "Avril",
                          "05" ~ "Mai",
                          "06" ~ "Juin",
                          "07" ~ "Juillet",
                          "08" ~ "Août", 
                          "09" ~ "Septembre",
                          "10" ~ "Octobre",
                          "11" ~ "Novembre",
                          "12" ~ "Décembre"))
                            
# otex en clair
exploit_vaches_otex_clair <- exploit_vaches_otex |> 
  mutate(otex_libelle = case_match(otefda_coef17,
      "1516" ~ "Grandes cultures",
      "2829" ~ "Maraîchage, horticulture",
      "3500" ~ "Viticulture",
      "3900" ~ "Fruits ou autres cultures permanentes",
      "4500" ~ "Bovins lait",
      "4600" ~ "Bovins viande",
      "4700" ~ "Bovins mixte",
      "4800" ~ "Ovins, caprins, autres herbivores",
      "5074" ~ "Porcs, volailles",
      "6184" ~ "Polyculture, polyélevage",
      "9000" ~ "Exploitations non classées",
      .default = "autre"))
# Calculer par département et statut 
# le nombre d’exploitations ayant des vaches,
# l’effectif de vaches et la pbs
exploit_vaches_statut <- exploit_bovins_c |> 
  filter(cheptq_111000 > 0) |> 
  mutate(statut_libelle = case_match(statut,
                            "01" ~ "1 - Exploitant individuel",
                            "02" ~ "2 - GAEC",
                            "03" ~ "3 - EARL",
                            .default = "4 - Autres statuts"))

nb_exploit_vaches_statut <- exploit_vaches_statut |> 
  group_by(siege_dep, statut_libelle) |> 
  summarise(nb_expl = n(),
            vaches = sum(cheptq_111000),
            pbs = sum(pbstot_coef17)) |> 
  ungroup() |> 
  arrange(siege_dep)
# Utilisation de la fonction case_match pour renommer les modalités

viande_volaille_avant_2010_4 <- viande_volaille_avant_2010_4 |>
  mutate(type_grandeur = case_match(
    type_grandeur,
    "Poids produit (tonne équivalent carcasse)" ~ "prod",
    "Production totale (1000 têtes)" ~ "nbtete"
  ))
# Recodage selon des intervalles
ages <- c(10, 20, 30, 40, 50, 60)

age_groupes <- case_match(
  ages,
  1:17 ~ "Enfant/Adolescent",
  18:39 ~ "Jeune adulte",
  40:59 ~ "Adulte",
  .default = "Senior")

print(age_groupes)

data <- data |>
  mutate(categorie = case_match(
    ages,
    ages < 18 ~ "mineur",
    ages >= 18 & age < 65 ~ "adulte",
    ages >= 65 ~ "senior"))

4.7.2 avec if_else()

if_else de dplyr prend trois arguments : un test, une valeur à renvoyer si le test est vrai, et une valeur à renvoyer si le test est faux.

Avec if_else : - variable = if_else(condition(x), valeur_si_oui, valeur_si_non) permet d’affecter valeur_si_oui ou valeur_si_non à variable en fonction du fait que x répond à condition. Exemple : création d’une variable résultat pour savoir si les résultats de nos analyses sont bons, ou non.

analyseb <- mutate(analyse, resultat_ok = if_else(code_remarque %in% c(1, 2, 7, 10),
                                                 true = TRUE, false = FALSE))

qui peut se résumer, lorsque true = TRUE et false = FALSE, à :

analyseb <- mutate(analyse, resultat_ok = code_remarque %in% c(1, 2, 7, 10))
laits_mensuel_chevre_cerise <- laits_mensuel_2 |>
  filter(type %in% c("CNB","CB")) |> 
  mutate(reg_etab = if_else(dep_etab %in% c("44", "49", "53", "72", "85"),
                           true = "PDL", false = "hors PDL"),
         reg_prod = if_else(dep_prod %in% c("44", "49", "53", "72", "85"),
                           true = "PDL", false = "hors PDL"))

La fonction permet d’utiliser des tests combinant plusieurs variables. Par exemple, imaginons qu’on souhaite créer une nouvelle variable indiquant les hommes de plus de 60 ans :

hdv2003$statut <- if_else(
    hdv2003$sexe == "Homme" & hdv2003$age > 60,
    "Homme de plus de 60 ans",
    "Autre"
)

On peut créer une nouvelle variable, commençant par un caractère commun à plusieurs modalités, avec ‘startsWith’

# Filtrer les enregistrements concernant les pommes
bio_pdl_pommes_yc_terre <- bio_pdl |> 
  mutate(libelle_ab_2 = if_else(startsWith(libelleonab, "Pomme"), "Pommes", "hors_pommes")) |> 
  filter(libelle_ab_2 %in% c("Pommes"))

unique(bio_pdl_pommes$bio_pdl_pommes_yc_terre)
# "Pommes de table" "Pommes de terre (hors féculière)" "Pommes à cidre et à jus" "Pommes (sans précision)"  

bio_pdl_pommes <- bio_pdl |> 
  mutate(libelle_ab_2 = if_else(startsWith(libelleonab, "Pomme"), "Pommes", libelleonab)) |> 
  filter(libelle_ab_2 %in% c("Pommes") & libelleonab != "Pommes de terre (hors féculière)")

4.7.3 avec case_when()

case_when est une généralisation de la logique du if_else qui permet d’indiquer plusieurs tests et leurs valeurs associées.

case_when prend en arguments une série d’instructions sous la forme condition ~ valeur. Il les exécute une par une, et dès qu’une condition est vraie, il renvoie la valeur associée.
Le ~ (ou tilde), obtenu avec deux raccourcis clavier (AltGr + 2 + espace ou Ctrl + Alt + 2 + espace), est usité pour noter une relation d’équivalence.

La dernière clause .default = "Autre" permet d’assigner une valeur à toutes les lignes pour lesquelles aucune des conditions précédentes n’est vraie. La condition .default = indique la valeur par défaut à appliquer si aucune des conditions précédentes n’est vérifiée. Elle permet de gérer notamment les données pour lesquelles on a des valeurs manquantes ou des erreurs de frappe.

Les conditions mises dans un case_when() ne sont pas exclusives. De ce fait, il faut pouvoir déterminer l’ordre d’évaluation des conditions qui y sont posées. Cet ordre s’effectue de bas en haut, c’est à dire que la dernière condition évaluée (celle qui primera sur toutes les autres) sera la première à écrire.

Attention : comme les conditions sont testées l’une après l’autre et que la valeur renvoyée est celle correspondant à la première condition vraie, l’ordre de ces conditions est très important. Il faut absolument aller du plus spécifique au plus général.

Exemple : on va ici calculer des seuils fictifs sur les analyses.

analyseb <- mutate(analyse, classe_resultat_analyse = case_when(
  resultat_analyse == 0     ~ "1",
  resultat_analyse <= 0.001 ~ "2",
  resultat_analyse <= 0.01  ~ "3",
  resultat_analyse <= 0.1   ~ "4",
  resultat_analyse > 0.1    ~ "5",
  .default = ""
 ))
# en fonction du numéro de ligne
produits_laitiers_3 <- produits_laitiers_2 |> 
  mutate(code_produit = case_when( 
    code_produit %in% c("062999 et 072999") ~ "062999_072999",
    code_produit %in% c("144221, 223, 224") ~ "144221_144223_144224",
    code_produit %in% c("188100 et 188220") ~ "188100_188220",
    code_produit %in% c("249319 et 249325") ~ "249319_249325",
    .default = code_produit),
    territoire = if_else(ident %in% 2:22,"France",territoire),
    territoire = if_else(ident %in% 23:45,"Pays de la Loire",territoire))
# nomenclature cultures
agri_bio_vegetal_fr_cultures <- agri_bio_vegetal_fr |>   
    mutate(cultures_2 = case_when(
    groupe_de_productions %in% c("Toutes productions") ~ "1_Surface totale",
    sous_groupe_de_productions %in% c("Céréales") ~ "2_Céréales",
    sous_groupe_de_productions %in% c("Oléagineux","Protéagineux") ~ "3_Oléo-protéagineux",
    sous_groupe_de_productions %in% c("Légumes secs","Légumes frais",
                    "Plantes à parfum, aromatiques et médicinales") ~ "4_Légumes, fruits et PPAM",
    groupe_de_productions %in% c("Fruits (yc à coque)") 
                      & sous_groupe_de_productions %in% c("Toutes") ~ "4_Légumes, fruits et PPAM",
    sous_groupe_de_productions %in% c("Viticulture") ~ "5_Vigne",
    sous_groupe_de_productions %in% c("Cultures fourragères",
                                      "Surfaces toujours en herbe") ~ "6_Fourrage et STH",
    groupe_de_productions %in% c("Autres surfaces") 
                      & sous_groupe_de_productions %in% c("Toutes") ~ "7_Autres",
    .default = "autre cas - doublon")
 )

# nomenclature animaux
agri_bio_animal_fr_animaux <- agri_bio_animal_fr |>   
  mutate(animaux_2 = case_when(
    groupe_de_productions %in% c("Vaches laitières") ~ "1_Vaches laitières",
    groupe_de_productions %in% c("Vaches allaitantes") ~ "2_Vaches viande",
    groupe_de_productions %in% c("Brebis viande") ~ "3_Brebis viande",
    groupe_de_productions %in% c("Brebis laitières") ~ "4_Brebis laitières",
    groupe_de_productions %in% c("Chèvres") ~ "5_Chèvres",
    groupe_de_productions %in% c("Truies") ~ "6_Truies reproductrices",
    groupe_de_productions %in% c("Poulets de chair") ~ "7_Poulets de chair",
    groupe_de_productions %in% c("Poules pondeuses") ~ "8_Poules pondeuses",
    .default = "autre cas - doublon")
 )
# Modification de la nomenclature agrégée libelle_ab_regroupt pour les animaux avec les regroupements :
# On conserve le détail pour les autres animaux
bio_pdl_nomenclature_animaux <- bio_pdl_nomenclature |> 
  mutate(libelle_ab_regroupt = case_when(
    libelle_ab_regroupt %in% c("Vaches allaitantes","Vaches laitières") ~ "Vaches",
    libelle_ab_regroupt %in% c("Brebis viande","Brebis laitières") ~ "Brebis",
    libelle_ab_regroupt %in% c("Porcs charcutiers","Truies") ~ "Porcs",
    libelle_ab_regroupt %in% c("Poulets","Poules pondeuses","Canards",
                            "Pintades","Dindes","Autres volailles") ~ "Volailles",
    libelle_ab_regroupt %in% c("Lapins","Lapines reproductrices") ~ "Lapins",
    .default = libelle_ab_regroupt)
 ) 

sort(unique(bio_pdl_nomenclature_animaux$libelle_ab_regroupt))
# nb chefs et coexploitants
stats_chefcoexpl_statutdirig <- mo_2020 |> 
  mutate(statut_dirig = case_when(statutdirig == "1" ~ "Chef d'exploitation",
                               statutdirig == "2" ~ "Coexploitant",
                               .default = statutdirig)) |> 
  group_by (statut_dirig) |> 
  summarise(nb = n()) |> 
  adorn_totals("row")
# Formation des chefs et coexploitants des exploitations
# si niveau égal, on indique dipl_max agricole
mo_2020_diplome_max <-  mo_2020_install2010 |> 
  mutate(filiere_dipl_max = case_when(as.numeric(mofgene) > as.numeric(mofagri) ~ "general",
                                   .default = "agricole"),
         dipl_max = case_when(filiere_dipl_max == "general" ~ mofgene,
                             filiere_dipl_max == "agricole" ~ mofagri),
         dipl_agri = if_else(mofagri %in% c("23","24","25","26","27","28"),1,0),
         dipl_max_groupe1 = case_when(dipl_max %in% c("00","10") ~ "a_Aucune scolarisation ou niveau primaire",
                                   dipl_max  %in% c("11","23","24") ~ "b_ niveau brevet CAP BEP",
                                   dipl_max %in% c("25","26") ~ "c_Niveau bac",
                                   dipl_max %in% c("27","28") ~ "d_Niveau études supérieures",
                                   .default = dipl_max),
         mofgene_car = case_when(mofgene == "00" ~ "Aucune scolarisation",
                              mofgene == "10" ~ "Niveau CEP ou scolarisé(e) jusqu'au primaire",
                              mofgene == "11" ~ "Niveau BEPC, brevet ou scolarisé(e) jusqu'au collège",
                              mofgene == "23" ~ "Niveau CAP (certificat d'aptitude professionnelle)",
                              mofgene == "24" ~ "Niveau BEP (brevet d'études professionnelles)",
                              mofgene == "25" ~ "Niveau baccalauréat général, brevet supérieur, bac techno",
                              mofgene == "26" ~ "Niveau baccalauréat pro., brevet pro. de technicien",
                              mofgene == "27" ~ "Niveau diplôme de 1er cycle universitaire, BTS, DUT",
                              mofgene == "28" ~ "Niveau d'études supérieures longues, diplôme d'ingénieur, ...",
                              .default = mofgene),
         mofagri_car=case_when(mofagri == "00" ~ "Aucune scolarisation",
                               mofagri == "11" ~ "Niveau BEPC, brevet ou scolarisé(e) jusqu'au collège",
                               mofagri == "23" ~ "Niveau CAP (certificat d'aptitude professionnelle)",
                               mofagri == "24" ~ "Niveau BEP (brevet d'études professionnelles)",
                               mofagri == "25" ~ "Niveau baccalauréat technologique agricole, bac pro agricole",
                               mofagri == "26" ~ "Niveau baccalauréat de technicien agricole (BTA), BEA,BPREA",
                               mofagri == "27" ~ "Niveau diplôme de 1er cycle universitaire, BTS, DUT",
                               mofagri == "28" ~ "Niveau d'études supérieures longues, diplôme d'ingénieur, ...",
                               .default = mofagri)
 )
# Fonction pour nettoyer les noms
clean_names <- function(name) {
  # Enlever les accents
  name <- stri_trans_general(name, "Latin-ASCII")
  # Convertir en minuscules
  name <- tolower(name)
  # Remplacer les caractères non alphanumériques par des underscores
  name <- str_replace_all(name, "[^a-z0-9]", "_")
  # Remplacer les multiples underscores par un seul
  name <- str_replace_all(name, "_+", "_")
  # Enlever les underscores en début et fin de chaîne
  name <- str_replace_all(name, "^_|_$", "")

  return(name)
}

# Nettoyer les modalités des groupes de production
bio_vege_clean_pdl_an <- bio_vege2_pdl_an |>
  mutate(
    groupe_de_productions = clean_names(groupe_de_productions),
    sous_groupe_de_productions = clean_names(sous_groupe_de_productions)
  )

# Simplifier les modalités des groupes de production
bio_vege_clean_pdl_an <- bio_vege_clean_pdl_an |>
  mutate(
    groupe_de_productions = case_when(
      groupe_de_productions == "cereales_et_oleoproteagineux_yc_legumes_secs" ~ "cop",
      groupe_de_productions == "surfaces_et_cultures_fourrageres" ~ "surf_cult_fourrag",
      groupe_de_productions == "plantes_a_parfum_aromatiques_et_medicinales" ~ "ppam",
      .default = groupe_de_productions
    ),
    sous_groupe_de_productions = case_when(
      sous_groupe_de_productions == "fruits_a_noyau_et_a_pepins" ~ "fruits_noyau_pepin",
      sous_groupe_de_productions == "plantes_a_parfum_aromatiques_et_medicinales" ~ "ppam",
      sous_groupe_de_productions == "surfaces_toujours_en_herbe" ~ "sth",
      sous_groupe_de_productions == "fruits_tropicaux_et_subtropicaux" ~ "fruits_tropicaux",
      .default = sous_groupe_de_productions
    )
  )

4.7.4 comparaisons entre case_match(), if_else() et case_when()

case_when(), comparativement à case_match() et if_else()

# Créer un type_regroupement à partir de la variable 'type'
unique(exploit_lait2_production$type)

# 1- Créer un type_regroupement, avec la méthode 'case_when'
exploit_lait2_production <- exploit_lait2_production |>
  mutate(type_regroupement = case_when(     
    type %in% c("VB", "VNB") ~ "Vaches",
    type %in% c("CB", "CNB") ~ "Chevres",
    type %in% c("BB", "BNB") ~ "Brebis",
    .default = "autre"))

# 2- Créer un type_regroupement_1, avec la méthode 'case_match'
exploit_lait2_production <- exploit_lait2_production |>
  mutate(type_regroupement_1 = case_match(type,
    "VB" ~ "Vaches",
    "VNB" ~ "Vaches",
    "CB" ~ "Chevres",
    "CNB" ~ "Chevres",
    "BB" ~ "Brebis",
    "BNB" ~ "Brebis",
    .default = "autre"))

# 3- Créer un type_regroupement_2, avec la méthode 'if_else'
exploit_lait2_production <- exploit_lait2_production |>
  mutate(type_regroupement_2 = if_else(type %in% c("VB", "VNB"), true = "Vaches", false = type),
         type_regroupement_2 = if_else(type %in% c("CB", "CNB"), true = "Chevres", false = type_regroupement_2),
         type_regroupement_2 = if_else(type %in% c("BB", "BNB"), true = "Brebis", false = type_regroupement_2))

4.7.5 avec fct_collapse() pour modifier les modalités d’une variable qualitative

Une opération courante consiste à modifier les valeurs d’une variable qualitative, que ce soit pour avoir des intitulés plus courts ou plus clairs ou pour regrouper des modalités entre elles.

Il existe plusieurs possibilités pour effectuer ce type de recodage, mais ici on va utiliser la fonction fct_collapse du package forcats. Celle-ci prend en argument une liste de recodages sous la forme "Nouvelle valeur" = "Ancienne valeur".

La fonction fct_collapse du package forcats transforme les modalités (niveaux) d’un facteur, soit pour les renommer soit pour les regrouper. Les facteurs en R sont une structure de données qui permettent de représenter des variables catégorielles. Les facteurs sont un type de variable ne pouvant prendre qu’un nombre défini de modalités nommés levels.

library(forcats)  # fonction "fct_collapse"

# regroupements par type de volailles
volailles_pdl$espece_regrpt1 <- fct_collapse(volailles_pdl$libelle_espece,
                                                "b11-poulets" =  c("Poulets (yc coquelets)",
                                                                   "Chapons, poulardes",
                                                # "Coqs et poules de réforme",
                                                "Poules de réforme (filière œufs de consommation)",
                                                "Coqs et poules de réforme (reproducteurs)"),
                                                "c12-dindes" = c("Dindes"),
                                                # "3-pintades" = c("Pintades (yc chaponnées)"),
                                                "d14-canards" = c("Canards à rôtir","Canards gras"),
                                                # "5-oies" =  c("Oies à rôtir", "Oies grasses"),
                                                # "6-petites_volailles" =  c("Pigeons", "Cailles"),
                                                "e17-lapins" = c("Lapins")) 

Si on souhaite recoder une modalité de la variable en NA, il faut (contre intuitivement) lui assigner la valeur NULL.

hdv2003$qualif_rec <- fct_collapse(
    hdv2003$qualif, 
    NULL = "Autre"
)

À l’inverse, si on souhaite recoder les NA d’une variable, on utilisera la fonction fct_explicit_na, qui convertit toutes les valeurs manquantes (NA) d’un facteur en une modalité spécifique.

hdv2003$qualif_rec <- fct_explicit_na(hdv2003$qualif, na_level = "(Manquant)")

4.8 Recodification de variables numériques

La fonction cut() permet de découper une variable quantitative (de type numérique) en un certain nombre de classes (plages de valeurs).

Elle génère un objet de type factor (variable qualitative, variable catégorielle ordonnée). Les facteurs sont un type de variable ne pouvant prendre qu’un nombre défini de modalités nommés levels.

On précise les intervalles (les amplitudes ou les limites des classes) avec l’argument breaks (points de rupture).

Les symboles dans les noms attribués aux classes ont leur importance :
( signifie que la frontière de la classe est exclue,
tandis que [ signifie qu’elle est incluse.
Ainsi, (20,40] signifie « strictement supérieur à 20 et inférieur ou égal à 40 ».

right = FALSE pour fermé à gauche (c’est-à-dire incluant), ouvert à droite (c’est-à-dire n’incluant pas) [inf,sup)

On peut indiquer manuellement les noms des modalités avec labels (étiquettes).

Exemple : cut(variable_quanti, seq(from = 0, to = 120, by = 5)) => de 0 à 120 par paliers de 5

Pour inclure une valeur extrême, il convient parfois de préciser l’argument include.lowest = TRUE pour ne pas oublier d’inclure l’observation avec la valeur minimale (ou maximale, pour right = FALSE), puisque par défaut les intervalles ont des bornes ouvertes (c’est-à-dire n’incluant pas) à gauche.

exploit_anim_produit_grand_pdl <- exploit_anim_produit_grand_pdl |> 
  mutate(tr_sau = cut(sau_tot, 
                      breaks = c(0, 10, 20, 50, 100, 200, 250, Inf), 
                      right = FALSE,
                      labels = c("01_moins de 10 ha",
                                 "02_de 10 à moins de 20 ha",
                                 "03_de 20 à moins de 50 ha",
                                 "04_de 50 à moins de 100 ha",
                                 "05_de 100 à moins de 200 ha",
                                 "06_de 200 à moins de 250 ha",
                                 "07_250 ha ou plus")),
         .after=nom_dossier) 

exploitants_2010 <- fam_2010 |> 
  filter(famlien %in% c("10", "11", "12", "13"))  |> 
  mutate(nom_dossier = id_dossier,
         annee = annee_ra_2010,
         age = annee_ra_2010 - as.integer(famanais), 
         tr_age_10 = cut(age, 
                         breaks = c(0, seq(from = 40, to = 60, by = 10), Inf), 
                         right = FALSE,
                         labels = c("moins de 40 ans",
                                    "de 40 à 49 ans",
                                    "de 50 à 59 ans",
                                    "60 ans ou plus"))
         
# Levels: [0,40) [40,50) [50,60) [60,Inf]
# Levels: moins de 40 ans, de 40 à 49 ans, de 50 à 59 ans, 60 ans ou plus

https://juba.github.io/tidyverse/09-recodages.html#d%C3%A9couper-une-variable-num%C3%A9rique-en-classes

4.9 Somme des variables

On cherche à agréger des données.

  • summarise() : calculer une statistique résumé à partir de données
  • group_by() : faire des opérations par groupe

4.9.1 Globalement

La fonction summarise() permet d’agréger des données, en appliquant une fonction sur les variables pour construire une statistique sur les observations de la table. C’est une fonction dite de “résumé” (summary en anglais).

summarise(table_entree, NomVariableAgregee = Fonction(NomVariableEtude))

base_med <- base_extrait |>
  summarise(population_med = median(P14_POP, na.rm = TRUE))

La fonction n() permet de calculer les effectifs dans un summarise() :

donnees |>
  summarise(Effectifs = n())

4.9.2 Selon un facteur

La fonction summarise() couplée à group_by() permet de calculer des statistiques pour chaque modalité d’une variable qualitative. Avec group_by(), on précise les variables qui formeront des groupes, sur lesquels on appliquera une fonction :

TableauGroupes <- group_by(table_entree, Variable1, ..., VariableN)

summarise(TableauGroupes, NomVariableAgregee = Fonction (NomVariableEtude))

Par exemple, si on souhaite avoir la médiane de la variable P14_POP, pour chaque ZE et chaque région :

base_reg_ann <- base_extrait |> 
  group_by(ZE, REG) |>
  summarise(population_med = median (P14_POP, na.rm = TRUE))

Cette fonction summarise est en général utilisée avec group_by, puisqu’elle permet du coup d’agréger et résumer les lignes du tableau groupe par groupe. Si on souhaite calculer le délai maximum, le délai minimum et le délai moyen au départ pour chaque mois, on pourra faire :

flights |> 
  group_by(month) |> 
  summarise(
      max_delay = max(dep_delay, na.rm = TRUE),
      min_delay = min(dep_delay, na.rm = TRUE),
      mean_delay = mean(dep_delay, na.rm = TRUE)
  )

summarise dispose d’un opérateur spécial, n(), qui retourne le nombre de lignes du groupe. Ainsi si on veut le nombre de vols par destination, on peut utiliser :

flights |> 
  group_by(dest) |> 
  summarise(nb = n())
library(janitor) # pour round_half_up

# calcul somme pdl fm + evolution
viande_volaille_sum <- viande_volaille_tec_lib |> 
  group_by(reg) |>
  summarise(an_2021 = sum(an_2021), an_2020 = sum(an_2020)) |>
  mutate(espece = "07_Total") |> 
  ungroup()

# moyenne
# age moyen
age_moyen <- exploitants_2010 |> 
            mutate(annee = annee_ra_2010,
                  age = annee_ra_2010 - as.integer(famanais)) |>
  bind_rows(mo_2020 |> 
            mutate(annee = annee_ra_2020,
                  anais = replace(anais, which(anais == 0 | anais == 9999), NA), 
                  # il existe des valeurs nulles d'années de naissance (2 dans la Manche !)
                  age = annee_ra_2020 - anais)) |> 
  group_by(annee = factor(annee, levels = c(annee_ra_2010, annee_ra_2020)),
           nom = "âge moyen des chefs d'exploitation et coexploitants",
           .drop = FALSE) |> 
  summarise(age_moy = mean(age, na.rm = TRUE))

À noter que quand on veut compter le nombre de lignes par groupe, il est plus simple d’utiliser directement la fonction count. Cette fonction compte le nombre d’occurrences pour chaque modalité de la variable. La table en sortie contient deux variables : la variable pour laquelle on compte les modalités, et une variable appelée n qui contient les effectifs.

Ainsi le code suivant est identique au précédent :

flights |> 
  count(dest)

Possibilité d’ajouter une pondération, à l’aide du paramètre wt. Par exemple, si on veut calculer le nombre de vols par destination, en pondérant par le nombre de passagers :

flights |> 
  count(dest, wt = passengers)

On peut également utiliser count sur plusieurs variables. Les commandes suivantes comptent le nombre de vols pour chaque couple aéroport de départ / aéroport d’arrivée, et trie le résultat par nombre de vols décroissant. Ici la colonne qui contient le nombre de vols, créée par count, s’appelle n par défaut :

flights |> 
  count(origin, dest) |> 
  arrange(desc(n))

Pour calculer le nombre de vols selon le mois et ajouter une colonne comportant le pourcentage de vols annuels réalisés par mois.

flights |> 
  count(month) |> 
  mutate(pourcentage = n / sum(n) * 100)

Le verbe count() permet de compter les individus par modalité d’une colonne définie.

Il existe quelques paramètres qui peuvent s’avérer utiles :

`name =` : permet de nommer la colonne des effectifs obtenus (“effectifs”, par exemple).   
Par défaut, R la nomme `n`.  

`sort =` : booléen qui permet, s’il est à TRUE, de classer le tableau   
par ordre décroissant des valeurs de la colonne `n`   
(ou “effectifs” si on l’a renommée)
departements |>
  count(libreg, name = "effectifs", sort = TRUE)
# Nombre d'exploitations par département
nb_exploit_pdl_2020 <- exploit_pdl_2020 |>
  select(nom_dossier, dep) |>
  group_by(dep) |>
  summarise(nb_exploitations = n() ) |>
  ungroup()

# autre méthode
nb_exploit_pdl_2020_b <- exploit_pdl_2020 |>
  count(dep, name = "nombre total d'exploitations")

La fonction summarise() permet de calculer une ou plusieurs statistiques à partir de la table de données. Cette fonction est souvent utilisée après la fonction group_by() pour calculer des statistiques par groupe, et elle conduit à une agrégation de la table en fonction des groupes définis par la fonction group_by (par défaut une agrégation sur l’ensemble de la table). Le code suivant calcule le nombre total d’équipements dans la BPE sum(NB_EQUIP, na.rm = TRUE), et le nombre total de boulangeries sum(NB_EQUIP * (TYPEQU == "B203"), na.rm = TRUE).

bpe_ens_2018_tbl |> 
  summarise(
    NB_EQUIP_TOT = sum(NB_EQUIP, na.rm = TRUE),
    NB_BOULANGERIES_TOT = sum(NB_EQUIP * (TYPEQU == "B203"), na.rm = TRUE)
  )

La fonction n_distinct renvoie le nombre de valeurs distinctes d’un vecteur. On peut notamment l’utiliser dans un summarise.

Dans l’exemple qui suit on calcule, pour les trois aéroports de départ de la table flights le nombre de valeurs distinctes de l’aéroport d’arrivée :

flights |> 
  group_by(origin) |> 
  summarise(n_dest = n_distinct(dest))
# Définition d'un opérateur `%not_in%` pour plus de clarté dans les filtres
`%not_in%` <- purrr::negate(`%in%`)

# somme par reg
# n_distinct() pour compter le nombre d'observations (d'occurrences) distinctes (de valeurs uniques)
iaa_select_2_reg <- iaa_select_2 |> 
  filter(apet_regroup_1 %not_in% "02_Artisanat_commercial",
         apet_groupe %not_in% "11_Fabrication de produits à base de tabac") |> 
  group_by(reg) |> 
  mutate(nb_entrep_employeurs_reg = n_distinct(siren),
         nb_etab_employeurs_reg = sum(as.numeric(employeur)),
         eff3112_reg = sum(eff3112)) |> 
  ungroup() |> 
  distinct(reg, nb_entrep_employeurs_reg, nb_etab_employeurs_reg, eff3112_reg)

Il est possible d’utiliser un grand nombre de fonctions différentes avec summarise(). Ces fonctions peuvent être combinées entre elles, et il est possible d’en définir de nouvelles. Voici quelques fonctions courantes :

Fonction Code
Moyenne mean()
Médiane median()
Ecart-type sd()
Minimum min()
Maximum max()
Valeur de la première valeur first()
Valeur de la dernière valeur last()
Nombre de lignes n()
Nombre de valeurs distinctes n_distinct()
Somme sum()
Somme cumulée cumsum()

4.9.3 Fonctions mutate() et summarise()

Les fonctions mutate() et summarise() calculent toutes les deux de nouvelles variables. Il arrive donc fréquemment qu’on les confonde, ou qu’on ne sache pas laquelle il faut utiliser. Comme indiqué précédemment, l’une, mutate(), conduit à l’ajout d’une variable supplémentaire, l’autre, summarise(), définit une procédure d’agrégation de la donnée. Voici une règle simple pour savoir quelle fonction utiliser :

  • Si on souhaite résumer une information contenue dans une table, il faut utiliser summarise(). Exemple : calculer le nombre total d’équipements pour chaque commune.

    bpe_ens_2018_tbl |>  
      group_by(DEPCOM) |> 
      summarise(NB_EQUIP_TOT = sum(NB_EQUIP, na.rm = TRUE))
  • Si on souhaite ajouter une information dans une table (en conservant toutes les autres variables), il faut utiliser mutate(). Exemple : ajouter dans la table bpe_ens_2018_tbl une colonne donnant le nombre total d’équipements pour chaque commune.

    bpe_ens_2018_tbl |>  
    group_by(DEPCOM) |> 
    mutate(NB_EQUIP_TOT = sum(NB_EQUIP, na.rm = TRUE))

    Avec distinct en fin d’un traitement utilisant mutate, on obtient un résumé équivalent à summarise. distinct filtre les lignes du tableau pour ne conserver que les lignes distinctes, en supprimant toutes les lignes en double.

# méthode 1 nb exploitations pour la région
nb_ha_sau_2020_reg_a <- exploit_2020 |> 
  select(nom_dossier, siege_dep, sau_tot) |> 
  summarise(sau_ha = sum(sau_tot, na.rm = TRUE)) |> 
  mutate(territoire = "Pays de la Loire") 

# méthode 2 nb exploitations pour la région
nb_ha_sau_2020_reg_b <- exploit_2020 |> 
  select(nom_dossier, siege_dep, sau_tot) |> 
  mutate(territoire = "Pays de la Loire",
         sau_ha = sum(sau_tot, na.rm = TRUE)) |> 
  distinct(territoire, sau_ha)

# Réaliser un tableau sommant la livraison de lait de chèvre 
# pour les producteurs des Pays de la Loire 
# pour chaque mois des années 2020 et 2021
# passer la variable 'colldiff' de litre en hectolitre (100l), 
# et mettre le total dans une variable 'livraison'

# En 2 étapes
# étape 1
exploit_laits_mensuel_chevre_pdl <- exploit_laits_mensuel_chevre |> 
  filter(reg_prod == "PDL") |> 
  group_by(annee, mois) |> 
  mutate(livraison = sum(colldiff / 100, na.rm = TRUE)) |> 
  ungroup()

# étape 2
laits_mensuel_chevre_production_pdl <- exploit_laits_mensuel_chevre_pdl |> 
  distinct(reg_prod,annee, mois, livraison)

# En 1 étape
laits_mensuel_chevre_production_pdl_2 <- exploit_laits_mensuel_chevre |> 
  filter(reg_prod == "PDL") |> 
  group_by(annee, mois) |> 
  summarise(livraison = sum(colldiff / 100, na.rm = TRUE)) |> 
  ungroup()

On peut avoir des NA car les valeurs manquantes sont absorbantes.
Pour éviter cela, il convient d’ajouter na.rm = TRUE

library(janitor) # pour round_half_up

# nb exploitations par dep
nb_exploit_2020 <- exploit_2020 |> 
              mutate(annee = annee_ra_2020) |> 
              select(nom_dossier,
                     dep_2020 = siege_dep,
                     annee) |> 
  group_by(annee = factor(annee, levels = c(annee_ra_2020)), dep_2020,
           .drop = FALSE) |> 
  summarise(`nombre total d'exploitations` = n() )

# nb exploitations par dep
nb_exploit_2020_dep <- exploit_2020 |> 
  select(nom_dossier, siege_dep) |> 
  mutate(territoire = "Pays de la Loire",
         nb_total_exploitations = n() ) |> 
  distinct(territoire, nb_total_exploitations)

# somme des regroupements par abattoir par type de volailles
volailles_abattoir <- volailles_pdl |>   
  group_by(siret,espece_regrpt1) |>     
  filter(espece_regrpt1 %in% c("b11-poulets", "c12-dindes", "d14-canards", "e17-lapins"))  |> 
  mutate(tec_regrpt = sum(poids / 1000, na.rm = TRUE)) |> 
  filter(!is.na(tec_regrpt) & tec_regrpt != 0)  |> 
  select(reg, dep, siret, rsoc, espece_regrpt1, tec_regrpt) |> 
  distinct()

# moyenne
# calcul moyenne semestrielle
oeufs_cot_det_2022_2021_an_mois <- oeufs_cot_det_2022_2021_an_mois |>   
  mutate(moy_sem_1_2021 = rowMeans((across(an_2021_01:an_2021_06)), na.rm = TRUE),
         moy_sem_1_2022 = rowMeans((across(an_2022_01:an_2022_06)), na.rm = TRUE),
         moy_sem_2_2021 = rowMeans((across(an_2021_07:an_2021_12)), na.rm = TRUE),
         moy_sem_2_2022 = rowMeans((across(an_2022_07:an_2022_12)), na.rm = TRUE),
         moy_an_2021 = rowMeans((across(an_2021_01:an_2021_12)), na.rm = TRUE),
         moy_an_2022 = rowMeans((across(an_2022_01:an_2022_12)), na.rm = TRUE))
         
# calcul moyenne semestrielle
# avec rowwise() (fonction dplyr) et c_across() 
# pour travailler dans les lignes, en combinaison avec rowwise()
vol_2022_mois <- vol_2022_mois |>  
  mutate(moy_an = mois_total / 12) |> 
  rowwise() |> # considère chaque ligne comme un groupe
  mutate(moy_sem_1 = mean(c_across(mois_01:mois_06), na.rm = TRUE),
         moy_sem_2 = mean(c_across(mois_07:mois_12), na.rm = TRUE),
         moy_an_b = mean(c_across(mois_01:mois_12), na.rm = TRUE))

# rowwise() est juste une forme spéciale de regroupement,
# donc pour l'annuler, ajouter à la fin ungroup()

# avec rowMeans (fonction base R) et across()
vol_2021_mois <- vol_2021_mois |>   
            mutate(moy_sem_1 = rowMeans(across(mois_01:mois_06), na.rm = TRUE),
                   moy_sem_2 = rowMeans(across(mois_07:mois_12), na.rm = TRUE),
                   moy_an = mois_total / 12,
                   moy_an_b = rowMeans(across(mois_01:mois_12), na.rm = TRUE))

Pour distinct, l’option .keep_all = TRUE permet de conserver l’ensemble des colonnes du tableau d’origine, même si on ne spécifie qu’un sous-ensemble de colonnes pour déterminer les lignes distinctes.

library(dplyr)

# Exemple de tableau de données
data <- tibble(
  id = c(1, 1, 2, 2, 3),
  value = c("A", "A", "B", "B", "C"),
  extra = c(10, 20, 30, 40, 50)
)

# Utilisation de distinct() avec .keep_all = TRUE
distinct_data <- distinct(data, id, .keep_all = TRUE)

print(distinct_data)

Cela donne un tableau avec toutes les colonnes conservées, mais seulement les lignes distinctes basées sur la colonne id.

# Exemple d'utilisation de distinct() 
df |> distinct()  # sur toutes les colonnes
df |> distinct(id, group)  # colonnes spécifiques

# Options utiles de distinct()
df |> distinct(id, .keep_all = TRUE)  # garde les autres colonnes
df |> distinct(across(starts_with("group")))  # sélection flexible

4.9.4 Somme avec une pondération

# Somme dans le cas de variable pondérée
# Utilisation de la variable coef_f Coefficient d'extrapolation des données des modules
# Les variables collectées en échantillon uniquement sont suffixées _ech
# mopfamfil_ech : Emploi de la main d'oeuvre familiale de manière permanente
# mopnfamfil_ech : Emploi de la main d'oeuvre non familiale de manière permanente
nb_exploit_2020_mop_fam_reg <- exploit_2020 |> 
  select(nom_dossier, siege_dep, mopfamfil_ech, mopnfamfil_ech, poids = coef_f) |> 
  filter(poids != 0 & mopfamfil_ech == 1) |> 
  mutate(nb_exploit_mop_fam = sum(poids)) |> 
 distinct(nb_exploit_mop_fam)

nb_exploit_2020_mop_non_fam_reg <- exploit_2020 |> 
  select(nom_dossier, siege_dep, mopfamfil_ech, mopnfamfil_ech, poids = coef_f) |> 
  mutate(nb_exploit = n() ) |> 
  filter(poids != 0) |> 
  mutate(nb_exploit_ech = n() ) |>
  filter(mopnfamfil_ech == 1) |> 
  mutate(nb_exploit_mop_non_fam = sum(poids)) |> 
  distinct(nb_exploit, nb_exploit_ech, nb_exploit_mop_non_fam)
# nb exploitations avec signes de qualité ----
# coef_f    : Coefficient d'extrapolation des données
# qualca_ech : Part de la commercialisation sous AOP/AOC, IGP, Label Rouge ou Spécialité Traditionnelle Garantie
# dans le chiffre d'affaires de l'exploitation
# 10    moins de 25% 
# 20    plus de 25% à 50%
# 30    plus de 50% à 75%
# 40    plus de 75% à moins de 100%
# 50    100%
# 90    ne sait pas 

unique(exploit_pdl_2020$qualca_ech)

# méthode 1 avec group_by(variable_categorielle) puis sum(coef_f)
# Somme dans le cas de variable pondérée
# Utilisation de la variable coef_f Coefficient d'extrapolation des données
# Les variables collectées en échantillon uniquement sont suffixées _ech
nb_exploit_qualca_1 <- exploit_pdl_2020 |>
  select(nom_dossier, dep, qualca_ech, poids = coef_f) |>  
  filter(!is.na(qualca_ech)) |>
  mutate(part_commercialisation_qualite_dans_ca = case_match(qualca_ech,
     "10" ~ "1- moins de 25%",
     "20" ~ "2- plus de 25% à 50%",
     "30" ~ "3- plus de 50% à 75%",
     "40" ~ "4- plus de 75% à moins de 100%",
     "50" ~ "5- 100%",
     "90" ~ "6- ne sait pas")) |> 
  group_by(part_commercialisation_qualite_dans_ca) |> 
  summarise(nombre_exploitations = sum(poids, na.rm = TRUE)) |> 
  ungroup()

# méthode 2 avec count(variable_categorielle, wt = coef_f) 
nb_exploit_qualca_2 <- exploit_pdl_2020 |> 
  select(nom_dossier, dep, qualca_ech, poids = coef_f) |>  
  filter(!is.na(qualca_ech)) |>
  mutate(part_commercialisation_qualite_dans_ca = case_match(qualca_ech,
      "10" ~ "1- moins de 25%",
      "20" ~ "2- plus de 25% à 50%",
      "30" ~ "3- plus de 50% à 75%",
      "40" ~ "4- plus de 75% à moins de 100%",
      "50" ~ "5- 100%",
      "90" ~ "6- ne sait pas")) |> 
  count(part_commercialisation_qualite_dans_ca, wt = poids) |> 
  rename(nombre_exploitations = n) 

# calcul part
nb_exploit_qualca_part <- nb_exploit_qualca_1 |> 
  adorn_totals(where = "row",
               name = "Ensemble des exploitations avec signes de qualité") |> 
  mutate(part_nb_exploitations = nombre_exploitations / last(nombre_exploitations) *100) |> 
  mutate(across(where(is.numeric), \(x) round_half_up(x, 0)))

Pour le calcul de statistiques pondérées, on peut charger le package Hmisc :
Tableau de fréquence pondéré : wtd.table(var, weights = var_pond, na.rm = TRUE, type = “table”)
Moyenne pondérée : wtd.mean(var, weights = var_pond, na.rm = TRUE)
Quantiles pondérés : wtd.quantile(var, weights = var_pond, probs = proba, na.rm = TRUE)
où proba est compris entre 0 et 1 (0,10 pour le D1, 0,25 pour le Q1, 0,5 pour la médiane, 0,75 pour le Q3, 0,9 pour le D9…)

4.10 Faire des opérations par groupe : group_by()

La fonction group_by() permet de définir des groupes dans la table de données pour faire des opérations par groupe. Elle permet de définir des groupes de lignes (ou groupes de modalités) à partir des valeurs d’une ou plusieurs colonnes. L’utilisation de group_by() rend très utiles les opérations avec summarise(). Le code suivant groupe les données de la BPE par département group_by(DEP) puis calcule le nombre total d’équipements sum(NB_EQUIP, na.rm = TRUE) et le nombre total de boulangeries sum(NB_EQUIP * (TYPEQU == "B203"), na.rm = TRUE).

bpe_ens_2018_tbl |> 
  group_by(DEP) |>
  summarise(NB_EQUIP_TOT = sum(NB_EQUIP, na.rm = TRUE),
            NB_BOULANGERIES_TOT = sum(NB_EQUIP * (TYPEQU == "B203"), na.rm = TRUE))

La fonction group_by peut également modifier le comportement des fonctions filter ou mutate, comme pour summarise. L’instruction suivante permet ainsi d’ajouter à la BPE une variable égale au nombre total de boulangeries dans le département.

bpe_ens_2018_tbl |> 
  group_by(DEP) |>
  mutate(NB_BOULANGERIES_DEP = sum(NB_EQUIP * (TYPEQU == "B203"), na.rm = TRUE))

L’instruction suivante permet de ne conserver que les communes pour lesquelles le nombre d’équipements est le plus important de leur région.

bpe_ens_2018_tbl |> 
  group_by(REG) |>
  filter(NB_EQUIP == max(NB_EQUIP))

Ainsi, les opérations à la suite d’un group_by(), comme ici sum et max, sont calculées par groupe de lignes (ici suivant la colonne DEP).

On peut aussi directement renommer une variable dans l’opération group_by()

# dimension économique par département
dim_eco_dep <-  exploit_2020 |> 
  group_by(dep = siege_dep,
           dim = dimeco_coef2017) |> 
  summarise(n_exploit = n(),
            sau_ha = sum(sau_tot, na.rm = TRUE)) |> 
  ungroup()

Par exemple, si on applique slice à un tableau préalablement groupé, il va sélectionner les lignes aux positions indiquées pour chaque groupe. Ainsi la commande suivante affiche le premier vol de chaque mois, selon leur ordre d’apparition dans le tableau :

flights |> group_by(month) |> 
  slice(1)

En utilisant une variante comme slice_min ou slice_max, on peut sélectionner les lignes ayant les valeurs les plus grandes ou les plus petites pour chaque groupe.

df |> 
  slice_max(order_by = var, n = N)  # pour top N plus grandes valeurs
df |>
  slice_min(order_by = var, n = N)  # pour top N plus petites valeurs

Par exemple la commande suivante sélectionne, pour chaque mois de l’année, le vol ayant eu le retard le plus important.

flights |> group_by(month) |> 
  slice_max(dep_delay)
# Région avec la plus grande population totale
region_max_pop <- recensement |>
  group_by(nom_de_la_region) |>
  summarise(population_totale = sum(population_totale)) |>
  slice_max(order_by = population_totale, 
            n = 1, 
            with_ties = TRUE) # conserve les égalités si nécessaire  

# ou bien
region_max_pop <- recensement |>
  group_by(nom_de_la_region) |>
  summarise(population_totale = sum(population_totale)) |>
  arrange(desc(population_totale)) |>
  slice(1)

Remarques sur l’utilisation de group_by() :

  • si vous ne savez pas si une table comporte des groupes, vous pouvez afficher la liste des variables de groupe avec la fonction group_vars() ;
  • si vous appliquez une instruction group_by() à une table qui comporte déjà des groupes, alors les groupes sont redéfinis ;
  • il est prudent d’appliquer la fonction ungroup() à vos données une fois que les opérations par groupe ont été réalisées, afin que les opérations suivantes ne soient pas effectuées par groupe par mégarde.

Avec dplyr 1.1.0, on peut éviter d’utiliser group_by, en utilisant seulement .by :

library(dplyr)
x |>  
  summarise(Frequency = sum(Frequency), .by = Category)

On peut spécifier précisément le comportement de dégroupage de summarise en lui fournissant un argument supplémentaire .groups, qui permet de gérer les groupes en sortie :

  • "drop_last" : dégroupe seulement de la dernière variable de groupage, supprime un niveau de regroupement (paramétrage par défaut)
  • "drop" : dégroupe totalement le tableau résultat (équivaut à l’application d’un ungroup)
  • "keep" : conserve toutes les variables de groupage

Ainsi .groups = 'drop' évite d’avoir un regroupement résiduel qui pourrait interférer avec les étapes suivantes.

4.11 Trier un tableau selon des variables

  • arrange() : trier la table selon une ou plusieurs variables

Le verbe arrange() permet de réordonner les lignes (les observations) d’un tableau selon une ou plusieurs colonnes. Par défaut, arrange trie par ordre croissant. Il faut utiliser desc(nom_de_variable) pour trier par ordre décroissant (descending). Le code suivant trie la BPE selon le code commune et le type d’équipement.

bpe_ens_2018_tbl |> 
  arrange(DEPCOM, TYPEQU)

Voici quelques utilisations fréquentes de arrange() :

Action Code
Trier sur une colonne en ordre croissant arrange(NB_EQUIP)
Trier sur plusieurs colonnes en ordre croissant arrange(DEPCOM, NB_EQUIP)
Trier sur une colonne en ordre décroissant arrange(desc(NB_EQUIP))
# Définition d'un opérateur `%not_in%` pour plus de clarté dans les filtres
`%not_in%` <- purrr::negate(`%in%`)

agri_bio_vegetal_fr_tab2 <- agri_bio_vegetal_fr_cultures |> 
  filter(cultures_2 %not_in% c("autre cas - doublon")) |> 
  group_by(annee_2 = annee,territoire, cultures_2) |> 
  summarise(surface_bio_conversion_en_ha = sum(surface_bio_et_en_conversion_en_ha, na.rm = TRUE)) |> 
  arrange(desc(territoire), cultures_2)

Combiné avec slice, arrange permet par exemple de sélectionner les trois vols ayant eu le plus de retard :

tmp <- arrange(flights, desc(dep_delay))
slice(tmp, 1:3)

Spécificité pour les vecteurs factor (variable qualitative), le tri peut se réaliser avec l’instruction levels

Les facteurs sont un type de variable ne pouvant prendre qu’un nombre défini de modalités nommés levels.

library(dplyr)
library(tidyr)

# regroupements et pivot_wider
volailles_sum <- bind_rows(volailles_dep_sum,volailles_pdl_sum) |> 
  pivot_wider(names_from = dep,
              values_from = tec_regrpt) |> 
  mutate(espece_ordre = factor(espece,levels = c("a10-volailles",
                                               "b11-poulets",
                                               "c12-dindes", 
                                               "d14-canards", 
                                               "e17-lapins")))

On peut aussi ordonner les modalités d’un facteur, avec la fonction fct_relevel() du package forcats.

library(forcats) 

hdv2003$qualif_rec <- fct_relevel(
    hdv2003$qualif,
    "Cadre", "Profession intermediaire", "Technicien",
    "Employe", "Ouvrier qualifie", "Ouvrier specialise",
    "Autre"
)

Une autre possibilité est d’ordonner les modalités d’un facteur selon les valeurs d’une autre variable. Pour trier les modalités de l’âge par âge median croissant, on peut dans ce cas utiliser la fonction fct_reorder. Celle-ci prend 3 arguments : le facteur à réordonner, la variable dont les valeurs doivent être utilisées pour ce réordonnancement, et enfin une fonction à appliquer à cette deuxième variable.

library(forcats) 

hdv2003$occup_age <- fct_reorder(hdv2003$occup, hdv2003$age, median)

4.12 Changement de l’ordre des colonnes

relocate peut être utilisé pour réordonner les colonnes d’une table. Par défaut, si on lui passe un ou plusieurs noms de colonnes, relocate les place en début de tableau.

airports |>  
  relocate(lat, lon)

Les arguments supplémentaires .before et .after permettent de préciser à quel endroit déplacer la ou les colonnes indiquées.

# Réorganiser les colonnes
bio_vege2_dep_pdl_an_rang <- bio_vege2_dep_pdl_an_rang |>
  relocate(starts_with("rang_groupe_prod"), .after = sous_groupe_de_productions)
library(dplyr)
library(tidyr)
library(janitor) # pour round_half_up

# la fonction relocate() pour réorganiser les colonnes.
# Le comportement par défaut est de déplacer la ou les colonnes nommées vers la première position.
otex_regroupe <- otex_regroupe |>
  mutate(otex_lib_det = substr(otex_lib,8,60)) |>
  relocate(otex_lib, otex_lib_det, .after = otex_code_regr)

eff_volailles_evol_part <- eff_volailles_evol |> 
  select(-c(evo_percent, region,an_2020)) |> 
  pivot_wider(names_from = reg,
              names_prefix = '',
              names_sep = "_",
              values_from = c(an_2021,evol_2021_2020)) |> 
  mutate(part_pdl_france_2021 = paste0(round_half_up(an_2021_pdl / an_2021_fm * 100, 0)," %")) |>
  relocate(an_2021_pdl, evol_2021_2020_pdl, an_2021_fm,
           evol_2021_2020_fm, part_pdl_france_2021, .after = espece)

# succession de relocate
lait_chevre_dep_2021_donnees <- lait_chevre_dep3 |> 
  select(-c(reg_dep_prod, dep_libdep, producteurs_2020_old, livraisons_2020_old, lait)) |> 
  relocate(Code_département, Département, .before = producteurs_2010) |> 
  relocate(producteurs_2020, livraisons_2020,
           producteurs_2021, livraisons_2021, .after = livraisons_2019)
exports_pdl_2020_pays_vol_vivantes <- exports_pdl_an_pays_vol |>   
  select(annee, lib_cpf4, pays, lib_pays, valeur_cpf4_pays_reg, valeur_cpf4_pays_reg_pct) |> 
  filter(lib_cpf4 %in% c('Volailles vivantes et œufs'),
         annee == 2020) |> 
  # Réorganisation des colonnes pour le total pour s'assurer que "lib_cpf4", et "pays" restent à blanc
  relocate(annee, lib_cpf4, pays, .after = last_col()) |> 
  adorn_totals("row", name = "Pays de la Loire") |> 
  # Réinitialisation de la valeur du total pour la colonne "annee"
  mutate(annee = if_else(lib_pays == "Pays de la Loire", NA, annee)) |> 
  # Réorganisation des colonnes pour s'assurer que "annee", "lib_cpf4", et "pays" restent en tête
  relocate(annee, lib_cpf4, pays, .before = lib_pays)
# fonction avec comme paramètres annee et lib_cpf4
filtrer_an_type_produit <- function(data, 
                                    an, 
                                    type_produit) {
  data_an_type_produit <- data |>
    select(annee, type_de_produit = lib_cpf4, 
           lib_pays, 
           valeur_milliers = valeur_cpf4_pays_reg, 
           valeur_pourcent = valeur_cpf4_pays_reg_pct,
           valeur_pourcent_national = valeur_cpf4_pays_reg_fr_pct,
           poids_tonnes = masse_cpf4_pays_reg, 
           poids_pourcent = masse_cpf4_pays_reg_pct,
           poids_pourcent_national = masse_cpf4_pays_reg_fr_pct) |>
    filter(type_de_produit %in% type_produit,
           annee == an) |>
    # Réorganisation des colonnes pour le total pour s'assurer que "type_de_produit", et "pays" restent à blanc
    relocate(annee, type_de_produit, .after = last_col()) |>
    adorn_totals("row", name = "Pays de la Loire") |>
    # Réinitialisation de la valeur du total pour la colonne "annee"
    mutate(annee = if_else(lib_pays == "Pays de la Loire", NA, annee)) |>
    # Réorganisation des colonnes pour s'assurer que "annee", "type_de_produit", et "pays" restent en tête
    relocate(annee, type_de_produit, .before = lib_pays)
  
  return(data_an_type_produit)
}

# Utilisation de la fonction pour différents cas
exports_pdl_2020_pays_vol_vivantes <- filtrer_an_type_produit(exports_pdl_fr_an_pays_vol, 
                                                              2020, 
                                                              'Volailles vivantes et œufs')
exports_pdl_2021_pays_vol_vivantes <- filtrer_an_type_produit(exports_pdl_fr_an_pays_vol, 
                                                              2021, 
                                                              'Volailles vivantes et œufs')

4.13 Ajout de colonne Total

sum avec rowwise() au préalable, "rowwise"considère chaque ligne comme un groupe

# somme de chaque ligne
volailles_pdl_espece_siqo_col <- volailles_pdl_espece_siqo_col |> 
  rowwise() |> 
  mutate(autres_siqo = sum(aoc_aop, aut_signe_off ,aut_demarche, na.rm = TRUE),
         total_qualite = sum(label_rouge, aoc_aop, agri_bio, aut_signe_off, aut_demarche, na.rm = TRUE))
         
## Pour sum en ligne, il convient d'utiliser rowwise() au préalable
## lorsqu'on souhaite faire un calcul par LIGNE avec des fonctions "vectorielles"
## comme sum, mean ...
## il faut indiquer à R que l'on souhaite un calcul par ligne 
## et non par colonne (calcul par défaut) d'où l'instruction rowwise()
## avant le calcul 

bio_pdl_poires_pommes_abconv_v2 <- bio_pdl_poires_pommes |> 
  rowwise() |> 
  mutate(surface_tot = sum(surfab, surfc1, surfc2, surfc3))
  
## calcul surface total bio et conv avec + pour éviter rowwise et sum
surf_bio_pdl_poires_pommes_abconv <- bio_pdl_poires_pommes |> 
  mutate(surface_tot = surfab + surfc1 + surfc2 + surfc3) |> 
  group_by(dep) |> 
  summarise(surface_ab_conversion = sum(surface_tot)) |> 
  ungroup()
# regroupement par espece
# On souhaite calculer la somme non pas pour l’ensemble du tableau 
# mais pour chaque ligne. 
# Pour cela, on va utiliser la fonction rowwise() : 
# celle-ci est équivalente à un group_by() 
# qui créerait autant de groupes qu’il y a de lignes dans le tableau.
# Quant le tableau est groupé via un rowwise(), 
# les opérations s’effectuent sur un tableau constitué uniquement de la ligne courante.
# ungroup() pour quitter rowwise()
exploit_volailles_regroupt <- exploit_dont_volailles_2020 |> 
  rename(eff_05_poulettes = cheptq_711130,
         # eff_03_poulets_chair = cheptq_711140,
         eff_08_autres_volailles = cheptq_719010) |> 
  rowwise() |>
  mutate(eff_03_poulets_chair_poules_pondeuses = sum(cheptq_711140, cheptq_711110,
                                                     cheptq_711120, na.rm = TRUE),
         eff_02_dindes = sum(cheptq_711151, cheptq_711152, na.rm = TRUE),
         eff_06_oies = sum(cheptq_711310, cheptq_711330, cheptq_711340,
                           cheptq_711320, na.rm = TRUE),
         eff_01_canards = sum(cheptq_711410, cheptq_711411,
                              cheptq_711421, cheptq_711422, na.rm = TRUE),
         eff_07_pintades = sum(cheptq_711510, cheptq_711520, na.rm = TRUE)) |> 
  ungroup() |> 
  mutate(elevage_01_canards = if_else(eff_01_canards > 0, 1, 0),
         elevage_02_dindes = if_else(eff_02_dindes > 0, 1, 0),
         elevage_03_poulets_chair_poules_pondeuses = if_else(eff_03_poulets_chair_poules_pondeuses > 0, 1, 0),
         elevage_05_poulettes = if_else(eff_05_poulettes > 0, 1, 0),
         elevage_06_oies = if_else(eff_06_oies > 0, 1, 0),
         elevage_07_pintades = if_else(eff_07_pintades > 0, 1, 0),
         elevage_08_autres_volailles = if_else(eff_08_autres_volailles > 0, 1, 0),
         nb_exploit_01_canards_pdl = sum(elevage_01_canards),
         nb_exploit_02_dindes_pdl = sum(elevage_02_dindes),
         nb_exploit_03_poulets_chair_poules_pondeuses_pdl = sum(elevage_03_poulets_chair_poules_pondeuses),
         nb_exploit_05_poulettes_pdl = sum(elevage_05_poulettes),
         nb_exploit_06_oies_pdl = sum(elevage_06_oies),
         nb_exploit_07_pintades_pdl = sum(elevage_07_pintades),
         nb_exploit_08_autres_volailles_pdl = sum(elevage_08_autres_volailles))

4.14 Calcul de pourcentage

Calcul de pourcentage par rapport à une ligne spécifique

# Calcul de pourcentage par rapport à une ligne spécifique
# Créer les colonnes de pourcentage par rapport à la ligne "Toutes productions"
# Référence dynamique avec pull(.) :
# Récupère les valeurs de la colonne actuelle de la ligne où groupe_de_productions == "Toutes productions".
# pour chaque colonne, on divise la valeur par la valeur correspondante de "Toutes productions"
bio_dep_pdl_vege_surf_tot_bio <- bio_dep_pdl_vege_surf_tot_bio |>
  # Trouver les valeurs de référence pour "Toutes productions"
  mutate(across(starts_with("surf_tot_bio_"),
    .fns = \(x) x / bio_dep_pdl_vege_surf_tot_bio |>
      filter(groupe_de_productions == "Toutes productions") |>
      pull(.) * 100,
    .names = "{.col}_pct_row"
  ))

4.15 Regroupement de colonnes

bind_cols permet de concaténer des colonnes.

# regroupement PDL + France
agri_bio_vegetal_tab1_final <- bind_cols(agri_bio_vegetal_pdl_tab1_final, agri_bio_vegetal_fr_tab1_final) 

str(agri_bio_vegetal_tab1_final)

# exploitants
exploitants_tot_2020 <- bind_cols(
  mo_2020 |> 
    summarise(annee = annee_ra_2020,
              nom = "nombre de chefs d'exploitation et coexploitants",
              n_exploit = n() ),
  mo_2020 |> 
    filter(sex == "FALSE") |> 
    summarise(femmes = n() )) |> 
    mutate(pcent_f = femmes / n_exploit * 100) 

La fonction bind_cols() permet de juxtaposer des tables (qui doivent avoir le même nombre d’observations). Il est conseillé d’utiliser cette fonction avec une extrême précaution. En effet, cette fonction juxtapose les colonnes par position (la première ligne d’une table est juxtaposée à la première de l’autre table), sans aucun contrôle. Si les différentes tables ne sont pas triées de la même façon, la table de sortie sera incohérente. Pour rapprocher deux tables, il est fortement conseillé d’utiliser les fonctions de jointures : inner_join, left_join, full_join

bind_cols associe les lignes uniquement par position. Les lignes des différents tableaux associés doivent donc correspondre (et leur nombre doit être identique). Pour associer des tables par valeur, on doit utiliser des jointures.

5 Manipulation d’observations (lignes)

5.1 Ajout de ligne Total

La fonction adorn_totals du package janitor permet d’ajouter une ligne ou une colonne de totaux à un tableau de données (un data.frame ou un tibble).

adorn_totals(“row”) : ajoute une ligne “Total” en bas du tableau, qui contient la somme de chaque colonne.
adorn_totals(“col”) : ajoute une colonne “Total” à droite du tableau, qui contient la somme de chaque ligne.
adorn_totals(c(“row”, “col”)) : ajoute à la fois une ligne et une colonne de totaux.

Le paramètre name permet de choisir l’intitulé de la ligne (ou de la colonne) de total (par défaut “Total”).

library(dplyr)
library(tidyr)
library(janitor) 

# nb exploitations par département et pour la région
nb_exploit_2020_dep_reg <- exploit_2020 |> 
  select(nom_dossier, siege_dep) |> 
  group_by(territoire = siege_dep) |> 
  summarise(nb_total_exploitations = n() ) |> 
  ungroup() |> 
  adorn_totals("row", name = "Pays de la Loire")

# ajout ligne Total
nb_exploitants_age_sau_2010_2020 <- exploitants_age_sau_2010_2020 |> 
  pivot_wider(names_from = annee,
              names_prefix = 'an_',
              names_sep = "_",
              values_from = c(n_exploit,
                             sau_ha)) |> 
  mutate(n_exploit_an_2010_percent = round_half_up(n_exploit_an_2010 / sum(n_exploit_an_2010) * 100, 2),
         sau_ha_an_2010_percent = round_half_up(sau_ha_an_2010 / sum(sau_ha_an_2010) * 100, 2),
         n_exploit_an_2020_percent = round_half_up(n_exploit_an_2020 / sum(n_exploit_an_2020) * 100, 2),
         sau_ha_an_2020_percent = round_half_up(sau_ha_an_2020 / sum(sau_ha_an_2020) * 100, 2)) |> 
  adorn_totals("row", name="Ensemble") |> 
  relocate(tr_age_10,
           sau_ha_an_2010_percent,
           sau_ha_an_2020_percent,
           sau_ha_an_2010,
           sau_ha_an_2020,
           n_exploit_an_2010_percent,
           n_exploit_an_2020_percent,
           n_exploit_an_2010,
           n_exploit_an_2020)

laits_mensuel_chevre_livr_coll_large <- laits_mensuel_chevre_livr_coll |> 
  pivot_wider(names_from = annee,
              names_sep = "_",
              values_from = c(livraison, collecte)) |> 
  adorn_totals(where = "row",
               name = "Total")
               
# à partir du tableau transposé, ajout du total régional
lait_production_vache_large <- lait_production_vache_2 |> 
  select(-annee) |> 
  pivot_wider(names_from = type,
              values_from = c(livraison_reg, livraison_dep, livraison_dep_perc)) |> 
  adorn_totals(where = "row", name = "Pays de la Loire") |> 
  select(-c(starts_with("livraison_reg")))

5.2 Filtrer des observations

filter sélectionne des lignes d’une table selon une condition. On lui passe en paramètre un test, et seules les lignes pour lesquelles ce test renvoie TRUE (vrai) sont conservées.

La fonction filter() permet de sélectionner les observations, selon une ou plusieurs conditions.

library(janitor) # pour round_half_up

table_sortie <- filter(table_entree, condition1, ..., conditionN)

# Par exemple :
base_filter <- filter(base, dep == "01" & p14_pop > 10000)

s0 <- filter(selection, dep == "62")

s1 <- filter(selection, dep != "62") # tout sauf le 62
s2 <- filter(selection, dep %in% c("59", "62")) 
s3 <- filter(selection, !(dep %in% c("59", "62"))) 
s4 <- filter(selection, densite > 100) # l'urbain
s5 <- filter(selection, dep == "62" & densite > 100) # le pas-de-calais urbain
s6 <- filter(selection, dep == "62" | densite > 100) # le Pas-de-Calais et l'urbain

Attention à l’opérateur de comparaison : == et non pas =

%in% correspond à l’opérateur “appartient à”.

library(janitor) # pour round_half_up

# Définition d'un opérateur `%not_in%` pour plus de clarté dans les filtres
`%not_in%` <- purrr::negate(`%in%`)

agri_bio_vegetal_fr_tab1 <- agri_bio_vegetal_fr |>   
  filter(groupe_de_productions %in% c("Toutes productions")) |> 
  select(annee, territoire, nombre_de_producteurs,
         surface_bio_et_en_conversion_en_ha, surface_en_conversion_en_ha, surface_bio_en_ha,
         part_bio_de_la_sau_en_percent) |>  
  mutate(part_bio_sau_plus_percent = paste0(part_bio_de_la_sau_en_percent," %"))

# filtre des exploitations dont la ligne est differente de zero
pa_2020_canards_total_ligne_positif <-  pa_2020_canards_total_ligne |> 
  filter(!total_ligne == 0)

statut_percent <- statut |> 
  select(statut_jur = "Statut.juridique", an_2010 = '2010', an_2019 = '2019', an_2020 = '2020') |> 
  filter(statut_jur %not_in% c("Tous statuts")) |> 
  mutate(an_2010_percent = round_half_up((an_2010 / sum(an_2010) * 100), 2),
         an_2019_percent = round_half_up((an_2019 / sum(an_2019) * 100), 2),
         an_2020_percent = round_half_up((an_2020 / sum(an_2020) * 100), 2)) 

# exploitants - coexploitants (age) + dim eco 
exploitants_age_dim_hors_micro_2010_2020 <- bind_rows(exploitants_2010,exploitants_2020) |> 
  filter(dimeco_coef2017 %not_in% c("1-micros"))  |> 
  group_by(annee = factor(annee, levels = c(annee_ra_2010, annee_ra_2020)),
           tr_age_10) |>
  summarise(n_exploit = n(),
            .groups = "drop_last")

# Créer le sous-tableau contenant l'établissement de collecte ou le producteur en Pays de la Loire
# Pour des conditions non cumulatives (au moins une des conditions doit être remplie), le ou “|”
laits_mensuel_3 <- laits_mensuel_2 |> 
  mutate(dep_etab = substr(sircodpost,1,2),
         dep_prod = substr(dep,2,3)) |> 
 filter(dep_etab %in% c("44","49","53","72","85") | dep_prod %in% c("44","49","53","72","85"))

# On ne conserve que les secteurs A01Z (Produits de la culture et de l’élevage) et C10, C11, C12
# C11 Boissons ; C12 Tabacs 
exports_fr_ans_az_c1 <- exports_fr_ans |>   
  mutate(a_129_3_positions = substr(a_129, 1, 3)) |> 
  filter(a_129 %in% c('A01Z') |
         a_129_3_positions %in% c('C10', 'C11', 'C12')) 

Autre exemple. On va ici récupérer les analyses produites par l’ARS

ars <- filter(prelevement, code_reseau == "ARS")

L’exemple ci-dessus n’exerce un filtre que sur une condition unique.

Pour des conditions cumulatives (toutes les conditions doivent être remplies), le "&" ou la "," Si on passe plusieurs arguments à filter, celui-ci rajoute automatiquement une condition et entre les conditions.

ars <- filter(prelevement, code_reseau == "ARS", code_intervenant == "44")

Pour des conditions non cumulatives (au moins une des conditions doit être remplie), le “|”

ars <- filter(prelevement, code_reseau == "ARS" | code_reseau == "FREDON")

Si une condition non cumulative s’applique sur une même variable, privilégier un test de sélection dans une liste avec le %in% pour l’opérateur “appartient à”.

ars <- filter(prelevement, code_reseau %in% c("ARS", "FREDON"))

Pour sélectionner des observations qui ne répondent pas à la condition, le ! (la négation d’un test)

Toutes les observations ayant été réalisées par un autre réseau que l’ARS :

non_ars <- filter(prelevement, code_reseau != "ARS")

Toutes les observations ayant été réalisées par un autre réseau que l’ARS ou FREDON :

ni_ars_ni_fredon <- filter(prelevement, !(code_reseau %in% c("ARS", "FREDON")))

Le verbe filter() permet de sélectionner des observations selon une ou plusieurs conditions logiques. Voici un exemple de code qui sélectionne les magasins de chaussures (TYPEQU == "B304") dans le premier arrondissement de Paris (DEPCOM == "75101") dans la BPE.

bpe_ens_2018_tbl |> 
  filter(DEPCOM == "75101" & TYPEQU == "B304")

Voici quelques utilisations fréquentes de filter() :

Action Code
Filtrer sur les modalités qualitatives d’une colonne filter(DEP %in% c("75", "92"))
Filtrer sur les modalités quantitatives d’une colonne filter(NB_EQUIP == 1)
Filtrer sur une variable caractère filter(str_detect(TYPEQU, "^A"))
Filtrer sur deux conditions (et) filter(DEP == "75" & NB_EQUIP == 1)
Filtrer sur une alternative (ou) filter(DEP == "75" | NB_EQUIP == 1)
Conserver les observations pour lesquelles la variable est manquante filter(is.na(pop_2016))
Conserver les observations pour lesquelles la variable est renseignée filter(!is.na(pop_2016))

On peut également placer des fonctions dans les tests, qui permettent par exemple de sélectionner les vols ayant une distance supérieure à la distance médiane :

filter(flights, distance > median(distance))

5.3 Sélection de lignes avec slice

Le verbe slice de dplyr sélectionne des lignes du tableau selon leur position. On lui passe un chiffre ou un vecteur de chiffres.
Cette fonction permet de sélectionner des observations (et donc d’en supprimer).

Si on souhaite sélectionner la 345e ligne du tableau airports :

slice(airports, 345)

Si on veut sélectionner les 5 premières lignes :

slice(airports, 1:5)

On peut avoir intérêt à sélectionner des lignes aléatoires afin d’avoir une plus grande variété de données :

# Inspection aléatoire
recensement_communes  |> 
 slice_sample(n = 10)

5.3.1 Sélection des n premières lignes avec slice_head

slice propose plusieurs variantes utiles, dont slice_head et slice_tail, qui permettent de sélectionner les premières ou les dernières lignes du tableau (on peut spécifier le nombre de lignes souhaitées avec n, ou la proportion avec prop).

slice_head(airports, n = 3)
slice_head(airlines, prop = 0.2)
# lait_chevre_mensuel, sélection des 13 premières lignes
lait_chevre <- read.xlsx(xlsxFile = path(chemin_entree, fichier_entree), 
                        sheet = "lait chevre", colNames = TRUE, startRow = 8) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  slice_head(n = 13) |> 
  # Quand on passe une fonction comme argument à une autre fonction, on utilise la notation sans les parenthèses.
  mutate(across(-x1, as.numeric)) 

Autres variantes utiles, slice_min et slice_max permettent de sélectionner les lignes avec les valeurs les plus grandes ou les plus petite d’une variable donnée. Ainsi, la commande suivante sélectionne le vol ayant le retard au départ le plus faible.

slice_min(flights, dep_delay)

On peut aussi spécifier le nombre de lignes souhaitées, par exemple la commande suivante retourne les 5 aéroports avec l’altitude la plus élevée (en cas de valeurs ex-aequo, il se peut que le nombre de lignes retournées soit plus élevé que celui demandé).

slice_max(airports, alt, n = 5)

5.4 Regroupement de lignes (par exemple pour concaténer deux tables)

Le package dplyr propose la fonction bind_rows() pour superposer deux ou plusieurs tables (en empilant des observations). Les colonnes ayant le même nom vont s’empiler. Les autres vont se compléter avec des valeurs manquantes.

Remarques sur l’utilisation de cette fonction :

  • bind_rows() combine les tables en fonction du nom des colonnes, l’ordre des colonnes n’a pas donc d’importance
  • si une colonne est manquante dans une des tables, alors des valeurs manquantes (NA) sont automatiquement insérées dans la table de sortie
  • pour que la concaténation fonctionne, il est indispensable que les types de variables soient bien les mêmes dans les tables.
library(janitor) # pour round_half_up

# 1- Dans ce tableau ne conserver que l'année, le département de la production, 
# le type (VB et VNB), les totaux départementaux et régionaux
lait_production_vache_type <- exploit_lait_production_vache_type |>
  group_by(type) |>
  mutate(livraison_reg = sum(colldiff, na.rm = TRUE)) |>
  ungroup() |> 
  group_by(type,dep_prod) |> 
  mutate(livraison_dep = sum(colldiff, na.rm = TRUE)) |>
  ungroup() |> 
  distinct(annee, dep_prod, type, livraison_reg, livraison_dep)
  
# 2- Réaliser un tableau similaire pour l'ensemble des vaches 
# (sans distinguer VB et VNB, en indiquant type = "vache")
lait_production_vache_ensemble <- lait_production_vache_1 |>
  mutate(type = "vache",
         livraison_reg = sum(colldiff / 100, na.rm = TRUE)) |>
  group_by(dep_prod) |> 
  mutate(livraison_dep = sum(colldiff / 100, na.rm = TRUE)) |>
  ungroup() |> 
  distinct(annee, dep_prod, type, livraison_reg, livraison_dep)

# 3- Regrouper les 2 tableaux agrégés : tableau pour l'ensemble des vaches 
# + tableau distinguant VB et VNB
lait_production_vache <-  bind_rows(lait_production_vache_ensemble,lait_production_vache_type) 

# statut
statut_percent <- statut |> 
  select(statut_jur = "Statut.juridique", an_2010 = '2010', an_2019 = '2019', an_2020 = '2020') |> 
  filter(statut_jur %not_in% c("Tous statuts")) |> 
  mutate(an_2010_percent = round_half_up((an_2010 / sum(an_2010) * 100), 2),
         an_2019_percent = round_half_up((an_2019 / sum(an_2019) * 100), 2),
         an_2020_percent = round_half_up((an_2020 / sum(an_2020) * 100), 2)) 

statut_complet <- statut_percent |> 
  bind_rows(statut |> 
              select(statut_jur = "Statut.juridique", an_2010 = '2010', an_2019 = '2019', an_2020 = '2020') |> 
              filter(statut_jur %in% c("Tous statuts")) |> 
              mutate(an_2010_percent = 100, an_2019_percent = 100, an_2020_percent = 100)  
  )

# agrégation données
emploi_salarie <- bind_rows(a_agri_long, b_iaa_long, cz_ind_tot_long, d_constr_long,
                            e_commerce_long, fz_serv_march_tot_long, fz_serv_non_march_tot_long)

# regroupement PDL + France 2021
groupe_produits_laitiers_2021 <- bind_rows(groupe_produits_laitiers_2021_France,
                                           groupe_produits_laitiers_2021_PDL)

# regroupements de lignes possibles aussi avec bind_rows
volailles_sum <- bind_rows(volailles_dep_sum,volailles_pdl_sum)

Il peut être utile, quand on concatène des lignes, de garder une trace du tableau d’origine de chacune des lignes dans le tableau final. C’est possible grâce à l’argument .id de bind_rows. On passe à cet argument le nom d’une colonne qui contiendra l’indicateur d’origine des lignes :

bind_rows(t1, t2, t3, .id = "source")

Par défaut la colonne .id ne contient qu’un nombre, différent pour chaque tableau. On peut lui spécifier des valeurs plus explicites en “nommant” les tables dans bind_rows de la manière suivante :

bind_rows(table1 = t1, table2 = t2, table3 = t3, .id = "source")

5.5 Ajout d’une ligne

La fonction add_row fait partie du package tibble et permet d’ajouter une ligne à un tibble.

# ajout 0 pour R52 pour RUB144500   Fromages de vache à pâte persillée     
produits_laitiers_2021_1b <- produits_laitiers_2021_1 |> 
  add_row(sir_regn = 52, rub = "144500",
          lib = "Fromages de vache à pâte persillée",
          finiqte = 0, finiqte1 = 0, nbfiniqte = 0, nbfiniqte1 = 0)

6 Transposition

Les deux principales opérations de restructuration des données peuvent être illustrées par les deux transformations suivantes :

  • Transformation wide to long :

  • Transformation long to wide :

6.1 Transposition de colonnes en lignes (plus de lignes)

pivot_longer : transformer des colonnes en lignes. On fait pivoter le tableau de départ d’un format “large” (avec plus de colonnes) vers un format “long” (avec plus de lignes).

La fonction pivot_longer permet de restructurer des données en transformant des colonnes en lignes. Cette fonction prend quatre arguments principaux :

  • le data.frame (ou le tibble) auquel elle est appliquée ;
  • cols : noms des colonnes à pivoter,
    un vecteur contenant le nom des colonnes dont les valeurs vont être transposées ;
  • names_to : nom de la première colonne à créer qui prendra comme modalités les actuels noms des colonnes pivotantes ;
  • values_to : nom de la deuxième colonne à créer qui recevra les valeurs des colonnes pivotantes.

La fonction pivot_longer prend comme premier argument cols contenant la liste des colonnes à rassembler.
On peut spécifier les colonnes à exclure en utilisant le signe négatif - suivi des noms des colonnes à exclure (avec cols = - ).

names_prefix : préfixe à enlever des noms de colonnes à pivoter, qui seront transformées en modalités.

library(dplyr)
library(tidyr) # pour pivot

# pivot_longer
# colonnes donnees en ligne
agri_bio_vegetal_fr_tab1_long <- agri_bio_vegetal_fr_tab1_car |>   
  pivot_longer(cols = c("nombre_de_producteurs", "surface_bio_et_en_conversion_en_ha",
                      "surface_en_conversion_en_ha", "surface_bio_en_ha",
                      "part_bio_sau_plus_percent"),
               names_to = c("vegetal"),
               values_to = "donnees") |> 
  arrange(desc(territoire))

# colonne espece en ligne
volailles_pdl_espece <- volailles_pdl_siqo2 |>   
  pivot_longer(cols = starts_with("espece_"),
               names_to = c("espece","siqo"),
               names_sep = "_siqo_",
               names_prefix = "espece_",
               values_to = "tec")

volailles_pdl_espece <- volailles_pdl_espece |> 
  mutate(tec = tec / 1000)

# nouvelles variables mois, type(avec nombre, poids) en enlevant suffixe mois, type par transposition
vol_2022 <- vol_2022 |>   
             pivot_longer(cols = starts_with('x2022'), 
                          names_prefix = "x2022_", 
                          names_to = c("mois", "type"), 
                          names_sep = "_",
                          values_to = "effectif")
  
# un seul montant en colonne
# nouvelle variable 'montant' en mettant la liste des montants par trimestre par transposition
# crée variables "trimestre"(modalités 'x1','x2','x3','x4') et 
# "annee" à partir de c('x4t2018','x1t2019'...)  
produits_long <- produits |>   
             pivot_longer(cols = c('x4t2018','x1t2019','x2t2019','x3t2019','x4t2019',
                                   'x1t2020','x2t2020','x3t2020','x4t2020',
                                   'x1t2021','x2t2021','x3t2021','x4t2021',
                                   'x1t2022','x2t2022','x3t2022'), 
                          names_to = c("trimestre","annee"),
                          names_sep = "t",
                          values_to = "montant")

# une seule espece en colonne
# nouvelle variable espece en mettant la liste des especes par transposition
itavi_2022_2021_espece <- itavi_2022_2021 |>   
             pivot_longer(cols = c(poulet_standard, poulet_label_rouge, dinde,pintade,
                                   poule_pondeuse, canard_rotir, canard_gras, lapin,
                                   poulet_label_rouge_non_ogm, poule_pondeuse_label_rouge_non_ogm), 
                          names_to = c("espece"),
                          values_to = "indice")

# colonnes donnees en ligne
# names_pattern = "(.*)_dep" pour enlever suffixe "_dep"
# names_pattern = paste0("(.*)_", suffixe)
agreg_anim_produit_petit_dep_1 <- agreg_anim_produit_petit_pdl |> 
  select(-contains("reg")) |> 
  pivot_longer(cols = contains("_dep"),
               names_to = "indicateur",
               values_to = "donnees_dep",
               names_pattern = "(.*)_dep")

On souhaite obtenir une nouvelle table, avec une observation par EPCI et par tranche d’âge. Voici le code qui permet d’obtenir cette table : on transpose les valeurs des colonnes dont le nom commence par “TP” (cols = starts_with("TP")), le nom des colonnes transposées sera indiquée dans la nouvelle colonne “tranche_age” (names_to = "tranche_age") et les valeurs des colonnes transposées seront indiquées dans la colonne “taux_pauvrete” (values_to = "taux_pauvrete").

library(dplyr)
library(tidyr)

donnees_pauvrete_long <- filosofi_epci_2016_tbl |> 
  pivot_longer(cols = starts_with("TP"), 
               names_to = "tranche_age", 
               values_to = "taux_pauvrete")

L’argument values_drop_na de la fonction pivot_longer permet de gérer les valeurs manquantes (NA).

Il détermine si les lignes contenant des NA dans la colonne des valeurs (spécifiée par values_to) doivent être conservées ou supprimées du résultat final après le pivot.

Si values_drop_na = FALSE (valeur par défaut), toutes les lignes sont conservées, y compris celles avec des NA.

Lorsqu’on utilise values_drop_na = TRUE, les lignes où la colonne spécifiée par values_to contient des NA seront exclues du tableau final. Le résultat ne contient pas des lignes avec des NA dans la colonne indiquée dans le paramètre values_to.

6.1.1 Utilisation de names_pattern dans pivot_longer

Lors de la transformation d’un data frame d’un format large à un format long avec pivot_longer, l’argument names_pattern permet de spécifier une expression régulière pour capturer et transformer les noms de colonnes.

  • Objectif :
    • Extraire des parties spécifiques des noms de colonnes pour les utiliser dans les nouvelles colonnes créées.
    • Supprimer des préfixes ou suffixes communs.
  • Syntaxe :
    • Utilise des groupes de capture (.*) pour extraire les parties souhaitées des noms de colonnes.
    • Exemple : names_pattern = "prefixe_(.*)_suffixe" capture la partie centrale entre prefixe_ et _suffixe.
  • Avantages :
    • Flexibilité : permet de gérer des noms de colonnes complexes avec des préfixes et suffixes variés.
    • Précision : capture uniquement les parties nécessaires, facilitant l’analyse et la visualisation des données.
# Extraction des parties centrales des noms de colonnes
# Création de nouvelles colonnes avec les parties centrales extraites
# Les préfixes et suffixes sont ignorés dans le processus de transformation.
# (.*) : capture toute séquence de caractères (y compris aucun caractère)
# Le point . représente n'importe quel caractère (sauf un retour à la ligne).
# L'astérisque * signifie "zéro ou plusieurs fois".
# Les parenthèses () autour de .* créent un groupe de capture, 
# ce qui signifie que cette partie de la chaîne sera extraite et utilisée 
#pour remplir la nouvelle colonne indicateurs.
iaa_vol_pdl_long <- iaa_vol_total_pdl |>
  pivot_longer(
    cols = contains("indicateur"),  # Sélectionne les colonnes contenant "indicateur"
    names_to = "indicateurs",
    values_to = "volailles",
    names_pattern = "pre_(.*)_suf"  # Capture la partie centrale entre "pre_" et "_suf"
  ) |>
  select(indicateurs, volailles)
# tous les noms de colonnes contenant '_vol' doivent aller dans la colonne 'vol' 
# et tous les noms de colonnes contenant '_reg' doivent aller dans la colonne 'reg'. 
# `names_pattern = "(.*)_(.*)"` spécifie comment diviser les noms des colonnes. 
# Le groupe (.*) correspond au nom de la colonne avant le suffixe (“_vol” ou “_reg”).
# Le paramètre names_pattern permet de spécifier un suffixe commun à enlever des noms des modalités.
# Il extrait la partie du nom de colonne avant "_vol" et la stocke dans la colonne "indicateurs".
iaa_vol_pdl_long <- iaa_vol_total_pdl |> 
  pivot_longer(cols = ends_with("_vol"),
               names_to = "indicateurs", 
               values_to = "volailles",
               names_pattern = "(.*)_vol$") |> 
  select(indicateurs, volailles)

iaa_total_pdl_long <- iaa_vol_total_pdl |> 
  pivot_longer(cols = ends_with("_reg"), 
               names_to = "indicateurs", 
               values_to = "ensemble",
               names_pattern = "(.*)_reg$") |> 
  select(indicateurs, ensemble)

iaa_vol_total_pdl_long <- iaa_vol_pdl_long |> 
  full_join(iaa_total_pdl_long,
            join_by(indicateurs),
            relationship = "one-to-one") |> 
  arrange(indicateurs)

# autre solution
# names_pattern = "(.*)_(vol$|reg$)" : pour séparer le nom de la colonne initiale en deux parties :
# la partie avant le dernier underscore (tout ce qui précède _(vol$|reg$) sera stockée dans "indicateurs", 
# et la partie après le dernier underscore sera stockée dans "type", 
# soit "vol" soit "reg" à la fin du nom de la colonne.
iaa_vol_total_pdl_long2 <- iaa_vol_total_pdl |> 
  pivot_longer(cols = -c(regsiege, libreg),
               names_to = c("indicateurs", "type"), 
               values_to = "valeurs", 
               names_pattern = "(.*)_(vol$|reg$)") |> 
  pivot_wider(names_from = type,
              values_from = valeurs) |> 
  arrange(indicateurs)

6.2 Transposition de lignes en colonnes (plus de colonnes)

pivot_wider : transformer des lignes en colonnes. On fait pivoter le tableau de départ d’un format “long” (avec plus de lignes) vers un format “large” (avec plus de colonnes).

La fonction pivot_wider permet de restructurer des données en transformant des lignes en colonnes. Cette fonction prend quatre arguments principaux :

  • le data.frame (ou le tibble) auquel elle est appliquée ;
  • id_cols : un vecteur contenant le nom des colonnes qui définissent les observations de la table transposée ;
  • names_from : nom de la variable dont les modalités deviendront les intitulés des futures colonnes,
    un vecteur contenant le nom de la (ou des) colonne(s) qui donne(nt) les noms des nouvelles colonnes à créer
    Les valeurs uniques de cette colonne deviendront les noms de colonnes dans le dataframe résultant ;
  • values_from : nom de la variable à utiliser pour remplir les colonnes,
    un vecteur contenant le nom de la (ou des) colonne(s) dont les valeurs vont être transposées.

L’option names_prefix permet de définir le préfixe du nom des nouvelles colonnes, ce qui est utile pour avoir des noms explicites.

L’option values_fill permet de spécifier comment remplir les cases vides (par exemple, si certaines combinaisons de valeurs ne sont pas présentes dans les données d’origine). Par défaut, les cases vides sont remplies avec des NA.

library(dplyr)
library(tidyr) # pour pivot
library(janitor) # pour round_half_up

bpe_ens_2018_tbl |>
    group_by(REG, TYPEQU) |>
    summarise(NB_EQUIP_TOT = sum(NB_EQUIP)) |>
    pivot_wider(id_cols = TYPEQU,  
                names_from = REG, 
                values_from = NB_EQUIP_TOT, 
                names_prefix = "nb_equip_reg")

# pivot_wider
volailles_sum <- bind_rows(volailles_dep_sum, volailles_pdl_sum) |> 
  pivot_wider(names_from = dep,
              values_from = tec_regrpt) 

# transposition lignes vers colonnes
produits_a17_large <- produits_a17 |>
   pivot_wider(names_from = c(annee,flux,semestre), 
               values_from = valeur, 
               names_prefix = "an_"
              ) |>
   mutate(an_2021_imports = an_2021_I_sem1 + an_2021_I_sem2,
          an_2022_imports = an_2022_I_sem1 + an_2022_I_sem2,
          an_2021_exports = an_2021_E_sem1 + an_2021_E_sem2,
          an_2022_exports = an_2022_E_sem1 + an_2022_E_sem2,
          .after=a17_lib_detail)

# territoire en colonne 
agri_bio_vegetal_fr_tab1_final <- agri_bio_vegetal_fr_tab1_long |>   
  pivot_wider( names_from = "territoire", 
               names_prefix = "",
               values_from = "donnees") 

# Transposer le tableau en format plus large, à partir de la variable type
lait_production_vache_large <- lait_production_vache_2 |> 
 pivot_wider(names_from = type,
             values_from = c(livraison_reg,livraison_dep,livraison_dep_perc))

# Le format large est approprié pour calculer des évolutions
# Calculer l'évolution de la production de lait et de la collecte de lait 
# entre 2020 et 2021, en hl et en %
laits_mensuel_chevre_livr_coll_large_2 <- laits_mensuel_chevre_livr_coll_large_1 |> 
  mutate(livr2021_evol = livraison_2021 - livraison_2020,
         livr2021_evol_perc = (livraison_2021 / livraison_2020 - 1) * 100,
         coll2021_evol = collecte_2021 - collecte_2020,
         coll2021_evol_perc = (collecte_2021 / collecte_2020 - 1) * 100) |> 
  mutate(across(c(livr2021_evol_perc,coll2021_evol_perc), \(x) round_half_up(x, digits = 3))) |> 
  mutate(across(c(livraison_2020, livraison_2021,
                  collecte_2020, collecte_2021, livr2021_evol, coll2021_evol),
                \(x) round_half_up(x, digits = 3)))
  
# colonne an_mois en colonne
oeufs_2022_2021_an_mois <- oeufs_2022_2021 |>   
             pivot_wider(names_from = c(Année,mois),
                         names_prefix = "an_",
                         values_from = c(Exportation_Nombre, Importation_Nombre, solde))

eff_volailles_evol_part <- eff_volailles_evol |> 
  select(-c(evo_percent, region, an_2020)) |> 
  pivot_wider(names_from = reg,
              names_prefix = '',
              names_sep = "_",
              values_from= c(an_2021, evol_2021_2020)) |> 
  mutate(part_pdl_france_2021 = paste0(round_half_up(an_2021_pdl / an_2021_fm * 100, 0), " %")) |>
  relocate(an_2021_pdl, evol_2021_2020_pdl, an_2021_fm,evol_2021_2020_fm,
           part_pdl_france_2021, .after = espece)

laits_mensuel_chevre_livr_coll_large <- laits_mensuel_chevre_livr_coll |> 
  pivot_wider(names_from = annee,
              names_sep = "_",
              values_from = c(livraison,collecte)) |> 
  adorn_totals(where = "row",
               name = "Total")

# passage au format large pour dep
exploit_otex_dep <- exploit_pdl |>
  arrange(desc(dep), otex_regroup) |> 
  pivot_wider(id_cols = otex_regroup,  
              names_from = dep, 
              values_from = c03_n_exploit_dep, 
              names_prefix = "dep_")

# on transpose cult_pdl_2020 pour avoir une seule ligne par exploitation (nom_dossier)
# les valeurs manquantes sont remplacées par la valeur 0
nom_dossier_cult_pdl <- cult_pdl_2020 |> 
  pivot_wider(id_cols = nom_dossier,  
              names_from = cult_ra_code, 
              values_from = c(cultsur, irrisur), 
              names_prefix = "",
              values_fill = 0)
# enchainement de pivots
# en général pivot_longer avant pivot_wider : cela augmente le nombre de lignes
# et diminue le nombre de colonnes, ce qui est généralement plus facile à gérer
# pour ensuite pivoter les données en utilisant pivot_wider.
# Pivoter de manière à avoir les indicateurs surf_2023 et prod_2023 en lignes,
# et les valeurs de dep en colonnes 
surf_fruits_tonnes_large <- surf_fruits_tonnes |>
  # on va d’abord transformer les colonnes surf_2023 et prod_2023 
  # en une colonne indicateur 
  # avec les valeurs correspondantes dans une colonne valeur.
  pivot_longer(cols = c(surf_2023, prod_2023), 
               names_to = "indicateur", 
               values_to = "valeur") |>
  # Ensuite, on va pivoter ces données 
  # pour que les valeurs de dep deviennent des colonnes
  pivot_wider(names_from = dep, values_from = valeur)

# autre enchainement de pivots
otex_regroupe <- otex_transp  |> 
  select(-c(ends_with('temp'), otex_4813, otex_4840, otex_5100, otex_5374)) |> 
  pivot_longer(cols = -c(annee), 
               names_to = c("otex_code_regr"),
               values_to = "etp") |> 
  pivot_wider(names_from = annee,
              values_from = c(etp))

# enchainement de pivots
# ici pivot_wider avant pivot_longer 
# on remet temporairement import export en colonne pour calculer le solde
# Étape 1 : Pivoter pour avoir Export et Import en colonnes
# on transforme les colonnes flux (qui contiennent Export et Import) en colonnes distinctes, 
# avec les valeurs correspondantes dans montant.
produits_large <- produits_long_AZ_C1 |> 
   pivot_wider(names_from = flux, 
               values_from = montant) |> 
   mutate(solde = Export - Import)  # Étape 2 : Calculer le solde

# Étape 3 : Revenir au format long
# on reconvertit les colonnes Export, Import et solde en une colonne flux 
# avec les valeurs correspondantes dans montant.
produits_long_AZ_C1 <- produits_large |>   
   pivot_longer(cols = c('Export', 'Import', 'solde'), 
                names_to = 'flux', 
                values_to = "montant")
# Transposition des données : années en lignes, variables en colonne indicateur
# les années deviennent des lignes
# et les autres variables sont rassemblées dans une colonne indicateur
bio_vege2_pdl_reg_ensemble_long <- bio_vege2_pdl_reg_ensemble |>
  select(-ident_vege1) |>
  pivot_longer(
    cols = -c(
      annee, echelle_geographique, territoire,
      type_de_production, groupe_de_productions, sous_groupe_de_productions
    ),
    names_to = "indicateur",
    values_to = "valeur"
  ) |>
  pivot_wider(
    names_from = annee,
    values_from = valeur
  )

# transposer les données de manière à ce que les années soient en ligne (deviennent des colonnes)
# et les variables deviennent des lignes de la colonne indicateur
bio_vege2_pdl_an_reg_ensemble_long <- bio_vege2_pdl_an_reg_ensemble |>
  select(-c(
    echelle_geographique, type_de_production,
    groupe_de_productions, sous_groupe_de_productions
  )) |>
  pivot_longer(
    cols = contains("an_"),
    names_to = c("indicateur", "annee"),
    names_sep = "_an_",
    values_to = "valeur"
  ) |>
  # Pivoter à nouveau pour avoir les années en colonnes
  pivot_wider(
    names_from = annee,
    values_from = valeur
  )

Il peut arriver que certaines variables soient absentes pour certaines observations.
Dans ce cas l’argument values_fill permet de spécifier la valeur à utiliser pour ces données manquantes (par défaut les valeurs absentes sont indiquées par des NA).

values_fill gère les valeurs manquantes lors du pivotement. Il permet de spécifier une valeur de remplacement pour les cellules n’ayant pas de valeur lorsqu’on effectue la transformation. On peut spécifier une valeur unique pour toutes les colonnes, ou une liste de valeurs pour chaque colonne.

Par défaut, les cellules sans correspondance reçoivent une valeur NA dans le tableau résultant. On peut ainsi utiliser values_fill pour attribuer une valeur différente, comme 0, une chaîne vide (““), ou toute autre valeur (par exemple”Aucun”).

df |> 
  pivot_wider(
    names_from = variable, 
    values_from = value,
    values_fill = list(value = 0) # Valeur de remplacement pour les valeurs manquantes
  )


7 Jointure de tableaux

On cherche à associer deux tables en se basant sur les valeurs d’une ou plusieurs colonnes. Ces colonnes sont appelées des clés de jointure (codes d’identification).

Les noms de variables peuvent être cités sans guillemets (sauf dans le cas des jointures). - les fonctions *_join(a, b, join_by) du package dplyr permettent de joindre les tables a et b sur les variables de jointure précisées dans l’argument join_by. Les variables non jointives portant le même nom sont suffixées par .x et par .y (par défaut).

7.1 Jointure à gauche avec left_join

Dans une jointure à gauche, on adjoint aux informations de la table de gauche les informations issues de la table de droite.

Ainsi, c’est le tableau de gauche qui définit les individus du tableau en sortie (quand bien-même il n’y a pas d’information à leur sujet dans le tableau de droite : on remarque ainsi la donnée manquante dans la colonne D pour le 3e individu). La fonction s’appuie sur les variables communes aux deux tableaux (ici A et B), qui permettent d’identifier les individus et de relier les informations des deux tables. On précise explicitement quelles sont les identifiants qui permettent de faire ce lien entre les deux tables, à travers l’argument join_by :

left_join(x, y, join_by(A, B))

Quand on fait un left_join(x, y), toutes les lignes de x sont présentes, et dupliquées si nécessaire quand elles apparaissent plusieurs fois dans y. Les lignes de y non présentes dans x disparaissent. Les lignes de x non présentes dans y se voient attribuer des valeurs manquantes NA (non available) pour les nouvelles colonnes.

Cela correspond à ramener l’information de la table y sur la table x.

# jointure des tableaux exploit et prod_anim, avec nom_dossier
exploit_anim <- exploit |> 
  left_join(prod_anim,
            join_by(nom_dossier))

# ajout au fichier historique des années 2020 et 2021
lait_chevre_historiq <- lait_chevre3  |>
  left_join(laits_mensuel_chevre_livr_coll_large,
            join_by(code_mois)) 

oeufs_annee <- oeufs_2021  |>
  left_join(oeufs_2020,
            join_by(reg, libelle_culture))

laits_mensuel_chevre_livr_coll <- laits_mensuel_chevre_livraison |> 
  left_join(laits_mensuel_chevre_collecte,
            join_by(annee, code_mois)) |> 
  select(-c(reg_etab, reg_prod))

# ajout au fichier historique des années 2020 et 2021
produits_laitiers_historiq <- produits_laitiers  |>
  left_join(groupe_produits_laitiers_2021 |> 
              select(-libellé_produit),
            join_by(territoire, code_produit))

# regroupement dep reg  avec left_join 
agreg_anim_produit_petit_dep_reg <- agreg_anim_produit_petit_dep_2 |> 
  left_join(agreg_anim_produit_petit_reg, 
            join_by(indicateur)) |> 
  mutate(across(where(is.numeric), \(x) round_half_up(x, digits = 0)))

# on enleve avec collectiffil les pacages communs (Brière par exemple)
exploit_2010 <- exploit_2010_fm |> 
  left_join(ra_2010_geo_2020 |>
              select(id_dossier,
                     siege_dep_2020,       
                     siege_reg_2020),
                join_by(id_dossier))  |> 
  filter(siege_reg_2020 == "52" & collectiffil == "0") 

# enchainement de left_join
exploitants_2010 <- fam_2010 |> 
  filter(famlien %in% c("10", "11", "12", "13"))  |> 
  mutate(nom_dossier = id_dossier,
         annee = annee_ra_2010,
         age = annee_ra_2010 - as.integer(famanais), 
         tr_age_10 = cut(age, c(0, seq(40, 60, by = 10), Inf), right = FALSE)) |> 
  left_join(exploit_2010 |> 
              mutate(sau_tot = sau / 100) |> 
              select(nom_dossier = id_dossier,
                     sau_tot), 
             join_by(nom_dossier)) |> 
  left_join(pbs_2010 |> 
              select(nom_dossier = id_dossier,
                     dimeco_coef2017,
                     pbstot_coef17),
            join_by(nom_dossier)) 

# on ajoute le libelle des nomenclatures a129, cpf4 et a17
produits <- produits |> 
   left_join(y = a129,     
             join_by(a_129)) |> 
   left_join(y = cpf4, 
             join_by(cpf4)) |> 
   left_join(y = table_NAF, 
             join_by(a_129)) |> 
   left_join(y = secteur_a17, 
             join_by(a_17))

Avec dplyr, les jointures se réalisent grâce aux fonctions left_join, right_join, inner_join, full_join et anti_join. Ces fonctions prennent les arguments suivants :

  • le nom des deux data.frame à joindre ;
  • les variables de jointure, défini par l’argument join_by. Lorsque la variable de jointure ne porte pas le même nom dans les deux tables, on utilise la syntaxe join_by(var_x == var_y) pour faire correspondre des colonnes avec des noms différents.
    S’il y a plusieurs variables de jointures, on utilise join_by(var_x1 == var_y1, var_x2 == var_y2).

Il est préférable d’utiliser ces fonctions sur des objets tibble plutôt que data.frame. On va donc convertir les deux tables avant de présenter un exemple :

library(dplyr)
filosofi_com_2016_tbl <- as_tibble(doremifasolData::filosofi_com_2016)
cog_com_2019_tbl <- as_tibble(doremifasolData::cog_com_2019)

Voici un exemple dans lequel on utilise la fonction left_join pour réaliser une jointure à gauche entre la table des données Filosofi et la table des communes du COG.

table_jointe_tbl <- filosofi_com_2016_tbl |> 
left_join(y = cog_com_2019_tbl, 
join_by(CODGEO == com))

head(table_jointe_tbl)
# Utiliser left_join pour combiner les résultats
bio_vege2_dep_pdl_an_rang <- bio_vege2_dep_pdl_an_rang_ss_groupe_prod |>
  left_join(
    bio_vege2_dep_pdl_an_rang_groupe_prod |>
      select(
        echelle_geographique,
        territoire,
        type_de_production,
        groupe_de_productions,
        sous_groupe_de_productions,
        starts_with("rang_groupe_prod")
      ),
    join_by(
      echelle_geographique,
      territoire,
      type_de_production,
      groupe_de_productions,
      sous_groupe_de_productions
    )
  ) |>
  arrange(
    ident_vege2
    # echelle_geographique,
    # territoire
  )

Parfois, il peut être utile de réaliser une jointure de la table avec elle-même pour affecter la valeur d’une variable à une autre variable. Dans l’exemple suivant, on affecte le nom du premier département à l’ensemble des lignes.

# ajout du rang 1 (département) à l'ensemble des lignes
# on réalise une jointure de la table avec elle-même pour affecter la valeur d’une variable # à une autre variable. 
# Dans l’exemple suivant, on affecte la valeur (le 1er dep) de valeur_dep_rang1 
# relative à valeur_dep_rang == 1.
# on prend valeur_dep_rang1 correspondant au dep du rang1 (pour valeur_dep_rang == 1) 
# et on affecte la valeur 
# pour l'ensemble des cas 
# jointure de l'ensemble des lignes avec la ligne contenant valeur_dep_rang1 (le 1er dep) 
# ligne par ligne
recolte_bois_dep_reg <-  recolte_bois_dep_reg |> 
  mutate(commun = 1) 

# en 2 étapes
recolte_bois_dep_reg_rang1_etape1 <-  recolte_bois_dep_reg |>
  select(categorie,commun,
         valeur_dep_rang_y = valeur_dep_rang,
         valeur_dep_rang1 = valeur_dep_rang1_a) |>
  filter(valeur_dep_rang_y == 1)

recolte_bois_dep_reg_2_etape2 <-  recolte_bois_dep_reg |>
  left_join(recolte_bois_dep_reg_rang1_etape1,
            join_by(categorie, commun)) |> 
  select(-c(valeur_dep_rang1_a, valeur_dep_rang_y))

# en une étape
recolte_bois_dep_reg_2 <-  recolte_bois_dep_reg |>
  left_join(recolte_bois_dep_reg |> 
                select(categorie,commun,
                       valeur_dep_rang_y = valeur_dep_rang,
                       valeur_dep_rang1 = valeur_dep_rang1_a) |>
                filter(valeur_dep_rang_y == 1),
            join_by(categorie, commun)) |> 
  select(-c(valeur_dep_rang1_a, valeur_dep_rang_y))

Utilisation de la numérotation de lignes comme identifiant commun pour la concaténation d’une table avec une sous-table.

# jointure des tableaux bio_pdl et bio_pdl_pommes,
# avec l'identifiant “numéro de ligne”

# Ajout d’une colonne “numéro de ligne”
# creation d'une variable identifiant reprenant le numéro de ligne 
bio_pdl_numero_ligne <- bio_pdl |> 
  mutate(ident = row.names(bio_pdl), .before = 1)

# Filtrer les enregistrements concernant les pommes
bio_pdl_pommes_2 <- bio_pdl_numero_ligne |> 
  mutate(libelle_ab_2 = if_else(startsWith(libelleonab, "Pomme"), "Pommes", libelleonab)) |> 
  filter(libelle_ab_2 %in% c("Pommes") & libelleonab != "Pommes de terre (hors féculière)")

# jointure des tableaux bio_pdl_numero_ligne et bio_pdl_pommes_2,
bio_pdl_nomenclature_numero_ligne <- bio_pdl_numero_ligne |> 
  left_join(bio_pdl_pommes_2 |> 
              select(ident, libelle_ab_2),
              join_by(ident)) 

recolte_bois_pdl_indic <- recolte_bois_pdl_indic |>
  mutate(identifiant = row.names(recolte_bois_pdl_indic), .before = 1) |> 
  mutate(identifiant = as.numeric(identifiant) + 10)

recolte_bois_pdl_indic <- recolte_bois_pdl_indic |>
  mutate(identifiant = case_when(
    indicateur == "Récolte de bois" ~ 62,
    .default = identifiant), 
    .before = 1)

7.2 Jointure complète avec full_join

Dans le cas d’une jointure complète, au contraire d’une jointure interne, on conserve l’ensemble des individus décrits par l’un ou l’autre des jeux de données. Dans le cas de full_join(x, y), toutes les lignes de x et toutes les lignes de y sont conservées (avec des NA ajoutés si nécessaire) même si elles sont absentes de l’autre table.

full_join(x, y, join_by(A,B))

Identifiants portant des noms différents

Il arrive qu’une variable identifiant les individus n’ait pas le même nom dans les deux tableaux. Dans ce cas on peut préciser de faire correspondre à une variable du premier tableau, une autre variable du deuxième tableau, de la manière suivante:

# Fait correspondre x$C et y$D:
full_join(x, y, join_by(C == D))
# jointure des tableaux, avec pacage
surface_bio_2022_2023 <- bio_2023 |> 
  full_join(bio_2022,
    join_by(pacage))

# creation indicatrice 
# present_2023_present_2022
# present_2023_absent_2022
# absent_2023_present_2022
# creation variable surface supplémentaire en 2023
surface_bio_2022_2023 <- surface_bio_2022_2023 |> 
  mutate(present_2023_present_2022 = if_else(!is.na(quantite_declaree_2023) & !is.na(quantite_declaree_2022),
                                            true = 1, false = 0),
         present_2023_absent_2022 = if_else(!is.na(quantite_declaree_2023) & is.na(quantite_declaree_2022),
                                            true = 1, false = 0),
         absent_2023_present_2022 = if_else(is.na(quantite_declaree_2023) & !is.na(quantite_declaree_2022),
                                            true = 1, false = 0),
         indicateur_presence = case_when(
           !is.na(quantite_declaree_2023) & !is.na(quantite_declaree_2022) ~ "present_2023_present_2022",
           !is.na(quantite_declaree_2023) & is.na(quantite_declaree_2022) ~ "present_2023_absent_2022",
           is.na(quantite_declaree_2023) & !is.na(quantite_declaree_2022) ~ "absent_2023_present_2022",
         .default = "autre"),
         surface_supplementaire_2023 = quantite_declaree_2023 - quantite_declaree_2022)

unique(surface_bio_2022_2023$indicateur_presence)
# jointure des tables des différents millésimes et de la table de passage
# plusieurs lignes par commune en geo 2022 
# filtre pour récupérer uniquement les communes des Pays de la Loire
pop <- pop_1990 |> 
  full_join(pop_1999, join_by(com)) |>
  full_join(pop_2010, join_by(com)) |>
  full_join(pop_2019, join_by(com)) |>
  full_join(comdepuis1943_com2022, join_by(com == depcom_initial)) |>
  filter(dep_2022 %in% c("44","49","53","72","85")) |>
  select(depcom_2022, libcom_2022, psdc90, psdc99, pmun10, pmun19)

7.3 Quelques bonnes pratiques sur les jointures

Pour plus de précisions, consulter la fiche https://book.utilitr.org/03_Fiches_thematiques/Fiche_joindre_donnees.html#quelques-bonnes-pratiques-sur-les-jointures qui présente un certain nombre de règles et de bonnes pratiques sur les jointures.

7.3.1 Avant la jointure

Avant de procéder à une jointure, il est essentiel de vérifier la qualité des identifiants dans les deux tables que l’on souhaite joindre.

  • Règle n°1 : vérifier la présence de valeurs manquantes dans les variables de jointure.

    Une première approche consiste à rechercher les valeurs manquantes (NA) dans les variables de jointure. Le code suivant permet de calculer le nombre d’observations pour lesquelles l’identifiant est manquant :

    sum(is.na(filosofi_com_2016$CODGEO))
    
    sum(is.na(cog_com_2019$com))

    Il convient de vérifier que les variables de jointure ne contiennent aucun NA dans les deux tables. Toutefois, les valeurs manquantes peuvent prendre des formes plus complexes que NA : 0, ., 999… c’est pourquoi il est important de procéder à une inspection visuelle des variables de jointure. Pour ce faire, on peut utiliser la fonction unique(), qui permet d’afficher la liste des valeurs qui apparaissent dans une variable.

    unique(filosofi_com_2016$CODGEO)
  • Règle n°2 : vérifier la présence de doublons dans les variables de jointure.

    Si les variables de jointure contiennent un grand nombre de fois les mêmes valeurs, la jointure peut devenir très gourmande en ressources, voire irréalisable. Il est donc indispensable de repérer les doublons, et de les traiter si nécessaire.

    La fonction eeptools::isid permet de déterminer si une ou plusieurs colonnes données constitue(nt) un identifiant unique.
    On recherche la combinaison de variables la plus simple qui permette d’identifier de manière unique chaque ligne d’une table. Par exemple :

  • Un numéro SIREN pour une entreprise

  • La combinaison (année, mois, département) pour des données mensuelles par département

# Exemple de vérification d'identifiant unique
eeptools::isid(df, c("annee", "mois", "departement"))

# Recherche de doublons sur cet identifiant
df |>
  janitor::get_dupes(annee, mois, departement)

Le code suivant calcule le nombre d’observations dans la table pour chaque valeur des variables de jointure, et affiche les premières lignes par nombre d’observations décroissant. Si la variable nb_obs est supérieure ou égale à 2, alors il y a des doublons.

doublons <- filosofi_com_2016_tbl |> 
              group_by(CODGEO) |> 
              summarise(nb_obs = n(), .groups = "drop") |> 
              filter(nb_obs > 1) |> 
              arrange(-nb_obs)
              
doublons

# Pour voir toutes les lignes concernées par les doublons :
filosofi_com_2016_tbl |>
 semi_join(doublons, join_by(CODGEO))


# On vérife l’unité d’observation des deux dataframes avant la jointure.
# La fonction eeptools::isid permet de vérifier si un ensemble de variables
# forme un identifiant unique pour les différentes observations dans un dataframe. 
# Elle renvoie un booléen (TRUE/FALSE) indiquant si les variables identifient 
# de manière unique une ligne dans le dataframe.

eeptools::isid(laits_mensuel_chevre_livraison, 
var = c("annee","mois"),
verbose = TRUE)

eeptools::isid(laits_mensuel_chevre_collecte, 
var = c("annee","mois"))

Une présence de doublons rend nécessaire une analyse de la table avant de réaliser la jointure.

Le fait que les variables de jointure contiennent des valeurs manquantes ou des doublons n’est pas nécessairement un problème. C’est à vous de déterminer si cela pose un problème. Voici deux questions à se poser pour analyser la situation :

  • La (ou les) variable(s) de jointure doi(ven)t-elle(s) impérativement être renseignée(s) pour chaque observation ? Si oui, il ne doit pas y avoir de valeurs manquantes.
  • La (ou les) variable(s) de jointure doi(ven)t-elle(s) identifier de façon unique chaque observation ? Si oui, il ne doit pas y avoir de doublons.

Vérification de doublons de lignes

Cette vérification peut permettre de déterminer les variables servant d’identifiant commun à 2 tables. On peut aussi utiliser la numérotation de lignes comme identifiant commun pour la concaténation d’une table avec une sous-table.

# afficher les lignes en doublon pour l'ensemble des variables
bio_pdl_test_doublons <- bio_pdl |> 
  group_by(across(everything())) |>
  filter(n() > 1) |> # Compter occurrences
  summarise(
    nb_doublons = n() - 1,  # Soustrait 1 car première occurrence n'est pas un doublon
    .groups = "drop"
  )

# afficher les lignes en doublon pour l'ensemble des variables servant d'identifiant
bio_pdl_test_doublons_var_ok <- bio_pdl |> 
  # group_by par colonnes d'identification
  group_by(raisonsociale,numeropacage,numerobio,libelleonab,ss_groupe_onab_libelle,nomoc) |>
  filter(n() > 1) |>
  arrange(raisonsociale,numeropacage,numerobio,libelleonab,ss_groupe_onab_libelle,nomoc) |>
  ungroup()

# afficher les lignes en doublon pour l'ensemble des variables servant d'identifiant sans nomoc
# il reste 11 lignes en double, soit 22 observations
bio_pdl_test_doublons_var_doubl <- bio_pdl |> 
  group_by(raisonsociale,numeropacage,numerobio,libelleonab,ss_groupe_onab_libelle) |>
  filter(n() > 1) |>
  arrange(raisonsociale,numeropacage,numerobio,libelleonab,ss_groupe_onab_libelle) |>
  ungroup()

Extraire les doublons avec la fonction get_dupes() du package janitor.
Cette fonction permet de comparer les lignes d’un dataframe et de renvoyer les doublons de lignes entièrement identiques.
Elle peut être utilisée pour comparer toutes les colonnes ou seulement certaines colonnes.
On peut également conserver toutes les colonnes dans le résultat.
get_dupes() ajoute automatiquement une colonne dupe_count comptant les occurrences.

library(janitor)

# 1. Doublons sur lignes entières
# renvoie les doublons de lignes entièrement identiques (les premières occurrences et leurs doublons)  
df |> 
  get_dupes() |> # Compare toutes les colonnes
  arrange(desc(dupe_count)) |>  # Trie par nombre de doublons

# 2. Doublons sur colonnes spécifiques
# renvoie les doublons de lignes partageant les mêmes valeurs sur les variables var1 et var2
df |> 
  get_dupes(var1, var2)  # Compare uniquement ces 2 colonnes

# 3. Doublons avec conservation des autres colonnes
# renvoie les doublons de lignes partageant les mêmes valeurs sur les variables var1 et var2 
# et conserve toutes les colonnes
df |> 
  get_dupes(
    var1, var2,
    .keep_all = TRUE  # Garde toutes les colonnes dans résultat
  )
# Fonction pour analyser les doublons
recherche_doublons <- function(df, vars) {
  
  # 1. Vérifier identifiant unique
  is_unique <- eeptools::isid(df, vars)
  
  # 2. Compter doublons
  doublons <- df |>
    group_by(across(all_of(vars))) |>
    summarise(
      nb_obs = n(),
      .groups = "drop"
    ) |>
    filter(nb_obs > 1) |>
    arrange(desc(nb_obs))
    
  # 3. Résumé
  resume <- list(
    id_unique = is_unique,
     nb_total_doublons = sum(doublons$nb_obs) - nrow(doublons),
    details_doublons = doublons
  )
  
  return(resume)
}

# Utilisation
resultats <- recherche_doublons(
  df = table,
  vars = c("annee", "mois")
)

print(glue("Identifiant unique: {resultats$id_unique}"))
print(glue("Nombre total de doublons: {resultats$nb_total_doublons}"))

La formule nb_total_doublons soustrait le nombre de groupes (nrow(doublons)) du total d’observations (sum(nb_obs)) pour obtenir le nombre réel de doublons.

# Vérifier les doublons dans la colonne 'pays'
doublons_pays <- pays_avec_doublons |>
  group_by(x1) |>
  filter(n() > 1) |>
  ungroup()

# Afficher les lignes doublons
print("Lignes doublons basées sur la colonne 'pays':")
print(doublons_pays)

# Une date de 999912 indique que l'association a cours au moment de la mise en ligne de cette annexe.
# 
# print(doublons_pays)
# # A tibble: 6 × 4
#   x1    x2                                     x3     x4
#   <chr> <chr>                               <dbl>  <dbl>
# 1 QU    Pays NDA                           190001 199912
# 2 QU    Pays et territoires non déterminés 201201 999912
# 3 XK    Yougoslavie                        199206 199212
# 4 XK    Kosovo                             200506 999912
# 5 XS    Saba                               190001 200505
# 6 XS    Serbie                             200506 999912

# enlever doublons
pays_actifs <- pays_avec_doublons |>
  filter(x4 == 999912)
  • Règle n°3 : vérifier la compatibilité des variables de jointure.

    • Une jointure ne peut être réalisée avec R que si les variables de jointure sont de même type. Il faut donc vérifier que c’est le cas. Les types de variables les plus fréquemment utilisées pour des jointures avec R sont integer (nombre entier), character (chaîne de caractères) et factor (catégorie). On peut utiliser la fonction class pour connaître le type d’une variable.
    # Type de la variable de jointure dans la table Filosofi
    class(filosofi_com_2016$CODGEO)
    
    # Type de la variable de jointure dans le COG 2019
    class(cog_com_2019$com)

    Si les variables sont de type différent, alors il faut convertir les variables de jointure dans l’une des tables. Pour ce faire, on peut utiliser les fonctions as.integer, as.character et as.factor.

    Pour afficher le nombre d’identifiants distincts dans une table.

    # Nombre d'identifiants distincts dans la table Filosofi
    length(unique(filosofi_com_2016$CODGEO))

7.3.2 Après la jointure

Après une jointure, il est essentiel de vérifier que la jointure a bien produit le résultat attendu.

  • Règle n°4 : vérifier que le nombre d’observations de la table de sortie est cohérent.

    Par exemple, dans le cas d’une jointure à gauche (left join) et si les variables de la table de droite ne présentent aucun doublon, alors la table de sortie doit avoir le même nombre d’observations que la table de gauche.

    dim(filosofi_com_2016_tbl)
    
    table_jointe_tbl <- left_join(x = filosofi_com_2016_tbl, 
                                  y = cog_com_2019_tbl, 
                                  join_by(CODGEO == com))
    
    dim(table_jointe_tbl)
    
    # L'argument relationship permet de vérifier que le nombre de correspondances 
    # est bien conforme aux attentes (one-to-one, one-to-many...).
    # L’utilisation de relationship = "one-to-one" dans la jointure permet de garantir 
    # qu’un enregistrement dans une table est associé à un, et un seul, enregistrement dans une autre table 
    laits_mensuel_chevre_livraison_collecte <- laits_mensuel_chevre_livraison |> 
      full_join(laits_mensuel_chevre_collecte,
                join_by(annee,mois),
                relationship = "one-to-one")
    
    # Si la valeur transmise à l’argument relationship est différente du réel lien 
    # entre les deux clés des tableaux de données, alors une erreur surviendra. 
    # error_laits_mensuel_chevre_livraison_collecte <- laits_mensuel_chevre_livraison |> 
    #   full_join(laits_mensuel_chevre_collecte,
    #             join_by(mois),
    #             relationship = "one-to-one")
    
    # Si l'on ne spécifie le type de jointure réalisé (1 to 1, 1 to many, many to 1, many to many),
    # la commande fonctionne et exécute la jointure. 
    # On obtient seulement un avertissement.
    # warning_laits_mensuel_chevre_livraison_collecte <- laits_mensuel_chevre_livraison |> 
    #   full_join(laits_mensuel_chevre_collecte,
    #             join_by(mois))

En effet, il existe plusieurs manières différentes d’associer des données qui déterminent directement la clé à utiliser :

1 to 1 (one to one) : 1 observation côté A correspond à 1 observation côté B.

1 to m (one to many) : 1 observation côté A correspond à plusieurs observations côté B.
Une observation côté A (par exemple d’une unique région) correspond à plusieurs observations côté B (par exemple des différents départements constituant la région).
La clé serait alors la région et des lignes de A se retrouveraient alors dupliquées.

m to 1 (many to one) : plusieurs observations côté A correspondent à 1 observation côté B.
Plusieurs observations côté A (le nombre d’observations des différents départements d’une unique région)
correspondent à une seule observation côté B (les données de la région composée par ces départements).
La clé serait alors la région et des lignes de B se retrouveraient alors dupliquées.

Pour les fonctions de jointure _join, il existe un argument relationship qui permet de vérifier que le nombre de correspondances est bien conforme aux attentes (one-to-one, one-to-many etc.). Si la valeur transmise à l’argument relationship est différente du réel lien entre les deux clés des bases de données, alors une erreur surviendra.

  • Règle n°5 : vérifier la présence éventuelle de valeurs manquantes (NA) dans les variables d’intérêt.

    On peut par exemple utiliser la fonction is.na() qui permet de repérer les observations manquantes dans les variables provenant de la table de droite. S’il y a des valeurs manquantes, cela peut indiquer que cette variable contient des valeurs manquantes dans la table de droite, ou que certaines observations de la table de gauche n’ont pas de correspondances dans la table de droite.

    table_jointe_tbl |> filter(is.na(dep))

7.3.3 Tester sa jointure sur un full join

Une bonne pratique est de tester sa jointure une première fois dans un full join. On peut distinguer dans un full join trois types d’observations : - Observations présentes côté A seulement (type 1)
- Observations présentes côté A et B (type 2)
- Observations présentes côté B seulement (type 3)

Réaliser dans un premier temps un full join permet ainsi de voir à quel point le taux de correspondance entre les deux dataframes est bon.
Il peut être intéressant de creuser en étudiant plus précisément les données sans correspondance côté dataframe A (type 1) ou B (type 3).

# Comparer deux tableaux de données tab1 et tab2 et analyser leur correspondance
# On crée deux nouvelles colonnes in_tab1 et in_tab2. 
# Ces colonnes contiendront des TRUE si l’observation provient de la table en question.
# On réalise une jointure complète entre les deux tableaux, avec les indicateurs de présence.
tab1_tab2 <- tab1 |>
  mutate(in_tab1 = TRUE) |>
  full_join(tab2 |> 
                mutate(in_tab2 = TRUE), 
            join_by(identifiant)) |> 
  mutate(across(contains("in"), 
         \(x) replace_na(x, FALSE))) |> # Remplacer les NA par FALSE
  mutate(
      indicateur_correspondance = case_when(
        in_tab1 & in_tab2 ~ "Présent dans les deux tables",
        in_tab1 ~ "Uniquement dans table 1",
        in_tab2 ~ "Uniquement dans table 2",
        .default = "Absent des deux tables"
      ))

tab1_tab2

# Sans le remplcement des NA par FALSE
# Résultat: NA crée des groupes supplémentaires non désirés
#   in_tab1 in_tab2     n
#   <lgl>   <lgl>   <int>
# 1 FALSE   FALSE       1
# 2 TRUE    TRUE        1
# 3 TRUE    NA          1
# 4 NA      TRUE        1

# Analyse des correspondances entre les deux tableaux
# Compter les occurrences des valeurs des colonnes in_tab1 et in_tab2 dans le tableau joint
# On groupe les données avec les indicateurs de présence et on somme leurs occurrences grâce à la commande group_by
test_tab1_tab2 <- tab1_tab2 |>
  group_by(indicateur_correspondance) |>
  summarise(
      nombre = n(),
      pourcentage = round_half_up(n() / nrow(tab1_tab2) * 100, 1),
      .groups = "drop"
    ) 
    
test_tab1_tab2
# Comparer deux tableaux de données tab1 et tab2 et analyser leur correspondance
# On crée deux nouvelles colonnes in_tab1 et in_tab2.
# Ces colonnes contiendront des TRUE si l’observation provient de la table en question.
# On réalise une jointure complète entre les deux tableaux, avec les indicateurs de présence.
# On remplace les NA par FALSE pour éviter la création de groupes supplémentaires non désirés.
# On crée un indicateur de correspondance pour chaque observation.
# On analyse les correspondances entre les deux tableaux.
# On compte les occurrences des valeurs des colonnes in_tab1 et in_tab2 dans le tableau joint.
# On groupe les données avec les indicateurs de présence et on somme leurs occurrences grâce à la commande group_by
# On affiche le nombre d'observations et le pourcentage d'observations pour chaque type de correspondance.
# On affiche le tableau récapitulatif des correspondances entre les deux tableaux.
# On crée une fonction pour réaliser cela.
comparer_tables <- function(tab1, tab2, identifiant) {
  # Jointure et indicateurs
  comparaison <- tab1 |>
    mutate(in_tab1 = TRUE) |>
    full_join(
      tab2 |> mutate(in_tab2 = TRUE),
      join_by(identifiant)
    ) |>
    mutate(
      # Remplacer NA par FALSE
      across(starts_with("in_"), \(x) replace_na(x, FALSE)),
      # Créer indicateur
      indicateur_correspondance = case_when(
        in_tab1 & in_tab2 ~ "Présent dans les deux tables",
        in_tab1 ~ "Uniquement dans table 1",
        in_tab2 ~ "Uniquement dans table 2",
        .default = "Absent des deux tables"
      )
    )
    
  # Résumé statistique
  resume <- comparaison |>
    group_by(indicateur_correspondance) |>
    summarise(
      nombre = n(),
      pourcentage = round_half_up(n() / nrow(comparaison) * 100, 1),
      .groups = "drop"
    )
    
  return(list(
    details = comparaison,
    resume = resume
  ))
}

# Utilisation
resultats <- comparer_tables(tab1, tab2, identifiant)
print(resultats$resume)

7.4 Compléments sur les différentes jointures

Le graphique ci-dessous illustre les différentes méthodes de jointure :

Type de jointure Exemple
Jointure interne (inner join)
Jointure à gauche (left join)
Jointure à droite (right join)
Jointure externe (full join)

full_join

Les lignes de chacune des tables abondent la fusion.

Comportement d’un full_join
Comportement d’un full_join

inner_join

Seules les lignes aux identifiants communs abondent la fusion. Dans le cas de inner_join(x, y), seules les lignes présentes à la fois dans x et y sont conservées (et si nécessaire dupliquées) dans la table résultat.

Comportement d’un inner_join
Comportement d’un inner_join

left_join

Les lignes de la première table sont conservées et sont abondées par les lignes de la deuxième table qui partagent les mêmes identifiants.

Comportement d’un left_join
Comportement d’un left_join

right_join

Les lignes de la seconde table sont conservées et sont abondées par les lignes de la première table qui partagent les mêmes identifiants.

Comportement d’un right_join
Comportement d’un right_join

anti_join

L’anti-jointure est une opération de jointure qui renvoie toutes les observations d’une table qui n’ont pas de correspondance dans une autre table.
Seules les lignes de la première table dont les variables identifiantes n’apparaissent pas dans la seconde table sont conservées. anti_join est une jointure filtrante, c’est-à-dire qu’elle sélectionne certaines lignes de a sans ajouter les colonnes de b. Un anti_join ne conserve que les lignes de a absentes de b.

Comportement d’un anti_join
Comportement d’un anti_join
# Trouver les siret qui ont disparu
siret_disparus_2021 <- anti_join(abatga_siret_2020, abatga_siret_2021, 
                                 join_by(siret))

siret_disparus_2022 <- anti_join(abatga_siret_2021, abatga_siret_2022, 
                                 join_by(siret))

siret_disparus_2023 <- anti_join(abatga_siret_2022, abatga_siret_2023, 
                                 join_by(siret))

siret_disparus_2024 <- anti_join(abatga_siret_2023, abatga_siret_2024, 
                                 join_by(siret))

semi_join

Seules les lignes de la première table dont les variables identifiantes apparaissent dans la seconde table sont conservées.

semi_join est une jointure filtrante, c’est-à-dire qu’elle sélectionne certaines lignes de a sans ajouter les colonnes de b.

Ainsi, semi_join ne conservera que les lignes de a pour lesquelles une ligne de b existe également, et supprimera les autres. Seulement les colonnes de a seront incluses dans le résultat.

Comportement d’un semi_join
Comportement d’un semi_join

On peut utiliser l’argument suffix de ..._join, pour indiquer les suffixes à ajouter aux colonnes communes.

Différencier les variables partageant le même nom dans les deux tables

Lorsque des variables ont le même nom dans deux tables différentes mais ne sont pas des variables de jointure, on peut choisir le suffixe à leur donner dans la table résultat avec l’argument suffix.
Il s’écrit sous la forme :
suffix = c(“_tab1”, “_tab2”)

flights_ex |> 
  left_join(
    airports_ex,
    join_by(dest == faa),
    suffix = c("_depart", "_arrivee")
  )

Remarque : toutes les variables ne seront pas suffixées selon leur table d’origine, seulement celles possédant le même nom dans les deux tables.



8 Manipuler du texte

Mettre x en majuscules : toupper(x) ou str_to_upper(x) du package stringr.
Mettre x en minuscules : tolower(x) ou str_to_lower(x) du package stringr.

8.1 Concaténer des chaînes de caractères

Pour concaténer des chaînes de caractères, on peut le faire avec la fonction paste.

Par défaut, paste concatène en ajoutant un espace entre les différentes chaînes. On peut spécifier un autre séparateur avec son argument sep :

# Par exemple, si on veut concaténer l'adresse et la ville
paste(df$adresse, df$ville, sep = " - ")

La variante paste0 concatène sans mettre de séparateur.

paste et paste0 sont des fonctions R de base. L’équivalent pour le package stringr se nomme str_c.

8.2 Extraire des éléments d’une chaîne de caractères avec substr()

La fonction substr() permet d’extraire une sous-chaine à partir d’une chaine de caractères.

substr(x, start, stop) extrait une sous-chaîne de caractères.

Le premier argument est la chaine de caractères,
le deuxième correspond à la position de départ du caractère de la chaîne
et le troisième à la position d’arrivée du caractère auquel ou souhaite s’arrêter (la fin de la sous-chaine que l’on souhaite extraire).

depcom <- "44001"
 
dep <- substr(depcom, 1, 2) 
# La sous-chaine débute au 1er caractère et se finit au 2e
# Ainsi on extrait "44"

# Filtrer les enregistrements de la région Pays de la Loire
bio_pdl <- bio |> 
  filter(substr(codecommuneinsee, 1, 2) %in% c('44', '49', '53', '72', '85'))

# Créer une variable dep pour département
bio_pdl <- bio |> 
  filter(substr(codecommuneinsee, 1, 2) %in% c('44', '49', '53', '72', '85')) |> 
  mutate(dep = substr(codecommuneinsee, 1, 2))

# Extraire une chaîne de caractères avec `substr`  
laits_mensuel_2 <- laits_mensuel_1 |> 
  mutate(dep_etab = substr(sircodpost, 1, 2),
         dep_prod = substr(dep, 2, 3))

8.3 Extraire une chaîne de caractères avec str_sub()

Parfois on a besoin de manipuler le contenu du texte d’une variable de type chaîne de caractères : combiner, rechercher, remplacer…

Le package stringr propose des fonctions de manipulation de chaînes de caractère. stringr est une interface simplifiée aux fonctions d’un autre package, stringi.

Extraire une chaîne de caractères avec str_sub()

str_sub() prend 3 arguments : une chaîne de caractère, une position de début, une position de fin. Les positions peuvent être positives, et dans ce cas, on compte à partir de la gauche, ou négatives, et dans ce cas on compte à partir de la droite.

library(dplyr)
library(stringr)
a <- data.frame(x = c(" libeatg", "delivo y"))

b <- mutate(a, pos3a4 = str_sub(string = x, start = 3, end = 4),
               pos3a2avtlafin = str_sub(string = x, start = 3, end = -2))

str_sub() peut être utilisé pour remplacer un caractère

str_sub(a$x, start = 6, end = 9) <- "rer"

a$x

Il est possible de rajouter le suffixe _all à la fonctions str_sub. Le “_all” permet de l’appliquer à toutes les occurrences et pas seulement à la première.

Si on souhaite réaliser ce genre d’opération dans le cadre d’un mutate, il faut utiliser une fonction dite “pipe-operator-friendly”, par exemple stri_sub_replace() du package stringi

# install.packages("stringi")
library(stringi)

a <- data.frame(x = c(" libeatg", "delivo y"))

b <- mutate(a, y = stri_sub_replace(str = x, from = 6, to = 9, value = "rer"))

https://stringr.tidyverse.org/ https://stringi.gagolewski.com/

8.4 Détecter des motifs

str_detect du package stringr permet de détecter la présence d’un motif parmi les élements d’un vecteur. Par exemple, si on souhaite identifier toutes les adresses contenant “Libération” :

str_detect(df$adresse, "Libération")

str_detect renvoie un vecteur de valeurs logiques et peut donc être utilisée, par exemple, avec le verbe filter de dplyr pour extraire des sous-populations.

# Filtrer les lignes où sous_groupe_de_productions contient "Toutes"
bio_vege2_pdl_reg_total <- bio_vege2_pdl_reg |>
  filter(str_detect(sous_groupe_de_productions, "Toutes"))

8.5 Remplacer des motifs

Pour remplacer un motif dans une chaîne de caractères, on peut utiliser str_replace du package stringr.
str_replace(x, motif, remplacement) remplace la première occurrence uniquement.

Pour remplacer toutes les occurrences d’un motif par un autre, il convient d’utiliser la fonction str_replace_all du package stringr.

Par exemple, on peut remplacer les occurrence de “Mr” par “M.” dans la variable nom :

str_replace_all(df$nom, "Mr", "M.")

On peut également spécifier plusieurs remplacements en une seule fois :

str_replace_all(
    df$adresse,
    c("avenue" = "Avenue", "ave" = "Avenue", "rue" = "Rue")
)

La fonction stri_trans_general(x, "Latin-ASCII") du package stringi permet d’enlever les accents dans une chaîne de caractères.

library(stringi)

# Remplacer les accents dans les noms des groupes de production
# stri_trans_general(x, "Latin-ASCII") :
# Cette fonction de stringi transforme les caractères accentués
# en leurs équivalents non accentués. Par exemple, "Chèvres" devient "Chevres".
bio_anim2_pdl_an <- bio_anim2_pdl_an |>
  mutate(groupe_de_productions = stri_trans_general(groupe_de_productions, "Latin-ASCII"))

8.6 Ajouter un préfixe à un nom de variable

# ajout prefixe annee pour Total
vol_2022 <- vol_2022 |>
  rename(total_nombre = paste0("x2022_", total_nombre), 
         total_poids = paste0("x2022_", total_poids))

# autre méthode
vol_2022 <- vol_2022 |>
  rename_with(.cols = c(total_nombre,total_poids), function(x){paste0("x2022_", x)})

# On peut simplifier le code en utilisant une fonction anonyme plus courte avec `\(x)`.
# Le `x` est une variable qui représente chaque élément des colonnes spécifiées. 
vol_2022 <- vol_2022 |>
  rename_with(.cols = c(total_nombre,total_poids), \(x) paste0("x2022_", x))

R fournit maintenant une notation abrégée pour créer des fonctions,
par exemple (x) x + 1 est interprété comme function(x) x + 1.

8.7 Ajouter un suffixe à un nom de variable

# ajout suffixe annee aux variables
bio_2023 <- read.xlsx(xlsxFile = path(chemin, fichier_2023), 
                      sheet = "Feuil1",
                      colNames = TRUE, startRow = 1) |> 
  as_tibble(.name_repair = make_clean_names) |>
  rename_with(.cols = c(nombre_dassocies_gaec, quantite_declaree), 
              function(x) {paste0(x, "_2023")})
# Renommage des colonnes : Les colonnes sont renommées avec un suffixe correspondant à l'année.
# ajout suffixe année aux noms de colonnes sauf pour la première colonne (indicateur)
montants_pdl_2020_p1_p2 <- montants_pdl_2020_p1_p2 |>
  rename_with(\(x) paste0(x, "_", 2020), -indicateur)

8.8 Supprimer un préfixe à un nom de variable pour l’ensemble des noms de colonnes

La fonction colnames() renvoie la liste des noms de colonnes d’un tableau.

colnames(df) <- … : Cette partie de la commande assigne les nouveaux noms de colonnes dans df

La fonction gsub() remplace toutes les occurrences d’un motif dans une chaîne de caractères. Pour supprimer un préfixe, on peut spécifier le motif du préfixe à supprimer et le remplacer par une chaîne vide.

# enlever le préfixe prod_ de tous les noms de colonnes dans le dataframe viande_volaille_annee_pdl
colnames(viande_volaille_annee_pdl) <- gsub("prod_","",colnames(viande_volaille_annee_pdl))

# ou bien
# names(viande_volaille_annee_pdl) <- gsub("prod_","",names(viande_volaille_annee_pdl))
# Pour enlever le préfixe « surf_ ».
# On modifie le libellé espece avec la fonction gsub ou avec la fonction remove (c'est équivalent)
balsa_dep <- balsa_dep |> 
  mutate(espece = gsub("surf_", "", espece))
balsa_reg <- balsa_reg |> 
  mutate(espece = str_remove(espece, "surf_"))
# Renommer des colonnes en supprimant un préfixe
# rename_with(\(x) str_replace(x, "nb_tetes_", ""), starts_with("nb_tetes"))
# rename_with : renommer les colonnes en utilisant une fonction de transformation anonyme `\(x)`
# rename_with() renames columns using a function.
# rename_with(.data, .fn, .cols = everything(), ...)
# str_replace(x, "nb_tetes_", "") : remplacer le préfixe "nb_tetes_" par une chaîne vide dans les noms de colonnes.
# starts_with("nb_tetes") : s'applique uniquement aux colonnes dont les noms commencent par "nb_tetes".
bio_anim_nb_tetes_pdl_an <- bio_anim2_pdl_an_export |>
  filter(echelle_geographique == "Régional") |>
  select(echelle_geographique, territoire, type_de_production, groupe_de_productions, starts_with("nb_tetes")) |>
  # Renommer les colonnes en supprimant le préfixe "nb_tetes_" avant l'année
  rename_with(\(x) str_replace(x, "nb_tetes_", "an_"),
    .cols = starts_with("nb_tetes")
  ) |>
  # Ajouter une nouvelle colonne indicateur avec la valeur "nb_tetes"
  mutate(indicateur = "nb_tetes", .after = "groupe_de_productions")

8.9 Supprimer un suffixe à un nom de variable pour l’ensemble des noms de colonnes

La fonction colnames() renvoie la liste des noms de colonnes d’un tableau.

colnames(df) <- … : Cette partie de la commande assigne les nouveaux noms de colonnes dans df

La fonction gsub() remplace toutes les occurrences d’un motif dans une chaîne de caractères. Pour supprimer un suffixe, on peut spécifier le motif du suffixe à supprimer et le remplacer par une chaîne vide.

# enlever le suffixe _valeur_en_k de tous les noms de colonnes dans le dataframe produits
colnames(produits) <- gsub("_valeur_en_k","",colnames(produits))

# ou bien
# names(produits) <- gsub("_valeur_en_k","",names(produits))

8.10 Remplacer partout les points par des virgules, en vue d’un export vers excel

library(stringr) # pour str_replace

# remplacement point par virgule
lait_vache_reg_dep_2021_donnees_secret_2 <- lait_vache_reg_dep_2021_donnees_secret_1 |> 
  mutate(across(
    .cols = everything(),
    \(x) str_replace(x, "[.]", ","))

8.11 Modificateurs de motifs

Par défaut, les motifs passés aux fonctions comme str_detect ou str_replace sont des expressions régulières classiques.

On peut spécifier qu’un motif n’est pas une expression régulière mais une chaîne de caractères normale en lui appliquant la fonction fixed. Par exemple, si on veut compter le nombre de points dans les noms du tableau, le paramétrage par défaut ne fonctionnera pas car dans une expression régulière le . est un symbole signifiant “n’importe quel caractère” :

Il faut donc spécifier que le point est bien un point avec fixed :

str_count(df$nom, fixed("."))

On peut aussi modifier le comportement des expressions régulières à l’aide de la fonction regex. On peut ainsi rendre les motifs insensibles à la casse avec ignore_case :

str_detect(df$nom, regex("mme", ignore_case = TRUE))

8.12 str_pad pour ajouter des caractères en début ou en fin de chaîne

La fonction str_pad remplit une chaîne de caractères avec des espaces (par défaut) ou une valeur choisie (argument pad) à une largeur fixe, de sorte que str_length(str_pad(x, n)) soit toujours supérieur ou égal à n.
Il est possible de remplir avant et / ou après la chaîne de caractères existant (argument side).

str_pad(x, width = longueur_format, side = ‘’left’’, pad = caractère_a_ajouter).

# exemple pour siren 
str_pad(x, width = 5, side = "left", pad = "0")
# pour formater le mois sur 2 positions en ajoutant un zéro devant si nécessaire ----

# la fonction str_pad() (du package stringr) est utilisée pour ajouter un zéro à gauche du mois si nécessaire, 
# afin d'obtenir une représentation sur 2 positions. 
# ajoute un zéro à gauche du mois si le mois est un chiffre unique. 
# pad pour “remplissage” 
# str_pad(string, width, side = "left", pad = " ")
# string est la chaîne de caractères à laquelle on ajoute du padding.
# width est la largeur totale souhaitée de la chaîne de caractères après l’ajout du padding.
# side indique de quel côté ajouter le padding. Les options sont "left", "right", ou "both".
# Par défaut, le padding est ajouté à gauche.
# pad est le caractère à utiliser pour le padding. Par défaut, c’est un espace.

# on utilise la fonction sprintf (fonction de base en R) qui permet de formater une chaîne de caractères
# le format "%02d" est utilisé pour formater un nombre en une chaîne de caractères de deux chiffres.
# % : indique le début d’un spécificateur de format.
# 0 : indique que les espaces vides doivent être remplis avec des zéros.
# 0 : indique que les nombres doivent être précédés d'un zéro si nécessaire 
# pour atteindre la longueur totale spécifiée.
# 2 : indique la longueur totale du résultat (la largeur minimale du champ de sortie) 
# Si le nombre à formater est plus court que cette largeur, 
# des zéros seront ajoutés au début pour atteindre la largeur spécifiée.
# d : indique que le type de données à formater est un nombre entier (décimal).

# La fonction formatC (base) est utilisée pour formater une valeur numérique en une chaîne de caractères 
# avec une largeur et un format spécifiés.
# width = 2 : Cela spécifie que la valeur formatée doit avoir une largeur de deux caractères, 
# y compris les zéros au début, si nécessaire.
# flag = 0 : Cela spécifie que les zéros en tête doivent être inclus dans la valeur formatée.
itavi <- itavi |> 
  mutate(annee = year(an_mois_date),
         mois = str_pad(month(an_mois_date), width = 2, pad = "0"),
         mois_b = sprintf("%02d", month(an_mois_date)), 
         # fonction strftime() du package lubridate
         mois_c = strftime(an_mois_date, "%m"),
         # format() est une fonction de base, souvent utilisée pour formater des dates.
         mois_d = format(as.Date(an_mois_date), "%m"),
         mois_e = formatC(month(an_mois_date), width = 2, flag = 0),
         # extrait le mois de la variable an_mois_date 
         # et le convertit en un nom de mois complet (par exemple, "janvier" pour le mois 1).
         mois_car = format(an_mois_date, "%B"),
         an_mois = paste0(annee, "_", mois)) 

str(itavi)

8.13 Obtenir la longueur d’une chaîne de caractères avec str_length() :

La fonction str_length() du package stringr permet de connaître le nombre de caractères dans une chaîne de caractères.

library(stringr)

str_length("abc")

8.14 Utiliser les expressions régulières

Une expression régulière (ou regex) est une chaîne de caractères qui décrit, selon une syntaxe précise, un ensemble de chaînes de caractères possibles.
Les expressions régulières permettent la détection de motifs (patterns) sur des chaînes de caractères. Cette recherche textuelle sur motif (pattern) consiste à décrire ce qu’on cherche plutôt que comment chercher.

Exemple : Un nombre suivi d’une virgule se dit \\d+,

- la séquence `\\d` représente un chiffre quelconque
- le `+`’ signifie que ce qui précède (un chiffre) peut être répété
- la `,` signifie bien qu’on cherche à trouver une virgule.

Note : il convient de redoubler les \ car ils sont interprétés à la lecture de la chaîne (comme pour les chemins windows).

Une expression régulière - également appelée regex (regular expression) - correspond à une chaîne de caratère décrivant, selon une syntaxe précise, un ensemble de chaînes de caractères possibles correspondant à cette même syntaxe.

Les expressions régulières sont représentées par un ensemble de symboles constituant le motif à rechercher dans des chaînes de caractères.

  • Restreindre par une condition sur la position dans la chaîne :
    • en début : le métacaractère ^ sert à indiquer que la chaîne de caractère recherchée doit se trouver au début de la chaîne examinée.
      str_replace_all("anormale", "^a", "") # un a mais seulement en début de chaîne # [1] “normale”

    • en fin : le métacaractère $ sert à indiquer que la chaîne de caractère recherchée doit se trouver à la fin.

  • Rechercher des caractères appartenant à des groupes particuliers
    • n’importe quel caractère : le métacaractère .
    • un chiffre : la séquence \d (équivalent à [0-9]) et son contraire \D (tout sauf un chiffre décimal)
    • un caractère alphanumérique (lettre ou chiffre) : la séquence \w et son contraire \W (tout sauf un caractère alphanumérique)
      \w correspond à [A-Za-z0-9_]
    • [a-zA-Z] : correspond à une lettre majuscule ou minuscule.
    • une liste de caractères : la séquence […] et son contraire [^...] str_replace_all(“Concrètement”, “[oe]”, ““)
      # un ‘o’ ou un ‘e’
      ## [1]”Cncrènt”
  • Rechercher des répétitions
    • un caractère répété 0 ou 1 fois : le métacaractère ?
    • un caractère répété 0 ou plusieurs fois : le métacaractère *
    • un caractère répété 1 ou plusieurs fois : le métacaractère +

Afin de détecter la présence d’un motif dans un string, il convient d’utiliser la fonction str_detect du package stringr. Celle-ci retourne TRUE si un élément du texte sur lequel la fonction est appliquée correspond au motif indiqué.

a <- data.frame(txt = c("vélo", "train", "voilier", "bus", "avion", "tram", "trottinette"))

b <- mutate(a, tr_au_debut = str_detect(string = txt, pattern = "^tr"))

b

filter(b, tr_au_debut)
# $ permet de s’assurer qu’on ne détecte que les noms se terminant par “n”, 
# et pas ceux contenant “n” à un autre endroit.
filter(a, str_detect(string = txt, pattern = "n$"))


# \\d+ est une expression régulière qui correspond à une séquence de un ou plusieurs chiffres consécutifs
# \\d : Correspond à un chiffre (0-9). 
# + : Indique que l'élément précédent doit apparaître une ou plusieurs fois.
# pour récupérer les codes de département et de région 
# str_extract() : fonction pour extraire une partie d'une chaîne de caractères.

# \\d{2} : recherche deux chiffres consécutifs.
# \\d : Il s'agit d'un caractère spécial qui représente un chiffre (decimal) dans les expressions régulières. 
# La double barre oblique (\\) est nécessaire pour échapper le caractère spécial 
# et l'interpréter comme un seul caractère correspondant à un chiffre.
# \\ est utilisé comme caractère d'échappement pour s'assurer que le caractère qui suit (d dans ce cas) 
# est interprété comme un motif spécial de regex plutôt que comme un caractère littéral.
# \\d : C’est un métacaractère qui correspond à n’importe quel chiffre, équivalent à [0-9].
# \d dans le langage des expressions régulières est le métacaractère correspondant à un chiffre.
# {2} : quantificateur spécifiant la répétition exacte du précédent élément : doit se répéter exactement deux fois

# [0-9]+ : cette expression trouvera les chiffres présents dans la chaîne de caractères.
# [0-9]+ : cette expression trouvera une ou plusieurs occurrences de chiffres.
# Le caractère + indique que l'on cherche une ou plusieurs occurrences des chiffres précédents.

# Pour extraire les chiffres qui se trouvent entre parenthèses :
# (?<=\\()\\d+(?=\\)) signifie trouver une séquence de chiffres (\\d+) 
# qui est précédée par une parenthèse ouvrante (?<=\\() 
# et suivie par une parenthèse fermante (?=\\))”
# (?<=motif): lookbehind (rétrovision) positif, cela signifie que la correspondance doit être précédée par "motif".
# Elle recherche une position juste après "motif".
# vérifie que la chaîne de caractères recherchée est précédée d'une autre chaîne de caractères spécifique,
# sans inclure cette chaîne de caractères dans le résultat de la recherche.
# (?=motif): lookahead (antériorité) positif, cela signifie que la correspondance doit être suivie par "motif".
# Elle vérifie si le motif suivant est "motif" sans inclure ce motif dans le résultat.
# vérifie que la chaîne de caractères recherchée est suivie d'une autre chaîne de caractères spécifique,
# sans inclure cette chaîne de caractères dans le résultat de la recherche.

# pb pour Corse-du-Sud (2A) et Haute-Corse (2B)
# problème avec les codes de département 2A et 2B car ils contiennent des lettres.
# \\d+[A-Z]? : pour extraire une séquence composée d'un ou plusieurs chiffres 
# suivis facultativement d'une lettre majuscule.
# \\d+" : représente un ou plusieurs chiffres.
# [A-Z]? : représente une lettre majuscule de A à Z de façon facultative. 
# Le point d'interrogation "?" signifie que la lettre majuscule est optionnelle et peut apparaître 0 ou 1 fois.

# On cherche tout ce qu'il y a entre "(" et ")".
# \\w+ correspond à une séquence de caractères alphanumériques (chiffres et lettres).
# \\w est utilisé pour correspondre à un caractère alphanumérique (word), c’est-à-dire un chiffre ou une lettre

# \\d{1,2}[A-B]? : accepte les séquences qui ont un ou deux chiffres suivis éventuellement par 'A' ou 'B'.
# {1,2} : Ce quantificateur spécifie que le motif précédent doit apparaître au moins 1 fois et jusqu'à 2 fois. 
# Cela signifie que l'expression peut capturer des nombres à un ou deux chiffres.
# [A-B] : n'importe quel caractère unique dans l'intervalle spécifié, ici de 'A' à 'B'.
# Cela signifie qu'elle peut capturer soit 'A', soit 'B'.
# ? : Ce quantificateur rend le motif précédent facultatif. 
# Cela signifie que l'expression [A-B] peut apparaître 0 ou 1 fois. 
nb_etp_2 <- nb_etp_1 |> 
  mutate(reg = str_extract(region, "\\d{2}"),
         reg_b = str_extract(region, "[0-9]+"),
         reg_c = str_extract(region, "(?<=\\()\\d+(?=\\))"),
         dep = str_extract(departement, "\\d+[A-Z]?"),
         dep_b = str_extract(departement, "(?<=\\()\\w+(?=\\))"),
         dep_c = str_extract(departement, "(?<=\\()\\d{1,2}[A-B]?"),
         .before = 1) 

sort(unique(nb_etp_2$reg))
sort(unique(nb_etp_2$dep))
# supprimer le préfixe (01_, 02_) des modalités dans la variable eff_tranche 
# "^\\d{2}_" : motif cherché dans chaque élément de eff_tranche. Le motif est une expression régulière. 
#   ^ : début de la chaîne.
# \\d : n’importe quel chiffre (équivalent à [0-9]).
# {2} : exactement à deux occurrences du caractère précédent (dans ce cas, \\d).
# _ : caractère de soulignement.
# "" : C’est la chaîne de remplacement. Dans ce cas, on remplace le motif trouvé par une chaîne vide, 
# ce qui a pour effet de supprimer le motif de la chaîne.

iaa_vol_regroup <- iaa_vol_regroup |>
  mutate(eff_tranche = str_replace(eff_tranche, "^\\d{2}_", ""))
# ouverture fichier janvier
# str_extract() : fonction pour extraire une partie d'une chaîne de caractères.
# Dans ce cas, elle extrait le mois du nom de chaque fichier.
# "(?<=_2025_)\\d{2}" : cette expression signifie “trouver exactement deux chiffres (\\d{2}) après _2025_”.
# \\d{2} : recherche deux chiffres consécutifs.
# \\d : Il s'agit d'un caractère spécial qui représente un chiffre (decimal) dans les expressions régulières. 
# La double barre oblique (\\) est nécessaire pour échapper le caractère spécial 
# et l'interpréter comme un seul caractère correspondant à un chiffre.
# \\ est utilisé comme caractère d'échappement pour s'assurer que le caractère qui suit (d dans ce cas) 
# est interprété comme un motif spécial de regex plutôt que comme un caractère littéral.
# \\d : C’est un métacaractère qui correspond à n’importe quel chiffre, équivalent à [0-9].
# \d dans le langage des expressions régulières est le métacaractère correspondant à un chiffre.
# {2} : quantificateur qui spécifie la répétition exacte du précédent élément (exactement deux fois).
# (?<=_2025_): lookbehind (rétrovision) positif, signifiant que la correspondance doit être précédée par "_2025_".
# Elle recherche une position juste après le motif _2025_.
# vérifie que la chaîne de caractères recherchée est précédée d'une autre chaîne de caractères spécifique, 
# sans inclure cette chaîne de caractères dans le résultat de la recherche.
# "(?<=2025_)[0-9]{2}" : cette expression trouvera deux chiffres ([0-9]{2}) après 2025_.
# "(?<=2025_)[0-9]+(?=_)" : cette expression trouvera une ou plusieurs occurrences de chiffres ([0-9]+) 
# après 2025_ jusqu'au prochain (_).
# Le caractère + indique que l'on cherche une ou plusieurs occurrences des chiffres précédents.
# (?=_): est un lookahead (antériorité) positif, cela signifie que la correspondance doit être suivie par "_".
# Elle vérifie si le motif suivant est "_" sans inclure ce motif dans le résultat.
# vérifie que la chaîne de caractères recherchée est suivie d'une autre chaîne de caractères spécifique, 
# sans inclure cette chaîne de caractères dans le résultat de la recherche.
eff_bovins_2024 <- read_csv2(file = path(chemin_bdni, eff_an_2025, fichier_01_2025)) |> 
  mutate(mois = str_extract(fichier_01_2025, "(?<=_2025_)\\d{2}"),
         mois_b = str_extract(fichier_01_2025, "(?<=2025_)[0-9]{2}"),
         mois_c = str_extract(fichier_01_2025, "(?<=2025_)[0-9]+"),
         mois_d = str_extract(fichier_01_2025, "(?<=2025_)[0-9]+(?=_)"))

Les fonctions de stringr étant prévues pour fonctionner avec des expressions régulières, certains caractères n’auront pas le sens habituel dans la chaîne indiquant le motif à rechercher. Par exemple, le . ne sera pas un point mais le symbole représentant “n’importe quel caractère”.

# Exclusion des colonnes dont les noms contiennent "ss_groupe_prod".
# Renommage des colonnes pour ne garder que le format an_aaaa,
# où \\1 est l'année capturée par le groupe de capture (\\d{4}).
# (\\d{4}) : Capture un groupe de quatre chiffres (représentant l'année)
# et les place dans un groupe de capture.
# Les parenthèses () définissent le groupe de capture,
# et \\d{4} signifie "quatre chiffres".
# La chaîne de remplacement "an_\\1"
# utilise \\1 pour insérer le contenu du premier groupe de capture
# (les quatre chiffres représentant l'année) dans le nouveau nom de la colonne.
# an_ : Préfixe littéral pour le nouveau nom de la colonne.
# \\1 : Référence au premier groupe de capture, qui contient l'année capturée par (\\d{4}).
bio_vege_groupe_prod_surf_tot_bio_dep_pdl_part_reg <- bio_vege_dep_pdl_part_reg_export |>
  filter(str_starts(sous_groupe_de_productions, "toutes")) |>
  select(
    echelle_geographique, territoire,
    type_de_production, groupe_de_productions, sous_groupe_de_productions,
    rang_groupe_prod_surf_tot_bio_an_2023,
    starts_with("surf_tot_bio_")
  ) |>
  select(-matches("ss_groupe_prod")) |>
  rename_with(
      \(x) str_replace(
      x,
      "surf_tot_bio_an_(\\d{4})_group_prod_part_reg",
      "an_\\1"
    ),
    .cols = starts_with("surf_tot_bio_")
  ) |>
  mutate(indicateur = "surf_tot_bio", .after = "groupe_de_productions")

8.14.1 Séquence d’échappement des caractères spéciaux et raw string

Si l’on souhaite indiquer comme caractère littéral un des symboles spéciaux ?,+,*,.,|,[,],(,), …, alors ils doivent être référencés dans une séquence d’échappement en les précédant du caractère \.

Par exemple, dans un regex, le caractère + est recherché avec l’expression \+.

Or, R utilise également \ comme caractère d’échappement pour signaler les caractères spéciaux (par exemple \n pour revenir à la ligne).
Ainsi en R, l’échappement des caractères spéciaux dans un regex se fera à l’aide de deux symboles \ ou bien à l’aide de raw strings, pour ne pas interpréter ce symbole comme un symbole d’échappement.

Par exemple, dans un regex en R, le caractère + est recherché avec l’expression "\\+" ou bien r"(\+)".

Un raw string commence par r"( et se termine par )", on peut également utiliser r"[...] ou r"{...}", notamment quand le string contient les caractères "(.

La syntaxe r"(...)" permet de définir un raw string, c’est à dire que le texte est à lire littéralement sans chercher à convertir les caractères spéciaux.
Cela permet d’éviter d’avoir à doubler les antislashs.

# Exemple
str_replace_all("anormale", r"^a", "")  # un 'a' mais seulement en début de chaîne
# [1] "normale"

On peut par exemple détecter une date de la façon suivante :

library(stringr)

textes <- c("14/07/1789", "J'ai 10 ans.", "Une date importante est le 16/04/1972")

str_detect(string = textes, 
pattern = r"(\d{2}/\d{2}/\d{4})") # On cherche 2 chiffres puis "/" puis 2 chiffres puis "/" puis 4 chiffres

# [1]  TRUE FALSE  TRUE

https://stringr.tidyverse.org/articles/regular-expressions.html



9 Utilisation des fonctions

Une fonction est une portion de code qui est exécutée quand elle est appelée. Elle peut prendre en entrée des arguments (ou aucun) pour les insérer dans le code à exécuter. Elle peut renvoyer une valeur à l’issue de l’exécution.

On affecte le résultat d’une fonction à un objet pour avoir le résultat dans l’environnement global et pouvoir le manipuler.

9.1 Les principes des fonctions de dplyr

Le but du package dplyr est d’identifier et de rassembler dans un seul package les outils de manipulation de données. Ce package rassemble donc des fonctions correspondant à un ensemble d’opérations élémentaires (ou verbes) qui permettent de :

  • Sélectionner un ensemble de variables : select()
  • Sélectionner un ensemble de lignes : filter()
  • Ajouter/modifier/renommer des variables : mutate() ou rename()
  • Produire des statistiques agrégées sur les dimensions d’une table : summarise()
  • Trier une table : arrange()
  • Manipuler plusieurs tables : left_join(), right_join(), full_join(), inner_join()

D’appliquer cela en articulation avec group_by() qui change la façon d’interpréter chaque fonction : d’une interprétation globale sur l’ensemble d’une table, on passe alors à une approche groupe par groupe : chaque groupe étant défini par un ensemble des modalités des variables définies dans l’instruction group_by().

9.2 Appliquer une fonction dplyr sur un groupe de colonnes : across()

La fonction across() permet d’appliquer, dans un environnement dplyr, un même traitement à plusieurs variables d’une même table.

Elle permet de sélectionner un groupe de colonnes sur lequel on souhaite appliquer une fonction.

Ainsi on peut appliquer une fonction sur des colonnes en les sélectionnant comme le fait select().
Si on souhaite sélectionner des colonnes en fonction de leur type, on utilise en outre where().

L’objectif de across() est d’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.

across() prend deux arguments principaux :

  • la définition d’un ensemble de colonnes du tableau de données
  • une ou plusieurs fonctions à appliquer aux colonnes sélectionnées

Le code suivant groupe les données de la BPE avec les deux colonnes dont le nom commence par “DEP”, puis effectue la somme de toutes les colonnes de type numérique.

bpe_ens_2018_tbl |> 
  group_by(across(starts_with('DEP'))) |>
  summarise(across(where(is.numeric), sum)) 

En résumé, Le traitement à effectuer correspond à une fonction. On peut la renseigner de différentes façons :

  • avec le nom de la fonction (sans parenthèses): lorsque la fonction n’a besoin que d’un seul paramètre, qui est la variable à traiter
    exemple across(…, mean)
  • avec une fonction anonyme
    • définie avec la syntaxe concise \(x) et x
      exemple across(…, \(x) mean(x, na.rm = TRUE))
    • définie avec la syntaxe classique, avec function(x) et des paramètres explicites
      exemple across(…, function(x) { mean(x, na.rm = TRUE) })

x est un raccourci pour désigner la colonne courante (l’élément courant sur lequel la fonction est appliquée).
x représente la valeur de l’élément en cours de traitement.

Exemples d’utilisation de la fonction across()

library(janitor) # pour round_half_up

# Ajout de la variable livraison_dep_perc (pourcentage par rapport au niveau régional)
lait_production_vache_2 <- lait_production_vache_1 |> 
  mutate(livraison_dep_perc =  livraison_dep / livraison_reg * 100) |> 
  mutate(across(livraison_dep_perc, \(x) round_half_up(x, digits = 3))) |> 
  arrange(type, dep_prod)

# Calcul de l'évolution de la production de lait de chèvre et 
# de la collecte de lait de chèvre entre 2020 et 2021, en hl et en %
laits_mensuel_chevre_livr_coll_large_2 <- laits_mensuel_chevre_livr_coll_large_1 |> 
  mutate(livr2021_evol = livraison_2021 - livraison_2020,
         livr2021_evol_perc = (livraison_2021 / livraison_2020 - 1) * 100,
         coll2021_evol = collecte_2021 - collecte_2020,
         coll2021_evol_perc = (collecte_2021 / collecte_2020 - 1) * 100) |> 
  mutate(across(c(livr2021_evol_perc, coll2021_evol_perc), \(x) round_half_up(x, digits = 3))) |> 
  mutate(across(c(livraison_2020, livraison_2021, collecte_2020, collecte_2021,
                  livr2021_evol, coll2021_evol), 
                \(x) round_half_up(x, digits = 0)))
  
# lait_chevre_mensuel, sélection des 13 premières lignes
# slice_head(13) est équivalent à slice(1:13)
lait_chevre <- read.xlsx(xlsxFile = path(chemin_entree, fichier_entree), 
                        sheet = "lait chevre", colNames = TRUE, startRow = 8) |> 
  as_tibble(.name_repair = make_clean_names) |> 
  slice_head(n = 13) |> 
  # Quand on passe une fonction comme argument à une autre fonction, on utilise la notation sans les parenthèses.
  mutate(across(-x1, as.numeric))

produits_laitiers_2021_2 <- produits_laitiers_2021_1b |> 
  mutate(across(c(finiqte,finiqte1), as.numeric))

# affichage des lignes en doublon pour l'ensemble des variables
bio_pdl_test_doublons <- bio_pdl |> 
  group_by(across(everything())) |>
  filter(n() > 1) |>
  ungroup()

Une erreur de syntaxe fréquente est de mettre la sélection des colonnes dans l’appel à across(), mais pas la fonction qu’on souhaite appliquer.

Ainsi le code suivant génèrera une erreur :

mutate(across(pcs:pcs_mere), recode_pcs)

Il faut bien penser à passer la fonction comme argument du across(), donc à l’intérieur de ses parenthèses.

mutate(across(pcs:pcs_mere, recode_pcs))

# conversion en numérique à partir de la colonne surf_tot_bio
bio_vege2_pdl <- bio_vege_pdl |>
  select(ident_vege1, annee, echelle_geographique, territoire,
    type_de_production, groupe_de_productions, sous_groupe_de_productions,
    surf_tot_bio = surface_bio_et_en_conversion_en_ha,
    surface_bio_en_ha,
    surface_en_conversion_en_ha,
    surface_en_conversion_annee_1_en_ha,
    part_bio_de_la_sau_en_percent,
    nombre_de_producteurs,
    part_des_fermes_bio_en_percent
  ) |>
  arrange(ident_vege1) |>
  mutate(across(c(
    surf_tot_bio, surface_bio_en_ha, surface_en_conversion_en_ha,
    surface_en_conversion_annee_1_en_ha,
    part_bio_de_la_sau_en_percent, nombre_de_producteurs,
    part_des_fermes_bio_en_percent
  ), as.numeric)) |>
  # Effectuer les calculs
  mutate(
    sau_tot = surf_tot_bio / part_bio_de_la_sau_en_percent * 100,
    pct_surf_bio_conv_sur_sau_tot = surf_tot_bio / sau_tot * 100,
    pct_surf_conv_sur_sau_tot = surface_en_conversion_en_ha / sau_tot * 100,
    pct_surf_conv_sur_surf_bio = surface_en_conversion_en_ha / surface_bio_en_ha * 100,
    pct_surf_conv1_sur_surf_bio = surface_en_conversion_annee_1_en_ha / surface_bio_en_ha * 100,
    .after = "sous_groupe_de_productions"
  ) |>
  mutate(across(contains("pct"), \(x) round_half_up(x, digits = 3)))

Le symbole | signifie ou dans les expressions régulières.

# Calcul des évolutions annuelles 
# matches() sélectionne les colonnes dont le nom correspond au motif "nb_tetes" ou "nombre_deleveurs".
bio_anim2_pdl_an_evol <- bio_anim2_pdl_an_evol |>
  mutate(
    nb_tetes_2023_2010_evol_an = ((nb_tetes_2023 / nb_tetes_2010)**(1 / 13) - 1) * 100,
    nombre_deleveurs_2023_2010_evol_an = ((nombre_deleveurs_2023 / nombre_deleveurs_2010)**(1 / 13) - 1) * 100,
    solde_eleveurs_2023_2010_evol_an = (nombre_deleveurs_2023 - nombre_deleveurs_2010) / 13,
    part_du_bio_en_percent_2023_2010_evol_an = (part_du_bio_en_percent_2023 - part_du_bio_en_percent_2010) / 13,
    .after = groupe_de_productions
  ) |>
  mutate(across(matches("nb_tetes|nombre_deleveurs"), \(x) round_half_up(x, digits = 3)))

9.2.1 Noms des colonnes créées par un mutate

Par défaut, lorsqu’on utilise across() dans un mutate, les nouvelles colonnes portent le même nom que les colonnes d’origine, ce qui signifie que ces dernières sont “écrasées” par les nouvelles valeurs.

Ainsi dans l’exemple suivant, les valeurs d’origine des colonnes PCS ont été écrasées par le résultat du recodage.

df |>
    mutate(
        across(
            starts_with("pcs"),
            recode_pcs
        )
    )

Si on préfère créer de nouvelles colonnes, on doit indiquer la manière de les nommer en utilisant l’argument .names de across().

On lui donne une chaîne de caractère concaténée où : {.col} désigne la variable à traiter (le nom de la colonne d’origine) et
{.fn} désigne la fonction de traitement appliquée.
Par exemple : .names = “{.col}_{.fn}”

Ainsi, si on souhaite plutôt que les variables recodées soient stockées dans de nouvelles colonnes nommées avec le suffixe _rec, on peut utiliser :

df |>
    mutate(
        across(
            starts_with("pcs"),    # sélectionner les colonnes commençant par "pcs"
            recode_pcs,            # appliquer la fonction recode_pcs
            .names = "{.col}_rec"  # nommer les nouvelles colonnes avec suffixe _rec
        )
    )

Ainsi, la structure de la fonction across() est :

across(variables à traiter, 
       traitement à appliquer, 
       .names = “règle de renommage”)
# calcul évolutions base 100 en 2010 ----
# across() est conçu pour appliquer une fonction à chaque colonne spécifiée individuellement.
# ici on calcule l'évolution pour chaque colonne commençant par "nb_tetes" ou "nombre_deleveurs".
# across() est vectorisé : il applique une fonction à toutes les lignes d'une colonne en une seule opération.
# La division x / nb_tetes_2010 * 100 est effectuée élément par élément sur toute la colonne.
# On divise chaque colonne par une valeur spécifique de la même ligne
# Autrement dit, chaque opération est appliquée sur le vecteur entier correspondant à chaque colonne.
bio_anim2_fm_an_evol <- bio_anim2_fm_an |>
  # Calculer l'évolution pour les colonnes commençant par "nb_tetes"
  mutate(
    across(starts_with("nb_tetes"), \(x) x / nb_tetes_2010 * 100, .names = "{.col}_evol_2010"),
    # Calculer l'évolution pour les colonnes commençant par "nombre_deleveurs"
    across(starts_with("nombre_deleveurs"), \(x) x / nombre_deleveurs_2010 * 100, .names = "{.col}_evol_2010")
  )

9.2.2 Appliquer plusieurs fonctions à plusieurs colonnes

across() offre également la possibilité d’appliquer plusieurs fonctions à un ensemble de colonnes. Dans ce cas, plutôt que de lui passer une seule fonction comme deuxième argument, on lui passe une liste nommée de fonctions.

Le code suivant calcule le minimum et le maximum pour les variables d’âge de df.

df |>
    summarise(
        across(
            starts_with("age"),
            list(minimum = min, maximum = max)
        )
    )

Par défaut les nouvelles variables sont nommées sous la forme {nom_variable}_{nom_fonction}, mais on peut personnaliser cette règle en ajoutant un argument .names à across(). Cet argument est une chaîne de caractères dans laquelle {.col} sera remplacé par le nom de la colonne courante, et {.fn} par le nom de la fonction.

df |>
    summarise(
        across(
            starts_with("age"),
            list(minimum = min, maximum = max),
            .names = "{.fn}_{.col}"
        )
    )

Exemple d’utilisation de la fonction across() pour appliquer une liste de fonctions statistiques à une seule variable.

# Calculer plusieurs agrégats sur une seule variable
base_RP |> summarise(
 across(variable,
   list(somme_totale = sum, 
        valeur_minimum = min,
        premier_quartile_q1 = \(x) quantile(x, probs = 0.25),
        mediane = median,
        troisieme_quartileq_3 = \(x) quantile(x, probs = 0.75),
        valeur_maximum = max)))
# Appliquer les calculs pour chaque année
bio_anim2_dep_pdl_an <- bio_anim2_pdl_an |>
  group_by(echelle_geographique, type_de_production, groupe_de_productions) |>
  mutate(across(starts_with("nb_tetes"), list(
    reg = \(x) sum(x, na.rm = TRUE),
    part_reg = \(x) x / sum(x, na.rm = TRUE) * 100
  ), .names = "{col}_{fn}")) |>
  mutate(across(starts_with("nombre_deleveurs"), list(
    reg = \(x) sum(x, na.rm = TRUE),
    part_reg = \(x) x / sum(x, na.rm = TRUE) * 100
  ), .names = "{col}_{fn}")) |>
  ungroup() |>
  mutate(across(contains("part_reg"), \(x) round_half_up(x, digits = 3)))
# Appliquer les calculs pour chaque année
bio_vege2_dep_pdl_an <- bio_vege_clean_pdl_an |>
  group_by(echelle_geographique, type_de_production, groupe_de_productions) |>
  mutate(across(starts_with("surf_tot_bio"), list(
    reg = \(x) sum(x, na.rm = TRUE),
    part_reg = \(x) x / sum(x, na.rm = TRUE) * 100
  ), .names = "{col}_group_prod_{fn}")) |>
  mutate(across(starts_with("nombre_de_producteurs"), list(
    reg = \(x) sum(x, na.rm = TRUE),
    part_reg = \(x) x / sum(x, na.rm = TRUE) * 100
  ), .names = "{col}_group_prod_{fn}")) |>
  ungroup() |>
  group_by(
    echelle_geographique,
    type_de_production,
    groupe_de_productions,
    sous_groupe_de_productions
  ) |>
  mutate(across(starts_with("surf_tot_bio"), list(
    reg = \(x) sum(x, na.rm = TRUE),
    part_reg = \(x) x / sum(x, na.rm = TRUE) * 100
  ), .names = "{col}_ss_groupe_prod_{fn}")) |>
  mutate(across(starts_with("nombre_de_producteurs"), list(
    reg = \(x) sum(x, na.rm = TRUE),
    part_reg = \(x) x / sum(x, na.rm = TRUE) * 100
  ), .names = "{col}_ss_groupe_prod_{fn}")) |>
  ungroup()
# appliquer la somme à certaines colonnes en fonction de leur préfixe
# crée une condition logique qui sélectionne les colonnes commençant soit par "nbtete_" soit par "prod_".
viande_volaille_apres_2010_regroupt_1 <- viande_volaille_apres_2010 |> 
  mutate(espece = case_when(
    lib_code %in% c("03 - Canards gras","05 - Canards à rôtir") ~ "03_04_Canards",
    .default = lib_code)
  ) |> 
  filter(espece %in% c("03_04_Canards")) |> 
  group_by(reg_court, espece) |> 
  mutate(across(starts_with("nbtete_") | starts_with("prod"), sum)) |> 
  # L’argument na.rm = TRUE est utilisé pour ignorer les valeurs NA lors de l’addition.
  # mutate(across(c(starts_with("nbtete_"), starts_with("prod")), \(x) sum(x, na.rm = TRUE))) |>
  ungroup() |> 
  select(-c(code,lib_code)) |> 
  distinct()

9.3 rowwise() et c_across() : appliquer une transformation ligne par ligne

Par défaut, mutate s’applique sur l’ensemble des valeurs de la colonne d’un tableau.

Parfois, on souhaite calculer une somme (ou une moyenne) non pas pour l’ensemble du tableau mais pour chaque ligne. Pour cela, on va utiliser la fonction rowwise() : celle-ci est équivalente à un group_by() qui créerait autant de groupes qu’il y a de lignes dans le tableau.

"rowwise" considère chaque ligne comme un groupe.

Quant le tableau est groupé via un rowwise(), les opérations s’effectuent sur un tableau constitué uniquement de la ligne courante.

restos |>
    rowwise() |>
    mutate(decor_accueil = mean(c(decor, accueil)))

Supposons qu’on souhaite désormais calculer la moyenne de l’ensemble des critères. On peut évidemment reprendre le code précédent en saisissant toutes les variables concernées.

restos |>
    rowwise() |>
    mutate(moyenne = mean(c(decor, accueil, cuisine, prix)))

Lister les variables de cette manière peut vite devenir pénible si le nombre de variables est important. C’est pourquoi dplyr propose la fonction c_across() : celle-ci permet de sélectionner des colonnes de la même manière que select() ou across(), et retourne un vecteur constitué des valeurs concaténées de ces colonnes.

L’exemple suivant calcule la moyenne de toutes les colonnes comprises entre decor et prix, en utilisant l’opérateur :.

restos |>
    rowwise() |>
    mutate(
        moyenne = mean(c_across(decor:prix))
    )

Comme pour across() ou select(), on peut utiliser la fonction where() pour calculer la moyenne sur toutes les colonnes numériques.

restos |>
    rowwise() |>
    mutate(
        moyenne = mean(
            c_across(where(is.numeric))
        )
    )
# rowwise() : Applique les opérations par ligne.
# calcul du pourcentage par rapport à la dernière colonne pour chaque ligne
# last(c_across()) sélectionne la dernière valeur de la ligne pour les colonnes spécifiées
# on utilise c_across() dans un contexte rowwise()
# pour sélectionner des colonnes spécifiques
# et effectuer des opérations sur les valeurs de ces colonnes pour chaque ligne individuellement
# c_across() sélectionne les colonnes à partir de la première colonne jusqu'à la dernière colonne
# c_across() ne sélectionne (combine) que des colonnes de même type (character ou numeric) en une seule colonne
# sinon erreur : Can't combine `` <character> and `` <double>.
# Il retourne un vecteur contenant les valeurs des colonnes sélectionnées pour la ligne actuelle.
bio_dep_pdl_anim_nb_tetes <- bio_dep_pdl_anim |>
  select(groupe_de_productions, contains("nb_tetes")) |>
  rowwise() |>
  mutate(
    test_derniere_valeur1 = last(c_across(contains("nb_tetes"))),
    test_derniere_valeur2 = last(c_across(-groupe_de_productions)),
    test_derniere_valeur3 = last(c_across(where(is.numeric)))
  ) |>
  mutate(across(contains("nb_tetes"),
    \(x) x / last(c_across(where(is.numeric))) * 100,
    .names = "pct_{.col}"
  )) |>
  ungroup() |>
  mutate(across(contains("pct"), \(x) round_half_up(x, digits = 3)))

9.4 Fonctions anonymes et syntaxes abrégées

Le fait de créer une fonction à part pour une opération d’une seule ligne ne se justifie pas forcément, surtout si on n’utilise pas cette fonction ailleurs dans le code. Dans, ce cas, on peut définir la fonction directement dans l’appel à rename_with() en utilisant une fonction anonyme, qui n’a pas de nom (elle n’a pas été stockée dans un objet), et qui n’existe que le temps de l’appel.

df |>
    rename_with(function(x) {
        str_replace_all(x, " ", "_")
    })

Cette notation est assez pratique et souvent utilisée pour les fonctions à usage unique, ne serait-ce que pour s’économiser le fait de devoir lui trouver un nom pertinent.

Il existe une façon d’avoir une définition plus concise, qui permet de remplacer function(...) par le raccourci \(...).

Il s’agit d’utiliser une syntaxe de type “formule” : le corps de la formule contient les instructions de la fonction, et les arguments sont nommés x s’il n’y en a qu’un, x et y s’il y en a deux.

Ainsi les écritures suivantes sont équivalentes :

# Fonction anonyme classique
function(x) { x + 2 }

# Fonction anonyme concise
\(x) { x + 2 }

De même que les écritures suivantes :

# Fonction anonyme classique
function(x, y) {
    res <- x / y
    round(res, 1)
}

# Fonction anonyme concise
\(x, y) {
    res <- x / y
    round(res, 1)
}

Quand la fonction anonyme est constituée d’une seule instruction, on peut supprimer les accolades dans sa définition.

# Fonction anonyme classique
function(x) x + 2

# Fonction anonyme concise
\(x) x + 2

On pourra du coup, si on le souhaite, utiliser cette syntaxe concise dans rename_with() pour définir une fonction anonyme.

df |>
    rename_with(\(x) str_replace_all(x, " ", "_"))

Cette syntaxe peut être utilisée partout où on peut passer une fonction comme argument et donc définir des fonctions anonymes.

On peut ainsi utiliser une fonction anonyme définie directement dans le where().

df |>
    summarise(
        across(
            where(function(x) { is.numeric(x) && sum(is.na(x)) == 0 }),
            mean
        )
    )

Et du coup utiliser une syntaxe concise.

df |>
    summarise(
        across(
            where(\(x) is.numeric(x) && sum(is.na(x)) == 0),
            mean
        )
    )


10 Ecriture de fonctions

10.1 Principe des fonctions

On utilise les fonctions pour effectuer des calculs, obtenir des résultats et accomplir des actions.

Une fonction a un nom, elle prend en entrée entre parenthèses un ou plusieurs arguments (ou paramètres), et peut fournir une valeur en retour (paramètre de sortie ou résultat). Elle se termine souvent par la fonction return().

Une fonction correspond à un regroupement d’instructions dans le but de réaliser une action précise et qui a vocation à être réutilisé.

On peut se demander dans quels cas il est utile de créer une fonction.

Une règle courante considère que dès qu’on a répété le même code plus de deux fois, il est préférable de l’encapsuler dans une fonction. Celles-ci ont en effet comme avantage d’éviter la duplication du code.

Cela permet d’éviter la redondance et d’améliorer la maintenabilité du code. En effet, si on a besoin de modifier le code, il suffit de le faire une seule fois dans la fonction, et non pas à chaque endroit où le code est dupliqué.

La fonction est un objet comme les autres, qu’on crée avec l’opérateur d’affectation.

Pour appliquer une fonction à un objet, la syntaxe est :

nom_de_la_fonction(objet, attribut1, attribut2, ...)

Pour stocker le résultat de cette fonction dans une variable :

ma_variable <- nom_de_la_fonction(objet, attribut1, attribut2, ...)

Une fonction est créée en utilisant l’instruction function. Celle-ci est suivie d’une paire de parenthèses et d’une paire d’accolades.

function() {

}

La syntaxe générale d’une fonction est la suivante :

nom_de_la_fonction <- function(parametre1, parametre2, ...) {
  # Corps de la fonction : ensemble d'instructions à exécuter
  # ...

  # La fonction peut retourner une valeur à l'appelant (ou NULL si aucune valeur n'est spécifiée)
  return(resultat)
}

10.1.1 Paramètres (ou arguments) d’une fonction

Les parenthèses servent à indiquer les paramètres (ou arguments) de la fonction, ceux qui devront lui être passés lors de l’appel de la fonction.

Une fonction peut prendre plusieurs arguments. Dans ce cas on liste les arguments dans les parenthèses en les séparant par des virgules.

Une fonction peut aussi n’accepter aucun argument, dans ce cas on laisse les parenthèses vides.

Pour déterminer les paramètres à mettre dans la fonction, on cherche les éléments qui changent d’une utilisation à l’autre. Ces éléments variables deviendront ainsi les paramètres (ou arguments) de la fonction.

Les arguments des fonctions peuvent potentiellement être n’importe quel objet R. En particulier, certaines fonctions peuvent accepter des fonctions en argument.

# La fonction "map" de purrr prend une fonction en argument
purrr::map(\(x) mean(x, na.rm = TRUE))

# C'est également le cas de "across" de dplyr
starwars |> 
  summarise(across(c(height, mass), function(x) mean(x, na.rm = TRUE)))

10.1.1.1 Valeurs par défaut

Il est possible de spécifier des valeurs par défaut pour les arguments lors de la définition d’une fonction. Ces valeurs seront utilisées si aucune valeur n’est renseignée lors de l’appel de la fonction. Pour renseigner une valeur par défaut, la syntaxe suivante peut être utilisée :

f <- function(argument = valeur_defaut) {
  # Corps de la fonction
}

Dans l’aide d’une fonction existante, on voit les paramètres qui ont une valeur par défaut dans la section Usage.

Si un argument n’a pas de valeur par défaut, il est obligatoire : si l’utilisateur essaye d’appeler la fonction sans définir cet argument, cela génère une erreur.

Si à l’inverse un argument a une valeur par défaut, il devient facultatif : on peut utiliser la fonction sans préciser de valeur pour ce paramètre.

Lors de la définition d’une fonction, il vaut 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, et il sera possible d’appeler la fonction sans préciser de valeur pour ces arguments.

# Exemple de fonction prenant deux arguments, dont un avec une valeur par défaut
ma_fonction <- function(arg1, arg2 = 2020) {
  print(arg1)
  print(arg2)
}

# Appel de la fonction
ma_fonction(5) # arg1 vaut 5, arg2 vaut 2020
ma_fonction(5, 2010) # arg1 vaut 5, arg2 vaut 2010

10.1.1.2 Nombre variable d’arguments

Il est possible de créer des fonctions qui acceptent un nombre variable d’arguments, ce qui les rend flexibles et adaptables à différentes situations. Cette fonctionnalité est souvent utilisée lorsque nous ne savons pas à l’avance combien d’arguments nous devrons passer à une fonction.

Pour ce faire, on utilise l’opérateur ... comme argument de la fonction. Par ailleurs, il est d’usage de transformer les arguments fournis en liste dans le corps de la fonction pour que les arguments soient traités par la fonction.
On utilise alors la commande arguments <- list(...).

# Exemple de fonction acceptant un nombre variable d’arguments
ma_fonction <- function(...) {
  arguments <- list(...)
  print(arguments)
}

# Appel de la fonction
ma_fonction(5, 2010, "texte")
# Exemple de fonction acceptant un nombre variable d’arguments
calculer_somme_personnes_departements <- function(data, ...) {
  # Les arguments passés à la fonction sont stockés dans la variable arguments
  arguments <- list(...)

  # Filtrer le dataframe en fonction des départements spécifiés
  data_filtre <- data |> 
    filter(libgeo %in% arguments)

  # Calculer la somme du nombre de personnes
  somme_personnes <- data_filtre |>
    summarise(nb_personnes_total = sum(nombre_personnes))

  # Renvoyer la somme calculée
  return(somme_personnes |> 
            pull(nb_personnes_total))
}

# Appel de la fonction
calculer_somme_personnes_departements(revenus_dep, "Mayenne", "Sarthe")

10.1.2 Corps d’une fonction

Les accolades contiennent les instructions qui seront exécutées lorsque la fonction est appelée. Les accolades comprennent une série d’instructions R qui constituent le corps de la fonction. C’est ce code qui sera exécuté à l’appel de la fonction.
On utilise dans le corps de la fonction les arguments qui lui sont passés (renseignés dans les parenthèses lors de l’appel de la fonction).

10.1.3 Résultat d’une fonction

Le plus souvent, pour qu’elle soit utile, la fonction doit renvoyer un résultat. Ceci se fait via l’instruction return à qui on passe la valeur à retourner.

En réalité, l’utilisation de return() n’est pas obligatoire : une fonction retourne automatiquement le résultat de la dernière instruction qu’elle exécute. Mais il est souvent préférable de l’utiliser pour rendre le code plus lisible.

Lorsque R rencontre une instruction return() dans une fonction, il interrompt immédiatement son exécution et “sort” de la fonction en renvoyant le résultat.

10.1.3.1 Résultat sous forme d’une liste

On ne peut pas utiliser plusieurs return() pour renvoyer plusieurs résultats depuis une seule fonction. L’exécution d’une fonction s’arrête au premier return rencontré.

Est-ce à dire qu’une fonction R ne pourrait renvoyer qu’une seule valeur ? Non, car si elle ne peut retourner qu’un seul objet, celui-ci peut être complexe et comporter plusieurs valeurs.

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.

Exemple avec une fonction nommée indicateurs(), où on retourne une liste contenant la moyenne et l’écart-type d’un vecteur :

indicateurs <- function(v) {
  moyenne <- mean(v)
  ecart_type <- sd(v)
  resultat <- list(moyenne = moyenne, ecart_type = ecart_type)
  return(resultat)
}
indicateurs(hdv2003$age)
#> $moyenne
#> [1] 48.157
#> 
#> $ecart_type
#> [1] 16.94181

On a du coup un affichage lisible, et on peut accéder aux éléments du résultat via leur nom :

res <- indicateurs(hdv2003$age)
res$moyenne
#> [1] 48.157

10.1.4 Nommer une fonction

Enfin, pour que la fonction puisse être appelée et utilisée, il convient de lui donner un nom, ou plus précisément de la stocker dans un objet.

Le choix du nom de la fonction doit refléter clairement son objectif.

Un bon nom de fonction devrait :

  • Décrire précisément l’action réalisée par la fonction
  • Utiliser généralement un verbe d’action
  • Être suffisamment explicite pour comprendre le rôle de la fonction sans avoir à lire son code

Par exemple : calculer_somme() ; convertir_en_majuscules()

# Exemple du calcul de l'IMC
calcul_IMC <- function (poids, taille) {
  ## La taille est exprimée en mètres
  imc <- poids / taille ^ 2
  return(imc)
}

# Appel de la fonction
# On l’utilise en tapant son nom suivi de la valeur de ses arguments entre parenthèses
calcul_IMC(poids = 80, taille = 1.89)
calcul_IMC(poids = 60, taille = 1.55)

Une fonction se définit donc de la manière suivante :

10.2 Appel d’une fonction

ma_fonction <- function(arg1, arg2, arg3) {
  print(arg1)
  print(arg2)
  print(arg3)
}

Lors de l’appel de la fonction, on peut lui passer les arguments par position :

ma_fonction(x, 12, TRUE)

Dans ce cas, arg1 vaudra x, arg2 vaudra 12 et arg3 vaudra TRUE.

On peut aussi passer les arguments par nom :

ma_fonction(arg1 = x, arg2 = 12, arg3 = TRUE)

10.3 Exemples de fonctions

Identifier et écrire une fonction en suivant plusieurs étapes :

Étape 1 : repérer un bloc de code qui se répète => bloc d’instructions de la fonction. Le corps de la fonction, situé à l’intérieur d’un bloc {}, est la partie de la fonction qui permet d’appliquer les manipulations voulues pour obtenir le résultat à retourner.

Étape 2 : identifier ce qui varie dans le code => arguments (éléments variables) de la fonction. Il peut s’agir de l’objet qui sera transformé par la fonction (par exemple un dataframe) ou d’un paramètre utilisé dans la fonction (par exemple un entier).

Étape 3 : définir ce que renvoie la fonction => objet retourné par return

Étape 4 : définir ce que fait la fonction => nom de la fonction. On conseille de donner un verbe comme nom de fonction (par exemple calculer_moyenne, renommer_colonnes).

Une fonction personnelle s’utilise comme n’importe quelle autre fonction de R. On l’appelle en tapant son nom suivi de la valeur de ses arguments entre parenthèses.
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.

Une bonne façon d’écrire des fonctions efficaces est d’écrire des fonctions courtes.
Idéalement, une fonction fait une et une seule chose. Ainsi, il est plus facile de :

Comprendre ce que fait chaque fonction

Insérer et réutiliser les fonctions à de multiples endroits

Maintenir le code et en particulier modifier des aspects du traitement
library(dplyr)
library(tidyr)
library(janitor) # pour round_half_up

# fonction secteurs
secteur_1 <- function(df) {
  df |> 
    select(region, '2019', '2020') |> 
    filter(region %in% c("France métropolitaine", "Pays de la Loire")) 
}

# nouvelle variable emploi_salarie en mettant la liste des especes par transposition
secteur_2 <- function(df) {
  df |>   
    pivot_longer(cols = c('2019', '2020'), 
                 names_to = c("annee"),
                 values_to = "nb_emploi_salarie")
}

# utilisation fonction secteur
b_iaa <- secteur_1(b_iaa)
# ajout colonne secteur
b_iaa_long <- secteur_2(b_iaa) |> 
  mutate(secteur = "b2_iaa")
# fonction pour an_semestre en colonne
an_sem_col <- function(df) {
  df |> 
    pivot_wider( names_from = c(annee,semestre), 
                 names_prefix="an_",
                 values_from = montant)
}

# utilisation fonction pour an_semestre en colonne   
produits_AZ_C1_reg <- an_sem_col(produits_long_AZ_C1_reg)
# fonction pour calcul évolutions et / semestre précédent
calcul_evol <- function(df) {
   df |> 
      mutate(diff2022_sem1_evol = an_2022_sem1 - an_2021_sem1,
             diff2022_sem1_evol_perc = (an_2022_sem1 / an_2021_sem1 -1) * 100,
             diff2021_sem2_evol_prec = an_2021_sem2-an_2021_sem1,
             diff2021_sem2_evol_prec_perc = (an_2021_sem2 / an_2021_sem1 -1) * 100,
             diff2022_sem1_evol_prec = an_2022_sem1-an_2021_sem2,
             diff2022_sem1_evol_prec_perc = (an_2022_sem1 / an_2021_sem2 -1) * 100 
             )
}

# utilisation fonction calcul évolutions
produits_AZ_C1_reg <- calcul_evol(produits_AZ_C1_reg) 
# pour calculer le ratio entre la production de PDL (eff_aaaa_pdl) et la production FM (eff_aaaa_fm).
# on crée une nouvelle colonne pour chaque colonne qui commence par "eff_" et finit par "_pdl"
# La fonction est définie avec \(x)
# x représente les données de la colonne courante 
# La valeur de cette colonne est calculée en divisant la valeur de colonne actuelle (x) 
# par la valeur d'une autre colonne.
# Le nom de cette autre colonne est obtenu en remplaçant "_pdl" par "_fm" 
# dans le nom de la colonne actuelle (cur_column()). 
# cur_column() donne le nom de la colonne actuelle (uniquement dans across())
# get est utilisé pour obtenir le contenu de cette autre colonne (le dénominateur) à partir de son nom. 
# Le résultat est multiplié par 100 pour obtenir un pourcentage.
# La fonction .names est utilisée pour renommer les nouvelles colonnes 
# en ajoutant le préfixe part_ avant le nom de la colonne originale.
# .col représente le nom de la colonne courante
# ne pas oublier le point dans .col
# .names = "part_{.col}" : Ceci spécifie le format du nom de la nouvelle colonne.
# {.col} est remplacé par le nom de la colonne actuelle. 
# Par exemple, si la colonne actuelle est "eff_2000_pdl", le nom de la nouvelle colonne sera "part_eff_2000_pdl".
eff_volailles_annee_part <- eff_volailles_annee |>
  pivot_wider(names_from = reg_court, 
              values_from = contains("eff"), 
              names_prefix = "") |>
  # fonction pour calcul_part_pdl
  mutate(across(
    matches("eff_.*_pdl"), 
    \(x) {
      fm_col <- str_replace(cur_column(), "_pdl", "_fm")
      x / get(fm_col) * 100
    },
    .names = "part_{.col}")) |>
  relocate(starts_with("part_"), .after = espece)

eff_volailles_annee_part <- eff_volailles_annee_part |> 
  mutate(across(starts_with("part_"), \(x) round_half_up(x, digits = 2))) |> 
  arrange(espece)
# cas de plusieurs modalités par variable
# traitement récurrent
# fonction pour effectuer la transformation des données dep + reg
transform_data <- function(data_entree) {
  data_dep_1 <- data_entree |>
    select(-contains("reg")) |> 
    pivot_longer(cols = contains("_dep"), 
                 names_to = "indicateur", 
                 values_to = "donnees_dep", 
                 names_pattern = "(.*)_dep")
  
  data_dep_2 <- data_dep_1 |>
    pivot_wider(names_from = depart, 
                values_from = donnees_dep)
  
  data_reg <- data_entree |> 
    select(-contains("dep")) |> 
    pivot_longer(cols = contains("_reg"),
                 names_to = "indicateur",
                 values_to = "ensemble",
                 names_pattern = "(.*)_reg") |> 
    distinct()
  
  # regroupement dep reg avec left_join 
  agreg_data_dep_reg <- data_dep_2 |> 
    left_join(data_reg, 
                join_by(indicateur, modalites))
  
  return(agreg_data_dep_reg)
}

# utilisation de la fonction transform_data
agreg_tr_surf_logt_vol_chair_grand_dep_reg_2 <- transform_data(data_entree=agreg_tr_surf_logt_vol_chair_grand)
# Définition d'un opérateur `%not_in%` pour plus de clarté dans les filtres
`%not_in%` <- purrr::negate(`%in%`)

# on utilise un paramètre negate pour inverser la logique de filtrage
# le paramètre negate inverse une condition logique avec !
# booléen qui détermine si on souhaite appliquer la logique normale ou inversée pour filtrer les données.
# on utilise la négation de %in% pour le total des volailles
# équivalent pour la ligne : produit %not_in% c("Ensemble gallus","Lapines reproductrices") ~ "12_Total_volailles",
# purrr::negate(`%in%`) : crée une nouvelle fonction qui inverse le résultat d'une fonction existante
process_data_avant_2010 <- function(data, filter_espece, espece_modalite, negate = FALSE) {
  result <- data |>
    mutate(
      espece = case_when(
        # Si negate est FALSE et produit est dans filter_espece, 
        # alors espece prend la valeur espece_modalite
        negate == FALSE & produit %in% filter_espece ~ espece_modalite,
        
        # Si negate est TRUE et produit n'est pas dans filter_espece,
        # alors espece prend la valeur espece_modalite
        negate == TRUE & !(produit %in% filter_espece) ~ espece_modalite,
        
        # Dans tous les autres cas, espece prend la valeur produit
        .default = produit
      )
    ) |>
    # Filtrage pour inclure uniquement les valeurs de espece_modalite
    filter(espece %in% espece_modalite) |>
    group_by(reg_court, annee, espece) |>
    mutate(valeur_pdl = sum(valeur)) |>
    arrange(reg_court, annee, espece) |>
    ungroup() |>
    group_by(annee, espece) |>
    mutate(valeur_fm = sum(valeur)) |>
    arrange(annee, espece) |>
    ungroup() |>
    filter(reg_court == "pdl") |>
    select(-c(reg, reg_court, produit, valeur)) |>
    distinct()
  
  return(result)
}

# Application de la fonction pour chaque cas
# Cas standard (negate = FALSE, par défaut)
# produit %in% filter_espece ~ espece_modalite
# Sélectionne les produits qui sont dans filter_espece
# Exemple : "Canards à gaver" ET "Canards à rôtir" → "06_07_canards_cheptel"
eff_volailles_avant_2010_regroupt_1 <- process_data_avant_2010(data = eff_volailles_avant_2010, 
                                           filter_espece = c("Canards à gaver", "Canards à rôtir"), 
                                           espece_modalite = "06_07_canards_cheptel")

eff_volailles_avant_2010_regroupt_2 <- process_data_avant_2010(eff_volailles_avant_2010, 
                                           filter_espece = c("Poules pondeuses d'oeufs à couver", 
                                                            "Poules pondeuses d'oeufs de consommation"), 
                                           espece_modalite = "01_02_Poules_pondeuses")

# Cas inversé (negate = TRUE) 
# !(produit %in% filter_espece) ~ espece_modalite
# Sélectionne les produits qui NE SONT PAS dans filter_espece
# Équivalent à produit %not_in% filter_espece
# pour la ligne : produit %not_in% c("Ensemble gallus","Lapines reproductrices") ~ "12_Total_volailles",
# Exemple : Tout SAUF "Ensemble gallus" et "Lapines reproductrices" → "12_Total_volailles"
eff_volailles_avant_2010_regroupt_3 <- process_data_avant_2010(eff_volailles_avant_2010, 
                                                  filter_espece = c("Ensemble gallus", "Lapines reproductrices"), 
                                                  espece_modalite = "12_Total_volailles",
                                                  negate = TRUE)

# Combinaison des résultats
eff_volailles_avant_2010_3 <- bind_rows(eff_volailles_avant_2010_2,
                                        eff_volailles_avant_2010_regroupt_1,
                                        eff_volailles_avant_2010_regroupt_2,
                                        eff_volailles_avant_2010_regroupt_3)

10.4 Portée des variables

Un objet créé dans une fonction n’existe que dans cette fonction.

Un objet créé à l’intérieur d’une fonction est temporaire et n’est pas accessible à l’extérieur de celle-ci.

f <- function() {
  nouvel_objet <- 15
  nouvel_objet
}

f()
#> [1] 15
nouvel_objet
#> Error: object 'nouvel_objet' not found

Ici, nouvel_objet existe tant qu’on est dans la fonction, mais il est détruit dès qu’on en sort et donc inaccessible dans l’environnement global.

Les objets créés dans la session courante et qui existent dans l’environnement (visible dans l’onglet Environment de RStudio) sont appelés des objets globaux : ils existent et sont accessibles pour les fonctions appelées depuis cet environnement.

Les objets créés lors de l’exécution d’une fonction sont à l’inverse des objets locaux : ils n’existent qu’à l’intérieur de la fonction et pour la durée de son exécution. Les variables locales ont une portée limitée à une fonction ou à un environnement spécifique. Elles existent dans des environnements à portée limitée (comme un environnement d’exécution).
Les variables locales sont par exemple les arguments d’une fonction ou les variables définies à l’intérieur d’une fonction.

Si deux objets du même nom coexistent, l’objet local est prioritaire par rapport à l’objet global.

Si on souhaite modifier un objet global, on doit le passer comme argument en entrée de la fonction, et le renvoyer comme résultat en sortie. Pour que le recodage soit bien répercuté dans le tableau df, on doit faire :

recode_taille <- function(d) {
  d$taille <- d$taille / 100
  return(d)
}

df <- recode_taille(df)

# Le recodage est bien conservé

10.5 Les conditions if, else

Les commandes if et else sont utilisables pour exécuter du code sous certaines conditions.
Cela permet de réaliser ainsi une exécution conditionnelle.
Le “then” n’existe pas : il est implicite après les accolades.

Ainsi, une série d’instructions ne sera exécutée que dans certins cas. Par exemple, on va pouvoir exécuter certaines instructions selon la valeur prise par les arguments de la fonction.

10.5.1 if

L’instruction if permet de n’exécuter du code que si une condition est remplie.

if est suivie d’une condition (entre parenthèses) puis d’un bloc de code (entre accolades). Ce bloc de code n’est exécuté que si la condition est vraie.

Une utilisation possible dans le cadre d’une fonction est de n’exécuter certaines instructions que si la valeur d’un argument vaut une valeur donnée. Dans l’exemple suivant, on n’applique la fonction round() que si l’argument arrondir vaut TRUE.

moyenne <- function(x, arrondir = TRUE) {
    res <- mean(x)
    if (arrondir == TRUE) {
        res <- round(res)
    }
    res
}

v <- c(1.4, 2.3, 8.9)
moyenne(v)
moyenne(v, arrondir = FALSE)

On notera que le test x == TRUE est en fait redondant, car son résultat est le même que la valeur de x :

  • si x vaut TRUE, x == TRUE vaut TRUE
  • si x vaut FALSE, x == TRUE vaut FALSE

On remplacera donc en général if (x == TRUE) par if (x).

De la même manière, on pourra remplacer if (x == FALSE) par if (!x).

Dans la fonction moyenne ci-dessus, on peut donc remplacer :

if (arrondir == TRUE) {
    res <- round(res)
}

Par :

if (arrondir) {
    res <- round(res)
}

À noter également que quand le bloc de code qui suit une instruction if ne comporte qu’une seule instruction, on peut omettre les accolades qui l’entourent. Les syntaxe suivantes sont donc équivalentes :

if (arrondir) {
    res <- round(res)
}

if (arrondir) res <- round(res)

10.5.2 if / else

else permet d’exécuter des instructions lorsque la condition donnée au if n’est pas remplie.

else s’utilise après if, mais n’est pas obligatoire.

else précède un autre bloc de code R qui ne s’exécute que si la condition donnée au if est fausse :

On peut ainsi utiliser if / else pour effectuer deux actions différentes en fonction de la valeur d’un argument. La fonction suivante génère deux graphiques différents selon le type du vecteur passé en argument :

graph_var <- function(x) {
    if (is.character(x)) {
        barplot(table(x))
    } else {
        hist(x)
    }
}

graph_var(c("Pomme", "Pomme", "Citron"))

graph_var(c(1, 5, 10, 3, 1, 4))

10.5.3if” / “else if” / “else

On peut enchaîner plusieurs blocs d’instructions if / else if / else.

Une possibilité complémentaire est d’ajouter des blocs else if qui permettent d’ajouter des conditions supplémentaires. Dès qu’une condition est vraie, le bloc de code correspondant est exécuté. Le dernier bloc else est exécuté si aucune des conditions n’est vraie.

On peut donc améliorer encore la fonction graph_var() pour tester plusieurs types explicitement et afficher un message si aucun type géré n’a été reconnu.

graph_var <- function(x) {
    if (is.character(x)) {
        barplot(table(x))
    } else if (is.numeric(x)) {
        hist(x)
    } else {
        message("Le type de x n'est pas géré par la fonction")
    }
}

graph_var(c(TRUE, FALSE, TRUE))

Attention, seul le bloc de la première condition vraie est exécuté, l’ordre des conditions est donc important. Dans l’exemple suivant, le second bloc n’est jamais exécuté et donc le second message jamais affiché.

test_x <- function(x) {
    if (x < 100) {
        message("x est inférieur à 100")
    } else if (x < 10) {
        message("x est inférieur à 10")
    }
}

test_x(5)

Il est donc important d’ordonner les conditions de la plus spécifique à la plus générale.

10.5.4 Construction de conditions complexes

On peut combiner plusieurs tests avec les opérateurs logiques classiques :

  • ! est l’opérateur “not”, qui teste si la condition qu’il précède est fausse
  • && est l’opérateur “et”, qui est vrai si les deux conditions qu’il réunit sont vraies
  • || est l’opérateur “ou inclusif”, qui est vrai si au moins l’une des deux conditions qu’il réunit sont vraies
  • xor est l’opérateur “ou exclusif”, qui retourne TRUE si l’une ou l’autre des propositions est vraie, pas les deux

Ainsi, si on veut qu’une variable temperature soit comprise entre 15 et 25, on écrira :

verifie_temperature <- function(temperature) {
    if (temperature >= 15 && temperature <= 25) {
        message("Température ok")
    }
}
verifie_temperature(20)

Si on souhaite tester que temperature est inférieure à 15 ou supérieure à 25 :

verifie_temperature <- function(temperature) {
    if (temperature < 15 || temperature > 25) {
        message("Température pas cool")
    }
}
verifie_temperature(10)

Si on veut tester si temperature vaut NULL, on peut utiliser is.null().

verifie_temperature <- function(temperature = NULL) {
    if (is.null(temperature)) {
        message("Merci d'indiquer une température")
    }
}
verifie_temperature()

Mais si à l’inverse on veut tester si temperature n’est pas NULL, on inverse le test précédent en utilisant !.

verifie_temperature <- function(temperature = NULL) {
    if (!is.null(temperature)) {
        message("Merci d'avoir indiqué une température")
    }
}
verifie_temperature(15)

On pourra noter qu’il existe deux types d’opérateurs “et” et “ou” dans R :

  • Les opérateurs simples & et | sont des opérateurs vectorisés. Ils peuvent s’appliquer à des vecteurs et retourneront un vecteur de TRUE et FALSE.
  • Les opérateurs doubles && et || ne peuvent retourner qu’une seule valeur, et si on leur fournit des vecteurs ils génèrent une erreur.
x <- 1:5
x > 0 & x <= 2
x > 0 && x <= 2

Quand on passe un test à un if, celui-ci est censé retourner une unique valeur TRUE ou FALSE. Une erreur fréquente, notamment quand on est dans une fonction, est de passer à if une condition appliquée à un vecteur. Dans ce cas R affiche une erreur.

superieur_a_5 <- function(x) {
    if (x >= 5) {
        message(">=5")
    }
}

superieur_a_5(1:10)
## Error in if (x >= 5) {: la condition est de longueur > 1

À retenir : quand on utilise l’instruction if, la condition qui lui est passée entre parenthèses ne doit renvoyer qu’une seule valeur TRUE ou FALSE. Si on utilise une condition complexe, on utilisera donc plutôt les opérateurs doubles && et ||.

10.5.5 Différence entre if / else et if_else

Une source fréquente de confusion concerne la différence entre les instructions if / else et la fonction if_else() de dplyr. Les deux sont pourtant très différentes :

  • if / else s’utilisent quand on teste une seule condition et qu’on veut exécuter des blocs de code différents selon son résultat
  • if_else applique un test à tous les éléments d’un vecteur et retournent un vecteur dont les éléments dépendent du résultat de chaque test

Premier cas de figure : un objet x contient une seule valeur et on veut afficher un message différent selon si celle-ci est inférieure ou supérieure à 10. Dans ce cas on utilise if / else.

x <- 5

if (x >= 10) {
    message(">=10")
} else {
    message("<10")
}

Deuxième cas de figure : x est un vecteur et on souhaite recoder chacune de ses valeurs selon le même critère que ci-dessus. Dans ce cas on utilise if_else.

x <- 5:15
x_rec <- if_else(x >= 10, ">=10", "<10")
x_rec
# fonction montants par departement pour les différentes années ----
# Fonction pour calculer les montants par département
# Paramètre annee : la fonction prend en entrée l'année pour laquelle on souhaite effectuer les calculs.
# La fonction glue() permet d’insérer des variables et des expressions directement dans des chaînes de caractères
# Cela permet d’évaluer dynamiquement les expressions à l’intérieur des accolades {}
calculer_montants_par_departement <- function(annee) {
  # Utilisation de substr() pour extraire les deux derniers chiffres de l'année
  an <- substr(as.character(annee), 3, 4)
  # Construire les noms de fichiers et les chemins
  # Les noms de fichiers sont stockés dans des variables qui ont des noms dynamiques,
  # c'est-à-dire que les noms de ces variables dépendent de la valeur de 'annee'.
  # Les noms des fichiers sont construits dynamiquement en fonction de l'année passée en paramètre.
  # Pour accéder à la valeur de la variable dont le nom est contenu dans une chaîne de caractères,
  # on utilise `get` (pour interpréter cette chaîne comme le nom d'une variable et accéder à sa valeur).
  # get` permet d'évaluer une chaîne de caractères comme un nom de variable
  # et de récupérer la valeur associée à ce nom de variable
  # get() prend cette chaîne de caractères et recherche une variable avec ce nom dans l'environnement actuel.
  # La fonction get() prend en entrée une chaîne de caractères représentant le nom d'un objet et retourne cet objet
  # Dans notre cas, la chaîne de caractères est le nom du fichier à charger.
  # get() va donc chercher dans l'environnement global l'objet ayant ce nom et le renvoyer.
  fichier_beneficiaires <- get(glue("fichier_beneficiaires_{annee}"))
  fichier_paiements_p1 <- get(glue("fichier_paiements_p1_{annee}"))
  fichier_paiements_ichn_aras <- get(paste0("fichier_paiements_ichn_aras_", annee))
  fichier_paiements_bio_maec <- get(paste0("fichier_paiements_bio_maec_", annee))
  
  # Construire la partie dynamique du chemin
  chemin_pac_annee <- get(glue("pac_{annee}"))
  
  # Construire le chemin complet
  chemin_fichier_benef <- glue("{chemin_aides_pac}{chemin_pac_annee}{fichier_beneficiaires}")
  chemin_fichier_paiements_p1 <- glue("{chemin_aides_pac}{chemin_pac_annee}{fichier_paiements_p1}")
  chemin_fichier_paiements_ichn_aras <- glue("{chemin_aides_pac}{chemin_pac_annee}{fichier_paiements_ichn_aras}")
  chemin_fichier_paiements_bio_maec <- glue("{chemin_aides_pac}{chemin_pac_annee}{fichier_paiements_bio_maec}")
  
  # Lecture des fichiers
  fic_benef <- readRDS(file = chemin_fichier_benef) |>
    as_tibble(.name_repair = make_clean_names) |>
    filter(nreg == "52")
  
  fic_paiements_p1 <- readRDS(file = chemin_fichier_paiements_p1) |>
    as_tibble(.name_repair = make_clean_names)
  
  fic_paiements_ichn_aras <- readRDS(file = chemin_fichier_paiements_ichn_aras) |>
    as_tibble(.name_repair = make_clean_names)
  
  fic_fichier_paiements_bio_maec <- readRDS(file = chemin_fichier_paiements_bio_maec) |>
    as_tibble(.name_repair = make_clean_names)
  
  # Jointure des fichiers et calcul des montants par département
  # on remplace les années fixes par le paramètre 'annee' (4 positions) ou 'an' (2 positions)
  # Pour accéder aux colonnes de manière dynamique (accéder aux données des variables dynamiques)
  # Si un argument est un nom de colonne passé sous la forme d’une chaîne de caractères ("variable"),
  # on y accède avec le pronom .data et l’opérateur double crochet, soit .data[["variable"]]
  # .data est un pronom qui fait référence au dataframe en cours de traitement.
  # [[]] est utilisé pour accéder à une colonne par son nom (en utilisant une chaîne de caractères).
  # NA_real_: spécifiquement pour les valeurs manquantes de type numérique
  montants_pdl <- fic_benef |>
    left_join(fic_paiements_p1, join_by(pacage)) 
    left_join(fic_paiements_ichn_aras, join_by(pacage)) |>
    left_join(fic_fichier_paiements_bio_maec, join_by(pacage)) |>
    group_by(dep) |>
    summarise(
      montant_aide_1er_pilier = sum(.data[[paste0("aides_p1_", an)]] / 1000, na.rm = TRUE),
      montant_aide_decouplee = sum(.data[[paste0("pu_", an)]] / 1000, na.rm = TRUE),
      dont_paiement_de_base = sum(.data[[paste0("pbase_", an)]] / 1000, na.rm = TRUE),
      dont_paiement_vert = if (an == "23") {
        NA_real_
      } else {
        sum(.data[[paste0("pvert_", an)]] / 1000, na.rm = TRUE)
      }, # n'existe plus en 2023
      dont_paiement_ecoregime = if (an == 23) {
        sum(.data[[paste0("pecor_", an)]] / 1000, na.rm = TRUE)
      } else {
        NA_real_
      }, # Nouvelle aide PAC 2023
      montant_aides_couplees_animales = sum(.data[[paste0("aca_", an)]] / 1000, na.rm = TRUE),
      dont_aide_aux_bovins_totale_post_2023 = if (an == 23) {
        sum(.data[[paste0("ab_", an)]] / 1000, na.rm = TRUE)
      } else {
        NA_real_
      }, # Nouvelle aide PAC 2023
      dont_aide_aux_bovins_totale = if (an == 23) {
        NA_real_
      } else {
        sum((.data[[paste0("aba_", an)]] +
               .data[[paste0("vm_", an)]] +
               .data[[paste0("abl_", an)]]) / 1000, na.rm = TRUE)
      }, # n'existe plus en 2023
      montant_aide_totale = sum(montant_aide_1er_pilier,
                                montant_aide_2d_pilier,
                                na.rm = TRUE
      )
    ) |>
    ungroup() |>
    pivot_longer(-dep,
                 names_to = "indicateur",
                 values_to = "valeur"
    ) |>
    pivot_wider(names_from = "dep", values_from = "valeur")
  
  # Renommage des colonnes : Les colonnes sont renommées avec un suffixe correspondant à l'année.
  # ajout suffixe année aux noms de colonnes sauf pour la première colonne (indicateur)
  montants_pdl <- montants_pdl |>
    rename_with(
          \(x) paste0(x, "_", annee),
          -indicateur
          )
  
  # Retourner une liste contenant les dataframes
  return(list(
    fic_benef = fic_benef,
    fic_paiements_p1 = fic_paiements_p1,
    fic_paiements_ichn_aras = fic_paiements_ichn_aras,
    fic_fichier_paiements_bio_maec = fic_fichier_paiements_bio_maec,
    montants_pdl = montants_pdl
  ))
}

# Calculer les montants pour 2020...
montants_2020 <- calculer_montants_par_departement(annee = 2020)
montants_2021 <- calculer_montants_par_departement(annee = 2021)
montants_2022 <- calculer_montants_par_departement(annee = 2022)
montants_2023 <- calculer_montants_par_departement(annee = 2023)

# Pour accéder aux éléments de la liste :
beneficiaires_2020 <- montants_2020$fic_benef
beneficiaires_2021 <- montants_2021$fic_benef
paiements_p1_2020 <- montants_2020$fic_paiements_p1
paiements_p1_2021 <- montants_2021$fic_paiements_p1
montants_pac_2020 <- montants_2020$montants_pdl

# Combiner les resultats des différentes années
montants_pac_2020_2023 <- montants_pac_2020 |>
  left_join(montants_2021$montants_pdl, join_by(indicateur)) |>
  left_join(montants_2022$montants_pdl, join_by(indicateur)) |>
  left_join(montants_2023$montants_pdl, join_by(indicateur))
# fonction nb_beneficiaires pour les différentes années ----
calculer_nb_beneficiaires_par_departement <- function(annee) {
  an <- substr(as.character(annee), 3, 4)
  fichier_beneficiaires <- get(glue("fichier_beneficiaires_{annee}"))
  fichier_paiements_p1 <- get(glue("fichier_paiements_p1_{annee}"))
  fichier_paiements_ichn_aras <- get(paste0("fichier_paiements_ichn_aras_", annee))
  fichier_paiements_bio_maec <- get(paste0("fichier_paiements_bio_maec_", annee))
  
  # Construire la partie dynamique du chemin
  chemin_pac_annee <- get(glue("pac_{annee}"))
  
  # Construire le chemin complet
  chemin_fichier_benef <- glue("{chemin_aides_pac}{chemin_pac_annee}{fichier_beneficiaires}")
  chemin_fichier_paiements_p1 <- glue("{chemin_aides_pac}{chemin_pac_annee}{fichier_paiements_p1}")
  chemin_fichier_paiements_ichn_aras <- glue("{chemin_aides_pac}{chemin_pac_annee}{fichier_paiements_ichn_aras}")
  chemin_fichier_paiements_bio_maec <- glue("{chemin_aides_pac}{chemin_pac_annee}{fichier_paiements_bio_maec}")
  
  fic_benef <- readRDS(file = chemin_fichier_benef) |>
    as_tibble(.name_repair = make_clean_names) |>
    filter(nreg == "52")
  
  fic_paiements_p1 <- readRDS(file = chemin_fichier_paiements_p1) |>
    as_tibble(.name_repair = make_clean_names)
  
  fic_paiements_ichn_aras <- readRDS(file = chemin_fichier_paiements_ichn_aras) |>
    as_tibble(.name_repair = make_clean_names)
  
  fic_fichier_paiements_bio_maec <- readRDS(file = chemin_fichier_paiements_bio_maec) |>
    as_tibble(.name_repair = make_clean_names)
  
  nb_beneficiaires_pdl <- fic_benef |>
    left_join(fic_paiements_p1, join_by(pacage)) |>
    left_join(fic_paiements_ichn_aras, join_by(pacage)) |>
    left_join(fic_fichier_paiements_bio_maec, join_by(pacage)) |>
    mutate(
      benef_p1_ligne = as.numeric(.data[[paste0("aides_p1_", an)]] > 0),
      benef_p2_ligne = as.numeric((.data[[paste0("ichn_", an)]] > 0) |
                                    (.data[[paste0("aras_", an)]] > 0) |
                                    (.data[[paste0("bio_", an)]] > 0) |
                                    (.data[[paste0("maec_", an)]] > 0)),
      benef_p1_p2_ligne = as.numeric((benef_p1_ligne > 0) |
                                       (benef_p2_ligne > 0)),
      .after = pacage
    ) |>
    group_by(dep) |>
    mutate(
      nb_beneficiaires_p1_dep = sum(benef_p1_ligne, na.rm = TRUE),
      nb_beneficiaires_p2_dep = sum(benef_p2_ligne, na.rm = TRUE),
      nb_beneficiaires_p1_p2_dep = sum(benef_p1_p2_ligne, na.rm = TRUE),
      .after = pacage
    ) |>
    ungroup() |>
    mutate(
      nb_beneficiaires_p1_reg = sum(benef_p1_ligne, na.rm = TRUE),
      nb_beneficiaires_p2_reg = sum(benef_p2_ligne, na.rm = TRUE),
      nb_beneficiaires_p1_p2_reg = sum(benef_p1_p2_ligne, na.rm = TRUE),
      .after = nb_beneficiaires_p1_p2_dep
    ) |>
    distinct(
      dep, nb_beneficiaires_p1_dep,
      nb_beneficiaires_p2_dep,
      nb_beneficiaires_p1_p2_dep,
      nb_beneficiaires_p1_reg,
      nb_beneficiaires_p2_reg,
      nb_beneficiaires_p1_p2_reg
    ) |>
    arrange(dep)
  
  return(nb_beneficiaires_pdl)
}

# uitilisation de la fonction pour les différentes années
nb_beneficiaires_2020 <- calculer_nb_beneficiaires_par_departement(annee = 2020)
nb_beneficiaires_2021 <- calculer_nb_beneficiaires_par_departement(annee = 2021)
nb_beneficiaires_2022 <- calculer_nb_beneficiaires_par_departement(annee = 2022)
nb_beneficiaires_2023 <- calculer_nb_beneficiaires_par_departement(annee = 2023)

# Combiner les resultats des différentes années
# Renommer les colonnes de chaque dataframe pour éviter les conflits
nb_beneficiaires_2020_suffixe <- nb_beneficiaires_2020 |>
rename_with(
  \(x) paste0(x, "_2020"),
  -dep
)

nb_beneficiaires_2021_suffixe <- nb_beneficiaires_2021 |>
rename_with(
  \(x) paste0(x, "_2021"),
  -dep
)

nb_beneficiaires_2022_suffixe <- nb_beneficiaires_2022 |>
rename_with(
  \(x) paste0(x, "_2022"),
  -dep
)

nb_beneficiaires_2023_suffixe <- nb_beneficiaires_2023 |>
rename_with(
  \(x) paste0(x, "_2023"),
  -dep
)

# Effectuer les jointures
nb_beneficiaires_pac_2020_2023 <- nb_beneficiaires_2020_suffixe |>
  left_join(nb_beneficiaires_2021_suffixe, join_by(dep)) |>
  left_join(nb_beneficiaires_2022_suffixe, join_by(dep)) |>
  left_join(nb_beneficiaires_2023_suffixe, join_by(dep))

10.6 Les boucles for et while pour répéter des instructions

Les boucles permettent de répéter un traitement plusieurs fois, soit en fonction d’une condition soit selon les éléments d’un vecteur.

10.6.1 for

La boucle définie par l’instruction for permet de répéter une opération sur un ensemble de valeurs d’une séquence.
Sa structure est la suivante :

Le principe est le suivant : on fournit à for entre parenthèses une expression du type item in vecteur, puis un bloc de code entre accolades. for va exécuter le bloc de codes pour chacune des valeurs de vecteur, et affectera tour à tour à item la valeur courante de vecteur.

Exemple de fonction

diag_IMC <- function(poids,taille) {
  imc <- poids / taille ^ 2
  if (imc < 18.5) {diag <- "maigre"}
  else if (imc < 25) {diag <- "normal"}
       else {diag <- "surpoids"}
  return(diag)
}

diag_IMC (poids=60,taille=1.89)
diag_IMC (poids=80,taille=1.89)
diag_IMC (poids=80,taille=1.55)

On peut utiliser une boucle for :

for (pp in seq(from = 50, to = 100, by = 5)) {
  # print permet d'afficher des informations dans la console, comme cat 
  print(paste ("Taille = 1,70m, poids =", pp, "Diagnotic :",
               diag_IMC (poids = pp, taille = 1.70)))
}

10.6.2 while

La boucle while permet de répéter une opération tant qu’une condition est vérifiée. while prend en argument une condition et est suivi d’un bloc de code entre accolades. Elle exécute le bloc tant que la condition est vraie :

Par exemple, la fonction suivante simule un tirage à pile ou face à l’aide de la fonction sample(). La simulation de tirage s’exécute et affiche le résultat tant qu’on obtient “Pile” (et interrompt la boucle au premier “Face”) :

resultat <- "" # initialisation de la variable `resultat` avec une chaîne vide
while (resultat != "Face") {
    resultat <- sample(c("Pile", "Face"), size = 1)
    print(resultat)
}
[1] "Pile"
[1] "Pile"
[1] "Face"

10.6.3 Quand (ne pas) utiliser des boucles for et while

Le mécanisme des boucles, assez intuitif, peut être utilisé pour beaucoup d’opérations. Il y a cependant sous R des alternatives souvent plus rapides, qu’il est préférable de privilégier.

Avant tout, de nombreuses fonctions R sont “vectorisées” et s’appliquent directement à tous les éléments d’un vecteur. Dans le cas où une fonction vectorisée existe déjà, elle propose en général une syntaxe plus compacte et une exécution (beaucoup) plus rapide.

Par exemple, si on souhaite remplacer dans tous les éléments d’un vecteur de chaînes de caractères le caractère “X” par le caractère “o”, on pourrait être tenter de faire une boucle du type :

mots <- c("brXuette", "mXtX", "iglXX")
for (i in seq_along(mots)) {
    mots[i] <- str_replace_all(mots[i], "X", "o")
}

Or c’est tout à fait inutile car str_replace_all() étant vectorisée, on peut l’appliquer directement à un vecteur sans utiliser de boucle.

mots <- str_replace_all(mots, "X", "o")

Entre les fonctions vectorisées existantes et les possibilités fournies par purrr, il est assez rare de devoir utiliser une boucle directement dans R. Pour autant, il ne faut pas non plus tomber dans l’excès inverse et considérer que tout usage de for ou while doit être évité : ces fonctions sont parfaitement justifiées dans certains cas de figure, et si vous trouvez une solution qui fonctionne de manière efficace avec une boucle for, il n’est pas forcément utile de chercher à la remplacer.

10.7 Itérer avec purrr

Des fonctions tirées du package purrr permettent d’appliquer une même fonction à chaque élément d’une liste ou d’un vecteur.

purrr est une extension du tidyverse qui fournit des outils pour travailler avec les vecteurs et les fonctions, et notamment pour itérer sur les éléments de vecteurs ou de listes en leur appliquant une fonction.

10.7.1 Principe demap et ses variantes

L’objectif de map est d’appliquer une fonction à l’ensemble des éléments d’un vecteur ou d’une liste.
La fonction map() de purrr prend deux arguments principaux :

  1. un vecteur ou une liste d’entrée
  2. une fonction

et elle retourne une liste contenant le résultat de la fonction appliquée à chaque élément du vecteur ou de la liste.

En résumé, Le traitement à effectuer correspond à une fonction à appliquer . On peut la renseigner de différentes façons :

  • avec le nom de la fonction (sans parenthèses): lorsque la fonction n’a besoin que d’un seul paramètre, qui est l’élément courant à traiter
    exemple map(…, mean)
  • avec une fonction anonyme
    • définie avec la syntaxe concise \(x) et x : les éléments successifs de la liste sont alors identifiés avec x
      exemple map(…, (x) x |> summarise(moyenne = mean(population))) exemple map(…, (x) read_csv2(x))

    • définie avec la syntaxe classique, avec function(x)
      exemple map(…, function(x) read_csv2(x))
      # ou avec un nom de paramètre plus explicite
      exemple map(…, function(nom_fichier) read_csv2(nom_fichier)) map(…, function(x) { … })

      map(fichiers, function(nom_fichier) {
        data <- read_csv2(nom_fichier)
        mutate(data, date = as.Date(date))  # Transformation supplémentaire
        })

x représente chaque élément courant à traiter (la place de la variable à traiter dans la fonction). x est une variable positionnelle qui représente l’élément courant. Les noms de paramètres dans function(x) peuvent être personnalisés pour améliorer la lisibilité du code.

# 1. Lecture de fichiers CSV avec \(x)
donnees <- map(fichiers, \(f) read_csv2(f))

# 2. Nettoyage avec function(nom_param)
donnees_nettoyees <- map(donnees, function(df) {
  df |>
    filter(!is.na(id)) |>
    mutate(across(where(is.numeric), 
           \(x) replace_na(x, 0)))
})

# 3. Résumé statistique avec \(x)
stats <- map(
  donnees_nettoyees, 
  \(x) x |> 
    summarise(
      across(
        where(is.numeric), 
        list(mean = mean, somme = sum)
      )
    )
)

Les fonctions map 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, de combiner tous les résultats en un seul dataframe.
reduce() permet de combiner les éléments d’une liste en un seul élément en appliquant une fonction de réduction.

reduce() applique une fonction de façon récursive à chaque élément d’une liste ou d’un vecteur

# Création d'une liste des fichiers à traiter
donnees_RP <- c("donnees_RP2020.csv", "donnees_RP2015.csv", "donnees_RP2010.csv") |> 
 map(read_delim,
     delim = ";", # séparateur point-virgule
     locale = locale(decimal_mark = ","), # virgule comme séparateur décimal
     col_types = cols(CODGEO = col_character(), .default = col_double())) |> # définit les types des colonnes
 reduce(full_join)

Dans cet exemple, on crée une liste de fichiers à traiter, puis on utilise map() pour lire chaque fichier avec read_delim().

On utilise reduce(full_join) pour fusionner tous les fichiers en une seule table.

Après import, les trois fichiers sont joints les uns après les autres pour obtenir une seule table contenant toutes les informations.

10.7.2 Nommage des résultats

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 (comme dans une liste classique).

La fonction set_names permet d’assigner des noms aux éléments d’un vecteur ou d’une liste en entrée de map.
Un piège à éviter est d’avoir une longueur incohérente : le vecteur de noms doit avoir la même longueur que la liste.

L’intérêt principal est de faciliter l’accès aux éléments dans une boucle map() en utilisant des noms explicites au lieu d’indices numériques.

# Liste de fichiers CSV
fichiers <- c("data1.csv", "data2.csv", "data3.csv") |> 
  set_names(c("an_2023", "an_2022", "an_2021"))  # Noms = années avec préfixe

# ou bien 
# Générer les noms dynamiquement
annees <- 2021:2023
fichiers <- paste0("data", 1:3, ".csv") |> 
  set_names(paste0("an_", annees))

# Lire tous les fichiers et garder les noms
donnees <- fichiers |> 
  map(\(x) read_csv2(x))

# Accéder aux données par année
donnees$an_2023 # Affiche les données de 2023

10.7.3 Simplifier l’agrégation de résultats

On cherche à simplifier l’agrégation des résultats en un seul data frame, après l’utilisation d’une fonction map.

On peut utiliser list_rbind() pour combiner les résultats en un seul data frame en les concaténant par lignes, c’est-à-dire en empilant les résultats en ajoutant les lignes.

On peut utiliser list_cbind() pour combiner les résultats par colonnes, c’est-à-dire en ajoutant les résultats comme nouvelles colonnes.

10.7.4 Types de retour

purrr propose des variantes de la fonction map qui permettent de s’assurer du type de résultat obtenu.

Ainsi map propose plusieurs variantes qui permettent de contrôler le type de résultat qu’elle retourne :

  • map() retourne une liste
  • map_int() retourne un vecteur de nombres entiers
  • map_dbl() retourne un vecteur de numériques
  • map_chr() retourne un vecteur de chaînes de caractères
  • map_lgl() retourne un vecteur de booléens (TRUE / FALSE)

Pour pouvoir utiliser ces variantes et obtenir un vecteur, chaque résultat retourné par la fonction appliquée doit être de longueur 1.

# Fonction pour créer des dataframes filtrés par groupe de production

# group_split(groupe_de_productions) : fonction de dplyr qui divise le dataframe en une liste de dataframes,
# où chaque dataframe correspond à un groupe de production unique.
# Les dataframes sont basés sur les valeurs d'une ou plusieurs colonnes de regroupement (ici groupe_de_productions)
# Chaque élément de la liste résultante est un dataframe contenant uniquement
# les lignes correspondant à un groupe de production spécifique.

# names() : Utilise map_chr pour générer des noms de dataframe dynamiques
# Pour chaque groupe de production, créer un nom de variable dynamique en remplaçant
# les espaces par des underscores et en convertissant en minuscules.
creer_dfs_filtre <- function(data, prefix, group_col) {
  # Diviser le dataframe en une liste de dataframes,
  # où chaque dataframe correspond à un groupe de production unique.

  # Liste des groupes de production uniques, triée
  # On utilise pull() pour extraire la colonne spécifiée par group_col du dataframe data.
  # On passe cette colonne à unique() pour obtenir les valeurs uniques.
  groupe_unique <- data |>
    pull({{ group_col }}) |>
    unique() |>
    sort()

  # Diviser le dataframe en une liste de dataframes par groupe de production
  dfs <- data |>
    arrange({{ group_col }}) |> # Tri selon le groupe de production
    group_split({{ group_col }})

  # Renommer les dataframes dans la liste avec des noms dynamiques
  # names() utilise map_chr pour générer des noms de dataframe dynamiques.
  #
  # Le nom est construit en concaténant la chaîne de caractères prefix,
  # la version en minuscules du nom de groupe (avec les espaces remplacés par des underscores) et le suffixe "_pdl"
  names(dfs) <- map_chr(
  groupe_unique,
  \(x) {
    nom_var <- str_replace_all(x, " ", "_")
    nom_final <- paste0(prefix, tolower(nom_var), "_pdl")
    return(nom_final)
  }
)
  
  return(dfs)
}

# Créer les dataframes pour nb_tetes
dfs_nb_tetes_par_groupe_production <- creer_dfs_filtre(
  bio_anim_nb_tetes_dep_pdl_part_reg,
  "bio_",
  groupe_de_productions
)

unique(bio_anim_nb_tetes_dep_pdl_part_reg$groupe_de_productions)

unique(dfs_nb_tetes_par_groupe_production$bio_chevres_pdl$groupe_de_productions)
unique(dfs_nb_tetes_par_groupe_production$bio_truies_pdl$groupe_de_productions)
unique(dfs_nb_tetes_par_groupe_production$bio_poules_pondeuses_pdl$groupe_de_productions)
unique(dfs_nb_tetes_par_groupe_production$bio_poulets_de_chair_pdl$groupe_de_productions)
unique(dfs_nb_tetes_par_groupe_production$bio_apiculture_pdl$groupe_de_productions)

# Créer les dataframes pour nb_eleveurs
dfs_nb_eleveurs_par_groupe_production <- creer_dfs_filtre(
  bio_anim_nb_eleveurs_dep_pdl_part_reg,
  "bio_",
  groupe_de_productions
)

# Afficher les noms des dataframes créés
names(dfs_nb_tetes_par_groupe_production)
names(dfs_nb_eleveurs_par_groupe_production)

# Exporter les résultats dans un fichier Excel ----
# Chargement du chemin
chemin_sortie <- "./sortie/pdl/"
# animaux ----
fic_bio_anim_nb_tetes_dep_pdl_part_reg <- "33_bio_anim_nb_tetes_dep_pdl_part_reg.xlsx"

# Exporter les dataframess dfs_nb_tetes_par_groupe_production dans un fichier Excel ----

# Vérifiez les noms des colonnes et corrigez-les si nécessaire
# utilise purrr::map() pour appliquer une fonction à chaque dataframe de la liste
# make.names() : renomme les colonnes de chaque dataframe en remplaçant les caractères spéciaux par des points.
# rend les noms de colonnes syntaxiquement valides (supprime les espaces, caractères spéciaux, etc.)
# unique = TRUE : pour éviter les noms de colonnes en double
dfs_nb_tetes_par_groupe_production_clean <- map(dfs_nb_tetes_par_groupe_production, ~ .x |>
  rename_with(~ make.names(.x, unique = TRUE)))

# Exporter les dataframes dans un fichier Excel
# Chaque élément de la liste (chaque dataframe) devient une feuille Excel
# na.string : spécifie comment les valeurs manquantes doivent être représentées dans le fichier Excel 
write.xlsx(dfs_nb_tetes_par_groupe_production_clean,
  file = path(chemin_sortie, fic_bio_anim_nb_tetes_dep_pdl_part_reg),
  na.string = "NA"
)

10.7.5 Filtrage des résultats

Si la fonction retourne un vecteur de longueur supérieure à 1, on obtiendra une liste. Dans ce cas, on doit donc utiliser map() et conserver le résultat sous forme de liste, qui elle peut contenir des éléments de longueurs différentes.

On peut utiliser des fonctions comme keep() ou discard() qui “filtrent” les éléments d’une liste via une fonction qui renvoie TRUE ou FALSE.

keep() garde les éléments vérifiant une condition
discard() supprime les éléments vérifiant une condition

On peut par exemple utiliser discard() après map() pour ne conserver que les colonnes ayant au moins une valeur NA.

Ce code donne le nombre de valeurs manquantes (NA) pour chaque colonne du jeu de données starwars, mais ne retient que les colonnes qui ont au moins une valeur manquante. Les colonnes sans valeurs manquantes sont éliminées du résultat.

# Garder colonnes avec NA
# retourne un vecteur d'entiers
starwars |> 
  map_int(\(x) sum(is.na(x))) |> # Compte les NA par colonne
  discard(\(x) x == 0)  # Garde seulement les colonnes avec NA > 0

La fonction anonyme \(x) sum(is.na(x)) est appliquée à chaque colonne du jeu de données starwars.
is.na(x) vérifie pour chaque élément s’il est NA (manquant).
sum(is.na(x)) compte le nombre de valeurs manquantes dans chaque colonne.

discard est une fonction qui élimine les éléments d’une liste ou d’un vecteur qui satisfont une certaine condition.
La fonction anonyme \(x) x == 0 est utilisée pour éliminer les colonnes qui n’ont pas de valeurs manquantes (où le nombre de NA est 0).

x représente chaque nombre de valeurs manquantes (NA) trouvé dans chaque colonne
x est une variable temporaire qui prend successivement la valeur du nombre de NA de chaque colonne
\(x) x == 0 vérifie si ce nombre est égal à 0
discard() supprime les éléments pour lesquels la condition est TRUE

Le résultat final ne gardera que les colonnes où x (le nombre de NA) n’est pas égal à 0.

10.7.6 walk

walk() est une variante de map() qui a pour particularité de ne pas retourner de résultat. walk est utilisé pour les effets de la fonction.
On l’utilise lorsqu’on souhaite parcourir un vecteur ou une liste et appliquer à ses éléments une fonction dont on ne souhaite conserver que les effets de bord : afficher un message, générer un graphique, enregistrer un fichier…

10.8 Programmer avec le tidyverse

On dit que dplyr fait du data masking : les objets de l’environnement sont masqués par les colonnes du même nom du tableau de données. On retrouve ce data masking dans des fonctions comme filter(), mutate() ou summarise().

Dans certains cas de figure, on peut vouloir outrepasser ce data masking.

Rien n’assure dans ce cas que le tableau df ne contient pas déjà une colonne nommée valeurs qui masquerait l’objet valeurs passé en argument…

Il est des cas où on ne connaît pas le nom des colonnes du tableau, par exemple quand l’opération se déroule dans une fonction et que le tableau est passé en paramètre :

filtre_nom <- function(df, valeurs) {
    df |> filter(nom %in% valeurs)
}

Pour pallier ce problème, à chaque fois qu’on est dans un environnement où du data masking se produit, on peut utiliser deux “pronoms” spécifiques nommés .data et .env :

  • .data$var ou .data[["var"]] pointe vers l’objet var correspondant à une colonne du tableau de données
  • .env$var ou .env[["var"]] pointe vers l’objet var correspondant à un objet de l’environnement

Avec ces deux outils, on peut donc explicitement choisir d’où viennent les données qu’on utilise. Cela permet de préciser si c’est objet de type “data” (une colonne du tableau de données dans lequel on travaille) ou de type “env” (un objet de l’environnement dans lequel on travaille).

En utilisant .env, on peut donc s’assurer que la fonction filtre_nom() va bien prendre les valeurs dans l’environnement, donc dans l’argument passé à la fonction, et pas dans une éventuelle colonne qui porterait le même nom.

filtre_nom <- function(df, valeurs) {
    df |> filter(nom %in% .env$valeurs)
}

10.8.1 Utilisation dans des fonctions

Une difficulté liée au data masking survient quand les colonnes du tableau ne sont pas saisies directement mais proviennent d’un argument de fonction.

Soit la fonction suivante qui prend en entrée un tableau de données et une colonne et retourne le résultat d’un summmarise.

Il convient d’utiliser un opérateur permettant de “forcer” l’évaluation d’expressions selon la manière attendue par les fonctions du tidyverse. Cet opérateur prend la forme de double accolades {{ }} et se nomme curly curly.

On fait ainsi passer l’argument col dans l’opérateur curly curly :

summarise_min <- function(df, col) {
    df |> summarise(min = min({{ col }}))
}

summarise_min(restos, evaluation)

À noter que curly curly permet de passer en argument toute expression qui serait acceptée directement par les fonctions appelées. On peut donc combiner plusieurs colonnes, effectuer des opérations, etc. Avec les {{ }}, on peut utiliser des colonnes passées en argument pour accéder à leur contenu.

Sans les {{ }}, on aurait un message d’erreur disant que l’objet evaluation est introuvable. Ce serait alors l’évaluation “normale” de R qui serait utilisée : l’objet evaluation est recherché dans l’environnement plutôt que dans le tableau, ce qui génère une erreur puisqu’aucun objet de ce nom n’existe en-dehors du tableau.

10.8.2 Utiliser une colonne passée en argument

On cherche à passer un nom de colonne en paramètre d’une fonction.
Une opération courante quand on utilise les fonctions de dplyr ou tidyr dans une fonction est de prendre en argument une colonne à laquelle on souhaite accéder. Dans ce cas on doit utiliser l’opérateur curly curly et entourer les utilisations de l’argument contenant la colonne par une paire d’accolades.

Cela permet de passer dynamiquement une colonne à la fonction, par exemple avec summarise().

resume <- function(df, col) {
    df |> summarise(
        moyenne = mean({{ col }}),
        min = min({{ col }}),
        max = max({{ col }})
    )
}

resume(restos, evaluation)

C’est le cas dans toutes les fonctions qui font du data masking, comme group_by() :

resume_groupe <- function(df, col_group, col_var) {
    df |>
        group_by({{ col_group }}) |>
        summarise(
            moyenne = mean({{col_var}}),
            min = min({{ col_var }}),
            max = max({{ col_var }})
        )
}

resume_groupe(restos, style, evaluation)

10.8.3 Utiliser une sélection de colonnes passée en argument

Si on souhaite grouper ou appliquer une fonction sur une série de colonnes, il faut alors utiliser across().

resume_groupe <- function(df, cols_group, cols_var) {
    df |>
        group_by(
            across({{ cols_group }})
        ) |>
        summarise(
            across(
                {{ cols_var }},
                mean
            )
        )
}

On peut du coup utiliser tous les modes de sélection de colonnes permises par la tidy selection.

resume_groupe(restos, c(style, ville), where(is.numeric))

De la même manière, si on utilise un argument de fonction pour sélectionner des variables avec select(), on doit l’entourer avec l’opérateur curly curly, et on peut dès lors utiliser toutes les possibilités de la tidy selection.

select_cols <- function(df, cols) {
    df |> select({{ cols }})
}

restos |> select_cols(where(is.character) & !c(nom, note))

10.8.4 Nommer de nouvelles colonnes à partir d’un argument

On peut vouloir passer en argument des noms de colonnes qu’on souhaite créer, par exemple avec un mutate() ou un summarise().

Dans ce cas de figure, voici la syntaxe à utiliser:

  • on remplace l’opérateur = du mutate() par l’opérateur := (appelé walrus operator)
  • on place à gauche du := le nouveau nom de colonne sous forme d’un texte :
    une chaîne de caractères dans laquelle l’argument contenant le nom de la nouvelle variable est entouré d’une paire d’accolades (ex : ’’{{var_moyenne}}_moy’’).

Le nom de la variable créée dépend de la variable passée en paramètre de la fonction.

Voici ce que ça donne pour l’exemple ci-dessus :

calcule_pourcentage <- function(df, col_new, col_var) {
    df |> 
        mutate(
            "{{col_new}}" := {{ col_var }} / sum({{ col_var }}) * 100
        )
}

calcule_pourcentage(restos, prop_places, places)

Sans les {{ }}, la colonne créée s’appellerait “col_new”, et ne prendrait pas la valeur de l’argument col_new.

Cette syntaxe a l’avantage d’être souple : en particulier, on peut placer le texte que l’on souhaite dans la chaîne de caractères en plus des noms de variables entre double accolades.

Cela permet par exemple de générer le nom d’une nouvelle variable automatiquement à partir de l’ancienne.

calcule_pourcentage <- function(df, col_var) {
    df |>
        mutate(
            "prop_{{col_var}}" := {{ col_var }} / sum({{ col_var }}) * 100
        )
}

calcule_pourcentage(restos, places)

Ou de personnaliser les noms de colonnes dans un summarise().

resume <- function(df, col) {
    df |> summarise(
        "{{col}}_moyenne" := mean({{ col }}),
        "{{col}}_min" := min({{ col }}),
        "{{col}}_max" := max({{ col }})
    )
}

resume(restos, places)

L’opérateur := n’est nécessaire que lorsqu’on veut créer un nom de colonne dynamique qui dépend d’un paramètre, quand on veut que le nom de la nouvelle colonne soit construit à partir d’une variable ou d’un paramètre de la fonction. Dans le cas contraire, on peut utiliser l’opérateur =.

10.8.5 Quand les arguments sont des chaînes de caractères

Il arrive que des noms de colonnes soient passés comme arguments de fonction sous forme de chaînes de caractères.

resume(restos, "places")
summarise_min(restos, "evaluation")

On ne peut pas dans ce cas utiliser l’opérateur curly curly, par contre on peut utiliser le pronom .data pour accéder aux colonnes à partir de leur nom.

summarise_min <- function(df, col) {
    df |> summarise(min = min(.data[[col]]))
}

summarise_min(restos, "evaluation")

Si dans l’exemple précédent on souhaite personnaliser le nom de la colonne créée en utilisant la valeur du paramètre evaluation, on place le nom de l’objet dans une chaîne de caractère en l’entourant d’accolades simples, et on utilise le walrus operator :=.

summarise_min <- function(df, col) {
    df |> summarise("min_{col}" := min(.data[[col]]))
}

summarise_min(restos, "evaluation")
  • si les noms sont passés sous forme de symboles ou d’expressions, on utilise l’opérateur curly curly ({{ }})

  • si les noms sont passés sous forme de chaînes de caractères, on utilise le pronom .data

Quand on veut plutôt sélectionner des colonnes avec select() ou across() et qu’on récupère les noms de ces colonnes dans un vecteur de chaînes de caractères, on doit utiliser les fonctions all_of() ou any_of().

evaluation_par_groupe <- function(cols_group) {
    restos |>
        group_by(
            across(all_of(cols_group))
        ) |>
        summarise(evaluation = mean(.data$evaluation))
}

evaluation_par_groupe("ville")

evaluation_par_groupe(c("ville", "style"))

La différence entre all_of() et any_of() est que all_of() produira une erreur si l’une des variables n’est pas trouvée.

select_all_cols <- function(cols) {
    restos |> select(all_of(cols))
}

select_all_cols(c("ville", "evaluation", "igloo"))

Tandis qu’any_off() renverra uniquement les colonnes existantes, sans générer d’erreur.

select_any_cols <- function(cols) {
    restos |> select(any_of(cols))
}

select_any_cols(c("ville", "evaluation", "igloo"))

10.8.6 Résumé concernant la programmation avec les fonctions du tidyverse

Les spécificités vues ici ne s’appliquent que quand on veut utiliser certaines fonctions du tidyverse (dplyr, tidyr) à l’intérieur d’autres fonctions. Plus spécifiquement, elles sont à prendre en compte quand on souhaite passer en argument d’une fonction des noms de colonnes qui seront utilisées par des fonctions du tidyverse.

Elles ne s’appliquent pas si on passe en arguments d’autres paramètres comme le tableau de données qu’on souhaite utiliser, des valeurs numériques ou des chaînes de caractères qu’on souhaite récupérer telles quelles.

  1. Dans le cas où deux objets du même nom pourraient exister à la fois comme colonne du tableau de données (objet data) et comme objet de l’environnement (objet env), on peut expliciter lequel on souhaite utiliser avec les pronoms .data$var et .env$var

  2. Si un argument est une colonne passée sous la forme d’un symbole (var), on doit l’encadrer de l’opérateur curly curly.

summarise_col <- function(df, col) {
    df |> summarise(moyenne = mean({{ col }}, na.rm = TRUE))
}

summarise_col(starwars, height)
  1. Si un argument est un nom de colonne passé sous la forme d’une chaîne de caractères ("var"), on y accède avec le pronom .data :
summarise_col <- function(df, col_name) {
    df |> summarise(moyenne = mean(.data[[col_name]], na.rm = TRUE))
}

summarise_col(starwars, "height")
  1. Si on utilise la tidy selection dans un select(), un across() ou une autre fonction, on l’encadre de l’opérateur curly curly :
select_cols <- function(df, cols) {
    df |> select({{ cols }})
}

select_cols(starwars, !where(is.list))
  1. Si on indique les noms de plusieurs colonnes sous la forme d’un vecteur de chaînes de caractères pour utilisation dans un select(), un across() ou une autre fonction acceptant la tidy selection, on utilise all_of() ou any_of() :
select_cols <- function(df, col_names) {
    df |> select(all_of(col_names))
}

select_cols(starwars, c("height", "mass"))
  1. Si on souhaite créer une nouvelle colonne à partir de la valeur d’un argument, on l’utilise sous la forme d’une chaîne de caractères avec l’opérateur walrus :=.

Si l’argument est un symbole on l’entoure avec des doubles accolades {{ }}, si c’est une chaîne de caractères on l’entoure avec des simples accolades {} :

add_mean_by_species <- function(col_var) {
    starwars |>
        group_by(species) |>
        mutate("moyenne_{{col_var}}" := mean({{ col_var}}))
}

add_mean_by_species(height)

Le schéma suivant récapitule les points précédents :

https://juba.github.io/tidyverse/19-programmer-tidyverse.html

10.8.7 Exemples de programmation avec le tidyverse

# créer un df_part_reg par groupe de production ----
vege_creer_df_part_reg <- function(
    data,
    nom_indicateur,
    modalite_groupe_prod) {
  df <- data |>
    filter(str_starts(groupe_de_productions, {{ modalite_groupe_prod }})) |>
    select(
      echelle_geographique, territoire, type_de_production,
      groupe_de_productions, sous_groupe_de_productions,
      # paste0("rang_groupe_prod_", {{ nom_indicateur }}, "an_2023"),
      contains(nom_indicateur)
    ) |>
    select(-matches("ss_groupe_prod")) |>
    rename_with(
        \(x) str_replace(
        x,
        paste0(
          {{ nom_indicateur }},
          "an_(\\d{4})_group_prod_part_reg"
        ),
        "an_\\1"
      ),
      .cols = starts_with({{ nom_indicateur }})
    ) |>
    mutate(indicateur = {{ nom_indicateur }}, .after = "groupe_de_productions")

  return(df)
}

# Créer des dataframes _part_reg par groupe de production pour "toutes" ----
vege_groupe_prod_surf_tot_bio_dep_pdl_part_reg <- vege_creer_df_part_reg(
  data = bio_vege_dep_pdl_part_reg_export,
  nom_indicateur = "surf_tot_bio_",
  modalite_groupe_prod = "toutes"
)

Impact des guillemets dans les appels de fonctions :
- Avec guillemets : traité comme une chaîne de caractères littérale
- Sans guillemets : traité comme une référence à une variable/colonne

Règle générale
- Utiliser des guillemets quand on veut la valeur littérale
- Ne pas utiliser de guillemets quand on fait référence à des noms de colonnes avec tidyeval

Ajouter une nouvelle ligne basée sur un ratio entre lignes : 1re méthode

library(dplyr)
library(purrr)

# créer une fonction qui ajoute une ligne à un dataframe en calculant une nouvelle modalité d’une variable
# à partir de deux autres modalités de la même variable
# fonction pour ajouter ligne à partir caractéristiques autres lignes : 
# calculer nouvelle modalité d'une variable à partir 2 autres modalités de la même variable 
# ajouter ligne modalite_3
# en utilisant les valeurs associées aux lignes modalite_1 et modalite_2
# (valeur_3 pour var == modalite_3) = (valeur_1 pour var == modalite_1) / (valeur_2 pour var == modalite_2) 
# L'opérateur := est nécessaire quand on utilise une référence de variable avec {{ }} 
#pour un nom de colonne dynamique
# du côté gauche d'une affectation dans une fonction tidyverse 
# où on crée/modifie des noms de colonnes

# 1- Transformer les données en format large
ajouter_ligne3_calcul_ligne1_sur_ligne2 <- function(tableau, var, valeur, modalite_1, modalite_2, modalite_3) {
  tableau_large <- tableau |>
  select(-c(geo, rica_region_soc2013_lib_dim2, rica_region_soc2013_mod_dim4, rica_region_soc2013_lib_dim4)) |> 
  arrange(annref, {{ var }}) |>
  # var et valeur avec guillemets dans l'appel de fonction car utilisés comme chaînes de caractère dans pivot_wider
   pivot_wider(names_from = {{ var }}, 
              values_from = {{ valeur }})

# 2- Calcul du ratio
  tableau_large <- tableau_large |>
  mutate({{ modalite_3 }} := {{ modalite_1 }} / {{ modalite_2 }}) 
  
# 3- on conserve seulement année et nouvelle variable
  tableau_large_new_var <- tableau_large |>
    select(annref, {{ modalite_3 }}) 
  
# 4- on remet en format long
  # {{ var }} est interprété par exemple comme "rica_reg_dim4"
  tableau_new_line <- tableau_large_new_var |>
    pivot_longer(cols = {{ modalite_3 }},
                 names_to = {{ var }}, 
                 values_to = {{ valeur }})

# 5- on ajoute les nouvelles lignes au dataframe original
  tableau_new <- tableau |> 
    bind_rows(tableau_new_line) |> 
    arrange(annref, {{ var }}) |>
    relocate({{ valeur }}, .after = last_col())

return(tableau_new)
}

# Utiliser la fonction
# Dans ce code, tableau est le dataframe, var est la variable qu'on souhaite modifier, 
# valeur est la colonne contenant les valeurs utilisées pour le calcul, 
# et modalite_1, modalite_2, et modalite_3 sont les modalités de la variable var.
# var et valeur avec guillemets dans l'appel de fonction car utilisés comme chaînes de caractère dans pivot_wider()
rica_2018_2021_vol_pdl_2 <- ajouter_ligne3_calcul_ligne1_sur_ligne2(
                                  tableau = rica_2018_2021_vol_pdl_filtre_2, 
                                  var = "rica_reg_dim4",
                                  valeur = "valeur_100",
                                  modalite_1 = n190_charges_approvisionnement,
                                  modalite_2 = n040_main_d_oeuvre_totale,
                                  modalite_3 = n054_charges_approvisionnement_par_UTA)

# calcul "n156_charges_tot_sans_charges_sociales_par_ha"
# à partir calcul n401_charges_totales_sans_charges_sociales / n020_surface_agricole_utile
rica_2018_2021_vol_pdl_2 <- ajouter_ligne3_calcul_ligne1_sur_ligne2(
                                  tableau = rica_2018_2021_vol_pdl_2, 
                                  var = "rica_reg_dim4",
                                  valeur = "valeur_100",
                                  modalite_1 = n401_charges_totales_sans_charges_sociales,
                                  modalite_2 = n020_surface_agricole_utile,
                                  modalite_3 = n156_charges_tot_sans_charges_sociales_par_ha)

Ajouter une nouvelle ligne basée sur un ratio entre lignes : 2e méthode

library(dplyr)
library(purrr)

# Autre possibilité qui nécessite d'utiliser annref comme 4 années concernées
# L'opérateur := est nécessaire quand on utilise une référence de variable avec {{ }} 
# pour un nom de colonne dynamique
# du côté gauche d'une affectation dans une fonction tidyverse 
# où on crée/modifie des noms de colonnes
ajouter_ligne <- function(df, var, valeur, modalite_1, modalite_2, modalite_3) {
  # Obtenir les années uniques
  annees <- unique(df$annref)
  
  # Créer une fonction qui traite une seule année
  calculer_ligne <- function(annee) {
    # Filtrer le dataframe pour l'année actuelle
    df_annee <- df |> filter(annref == annee)
    
  # Calculer la nouvelle valeur
  # modalite_1/2/3 avec guillemets dans l'appel de fonction car utilisés comme chaine de caractères dans filter()
  valeur_1 <- df_annee |> 
    filter({{ var }} == modalite_1) |> 
    pull({{ valeur }})
  
  valeur_2 <- df_annee |> 
    filter({{ var }} == modalite_2) |>
    pull({{ valeur }})
  
  valeur_3 <- valeur_1 / valeur_2
  
  # Ajouter la nouvelle ligne
  # La fonction tibble() est utilisée pour créer un nouveau dataframe
  nouvelle_ligne <- tibble(annref = annee, 
                          {{ var }} := modalite_3, 
                          {{ valeur }} := valeur_3)
  
  return(nouvelle_ligne)
  }
  
  # Utiliser map() avec list_rbind() pour appliquer la fonction à chaque année
  nouvelles_lignes <- annees |>
    map(\(x) calculer_ligne(x)) |>
    list_rbind()
  
  # Combiner avec le dataframe original
  df_new <- bind_rows(df, nouvelles_lignes)

  return(df_new)  # On retourne le nouveau dataframe
}

# Utiliser la fonction
# modalite_1/2/3 avec guillemets dans l'appel de la fonction car utilisés comme chaine de caractères dans filter()
rica_2018_2021_vol_pdl_test <- ajouter_ligne(df = rica_2018_2021_vol_pdl_filtre_2, 
                    var = rica_reg_dim4,
                    valeur = valeur_100,
                    modalite_1 = "n190_charges_approvisionnement",
                    modalite_2 = "n040_main_d_oeuvre_totale",
                    modalite_3 = "n054_charges_approvisionnement_par_UTA")

rica_2018_2021_vol_pdl_test2 <- rica_2018_2021_vol_pdl_test |> 
  select(-c(rica_region_soc2013_lib_dim2,rica_region_soc2013_mod_dim4,rica_region_soc2013_lib_dim4)) |> 
  filter(rica_reg_dim4 == "n054_charges_approvisionnement_par_UTA")
# Définir une fonction pour le regroupement des facteurs
# {{ }} (double accolades) est utilisé pour évaluer l'expression au moment approprié.
# permet de capturer une expression non évaluée et de l'évaluer dans le contexte d'un dataframe. 
# pour créer de nouvelles colonnes à partir d’un argument, on utilise les deux opérateurs {{ }} et :=
regrouper_especes <- function(data, colonne) {
  data <- data |>
    mutate("{{ colonne }}_regrpt1" := fct_collapse({{ colonne }},
                                                 "1-poulets" = c("Poulets (y c. coquelets)", "Chapons, poulardes",
                                                                 "Poules de réforme (filière œufs de consommation)", 
                                                                 "Coqs et poules de réforme (reproducteurs)"),
                                                 "2-dindes" = c("Dindes"),
                                                 "3-pintades" = c("Pintades (y c. chaponnées)"),
                                                 "4-canards" = c("Canards à rôtir", "Canards gras"),
                                                 "5-oies" = c("Oies à rôtir", "Oies grasses"),
                                                 "6-petites_volailles" = c("Pigeons", "Cailles"),
                                                 "7-lapins" = c("Lapins")
    )) |>
    mutate("{{ colonne }}_regrpt2" := fct_collapse({{ colonne }},
                                                 "1-volailles" = c("Poulets (y c. coquelets)", "Chapons, poulardes", 
                                                                   "Poules de réforme (filière œufs de consommation)", 
                                                                   "Coqs et poules de réforme (reproducteurs)", 
                                                                   "Dindes", "Pintades (y c. chaponnées)", 
                                                                   "Canards à rôtir", "Canards gras", "Oies à rôtir",
                                                                   "Oies grasses", "Pigeons", "Cailles"),
                                                 "2-lapins" = c("Lapins")
    ))
  
  return(data)
}

# Appliquer la fonction à chaque dataframe
vol_2024_2 <- regrouper_especes(vol_2024_2, espece)
vol_2023_2 <- regrouper_especes(vol_2023_2, espece)

10.9 Paramétrer une chaîne de caractères avec le package glue

Construire une chaîne de caractères agrégeant des parties fixes et des parties variables peut devenir fastidieux avec la fonction paste0(beaucoup de quotes et de virgules).
Le package glue peut être intéressant : on met entre accolades {} les parties de la chaîne de caractères qui doivent être évaluées lors de la construction de la chaîne finale.

library(glue)

# Exemple simple
name <- "Alice"
age <- 30

# Avec glue
glue("Je m'appelle {name} et j'ai {age} ans")

# Equivalent avec paste0
paste0("Je m'appelle ", name, " et j'ai ", age, " ans")

# Exemple plus complexe
data <- data.frame(
  prenom = c("Alice", "Bob"),
  age = c(30, 25),
  ville = c("Paris", "Lyon")
)

# Avec glue
glue_data(data, 
  "{prenom} a {age} ans et habite à {ville}")

# Multiples lignes
glue("
  Nom: {name}
  Age: {age}
  Ville: {data$ville[1]}
")

10.10 Style de codage

Suivre le style de code du tidyverse.

Quelques principes à retenir :

  • éviter le mélange de majuscules et minuscules dans les noms des objets et donc utiliser la snake case (minuscules et underscores) pour les noms de variables et de fonctions (ma_variable, ouvrir_ra()) ;
  • nommer clairement ses variables et fonctions (pas de a <- “84” mais insee_reg <- “84”) ;
  • retourner à la ligne après le pipe |> (privilégier le pipe natif) ;
  • conserver des lignes pas trop longues (80 caractères si possible, pas plus de 100) ;
  • aérer le code (espace après les , avant et après les opérateurs =, ==, <-, etc.) ;
  • utiliser TRUE et FALSE plutôt que T et F ;
  • utiliser, comme délimiteurs de chaînes, les guillemets anglais ” plutôt que les apostrophes ’ ;
  • indiquer les noms des arguments dans les fonctions utilisées, au moins pour les arguments secondaires : read_delim(“fic.txt”, delim = “;”, dec = “,”, skip = 2) plutôt que read_delim(“fic.txt”, “;”, “,”, 2).

Le nommage des objets : variables, constantes, fonctions doit être explicite. Une fonction est par exemple nommée à partir d’un verbe d’action à l’infinitif : ouvrir_fichier_enquete()



11 Foire aux questions

11.1 Où trouver de la documentation ?

utilitR : documentation collaborative sur le langage de programmation R réalisée par des agents de l’Insee
https://book.utilitr.org/

Documentation réalisée par un groupe de référents en R du ministère de la transition écologique
https://mtes-mct.github.io/parcours-r/

Site de formation R du Ministère de l’agriculture et de la souveraineté alimentaire
https://ssm-agriculture.github.io/site-formations-R/

Site Introduction à R et au tidyverse

Ces 4 sites servent de source pour alimenter le présent document.

Il est également disponible sur Cerise
- se rendre dans
~/CERISE/01-Espace-de-Partage/SRISE/Pays-De-La-Loire/Partage_R
- faire un clic gauche sur “pratique_R.html” et choisir “Voir dans le navigateur Web”

Les lettres du groupe des utilisateurs de R (GUR) sont aussi très utiles :

Comment fai’R pour ajouter des 0 manquants en début de chaîne ?

Comment fai’R pour choisir la bonne fonction de jointure ?

Comment fai’R pour détecter les valeurs manquantes dans une table ?

Comment fai’R un tri personnalisé dans une table ?

Comment fai’R pour importer des données depuis un fichier Excel contenant des en-têtes ?

Comment fai’R : les paramètres de mutate()

Comment fai’R : créer un tableau de contingence

Comment fai’R : le top 10 des fonctions R qui changent la vie

Comment fai’R : mettre en forme un pourcentage

Comment fai’R : choisir les niveaux de croisement d’une agrégation

Comment fai’R : R et tableurs

Comment fai’R : un diagramme des principales étapes d’un programme

Elles sont disponibles sur : ~/CERISE/01-Espace-de-Partage/SSP/BMIS/comment_fair.

On peut aussi s’appuyer sur des aide-mémoire (antisèches ou ‘feuilles de triche’).

11.2 Comment nommer les fichiers ?

Les différents segments du nom d’un fichier sont séparés par un underscore (le caractère “_“).

Les accents et les caractères spéciaux ne doivent pas être utilisés.

Idéalement, le nom du fichier ne sera pas trop long et suffisamment descriptif.

11.3 Aide sur une fonction

Pour obtenir de l’aide sur une fonction (installée, peut-être non chargée) :

??nom_fonction

Pour obtenir de l’aide sur une fonction (installée et chargée) :

?nom_fonction
help(nom_fonction)

Documentation simplifiée : vignettes

Les vignettes sont des documents qui aident à prendre en main un package en identifiant ses fonctions les plus importantes, et en montrant un ou plusieurs cas d’usage.

Pour lister les vignettes relatives à un package (par exemple dplyr):

vignette(package="dplyr")

Pour visualiser une vignette donnée :

vignette("rowwise", package="dplyr") # dans RStudio
RShowDoc("rowwise", package="dplyr") # dans le navigateur web

11.4 L’encodage

Utiliser l’encodage UTF-8. Le nombre minimal de bits est 8. En fait, il représente les premiers caractères (ceux de l’ASCII) sur un octet, les suivants sur 2 octets, 3 et jusqu’à 4 octets.

https://orion.agriculture/confluence/pages/viewpage.action?pageId=285442455

11.5 Les types de valeurs dans R

Une valeur constitue l’unité de base des données pour R. Comme pour la plupart des logiciels, elles peuvent être de trois types :

  • Numérique : entier, double
  • Caractère : texte ou code
  • Logique : booléens

En anglais : numeric, character et logical

Les vecteurs peuvent être de classes différentes, selon le type de données qu’ils contiennent.

On a ainsi des vecteurs de type integer ou double, qui contiennent respectivement des nombres entiers ou décimaux ; des vecteurs de type character, qui contiennent des chaînes de caractères ; et des vecteurs de type logical, qui ne peuvent contenir que les valeurs vraie (TRUE) ou fausse (FALSE).

Chaque variable est du type de son contenu (numeric, character, logical).

À chaque type de variable correspond une utilisation. Lors de l’import des données, un type est affecté automatiquement par R. Mais le type peut être erroné. Il suffit alors de les convertir :

base <- mutate(base0, LIBGEO = as.character(LIBGEO))

On peut convertir un vecteur d’un type à un autre avec les fonctions de forme as.XXX (as.character(), as.numeric(), as.factor() et as.logical()).

Si une valeur ne peut pas être convertie, elle est remplacée par un NA, et R affiche un avertissement.

as.character(1:3)
as.logical(c(0, 2, 4))
as.numeric(c("foo", "23"))
## Warning: NAs introduits lors de la conversion automatique

On peut aussi gérer les types au moment de l’importation (par exemple avec le paramètre colClasses).

11.5.1 Factor pour une variable catégorielle

Si les valeurs prises par la variables correspondent à un nombre fini de modalités, la variable peut être du type factor. Ses composantes sont toujours d’un des 3 types décrits ci-dessus mais il est possible d’employer des fonctions spécifiques au traitement de modalités.

Un factor est une manière de représenter une variable catégorielle, généralement utilisée lorsqu’il y a un nombre restreint de catégories (par exemple les tranches d’âges).

Les différentes modalités autorisées de la variable, appelées levels, sont associées chacune à un entier. Le factor stocke alors les valeurs des entiers à la place des modalités complètes. Cela permet :

  • Un stockage efficace en évitant de stocker de multiples fois de longs labels.
  • De formaliser le caractère catégoriel de la variable en limitant les valeurs acceptées.
  • De faciliter certaines analyses en identifiant ces variables comme catégorielles et non autre chose (texte, numérique etc.).

Les factors peuvent être délicats à manipuler et sont souvent source d’erreur car les valeurs intrinsèques de l’objet sont les entiers codant les levels.

11.5.2 Travail sur les dates

itavi <- itavi |>
  mutate(an_mois_date = convertToDate(an_mois))

# itavi <- itavi |> 
# mutate(an_mois = substr(as.character(an_mois_date),1,7)) 

# itavi <- itavi |>
# gsub("-","_",itavi$an_mois)

# itavi <- itavi |> 
# str_replace_all("-","_")

itavi <- itavi |> 
  mutate(annee = year(an_mois_date),
         mois = month(an_mois_date),
         an_mois = paste0(annee, "_", mois)) 

11.6 Les vecteurs

Un vecteur est un ensemble d’une seule dimension de 1 ou plusieurs éléments du même type. Ces vecteurs peuvent être vus comme des colonnes de valeurs toutes du même type (numérique, chaîne de caractères, booléen, catégorie).

Vecteurs : suite unidimensionnelle de valeurs ayant le même type.

Différentes fonctions permettent de créer des vecteurs.

vect_num <- c(1, 160, 2, 9)
vect_txt <- c("Je", "programme", "en", "R")
sequence <- seq(from = 1, to = 10, by = 1)
sequence_pareille <- 1:10
repetition <- rep("bla", 3)

La fonction seq() (comme séquence) génère une séquence de valeurs numériques. Elle prend comme paramètre un point de départ (1^er paramètre from =), un point d’arrivée (2e paramètre to =) et le pas (3e paramètre by =) qui indique l’écart régulier souhaité entre deux valeurs successives.

L’opérateur : permet de générer un vecteur comprenant l’ensemble des entiers entre deux nombres entiers :

x <- 1:10
x

La commande c() est à placer devant une liste. Cette fonction, appelée collecteur, permet de combiner plusieurs valeurs pour les ranger dans un objet. c est l’abréviation de combine.

L’avantage d’un vecteur est que lorsqu’on lui applique une opération, celle-ci s’applique à toutes les valeurs qu’il contient.

On peut accéder à un élément particulier d’un vecteur en faisant suivre le nom du vecteur de crochets contenant le numéro de l’élément désiré.

diplome[2]

Cette opération, qui utilise l’opérateur [], permet donc la sélection d’éléments d’un vecteur.

Si on affiche dans la console un vecteur avec beaucoup d’éléments, ceux-ci seront répartis sur plusieurs lignes. Par exemple, si on a un vecteur de 50 nombres on peut obtenir quelque chose comme :

 [1] 294 425 339 914 114 896 716 648 915 587 181 926 489
[14] 848 583 182 662 888 417 133 146 322 400 698 506 944
[27] 237 324 333 443 487 658 793 288 897 588 697 439 697
[40] 914 694 126 969 744 927 337 439 226 704 635

On remarque que R ajoute systématiquement un nombre entre crochets au début de chaque ligne : il s’agit en fait de la position du premier élément de la ligne dans le vecteur. Ainsi, le 848 de la deuxième ligne est le 14e élément du vecteur.

Pour connaitre le type d’un vecteur, on peut utiliser la fonction class().

Des fonctions is.xxxx() permettent de tester les valeurs. Le résultat renvoyé est un booléen.

`is.numeric(x)` : est-ce que x contient des valeurs de type numérique ?  

`is.character(x)` : est-ce que x contient des valeurs de type chaîne de caractères ?  

`is.logical(x)` : est-ce que x contient des valeurs de type booléen ?  

Facteurs : vecteur qui prend un nombre limité de modalités (exemple : sexe). Il est défini par les modalités possibles, appelées niveaux (levels) et les libellés associés (labels). On parle aussi de variables qualitatives ou catégorielles (de type catégoriel).

Les variables sont principalement de 2 types : variable quantitative (variable numérique pouvant prendre un grand nombre de valeurs : l’âge, le revenu, un pourcentage…) et variable qualitative (variable pouvant prendre un nombre limité de valeurs appelées modalités : le sexe, la profession, le dernier diplôme obtenu…).

11.6.1 Extraire un vecteur : $ et pull()

Pour accéder aux variables (colonnes) d’un tableau, on peut utiliser l’opérateur $. L’opérateur $ est utilisé pour accéder directement à une colonne par son nom.

La fonction table$colonne renvoie la colonne nommée colonne du tableau table, c’est-à-dire un vecteur, en général de nombres ou de chaînes de caractères.

Si on souhaite afficher seulement les premières ou dernières valeurs d’une variable, on peut utiliser les fonctions head et tail.

head(df$age)
tail(df$age, 10)

Le deuxième argument numérique permet d’indiquer le nombre de valeurs à afficher.

L’équivalent de l’opérateur $ pour accéder aux éléments d’une liste ou d’un dataframe est la fonction [[ ]].

# Accès à la colonne 'colonne_1'
colonne_data <- df[["colonne_1"]]

La fonction pull() de dplyr permet d’extraire sous forme de vecteur une variable d’un dataframe.

# Utilisation de pull() pour extraire la colonne 'a'
vecteur_a <- df |> 
  pull(a)

# Utilisation de $
vecteur_a <- df$a

# Autre façon d'extraire la colonne 'a' en tant que vecteur avec [[ ]] 
vecteur_a <- df[["a"]]

stations_de_la_table_prelevement <- pull(prelevement, code_station)
stations_de_la_table_prelevement <- unique(stations_de_la_table_prelevement)

La fonction identical est utilisée pour comparer des objets, pour vérifier si le contenu de deux variables est identique.
- Objectif : - Vérifie si deux objets R sont strictement identiques.

  • Utilisation :
    • identical(x, y) retourne TRUE si x et y sont exactement les mêmes objets, sinon FALSE.
# Pour vérifier si le contenu de deux variables est identique
identical(rica_2018_2021_qualite_oui$valeur,
          rica_2018_2021_qualite_oui$valeur_extrapolee)

11.7 Les dataframes

Plusieurs vecteurs de types différents possédant un même nombre de lignes peuvent être accolés pour former une dataframe. Les dataframes sont les objets les plus courants dans le traitement de données usuel. Il s’agit de tableaux dont les lignes correspondent à des observations et les colonnes à des variables.

Ces dataframes peuvent être créées par association des vecteurs avec les fonctions :

dataframe_a <- data.frame(vect_num, vect_txt)
dataframe_b <- bind_cols("vect_num" = vect_num, "vect_txt" = vect_txt)

Une dataframe peut aussi être créée par l’import d’un tableau.

Il est possible d’accéder aux éléments d’une dataframe à partir du numéro de ligne et de colonne, grâce aux crochets :

  • base[1,3] \(\rightarrow\) valeur de la première ligne et de la troisième colonne
  • base[2,] \(\rightarrow\) toutes les variables pour la 2e observation
  • base[,4] \(\rightarrow\) toutes les observations de la quatrième colonne
  • base[,’V6’] \(\rightarrow\) toutes les observations de la variable V6

Le dataframe est constitué de - n lignes (observations) - p colonnes (variables)

Les colonnes étant des vecteurs, il faut que toutes les valeurs soient du même type. Ce n’est pas le cas pour les lignes qui représentent les individus statistiques.

Un data frame (ou tableau de données, ou table) est un type d’objet R qui contient des données au format tabulaire, avec les observations en ligne (individus) et les variables (caractéristiques) en colonnes, comme dans une feuille de tableur de type Calc ou Excel.

Une manière d’afficher le contenu du tableau est de cliquer sur l’icône en forme de tableau à droite du nom de l’objet dans l’onglet Environment :

View icon
View icon

Ou d’utiliser la fonction View :

View(hdv2003)

Dans les deux cas le tableau devrait s’afficher dans RStudio avec une interface de type tableur.

On peut lister et modifier les noms des colonnes d’un tableau avec les fonctions names() ou colnames() (qui sont équivalentes). Ces fonctions ont deux utilités :
- nommer les colonnes d’un data-frame
- connaître les noms des colonnes d’un data-frame.

Pour obtenir le nombre de lignes et de colonnes dans un data frame on utilise les fonctions nrow() et ncol().
La fonction dim(x) renvoie le vecteur c(nrow(x),ncol(x)).

11.8 Le tibble : un data.frame amélioré

Le tibble est une version améliorée du data.frame de base. Il s’agit d’un data.frame standard, avec quelques propriétés supplémentaires qui rendent son utilisation plus facile.

Pour convertir un data.frame en tibble on utilise la fonction tibble::as_tibble(). Dans l’exemple suivant, on charge la table de la base permanente des équipements puis on la convertit en tibble.

# Charger la base permanente des équipements
bpe_ens_2018 <- doremifasolData::bpe_ens_2018
# Convertir ce data.frame en tibble
bpe_ens_2018_tbl <- as_tibble(bpe_ens_2018)
  • L’affichage des tibbles est meilleur que celui des data.frames. Même sans la fonction head(), l’affichage d’un tibble affiche les dimensions de celui-ci (nombres de lignes et de colonnes), ainsi que le type des variables (en-dessous des noms des colonnes).

L’affichage print dans la console est amélioré ; il affiche notamment les types des colonnes et limite le nombre de lignes.

Les tableaux de données au format tibble autorisent des noms de colonnes invalides pour les data frames (espaces, caractères spéciaux, nombres…). Quand on veut utiliser des noms de ce type, on doit les entourer avec des backticks (`) (“apostrophes inverses”).

https://book.utilitr.org/03_Fiches_thematiques/Fiche_tidyverse.html#le-tibble-un-data.frame-am%C3%A9lior%C3%A9

11.8.1 Sélection de lignes et de colonnes

Il existe plusieurs manières de sélectionner des éléments dans un tableau de données.

Ainsi, on peut sélectionner une colonne via l’opérateur $.

df$fruit

Comme un tableau de données est en réalité une liste de colonnes, on peut aussi utiliser l’opérateur [[]] pour sélectionner l’une de ses colonnes, par position ou par nom.

df[["fruit"]]
df[[2]]

On peut également utiliser l’opérateur [,] pour sélectionner à la fois des lignes et des colonnes, en lui passant deux arguments séparés par une virgule : d’abord la sélection des lignes puis celle des colonnes. Dans les deux cas on peut sélectionner par position, nom ou condition. Si on laisse un argument vide, on sélectionne l’intégralité des lignes ou des colonnes.

# Lignes 1 et 3 et colonne "poids"
df[c(1, 3), "poids"]
# Toutes les lignes et colonnes "poids" et "fruit"
df[, c("poids", "fruit")]
# Lignes pour lesquelles poids > 150, et toutes les colonnes
df[df$poids > 150, ]
library(stringr)
# Colonnes dont le nom contient un "o", et toutes les lignes
df[, str_detect(names(df), "o")]

Attention, le comportement de [,] est différent entre les tibbles et les data frame lorsqu’on ne sélectionne qu’une seule colonne. Dans le cas d’un data frame, le résultat est un vecteur, dans le cas d’un tibble le résultat est un tableau à une colonne.

df[, "fruit"]
df_tib[, "fruit"]

Cette différence peut parfois être source d’erreurs, notamment quand on développe une fonction qui prend un tableau de données en argument.

11.8.2 Modification

On peut utiliser [[]] et [,] avec l’opérateur d’assignation <- pour modifier tout ou partie d’un tableau de données.

# Création d'une nouvelle colonne poids_kg
df[["poids_kg"]] <- df$poids / 1000
df
# Remplacement de la valeur de la colonne "fruit" pour les lignes 
# pour lesquelles "fruit" vaut "Citron"
df[df$fruit == "Citron", "fruit"] <- "Agrume"
df

L’utilisation des opérateurs [[]] et [,] sur un tableau de données peut sembler redondante et moins pratique que l’utilisation des verbes de dplyr comme select() ou filter(). Ils peuvent cependant être utiles lorsqu’on souhaite éviter les complications liées à l’utilisation du tidyverse à l’intérieur de fonctions.

11.9 Les fonctions et les valeurs particulières (NA, NaN, Inf)

  • NA : valeur manquante (Not Available, non disponible) dans un dataframe ou en résultat d’une fonction. Cette valeur particulière est utilisée pour indiquer une valeur manquante, qu’il s’agisse d’un nombre, d’une chaîne de caractères, etc.

NA est de type logical ; il existe des variantes pour les autres types : NA_integer_, NA_real_, NA_character_.

Il faut utiliser is.na pour vérifier si une valeur vaut NA :

is.na(NA)
[1] TRUE
  • NaN : pas un nombre (Not a Number) lorsqu’une fonction tente de diviser par 0

  • NULL désigne l’absence de valeur. À la différence de NA qui désigne une valeur inconnue / manquante, NULL indique qu’il n’y a pas de valeur.
    NULL peut être utilisé pour assigner des valeurs par défaut à des arguments d’une fonction ou pour supprimer des éléments.

  • -Inf, Inf : infini positif ou négatif lorsque une fonction diverge

Une valeur manquante peut perturber l’exécution d’une fonction :

V1 <- c(1, 14, NA, 32.7)

mean(V1)              # renvoie NA
mean(V1, na.rm = TRUE)   # renvoie 15.9

R considère par défaut qu’il ne peut pas calculer la moyenne si une des valeurs n’est pas disponible. Dans ce cas il considère que la moyenne est elle-même “non disponible” et renvoie donc NA comme résultat.

On peut cependant indiquer à mean d’effectuer le calcul en ignorant les valeurs manquantes. Ceci se fait en ajoutant un argument supplémentaire, nommé na.rm (abréviation de NA remove, “enlever les NA”), et de lui attribuer la valeur TRUE (vrai).

mean(tailles, na.rm = TRUE)

Positionner le paramètre na.rm à TRUE indique à la fonction mean de ne pas tenir compte des valeurs manquantes dans le calcul.

Si on ne dit rien à la fonction mean, cet argument a une valeur par défaut, en l’occurrence FALSE (faux), qui fait qu’il ne supprime pas les valeurs manquantes.

La fonction coalesce(x, y) permet de remplacer les valeurs manquantes de x par la valeur de y.

11.10 Travailler avec les valeurs manquantes NA

library(janitor) # pour round_half_up

# Calculer le nombre de valeurs manquantes dans chaque ligne
# Calculer proportion de NA par ligne / ncol (nombre de colonnes)
lignes_avec_na <- df |>
  mutate(
    nb_na = rowSums(is.na(.)),
    pct_na = (nb_na / ncol(.)) * 100
  ) 

lignes_avec_bcp_na <- (nombre_na / ncol(df)) >= 50/100

# mettre NA pour variable d'évolution avec regroupement
otex_regroupe <- otex_regroupe |> 
  mutate(evol_2020_2019 = case_when(
        otex_code_regr %in% c("otex_5374_5100", "otex_4813_4840") ~ as.numeric(NA), 
        .default = evol_2020_2019),
    evol_2020_2010_par_an = case_when(
         otex_code_regr %in% c("otex_5374_5100", "otex_4813_4840") ~ as.numeric(NA), 
         .default = evol_2020_2010_par_an)
  )

# calcul evolution
otex_regroupe <- otex_regroupe |>
  mutate(tx_evol_2020_2010_par_an = round_half_up((((an_2020 / an_2010)**(1/10)-1) * 100), 3),
         tx_evol_2020_2019 = round_half_up(((an_2020 / an_2019 -1) * 100), 3),
         otex_code = substr(otex_code_regr, 6, 10)) |>
  arrange(otex_code_regr) |>
  left_join(otex |> 
              select(otex_code, otex_lib),
            join_by(otex_code)) |>

# remplacement valeurs NA pour regroupements
otex_regroupe <- otex_regroupe |> 
  mutate(evol_2020_2010_par_an = case_when(
    is.na(evol_2020_2010_par_an) ~ round_half_up(tx_evol_2020_2010_par_an, 1),
    .default = evol_2020_2010_par_an),
    evol_2020_2019 = case_when(
      is.na(evol_2020_2019) ~ round_half_up(tx_evol_2020_2019, 1),
      .default = evol_2020_2019))

# remplacement valeurs NA
unique(vab_agri_2020_2$libreg)
vab_agri_2020_3 <- vab_agri_2020_2 |> 
  filter(libreg != "France métropolitaine hors Ile-de-France") |> 
  mutate(reg = case_when(
      is.na(reg) ~ "FM",
    .default = reg))

# transformation des valeurs numériques manquantes par des zéros 
exploit_prod_anim_pdl <- exploit_prod_anim_pdl |> 
  mutate(
    across(
      where(is.numeric),
      \(x) replace(x, is.na(x), 0)
    )
  )

11.10.1 replace_na : remplacer des valeurs manquantes

La fonction replace_na permet de remplacer des valeurs manquantes (NA). Cette fonction peut être utilisée de deux façons, que l’on va illustrer avec les données suivantes :

df <- tibble::tibble(x = c(1, 2, 3, NA), y = c("a", NA, "b", NA))

df

Premier usage : on remplace les valeurs manquantes dans une colonne d’un data.frame. Dans ce cas, la fonction prend deux arguments : le nom de la variable et la valeur utilisée pour remplacer les valeurs manquantes. Voici un exemple :

library(tidyr) # pour replace_na

df |> 
  dplyr::mutate(x = replace_na(x, 999))
# remplacera tous les NA de la colonne x par la valeur 'val'
# Attention: 'val' doit être du même type que les valeurs de x
data |> 
  mutate(x = replace_na(x, val))

Second usage : on remplace les valeurs manquantes dans toutes les colonnes d’un data.frame. Dans ce cas, la fonction prend deux arguments : le nom du data.frame, et une liste donnant pour chaque variable la valeur à utiliser pour remplacer les valeurs manquantes. Voici un exemple :

df |> 
  replace_na(list(x = 999, y = "zzz"))

# Remplacement des valeurs manquantes dans les colonnes A et B
data_sans_NA_1 <- replace_na(data, list(A = 0, B = "manquant"))

data_sans_NA_2 <- data |>  
  mutate(A = replace_na(A, 0), 
         B = replace_na(B, "manquant")
         )
# Créer une nomenclature agrégée libelle_ab_regroupt à partir de libelleonab et libelle_ab_2
# La valeur de libelle_ab_regroupt est basée sur libelle_ab_2 
# si libelle_ab_2 correspond à "Pommes" et sur libelleonab sinon

# pb avec == si présence de valeurs manquantes
bio_pdl_nomenclature_pb_na <- bio_pdl_nomenclature |> 
  mutate(libelle_ab_regroupt = if_else(libelle_ab_2 == "Pommes", true = libelle_ab_2, false = libelleonab))

unique(bio_pdl_nomenclature_pb_na$libelle_ab_regroupt)

# %in% permet de gérer la présence de valeurs manquantes
bio_pdl_nomenclature <- bio_pdl_nomenclature |> 
  mutate(libelle_ab_regroupt = if_else(libelle_ab_2 %in% c("Pommes"), 
                                      true = libelle_ab_2, 
                                      false = libelleonab))

unique(bio_pdl_nomenclature$libelle_ab_regroupt)

# on remplace au préalable les valeurs manquantes si on souhaite utiliser ==
bio_pdl_nomenclature_renommage_na <- bio_pdl_nomenclature |> 
  mutate(libelle_ab_2 = replace_na(libelle_ab_2, "hors_pommes"))

# Créer une nomenclature agrégée libelle_ab_regroupt à partir de libelleonab et libelle_ab_2
# La valeur de libelle_ab_regroupt est basée sur libelle_ab_2 
# si libelle_ab_2 == "Pommes" et sur libelleonab sinon
bio_pdl_nomenclature_gestion_na <- bio_pdl_nomenclature_renommage_na |> 
  mutate(libelle_ab_regroupt = if_else(libelle_ab_2 == "Pommes", 
                                      true = libelle_ab_2, 
                                      false = libelleonab))

unique(bio_pdl_nomenclature$libelle_ab_regroupt)

11.11 Les listes

Les listes regroupent plusieurs éléments ensemble. Une liste peut donc contenir des vecteurs, des listes, des tableaux de données. Une liste dans R est une structure de données qui peut contenir des éléments de types différents (contrairement aux vecteurs qui doivent être homogènes). Contrairement à un vecteur, qui doit contenir des éléments du même type (par exemple, tous numériques ou tous caractères), une liste peut mélanger différents types.

11.11.1 Création

On construit une liste avec la fonction list.

On peut nommer les éléments à la création de la liste.

liste <- list(nombre = 1, char = "foo", vecteur = c("Pomme", "Citron"))
liste

On peut utiliser names() pour afficher ou modifier les noms des éléments.

names(liste)

Quand la liste est plus complexe, la fonction str peut être utile pour afficher de manière plus compacte la structure de la liste.

str(liste)

11.11.2 Sélection d’éléments

Il y a deux opérateurs différents qui permettent de sélectionner les éléments d’une liste : les crochets simples [] et les crochets doubles [[]]. La différence entre ces deux opérateurs est souvent source de confusion.

Partons de la liste suivante :

liste <- list(1:5, "foo", c("Pomme", "Citron"))
liste

Si on utilise les crochets simples pour sélectionner le premier élément de cette liste, on notera que le résultat est une liste à un seul élément.

liste[1]

Si on utilise les crochets doubles, pn obtient cette fois-ci non pas une liste composée du premier élément, mais le contenu de ce premier élément.

liste[[1]]

La différence est importante, mais pas toujours facile à retenir. On peut utiliser deux petites astuces mnémotechniques :

  • si une liste est un train composé de plusieurs wagons, [1] retourne le premier wagon du train, tandis que [[1]] renvoie le contenu du premier wagon.
  • une alternative est de considérer que [[]] va chercher “plus profondément” que [].

Un autre point important est que si on passe plusieurs éléments à [[]], la sélection se fait d’une manière récursive peu intuitive et source d’erreurs. Il est donc conseillé de toujours utiliser [[]] avec un seul argument, et d’utiliser [] si on souhaite sélectionner plusieurs éléments d’une liste.

liste[c(1, 2)]

En résumé :

  • si on souhaite récupérer uniquement le contenu d’un élément d’une liste, on utilise [[]] avec un seul argument.
  • si on souhaite récupérer une nouvelle liste en sélectionnant des éléments de laliste actuelle, on utilise [] avec un ou plusieurs arguments.

Comme pour les vecteurs, on peut utiliser des nombres négatifs avec [] pour exclure des éléments plutôt que les sélectionner, et on peut également utiliser les fonctions head() et tail().

Si la liste est nommée, on peut sélectionner des éléments par noms avec les deux opérateurs.

liste <- list(nombre = 1, char = "foo", vecteur = c("Pomme", "Citron"))
liste[c("nombre", "char")]
liste[["vecteur"]]

On peut aussi utiliser l’opérateur $, qui équivaut à [[]] :

liste$vecteur

11.11.3 Utilisation

En tant que généralisation des vecteurs atomiques, les listes sont utiles dès qu’on souhaite regrouper des éléments complexes ou hétérogènes.

On les utilisera par exemple pour retourner plusieurs résultats depuis une fonction.

indicateurs <- function(x) {
    list(
        moyenne = mean(x),
        variance = var(x)
    )
}

x <- 1:10
res <- indicateurs(x)
res$moyenne
res$variance

On utilise également les listes pour stocker des objets complexes (par exemple des tableaux ) et leur appliquer des fonctions.

On pourra ensuite utiliser cette liste de tableaux pour leur appliquer des transformations ou les fusionner.

Piège courant à éviter : la confusion entre [ ] et [[ ]].

# Piège : Confusion entre [] et [[]]
ma_liste[1]      # renvoie une liste
ma_liste[[1]]    # renvoie l'élément

11.12 Qu’est-ce que l’opérateur pipe ?

Lorsqu’on enchaîne les manipulations sur une table de données, un problème est que le code devient peu lisible car il y a beaucoup d’opérations imbriquées les unes dans les autres, avec un grand nombre de parenthèses ou de crochets.
L’opérateur pipe (noté |>) permet de résoudre ce problème en réécrivant les opérations de façon plus lisible. Tout ce qui suit le pipe est appliqué à tout ce qui le précède. Le principe de l’opérateur pipe est simple :

  • le terme qui précède l’opérateur est utilisé comme premier argument de la fonction qui suit l’opérateur ;
  • les opérations peuvent être enchaînées en enchaînant les opérateurs pipe ;
  • l’opérateur pipe fonctionne quelle que soit la nature de l’argument ;
  • l’opérateur pipe fonctionne également à l’intérieur de parenthèses.

Voici un petit tableau qui donne des exemples :

Ce code est équivalent à… … ce code
fonction(x) x |> fonction()
fonction3(fonction2(fonction1(x))) x |> fonction1() |> fonction2() |> fonction3()
mutate(tibble, y = log(x)) tibble |> mutate(y = x |> log())

Avec l’usage du pipe, le premier argument -qui devrait être une table- disparaît de l’appel aux fonctions ‘dplyr’ (filter, arrange…) En effet, le pipe envoie le résultat de la ligne précédente comme premier argument de la ligne suivante.

11.12.1 Comment utiliser l’opérateur pipe avec le tidyverse

Un traitement statistique avec les packages du tidyverse prend généralement la forme d’une succession de verbes séparés par l’opérateur pipe (|>). Il est possible d’aller à la ligne en mettant le pipe en bout de ligne (mais pas en début de ligne).

Voici un exemple détaillé pour comprendre l’utilisation du pipe. Ce code se lit comme ceci : on part de la base permanente des équipements 2018, puis on la transforme en tibble, puis on conserve uniquement les stations services TYPEQU == "B316", puis on groupe les observations par département group_by(DEP), puis on calcule la somme du nombre de stations-services par département summarise(nb_equip_total = sum(NB_EQUIP, na.rm = TRUE)).

library(janitor) # pour round_half_up

nombre <- bpe_ens_2018 |>
  as_tibble() |>
  filter(TYPEQU == "B316") |> 
  group_by(DEP) |> 
  summarise(nombre_station_serv = sum(NB_EQUIP, na.rm = TRUE)) 

# on ne conserve que les exploitations avec des bovins
exploit_bovins_a <- exploit_anim_sel |>  
  filter(bovinfil == 1)

# Calculer par département, le nombre d’exploitations ayant des vaches, avec l’effectif de vaches
nb_exploit_vaches_pdl <- exploit_bovins_c |>
  filter(cheptq_111000>0) |> 
  group_by(siege_dep) |> 
  summarise(nb_expl=n(),
            vaches = sum(cheptq_111000)) |> 
  ungroup() |> 
  adorn_totals("row", name="Pays de la Loire")

# Calculer par département, le nombre d’exploitations ayant des vaches, avec l’effectif de vaches
# La fonction summarise() permet d’agréger des données, en appliquant une fonction sur les variables 
# pour construire une statistique sur les observations de la table.
# C’est une fonction dite de “résumé”.
nb_exploit_vaches_dep <- exploit_bovins_c |>
  filter(cheptq_111000>0) |> 
  group_by(siege_dep) |> 
  summarise(nb_expl=n(),
            vaches = sum(cheptq_111000)) |> 
  ungroup()
  
# ajouter la part par rapport à l'ensemble de la région
nb_exploit_vaches_dep_reg <- nb_exploit_vaches_dep |>
  mutate(nb_expl_reg = sum(nb_expl),
         vaches_reg = sum(vaches),
         part_expl = nb_expl / nb_expl_reg * 100,
         part_vaches = vaches / vaches_reg * 100) |> 
  adorn_totals("row", name="Pays de la Loire") |> 
  mutate(across(c(part_expl,part_vaches), \(x) round_half_up(x, digits = 3)))

https://book.utilitr.org/03_Fiches_thematiques/Fiche_tidyverse.html#encha%C3%AEner-les-manipulations-avec-lop%C3%A9rateur-pipe

11.12.2 Exemple d’utilisation du pipe |>

On peut combiner les opérations à l’aide de l’opérateur pipe |> :

selection_62 <- base |>
  mutate(densite = p14_pop / superf,
         tx_natal = 1000 * naisd15 / p14_pop,
         tx_mort = 1000 * decesd15 / p14_pop) |>
  select(codgeo, zau, reg, dep, densite, tx_natal) |>
  filter(dep == "62")

Cette écriture permet d’enchaîner les opérations telles qu’on les décrirait à l’oral. L’objet auquel s’applique chaque nouvelle opération est le résultat de l’opération précédente.

Sans le pipe |>, on peut effectuer les opérations les unes après les autres, en créant des variables successives permettant de stocker les résultats intermédiaires dans des objets temporaires (dont on n’a pas réellement besoin) :

df <- mutate(base, densite = p14_pop / superf,
             tx_natal = 1000 * naisd15 / p14_pop,
             tx_mort = 1000 * decesd15 / p14_pop)
selection <- select(df, codgeo, zau, reg, dep, densite, tx_natal)
filtre_62 <- filter(selection, dep == "62")

Sans le pipe |>, on peut emboîter les fonctions avec des () :

selection_62 <- filter(select(mutate(base, densite = p14_pop / superf,
                                     tx_natal = 1000 * naisd15 / p14_pop,
                                     tx_mort = 1000 * decesd15 / p14_pop),
                              codgeo, zau, reg, dep, densite, tx_natal), 
                       dep == "62")

La lecture est particulièrement confuse et le risque d’oubli de parenthèses important.

Cette notation a plusieurs inconvénients :
- elle est peu lisible
- les opérations apparaissent dans l’ordre inverse de leur réalisation.
- Il est difficile de voir quel paramètre se rapporte à quelle fonction

11.13 Les tests logiques dans R

Un test est une opération logique de comparaison qui renvoie vrai (TRUE) ou faux (FALSE) pour chacun des éléments d’un vecteur.

Parmi les opérateurs de comparaison disponibles, on trouve notamment :

  • == qui teste l’égalité
  • != qui teste la différence
  • >, <, <=, >= qui testent la supériorité ou l’infériorité
  • %in% qui teste l’appartenance à un ensemble de valeurs. La fonction %in%permet de tester si une valeur est présente dans une série de valeurs.
Syntaxe Action
== Test d’égalité
!= Différent de
%in% c(...) Dans une liste de valeurs
>, >=, <, <= Supérieur (ou inférieur) (ou égal)
! (x %in% c(...)) N’est pas dans une liste de valeurs

On peut inverser un test avec l’opérateur non (!)

On peut combiner plusieurs tests avec les opérateurs logiques et (&) et ou (|).

table_sortie <- filter(table_entree, x==a & y==b) # x vaut a **ET** y vaut b
table_sortie <- filter(table_entree, x==a | y==b) # x vaut a **OU** y vaut b (barre verticale AltGr+6)

Pour accéder à la page de documentation (d’aide) de la fonction %in%, faire ?"%in%" ou help("%in%").

Attention, si on souhaite tester si une valeur x est inconnue (ou ‘manquante’), c’est-à-dire si elle est codée NA (Not Available), il faut utiliser la fonction dédiée is.na et faire is.na(x).

La conversion automatique d’un type logique (FALSE,TRUE) en un type entier (0,1) a des aspects pratiques. Quand on applique une fonction qui attend un vecteur de nombres à un vecteur de valeurs logiques, celles-ci sont automatiquement converties, les TRUE devenant 1 et les FALSE devenant 0. Du coup, si on applique sum() à un vecteur de valeurs logiques, le résultat est égal au nombre de valeurs TRUE.

On peut donc appliquer sum() à un test, et on obtiendra le nombre de valeurs pour lesquelles le test est vrai.

Ceci fournit un raccourci très pratique. Par exemple, pour compter le nombre de valeurs manquantes dans un vecteur, on peut faire sum(is.na(vecteur)).

11.14 Ajout d’un opérateur %not_in%

# Définition d'un opérateur `%not_in%` pour plus de clarté dans les filtres
`%not_in%` <- purrr::negate(`%in%`)

à placer en début de programme

Comme le nom de fonction %in% ne contient pas que des lettres, on utilise les “backticks” ` (AltGr+7) qui servent à définir des noms de symbole contenant des caractères spéciaux.

11.15 La question des arrondis

La fonction round arrondit selon le principe de la parité. Pour arrondir systématiquement à l’excès (en valeur absolue) au-delà de 5, on peut utiliser une fonction spécifique, comme la fonction round_half_up() du package janitor.

La fonction ‘round’ standard de R applique la norme IEC 60559 qui indique que pour l’arrondi de 5 on arrondit au chiffre pair le plus proche (ce qui évite de surestimer systématiquement les valeurs en arrondissant toujours au dessus).

https://fr.wikipedia.org/wiki/Arrondi_(math%C3%A9matiques)

library(janitor) # pour round_half_up

# floor : l'entier le plus proche inférieur ou égal à l'argument (partie entière) 
# ceiling : l'entier le plus proche supérieur ou égal à l'argument
# trunc : équivalent de floor pour les valeurs positives et de ceiling pour les valeurs négatives

# Fonction pour gérer les arrondis de .5 (renommage de la fonction janitor::round_half_up)
arrondi <- function(x, digits = 0) {
  posneg <- sign(x)
  z <- abs(x) * 10^digits
  z <- z + 0.5 + sqrt(.Machine$double.eps)
  z <- trunc(z)
  z <- z / 10^digits
  z * posneg
}

# Avec 'round_half_up', si le nombre se situe à mi-chemin, 
# il est arrondi à la valeur la plus proche au-dessus (pour les nombres positifs)
# ou en dessous (pour les nombres négatifs).

# Comparaison des fonctions 'round' et 'round_half_up'
# L'aide de la fonction 'round' indique que les arrondis respectent la norme IEC 60559 
# qui prévoit que l'arrondi soit à la valeur de décimale paire la plus proche 
# donc round (3.5, digits = 0) donne 4 tout comme round(4.5, digits = 0). 

# données exemple
v1 <- seq(from = 0.5, to = 9.5, by = 1)
v1
# [1] 0.5 1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5

# fonction 'round' de Base R
r1 <- round(v1)
# arrondi au chiffre pair le plus proche
r1
# [1]  0  2  2  4  4  6  6  8  8 10

# fonction 'round_half_up' vers le plus loin de zéro 
a1 <- round_half_up(v1)
# arrondi au supérieur (vers le haut en valeur absolue) 
a1
# [1]  1  2  3  4  5  6  7  8  9 10

# avec des valeurs négatives
v1neg <- -v1
v1neg
# [1] -0.5 -1.5 -2.5 -3.5 -4.5 -5.5 -6.5 -7.5 -8.5 -9.5

# avec 'round'
r_neg <- round(v1neg)
r_neg
# [1]   0  -2  -2  -4  -4  -6  -6  -8  -8 -10

# fonction 'round_half_up'
a_neg <- round_half_up(v1neg)
# 'round_half_up' pour valeurs négatives
a_neg
# [1]  -1  -2  -3  -4  -5  -6  -7  -8  -9 -10

# autre exemple
v2 <- seq(-2, 2, by = 0.5)
v2
# [1] -2.0 -1.5 -1.0 -0.5  0.0  0.5  1.0  1.5  2.0
r2 <- round(v2)
r2
# [1] -2 -2 -1  0  0  0  1  2  2
a2 <- round_half_up(v2)
a2
# [1] -2 -2 -1 -1  0  1  1  2  2

# exemple
# Les noms des colonnes peuvent être appelés avec la syntaxe `nomtable$nomvariable`. 
# L’indexation des colonnes de tables (le “$”) permet d'appeler une variable.

# volailles_pdl_sum$tec_regrpt <- round_half_up(volailles_pdl_sum$tec_regrpt, digits = 2)

# eff_volailles_evol <- eff_volailles_annee |> 
#   mutate(evol_2021_2020 = paste0(round_half_up((((an_2021/an_2020)-1) * 100),0)," %"),
#          reg = case_when(
#            region %in% c("52  -  Pays de la Loire") ~ "pdl",
#            region %in% c("98  -  Total France métropolitaine") ~ "fm"))

Application de la fonction round_half_up à plusieurs variables, en utilisant la fonction across

library(janitor) # pour round_half_up

# ajout de la comparaison au niveau reg et au niveau FM (en %)
names(recolte_bois_dep_reg)
recolte_bois_dep_reg <- recolte_bois_dep_reg |>
  mutate(valeur_dep_percent_reg = valeur_dep / valeur_reg * 100,
         valeur_dep_percent_fm = valeur_dep / valeur_fm * 100,
         valeur_reg_percent_fm = valeur_reg / valeur_fm * 100,
         .after = dep) |> 
  mutate(across(contains("percent"), \(x) round_half_up(x, digits = 2)))

11.16 Gestion du secret

# gestion secret
# messages non contraignants Warning messages:
# 1: Problem while computing as.numeric(producteurs_2020) 
# i NAs introduits lors de la conversion automatique 
lait_vache_reg_dep_2021_donnees_secret_1 <- lait_vache_reg_dep_2021_donnees |> 
  mutate(livraisons_2021 = as.character(livraisons_2021)) |> 
  mutate(livraisons_2021 = if_else(as.numeric(producteurs_2021) > 0 & 
                                as.numeric(producteurs_2021) < 3, "Secret", livraisons_2021)) |>
  mutate(producteurs_2021 = if_else(as.numeric(producteurs_2021) > 0 & 
                                as.numeric(producteurs_2021) < 3, "Secret", producteurs_2021)) |> 
  mutate(livraisons_2021 = if_else(Département == "Hérault", "Secret", livraisons_2021)) |>
  mutate(producteurs_2021 = if_else(Département == "Hérault", "Secret", producteurs_2021))
  
# gestion secret
# au moins 3 établissements 
exploit_volailles_com_secret_1 <- exploit_volailles_com |> 
  mutate(secret_eff_01_canards_com = if_else(nb_exploit_01_canards_com > 0 & 
                                          nb_exploit_01_canards_com < 3, "Secret", eff_01_canards_com),   
  secret_nb_exploit_01_canards_com = if_else(nb_exploit_01_canards_com > 0 & 
                                          nb_exploit_01_canards_com < 3, 
                                          "Secret",
                                          nb_exploit_01_canards_com),
  secret_eff_02_dindes_com = if_else(nb_exploit_02_dindes_com > 0 & 
                                  nb_exploit_02_dindes_com < 3, "Secret", eff_02_dindes_com),         
  secret_nb_exploit_02_dindes_com = if_else(nb_exploit_02_dindes_com > 0 & 
                                         nb_exploit_02_dindes_com < 3, "Secret", nb_exploit_02_dindes_com))
# 1 établissement < 85 % total
# recherche plus gros établissement
# gros_exploit_volailles <- exploit_volailles_com |> 
#   group_by(depcom_2022) |> 
#   slice(which.max(eff_01_canards))
# division par zéro crée NaN, qu'on va remplacer par 0
exploit_volailles_com_secret_2 <- exploit_volailles_com_secret_1 |>
  group_by(depcom_2022) |> 
  mutate(max_eff_01_canards_com = max(eff_01_canards),
         max_eff_02_dindes_com = max(eff_02_dindes)) |> 
  mutate(part_max_eff_01_canards_com = max_eff_01_canards_com / eff_01_canards_com * 100 |> 
                    round_half_up(digits  =  2),
         part_max_eff_02_dindes_com = max_eff_02_dindes_com / eff_02_dindes_com * 100 |>
                    round_half_up(digits = 2)) |>  
  mutate(across(everything(),
    \(x) replace(x, is.nan(x), 0)
  )
)
  
exploit_volailles_com_secret_3 <- exploit_volailles_com_secret_2 |> 
  mutate(secret_eff_01_canards_com = if_else(part_max_eff_01_canards_com >= 85,
                                          "Secret",
                                          secret_eff_01_canards_com),         
         secret_nb_exploit_01_canards_com = if_else(part_max_eff_01_canards_com >= 85,
                                                 "Secret",
                                                 secret_nb_exploit_01_canards_com),
         secret_eff_02_dindes_com = if_else(part_max_eff_02_dindes_com >= 85,
                                         "Secret",
                                         secret_eff_02_dindes_com),         
         secret_nb_exploit_02_dindes_com = if_else(part_max_eff_02_dindes_com >= 85,
                                                "Secret",
                                                secret_nb_exploit_02_dindes_com))
# gestion secret ----
# au moins 3 établissements

# Étape 1 : Secret sur les nombres de bénéficiaires
# Modifier uniquement les valeurs des lignes où l'indicateur commence par "dont_nb_benef"
# transformer en "Secret" si la valeur est entre 0 et 3 exclus
# Laisser les autres valeurs inchangées
# avec `if_else()', il convient d'éviter l'erreur de mélange de types character/double
# solution : Convertir d'abord toutes les colonnes numériques en caractères
# Applique ensuite la règle du secret en convertissant temporairement en numérique pour la comparaison
nb_benef_montants_pac_2020_2023_secret1 <- nb_benef_montants_pac_2020_2023 |>
  mutate(across(
    .cols = -indicateur, # toutes les colonnes sauf indicateur
    .fns = as.character # convertir en caractère
  )) |>
  mutate(across(
    .cols = -indicateur,
    .fns = \(x) if_else(
        str_starts(indicateur, "dont_nb_benef") & (as.numeric(x) > 0 & as.numeric(x) < 3),
        "Secret",
        x)
  ))

# `str_starts()` vérifie si une chaîne de caractères commence par un motif spécifique.
# str_starts(indicateur, "dont_nb_benef")
# - `indicateur`: colonne contenant les noms des indicateurs
# - `"dont_nb_benef"`: motif recherché au début de chaque indicateur
# - Retourne `TRUE` si l'indicateur commence par "dont_nb_benef", `FALSE` sinon
# on peut aussi utiliser:
# - `str_detect(indicateur, "^dont_nb_benef")` avec `^` pour début de chaîne
# - `startsWith(indicateur, "dont_nb_benef")` en base R

# Étape 2 : Secret induit
# Pour les noms de colonnes commençant par des chiffres, on doit les entourer de backticks (`) pour les utiliser
nb_benef_montants_pac_2020_2023_secret2 <- nb_benef_montants_pac_2020_2023_secret1 |>
  mutate(
    # Secret 2020
    `44_2020` = if_else(indicateur == "dont_nb_benef_aide_legumineuses_fourrage_deshy", "Secret", `44_2020`),
    `53_2020` = if_else(indicateur == "dont_nb_benef_aide_legumineuses_fourrage_deshy", "Secret", `53_2020`),
    # Secret 2021
    `44_2021` = if_else(indicateur == "dont_nb_benef_aide_legumineuses_fourrage_deshy", "Secret", `44_2021`),
    `49_2021` = if_else(indicateur == "dont_nb_benef_aide_semences_legumineuses_fourrageres", "Secret", `49_2021`),
    # Secret 2022
    `44_2022` = if_else(indicateur == "dont_nb_benef_aide_legumineuses_fourrage_deshy", "Secret", `44_2022`),
    `53_2022` = if_else(indicateur == "dont_nb_benef_aide_legumineuses_fourrage_deshy", "Secret", `53_2022`),
    `49_2022` = if_else(indicateur == "dont_nb_benef_aide_semences_legumineuses_fourrageres", "Secret", `49_2022`),
    `72_2022` = if_else(indicateur == "dont_nb_benef_aide_semences_legumineuses_fourrageres", "Secret", `72_2022`)
  )

# Étape 3 : Secret sur les montants correspondants
# masquer les montants associés aux nombres de bénéficiaires secrets
# pour cela, on vérifie si la ligne précédente commence par "dont_nb_benef" et si la valeur est "Secret"
# si c'est le cas, on remplace la valeur par "Secret"
# Étape 3_1 : Version avec lag()
# lag() décale les valeurs et crée des NA pour la première ligne
nb_benef_montants_pac_2020_2023_secret3 <- nb_benef_montants_pac_2020_2023_secret2 |>
  mutate(across(
    .cols = -indicateur,
    .fns = \(x) if_else(
      str_starts(lag(indicateur), "dont_nb_benef") & lag(x) == "Secret",
      "Secret",
      x)
  ))

# Étape 3_2 : Correction première ligne
# remplacer les NA par les valeurs de la première ligne
# Utiliser slice() pour sélectionner la première ligne
# Utiliser bind_rows() pour combiner avec le reste des données
nb_benef_montants_pac_2020_2023_secret3 <- bind_rows(
  nb_benef_montants_pac_2020_2023_secret2 |> slice(1),
  nb_benef_montants_pac_2020_2023_secret3 |> slice(-1)
)

# ou nb_benef_montants_pac_2020_2023_secret3[1,] <- nb_benef_montants_pac_2020_2023_secret2[1,]

# Explication du fonctionnement de lag() et de la nécessité de corriger la première ligne
# 1_ lag() décale les valeurs d'une position vers le bas:
# # Exemple avec un vecteur simple
# x = c(1, 2, 3, 4, 5)
# lag(x)
# # Résultat: NA, 1, 2, 3, 4
# 2_ Pour la première ligne:
# lag() cherche la valeur précédente
# Mais il n'y a pas de valeur avant la première ligne
# Donc R remplace par NA (valeur manquante)
# 3_ Dans le code prédent :
# if_else(
#   str_starts(lag(indicateur), "dont_nb_benef") & lag(.x) == "Secret",
#   "Secret",
#   .x
# )
# on obtient
# lag(indicateur) pour la 1ère ligne = NA
# lag(.x) pour la 1ère ligne = NA
# D'où la nécessité de corriger cette première ligne manuellement

11.17 Calculer des statistiques spécifiques

Les fonctions sum(), mean(), median(), min(), max(), var(), sd()… résument l’information pour en donner une statistique. La fonction quantile() renvoie les quartiles de la variables (ou bien tout autre découpage qu’on lui renseigne).

Une manière de mesurer la dispersion est de calculer les quartiles :

  • le premier quartile est la valeur pour laquelle on a 25% des observations en dessous et 75% au dessus
  • le deuxième quartile est la valeur pour laquelle on a 50% des observations en dessous et 50% au dessus (c’est la médiane)
  • le troisième quartile est la valeur pour laquelle on a 75% des observations en dessous et 25% au dessus
## Premier quartile
quantile(df$age, probs = 0.25)
## Troisième quartile
quantile(df$age, probs = 0.75)

quantile prend deux arguments principaux : le vecteur dont on veut calculer le quantile, et un argument probs qui indique quel quantile on souhaite obtenir. probs prend une valeur entre 0 et 1 : 0.5 est la médiane, 0.25 le premier quartile, 0.1 le premier décile.

sum(pull(base_extrait, P14_POP), na.rm = TRUE)
mean(pull(base_extrait, P14_POP), na.rm = TRUE)
median(pull(base_extrait, P14_POP), na.rm = TRUE)
quantile(pull(base_extrait, P14_POP), probs = c(0.25, 0.5, 0.75), na.rm = TRUE)

# recherche de seuils
quantile(pull(exploit_anim_produit_grand_pdl, chair_tot), 
         probs = c(0, seq(from = 0.10, to = 1.0, by = 0.10)), 
         na.rm = TRUE)

quantile(pull(exploit_anim_produit_grand_pdl, chair_tot), 
         probs = c(0, seq(from = 0.05, to = 1.0, by = 0.05)), 
         na.rm = TRUE)

quantile(pull(exploit_anim_produit_grand_pdl, chair_tot), 
         probs = c(seq(from = 0.95, to = 1.0, by = 0.01)), 
         na.rm = TRUE)

Ces fonctions retournent une valeur, ou bien un ensemble de valeur (pour quantile()). Le résultat est donc un vecteur de un ou plusieurs nombres.

On utilise le paramètre na.rm = TRUE pour gérer les valeurs manquantes.

11.18 Représentation graphique d’une distribution

Pour étudier la distribution des valeurs d’une variable quantitative, on peut utiliser une représentation graphique, sous forme d’histogramme. On peut l’obtenir avec la fonction hist.

hist(df$age)

Cette fonction génère un graphique qui va s’afficher dans l’onglet Plots de RStudio (en bas à droite).

On peut personnaliser l’apparence de l’histogramme en ajoutant des arguments supplémentaires à la fonction hist. L’argument le plus important est breaks, qui permet d’indiquer le nombre de classes que l’on souhaite.

hist(df$age, breaks = 10)

Les arguments de hist permettent également de modifier la présentation du graphique. On peut ainsi changer la couleur des barres avec col, le titre avec main, les étiquettes des axes avec xlab et ylab :

hist(df$age, col = "skyblue",
     main = "Répartition des âges des enquêtés",
     xlab = "Âge",
     ylab = "Effectif")

11.19 Comment ajouter le total régional à des données par département ?

La première méthode consiste à utiliser la fonction ‘adorn_totals’ du package janitor.

# nb exploitations par département et pour la région
# méthode 1 avec adorn_totals
nb_exploit_2020_dep_reg <- exploit_2020 |> 
  select(nom_dossier, siege_dep) |> 
  group_by(territoire = siege_dep) |> 
  summarise(nb_total_exploitations = n() ) |> 
  ungroup() |> 
  adorn_totals("row", name = "Pays de la Loire")

# ajouter le total régional (sauf pour les variables avec %)
bio_pdl_dep <- bio_pdl_dep |>
  select(-c(contains("_pct"))) |> 
  adorn_totals("row", name = "Pays de la Loire")

La deuxième méthode consiste à réaliser les sommes (ou moyennes) pour le niveau régional, et à concaténer ensuite les lignes avec le niveau départemental.

# méthode 2 avec ajout variable territoire commune (pour département et région)
# nb exploitations par département
nb_exploit_2020_dep <- exploit_2020 |> 
  select(nom_dossier, siege_dep, sau_tot) |> 
  group_by(territoire = siege_dep) |> 
  summarise(nb_total_exploitations = n(),
            SAU_moyenne_ha = sum(sau_tot, na.rm = TRUE) / n() ) |> 
  ungroup()      

# méthode 2.1 nb exploitations pour la région
nb_exploit_2020_reg_a <- exploit_2020 |> 
  select(nom_dossier, siege_dep, sau_tot) |> 
  summarise(nb_total_exploitations = n(),
         SAU_moyenne_ha = sum(sau_tot, na.rm = TRUE) / n() ) |> 
  mutate(territoire = "Pays de la Loire") 

# méthode 2.2 nb exploitations pour la région
nb_exploit_2020_reg_b <- exploit_2020 |> 
  select(nom_dossier, siege_dep, sau_tot) |> 
  mutate(territoire = "Pays de la Loire",
         nb_total_exploitations = n(),
         SAU_moyenne_ha = sum(sau_tot, na.rm = TRUE) / n() ) |> 
  distinct(territoire, nb_total_exploitations, SAU_moyenne_ha)

# nb exploitations par département et pour la région par concaténation des lignes
nb_exploit_2020_dep_reg2a <- bind_rows(nb_exploit_2020_dep, nb_exploit_2020_reg_a)

nb_exploit_2020_dep_reg2b <- bind_rows(nb_exploit_2020_dep, nb_exploit_2020_reg_b)

11.20 Calcul de rangs

On cherche par exemple à déterminer en quelle position se trouve la région d’intérêt, comme dans le fichier https://draaf.pays-de-la-loire.agriculture.gouv.fr/IMG/xls/annexe_regionale_2023.xls présent dans https://draaf.pays-de-la-loire.agriculture.gouv.fr/dossier-territorial-a1657.html.

# ajouter le rang du département en ordre décroissant
# Explication de rank(-nb_tetes_2023, ties.method = "min") :
# La fonction rank attribue des rangs aux valeurs d'une colonne.
# Par défaut, les rangs sont attribués de manière croissante (du plus petit au plus grand).
# -nb_tetes_2023 : Le signe négatif - est utilisé pour inverser l'ordre des valeurs.
# Cela signifie que les valeurs les plus grandes de nb_tetes_2023 recevront les rangs les plus petits
# (c'est-à-dire, le rang 1 pour la valeur la plus grande).
# ties.method = "min" : signifie que toutes les valeurs ex aequo recevront le rang le plus petit
# parmi les rangs qu'elles auraient occupés.
# Par exemple, si deux départements ont la même valeur et devraient être classés 2ème et 3ème,
# ils recevront tous les deux le rang 2.
bio_anim2_dep_pdl_an <- bio_anim2_dep_pdl_an |>
  group_by(echelle_geographique, type_de_production, groupe_de_productions) |>
  mutate(
    rang_nb_tetes_2023 = rank(-nb_tetes_2023, ties.method = "min"),
    rang_nombre_deleveurs_2023 = rank(-nombre_deleveurs_2023, ties.method = "min"),
    rang_part_du_bio_en_percent_2023 = rank(-part_du_bio_en_percent_2023, ties.method = "min"),
    .after = groupe_de_productions
  ) |>
  ungroup()
# Calcul de rangs 1 ----  
# ajout des rangs au niveau dep, par categorie
# créer les variables valeur_dep_rang à partir de valeur_dep
# et valeur_dep_rang1_a correspondant à libdep
# pour valeur_dep_rang correspondant au 1er rang
recolte_bois_dep_reg <- recolte_bois_dep_reg |>
  group_by(categorie) |>
  mutate(valeur_dep_rang = rank(-valeur_dep, ties.method = "min"),
         .before = 1) |>
  mutate(valeur_dep_rang1_a = if_else(valeur_dep_rang == 1, true = libdep, false = ""),
         .after = valeur_dep_rang) |> 
  ungroup()

# Calcul de rangs 2 ----  
# ajout du rang 1 (département) à l'ensemble des lignes
# on réalise une jointure de la table avec elle-même 
# pour affecter la valeur d’une variable à une autre variable. 
# Dans l’exemple suivant, on affecte la valeur (le 1er dep)
# de valeur_dep_rang1 relative à valeur_dep_rang == 1.
# on prend valeur_dep_rang1 correspondant au dep du rang1 
# (pour valeur_dep_rang == 1) et on affecte la valeur 
# pour l'ensemble des cas 
# jointure de l'ensemble des lignes avec la ligne contenant valeur_dep_rang1 (le 1er dep)
# ligne par ligne
recolte_bois_dep_reg <-  recolte_bois_dep_reg |> 
  mutate(commun = 1) 

# en 2 étapes
recolte_bois_dep_reg_rang1_etape1 <-  recolte_bois_dep_reg |>
  select(categorie, commun,
         valeur_dep_rang_y = valeur_dep_rang,
         valeur_dep_rang1 = valeur_dep_rang1_a) |>
  filter(valeur_dep_rang_y == 1)

recolte_bois_dep_reg_2_etape2 <-  recolte_bois_dep_reg |>
  left_join(recolte_bois_dep_reg_rang1_etape1,
            join_by(categorie, commun)) |>
  select(-c(valeur_dep_rang1_a, valeur_dep_rang_y))

# en une étape
recolte_bois_dep_reg_2 <-  recolte_bois_dep_reg |>
  left_join(recolte_bois_dep_reg |> 
                select(categorie, commun,
                       valeur_dep_rang_y = valeur_dep_rang,
                       valeur_dep_rang1 = valeur_dep_rang1_a) |>
                filter(valeur_dep_rang_y == 1),
            join_by(categorie, commun)) |> 
  select(-c(valeur_dep_rang1_a, valeur_dep_rang_y))

11.21 Erreurs fréquentes

R est prêt à exécuter une commande lorsque le chevron > est affiché dans la console. Si le chevron n’apparaît pas, c’est qu’une commande est incomplète. Appuyer sur ‘Echap’ (‘ESC’) pour sortir de cette commande.

11.21.1 Error in nom_de_la_fonction()

impossible de trouver la fonction “nom_de_la_fonction”

Cela signifie que le package auquel appartient la fonction n’est pas chargé, voire pas installé. Il convient alors d’ajouter :

library(nom_package)

11.21.2 there is no package called ‘nom_package’

library(nom_package)

Il convient alors d’ajouter dans le fichier ‘installation_packages.R’

install.packages("nom_package")

11.21.3 erreur avec mutate associé à case_when

rp_individu_pdl_age_actif <-  rp_individu_pdl_var |> 
 mutate(age = case_when(agerevq %in% c("20","25","30","35","40","45","50","55","60") ~ "a_20_64_ans",
                        .default = agerevq),
        actif = case_when(tact %in% c(11,12) ~ "actifs",
                        .default = tact))
Error in `mutate()`:
! Problem while computing `actif = case_when(tact %in% c(11, 12) ~ "actifs", .default = tact)`.
Caused by error in `` names(message) <- `*vtmp*` ``:
! attribut 'names' [1] doit être de même longueur que le vecteur [0]
Run `rlang::last_error()` to see where the error occurred.

En effet, la valeur associée à .default = doit être de même type que la variable créée (ici actif), soit de type caractère. En associant, .default = à un élément de type “caractère”, cela a résolu le problème.

rp_individu_pdl_age_actif <-  rp_individu_pdl_var |> 
 mutate(age = case_when(agerevq %in%
                         c("020","025","030","035","040","045","050","055","060") ~ "a_20_64_ans",
                       .default = agerevq),
        activite = case_when(tact %in% c(11,12) ~ "actifs",
                            .default = "inactifs"))


11.22 Tableau de contingence

Il s’agit de compter, pour chacune des valeurs possibles de la variable (pour chacune des modalités), le nombre d’observations ayant cette valeur. Il s’obtient à l’aide de la fonction table.

table(df$qualif)

Un tableau de ce type peut être affiché ou stocké dans un objet, et on peut à son tour lui appliquer des fonctions.

tab <- table(df$qualif)
sort(tab)

Par défaut la fonction table n’affiche pas les valeurs manquantes (NA). Si on souhaite les inclure il faut utiliser l’argument useNA = "always", soit : table(df$qualif, useNA = "always").

La fonction table() peut calculer les effectifs d’un tableau croisé :

t <- base_extrait |> 
  select(dep, reg) |> 
  table()

print(t)

11.23 Tableau de proportions

La fonction tabyl() de janitor permet d’ajouter des pourcentages.

library(janitor)
data <- c("A", "B", "A", "C", "B", "A")
tabyl_exemple <- tabyl(data)
print(tabyl_exemple)

La fonction prop.table() prend en entrée un objet table (tableau de contingence avec les effectifs) et calcule les pourcentages (total, ligne, colonne) associés \(\rightarrow\) ?prop.table

# Calcule la fréquence en % (la somme de tous les pourcentages vaut 100)
(prop.table(t) * 100) |> 
  round(digits = 1)
# Calcule la fréquence en % par région (la somme de tous les pourcentages d'une colonne vaut 100)
(prop.table(t,"REG") * 100) |> 
  round(digits = 1)