9  Tidyverse

Une grande partie du travail en analyse de données consiste à préparer, nettoyer et réorganiser les données afin de les rendre exploitables pour les étapes suivantes, comme la visualisation, la modélisation ou l’interprétation.

Pour accomplir ces tâches, au-delà des techniques de base abordées dans le chapitre précédent, R propose des outils bien plus puissants et spécialisés. Parmi les plus populaires figurent :

Tidyverse est une collection de packages conçus pour simplifier et harmoniser le traitement des données en R. Il comprend notamment les packages suivants : tibble, qui propose une alternative moderne aux dataframes classiques, readr pour l’importation de données, dplyr destiné à faciliter la manipulation des données, tidyr utilisé pour leur organisation et leur restructuration, ainsi que ggplot2, dédié à la visualisation graphique — ce dernier fera d’ailleurs l’objet d’un chapitre à part entière. Ces outils partagent une philosophie de conception cohérente, ce qui rend leur apprentissage ainsi que leur utilisation particulièrement intuitifs.

Les principaux packages du tidyverse peuvent être installés et chargés en une seule fois, comme s’il s’agissait d’un seul package :

library(tidyverse) #<-- Au préalable install.packages("tidyverse")

Toutefois, chaque package peut également être chargé/installé individuellement selon les besoins sans importer l’ensemble de tidyverse.

9.1 Manipuler les données avec dplyr

“dplyr” vient de “data pliers” (pliers signifie pince), suggérant un outil conçu pour manipuler ou travailler les données de manière intuitive et conviviale.

Les fonctions principales de dplyr sont :

  • select() : sélectionner des colonnes.
  • mutate() : ajouter, supprimer ou modifier des colonnes.
  • filter() : filtrer les lignes selon des conditions.
  • arrange() : trier les lignes.
  • summarise() : résumer les données, souvent en combinaison avec group_by() pour produire des statistiques par groupe.

Dans la suite, ces fonctionnalités seront illustrées à l’aide du jeu de données suivant (déjà utilisé précédemment) :

Taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170)
Poids <- c(86, 74, 83, 50, 78, 66, 66, 51, 50, 55)
Prog <- c("Bac", "Bac", "Master", "Bac", "Bac", "Master", "Master", NA, "Bac", "Bac")
Sexe <- c("H", "H", "F", "H", "H", "H", "F", "H", "H", "H") |> factor()
df <- data.frame(Taille, Poids, Prog, Sexe)
df
   Taille Poids   Prog Sexe
1     167    86    Bac    H
2     192    74    Bac    H
3     173    83 Master    F
4     174    50    Bac    H
5     172    78    Bac    H
6     167    66 Master    H
7     171    66 Master    F
8     185    51   <NA>    H
9     163    50    Bac    H
10    170    55    Bac    H

La présentation qui suit offre une introduction aux concepts fondamentaux. Pour des informations plus complètes, consultez la documentation officielle : https://dplyr.tidyverse.org/.

Sélectionner des colonnes avec select()

select(df, 1:2)                      # Sélection par position : colonnes 1 et 2

select(df, c(Poids, Prog))           # Sélection par noms
select(df, Poids, Prog)              # Idem

select(df, -c(Poids, Prog))          # Exclusion par noms
select(df, !c(Poids, Prog))          # Idem
Output
   Taille Poids
1     167    86
2     192    74
3     173    83
4     174    50
5     172    78
6     167    66
7     171    66
8     185    51
9     163    50
10    170    55
   Poids   Prog
1     86    Bac
2     74    Bac
3     83 Master
4     50    Bac
5     78    Bac
6     66 Master
7     66 Master
8     51   <NA>
9     50    Bac
10    55    Bac
   Poids   Prog
1     86    Bac
2     74    Bac
3     83 Master
4     50    Bac
5     78    Bac
6     66 Master
7     66 Master
8     51   <NA>
9     50    Bac
10    55    Bac
   Taille Sexe
1     167    H
2     192    H
3     173    F
4     174    H
5     172    H
6     167    H
7     171    F
8     185    H
9     163    H
10    170    H
   Taille Sexe
1     167    H
2     192    H
3     173    F
4     174    H
5     172    H
6     167    H
7     171    F
8     185    H
9     163    H
10    170    H

