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) 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.
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ération21 :
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
).
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"
22.
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 de nombreux niveaux comme on peut en retrouver dans le jeu de données zooplankton
du package {data.io}, 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.
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 :
%/%
- addition :
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()
oulog()
(logarithme népérien),lg()
oulog10()
(logarithme en base 10)ln1p()
oulog1p()
(logarithme népérien de x + 1), oulg1p()
(logarithme en base 10 de x + 1)exp()
(exponentielle, ex) etexpm1()
(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()
.
# # 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>
# [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.")
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).")
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).")
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 |
summarise()
calcule ses variables dans le même environnement que le tableau de départ. Donc, si vous utiliser des noms de colonnes qui existent déjà, elles seront écrasées par le résultat du calcul. Voici un exemple concret :
tooth_summary3 <- summarise(tooth,
"len" = mean(len), # Notez le même nom à gauche et à droite du = (len)
"len_sd" = sd(len))
knitr::kable(tooth_summary3, digits = 2,
caption = "Exemple de résumé des données erroné à cause de l'écrasement de la variable `len`.")
len | len_sd |
---|---|
18.81 | NA |
L’écart type est… NA
??? Pour comprendre ce qui s’est passé, il faut lire la transformation réalisée par summarise()
ligne après ligne :
- la moyenne de la variable
len
est placée dans …len
. Donc ici, nous écrasons la variable initiale de 60 observations par un nombre unique : la moyenne, - l’écart type de
len
est ensuite calculé. Attendez une minute, de quellen
s’agit-il ici ? Et bien la dernière en date, soit celle qui contient une seule valeur, la moyenne. Or,sd()
nécessite au moins deux valeurs pour que l’écart type puisse être calculé, sinon,NA
est renvoyé. C’est encore heureux ici, car nous aurions pu faire un calcul qui renvoie un résultat, … mais qui n’est pas celui qu’on croit !
Conclusion : ne nommez jamais vos variables créées avec summarise()
exactement comme les variables de votre tableau en entrée.
Voyez
?select_helpers
pour une panoplie de fonctions supplémentaires qui permettent une sélection “intelligente” des variables.↩︎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.↩︎