Introduction
Quand on veut appliquer une meme operation un grand nombre de fois, on cree souvent une fonction qui realise cette operation et on l’applique de facon repetee avec une boucle. Le package dplyr et sa syntaxe sans guillemets peut se retrouver en difficulte dans cette situation, et plusieurs evolutions du package ont tente d’y remedier :
- les fonctions comme mutate_ desormais obsoletes
- les fonctions mutate_if, mutate_at, … qui n’acceptent que des fonctions d’une variable, impossible donc de calculer des ratios pour l’evolution d’un indicateur d’une annee a l’autre
- les “!!”, “enquo” et autres horreurs du meme genre qui remplacent le tout aussi horrible “eval(parse(paste(…)))” de R base
Dans l’article https://www.tidyverse.org/articles/2019/06/rlang-0-4-0/, Rstudio fait amende honorable et choisit de revenir a plus de simplicite et d’efficacite avec le nouvel operateur “{{ }}” (prononcer curly-curly). Et l’article plus recent https://www.tidyverse.org/blog/2020/02/glue-strings-and-tidy-eval/ complete le paysage avec l’operateur {}.
Pour resumer ce qu’on va voir dans les exemples qui suivent :
- La syntaxe {{}} permet de creer des fonctions parametrees par des noms de colonnes sans guillemets, comme avec les fonctions usuelles de dplyr
- La syntaxe {}:= permet de nommer un champ calcule a partir d’un nom entre guillemets
- La syntaxe .data[[]] permet de manipuler des colonnes a partir de leur nom entre guillemets
Fonctions “a la dplyr” avec {{}}
Le code dplyr classique ne fonctionne pas bien a l’interieur d’une fonction, mais en entourant tous les parametres avec 2 accolades on regle la plupart des difficultes :
library("dplyr")
<- function(data, var, by) {
max_by %>% group_by({{ by }}) %>% summarise(maximum = max({{ var }}, na.rm = TRUE))
data
}
%>% max_by(height)
starwars
%>% max_by(height, by = gender) starwars
# A tibble: 1 x 1
maximum
<int>
1 264
# A tibble: 3 x 2
gender maximum
<chr> <int>
1 feminine 213
2 masculine 264
3 <NA> 183
On peut passer des formules de calculs si on veut :
<- function(data, ..., by) {
summarise_by %>% group_by({{ by }}) %>% summarise(...)
data
}
%>% summarise_by(average = mean(height, na.rm = TRUE),
starwars maximum = max(height, na.rm = TRUE),
by = gender)
# A tibble: 3 x 3
gender average maximum
<chr> <dbl> <int>
1 feminine 165. 213
2 masculine 177. 264
3 <NA> 181. 183
Et on peut utiliser les {} simples suivis de := pour nommer le champ calcule avec une chaine de caracteres :
<- function(data, var, by, nom = "mon_champ") {
max_by_nom %>% group_by({{ by }}) %>% summarise("{nom}" := max({{ var }}, na.rm = TRUE))
data
}
%>% max_by_nom(height, by = gender)
starwars
%>% max_by_nom(height, by = gender, nom = "ma_variable") starwars
# A tibble: 3 x 2
gender mon_champ
<chr> <int>
1 feminine 213
2 masculine 264
3 <NA> 183
# A tibble: 3 x 2
gender ma_variable
<chr> <int>
1 feminine 213
2 masculine 264
3 <NA> 183
Exemples de fonctions et boucles avec les noms entre guillemets
On utilise ici la notation classique de R base .data[[ma_variable]] dans les fonctions de dplyr.
<- function(data, var_char, by_char) {
max_by_char %>% group_by(.data[[by_char]]) %>% summarise(maximum = max(.data[[var_char]], na.rm = TRUE))
data
}
%>% max_by_char("height", by = "gender") starwars
# A tibble: 3 x 2
gender maximum
<chr> <int>
1 feminine 213
2 masculine 264
3 <NA> 183
Et pour finir on realise une boucle sur 2 champs.
# fonction qui calcule un ratio
<- function(data, num_char, denom_char, ratio_char) {
ratio_char %>% mutate("{ratio_char}" := .data[[num_char]] / .data[[denom_char]])
data
}
= c("Sepal.Width", "Sepal.Length")
vect_numerateur = c("Petal.Width", "Petal.Length")
vect_denom = c("ratio_W", "ratio_L")
vect_ratio
= iris %>% filter(Sepal.Length > 7.2) %>% select(-Species)
dtf
for (i in 1:length(vect_ratio)) {
= dtf %>% ratio_char(vect_numerateur[i], vect_denom[i], vect_ratio[i])
dtf
}
dtf
Sepal.Length Sepal.Width Petal.Length Petal.Width ratio_W ratio_L
1 7.6 3.0 6.6 2.1 1.428571 1.151515
2 7.3 2.9 6.3 1.8 1.611111 1.158730
3 7.7 3.8 6.7 2.2 1.727273 1.149254
4 7.7 2.6 6.9 2.3 1.130435 1.115942
5 7.7 2.8 6.7 2.0 1.400000 1.149254
6 7.4 2.8 6.1 1.9 1.473684 1.213115
7 7.9 3.8 6.4 2.0 1.900000 1.234375
8 7.7 3.0 6.1 2.3 1.304348 1.262295