La fonction select() s’accompagne d’un ensemble d’outils qui permettent de cibler les colonnes à sélectionner de manière expressive, flexible et cohérente. Par example, des fonctions comme starts_with(), ends_with(), contains() ou where() facilitent la sélection des colonnes selon des critères liés à leurs noms ou à leur type. Exemple :

# Sélectionner toutes les colonnes numériques
1select(df, where(is.numeric))

# Sélectionne toutes les colonnes dont le nom contient "o"
select(df, contains("o"))
1
Cela fonctionne aussi avec d’autres tests/fonctions comme is.character(), is.logical(), is.double(), etc; voir l’aide de where().
Output
   Taille Poids
1     167    86
2     192    74
3     173    83
4     174    50
5     172    78
6     167    66
7     171    66
8     185    51
9     163    50
10    170    55
   Poids   Prog
1     86    Bac
2     74    Bac
3     83 Master
4     50    Bac
5     78    Bac
6     66 Master
7     66 Master
8     51   <NA>
9     50    Bac
10    55    Bac

Pour voir les options de sélection disponibles, consultez l’aide de la fonction (?select) ainsi que dans sa documentation officielle en ligne.

Note

Les fonctions starts_with(), ends_with(), contains(), where(), etc. font partie d’un système appelé tidyselect. Elles peuvent être utilisées dans tout contexte de sélection de colonnes au sein du tidyverse, et pas uniquement avec select().

Transfomer des colonnes avec mutate()

La fonction mutate() permet de créer de nouvelles colonnes ou de modifier des colonnes existantes. Contrairement à la fonction de base transform(), mutate() autorise l’utilisation immédiate des variables nouvellement créées. Exemple :

df |> mutate(
  Taille.m = 0.01 * Taille,
  Poids = Poids * 1000,
  Taille = NULL,
  imc = (Poids / 1000) / Taille.m^2
)
   Poids   Prog Sexe Taille.m      imc
1  86000    Bac    H     1.67 30.83653
2  74000    Bac    H     1.92 20.07378
3  83000 Master    F     1.73 27.73230
4  50000    Bac    H     1.74 16.51473
5  78000    Bac    H     1.72 26.36560
6  66000 Master    H     1.67 23.66524
7  66000 Master    F     1.71 22.57105
8  51000   <NA>    H     1.85 14.90139
9  50000    Bac    H     1.63 18.81892
10 55000    Bac    H     1.70 19.03114

Il est fréquent de veloir appliquer une même transformation à plusieurs variables. La fonction across() simplifie cette opération. Elle prend deux arguments principaux : (i) le sous-ensemble de colonnes de la dataframe; (ii) une ou plusieurs fonctions à appliquer aux colonnes sélectionnées. Exemple :

# Applique log() aux colonnes Taille et Poids
1mutate(df, across(.cols = c(Taille, Poids), .fns = log))

# Applique log() à toutes les colonnes numériques
mutate(df, across(where(is.numeric), log))

# Applique log() et log10() à toutes les colonnes numériques
mutate(df, across(where(is.numeric), list(log10 = log10, log = log)))
1
L’utilisation de across() exige de spécifier à la fois les colonnes à sélectionner ET la ou les fonctions à appliquer à l’intérieur des parenthèses. Ainsi, le code suivant génère une erreur : mutate(df, across(c(Taille, Poids)), log) car la fonction log() est placée en dehors de across().
Output
     Taille    Poids   Prog Sexe
1  5.117994 4.454347    Bac    H
2  5.257495 4.304065    Bac    H
3  5.153292 4.418841 Master    F
4  5.159055 3.912023    Bac    H
5  5.147494 4.356709    Bac    H
6  5.117994 4.189655 Master    H
7  5.141664 4.189655 Master    F
8  5.220356 3.931826   <NA>    H
9  5.093750 3.912023    Bac    H
10 5.135798 4.007333    Bac    H
     Taille    Poids   Prog Sexe
1  5.117994 4.454347    Bac    H
2  5.257495 4.304065    Bac    H
3  5.153292 4.418841 Master    F
4  5.159055 3.912023    Bac    H
5  5.147494 4.356709    Bac    H
6  5.117994 4.189655 Master    H
7  5.141664 4.189655 Master    F
8  5.220356 3.931826   <NA>    H
9  5.093750 3.912023    Bac    H
10 5.135798 4.007333    Bac    H
   Taille Poids   Prog Sexe Taille_log10 Taille_log Poids_log10 Poids_log
