-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathr_tidy_prog.Rmd
323 lines (252 loc) · 12.7 KB
/
r_tidy_prog.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
---
title: "Tidyverse et rlang"
author: "State of the R"
date: "24-28/08/2020"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, warning = FALSE)
```
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/stateofther/finistR2020/website?filepath=finistR2020_website/advanced_r.Rmd)
# Eléments généraux
Les éléments de l'introduction proviennent du site d'aide de [dplyr](https://cran.r-project.org/web/packages/dplyr/vignettes/programming.html).
L'évaluation tidy est un type d'évaluation utilisée dans tout le tidyverse et les verbes `dplyr` utilise d'une manière ou d'une autre l'évaluation tidy. Il en existe essentiellement de 2 formes :
- `arrange()`, `count()`, `filter()`, `group_by()`, `mutate()`, et `summarise()` : ces verbes utilisent le masquage de données (data masking). De cette façon, les variables des données peuvent être utilisées sans avoir recours au `$`. Plus besoin d'écrire `df$var` mais simplement `var` dans le verbe dplyr.
- `across()`, `relocate()`, `rename()`, `select()`, et `pull()` : ces verbes utilisent la sélection tidy (tidy selection) et permettent de choisir facilement des variables en se basant sur leur position, leur nom ou leur type (e.g. `starts_with("x")` or `is.numeric()`).
Ces verbes rendent l'exploration de données rapide et fluide, mais ils peuvent poser des problèmes dans le cas où ils sont utilisés dans des boucles ou des fonctions.
Par exemple, une fonction qui utilise le data masking peut notamment poser problème lorsqu'elle appelle le nom d'une variable dans un tableau à partir d'une variable de l'environnement sans taper explicitement la variable du tableau (des exemples précis sont donnés par la suite). Ces problèmes peuvent limiter la généricité des codes utilisant le langage dplyr. [rlang](https://cran.r-project.org/web/packages/rlang/index.html) fournit justement des outils pour pallier aux limitations introduites par le data masking.
Par ailleurs, la sélection tidy est un outil complémentaire qui facilite le travail sur les colonnes d'un jeu de données.
La suite du document donnent quelques exemple d'utilisation de rlang.
# Examples d'utilisation de rlang
## Tidy evaluation à l'aide de `{{` et `.data`.
L'utilisation de fonctions du tidyverse dans des fonctions personnelles peut parfois poser problème
```{r, message=FALSE}
library(tidyverse)
theme_set(theme_bw())
data(penguins, package = "palmerpenguins")
```
```{r}
## This works
ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm, color = species)) +
geom_point() +
ggtitle("Variable = species")
```
```{r, error = TRUE}
## This doesn't
my_plot <- function(var) {
ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm, color = var)) +
geom_point() +
ggtitle(glue::glue("{var}"))
}
my_plot(species)
```
Le problème vient de l'évaluation de `var` dans l'appel à `my_plot()`: R cherche `species` dans l'environnement parent de la fonction plutôt que dans le contexte du data.frame `penguins`.
On peut remédier au problème à l'aide de l'opérateur `{{` qui permet de remplacer `var` par `species` dans l'appel à `ggplot` via le mécanisme de `quosure`, détaillé dans la [vignette}(https://rlang.r-lib.org/reference/nse-defuse.html) sur l'évaluation non-standard de rlang.
```{r}
## This works
my_plot <- function(var) {
ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm, color = {{ var }})) +
geom_point()
}
my_plot(species)
```
À noter qu'on obtient l'équivalent de `{{ var }}` en combinant `enquo` (conversion en "quosure" = expression + environnement) et `!!` (évaluation sauce "tidy") :
```{r, results='hide'}
## This works
my_plot <- function(var) {
ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm, color = !!enquo(var))) +
geom_point()
}
my_plot(species)
```
Si on veut afficher le titre de façon programmatique, il faut le protéger avec `ensym()` (conversion en symbole de l'expression passée en argument à une fonction) pour qu'il puisse être interprété comme une chaîne de caractères dans l'expression `glue::glue()`.
```{r}
## This works
my_plot <- function(var) {
ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm, color = {{ var }})) +
geom_point() +
ggtitle(glue::glue("Variable = {ensym(var)}"))
}
my_plot(species)
```
Attention, dans cet example, l'argument de `my_plot()` est une expression. On ne peut donc pas itérer sur les colonnes catégorielles de `penguins` stockées dans un vecteur de `"character"`: ces dernières ne sont pas interprétées correctement...
```{r}
cat_vars <- names(penguins)[map_lgl(penguins, is.factor)] ## [1] "species" "island" "sex"
map(cat_vars, my_plot) %>% cowplot::plot_grid(plotlist = .)
```
Pour que `my_plot()` accepte des chaînes de caractères, il faut utiliser le pronom spécial `.data` pour préciser que la variable doit être cherchée dans le tableau `penguins`.
```{r}
## This works
my_plot <- function(var) {
ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm, color = .data[[var]] )) +
geom_point() +
ggtitle(glue::glue("Variable = {var}"))
}
my_plot("species")
```
```{r}
map(cat_vars, my_plot) %>% cowplot::plot_grid(plotlist = .)
```
## Comparaisons entre `.env` et `.data`.
Essayons de ne représenter qu'une partie du jeu de données, par exemple les manchots Adelie
```{r}
ggplot(penguins %>% filter(species == "Adelie"),
aes(x = bill_length_mm, y = bill_depth_mm, color = island)) +
geom_point() +
ggtitle("Penguins of species Adelie, colored by island")
```
On peut écrire une fonction qui produit ce graphique pour n'importe quelle espèce de manchot:
```{r}
## This seems to works
my_plot <- function(species) {
ggplot(penguins %>% filter(species == species),
aes(x = bill_length_mm, y = bill_depth_mm, color = island )) +
geom_point() +
ggtitle(glue::glue("Penguins of species {species}, colored by island"))
}
```
Mais ça ne marche pas...
```{r}
my_plot("Adelie")
```
Il faut être rigoureux et distinguer la _data variable_ `species` de la valeur de l'_argument_ `species`. On peut renommer l'argument en `.species` mais c'est désagréable. Une solution élégante consiste à utiliser le pronom `.env` pour distinguer les deux symboles.
```{r}
## This works
my_plot <- function(species) {
ggplot(penguins %>% filter(species == .env$species),
aes(x = bill_length_mm, y = bill_depth_mm, color = island )) +
geom_point() +
ggtitle(glue::glue("Penguins of species {species}, colored by island"))
}
```
```{r}
my_plot("Adelie")
```
On peut alors itérer sur les espèces de manchots
```{r}
unique(penguins$species) %>% map(my_plot) %>% cowplot::plot_grid(plotlist = .)
```
On peut évidemment combiner la sélection d'une espèce avec le coloriage par une variable catégorielle.
```{r}
my_plot <- function(species, color) {
ggplot(penguins %>% filter(species == .env$species),
aes(x = bill_length_mm, y = bill_depth_mm, color = .data[[color]])) +
geom_point() +
ggtitle(glue::glue("Penguins of species {species}, colored by {color}"))
}
my_plot("Adelie", "sex")
```
## Passage d'arguments par promesse versus par nom
Les versions `my_plot(var = species)` et `my_plot(var = "species")` diffèrent dans la nature de l'argument: une expression dans le premier cas, une chaîne de caractères dans le deuxième. La première version est plus souple.
```{r}
my_plot_char <- function(x, y) {
ggplot(penguins,
aes(x = .data[[x]], y = .data[[y]], color = species)) +
geom_point()
}
my_plot_char(x = "bill_length_mm", y = "bill_depth_mm")
```
```{r}
my_plot_expr <- function(x, y) {
ggplot(penguins,
aes(x = {{x}}, y = {{y}}, color = species)) +
geom_point()
}
my_plot_expr(x = bill_length_mm, y = bill_depth_mm)
```
Contrairement à `my_plot_char()`, `my_plot_expr()` peut être utilisée avec des expressions, _i.e._ des transformations des variables de `penguins` calculées à la volée:
```{r}
my_plot_expr(x = bill_length_mm - bill_depth_mm, y = bill_length_mm + bill_depth_mm)
```
## Utiliser des chaînes de caractères dans fonctions du tidyverse
On peut avoir envie d'itérer sur des variables stockées dans vecteurs de type `character`. C'est facile avec `my_plot_char()` mais légèrement plus difficile avec `my_plot_expr()`
```{r}
var_x <- "bill_length_mm"
var_y <- "bill_depth_mm"
## Works fine
my_plot_char(var_x, var_y)
```
```{r error = TRUE}
## Does not work as intended
my_plot_expr(var_x, var_y)
```
On peut néanmoins s'en sortir à l'aide du pronom `.data` précédemment présenté
```{r error = TRUE}
## Works as intended
my_plot_expr(.data[[var_x]], .data[[var_y]])
```
Et itérer sur des tableaux
```{r}
cont_vars <- c("bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g")
df <- tidyr::crossing(x = cont_vars, y = cont_vars)
plot_list <- map2(.x = df$x, .y = df$y, ~ my_plot_expr(.data[[.x]], .data[[.y]]) + theme(legend.position = "none"))
cowplot::plot_grid(plotlist = plot_list)
```
## Utilisation de `:=`
On peut évidemment utiliser l'opérateur `{{` d'autres verbes du tidyverse:
```{r}
summarize_penguins <- function(filter_condition, group, summary_var, summary_fun, ...) {
penguins %>%
filter({{ filter_condition }}) %>%
group_by({{ group }}) %>%
summarize(my_summary = summary_fun({{summary_var}}, ...))
}
```
```{r}
summarize_penguins(species == "Adelie", island, bill_depth_mm, mean, na.rm = TRUE)
```
Mais le fait de devoir spécifier dès le départ le nom de colonne est désagréable et peu informatif. L'opérateur `:=` permet de créer des variables *nommées de façon dynamique* (à partir des arguments de la fonction) dans des verbes du tidyverse. Il faut utiliser la syntaxe de `glue::glue()` pour construire les nouveaux noms de variables à la volée.
```{r}
summarize_penguins <- function(filter_condition, group, summary_var, summary_fun, ...) {
penguins %>%
filter({{ filter_condition }}) %>%
group_by({{ group }}) %>%
summarize("{{summary_var}}" := summary_fun({{summary_var}}, ...))
}
```
```{r}
summarize_penguins(species == "Adelie", island, bill_depth_mm, mean, na.rm = TRUE)
```
On peut évidemment, toujours à l'aide de la syntaxe `glue::glue()`, construire des noms complexes qui intègrent à la fois le nom de variable et le nom de la statistique descriptive:
```{r}
summarize_penguins <- function(filter_condition, group, summary_var, summary_fun, ...) {
penguins %>%
filter({{ filter_condition }}) %>%
group_by({{ group }}) %>%
summarize("{{summary_var}}_{{summary_fun}}" := summary_fun({{summary_var}}, ...))
}
```
```{r}
summarize_penguins(species == "Adelie", island, bill_depth_mm, mean, na.rm = TRUE)
```
## Utilisation de tidyselect
## Un exemple sur un jeu de données de pêche
Le jeu de données VMS_data contient les données spatialisées des captures (`LE_KG`), des efforts (`HF`) et des indices d'abondance (`CPUE`) par navire (`aggreg_level`) des chalutiers ciblant les espèces démersales (`OTB_DEF`) dans le golfe de Gascogne. La donnée est agrégée à l'échelle des cellules (`layer`) d'une grille de résolution 0.05°. Les captures et les indices d'abondance pour chaque espèce sont en colonne.
```{r}
load("data/VMS_data.RData")
VMS_data <- as_tibble(VMS_data)
```
On peut chercher à créer une fonction représentant les captures de deux navires pour 3 espèces (ici la sole, le merlu, la langoustine). La fonction suivante utilise les différentes commandes vues précédemment (`{{}}`, `.Data`, `.env$`, `:=`).
```{r fig.width = 9, fig.height=3}
plot_VMS_data <- function(data,species,unit){
data %>%
dplyr::select(aggreg_level:lati,contains(paste0(.env$unit,"_",.env$species))) %>% # aller chercher l'unité et les espèces dans l'environnement
pivot_longer(contains(paste0(.env$unit,"_",.env$species)),names_to = "species",values_to = {{unit}}) %>% # passer les colonnes de valeurs en une seule colonne
mutate(species = str_remove(species, paste0(.env$unit,"_"))) %>% # modifier le nom de la colonne species
group_by(layer,long,lati,species) %>% # moyenner les valeurs par espèce par cellule
summarise("{unit}" := mean(.data[[unit]])) %>%
ggplot() + # ploter les données
geom_point(aes(x=long,y=lati,col = log(.data[[unit]]+1)),size = 0.5,shape=15) +
scale_color_gradient(low = "white", high = "red")+xlab("")+ylab("")+
theme(panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "skyblue"),
panel.spacing.x = unit(6, "mm"))+
facet_wrap(.~species) # pour représenter la côte regarder le package `rnaturalearth`
}
data_2 <- VMS_data
species_2 <- c("Solea_solea","Merluccius_merluccius","Nephrops_norvegicus")
unit_2 <-"CPUE"
plot_VMS_data(data_2,species_2,unit_2)
```