forked from juba/tidyverse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path12-tidyr.Rmd
359 lines (244 loc) · 15.6 KB
/
12-tidyr.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# Mettre en ordre avec `tidyr` {#tidyr}
```{r include=FALSE}
library(tidyverse)
```
## Tidy data
Comme indiqué dans la section \@ref(tidydata), les extensions du *tidyverse* comme `dplyr` ou `ggplot2` partent du principe que les données sont "bien rangées" sous forme de *tidy data*.
Prenons un exemple avec les données suivantes, qui indique la population de trois pays pour quatre années différentes :
```{r echo=FALSE, paged.print=FALSE, warning=FALSE}
library(gapminder)
data(gapminder)
d <- gapminder
dm <- d %>%
filter(country %in% c("France", "Germany", "Belgium"),
year >= 1992) %>%
select(country, year, pop) %>%
pivot_wider(names_from = year, values_from = pop)
kable(dm)
```
Imaginons qu'on souhaite représenter avec `ggplot2` l'évolution de la population pour chaque pays sous forme de lignes : c'est impossible avec les données sous ce format. On a besoin d'arranger le tableau de la manière suivante :
```{r echo=FALSE, warning=FALSE, paged.print=FALSE}
d <- dm %>%
pivot_longer(-country, names_to = "annee", values_to = "population") %>%
mutate(annee = as.numeric(annee))
kable(d)
```
C'est seulement avec les données dans ce format qu'on peut réaliser le graphique :
```{r}
ggplot(d) +
geom_line(aes(x = annee, y = population, color = country)) +
scale_x_continuous(breaks = unique(d$annee))
```
C'est la même chose pour `dplyr`, par exemple si on voulait calculer la population minimale pour chaque pays avec `summarise` :
```{r warning=FALSE, paged.print=FALSE}
d %>%
group_by(country) %>%
summarise(pop_min = min(population))
```
## Trois règles pour des données bien rangées
Le concept de *tidy data* repose sur trois règles interdépendantes. Des données sont considérées comme *tidy* si :
1. chaque ligne correspond à une observation
2. chaque colonne correspond à une variable
3. chaque valeur est présente dans une unique case de la table ou, de manière équivalente, des unités d'observations différentes sont présentes dans des tables différentes
Ces règles ne sont pas forcément très intuitives. De plus, il y a une infinité de manières pour un tableau de données de ne pas être *tidy*.
Prenons par exemple les règles 1 et 2 et le tableau de notre premier exemple :
```{r warning=FALSE, paged.print=FALSE, echo=FALSE}
kable(dm)
```
Pourquoi ce tableau n'est pas *tidy* ? Parce que si on essaie d'identifier les variables mesurées dans le tableau, il y en a trois : le pays, l'année et la population. Or elles ne correspondent pas aux colonnes de la table. C'est le cas par contre pour la table transformée :
```{r echo=FALSE, warning=FALSE}
kable(dm %>%
pivot_longer(-country, names_to = "annee", values_to = "population"))
```
On peut remarquer qu'en modifiant notre table pour satisfaire à la deuxième règle, on a aussi réglé la première : chaque ligne correspond désormais à une observation, en l'occurrence l'observation de trois pays à plusieurs moments dans le temps. Dans notre table d'origine, chaque ligne comportait en réalité quatre observations différentes.
Ce point permet d'illustrer le fait que les règles sont interdépendantes.
Autre exemple, généré depuis le jeu de données `nycflights13`, permettant cette fois d'illustrer la troisième règle :
```{r echo=FALSE, message=FALSE, warning=FALSE, paged.print=FALSE}
library(nycflights13)
data(flights)
data(airlines)
df <- flights %>%
filter(carrier %in% c("AA", "UA")) %>%
slice(1:8) %>%
select(year, month, day, dep_time, carrier) %>%
left_join(airlines)
kable(df)
```
Dans ce tableau on a bien une observation par ligne (un vol), et une variable par colonne. Mais on a une "infraction" à la troisième règle, qui est que chaque valeur doit être présente dans une unique case : si on regarde la colonne `name`, on a en effet une duplication de l'information concernant le nom des compagnies aériennes. Notre tableau mêle en fait deux types d'observations différents : des observations sur les vols, et des observations sur les compagnies aériennes.
Pour "arranger" ce tableau, il faut séparer les deux types d'observations en deux tables différentes :
```{r echo=FALSE, message=FALSE, warning=FALSE, paged.print=FALSE}
kable(df %>% select(-name))
```
```{r echo=FALSE, message=FALSE, warning=FALSE, paged.print=FALSE}
kable(df %>% select(carrier, name) %>% distinct)
```
On a désormais deux tables distinctes, l'information n'est pas dupliquée, et on peut facilement faire une jointure si on a besoin de récupérer l'information d'une table dans une autre.
## Les verbes de `tidyr`
L'objectif de `tidyr` est de fournir des fonctions pour arranger ses données et les convertir dans un format *tidy*. Ces fonctions prennent la forme de verbes qui viennent compléter ceux de `dplyr` et s'intègrent parfaitement dans les séries de *pipes* (`%>%`), les *pipelines*, permettant d'enchaîner les opérations.
### `pivot_longer` : transformer des colonnes en lignes
Prenons le tableau `d` suivant, qui liste la population de 6 pays en 2002 et 2007 :
```{r echo=FALSE, message=FALSE, warning=FALSE, paged.print=FALSE}
library(gapminder)
data(gapminder)
d <- gapminder
d <- d %>%
filter(country %in% c("France", "Germany", "Belgium", "Switzerland", "Spain", "Italy"),
year >= 2002) %>%
select(country, year, pop) %>%
pivot_wider(names_from = year, values_from = pop)
kable(d)
```
Dans ce tableau, une même variable (la population) est répartie sur plusieurs colonnes, chacune représentant une observation à un moment différent. On souhaite que la variable ne représente plus qu'une seule colonne, et que les observations soient réparties sur plusieurs lignes.
Pour cela on va utiliser la fonction `pivot_longer`^[`pivot_longer` et `pivot_wider` ont été introduites dans la version 1.0 de `tidyr`. Elles ont alors remplacé `gather` et `spread`.] :
```{r warning=FALSE, paged.print=FALSE}
d %>% pivot_longer(c(`2002`,`2007`))
```
La fonction `pivot_longer` prend comme premier argument la liste des colonnes à rassembler (ici on a mis `2002` et `2007` entre *backticks* (`` `2002` ``) pour indiquer à `pivot_longer` qu'il s'agit d'un nom de colonne et pas d'un nombre). Ces colonnes peuvent être spécifiées avec la même syntaxe que celle de la fonction `select` de `dplyr`.
Par exemple, il est parfois plus rapide d'indiquer à `pivot_longer` les colonnes qu'on ne souhaite pas "rassembler". On peut le faire avec la syntaxe suivante :
```{r warning=FALSE, paged.print=FALSE}
d %>% pivot_longer(-country)
```
Par défaut, les colonnes qui contiennent les noms des colonnes d'origine et leurs valeurs sont nommées `name` et `value`. Si cela ne convient pas, on peut indiquer les noms à utiliser via les arguments `names_to` et `values_to` :
```{r warning=FALSE, paged.print=FALSE}
d %>% pivot_longer(c(`2002`,`2007`), names_to = "population", values_to = "annee")
```
Au final, la nom de `pivot_longer` s'explique par le fait qu'on fait "pivoter" notre tableau de départ d'un format "large" (avec plus de colonnes) vers un format "long" (avec plus de lignes).
### `pivot_wider` : transformer des lignes en colonnes
La fonction `pivot_wider` est l'inverse de `pivot_longer`.
Soit le tableau `d` suivant :
```{r echo=FALSE, warning=FALSE, paged.print=FALSE}
library(gapminder)
data(gapminder)
dm <- gapminder
d <- dm %>%
filter(country %in% c("France", "Belgium"),
year >= 2002) %>%
select(-gdpPercap) %>%
pivot_longer(c(`lifeExp`, `pop`), names_to = "variable", values_to = "value")
kable(d)
```
Ce tableau a le problème inverse du précédent : on a deux variables, `lifeExp` et `pop` qui, plutôt que d'être réparties en deux colonnes, sont réparties entre plusieurs lignes.
On va donc utiliser `pivot_wider` pour répartir ces lignes dans deux colonnes différentes :
```{r warning=FALSE, paged.print=FALSE}
d %>% pivot_wider(names_from = variable, values_from = value)
```
`pivot_wider` prend deux arguments principaux :
- `names_from` indique la colonne contenant les noms des nouvelles variables à créer
- `values_from` indique la colonne contenant les valeurs de ces variables
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, logiquement, indiquées par des `NA`).
Exemple avec le tableau `d` suivant :
```{r warning=FALSE, paged.print=FALSE, echo = FALSE}
d <- d %>% bind_rows(list(country = "France", continent = "Europe", year = 2002, variable = "density", value = 94))
kable(d)
```
```{r warning=FALSE, paged.print=FALSE}
d %>%
pivot_wider(names_from = variable, values_from = value)
```
```{r warning=FALSE, paged.print=FALSE}
d %>%
pivot_wider(
names_from = variable, values_from = value,
values_fill = list(value = 0)
)
```
Au final, la nom de `pivot_wider` s'explique par le fait qu'on fait "pivoter" notre tableau de départ d'un format "long" (avec plus de lignes) vers un format "large" (avec plus de colonnes).
### `separate` : séparer une colonne en plusieurs {#separate}
Parfois on a plusieurs informations réunies en une seule colonne et on souhaite les séparer. Soit le tableau d'exemple caricatural suivant, nommé `df` :
```{r echo=FALSE, warning=FALSE, paged.print=FALSE}
df <- tibble(eleve = c("Félicien Machin", "Raymonde Bidule", "Martial Truc"),
note = c("5/20", "6/10", "87/100"))
kable(df)
```
`separate` permet de séparer la colonne `note` en deux nouvelles colonnes `note` et `note_sur` :
```{r warning=FALSE, paged.print=FALSE}
df %>% separate(note, c("note", "note_sur"))
```
`separate` prend deux arguments principaux, le nom de la colonne à séparer et un vecteur indiquant les noms des nouvelles variables à créer. Par défaut `separate` "sépare" au niveau des caractères non-alphanumérique (espace, symbole, etc.). On peut lui indiquer explicitement le caractère sur lequel séparer avec l'argument `sep` :
```{r warning=FALSE, paged.print=FALSE}
df %>% separate(eleve, c("prenom", "nom"), sep = " ")
```
### `unite` : regrouper plusieurs colonnes en une seule
`unite` est l'opération inverse de `separate`. Elle permet de regrouper plusieurs colonnes en une seule. Imaginons qu'on obtient le tableau `d` suivant :
```{r echo=FALSE, warning=FALSE, paged.print=FALSE}
library(questionr)
data(rp2012)
d <- rp2012 %>%
slice(1:6) %>%
select(code_insee, commune, pop_tot) %>%
extract(code_insee, c("code_departement", "code_commune"), regex = "(..)(...)")
kable(d)
```
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. On peut utiliser `unite` pour cela :
```{r warning=FALSE, paged.print=FALSE}
d %>% unite(code_insee, code_departement, code_commune)
```
Le résultat n'est pas idéal : par défaut `unite` ajoute un caractère `_` entre les deux valeurs concaténées, alors qu'on ne veut aucun séparateur. De plus, on souhaite conserver nos deux colonnes d'origine, qui peuvent nous être utiles. On peut résoudre ces deux problèmes à l'aide des arguments `sep` et `remove` :
```{r warning=FALSE, paged.print=FALSE}
d %>%
unite(code_insee, code_departement, code_commune,
sep = "", remove = FALSE)
```
### `extract` : créer de nouvelles colonnes à partir d'une colonne de texte {#extract}
`extract` permet de créer de nouvelles colonnes à partir de sous-chaînes d'une colonne de texte existante, identifiées par des groupes dans une expression régulière.
Par exemple, à partir du tableau suivant :
```{r echo=FALSE, warning=FALSE, paged.print=FALSE}
df <- tibble(eleve = c("Félicien Machin", "Raymonde Bidule", "Martial Truc"),
note = c("5/20", "6/10", "87/100"))
kable(df)
```
On peut extraire les noms et prénoms dans deux nouvelles colonnes avec :
```{r}
df %>% extract(eleve,
c("prenom", "nom"),
"^(.*) (.*)$")
```
On passe donc à `extract` trois arguments : la colonne d'où on doit extraire les valeurs, un vecteur avec les noms des nouvelles colonnes à créer, et une expression régulière comportant autant de groupes (identifiés par des parenthèses) que de nouvelles colonnes.
Par défaut la colonne d'origine n'est pas conservée dans la table résultat. On peut modifier ce comportement avec l'argument `remove = FALSE`. Ainsi, le code suivant extrait les initiales du prénom et du nom mais conserve la colonne d'origine :
```{r}
df %>% extract(eleve,
c("initiale_prenom", "initiale_nom"),
"^(.).* (.).*$",
remove = FALSE)
```
### `complete` : compléter des combinaisons de variables manquantes
Imaginons qu'on ait le tableau de résultats suivants :
```{r echo=FALSE, warning=FALSE, paged.print=FALSE}
df <- tibble(eleve = c("Alain", "Alain", "Barnabé", "Chantal"),
matiere = c("Maths", "Français", "Maths", "Français"),
note = c(16, 9, 17, 11))
kable(df)
```
Les élèves Barnabé et Chantal n'ont pas de notes dans toutes les matières. Supposons que c'est parce qu'ils étaient absents et que leur note est en fait un 0. Si on veut calculer les moyennes des élèves, on doit compléter ces notes manquantes.
La fonction `complete` est prévue pour ce cas de figure : elle permet de compléter des combinaisons manquantes de valeurs de plusieurs colonnes.
On peut l'utiliser de cette manière :
```{r}
df %>% complete(eleve, matiere)
```
On voit que les combinaisons manquante "Barnabé - Français" et "Chantal - Maths" ont bien été ajoutées par `complete`.
Par défaut les lignes insérées récupèrent des valeurs manquantes `NA` pour les colonnes restantes. On peut néanmoins choisir une autre valeur avec l'argument `fill`, qui prend la forme d'une liste nommée :
```{r}
df %>% complete(eleve, matiere, fill = list(note = 0))
```
Parfois on ne souhaite pas inclure toutes les colonnes dans le calcul des combinaisons de valeurs. Par exemple, supposons qu'on rajoute dans notre tableau une colonne avec les identifiants de chaque élève :
```{r echo=FALSE, warning=FALSE, paged.print=FALSE}
df <- tibble(
id = c(1001001, 1001001, 1001002, 1001003),
eleve = c("Alain", "Alain", "Barnabé", "Chantal"),
matiere = c("Maths", "Français", "Maths", "Français"),
note = c(16, 9, 17, 11))
kable(df)
```
Si on applique `complete` comme précédemment, le résultat n'est pas bon car il contient toutes les combinaisons de `id`, `eleve` et `matiere`.
```{r}
df %>% complete(id, eleve, matiere)
```
Dans ce cas, pour signifier à `complete` que `id` et `eleve` sont deux attributs d'un même individu et ne doivent pas être combinés entre eux, on doit les placer dans une fonction `nesting` :
```{r}
df %>% complete(nesting(id, eleve), matiere)
```
## Ressources
Chaque jeu de données est différent, et le travail de remise en forme est souvent long et plus ou moins compliqué. On n'a donné ici que les exemples les plus simples, et c'est souvent en combinant différentes opérations qu'on finit par obtenir le résultat souhaité.
Le livre *R for data science*, librement accessible en ligne, contient [un chapitre complet](http://r4ds.had.co.nz/tidy-data.html) sur la remise en forme des données.
L'article [Tidy data](https://www.jstatsoft.org/article/view/v059i10), publié en 2014 dans le *Journal of Statistical Software*, présente de manière détaillée le concept éponyme (mais il utilise des extensions désormais obsolètes qui ont depuis été remplacées par `dplyr` et`tidyr`).
Le site de l'extension est accessible à l'adresse : https://tidyr.tidyverse.org/ et contient une liste des fonctions et les pages d'aide associées.