1     167    86    Bac    H     2.222716   5.117994    1.934498  4.454347
2     192    74    Bac    H     2.283301   5.257495    1.869232  4.304065
3     173    83 Master    F     2.238046   5.153292    1.919078  4.418841
4     174    50    Bac    H     2.240549   5.159055    1.698970  3.912023
5     172    78    Bac    H     2.235528   5.147494    1.892095  4.356709
6     167    66 Master    H     2.222716   5.117994    1.819544  4.189655
7     171    66 Master    F     2.232996   5.141664    1.819544  4.189655
8     185    51   <NA>    H     2.267172   5.220356    1.707570  3.931826
9     163    50    Bac    H     2.212188   5.093750    1.698970  3.912023
10    170    55    Bac    H     2.230449   5.135798    1.740363  4.007333

Filtrer des lignes avec filter()

La fonction filter() permet de sélectionner uniquement les lignes d’un dataframe qui répondent à une ou plusieurs conditions logiques. Lorsque plusieurs conditions sont fournies séparément, filter() les combine implicitement à l’aide d’un opérateur logique ET (&).

filter(df, Sexe == "H")                            # Hommes uniquement
filter(df, is.na(Prog))                            # Lignes où 'Prog' est manquant
filter(df, Sexe == "H" & Poids < mean(Poids))      # Hommes avec poids sous la moyenne
filter(df, Sexe == "H", Poids < mean(Poids))       # Idem
Output
  Taille Poids   Prog Sexe
1    167    86    Bac    H
2    192    74    Bac    H
3    174    50    Bac    H
4    172    78    Bac    H
5    167    66 Master    H
6    185    51   <NA>    H
7    163    50    Bac    H
8    170    55    Bac    H
  Taille Poids Prog Sexe
1    185    51 <NA>    H
  Taille Poids Prog Sexe
1    174    50  Bac    H
2    185    51 <NA>    H
3    163    50  Bac    H
4    170    55  Bac    H
  Taille Poids Prog Sexe
1    174    50  Bac    H
2    185    51 <NA>    H
3    163    50  Bac    H
4    170    55  Bac    H

Notez que filter(), comme la plupart des fonctions du tidyverse, adopte un mécanisme d’évaluation non standard permettant d’utiliser directement les noms de colonnes sans les préfixer par $, car les expressions sont évaluées dans le contexte du dataframe fourni. Ce comportement ressemble à celui de certaines fonctions du R de base comme subset(), mais le tidyverse repose sur une approche différente appelé tidy evaluation.

Ordonner les lignes avec arange()

La fonction arrange() permet d’ordonner les lignes d’un tableau en fonction des valeurs d’une ou plusieurs colonnes. Lorsque plusieurs colonnes sont indiquées, le tri se fait d’abord selon la première, puis — en cas de valeurs identiques — selon la deuxième, et ainsi de suite. Pour trier en ordre décroissant, on utilise la fonction desc() à l’intérieur de arrange(). Exemple :

arrange(df, Taille, desc(Poids))
   Taille Poids   Prog Sexe
1     163    50    Bac    H
2     167    86    Bac    H
3     167    66 Master    H
4     170    55    Bac    H
5     171    66 Master    F
6     172    78    Bac    H
7     173    83 Master    F
8     174    50    Bac    H
9     185    51   <NA>    H
10    192    74    Bac    H

Résumer et agréger avec summarize() et group_by()

Agréger des données consiste à regrouper et à résumer des informations issues de plusieurs observations, généralement selon une ou plusieurs variables clés. Cela permet de produire des valeurs synthétiques — telles que des moyennes, des totaux ou des médianes — qui facilitent l’analyse comparative et la synthèse globale.

La fonction summarise() permet justement de créer ces résumés statistiques, à partir d’une ou plusieurs colonnes. Exemple :

# Moyennes de Taille et Poids
summarise(df, mean.Taille = mean(Taille), mean.Poids = mean(Poids))

# Moyennes et écarts-types de toutes les variables numériques
summarise(df, across(where(is.numeric), list(mean = mean, sd = sd)))
  mean.Taille mean.Poids
1       173.4       65.9
  Taille_mean Taille_sd Poids_mean Poids_sd
1       173.4  8.758488       65.9 13.96384

La fonction group_by() permet, quant à elle, de regrouper les observations (lignes) en fonction des valeurs d’une ou plusieurs variables. Exemple :

