5.4 Remaniement des données

Dans le module 4, vous avez réalisé vos premiers remaniements de données dans le cadre des graphiques en barres. Nous ne nous sommes pas étendu sur les fonctions utilisées à cette occasion. Le remaniement des données est une étape cruciale en analyse des données et il faut en maîtriser au moins les principaux outils. Heureusement, il est déjà possible d’aller loin en combinant une petite dizaine d’outils simples. Les cinq principaux (les plus utilisés) en tout cas dans l’approche Tidyverse utilisée ici sont :

  • sélectionner des colonnes au sein d’un jeu de données avec select()

  • filtrer des lignes dans un jeu de données avec filter()

  • calculer de nouvelles variables dans un jeu de données avec mutate()

  • regrouper les données au sein d’un tableau avec group_by()

  • résumer les variables d’un jeu de données avec summarise()

Ces outils provenant du package dplyr sont décrits en détails dans le chapitre 5 de “R for Data Science”. Nous allons nous familiariser avec eux via une approche pratique sur base d’exemples concrets.

urchin <- read("urchin_bio", package = "data.io", lang = "fr")
rmarkdown::paged_table(urchin)

5.4.1 select()

Lors de l’utilisation de vos jeux de données, vous serez amené à réduire vos données en sous-tableau ne reprenant qu’un sous-ensemble des variables initiales. select() effectue cette opération20 :

urchin1 <- select(urchin, origin, solid_parts, test)
rmarkdown::paged_table(urchin1)
urchin2 <- select(urchin, c(1, 4, 14))
rmarkdown::paged_table(urchin2)
urchin3 <-  select(urchin, origin, contains("weight"))
rmarkdown::paged_table(urchin3)
urchin4 <- select(urchin, ends_with("ht"))
rmarkdown::paged_table(urchin4)

5.4.2 filter()

De même que toutes les colonnes d’un tableau ne sont pas forcément utiles, il est souvent nécessaire de sélectionner les lignes en fonction de critères particuliers pour restreindre l’analyse à une sous-population données, ou pour éliminer les cas qui ne correspondent pas à ce que vous voulez. La fonction filter() effectue ce travail. Repartons du jeu de données urchin_bio simplifié à trois variables (urchin2).

rmarkdown::paged_table(urchin2)

Si vous voulez sélectionner uniquement un niveau lvl d’une variable facteur fact, vous pouvez utiliser un test de condition “égal à” (==) : fact == "lvl". Notez bien le double signe égal ici, et n’oubliez pas d’indiquer le niveau entre guillemets. De même, vous pouvez sélectionner tout sauf ce niveau avec l’opérateur “différent de” (!=). Les opérateur “plus petit que” (<) ou “plus grand que” (>) fonctionnent sur les chaines de caractère selon une logique d’ordre alphabétique, donc, "a" < "b"21.

Comparaison Opérateur Exemple
Égal à == fact == "lvl"
Différent de != fact != "lvl"
Plus grand que > fact > "lvl"
Plus grand ou égal à >= fact >= "lvl"
Plus petit que < fact < "lvl"
Plus petit ou égale à <= fact <= "lvl"
# Tous les oursins sauf ceux issus de la pêche
urchin_sub1 <- filter(urchin2, origin != "Fishery")
rmarkdown::paged_table(urchin_sub1)

Vous pouvez aussi utiliser une variable numérique pour filtrer les données. Les comparaisons précédentes sont toujours applicables, sauf que cette fois vous faites porter la comparaison par rapport à une constante (ou par rapport à une autre variable numérique).

# Oursins plus hauts que 20mm
urchin_sub2 <- filter(urchin2, height > 20)
rmarkdown::paged_table(urchin_sub2)

Vous pouvez combiner différentes comparaisons avec les opérateurs “et” (&) et “ou” (|) :

# Oursins plus hauts que 20 mm ET issus d'élevage ("Farm")
urchin_sub3 <- filter(urchin2, height > 20 & origin == "Farm") 
rmarkdown::paged_table(urchin_sub3)

Avec des variables facteurs composées des nombreux niveaux comme on peut en retrouver dans le jeu de données zooplankton du package BioDataScience, vous pouvez être amené à sélectionner plusieurs niveaux au sein de cette variable. L’opérateur %in% permet d’indiquer que nous souhaitons garder tous les niveaux qui sont dans une liste. Il n’existe pas d’opérateur %not_in%, mais il suffit d’inverser le résultat en précédent l’instruction de ! pour obtenir cet effet. Par exemple, !letters %in% c("a", "d", "f") conserve toutes les lettres sauf a, d et f. L’opérateur ! est d’ailleurs utilisable avec toutes les comparaisons pour en inverser les effets. Ainsi, !x == 1 est équivalent à x != 1.

