library(tidyverse)
library(questionr)
data(hdv2003)
data(rp2018)
15 dplyr
avancé
L’extension dplyr
a déjà été présentée Chapitre 10. On va voir ici comment aller un peu plus loin dans l’utilisation du package, notamment en utilisant nos propres fonctions et en appliquant des transformations à des ensembles de colonnes.
On commence par charger les extensions du tidyverse ainsi que les jeux de données hdv2003
et rp2018
de l’extension questionr
.
15.1 Appliquer ses propres fonctions
15.1.1 Exemple avec mutate
Soit le jeu de données fictif suivant, dont chaque ligne représente un individu pour lequel on dispose de sa PCS, celle de ses parents, son âge et celui de ses enfants.
<- tribble(
df ~id, ~pcs, ~pcs_pere, ~pcs_mere, ~age, ~`age enf1`, ~`age enf2`, ~`age enf3`,
1, "5", "5", "6", 25, 2, NA, NA,
2, "3", "3", "2", 45, 12, 8, 2,
3, "4", "2", "5", 29, 7, NA, NA,
4, "2", "1", "4", 32, 6, 3, NA,
5, "1", "4", "3", 65, 39, 36, 28,
6, "6", "6", "6", 51, 18, 12, NA,
7, "5", "4", "6", 37, 8, 4, 1,
8, "3", "3", "1", 42, 16, 10, 5
)
df#> # A tibble: 8 × 8
#> id pcs pcs_pere pcs_mere age `age enf1` `age enf2` `age enf3`
#> <dbl> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 1 5 5 6 25 2 NA NA
#> 2 2 3 3 2 45 12 8 2
#> 3 3 4 2 5 29 7 NA NA
#> 4 4 2 1 4 32 6 3 NA
#> 5 5 1 4 3 65 39 36 28
#> 6 6 6 6 6 51 18 12 NA
#> 7 7 5 4 6 37 8 4 1
#> 8 8 3 3 1 42 16 10 5
Dans ce tableau les PCS sont indiquées sous forme de codes : il serait plus lisible de les avoir sous forme d’intitulés de catégorie socio-professionnelle. On a vu Section 9.3.2 qu’on peut effectuer ce recodage avec la fonction fct_recode()
de l’extension forcats
.
%>%
df mutate(
pcs = fct_recode(pcs,
"Agriculteur" = "1",
"Indépendant" = "2",
"Cadre" = "3",
"Intermédiaire" = "4",
"Employé" = "5",
"Ouvrier" = "6"
) )
Plutôt que d’intégrer le code du recodage directement dans le mutate()
, on peut l’extraire en créant une fonction.
<- function(v) {
recode_pcs fct_recode(v,
"Agriculteur" = "1",
"Indépendant" = "2",
"Cadre" = "3",
"Intermédiaire" = "4",
"Employé" = "5",
"Ouvrier" = "6"
) }
On peut dès lors simplifier notre mutate
en appelant notre nouvelle fonction.
%>%
df mutate(pcs = recode_pcs(pcs))
Premier avantage : on gagne en lisibilité. On a déplacé le code d’une opération spécifique dans une fonction avec un nom “parlant”, ce qui permet de savoir facilement à quoi elle sert. Et on a simplifié notre mutate
qui est désormais plus lisible parce qu’il fait apparaître la logique de nos opérations (on veut recoder les PCS) sans en inclure les détails.
Le deuxième avantage évident, comme pour toute fonction, est qu’on peut la réutiliser pour appliquer ce recodage à plusieurs variables. Ainsi, si on veut recoder de la même manière pcs
et pcs_mere
, il suffit de faire :
%>%
df mutate(
pcs = recode_pcs(pcs),
pcs_mere = recode_pcs(pcs_mere)
)
Le code est plus court, plus lisible, on évite les erreurs de copier/coller, et si on souhaite modifier le recodage on n’a à intervenir qu’à un seul endroit en modifiant notre fonction.
15.1.2 Exemple avec summarise
Autre exemple, cette fois sur le jeu de données rp2018
. Imaginons qu’on souhaite calculer, pour chaque région, le pourcentage de communes dont le nom se termine par une série de caractères donnée : par exemple, le pourcentage de communes dont le nom se termine par “ac”.
Comme il ne s’agit pas forcément d’une question triviale, on va décomposer le problème et rappeler (comme vu Section 11.6) que la fonction str_detect()
de l’extension stringr
permet de détecter quels éléments d’un vecteur de chaînes de caractères correspondent à une expression régulière. Ainsi, si on veut détecter si un nom de commune (variable rp2018$commune
) se termine par "ac"
, on utilisera :
str_detect(rp2018$commune, "ac$")
Le symbole \$ dans l’expression régulière "ac$"
représente la fin de la chaîne de caractères. Il permet de s’assurer qu’on ne détecte que les noms de communes se terminant par “ac” (comme “Figeac”), et pas ceux contenant “ac” à un autre endroit (comme “Arcachon”).
Si on veut compter le nombre de communes pour lesquelles on a détecté une terminaison en “ac”, on peut utiliser un idiome courant en R et appliquer la fonction sum()
au résultat précédent : les TRUE
du résultat du str_detect
sont alors convertis en 1, les FALSE
en 0, et le sum()
renverra donc le nombre de TRUE
.
sum(str_detect(rp2018$commune, "ac$"))
#> [1] 131
Si on souhaite convertir ce résultat en pourcentage, il faut qu’on divise par le nombre total de communes, et qu’on multiplie par 100.
sum(str_detect(rp2018$commune, "ac$")) / length(rp2018$commune) * 100
#> [1] 2.418313
On crée une fonction nommée prop_suffixe
qui a pour objectif d’effectuer ce calcul. Elle prend en entrée deux arguments : un vecteur de chaînes de caractères et un suffixe à détecter, et retourne le pourcentage d’éléments du vecteur se terminant par le suffixe. On rajoute nous-même le “$” à la fin du suffixe en question pour faciliter l’usage de la fonction.
Le résultat final est le suivant :
<- function(v, suffixe) {
prop_suffixe # On ajoute $ à la fin du suffixe pour capturer uniquement en fin de chaîne
<- paste0(suffixe, "$")
suffixe # Détection du suffixe
<- sum(str_detect(v, suffixe))
nb_detect # On retourne le pourcentage
/ length(v) * 100
nb_detect }
On peut utiliser notre fonction de la manière suivante :
prop_suffixe(rp2018$commune, "ac")
#> [1] 2.418313
On a donc dans notre jeu de données 2.42% de communes dont le nom se termine par “ac”1.
Si maintenant on souhaite calculer ce pourcentage pour toutes les régions françaises, il suffit d’appeler notre fonction dans un summarise
:
%>%
rp2018 group_by(region) %>%
summarise(prop_ac = prop_suffixe(commune, "ac")) %>%
arrange(desc(prop_ac))
#> # A tibble: 17 × 2
#> region prop_ac
#> <chr> <dbl>
#> 1 Nouvelle-Aquitaine 10.8
#> 2 Occitanie 4.39
#> 3 Bretagne 4.26
#> 4 Auvergne-Rhône-Alpes 2.25
#> 5 Pays de la Loire 2.20
#> 6 Bourgogne-Franche-Comté 0.995
#> 7 Provence-Alpes-Côte d'Azur 0.581
#> 8 Normandie 0.347
#> 9 Hauts-de-France 0.185
#> 10 Centre-Val de Loire 0
#> 11 Corse 0
#> 12 Grand Est 0
#> 13 Guadeloupe 0
#> 14 Guyane 0
#> 15 La Réunion 0
#> 16 Martinique 0
#> 17 Île-de-France 0
L’avantage d’avoir créé une fonction pour effectuer cette opération et qu’on peut du coup très facilement faire le même calcul en faisant varier le suffixe recherché.
%>%
rp2018 group_by(region) %>%
summarise(prop_ac = prop_suffixe(commune, "ieu")) %>%
arrange(desc(prop_ac))
#> # A tibble: 17 × 2
#> region prop_ac
#> <chr> <dbl>
#> 1 Auvergne-Rhône-Alpes 2.79
#> 2 Occitanie 0.763
#> 3 Bourgogne-Franche-Comté 0.498
#> 4 Pays de la Loire 0.489
#> 5 Normandie 0.347
#> 6 Nouvelle-Aquitaine 0.187
#> 7 Bretagne 0
#> 8 Centre-Val de Loire 0
#> 9 Corse 0
#> 10 Grand Est 0
#> 11 Guadeloupe 0
#> 12 Guyane 0
#> 13 Hauts-de-France 0
#> 14 La Réunion 0
#> 15 Martinique 0
#> 16 Provence-Alpes-Côte d'Azur 0
#> 17 Île-de-France 0
En créant une fonction plutôt qu’en mettant notre code directement dans le summarise
on a un script plus lisible, plus facile à maintenir, et des fonctionnalités facilement réutilisables.
15.1.3 Exemple avec rename_with
On a vu Section 10.2.3 que dplyr
propose la fonction rename()
pour renommer des colonnes d’un tableau de données. On peut l’utiliser par exemple pour remplacer un espace par un _
dans le nom d’une variable de df
.
%>% rename("age_enf1" = "age enf1") df
Supposons maintenant qu’on souhaite appliquer la même transformation à l’ensemble des variables de df
. Une solution pour cela est d’utiliser la fonction rename_with()
, toujours fournie par dplyr
, qui prend en argument non pas une correspondance "nouveau nom" = "ancien nom"
mais une fonction qui sera appliquée à l’ensemble des noms de colonnes.
Par exemple, si on souhaite convertir tous les noms de colonnes en majuscules, on peut passer comme argument la fonction str_to_upper()
de stringr
.
%>% rename_with(str_to_upper)
df #> # A tibble: 8 × 8
#> ID PCS PCS_PERE PCS_MERE AGE `AGE ENF1` `AGE ENF2` `AGE ENF3`
#> <dbl> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 1 5 5 6 25 2 NA NA
#> 2 2 3 3 2 45 12 8 2
#> 3 3 4 2 5 29 7 NA NA
#> 4 4 2 1 4 32 6 3 NA
#> 5 5 1 4 3 65 39 36 28
#> 6 6 6 6 6 51 18 12 NA
#> 7 7 5 4 6 37 8 4 1
#> 8 8 3 3 1 42 16 10 5
Pour remplacer les espaces par des _
, on va d’abord créer une fonction ad hoc qui utilise str_replace_all
.
<- function(v) {
remplace_espaces str_replace_all(v, " ", "_")
}
Dès lors, on peut appliquer cette fonction à l’ensemble de nos noms de variables :
%>% rename_with(remplace_espaces)
df #> # A tibble: 8 × 8
#> id pcs pcs_pere pcs_mere age age_enf1 age_enf2 age_enf3
#> <dbl> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 1 5 5 6 25 2 NA NA
#> 2 2 3 3 2 45 12 8 2
#> 3 3 4 2 5 29 7 NA NA
#> 4 4 2 1 4 32 6 3 NA
#> 5 5 1 4 3 65 39 36 28
#> 6 6 6 6 6 51 18 12 NA
#> 7 7 5 4 6 37 8 4 1
#> 8 8 3 3 1 42 16 10 5
Certain.es lectrices et lecteurs attentives auront peut-être noté que le même résultat peut être obtenu en utilisant remplace_espaces()
avec la fonction names()
.
names(df) <- remplace_espaces(names(df))
L’avantage de rename_with()
c’est qu’elle peut s’intégrer dans un pipeline de dplyr, et, comme nous allons le voir un peu plus loin, permet si nécessaire de n’appliquer cette transformation qu’à certaines colonnes seulement.
15.2 across()
: appliquer des fonctions à plusieurs colonnes
15.2.1 Appliquer une fonction à plusieurs colonnes
On a défini précédemment une fonction qui recode les modalités d’une variable PCS et on a vu comment appliquer ce recodage à deux variables de df
.
<- function(v) {
recode_pcs fct_recode(v,
"Agriculteur" = "1",
"Indépendant" = "2",
"Cadre" = "3",
"Intermédiaire" = "4",
"Employé" = "5",
"Ouvrier" = "6"
)
}
%>%
df mutate(
pcs = recode_pcs(pcs),
pcs_mere = recode_pcs(pcs_mere)
)
Supposons qu’on souhaite appliquer ce recodage à toutes les variables PCS de notre tableau. On pourrait évidemment créer autant de lignes que nécessaires dans notre mutate
, mais on peut aussi utiliser la fonction across()
de dplyr
, qui facilite justement ce type d’opérations.
across()
prend deux arguments principaux :
- la définition d’un ensemble de colonnes de notre tableau de données
- une ou plusieurs fonctions à appliquer aux colonnes sélectionnées
Il existe de nombreuses manières de définir les colonnes qu’on souhaite transformer : celles-ci sont en fait les mêmes que celles offertes par des verbes de dplyr
comme select()
.
Une première possibilité est d’utiliser c()
en lui passant les noms des variables (on notera qu’on n’est pas obligés de mettre ces noms entre guillemets).
%>%
df mutate(
across(
c(pcs, pcs_mere),
recode_pcs
)
)#> # A tibble: 8 × 8
#> id pcs pcs_pere pcs_mere age `age enf1` `age enf2` `age enf3`
#> <dbl> <fct> <chr> <fct> <dbl> <dbl> <dbl> <dbl>
#> 1 1 Employé 5 Ouvrier 25 2 NA NA
#> 2 2 Cadre 3 Indépenda… 45 12 8 2
#> 3 3 Intermédiaire 2 Employé 29 7 NA NA
#> 4 4 Indépendant 1 Intermédi… 32 6 3 NA
#> 5 5 Agriculteur 4 Cadre 65 39 36 28
#> 6 6 Ouvrier 6 Ouvrier 51 18 12 NA
#> 7 7 Employé 4 Ouvrier 37 8 4 1
#> 8 8 Cadre 3 Agriculte… 42 16 10 5
Une autre possibilité est d’utiliser :
, qui permet de définir une plage de colonnes en lui indiquant la colonne de début et la colonne de fin. Ainsi dans l’exemple suivant notre recodage est appliqué à toutes les colonnes situées entre pcs
et pcs_mere
(incluses).
%>%
df mutate(
across(
:pcs_pere,
pcs
recode_pcs
)
)#> # A tibble: 8 × 8
#> id pcs pcs_pere pcs_mere age `age enf1` `age enf2` `age enf3`
#> <dbl> <fct> <fct> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 1 Employé Employé 6 25 2 NA NA
#> 2 2 Cadre Cadre 2 45 12 8 2
#> 3 3 Intermédiaire Indépenda… 5 29 7 NA NA
#> 4 4 Indépendant Agriculte… 4 32 6 3 NA
#> 5 5 Agriculteur Intermédi… 3 65 39 36 28
#> 6 6 Ouvrier Ouvrier 6 51 18 12 NA
#> 7 7 Employé Intermédi… 6 37 8 4 1
#> 8 8 Cadre Cadre 1 42 16 10 5
On peut aussi sélectionner les variables via leurs noms. On peut ainsi choisir les variables qui commencent par une certaine chaîne de caractères via la fonction starts_with()
, celles qui se terminent ou qui contiennent certains caractères avec ends_with()
et contains()
.
%>%
df mutate(
across(
starts_with("pcs"),
recode_pcs
)
)#> # A tibble: 8 × 8
#> id pcs pcs_pere pcs_mere age `age enf1` `age enf2` `age enf3`
#> <dbl> <fct> <fct> <fct> <dbl> <dbl> <dbl> <dbl>
#> 1 1 Employé Employé Ouvrier 25 2 NA NA
#> 2 2 Cadre Cadre Indépen… 45 12 8 2
#> 3 3 Intermédiaire Indépenda… Employé 29 7 NA NA
#> 4 4 Indépendant Agriculte… Intermé… 32 6 3 NA
#> 5 5 Agriculteur Intermédi… Cadre 65 39 36 28
#> 6 6 Ouvrier Ouvrier Ouvrier 51 18 12 NA
#> 7 7 Employé Intermédi… Ouvrier 37 8 4 1
#> 8 8 Cadre Cadre Agricul… 42 16 10 5
across()
fonctionne dans un mutate
, mais aussi dans un summarise
. Dans l’exemple suivant, on calcule la moyenne de toutes les variables qui contiennent “enf”.
%>%
df summarise(
across(
contains("enf"),
mean
)
)#> # A tibble: 1 × 3
#> `age enf1` `age enf2` `age enf3`
#> <dbl> <dbl> <dbl>
#> 1 13.5 NA NA
De manière similaire, la fonction num_range()
permet de sélectionner des colonnes ayant un préfixe commun suivi d’un indicateur numérique, comme x1
, x2
… Par exemple la syntaxe suivante sélectionnerait toutes les colonnes de Q01
à Q12
:
across(num_range("Q", 1:12, width = 2))
On peut également sélectionner des colonnes via une condition avec la fonction where()
. Celle-ci prend elle-même en argument une fonction qui doit renvoyer TRUE
ou FALSE
, et ne conserve que les colonnes qui correspondent à des TRUE
.
Dans l’exemple suivant, on applique la fonction mean
seulement aux colonnes de df
pour lesquelles la fonction is.numeric
renvoie TRUE
.
%>%
df summarise(
across(
where(is.numeric),
mean
)
)#> # A tibble: 1 × 5
#> id age `age enf1` `age enf2` `age enf3`
#> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 4.5 40.8 13.5 NA NA
Pour des conditions plus complexes, on doit parfois définir soi-même la fonction passée à where()
. Dans l’exemple suivant on calcule la moyenne uniquement pour les variables de df
qui sont numériques et n’ont pas de valeurs manquantes.
<- function(v) {
no_na is.numeric(v) && sum(is.na(v)) == 0
}
%>%
df summarise(
across(
where(no_na),
mean
)
)#> # A tibble: 1 × 3
#> id age `age enf1`
#> <dbl> <dbl> <dbl>
#> 1 4.5 40.8 13.5
Il est même possible, pour les cas les plus complexes, de combiner plusieurs sélections avec les opérateurs &
, |
et !
. L’exemple suivant applique la fonction mean()
à toutes les colonnes numériques de df
, sauf à la colonne id
.
%>%
df summarise(
across(
where(is.numeric) & !id,
mean
)
)#> # A tibble: 1 × 4
#> age `age enf1` `age enf2` `age enf3`
#> <dbl> <dbl> <dbl> <dbl>
#> 1 40.8 13.5 NA NA
Enfin, la fonction spéciale everything()
permet de sélectionner la totalité des colonnes d’un tableau. Dans l’exemple suivant, on applique n_distinct()
pour afficher le nombre de valeurs distinctes de toutes les variables de df
.
%>%
df summarise(
across(
everything(),
n_distinct
)
)#> # A tibble: 1 × 8
#> id pcs pcs_pere pcs_mere age `age enf1` `age enf2` `age enf3`
#> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 8 6 6 6 8 8 7 5
Ces différentes manières de sélectionner un ensemble de colonnes sont appelées tidy selection. Il y a encore d’autres possibilités de sélection, pour avoir un aperçu complet on pourra se référer à la page de documentation de la fonction select().
Une erreur de syntaxe fréquente est de mettre la sélection des colonnes dans l’appel à across()
, mais pas la fonction qu’on souhaite appliquer.
Ainsi le code suivant génèrera une erreur :
mutate(across(pcs:pcs_mere), recode_pcs)
Il faut bien penser à passer la fonction comme argument du across()
, donc à l’intérieur de ses parenthèses.
mutate(across(pcs:pcs_mere, recode_pcs))
15.2.2 Passer des arguments supplémentaires à la fonction appliquée
Par défaut, si on passe des arguments supplémentaires à across()
, ils seront automatiquement transmis comme arguments à la fonction appliquée.
Dans l’exemple vu précédemment, on appliquait mean()
à toutes les variables d’âge de df
. Or comme certaines colonnes ont des valeurs manquantes, leur résultat vaut NA
.
%>%
df summarise(
across(
starts_with("age"),
mean
) )
Si on préfère que mean()
soit appelée avec l’argument na.rm = TRUE
, on pourrait définir explicitement une fonction à part qui utilise cet argument :
<- function(x) {
mean_sans_na max(x, na.rm = TRUE)
}
%>%
df summarise(
across(
starts_with("age"),
mean_sans_na
) )
Mais on peut faire plus simple, car tout argument supplémentaire passé à across()
est transmis directement à la fonction appelée. Il est donc possible de faire :
%>%
df summarise(
across(
starts_with("age"),
max,na.rm = TRUE
)
)#> Warning: There was 1 warning in `summarise()`.
#> ℹ In argument: `across(starts_with("age"), max, na.rm = TRUE)`.
#> Caused by warning:
#> ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
#> Supply arguments directly to `.fns` through an anonymous function instead.
#>
#> # Previously
#> across(a:b, mean, na.rm = TRUE)
#>
#> # Now
#> across(a:b, \(x) mean(x, na.rm = TRUE))
#> # A tibble: 1 × 4
#> age `age enf1` `age enf2` `age enf3`
#> <dbl> <dbl> <dbl> <dbl>
#> 1 65 39 36 28
15.2.3 Noms des colonnes créées par un mutate
Par défaut, lorsqu’on utilise across()
dans un mutate
, les nouvelles colonnes portent le même nom que les colonnes d’origine, ce qui signifie que ces dernières sont “écrasées” par les nouvelles valeurs.
Ainsi dans l’exemple suivant, les valeurs d’origine des colonnes PCS ont été écrasées par le résultat du recodage.
%>%
df mutate(
across(
starts_with("pcs"),
recode_pcs
) )
Si on préfère créer de nouvelles colonnes, on doit indiquer la manière de les nommer en utilisant l’argument .names
de across()
. Celui prend comme valeur une chaîne de caractère dans laquelle le motif {.col}
sera remplacé par le nom de la colonne d’origine.
Ainsi, si on souhaite plutôt que les variables recodées soient stockées dans de nouvelles colonnes nommées avec le suffixe _rec
, on peut utiliser :
%>%
df mutate(
across(
starts_with("pcs"),
recode_pcs,.names = "{.col}_rec"
)
)#> # A tibble: 8 × 11
#> id pcs pcs_pere pcs_mere age `age enf1` `age enf2` `age enf3` pcs_rec
#> <dbl> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <fct>
#> 1 1 5 5 6 25 2 NA NA Employé
#> 2 2 3 3 2 45 12 8 2 Cadre
#> 3 3 4 2 5 29 7 NA NA Interméd…
#> 4 4 2 1 4 32 6 3 NA Indépend…
#> 5 5 1 4 3 65 39 36 28 Agricult…
#> 6 6 6 6 6 51 18 12 NA Ouvrier
#> 7 7 5 4 6 37 8 4 1 Employé
#> 8 8 3 3 1 42 16 10 5 Cadre
#> # ℹ 2 more variables: pcs_pere_rec <fct>, pcs_mere_rec <fct>
15.2.4 Appliquer plusieurs fonctions à plusieurs colonnes
across()
offre également la possibilité d’appliquer plusieurs fonctions à un ensemble de colonnes. Dans ce cas, plutôt que de lui passer une seule fonction comme deuxième argument, on lui passe une liste nommée de fonctions.
Le code suivant calcule le minimum et le maximum pour les variables d’âge de df
.
%>%
df summarise(
across(
starts_with("age"),
list(minimum = min, maximum = max)
)
)#> # A tibble: 1 × 8
#> age_minimum age_maximum `age enf1_minimum` `age enf1_maximum`
#> <dbl> <dbl> <dbl> <dbl>
#> 1 25 65 2 39
#> # ℹ 4 more variables: `age enf2_minimum` <dbl>, `age enf2_maximum` <dbl>,
#> # `age enf3_minimum` <dbl>, `age enf3_maximum` <dbl>
Par défaut les nouvelles variables sont nommées sous la forme {nom_variable}_{nom_fonction}
, mais on peut personnaliser cette règle en ajoutant un argument .names
à across()
. Cet argument est une chaîne de caractères dans laquelle {.col}
sera remplacé par le nom de la colonne courante, et {.fn}
par le nom de la fonction.
%>%
df summarise(
across(
starts_with("age"),
list(minimum = min, maximum = max),
.names = "{.fn}_{.col}"
)
)#> # A tibble: 1 × 8
#> minimum_age maximum_age `minimum_age enf1` `maximum_age enf1`
#> <dbl> <dbl> <dbl> <dbl>
#> 1 25 65 2 39
#> # ℹ 4 more variables: `minimum_age enf2` <dbl>, `maximum_age enf2` <dbl>,
#> # `minimum_age enf3` <dbl>, `maximum_age enf3` <dbl>
15.2.5 Renommer plusieurs colonnes avec une fonction
On a vu précédemment qu’on peut utiliser rename_with()
pour renommer les colonnes d’un tableau de données à l’aide d’une fonction.
<- function(v) {
remplace_espaces str_replace_all(v, " ", "_")
}
%>% rename_with(remplace_espaces) df
Par défaut, rename_with()
applique la fonction de renommage à l’ensemble des colonnes du tableau. Il est cependant possible de lui indiquer de ne renommer que certaines de ces colonnes. Pour cela, on peut lui ajouter un argument supplémentaire nommé .cols
, dont la syntaxe est exactement la même que pour across()
ou select()
.
Par exemple, le code suivant convertit en majuscule uniquement les noms des colonnes id
et poids
.
%>%
df rename_with(str_to_upper, .cols = starts_with("pcs"))
#> # A tibble: 8 × 8
#> id PCS PCS_PERE PCS_MERE age `age enf1` `age enf2` `age enf3`
#> <dbl> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 1 5 5 6 25 2 NA NA
#> 2 2 3 3 2 45 12 8 2
#> 3 3 4 2 5 29 7 NA NA
#> 4 4 2 1 4 32 6 3 NA
#> 5 5 1 4 3 65 39 36 28
#> 6 6 6 6 6 51 18 12 NA
#> 7 7 5 4 6 37 8 4 1
#> 8 8 3 3 1 42 16 10 5
Et le code suivant remplace les espaces par des _
uniquement pour les colonnes dont le nom contient “enf”.
%>%
df rename_with(remplace_espaces, .cols = contains("enf"))
#> # A tibble: 8 × 8
#> id pcs pcs_pere pcs_mere age age_enf1 age_enf2 age_enf3
#> <dbl> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 1 5 5 6 25 2 NA NA
#> 2 2 3 3 2 45 12 8 2
#> 3 3 4 2 5 29 7 NA NA
#> 4 4 2 1 4 32 6 3 NA
#> 5 5 1 4 3 65 39 36 28
#> 6 6 6 6 6 51 18 12 NA
#> 7 7 5 4 6 37 8 4 1
#> 8 8 3 3 1 42 16 10 5
15.3 Fonctions anonymes et syntaxes abrégées
Dans les sections précédentes, nous avons rencontré plusieurs fonctions, comme rename_with()
ou across()
, qui prennent une fonction en argument.
Par exemple, dans l’utilisation suivante de rename_with()
, on avait créé une fonction remplace_espaces()
.
<- function(v) {
remplace_espaces str_replace_all(v, " ", "_")
}
%>% rename_with(remplace_espaces) df
Le fait de créer une fonction à part pour une opération d’une seule ligne ne se justifie pas forcément, surtout si on n’utilise pas cette fonction ailleurs dans notre code. Dans, ce cas, on peut définir notre fonction directement dans l’appel à rename_with()
en utilisant une fonction anonyme, déjà introduites Section 14.4.2.
%>%
df rename_with(function(v) {
str_replace_all(v, " ", "_")
})
Cette notation est assez pratique et souvent utilisée pour les fonctions à usage unique, ne serait-ce que pour s’économiser le fait de devoir lui trouver un nom pertinent.
La syntaxe étant un peu lourde, il existe deux alternatives permettant une définition plus “compacte”.
- La première alternative est propre aux packages du tidyverse (notamment
dplyr
etpurrr
), et ne fonctionnera pas pour les fonctions n’appartenant pas à ces packages. Il s’agit d’utiliser une syntaxe de type “formule” : le corps de la formule contient les instructions de la fonction, et les arguments sont nommés.x
(ou.
) s’il n’y en a qu’un,.x
et.y
s’il y en a deux, et..1
,..2
, etc. s’ils sont plus nombreux. - La deuxième alternative est une syntaxe apparue avec la version 4.1 de R, qui permet de remplacer
function(...)
par le raccourci\(...)
.
Ainsi les définitions suivantes sont équivalents :
# Fonctionne partout et tout le temps
function(v) { v + 2 }
# Fonctionne uniquement dans les fonctions du tidyverse
~ { .x + 2 }
# Fonctionne uniquement à partir de R 4.1
+ 2 } \(v) { v
De même que les définitions suivantes :
function(v1, v2) {
<- v1 / v2
res round(res, 1)
}
~ {
<- .x / .y
res round(res, 1)
}
\(v1, v2) {<- v1 / v2
res round(res, 1)
}
Quand la fonction anonyme est constituée d’une seule instruction, on peut supprimer les accolades dans sa définition.
function(x) x + 2
~ .x + 2
+ 2 \(x) x
On pourra du coup, si on le souhaite, utiliser ces syntaxes compactes dans notre rename_with()
pour définir notre fonction anonyme.
%>%
df rename_with(~ str_replace_all(.x, " ", "_") )
%>%
df rename_with( \(x) str_replace_all(x, " ", "_") )
Cette syntaxe peut être utilisée partout où on peut passer une fonction comme argument et donc définir des fonctions anonymes. Dans cet exemple déjà vu précédemment, on passe la fonction no_na
comme argument de where()
.
<- function(v) {
no_na is.numeric(v) && sum(is.na(v)) == 0
}
%>%
df summarise(
across(
where(no_na),
mean
) )
On peut donc remplacer la fonction no_na
par une fonction anonyme définie directement dans le where()
.
%>%
df summarise(
across(
where(function(v) { is.numeric(v) && sum(is.na(v)) == 0 }),
mean
) )
Et du coup utiliser une des deux syntaxes “compactes”.
%>%
df summarise(
across(
where(~ is.numeric(.x) && sum(is.na(.x)) == 0),
mean
)
)
%>%
df summarise(
across(
where(\(v) is.numeric(v) && sum(is.na(v)) == 0),
mean
) )
15.4 rowwise()
et c_across()
: appliquer une transformation ligne par ligne
Soit le tableau de données suivant, qui contient des évaluations de restaurants sur quatre critères différents2 :
<- tribble(
restos ~nom, ~cuisine, ~decor, ~accueil, ~prix,
"La bonne fourchette", 4, 2, 5, 4,
"La choucroute de l'amer", 3, 3, 2, 3,
"L'Hair de rien", 1, 4, 4, 3,
"La blanquette de Vaulx", 5, 4, 4, 5,
)
restos#> # A tibble: 4 × 5
#> nom cuisine decor accueil prix
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 La bonne fourchette 4 2 5 4
#> 2 La choucroute de l'amer 3 3 2 3
#> 3 L'Hair de rien 1 4 4 3
#> 4 La blanquette de Vaulx 5 4 4 5
Imaginons qu’on souhaite faire la moyenne, pour chaque restaurant, des critères decor
et accueil
. On pourrait être tentés d’utiliser mean()
de la manière suivante :
%>%
restos mutate(
decor_accueil = mean(c(decor, accueil))
)#> # A tibble: 4 × 6
#> nom cuisine decor accueil prix decor_accueil
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 La bonne fourchette 4 2 5 4 3.5
#> 2 La choucroute de l'amer 3 3 2 3 3.5
#> 3 L'Hair de rien 1 4 4 3 3.5
#> 4 La blanquette de Vaulx 5 4 4 5 3.5
Si on regarde le résultat, on constate qu’il ne correspond pas à ce que l’on souhaite puisque toutes les valeurs sont les mêmes.
Que s’est-il passé ? En fait le mutate
s’est appliqué sur la totalité du tableau. Ceci signifie que dans mean(c(decor, accueil))
, les objets decor
et accueil
correspondent à la totalité des valeurs de chaque variable. On a donc concaténé ces deux vecteurs et calculé la moyenne, qui est du coup la même pour chaque ligne.
La valeur obtenue correspond aux résultat de :
mean(c(restos$decor, restos$accueil))
#> [1] 3.5
Ce que nous souhaitons ici, c’est calculer la moyenne non pas pour l’ensemble du tableau mais pour chaque ligne. Pour cela, on va utiliser la fonction rowwise()
: celle-ci est équivalente à un group_by()
qui créerait autant de groupes qu’il y a de lignes dans notre tableau.
%>% rowwise()
restos #> # A tibble: 4 × 5
#> # Rowwise:
#> nom cuisine decor accueil prix
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 La bonne fourchette 4 2 5 4
#> 2 La choucroute de l'amer 3 3 2 3
#> 3 L'Hair de rien 1 4 4 3
#> 4 La blanquette de Vaulx 5 4 4 5
Quant notre tableau est groupé via un rowwise()
, les opérations s’effectuent sur un tableau constitué uniquement de la ligne courante. Si on calcule la moyenne précédente, on obtient désormais le bon résultat.
%>%
restos rowwise() %>%
mutate(decor_accueil = mean(c(decor, accueil)))
#> # A tibble: 4 × 6
#> # Rowwise:
#> nom cuisine decor accueil prix decor_accueil
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 La bonne fourchette 4 2 5 4 3.5
#> 2 La choucroute de l'amer 3 3 2 3 2.5
#> 3 L'Hair de rien 1 4 4 3 4
#> 4 La blanquette de Vaulx 5 4 4 5 4
Supposons qu’on souhaite désormais calculer la moyenne de l’ensemble des critères. On peut évidemment reprendre le code précédent en saisissant toutes les variables concernées.
%>%
restos rowwise() %>%
mutate(moyenne = mean(c(decor, accueil, cuisine, prix)))
#> # A tibble: 4 × 6
#> # Rowwise:
#> nom cuisine decor accueil prix moyenne
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 La bonne fourchette 4 2 5 4 3.75
#> 2 La choucroute de l'amer 3 3 2 3 2.75
#> 3 L'Hair de rien 1 4 4 3 3
#> 4 La blanquette de Vaulx 5 4 4 5 4.5
Lister les variables de cette manière peut vite devenir pénible si le nombre de variables est important. C’est pourquoi dplyr
propose la fonction c_across()
: celle-ci permet de sélectionner des colonnes de la même manière que select()
ou across()
, et retourne un vecteur constitué des valeurs concaténées de ces colonnes.
L’exemple suivant calcule la moyenne de toutes les colonnes comprises entre decor
et prix
, en utilisant l’opérateur :
.
%>%
restos rowwise() %>%
mutate(
moyenne = mean(c_across(decor:prix))
)#> # A tibble: 4 × 6
#> # Rowwise:
#> nom cuisine decor accueil prix moyenne
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 La bonne fourchette 4 2 5 4 3.67
#> 2 La choucroute de l'amer 3 3 2 3 2.67
#> 3 L'Hair de rien 1 4 4 3 3.67
#> 4 La blanquette de Vaulx 5 4 4 5 4.33
Comme pour across()
ou select()
, on peut utiliser la fonction where()
pour calculer la moyenne sur toutes les colonnes numériques.
%>%
restos rowwise() %>%
mutate(
moyenne = mean(
c_across(where(is.numeric))
)
)#> # A tibble: 4 × 6
#> # Rowwise:
#> nom cuisine decor accueil prix moyenne
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 La bonne fourchette 4 2 5 4 3.75
#> 2 La choucroute de l'amer 3 3 2 3 2.75
#> 3 L'Hair de rien 1 4 4 3 3
#> 4 La blanquette de Vaulx 5 4 4 5 4.5
L’utilisation de rowwise()
et c_across()
est intéressante principalement quand il n’existe pas de fonction vectorisée pour la transformation qu’on souhaite appliquer. Quand elle existe, il est en général plus simple et plus rapide de l’utiliser.
Par exemple, pour trouver la valeur la plus élevée par restaurant, on pourrait être tenté d’utiliser le code suivant :
%>%
restos rowwise() %>%
summarise(note_max = max(c(decor, accueil)))
#> # A tibble: 4 × 1
#> note_max
#> <dbl>
#> 1 5
#> 2 3
#> 3 4
#> 4 4
Il est cependant plus lisible et plus efficace d’utiliser la fonction pmax
, qui a justement pour objectif de parcourir des vecteurs en parallèle et de ne conserver que la plus grande valeur.
%>%
restos summarise(note_max = pmax(decor, accueil))
#> Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
#> dplyr 1.1.0.
#> ℹ Please use `reframe()` instead.
#> ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
#> always returns an ungrouped data frame and adjust accordingly.
#> # A tibble: 4 × 1
#> note_max
#> <dbl>
#> 1 5
#> 2 3
#> 3 4
#> 4 4
Une des limites de pmax
cependant est qu’on ne peut pas l’utiliser avec c_across()
, et qu’on ne peut donc pas faire de sélection des colonnes : on est obligés de saisir leurs noms.
%>%
restos summarise(note_max = pmax(cuisine, decor, accueil, prix))
#> Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
#> dplyr 1.1.0.
#> ℹ Please use `reframe()` instead.
#> ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
#> always returns an ungrouped data frame and adjust accordingly.
#> # A tibble: 4 × 1
#> note_max
#> <dbl>
#> 1 5
#> 2 3
#> 3 4
#> 4 5
Dans certains cas, notamment lorsque les colonnes sont nombreuses ou qu’on ne les a pas identifiées à l’avance, on pourra donc utiliser rowwise()
et c_across()
même quand des alternatives vectorisées existent.
%>%
restos rowwise() %>%
summarise(
note_max = max(
c_across(where(is.numeric))
)
)#> # A tibble: 4 × 1
#> note_max
#> <dbl>
#> 1 5
#> 2 3
#> 3 4
#> 4 5
15.5 Ressources
La page d’aide de la fonction select (en anglais) liste toutes les possibilités offertes pour spécifier des ensembles de colonnes d’un tableau de données.
La vignette Column-wise operations de dplyr
(en anglais) présente en détail l’utilisation et les fonctionnalités de across()
.
La vignette Row-wise operations de dplyr
(toujours en anglais) présente de manière approfondie l’utilisation de rowwise()
et c_across()
pour opérer individuellement sur les lignes d’un tableau de données.
15.6 Exercices
Pour certains des exercices qui suivent on utilisera le jeu de données starwars
de dplyr
. On peut le charger avec les instructions suivantes :
library(dplyr)
data(starwars)
Le jeu de données contient les caractéristiques de 87 personnages présents dans les films : espèce, âge, planète d’origine, etc.
15.6.1 Appliquer ses propres fonctions
Exercice 1.1
Créer une fonction imc
qui prend en argument un vecteur taille
(en cm) et un vecteur poids
(en kg) et retourne les valeurs correspondantes de l’indice de masse corporelle, qui se calcule en divisant le poids en kilos par la taille en mètres au carré.
<- function(tailles, poids) {
imc <- tailles / 100
tailles_m / tailles_m ^ 2
poids }
Utiliser cette fonction pour ajouter une nouvelle variable imc
au tableau starwars
.
%>%
starwars mutate(imc = imc(height, mass))
À l’aide de group_by()
et summarise()
, utiliser à nouveau cette fonction pour calculer l’IMC moyen selon les valeurs de la variable species
.
%>%
starwars group_by(species) %>%
summarise(
imc = mean(imc(height, mass), na.rm = TRUE)
)
Exercice 1.2
Toujours dans le jeu de données starwars
, à l’aide d’un group_by()
et d’un summarise()
, calculer pour chaque valeur de la variable sex
la valeur de l’étendue de la variable height
du jeu de données starwars
, c’est-à-dire la différence entre sa valeur maximale et sa valeur minimale.
%>%
starwars group_by(sex) %>%
summarise(
etendue_taille = max(height, na.rm = TRUE) - min(height, na.rm = TRUE)
)
En partant du code précédent, créer une fonction etendue
qui prend en argument un vecteur et retourne la différence entre sa valeur maximale et sa valeur minimale. En utilisant cette fonction, calculer pour chaque valeur de sex
la valeur de l’étendue des variables height
et mass
.
<- function(v) {
etendue max(v, na.rm = TRUE) - min(v, na.rm = TRUE)
}%>%
starwars group_by(sex) %>%
summarise(
etendue_taille = etendue(height),
etendue_poids = etendue(mass)
)
Exercice 1.3
On a vu que la fonction suivante permet de calculer le pourcentage des éléments d’un vecteur de chaînes de caractères se terminant par un suffixe passé en argument.
<- function(v, suffixe) {
prop_suffixe # On ajoute $ à la fin du suffixe pour capturer uniquement en fin de chaîne
<- paste0(suffixe, "$")
suffixe # Détection du suffixe
<- sum(str_detect(v, suffixe))
nb_detect # On retourne le pourcentage
/ length(v) * 100
nb_detect }
Modifier cette fonction en une fonction prop_prefixe
qui retourne le pourcentage d’éléments commençant par un préfixe passé en argument. Indication : pour détecter si une chaîne commence par "ker"
, on utilise l’expression régulière "^ker"
.
<- function(v, prefixe) {
prop_prefixe # On ajoute $ à la fin du prefixe pour capturer uniquement en début de chaîne
<- paste0("^", prefixe)
prefixe # Détection du motif
<- sum(str_detect(v, prefixe))
nb_detect # On retourne le pourcentage
/ length(v) * 100
nb_detect }
Utiliser prop_prefixe
dans un summarise
appliqué à rp2018
pour calculer le pourcentage de communes commençant par “Saint” selon le département. Ordonner les résultats par pourcentage décroissant.
%>%
rp2018 group_by(departement) %>%
summarise(
prop_saint = prop_prefixe(commune, "Saint")
%>%
) arrange(desc(prop_saint))
Créer une fonction tab_prefixe
qui prend un seul argument prefixe
et renvoie le tableau obtenu à la question précédente pour le préfixe passé en argument. Tester avec tab_prefixe("Plou")
et tab_prefixe("Sch")
<- function(prefixe) {
tab_prefixe %>%
rp2018 group_by(departement) %>%
summarise(
prop = prop_prefixe(commune, prefixe)
%>%
) arrange(desc(prop))
}
Exercice 1.4
Le vecteur suivant donne, pour chacun des neuf principaux films de la saga Star Wars, la date à laquelle ils se déroulent dans l’univers de la saga.
c(
"I" = -32,
"II" = -22,
"III" = -19,
"IV" = 0,
"V" = 3,
"VI" = 4,
"VII" = 34,
"VIII" = 34,
"IX" = 35
)
Dans le jeu de données starwars
, la variable birth_year
indique l’année de naissance du personnage en “années avant l’an zéro” (une valeur de 19 signifie donc une année de naissance de -19).
Créer une fonction age_film
qui prend en entrée un vecteur d’années de naissance au même format que birth_year
ainsi que l’identifiant d’un film, et calcule les âges à la date du film.
Vérifier avec :
age_film(starwars$birth_year, "IV")
#> [1] 19.0 112.0 33.0 41.9 19.0 52.0 47.0 NA 24.0 57.0 41.9 64.0
#> [13] 200.0 29.0 44.0 600.0 21.0 NA 896.0 82.0 31.5 15.0 53.0 31.0
#> [25] 37.0 41.0 48.0 NA 8.0 NA 92.0 NA 91.0 46.0 52.0 NA
#> [37] NA NA NA NA 62.0 72.0 54.0 NA 48.0 NA NA NA
#> [49] NA 72.0 92.0 NA NA NA NA NA 22.0 NA NA NA
#> [61] 82.0 NA 58.0 40.0 NA 102.0 67.0 66.0 NA NA NA NA
#> [73] NA NA NA NA NA NA NA NA NA NA NA NA
#> [85] NA NA NA
<- function(annees, film) {
age_film <- c(
annees_films "I" = -32,
"II" = -22,
"III" = -19,
"IV" = 0,
"V" = 3,
"VI" = 4,
"VII" = 34,
"VIII" = 34,
"IX" = 35
)<- -annees
annees_naissance <- annees_films[film]
annee_ref - annees_naissance
annee_ref }
Utiliser la fonction pour ajouter deux nouvelles variables au tableau starwars
: age_iv
qui correspond à l’âge (potentiel) au moment du film IV, et age_ix
qui correspond à l’âge au moment du film IX.
%>%
starwars mutate(
age_iv = age_film(birth_year, "IV"),
age_ix = age_film(birth_year, "IX"),
)
15.6.2 across()
Exercice 2.1
Reprendre la fonction etendue
de l’exercice 1.2 :
<- function(v) {
etendue max(v, na.rm = TRUE) - min(v, na.rm = TRUE)
}
Dans le jeu de données starwars
, calculer l’étendue des variables height
et mass
pour chaque valeur de sex
à l’aide de group_by()
, summarise()
et across()
.
%>%
starwars group_by(sex) %>%
summarise(
across(
c(height, mass),
etendue
) )
Toujours à l’aide d’across()
, appliquer etendue
à toutes les variables numériques, toujours pour chaque valeur de sex
.
%>%
starwars group_by(sex) %>%
summarise(
across(
where(is.numeric),
etendue
) )
En utilisant &
et !
, appliquer etendue
à toutes les variables numériques sauf à celles qui finissent par “year”.
%>%
starwars group_by(sex) %>%
summarise(
across(
where(is.numeric) & !ends_with("year"),
etendue
) )
Exercice 2.2
Dans le jeu de données starwars
, appliquer en un seul summarise
les fonctions min
et max
aux variables height
et mass
.
%>%
starwars summarise(
across(
c(height, mass),
list(min = min, max = max)
) )
Si vous ne l’avez pas déjà fait à la question précédente, modifier le code pour que le calcul des valeurs minimales et maximales ne prennent pas en compte les valeurs manquantes.
<- list(
funs min = function(v) { min(v, na.rm = TRUE) },
max = function(v) { max(v, na.rm = TRUE) }
)%>%
starwars summarise(
across(
c(height, mass),
funs
)
)# Autre possibilité : les arguments supplémentaires passés à across() sont
# transmis aux fonctions appliquées
%>%
starwars summarise(
across(
c(height, mass),
list(min = min, max = max),
na.rm = TRUE
) )
Exercice 2.3
Dans le jeu de données hdv2003
, utiliser across()
pour transformer les modalités “Oui” et “Non” en TRUE
et FALSE
pour toutes les variables de hard.rock
à sport
.
<- function(v) {
detecte_oui == "Oui"
v
}%>%
hdv2003 mutate(
across(
:sport,
hard.rock
detecte_oui
) )
Ajouter un argument .names
à across()
pour que les variables recodées soient stockées dans de nouvelles colonnes nommées avec le suffixe "_true"
.
<- function(v) {
detecte_oui == "Oui"
v
}%>%
hdv2003 mutate(
across(
:sport,
hard.rock
detecte_oui,.names = "{.col}_true"
) )
15.6.3 Fonctions anonymes et notations abrégées
Exercice 3.1
Dans un exercice précédent, on a vu que le code ci-dessous permet de calculer l’étendue des variables height
et mass
du jeu de données starwars
.
<- function(v) {
etendue max(v, na.rm = TRUE) - min(v, na.rm = TRUE)
}
%>%
starwars group_by(sex) %>%
summarise(
across(
c(height, mass),
etendue
) )
Modifier ce code en supprimant la définition de etendue
et en utilisant à la place une fonction anonyme directement dans le across()
.
%>%
starwars group_by(sex) %>%
summarise(
across(
c(height, mass),
function(v) {
max(v, na.rm = TRUE) - min(v, na.rm = TRUE)
}
) )
Modifier à nouveau ce code pour utiliser la syntaxe abrégée de type “formule” du tidyverse.
%>%
starwars group_by(sex) %>%
summarise(
across(
c(height, mass),
~ max(.x, na.rm = TRUE) - min(.x, na.rm = TRUE)
) )
Exercice 3.2
Soit le code suivant, qui renomme les colonnes du tableau starwars
de type liste en leur ajoutant le préfixe “liste_”.
<- function(nom) {
ajoute_prefixe_liste paste0("liste_", nom)
}
%>%
starwars rename_with(ajoute_prefixe_liste, .cols = where(is.list))
Réécrire ce code avec une fonction anonyme en utilisant les trois notations :
- classique (avec
function()
) - formule (du tidyverse)
- compacte (à partir de R 4.1)
# Classique
%>%
starwars rename_with(
function(nom) { paste0("liste_", nom) },
.cols = where(is.list)
)# Formule
%>%
starwars rename_with(
~ paste0("liste_", .x),
.cols = where(is.list)
)# Compacte
%>%
starwars rename_with(
paste0("liste_", nom),
\(nom) .cols = where(is.list)
)
Exercice 3.3
Le code suivant indique, pour chaque région du jeu de données rp2018
, le nom de la commune ayant la valeur maximale pour les variables dipl_aucun
et dipl_sup
.
<- function(valeurs, communes) {
nom_commune_max == max(valeurs)]
communes[valeurs
}
%>%
rp2018 group_by(region) %>%
summarise(
across(
c(dipl_aucun, dipl_sup),
nom_commune_max,
commune
)
)#> # A tibble: 17 × 3
#> region dipl_aucun dipl_sup
#> <chr> <chr> <chr>
#> 1 Auvergne-Rhône-Alpes Oyonnax Corenc
#> 2 Bourgogne-Franche-Comté Saint-Loup-sur-Semouse Fontaine-lès-Dijon
#> 3 Bretagne Louvigné-du-Désert Saint-Grégoire
#> 4 Centre-Val de Loire La Loupe Olivet
#> 5 Corse Ghisonaccia Ville-di-Pietrabugno
#> 6 Grand Est Behren-lès-Forbach Mittelhausbergen
#> 7 Guadeloupe Saint-Louis Le Gosier
#> 8 Guyane Papaichton Remire-Montjoly
#> 9 Hauts-de-France Bohain-en-Vermandois La Madeleine
#> 10 La Réunion Cilaos La Possession
#> 11 Martinique Basse-Pointe Schœlcher
#> 12 Normandie Sourdeval Mont-Saint-Aignan
#> 13 Nouvelle-Aquitaine Aiguillon Bordeaux
#> 14 Occitanie Bessèges Montferrier-sur-Lez
#> 15 Pays de la Loire Saint-Calais Nantes
#> 16 Provence-Alpes-Côte d'Azur Marseille 15e Arrondissement Le Tholonet
#> 17 Île-de-France Clichy-sous-Bois Paris 5e Arrondissem…
Réécrire ce code en utilisant une fonction anonyme, avec la syntaxe de votre choix (classique, formule ou compacte).
# Classique
%>%
rp2018 group_by(region) %>%
summarise(
across(
c(dipl_aucun, dipl_sup),
function(valeurs, communes) { communes[valeurs == max(valeurs)] },
commune
)
)# Formule
%>%
rp2018 group_by(region) %>%
summarise(
across(
c(dipl_aucun, dipl_sup),
~ .y[.x == max(.x)],
commune
)
)# Compacte
%>%
rp2018 group_by(region) %>%
summarise(
across(
c(dipl_aucun, dipl_sup),
== max(valeurs)],
\(valeurs, communes) communes[valeurs
commune
) )
À l’aide d’une fonction anonyme supplémentaire, modifier le code pour qu’il retourne également, pour les mêmes variables, le nom des communes avec les valeurs minimales.
# Formule
%>%
rp2018 group_by(region) %>%
summarise(
across(
c(dipl_aucun, dipl_sup),
list(
max = ~ .y[.x == max(.x)],
min = ~ .y[.x == min(.x)]
),
commune
)
)# Compacte
%>%
rp2018 group_by(region) %>%
summarise(
across(
c(dipl_aucun, dipl_sup),
list(
max = \(valeurs, communes) communes[valeurs == max(valeurs)],
min = \(valeurs, communes) communes[valeurs == min(valeurs)]
),
commune
)%>% View() )
15.6.4 rowwise()
et c_across()
Exercice 4.1
On repart du code final de l’exercice 2.3, qui recodait une série de variables de hdv2003
en valeurs TRUE
/FALSE
dans de nouvelles variables avec le suffixe "_true"
.
<- function(v) {
detecte_oui == "Oui"
v
}<- hdv2003 %>%
hdv2003 mutate(
across(
:sport,
hard.rock
detecte_oui,.names = "{.col}_true"
) )
Calculer le plus simplement possible une nouvelle variable total
qui contient, pour chaque ligne, le nombre de valeurs TRUE
des deux variables cinema_true
et sport_true
(si une ligne contient TRUE
pour ces deux variables, total
doit valoir 2, etc.)
%>%
hdv2003 mutate(total = cuisine_true + sport_true)
Recalculer la variable total
pour qu’elle contienne le nombre de TRUE
par ligne pour les variables bricol_true
, cinema_true
et sport_true
.
%>%
hdv2003 rowwise() %>%
mutate(total = sum(cuisine_true, sport_true, bricol_true))
Recalculer la variable total
pour qu’elle contienne le nombre de TRUE
par ligne pour toutes les variables se terminant par "_true"
.
%>%
hdv2003 rowwise() %>%
mutate(total = sum(c_across(ends_with("_true"))))
Reprendre le code précédent pour qu’il puisse s’appliquer directement sur les variables hard.rock
…sport
, sans passer par le recodage en TRUE
/FALSE
.
<- function(v) {
count_oui sum(v == "Oui")
}
%>%
hdv2003 rowwise() %>%
mutate(
total = count_oui(c_across(hard.rock:sport))
)
Exercice 4.2
Dans le jeu de données starwars
, la colonne films
contient la liste des films dans lesquels apparaissent les différents personnages. Cette colonne a une forme un peu particulière puisqu’il s’agit d’une “colonne-liste” : les éléments de cette colonne sont eux-mêmes des listes.
head(starwars$films, 3)
#> [[1]]
#> [1] "A New Hope" "The Empire Strikes Back"
#> [3] "Return of the Jedi" "Revenge of the Sith"
#> [5] "The Force Awakens"
#>
#> [[2]]
#> [1] "A New Hope" "The Empire Strikes Back"
#> [3] "Return of the Jedi" "The Phantom Menace"
#> [5] "Attack of the Clones" "Revenge of the Sith"
#>
#> [[3]]
#> [1] "A New Hope" "The Empire Strikes Back"
#> [3] "Return of the Jedi" "The Phantom Menace"
#> [5] "Attack of the Clones" "Revenge of the Sith"
#> [7] "The Force Awakens"
On essaye de calculer le nombre de films pour chaque personnage avec le code suivant. Est-ce que ça fonctionne ? Pourquoi ?
%>%
starwars mutate(n_films = length(films))
Trouver une manière d’obtenir le résultat attendu.
%>%
starwars rowwise() %>%
mutate(n_films = length(films))