group_by(df, Sexe)
# A tibble: 10 × 4
# Groups:   Sexe [2]
   Taille Poids Prog   Sexe 
    <dbl> <dbl> <chr>  <fct>
 1    167    86 Bac    H    
 2    192    74 Bac    H    
 3    173    83 Master F    
 4    174    50 Bac    H    
 5    172    78 Bac    H    
 6    167    66 Master H    
 7    171    66 Master F    
 8    185    51 <NA>   H    
 9    163    50 Bac    H    
10    170    55 Bac    H    

group_by() ne modifie pas les données, mais ajoute une mention Groups dans l’affichage du tableau pour signaler qu’en interne, les données sont désormais divisées/taguées par groupe. Toutes les opérations suivantes — comme summarise(), mutate() ou filter() — seront alors exécutées séparément au sein de chaque groupe. Exemple :

# Moyennes de taille et poids par Sexe
df |> group_by(Sexe) |>
  summarise(mean.Taille = mean(Taille), mean.Poids = mean(Poids))

# Taille relative à la moyenne du groupe
df |> group_by(Sexe) |>
  mutate(TAILLE = Taille / mean(Taille))

# Individus plus petits que la moyenne du groupe
df |> group_by(Sexe) |>
  filter(Taille < mean(Taille))
Output
# A tibble: 2 × 3
  Sexe  mean.Taille mean.Poids
  <fct>       <dbl>      <dbl>
1 F            172        74.5
2 H            174.       63.8
# A tibble: 10 × 5
# Groups:   Sexe [2]
   Taille Poids Prog   Sexe  TAILLE
    <dbl> <dbl> <chr>  <fct>  <dbl>
 1    167    86 Bac    H      0.961
 2    192    74 Bac    H      1.11 
 3    173    83 Master F      1.01 
 4    174    50 Bac    H      1.00 
 5    172    78 Bac    H      0.990
 6    167    66 Master H      0.961
 7    171    66 Master F      0.994
 8    185    51 <NA>   H      1.06 
 9    163    50 Bac    H      0.938
10    170    55 Bac    H      0.978
# A tibble: 6 × 4
# Groups:   Sexe [2]
  Taille Poids Prog   Sexe 
   <dbl> <dbl> <chr>  <fct>
1    167    86 Bac    H    
2    172    78 Bac    H    
3    167    66 Master H    
4    171    66 Master F    
5    163    50 Bac    H    
6    170    55 Bac    H    

Dans ce contexte, une fonction bien utile est n(). Elle permet de compter le nombre d’observations. Elles est souvent utilisée avec summarise() :

# Nombre total d'observations
summarise(df, eff = n())

# Nombre total d'observations par Sexe
df |> group_by(Sexe) |>
  summarise(eff = n())

# Calcule l'effectif par Sexe et ajoute les pourcentages
df |> group_by(Sexe) |>
  summarise(eff = n()) |>
  mutate(pourcentage = eff / sum(eff) * 100)
  eff
1  10
# A tibble: 2 × 2
  Sexe    eff
  <fct> <int>
1 F         2
2 H         8
# A tibble: 2 × 3
  Sexe    eff pourcentage
  <fct> <int>       <dbl>
1 F         2          20
2 H         8          80

Une façon alternative et souvent plus pratique d’effectuer des comptages consiste à utiliser la fonction count(). Cette fonction permet de calculer directement le nombre d’occurrences d’une ou plusieurs variables, sans passer par group_by() suivi de summarise(). Exemple :

df |> count(Sexe) |> mutate(pourcentage = n / sum(n) * 100)
df |> count(Sexe, Prog)
  Sexe n pourcentage
1    F 2          20
2    H 8          80
  Sexe   Prog n
1    F Master 2
2    H    Bac 6
3    H Master 1
4    H   <NA> 1

Introduit dans dplyr 1.1.0, l’argument .by offre une syntaxe plus concise pour effectuer des opérations par groupe, sans avoir à recourir à group_by(). Il permet de spécifier directement les variables de regroupement à l’intérieur de fonctions comme summarise(), mutate() ou filter().

df |> summarise(mean.Taille = mean(Taille), mean.Poids = mean(Poids), .by = Sexe)
df |> mutate(TAILLE = Taille / mean(Taille), .by = Sexe)
df |> filter(Taille < mean(Taille), .by = Sexe)
df |> count(Sexe, Prog) |> mutate(pourcentage = n / sum(n) * 100, .by = Sexe)
  Sexe mean.Taille mean.Poids