zooplankton <- read("zooplankton", package = "data.io", lang  = "FR")
# Garde uniquement les copépodes (correspondant à 4 groupes distincts)
copepoda <- filter(zooplankton,
  class %in% c("Calanoïde", "Cyclopoïde",  "Harpacticoïde", "Poecilostomatoïde"))
rmarkdown::paged_table(select(copepoda, ecd:perimeter, class))

Enfin, la détection et l’élimination de lignes contenant des valeurs manquantes (encodées comme NA) est spéciale. En effet, vous ne pouvez pas écrire quelque chose comme x == NA car ceci se lit comme “x est égale à … je ne sais pas quoi”, ce qui renvoie à son tour NA pour toutes les comparaisons quelles qu’elles soient. Vous pouvez utiliser la fonction spécialement prévue pour ce test is.na(). Ainsi, is.na(x) effectue en réalité ce que vous voulez et peut être utilisée à l’intérieur de filter(). Cependant, il existe une fonction spécialement prévue pour débarrasser les tableaux des lignes contenant des valeurs manquantes : drop_na() du package tidyr. Si vous spécifier des noms de colonnes (facultatifs), la fonction ira rechercher les valeurs manquantes uniquement dans ces colonnes-là, sinon, elle scrutera tout le tableau.

urchin_sub4 <- drop_na(urchin) 
rmarkdown::paged_table(urchin_sub4)

5.4.3 mutate()

La fonction mutate() permet de calculer de nouvelles variables (si le nom fourni n’existe pas encore dans le jeu de donnée) ou écrase les variables existantes de même nom. Repartons du jeu de données urchin. Pour calculer de nouvelles variables, vous pouvez employer :

  • les opérateurs arithmétiques :
    • addition : +
    • soustraction : -
    • multiplication : *
    • division : /
    • exposant : ^
    • modulo (reste lors d’une division entière) : %%
    • division entière : %/%
urchin <- mutate(urchin, 
  sum_skel  =  lantern + spines + test, 
  ratio     = sum_skel / skeleton,
  skeleton2 = skeleton^2)
rmarkdown::paged_table(select(urchin, skeleton:spines, sum_skel:skeleton2))
  • les fonctions mathématiques :
    • ln() ou log() (logarithme népérien), lg() ou log10() (logarithme en base 10)
    • ln1p() ou log1p() (logarithme népérien de x + 1), ou lg1p() (logarithme en base 10 de x + 1)
    • exp() (exponentielle, ex) et expm1() (ex - 1)
    • sqrt() (racine carrée)
    • sin(), cos(), tan()
urchin <- mutate(urchin,
  skeleton_log  = log(skeleton), 
  skeleton_sqrt = sqrt(skeleton))
rmarkdown::paged_table(select(urchin, skeleton, skeleton_log, skeleton_sqrt))

La fonction transmute() effectue la même opération, mais en plus, elle laisse tomber les variables d’origine pour ne garder que les nouvelles variables calculées.

5.4.4 group_by()

La fonction group_by() ne change rien dans le tableau lui-même, mais ajoute une annotation qui indique que les calculs ultérieurs devront être effectués sur des sous-ensembles du tableau en parallèle. Ceci est surtout utile avec summarise() (voir ci-dessous). Pour annuler le regroupement, il suffit d’utiliser ungroup().

urchin_by_orig <- group_by(urchin, origin)
head(urchin_by_orig)
# # A tibble: 6 x 24
# # Groups:   origin [1]
#   origin diameter1 diameter2 height buoyant_weight weight solid_parts
#   <fct>      <dbl>     <dbl>  <dbl>          <dbl>  <dbl>       <dbl>
# 1 Fishe…       9.9      10.2    5               NA  0.522       0.478
# 2 Fishe…      10.5      10.6    5.7             NA  0.642       0.589
# 3 Fishe…      10.8      10.8    5.2             NA  0.734       0.677
# 4 Fishe…       9.6       9.3    4.6             NA  0.370       0.344
# 5 Fishe…      10.4      10.7    4.8             NA  0.610       0.559
# 6 Fishe…      10.5      11.1    5               NA  0.610       0.551
# # … with 17 more variables: integuments <dbl>, dry_integuments <dbl>,
# #   digestive_tract <dbl>, dry_digestive_tract <dbl>, gonads <dbl>,
# #   dry_gonads <dbl>, skeleton <dbl>, lantern <dbl>, test <dbl>,
# #   spines <dbl>, maturity <int>, sex <fct>, sum_skel <dbl>, ratio <dbl>,
# #   skeleton2 <dbl>, skeleton_log <dbl>, skeleton_sqrt <dbl>
identical(ungroup(urchin_by_orig), urchin)
# [1] FALSE