1    H      173.75      63.75
2    F      172.00      74.50
   Taille Poids   Prog Sexe    TAILLE
1     167    86    Bac    H 0.9611511
2     192    74    Bac    H 1.1050360
3     173    83 Master    F 1.0058140
4     174    50    Bac    H 1.0014388
5     172    78    Bac    H 0.9899281
6     167    66 Master    H 0.9611511
7     171    66 Master    F 0.9941860
8     185    51   <NA>    H 1.0647482
9     163    50    Bac    H 0.9381295
10    170    55    Bac    H 0.9784173
  Taille Poids   Prog Sexe
1    167    86    Bac    H
2    172    78    Bac    H
3    167    66 Master    H
4    171    66 Master    F
5    163    50    Bac    H
6    170    55    Bac    H
  Sexe   Prog n pourcentage
1    F Master 2       100.0
2    H    Bac 6        75.0
3    H Master 1        12.5
4    H   <NA> 1        12.5

Notez que group_by() et l’argument .by peuvent être utilisés avec plusieurs variables simultanément. Pour cela, on utilise la syntaxe group_by(var1, var2, ...) ou .by = c(var1, var2, ...); voir ?dplyr_by pour plus de détails.

9.2 Remodeler un tableu avec tidyr

Les données ne sont pas toujours organisées de manière optimale pour l’analyse statistique ou la visualisation. Il est donc souvent nécessaire de modifier la structure d’un tableau de données pour rendre celui-ci plus exploitable. Le package tidyr du tidyverse propose des fonctions simples et puissantes pour effectuer ces transformations.

Parmi les plus courantes :

  • pivot_longer() permet de convertir un tableau large en format long, en regroupant plusieurs colonnes sous une même variable.
  • pivot_wider() fait l’inverse : il étale les valeurs d’une variable sur plusieurs colonnes pour obtenir un format plus large.

Voyons comment remodeler un jeu de données à travers un exemple concret. Voici, à titre d’exemple, notre dataframe de départ (en format long, où chaque observation occupe une ligne et chaque variable (unique) occupe une colonne) :

df <- data.frame(
  Contry = c(rep("A", 3), rep("B", 3), rep("C", 3)),
  Year = rep(c(2000, 2010, 2020), 3),
  Metric = c(10, 13, 15, 20, 23, 25, 30, 33, 35)
)
df
  Contry Year Metric
1      A 2000     10
2      A 2010     13
3      A 2020     15
4      B 2000     20
5      B 2010     23
6      B 2020     25
7      C 2000     30
8      C 2010     33
9      C 2020     35

pivot_wider()

dfW <- pivot_wider(df,
  names_from  = Year,       # Crée des colonnes à partir de `Year`
  values_from = Metric,     # Remplit ces colonnes avec les valeurs de `Metric`
  names_prefix = "Yr_"      # Ajoute le préfixe "Yr_" aux noms de colonnes
) |> print()
# A tibble: 3 × 4
  Contry Yr_2000 Yr_2010 Yr_2020
  <chr>    <dbl>   <dbl>   <dbl>
1 A           10      13      15
2 B           20      23      25
3 C           30      33      35

Dans dfW, la variable Year se trouve étalée sur plusieurs colonnes : c’est ce qu’on appelle le format wide (ou format large).

pivot_longer()

pivot_longer(dfW,
  cols = starts_with("Yr"),   # Sélectionne les colonnes à transformer en lignes (celles commençant par "Yr")
  names_to = "Year",          # Crée une colonne "Year" contenant les noms des colonnes sélectionnées
  values_to = "Metric",       # Crée une colonne "Metric" contenant les valeurs correspondantes
  names_prefix = "Yr_"        # Supprime le préfixe "Yr_" des noms avant de les insérer dans "Year"
)
# A tibble: 9 × 3
  Contry Year  Metric
  <chr>  <chr>  <dbl>
1 A      2000      10
2 A      2010      13
3 A      2020      15
4 B      2000      20
5 B      2010      23
6 B      2020      25
7 C      2000      30
8 C      2010      33
9 C      2020      35

Les fonctions pivot_longer() et pivot_wider() en particulier, ainsi que le package tidyr de manière générale, offrent de nombreuses options pour restructurer les données selon les besoins. Pour explorer davantage leurs fonctionnalités, consulter des exemples concrets et découvrir d’autres cas d’usage, il est recommandé de se référer à la documentation officielle https://tidyr.tidyverse.org/.