5.4.5 summarise()

Si vous voulez résumer vos données (calcul de la moyenne, médiane, etc.), vous pouvez réaliser ceci sur une variable en particulier avec les fonctions dédiées. Par exemple mean(urchin$skeleton) renvoie la masse moyenne de squelette pour tous les oursins (ce calcul donne NA dès qu’il y a des valeurs manquantes, mais l’argument na.rm = TRUE permet d’obtenir un résultat en ne tenant pas compte de ces données manquantes : mean(urchin$skeleton, na.rm = TRUE)). Cela devient vite laborieux s’il faut réitérer ce genre de calcul sur plusieurs variables du jeu de données, et assembler ensuite les résultats dans un petit tableau synthétique. D’autant plus, s’il faut séparer d’abord le jeu de données en sous-groupes pour faire ces calculs. La fonction summarise() reporte automatiquement ces calculs, en tenant compte automatiquement des regroupements proposés via group_by().

tooth <- read("ToothGrowth", package = "datasets", lang = "fr")
tooth_summary <- summarise(tooth,
  "moyenne" = mean(len), 
  "minimum" = min(len), 
  "médiane" = median(len), 
  "maximum" = max(len))
knitr::kable(tooth_summary, digits = 2,
  caption = "Allongement des dents chez des cochons d'Inde recevant de l'acide ascorbique.")
Tableau 5.1: Allongement des dents chez des cochons d’Inde recevant de l’acide ascorbique.
moyenne minimum médiane maximum
18.81 4.2 19.25 33.9

Voici les mêmes calculs, mais effectués séparément pour les deux types de supplémentations alimentaires :

tooth_by_supp <- group_by(tooth, supp)
tooth_summary2 <- summarise(tooth_by_supp,
  "moyenne" = mean(len), 
  "minimum" = min(len), 
  "médiane" = median(len), 
  "maximum" = max(len))
knitr::kable(tooth_summary2, digits = 2,
  caption = "Allongement des dents chez des cochons d'Inde en fonction du supplément jus d'orange (OJ) ou vitamine C (VC).")
Tableau 5.2: Allongement des dents chez des cochons d’Inde en fonction du supplément jus d’orange (OJ) ou vitamine C (VC).
supp moyenne minimum médiane maximum
OJ 20.66 8.2 22.7 30.9
VC 16.96 4.2 16.5 33.9
Pièges et astuces

Tout comme lors de réalisation d’une boite de dispersion, vous devez être particulièrement vigilant au nombre d’observation par sous-groupe. Pensez toujours à ajoutez à chaque tableau de résumé des données, le nombre d’observation par sous-groupe grâce à la fonction n().

tooth_summary2 <- summarise(tooth_by_supp,
  "moyenne" = mean(len), 
  "minimum" = min(len), 
  "médiane" = median(len), 
  "maximum" = max(len),
  "n"       = n())
knitr::kable(tooth_summary2, digits = 2,
  caption = "Allongement des dents chez des cochons d'Inde en fonction du supplément jus d'orange (OJ) ou vitamine C (VC).")
Tableau 5.3: Allongement des dents chez des cochons d’Inde en fonction du supplément jus d’orange (OJ) ou vitamine C (VC).
supp moyenne minimum médiane maximum n
OJ 20.66 8.2 22.7 30.9 30
VC 16.96 4.2 16.5 33.9 30

  1. Voyez ?select_helpers pour une panoplie de fonctions supplémentaires qui permettent une sélection “intelligente” des variables.

  2. L’ordre alphabétique qui fait également intervenir les caractères accentués diffère en fonction de la configuration du système (langue). L’état du système tel que vu par R pour le tri alphabétique est obtenu par Sys.getlocale("LC_COLLATE"). Dans la SciViews Box, ceci est toujours "en_US.UTF-8", ceci afin de rendre le traitement reproductible d’un PC à l’autre, qu’il soit en anglais, français, espagnol, chinois, ou n’importe quelle autre langue.