« Manipuler des données avec dplyr » : différence entre les versions
(51 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
{{tutoriel | |||
|fait_partie_du_cours=Pensée computationnelle avec R | |||
|module_suivant= | |||
|pas_afficher_sous-page=Non | |||
|page_suivante= | |||
|statut=à finaliser | |||
|difficulté=débutant | |||
|cat tutoriels=R | |||
}} | |||
== Introduction == | == Introduction == | ||
Ligne 10 : | Ligne 19 : | ||
Cet article propose un survol des manipulations les plus fréquentes, ainsi que des ressources complémentaires. | Cet article propose un survol des manipulations les plus fréquentes, ainsi que des ressources complémentaires. | ||
=== Note sur la version === | |||
Cette page se réfère à la version <code>1.0.x</code> de dplyr(voir [[versionnage sémantique]]). Les informations contenus abordent cependant des principes fondamentaux du ''tidy data'' et devraient par conséquent rester valides pour des versions successives. | |||
=== Prérequis === | === Prérequis === | ||
Ligne 195 : | Ligne 208 : | ||
* sélectionner un sous-groupe de variables/colonnes | * sélectionner un sous-groupe de variables/colonnes | ||
* filtrer des observations/lignes selon certains critères | * filtrer des observations/lignes selon certains critères | ||
* trier les | * trier les observations/lignes selon un ordre spécifique | ||
* sélectionner un sous-groupe d'observations/lignes | |||
=== Sélectionner un sous-groupe de variables/colonnes === | === Sélectionner un sous-groupe de variables/colonnes === | ||
Ligne 482 : | Ligne 496 : | ||
==== Filtrer des observations/lignes par quantité (majeur, mineur, ...) ==== | ==== Filtrer des observations/lignes par quantité (majeur, mineur, ...) ==== | ||
Avec les opérateurs suivants, il est possible de filtrer par de critères de comparaison quantitative : | |||
* <code>></code> majeur que ... | |||
* <code>>=</code> majeur ou égale à ... | |||
* <code><</code> mineur que ... | |||
* <code><=</code> mineur ou égale à ... | |||
Les filtres peuvent être combinés entre eux, ou avec les autres types de filtre disponibles. Voici quelques exemples : | |||
<source lang="R"> | |||
# Pays avec population mineur de 100'000 habitants à un moment donné | |||
gapminder %>% | |||
filter(pop < 100000) | |||
# Pays avec PIB par habitant entre 20'000 et 30'000 en 2007 | |||
gapminder %>% | |||
filter(gdpPercap >= 20000 & gdpPercap <= 30000 & year == 2007) | |||
</source> | |||
On peut rendre le filtre par quantité intéressant en utilisant des valeurs de comparaison qui sont calculés directement sur la base du jeu de données, comme par exemple la moyenne. Le code suivant filtre seulement les pays dont la population est supérieur à la moyenne de l'ensemble du jeu des données : | |||
<source lang="R"> | |||
# Pays dont la population est supérieur à la moyenne du jeu des données entier | |||
gapminder %>% | |||
filter(pop > mean(pop)) | |||
</source> | |||
==== Filtrer des observations/lignes par valeurs manquantes ==== | ==== Filtrer des observations/lignes par valeurs manquantes ==== | ||
Une opération de filtrage très répandue dans le traitement des données consiste à exclure les données manquantes. Cette opération dépend de la manière dont votre jeu de données codifie les valeurs manquantes, mais en général il est de bonne pratique en [[R]] d'utiliser la valeur <code>NA</code> (pour ''Not Available''). Lorsqu'on utilise ce type de codage, on peut exploiter la fonction disponible en [[R]] de base <code>is.na()</code> pour savoir si la valeur d'une certaine variable dans une observation est NA. Dans la plupart des cas, on utilise l'opérateur de négation <code>!is.na(...)</code> car on veut inclure les données qui ne sont pas NA. | |||
Le jeu de données <code>gapminder</code> est conçu de manière à ce qu'il n'existe pas de valeurs manquants, donc ce type de filtrage n'a pas d'effet sur les données d'exemple. Mais on ajoute quelques exemples pour voir la notation : | |||
<source lang="R"> | |||
# Observations qui n'ont pas le PBI par habitant | |||
gapminder %>% | |||
filter(is.na(gdpPercap)) | |||
# Observations qui reportent la population | |||
gapminder %>% | |||
filter(!is.na(pop)) | |||
</source> | |||
==== Filtrer des observations/lignes avec des critères avancés ==== | ==== Filtrer des observations/lignes avec des critères avancés ==== | ||
Il est possible d'appliquer des filtres plus avancés en exploitants des fonctions disponibles directement en [[R]], ou mises à dispositions directement par '''dplyr'''. Parmi ces fonctions on trouve <code>near()</code> et <code>between()</code>. Les deux fonctions peuvent être considérées des raccourcis des comparaisons quantitatives vue plus haut. | |||
La fonction <code>near()</code> compare deux valeurs en spécifiant un écart de tolérance de la manière suivante : | |||
near(variable, valeur_reference, tolérance) | |||
Par exemple on peut filtrer les pays avec une espérance de vie autour de 80 ans, avec une tolérance de 0,5 ans : | |||
<source lang="R"> | |||
gapminder %>% | |||
filter(near(lifeExp, 80, 0.5)) | |||
</source> | |||
La fonction <code>between()</code> filtre par rapport à deux limites inférieur et supérieur selon la notation suivante : | |||
between(variable, limite_inferieure, limite_superieure) | |||
Par exemple, on peut filtrer les observations qui ont une population comprise entre 1 et 10 mio. d'habitants : | |||
<source lang="R"> | |||
gapminder %>% | |||
filter(between(pop, 1000000, 10000000)) | |||
</source> | |||
Pour des filtres sur les suites de caractères, on peut exploiter les fonctions du paquet [https://stringr.tidyverse.org/ '''stringr'''] qui est automatiquement chargé avec '''tidyverse'''. Le paquet est très exhaustive et la documentation bien fourni. Par conséquent, nous présentons ici seulement quelques exemples : | |||
<source lang="R"> | |||
# Filtrer les pay qui commence par "Sw" en 2007 | |||
gapminder %>% | |||
filter(str_starts(country, "Sw"), year == 1992) # Swaziland, Sweden, Switzerland | |||
# Filtrer les pays qui contiennent "land" dans leur nom en 1992 | |||
gapminder %>% | |||
filter(str_detect(country, "land"), year == 1992) | |||
</source> | |||
=== Trier les observations/lignes selon un ordre spécifique === | |||
Il est possible de trier les observations/lignes d'un jeu de données avec la fonction | |||
arrange(...) | |||
Cette fonction accepte une ou plusieurs noms de variables/colonnes pour déterminer l'ordre ascendant. Pour l'ordre descendant, il faut passer le nom de la variable/colonne à l'intérieur de la fonction <code>desc()</code>. | |||
Voici quelques exemples de base : | |||
<source lang="R"> | |||
# Afficher d'abord les observations avec le plus de population | |||
gapminder %>% | |||
arrange(desc(pop)) | |||
# Trier selon le continent et le PBI par habitant | |||
gapminder %>% | |||
arrange(continent, gdpPercap) | |||
</source> | |||
La fonction de trie est souvent appliquée avec les filtres (voir plus haut), ou les fonctions de sélection des observations/lignes ou d'agrégation traitées plus bas. Par exemple on peut trier un jeu de données filtré par année spécifique : | |||
<source lang="R"> | |||
# Quelles étaient les espérances de vie les plus élevées en 1952 | |||
gapminder %>% | |||
filter(year == 1952) %>% | |||
arrange(desc(lifeExp)) | |||
</source> | |||
=== Sélectionner un sous-groupe d'observations/lignes === | |||
On peut appliquer des critères de sélection pour un sous-groupe d'observations/colonnes avec plusieurs fonctions qui partagent le préfixe <code>slice</code>. | |||
==== Sélectionner par position/index des observations/colonnes ==== | |||
La fonction de base <code>slice()</code> accept l'index ou un range d'indexes qui déterminent la position de l'observation de 1 au total des lignes dans le jeu de données. Le total des lignes peut être récupéré avec la fonction <code>n()</code>. Voici quelques exemples : | |||
<source lang="R"> | |||
# Récupérer la 400ème ligne du jeu des données | |||
gapminder %>% | |||
slice(400) | |||
# Récupérer les lignes de 500 à 700 | |||
gapminder %>% | |||
slice(500:700) | |||
# Récupérer les lignes de 700 à la fin du jeu des données | |||
gapminder %>% | |||
slice(700:n()) | |||
</source> | |||
==== Sélectionner depuis le début/fin de l'ordre des observations/lignes ==== | |||
Les fonctions <code>slice_head()</code> et <code>slice_tail()</code> acceptent un nombre de lignes à récupérer respectivement depuis le début ou la fin de l'ordre des observations. Elles s'appliquent donc souvent avec la fonction <code>filter()</code> et <code>arrange()</code>. Voici quelques exemples : | |||
<source lang="R"> | |||
# Récupérer les 10 pays les plus peuplés en 2007 | |||
gapminder %>% | |||
filter(year == 2007) %>% | |||
arrange(desc(pop)) %>% | |||
slice_head(n = 10) | |||
# Récupérer les 10 derniers pays en Europe ordonnés par PBI par habitant en 1982 | |||
gapminder %>% | |||
filter(year == 1982, continent == "Europe") %>% | |||
arrange(desc(gdpPercap)) %>% | |||
slice_tail(n = 10) | |||
</source> | |||
==== Sélectionner selon les valeurs maximales/minimales d'une colonne ==== | |||
Avec <code>slice_min()</code> et <code>slice_max()</code> on peut éviter d'utiliser la fonction <code>arrange()</code>, car les observations sont déjà triées en ordre ascendant ou descendant en fonction de la variable/colonne passée en argument. | |||
Par exemple, on peur raccourcir l'instruction pour récupérer les 10 pays les plus peuplés en 2007 vue dans la section précédente de la manière suivante : | |||
<source lang="R"> | |||
# Récupérer les 10 pays les plus peuplés en 2007 | |||
gapminder %>% | |||
filter(year == 2007) %>% | |||
slice_max(pop, n = 10) | |||
</source> | |||
À noter que s'il y avait parmi les 10 pays plusieurs pays avec exactement la même population en 2007, la fonction slice_max() aurait retenu plus des 10 lignes, en considérant les ex-equo comme une seule position. Pour éviter ce comportement, on peut passer l'argument '''with_ties = FALSE'''. | |||
==== Sélectionner un nombre d'observations/lignes aléatoire ==== | |||
La fonction <code>slice_sample()</code> permet de retenir un sous-groupe d'observations/lignes tirées au hazard parmi toutes les observations/lignes disponibles. Cette opération peut être très utile notamment en relation avec la [[Introduction à la modélisation des données avec R|modélisation des données]]. | |||
Voici un exemple qui crée un échantillon de 10 observations : | |||
<source lang="R"> | |||
# Échantillon de 10 observations uniques au hasard | |||
gapminder %>% | |||
slice_sample(n = 10) | |||
</source> | |||
Avec ce code, nous sommes sûrs d'avoir 10 observations différentes, c'est-à-dire, en jargon statistique, que l'échantillonnage est fait '''sans replacement'''. Par conséquence, on ne peut pas obtenir deux fois la même observation. | |||
Si on passe l'argument '''replacement = TRUE''', en revanche, chaque observation peut apparaître plusieurs fois dans le sous-groupe d'observations. On a dans ce cas ce qu'on appelle un échantillonnage avec replacement : | |||
<source lang="R"> | |||
# Échantillon de 100 observations, avec répétitions potentielles | |||
gapminder %>% | |||
slice_sample(n = 100, replace = TRUE) | |||
</source> | |||
Enfin, la fonction <code>slice_sample()</code> accepte un autre argument qui permet de pondérer les chances que chaque observation a d'être retenue dans l'échantillon en fonction de la valeur d'une variable/colonne. Par exemple, on peut être intéressé à avoir un échantillon de 50 observations de 1997 avec les pays dont l'espérance de vie est plus élevée qui ont plus de chances d'être retenus : | |||
<source lang="R"> | |||
# Échantillon de 50 observations pondéré par espérance de vie | |||
gapminder %>% | |||
filter(year == 1997) %>% | |||
slice_sample(n = 50, weight_by = lifeExp) | |||
</source> | |||
== Grouper et résumer les observations == | |||
L'une des éléments les plus intéressants de '''dplyr''' concerne la possibilité d'effectuer les manipulations de base illustrées dans la section précédente à des données groupées, c'est-à-dire agrégées selon une ou plusieurs critères. Un exemple concret permet de rendre cette description plus concrète. Le code suivant permet de calculer la population moyenne des différentes continents en 2007 : | |||
<source lang="R" line highlight="4,5"> | |||
# Population moyenne par continent en 2007 | |||
gapminder %>% | |||
filter(year == 2007) %>% | |||
group_by(continent) %>% | |||
summarise(avg_pop_2007 = mean(pop)) | |||
</source> | |||
Le résultat de cette instruction est le suivant : | |||
<source lang="R"> | |||
# A tibble: 5 x 2 | |||
continent avg_pop_2007 | |||
<fct> <dbl> | |||
1 Africa 17875763. | |||
2 Americas 35954847. | |||
3 Asia 115513752. | |||
4 Europe 19536618. | |||
5 Oceania 12274974. | |||
</source> | |||
On peut bien noter que la population moyenne a été calculée pour chacun des 5 continents du jeu des données. Ce code introduit deux fonctions importantes : | |||
* <code>group_by()</code> à la ligne 4 qui s'occupe de déterminer par quelle variable/colonne les données sont agrégées, c'est-à-dire quel critère détermine si deux lignes appartiennent ou pas au même groupe. Dans ce cas, les observations/colonnes sont groupées par '''continent'''. | |||
* <code>summarise()</code> à la ligne 5 qui s'occupe de déterminer : | |||
*# Le nom de la variable qui sera créée pour ''compacter'' le groupe, dans ce cas <code>avg_pop_2007</code> | |||
*# La fonction qui traitent toutes les données appartenant au même groupe, dans ce cas la fonction <code>mean()</code> appliqué à la variable '''pop''' | |||
=== Le mécanisme de groupement === | |||
La fonction <code>group_by()</code> permet de regrouper des observations/lignes selon une ou plusieurs variables/colonnes de référence. Nous allons analyse deux exemples. Le premier regroupe seulement à travers la variable '''continent''', comme dans l'exemple du préambule. Le deuxième regroupe par le croisement de deux variables : '''continent''' et '''year'''. | |||
==== Grouper avec une seule variable ==== | |||
Le code suivant s'occupe de grouper par la variable '''continent''', sans faire d'agrégation avec la fonction <code>summarise()</code>. Cela nous permet de voir ce qui se passe au jeu de données quand on effectue seulement le groupement : | |||
<source lang="R"> | |||
gapminder %>% | |||
group_by(continent) | |||
</source> | |||
Le résultat que nous obtenons depuis cette opération est le suivant (avec l'ajout du nombre des lignes du code pour favoriser la référence) : | |||
<source lang="R" line highlight=2> | |||
# A tibble: 1,704 x 6 | |||
# Groups: continent [5] | |||
country continent year lifeExp pop gdpPercap | |||
<fct> <fct> <int> <dbl> <int> <dbl> | |||
1 Afghanistan Asia 1952 28.8 8425333 779. | |||
2 Afghanistan Asia 1957 30.3 9240934 821. | |||
3 Afghanistan Asia 1962 32.0 10267083 853. | |||
4 Afghanistan Asia 1967 34.0 11537966 836. | |||
5 Afghanistan Asia 1972 36.1 13079460 740. | |||
6 Afghanistan Asia 1977 38.4 14880372 786. | |||
7 Afghanistan Asia 1982 39.9 12881816 978. | |||
8 Afghanistan Asia 1987 40.8 13867957 852. | |||
9 Afghanistan Asia 1992 41.7 16317921 649. | |||
10 Afghanistan Asia 1997 41.8 22227415 635. | |||
# ... with 1,694 more rows | |||
</source> | |||
On peut noter que le jeu de données n'a pas subit de modifications évidentes, car il constate encore de 1'706 lignes et 6 colonnes (ligne 1 dans le code). En revanche, le output confirme à la ligne 3 que nous avons bien grouper par '''continent''' et que le nombre de groupes et de [5], ce qui correspond effectivement aux 5 continents. | |||
Il en résulte que la fonction <code>group_by()</code> n'effectue aucune opération ''perceptible'' au niveau du jeu des données, mais ''préparent'' le jeu des données pour un traitement groupé qui peut être effectué par la suite, par exemple à travers la fonction <code>summarise()</code>, vue plus haut, mais également d'autres, comme <code>mutate()</code> qu'on verra plus bas dans l'article. | |||
Si on reprend la fonction <code>summarise()</code> exactement comme l'exemple dans le préambule pour calculer la moyenne (mais sans filtrer par année), on aura le code suivant : | |||
<source lang="R"> | |||
gapminder %>% | |||
group_by(continent) %>% | |||
summarise(avg_pop = mean(pop)) | |||
</source> | |||
Ce qui produit ce résultat : | |||
<source lang="R"> | |||
# A tibble: 5 x 2 | |||
continent avg_pop | |||
<fct> <dbl> | |||
1 Africa 9916003. | |||
2 Americas 24504795. | |||
3 Asia 77038722. | |||
4 Europe 17169765. | |||
5 Oceania 8874672. | |||
</source> | |||
==== Groupement avec plusieurs variables ==== | |||
On peut passer plusieurs variables/colonnes de groupement à la fonction <code>group_by()</code> en le séparant par une virgule. L'ordre des variables peut être déterminant selon le type de fonction d'agrégation utilisé. Dans le cas de cet exemple, l'impact est limité à l'ordre d'affichage des observations, mais ne modife pas les moyennes calculés. | |||
Maintenant donc d'abord le groupement par '''continent''' comme dans l'exemple avec une seule variable, mais ajoutons cette fois-ci également le groupement par '''year''' : | |||
<source lang="R"> | |||
gapminder %>% | |||
group_by(continent, year) | |||
</source> | |||
Le résultat de cette instruction est très similaire à celui avec une seule variable, si ce n'est pour la ligne 2 mise en évidence ci-bas : | |||
<source lang="R" line highlight=2> | |||
# A tibble: 1,704 x 6 | |||
# Groups: continent, year [60] | |||
country continent year lifeExp pop gdpPercap | |||
<fct> <fct> <int> <dbl> <int> <dbl> | |||
1 Afghanistan Asia 1952 28.8 8425333 779. | |||
2 Afghanistan Asia 1957 30.3 9240934 821. | |||
3 Afghanistan Asia 1962 32.0 10267083 853. | |||
4 Afghanistan Asia 1967 34.0 11537966 836. | |||
5 Afghanistan Asia 1972 36.1 13079460 740. | |||
6 Afghanistan Asia 1977 38.4 14880372 786. | |||
7 Afghanistan Asia 1982 39.9 12881816 978. | |||
8 Afghanistan Asia 1987 40.8 13867957 852. | |||
9 Afghanistan Asia 1992 41.7 16317921 649. | |||
10 Afghanistan Asia 1997 41.8 22227415 635. | |||
# ... with 1,694 more rows | |||
</source> | |||
On peut voir ici que le mécanisme de groupement produit '''[60]''' groupes, et non pas [5] comme dans l'exemple avec seulement le continent. Les 60 groupes sont en effet le produit du croisement entre les 5 continents fois les 12 années de recueil des données. Lorsqu'on ajoute la même fonction <code>summarise()</code> pour calculer la moyenne, le code complet est le suivant : | |||
<source lang="R"> | |||
gapminder %>% | |||
group_by(continent, year) %>% | |||
summarise( | |||
avg_pop = mean(pop) | |||
) | |||
</source> | |||
Ce code produit un résultat différent par rapport à l'exemple précédent, car la moyenne de la population pour chaque continent est calculée pour chacun des 12 ans de recueil des données : | |||
<source lang="R"> | |||
# A tibble: 60 x 3 | |||
# Groups: year [12] | |||
year continent avg_pop | |||
<int> <fct> <dbl> | |||
1 1952 Africa 4570010. | |||
2 1952 Americas 13806098. | |||
3 1952 Asia 42283556. | |||
4 1952 Europe 13937362. | |||
5 1952 Oceania 5343003 | |||
6 1957 Africa 5093033. | |||
7 1957 Americas 15478157. | |||
8 1957 Asia 47356988. | |||
9 1957 Europe 14596345. | |||
10 1957 Oceania 5970988 | |||
# ... with 50 more rows | |||
</source> | |||
=== Utiliser des fonctions pour résumer des observations groupées === | |||
Comme on a vu plus haut, la fonction de '''dplyr''' <code>summarise()</code> (ou l'équivalence <code>summarize()</code>) permet de créer de résumer des observations selon les critères de groupement. Concrètement, la fonction <code>summarise()</code> s'occupe de créer un nouveau data.frame ou tibble avec des variables qui sont calculées sur la base de fonctions selon la notation : | |||
<source lang="R"> | |||
# Notation d'exemple | |||
data %>% | |||
group_by(criterion) %>% | |||
summarise( | |||
reference_to_newly_defined_variable = function_to_summarise(which_variable_of_data) | |||
) | |||
</source> | |||
À l'intérieur de la fonction <code>summarise()</code> on peut définir autant des variables qu'on veut, en séparant les associations avec des virgules. Voici un exemple qui utilise cinq fonctions pour afficher des valeurs calculées à propos de la variable '''lifeExp''' groupée par '''continent''' : | |||
<source lang="R" line> | |||
# Plusieurs indicateurs résumés par continent | |||
gapminder %>% | |||
group_by(continent) %>% | |||
summarise( | |||
avg_lifeExp = mean(lifeExp), | |||
sd_lifeExp = sd(lifeExp), | |||
min_lifeExp = min(lifeExp), | |||
max_lifeExp = max(lifeExp) | |||
) | |||
</source> | |||
Le code s'explique de la manière suivante : | |||
* À la ligne 4, on calcule la moyenne de '''lifeExp''' avec <code>mean()</code> | |||
* À la ligne 5, on calcule l'écart type de '''lifeExp''' avec <code>sd()</code> | |||
* À la ligne 6, on calcule la valeur minale de '''lifeExp''' avec <code>min()</code> | |||
* À la ligne 7, on calcule la valeur maximale de '''lifeExp''' avec <code>max()</code> | |||
Le résultat de cette instruction est le suivant : | |||
<source lang="R"> | |||
# A tibble: 5 x 5 | |||
continent avg_lifeExp sd_lifeExp min_lifeExp max_lifeExp | |||
<fct> <dbl> <dbl> <dbl> <dbl> | |||
1 Africa 48.9 9.15 23.6 76.4 | |||
2 Americas 64.7 9.35 37.6 80.7 | |||
3 Asia 60.1 11.9 28.8 82.6 | |||
4 Europe 71.9 5.43 43.6 81.8 | |||
5 Oceania 74.3 3.80 69.1 81.2 | |||
</source> | |||
À noter que le nom de la variable (e.g. '''mean_lifeExp''') est totalement arbitraire, on peut utiliser la référence symbolique que l'on souhaite. En revanche, le nom de la variable qui est passé dans la fonction qui resume les observations doit exister dans le jeu de données qu'on veut résumer (dans ce cas, l'une des variables existantes dans <code>gapminder</code>. | |||
On peut résumer plusieurs variables différentes dans la même fonction <code>summarise()</code>. Dans l'exemple précédent on s'est intéressé exclusivement à '''lifeExp''', mais on peut par exemple calculer la moyenne de '''lifeExp''', '''pop''', et '''gdpPercap''' dans le même ''sommaire'' : | |||
<source lang="R"> | |||
# Différentes moyennes | |||
gapminder %>% | |||
group_by(continent) %>% | |||
summarise( | |||
avg_lifeExp = mean(lifeExp), | |||
avg_pop = mean(pop), | |||
avg_gdpPercap = mean(gdpPercap) | |||
) | |||
</source> | |||
On pourrait même mixer différentes fonctions et différentes variables (e.g. la moyenne de '''lifeExp''' et l'écart type de '''pop'''), mais à ce moment c'est la cohérence interne du nouveau jeu de données qui détermine si ces informations ont du sense regroupées de telle manière. Comme indiqué dans l'[[introduction à Tidyverse]] selon le principe du Tidy data, chaque data.frame ou tibble devrait représenter une unité informationnelle congruente. | |||
=== Fonctions utiles pour résumer des observations === | |||
Voici une liste non exhaustive des fonctions qui permettent de résumer/réduire des observations (tirée de la documentation officielle de '''dplyr''') : | |||
* Mesures de tendance centrale : <code>mean()</code>, <code>median()</code> | |||
* Mesures de dispersion : <code>sd()</code>, <code>IQR()</code>, <code>mad()</code> | |||
* Mesures d'écart : <code>min()</code>, <code>max()</code>, <code>quantile()</code> | |||
* Mesures de position : <code>first()</code>, <code>last()</code>, <code>nth()</code>, | |||
* Mesures de comptage : <code>n()</code>, <code>n_distinct()</code> | |||
* Fonctions logiques : <code>any()</code>, <code>all()</code> | |||
Certains de ces fonctions sont utilisées dans les exemples suivants, qui utilisent souvent également des manipulations de filtrage et triage illustrées plus haut dans l'article. | |||
==== Compter les nombres d'observations dans un groupe ==== | |||
Une action souvent utile concerne à determiner le nombre d'observations qui respectent un critère de groupement. On peut utiliser à cet effet les fonctions <code>n()</code> ou <code>n_distinct()</code>. La différence entre les deux, comme le nom l'implique, consiste dans le fait que <code>n_distinct()</code> ne tient pas compte de valeurs dupliquées. | |||
Voici un simple exemple qui compte le nombre de pays par continent en 2007 : | |||
<source lang="R"> | |||
# Nombre de pays par continent | |||
gapminder %>% | |||
filter(year == 2007) %>% | |||
group_by(continent) %>% | |||
summarise( | |||
n_countries = n() | |||
) | |||
</source> | |||
Ce qui donne le résultat suivant : | |||
<source lang="R"> | |||
# A tibble: 5 x 2 | |||
continent n_countries | |||
<fct> <int> | |||
1 Africa 52 | |||
2 Americas 25 | |||
3 Asia 33 | |||
4 Europe 30 | |||
5 Oceania 2 | |||
</source> | |||
==== Récupérer la première/dernière observation dans un groupe ==== | |||
On peut exploiter les fonctions <code>first()</code> et <code>last()</code> pour récupérer la première ou la dernière observation pour chaque groupe. Le code suivant récupère le pay avec l'espérance de vie la plus élevée pour chaque année disponible : | |||
<source lang="R"> | |||
# Pays avec l'espérance de vie la plus élevée pour les différents années | |||
gapminder %>% | |||
group_by(year) %>% | |||
arrange(desc(lifeExp)) %>% | |||
summarise( | |||
country = first(country), | |||
lifeExp = first(lifeExp) | |||
) | |||
</source> | |||
La fonction <code>first()</code> retient la première valeur de la variable indiquée pour chaque sous-groupe. De ce fait, le résultat du code est le suivant : | |||
<source lang="R"> | |||
# A tibble: 12 x 3 | |||
year country lifeExp | |||
<int> <fct> <dbl> | |||
1 1952 Norway 72.7 | |||
2 1957 Iceland 73.5 | |||
3 1962 Iceland 73.7 | |||
4 1967 Sweden 74.2 | |||
5 1972 Sweden 74.7 | |||
6 1977 Iceland 76.1 | |||
7 1982 Japan 77.1 | |||
8 1987 Japan 78.7 | |||
9 1992 Japan 79.4 | |||
10 1997 Japan 80.7 | |||
11 2002 Japan 82 | |||
12 2007 Japan 82.6 | |||
</source> | |||
== Créer ou modifier des variables/colonnes == | |||
'''dplyr''' permet des opérations au niveaux des variables/colonnes à travers les fonctions <code>mutate()</code> et <code>transmute()</code>. Les deux fonctions ont un fonctionnement très similaire, si ce n'est pour une différence importante : | |||
* <code>mutate()</code> garde intactes les variables/colonnes qui ne sont pas concernées par l'opération | |||
* <code>transmute()</code> crée un jeu de données avec seulement les variables/colonnes qui sont concernées par l'opération | |||
Un exemple concret peut aider à mieux cerner la différence. Nous allons créer une nouvelle variable/colonne '''gdpTot''' qui propose le produit intérieur brute totale du pays. Pour obtenir ce résultat, nous allons tout simplement multiplier la variable/colonne '''gdpPercap''' fois la variable/colonne '''pop'''. | |||
; Utilisation de <code>mutate()</code> | |||
<source lang="R"> | |||
# Utilisation de mutate() | |||
gapminder %>% | |||
mutate(gdpTot = gdpPercap * pop) | |||
</source> | |||
Cette opération retourne un jeu de données avec 7 variables/colonnes : les 6 variables originales de <code>gapminder</code>, plus la nouvelle variable '''gdpTot''' que nous venons de créer. | |||
; Utilisation de <code>transmute()</code> | |||
<source lang="R"> | |||
# Utilisation de transmute() | |||
gapminder %>% | |||
transmute(gdpTot = gdpPercap * pop) | |||
</source> | |||
Cette opération retourne un jeu de données avec 1 seule variable/colonne, c'est-à-dire la nouvelle variable '''gdpTot''' que nous venons de créer, tandis que les 6 variables originales de <code>gapminder</code> ne sont pas retenues. | |||
Dans la suite de la section nous utiliserons seulement <code>mutate()</code>, mais le code s'applique également à <code>transmute()</code>. | |||
=== Créer ou modifier === | |||
Le mécanisme de création ou modification est très simple : | |||
* '''Créer''' : pour créer une nouvelle variable, il suffit de spécifier un nom qui n'existe pas déja. | |||
*: <code>mutate(nouveau_nom = valeur)</code> | |||
* '''Modifier''' : pour modifier une variable existante, il faut passer le nom précis de la variable déjà existante. | |||
*: <code>mutate(nom_existant = valeur)</code> | |||
Les exemples de code suivants s'appliquent à la création ou à la modification selon ce simple mécanisme. | |||
=== Agir sur plusieurs variables à la fois === | |||
On peut agir sur plusieurs variables à la fois (créer et/ou modifier en même temps) simplement en passant plusieurs combinaison de <code>variable = valeur</code> à l'intérieur de la même fonction <code>mutate()</code>. Lorsqu'on a plusieurs variables concernées, il peut être utile d'utiliser une indentation et le retour à la ligne pour favoriser la lecture du code : | |||
<source lang="R"> | |||
# Syntaxe d'exemple | |||
data %>% | |||
mutate( | |||
var1 = ..., | |||
var2 = ..., | |||
var3 = ..., | |||
... | |||
) | |||
</source> | |||
=== Attribuer à une variable une valuer calculée selon d'autres variables === | |||
Une opération fréquente avec <code>mutate()</code> consiste à calculer une variable en fonction des valeurs d'une ou plusieurs autres variables disponibles dans le même jeu. On a vu un exemple de ce principe avec la création de '''gdpTot''' dans le préambule de cette section, dont le code est repris ici : | |||
<source lang="R"> | |||
# Créer une variable avec le PBI total | |||
gapminder %>% | |||
mutate(gdpTot = gdpPercap * pop) | |||
</source> | |||
Il est donc suffisant de passer le nom précis de la variable pour accéder à sa valeur relative à chaque observation/ligne du jeu des données. | |||
On peut combiner des références à des variables du jeu avec des valeurs littéraux. Par exemple, on peut transformer la population pour l'exprimer en millions d'habitants : | |||
<source lang="R"> | |||
# Créer une variable avec population exprimée par mio. d'habitants | |||
gapminder %>% | |||
mutate(popInMio = pop / 1000000) | |||
</source> | |||
=== Attribuer à une variable une valeur selon certaines conditions === | |||
'''dplyr''' met à disposition deux fonctions utiles pour attribuer à une variable une valeur selon certaines conditions : | |||
* <code>if_else()</code> | |||
* <code>case_when()</code> | |||
==== La fonction if_else() ==== | |||
La fonction <code>if_else()</code> peut être considérée un cas particulier de <code>case_when()</code>, car elle s'applique à la comparaison entre seulement deux conditions. La fonction accepte en effet trois arguments : | |||
if_else(condition_à_evaluer, valeur_si_condition_vraie, valeur_si_condition_fausse | |||
On peut voir le mécanisme à l'oeuvre même sans utiliser encore <code>mutate()</code> : | |||
<source lang="R"> | |||
if_else(10 < 100, "C'est vrai", "C'est faux") | |||
</source> | |||
Dans ce cas, 10 est inférieur à 100 est donc la valeur qui sera retenue par la fonction est "C'est vraie". On peut intégrer cette fonction à l'intérieur de <code>mutate()</code>. Pour montrer cette intégration, on peut imaginer qu'un chercheur ne soit pas satisfait avec la valeur de '''gdpPercap'''. Selon lui, cette valeur est surestimée pour les années de 1952 à 1982 comprises. Il veut donc effectuer une correction qui diminue la valeur de '''gdpPercap''' de 10%, mais seulement pour les années faisant partie de cette période. Pour pouvoir comparer les deux valeurs, le chercheur décide de créer une nouvelle variable '''gdpPercap_corrected'''. Voici le code pour obtenir ce résultat : | |||
<source lang="R" line highlight=4> | |||
# Calculer un nouveau gdpPercap corrigé selon l'année | |||
gapminder %>% | |||
mutate( | |||
gdpPercap_corrected = if_else(year <= 1982, gdpPercap * 0.9, gdpPercap) | |||
) | |||
</source> | |||
Le code d'intérêt se trouve à la ligne 4 et ce traduit de la manière suivante : si l'année est inférieur ou égale à 1982, alors multiplie la valeur actuelle de gdpPercap de 0.9 (ce qui équivaut à diminuer de 10%); autrement, passe la valeur actuelle sans modification. | |||
==== La fonction case_when() ==== | |||
Avec <code>case_when()</code> on peut évaluer plusieurs conditions à la fois, et définir une valeur ''de sortie'' qui est appliquée si aucune des conditions spécifiées n'est respectée. La notation de la fonction est la suivante : | |||
<source lang="R" line> | |||
case_when( | |||
premiere_evaluation ~ "Valeur si vraie", | |||
deuxieme_evaluation ~ "Valeur si vraie", | |||
troisieme_evaluation ~ "Valeur si vraie", | |||
... | |||
TRUE ~ "Valeur si aucune des conditions précédente était vraie" | |||
) | |||
</source> | |||
Les deux éléments à signaler sont : | |||
# l'utilisation de <code>~</code> qui équivaut à dire ''si vraie, alors applique cette valeur'' | |||
# l'utilisation de <code>TRUE</code> dans la dernière évaluation, qui est une manière de dire ''cette évaluation sera vraie dans tous les cas'', et par conséquent la valeur après le <code>~</code> est attribué automatiquement. | |||
Voyons le mécanisme à l'oeuvre dans un exemple. Imaginons qu'un chercher soit intéressé à diviser les populations en trois catégories : | |||
# "Small country" si la population est inférieur à 1mio. | |||
# "Medium country" si la population est 1mio. ou plus, mais inférieur à 10mio. | |||
# "Big country" si la population est 10 mio. ou plus, mais inférieur à 100mio. | |||
# "Huge country" dans tous les autres cas | |||
Le code pour obtenir cette catégorisation dans une nouvelle variable est le suivant : | |||
<source lang="R"> | |||
# Ajouter une catégorisation selon la population | |||
gapminder %>% | |||
mutate( | |||
pop_category = case_when( | |||
pop < 1000000 ~ "Small country", | |||
pop < 10000000 ~ "Medium country", | |||
pop < 100000000 ~ "Big country", | |||
TRUE ~ "Huge country" | |||
) | |||
) | |||
</source> | |||
=== Attribuer à une variable une valeur selon un mécanisme de groupement === | |||
Il est possible d'utiliser <code>mutate()</code> en combination avec la fonction de groupement <code>group_by()</code> illustrée plus haut. Ce mécanisme est particulièrement utile si on veut attribuer des valeurs sur la base d'éléments calculés en fonction du sous-groupe. L'exemple suivant met en évidence cette logique en comparant deux attributions, une non-groupée et l'autre groupée. | |||
Imaginons qu'un chercheur veut ajouter une variable qui calcule la variation du gdpPercap par rapport à la moyenne. Or, il peut faire ce calcule par exemple : | |||
1. Sur la base de la moyenne globale des observations, ou | |||
2. Sur la base de la moyenne du continent | |||
Le code suivant montre la différence dans les deux instructions : | |||
<source lang="R"> | |||
# Attribution non-groupée | |||
gapminder %>% | |||
mutate( | |||
diff_mean = gdpPercap - mean(gdpPercap) | |||
) | |||
# Attribution groupée | |||
gapminder %>% | |||
group_by(continent) %>% | |||
mutate( | |||
diff_mean = gdpPercap - mean(gdpPercap) | |||
) | |||
) | |||
</source> | |||
Les premières lignes des deux jeux des données, que nous ne reportons pas ici pour brévité, sont suffisantes pour s'apercevoir que la nouvelle variable n'a pas la même valeur selon que la moyenne soit globale ou relative au continent. | |||
=== Renommer une ou plusieurs colonnes === | |||
Avec dplyr on peut égalemenr renommer une ou plusieurs colonnes. La fonction <code>rename</code> permet de modifier le nom d'une colonne, tandis que <code>rename_with</code> permet de renommer plusieurs colonnes, en utilisant notamment une fonction pour changer le nom de manière programmée. | |||
==== rename() ==== | |||
Avec la fonction <code>rename()</code> on attribut un nouveau nom selon le pattern '''new_name = old_name'''. Par exemple : | |||
<syntaxhighlight> | |||
gapminder |> | |||
rename( | |||
life_exp = lifeExp, | |||
gdpPercap = gdp_percap | |||
) | |||
</syntaxhighlight> | |||
==== rename_with() ==== | |||
Avec la fonction <code>rename_with()</code> on peut utiliser une fonction pour modifier le nom d'une sélection de colonnes (avec les mêmes notations de la fonction <code>select()</code>) ou de toutes les colonnes à la fois (avec la fonction <code>everything()</code>). | |||
Par exemple, cette commande change tous les noms des colonnes en minuscule avec la fonction <code>tolower()</code> : | |||
<syntaxhighlight> | |||
gapminder |> | |||
rename_with(~ tolower(.x), everything()) | |||
</syntaxhighlight> | |||
On notera comme le placeholder <code>.x</code> correspond à chaque fois au nom de la colonne actuelle. | |||
Une application utile de cette fonction est la possibilité par exemple d'appliquer des préfixes : | |||
<syntaxhighlight> | |||
gapminder |> | |||
rename_with(~ paste0("num_", .x), year:gdpPercap) | |||
</syntaxhighlight> | |||
Cette fonction applique le préfixe "num_" aux colonnes de year à gdpPercap. | |||
== Manipulations sur plusieurs tables == | |||
'''dplyr''' permet d'effectuer des opérations qui concernent deux ou plus tables. Dans cette section, nous illustrerons principalement les opérations sur deux tables, mais par extension ces opérations peuvent s'effectuer aussi sur plusieurs tables. Les opérations peuvent être divisées en trois catégories : | |||
# Combiner les données depuis deux tables pour créer un nouveau jeu de données | |||
# Créer des filtres ''croisés'', c'est-à-dire filtrer les données d'une table en fonction d'une autre table | |||
# Utiliser les tables comme des ensembles (e.g. intersection, union, ...) | |||
=== Combiner des données depuis plusieurs tables === | |||
==== Combiner par observations/lignes ==== | |||
Il est souvent utile de combiner deux ou plusieurs jeux de données qui proposent le même type de données, par exemple lors de l'importation depuis plusieurs fichiers qui ont la même structure, c'est-à-dire les mêmes variables/colonnes. Pour ce faire, '''dplyr''' met à disposition la fonction <code>bind_rows()</code> qui accepte deux data frames ou plus à combiner. L'un des avantages de <code>bind_rows()</code> concerne le fait que les jeux de données doivent avoir exactement les mêmes noms des variables/colonnes, mais pas forcément dans le même ordre. | |||
Pour montrer le fonctionnement de <code>bind_rows()</code> en restant dans l'exemple des données <code>gapminder</code>, nous allons d'abord créer des jeux des données en créant des filtre du jeu de données en entier, pour ensuite les recombiner. On pourrait obtenir le même résultat avec des filtres (voir plus haut), donc les exemples ne sont pas pertinents au niveau de leur ''praticité'', mais plutôt pour comprendre le fonctionnement. | |||
<source lang="R"> | |||
data_switzerland <- gapminder %>% | |||
filter(country == "Switzerland") | |||
data_france <- gapminder %>% | |||
filter(country == "France") | |||
# Combiner les deux jeux de données | |||
data_combined <- bind_rows(data_switzerland, data_france) | |||
</source> | |||
<code>data_combined</code> contient à ce moment 24 observations/lignes, c'est-à-dire 12 observations pour les deux pays Suisse et France. | |||
==== Combiner par variables/colonnes ==== | |||
On peut combiner deux jeux de données en fonction des variables/colonnes, c'est-à-dire ajouter une ou plusieurs variables/colonnes d'un jeu de données à un autre. L'exemple suivant crée une sorte de jeu de données ''hybride'' en ajoutant le '''gdpPercap''' de la France à '''lifeExp''' de la Suisse : | |||
<source lang="R"> | |||
data_switzerland <- gapminder %>% | |||
filter(country == "Switzerland") %>% | |||
select(country, lifeExp) | |||
data_france <- gapminder %>% | |||
filter(country == "France") %>% | |||
select(gdpPercap) | |||
hybrid_nation <- bind_cols(data_switzerland, data_france) | |||
</source> | |||
<code>hybrid_nation</code> contient à ce moment deux colonnes de la Suisse (country et lifeExp), ainsi qu'une colonne de la France (gdpPercap). Il faut noter que dans ce type de combination l'ordre des observations/lignes et important, car les données sont intégrées dans l'ordre. | |||
==== Joindre deux tables ==== | |||
'''dplyr''' met à disposition plusieurs fonctions de type <code>*_join()</code> qui permettent de joindre deux ou plusieurs tables : | |||
* <code>inner_join()</code> : retient les données qui sont à la fois dans les deux tables | |||
* <code>left_join()</code> : maintient les données de la première table et, si possible, intègre les correspondantes dans la deuxième table. Cette fonction est en général la plus utilisée | |||
* <code>right_join()</code> : fonction spéculaire à <code>left_join()</code> | |||
* <code>full_join()</code> : retient toutes les données des deux tables | |||
Lorsque des données ne sont pas disponibles dans l'une ou l'autre table, elles sont remplacées par <code>NA</code>, c'est-à-dire l'absence de valeur. | |||
La compréhension des fonctions de fonction nécessite surtout de la pratique, car conceptuellement elles restent très abstraites. Mais elles peuvent être très utiles pour combiner différentes sources de données qui partagent une relation sémantique entre elles. | |||
Pour illustrer un simple exemple, nous allons exploiter le jeu de données <code>country_codes</code> mis à disposition par '''gapminder''' qui propose les abbréviations de chaque pays ('''iso_alpha'''), ainsi qu'un code numérique conventionnel pour identifier le pays ('''iso_number'''). On peut voir la structure de ce jeu de données avec la commande : | |||
str(country_codes) | |||
On peut relever que le jeu de données propose 187 observations : | |||
<source lang="R"> | |||
tibble [187 x 3] (S3: tbl_df/tbl/data.frame) | |||
$ country : chr [1:187] "Afghanistan" "Albania" "Algeria" "Angola" ... | |||
$ iso_alpha: chr [1:187] "AFG" "ALB" "DZA" "AGO" ... | |||
$ iso_num : int [1:187] 4 8 12 24 32 51 533 36 40 31 ... | |||
</source> | |||
Imaginons maintenant que pour des raisons d'affichage, un chercheur préfère utiliser l'abbréviation officielle de la nation plutôt que le nom complet. On peut utiliser la fonction <code>left_join()</code> pour combiner les données du jeu de données <code>gapminder</code> avec les abbreviations du jeu de données <code>country_codes</code>. Comme on peut le noter depuis la structure, les deux jeux de données partagent en effet la variable '''country'''. Dans l'exemle suivant, le code filtre d'abord par l'année 2007 ainsi d'avoir une seule fois chaque nation dans le jeux de données combiné : | |||
<source lang="R"> | |||
integrated_country_code <- gapminder %>% | |||
filter(year == 2007) %>% | |||
left_join(country_codes, by = "country") | |||
</source> | |||
Le résultat de cette instruction est un jeu de données de 142 observations de <code>gapminder</code>, auxquelles on a ajouté les colonnes '''iso_alpha''' et '''iso_num''' depuis <code>country_codes</code>. Vous pouvez remarquez que certains pays disponibles dans le jeu de données <code>country_codes</code> (avec 187 observations) n'ont pas servi, car ces pays ne figurent pas dans le jeu de données <code>gapminder</code>. Voici les premières observations du jeu de données créé : | |||
<source lang="R"> | |||
# A tibble: 142 x 8 | |||
country continent year lifeExp pop gdpPercap iso_alpha iso_num | |||
<chr> <fct> <int> <dbl> <int> <dbl> <chr> <int> | |||
1 Afghanistan Asia 2007 43.8 31889923 975. AFG 4 | |||
2 Albania Europe 2007 76.4 3600523 5937. ALB 8 | |||
3 Algeria Africa 2007 72.3 33333216 6223. DZA 12 | |||
4 Angola Africa 2007 42.7 12420476 4797. AGO 24 | |||
5 Argentina Americas 2007 75.3 40301927 12779. ARG 32 | |||
... | |||
</source> | |||
Si on utilise la fonction <code>right_join()</code> dans le même exemple, le résultat est un jeu de données de 187 observations, dont 45 pays (187 - 142) n'ont pas de données récoltés. On peut les voir si après la fonction de jonction on filtre par exemple pour des valeurs manquantes dans '''year''' : | |||
<source lang="R"> | |||
integrated_country_code_right_join <- gapminder %>% | |||
filter(year == 2007) %>% | |||
right_join(country_codes, by = "country") %>% | |||
filter(is.na(year)) | |||
</source> | |||
Les premières lignes du résultat de cette fonction sont les suivantes : | |||
<source lang="R"> | |||
# A tibble: 45 x 8 | |||
country continent year lifeExp pop gdpPercap iso_alpha iso_num | |||
<chr> <fct> <int> <dbl> <int> <dbl> <chr> <int> | |||
1 Armenia NA NA NA NA NA ARM 51 | |||
2 Aruba NA NA NA NA NA ABW 533 | |||
3 Azerbaijan NA NA NA NA NA AZE 31 | |||
4 Bahamas NA NA NA NA NA BHS 44 | |||
5 Barbados NA NA NA NA NA BRB 52 | |||
... | |||
</source> | |||
=== Créer des filtres ''croisés'' === | |||
Il est souvent utile dans le cadre de deux ou plusieurs jeux de données de filtrer les données en fonction de la présence ou de l'absence de correspondance dans les tables. À ce propos, '''dplyr''' met à disposition deux fonctions utiles : | |||
* <code>semi_join()</code> : cette fonction retient toutes les observations dans la première table seulement si elles ont une correspondance dans la deuxième. | |||
* <code>anti_join()</code> : cette fonction fait le contraire, c'est-à-dire qu'elle ne retient que les observations qui n'ont pas de correspondance dans la deuxième table. Cette fonction peut-être utile par exemple pour afficher les participants exclus dans les résultats pour manque de données dans une unité informationnelle spécifique. | |||
Voici deux exemples qui utilisent ce fonction en combinant les jeux de données <code>country_codes</code> et <code>gapminder</code>. Pour rappel, <code>coutry_codes</code> contient 187 pays, tandis que le jeu de données <code>gapminder</code> a récolté de manière consistente les données seulement de 142 pays. | |||
<source lang="R"> | |||
# Retenir les pays avec les données | |||
countries_with_data <- country_codes %>% | |||
semi_join(gapminder, by = "country") | |||
# Donne un jeu de données avec les 142 pays avec données récoltées | |||
# Retenir les pays qui n'ont pas de données | |||
countries_without_data <- country_codes %>% | |||
anti_join(gapminder, by = "country") | |||
# Donne les 45 pays dont les données ne figure pas dans gapminder | |||
</source> | |||
À noter que, contrairement aux fonctions de joction illustrées plus haut, dans ce cas le jeu de données maintient seulement les variables/colonnes originales de la première table, sans ajouter rien de la deuxième, car il s'agit en effet de fonctions de filtrage et pas de jonction. | |||
=== Utiliser les tables comme des ensembles === | |||
Les jeux des données peuvent être considérés également des '''ensembles''' et considérés donc en fonction de la [https://fr.wikipedia.org/wiki/Th%C3%A9orie_des_ensembles théorie mathématique des ensembles]. '''dplyr''' met à disposition trois fonctions utiles à cet effet : | |||
* <code>intersect()</code> qui retient les observations communes entre deux jeux de données | |||
* <code>union()</code> qui combine toutes les observations de deux jeux de données, mais sans dupliquer les observations communes | |||
* <code>setdiff()</code> qui retient les observations propres à un jeu de données et donc pas présente dans l'autre | |||
Pour montrer le fonctionnement des troi fonctions avec le jeu de données <code>gapminder</code> nous allons d'abord créer deux jeux de données de la manière suivante : | |||
# Le premier contient des observations des pays d'Afrique '''et''' d'Europe | |||
# Le deuxième contient des observations des pays d'Asie '''et''' d'Europe | |||
Les données sont filtrées dans une année spécifique pour réduire le nombre d'observations, mais ce qui est important à retenir concerne la présence des données des pays d'Europe dans les deux jeux de données. | |||
<source lang="R"> | |||
africa_and_europe <- gapminder %>% | |||
filter(continent %in% c("Africa", "Europe")) %>% | |||
filter(year == 1952) | |||
asia_and_europe <- gapminder %>% | |||
filter(continent %in% c("Asia", "Europe")) %>% | |||
filter(year == 1952) | |||
# Retient données communes, c'est-à-dire seulement les pays d'Europe | |||
intersect(africa_and_europe, asia_and_europe) | |||
# Retient les pays des trois continents, mais sans dupliquer les pays d'Europe | |||
union(africa_and_europe, asia_and_europe) | |||
# Retient seulement les pays d'Afrique | |||
setdiff(africa_and_europe, asia_and_europe) | |||
# Retient seulement les pays d'Asie (spéculaire au précédent) | |||
setdiff(asia_and_europe, africa_and_europe) | |||
</source> | |||
== Le paquet dbplyr == | |||
Le paquet [https://dbplyr.tidyverse.org/ dbplyr] (avec le préfix '''db''' depuis database) permet d'appliquer les mêmes principes de '''dplyr''' aux données qui sont contenues directement dans une [[base de données]]. Le paquet s'intègre avec différents types de base de données et permet donc de maintenir une grammaire similaire pour la manipulation des données indépendamment du système utilisé par la base de données sous-jacente. | |||
Pour plus d'informations, se référer à la documentation officielle : | |||
* {{ Goblock | [https://dbplyr.tidyverse.org/ Site officiel de dbplyr] }} | |||
== Conclusion == | |||
Cet article a présenté un aperçu du paquet ''dplyr'' qui permet de manipuler des données à l'intérieur d'un ou plusieurs jeux de données, avec des opérations qui sont très fréquentes et, par conséquent, très utiles dans l'analyse des données. | |||
Les concepts traités dans cet article nécessite d'un certain temps pour être compris et maitrisés, surtout pour des novices, pour lesquels l'utilité même de certains fonctions n'est pas évidente. En effet, pour exploiter aux maximum les potentialités de '''dplyr''' il faut avoir d'abord une bonne vision d'ensemble de données qui peuvent être organisées de différentes manières, notamment selon les principes ''tidy data'' expliqués dans l'[[introduction à tidyverse]. | |||
La possibilité d'''intéroger'' les données, ainsi que de pouvoir les filtrer de manière complexe, sont néanmoins des éléments assez évident, qui peuvent être appliqués assez facilement même par des débutants, une fois que la grammaire et le codage deviennent de plus en plus familiers. | |||
== Ressources == | |||
Une collection de ressources pour approfondir ou consolider les connaissances de dplyr : | |||
* [https://dplyr.tidyverse.org/ Site officiel du paquet] | |||
* [https://juba.github.io/tidyverse/10-dplyr.html Manipuler les données avec dplyr] : partie sur dplyr d'une ressources plus complète sur Tidyverse écrite par Julien Barnier | |||
* [https://dplyr.tidyverse.org/articles/dplyr.html Introduction to dplyr] : documentation contenus dans le paquet sur CRAN | |||
* [https://dplyr.tidyverse.org/articles/rowwise.html Row-wise operations] : cet article a abordé principalement des opérations sur les colonnes/variables, mais dplyr permet également d'agir sur les lignes/observations | |||
[[Catégorie:R]] |
Dernière version du 13 janvier 2024 à 20:56
Pensée computationnelle avec R | |
---|---|
⚐ à finaliser | ☸ débutant |
⚒ 2024/01/13 | |
Catégorie: R |
Introduction
dplyr est un paquet de R faisant partie de l'écosystème Tidyverse utile pour manipuler des données en format rectangulaire (i.e. lignes et colonnes). Il utilise une grammaire basée sur les actions les plus fréquentes dans la manipulations des données comme par exemple filtrer, agréger, sélectionner, transformer, etc. Le paquet dplyr peut être utilisé dans plusieurs contextes, comme par exemple :
- le nettoyage des données brutes importées avec R
- l'agrégation de données depuis une ou plusieurs sources
- interroger les données à travers des filtres complexes
- l'organisation de données pour l'affichage dans des reports, pages web, etc.
- la mise en forme de données pour visualisation des données avec R, notamment en combinaison avec le paquet ggplot2.
Cet article propose un survol des manipulations les plus fréquentes, ainsi que des ressources complémentaires.
Note sur la version
Cette page se réfère à la version 1.0.x
de dplyr(voir versionnage sémantique). Les informations contenus abordent cependant des principes fondamentaux du tidy data et devraient par conséquent rester valides pour des versions successives.
Prérequis
L'article nécessite de connaissances de base de R, notamment au niveau des structures de données de type data.frame
ou tibble
(i.e. organisées en lignes et colonnes). La lecture préalable de l'article Introduction à Tidyverse est également recommandée.
Installation et chargement
dplyr est l'un des paquets qui composent l'écosystème Tidyverse. Il peut donc être installé deux deux manières :
- Paquet individuel
- Paquet global Tidyverse
Paquet dplyr individuel
Pour installer seulement le paquet dplyr, la commande est la suivante :
# Installation individuelle
install.packages("dplyr")
Pour utiliser le paquet il faudra à ce moment le charger :
library(dplyr)
Paquet global Tidyverse
Si vous installez le paquet global Tidyverse, dplyr est installé automatiquement.
# Installation de Tidyverse
install.packages("tidyverse")
L'installation de l'écosystème Tidyverse est conseillée, car dplyr peut s'intégrer facilement avec d'autres manipulations sur les données comme par exemple la visualisation des données avec ggplot2.
Pour utiliser le paquet vous pouvez à ce moment choisir si :
- Charger seulement dplyr
library(dplyr)
- Charger tous les paquets de Tidyverse
library(tidyverse)
Voir Introduction à Tidyverse pour plus de détails.
Données gapminder utilisées dans le tutoriel
Pour faciliter la compréhension des différents éléments de dplyr, cet article utilise un jeu de données issue du paquet gapminder (Bryan, 2017), créé par Jennifer Bryan, qui est un extrait des données collectées par la fondation Gapminder, un institution indépendante qui utilise les données afin de modifier des mauvaises conceptions que les personnes ont souvent à propos de phénomènes globaux.
Cette section explique comment installer le paquet et fourni une description des donnée disponibles. Au moment de l'écriture de ce tutoriel, la version du paquet gapminder est la 0.3.0
(voir versionnage sémantique). Le paquet propose des données sur plusieurs nations du monde. À la version 0.3.0, les données sont disponibles jusqu'en 2007.
Installation et chargement
Pour disposer des données, il faut d'abord installer le paquet avec la commande :
install.packages("gapminder")
À ce moment, il faut charger le paquet pour pouvoir l'utiliser
library(gapminder)
Description des données
Une fois chargé le paquet avec l'instruction library(gapminder)
vous aurez accès à deux références symboliques :
gapminder
gapminder_unfiltered
Les deux jeux de données ont la même structure, mais gapminder
compte seulement 1'704 observations, tandis que gapminder_unfiltered en a 3'313. Nous allons utiliser gapminder
dans la suite de cet article.
Avec la commande str(gapminder)
on peut accéder à des informations sur le jeu de données :
tibble[,6] [1,704 x 6] (S3: tbl_df/tbl/data.frame)
$ country : Factor w/ 142 levels "Afghanistan",..: 1 1 1 1 1 1 1 1 1 1 ...
$ continent: Factor w/ 5 levels "Africa","Americas",..: 3 3 3 3 3 3 3 3 3 3 ...
$ year : int [1:1704] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
$ lifeExp : num [1:1704] 28.8 30.3 32 34 36.1 ...
$ pop : int [1:1704] 8425333 9240934 10267083 11537966 13079460 14880372 12881816 13867957 16317921 22227415 ...
$ gdpPercap: num [1:1704] 779 821 853 836 740 ...
Avec la fonction glimpse(gapminder)
on peut implémenter la structure avec un aperçu des données contenues :
Rows: 1,704
Columns: 6
$ country <fct> "Afghanistan", "Afghanistan", "Afghanistan", "Afghanistan", "Afghanista~
$ continent <fct> Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia,~
$ year <int> 1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, 2002, 2007,~
$ lifeExp <dbl> 28.801, 30.332, 31.997, 34.020, 36.088, 38.438, 39.854, 40.822, 41.674,~
$ pop <int> 8425333, 9240934, 10267083, 11537966, 13079460, 14880372, 12881816, 138~
$ gdpPercap <dbl> 779.4453, 820.8530, 853.1007, 836.1971, 739.9811, 786.1134, 978.0114, 8~
Si vous utilisez RStudio, vous pouvez voir directement les données en format spreadsheet avec l'instruction View(gapminder)
.
Ces commandes nous permettent de découvrir que le jeu de données gapminder
est organisé dans un format long (voir la section Tidy data de l'Introduction à Tidyverse), avec 6 variables et 1'704 observations. En combinant des informations issues des commandes précédentes, ainsi que depuis la documentation du paquet, on découvre les informations suivantes à propos des six variables du jeu de données :
country
: le nom de la nation, un facteur avec 142 modalités, c'est-à-dire 142 nations différentes. La documentation suggère que pour chaque nation, il y existe 12 observations (i.e. lignes) avec les données recueillies depuis 1952 à 2007, avec des intervalles fixes de 5 ans. On peut récupérer les noms des nations avec la commandelevels(gapminder$country)
continent
: le nom du continent dans lequel se trouve la nation, un facteur avec 5 modalités, donc les 5 continents Africa, America, Asia, Europe, Oceania. On peut récupérer les noms des facteurs avec la commandelevels(gapminder$continent)
year
: l'année de référence. Comme indiqué plus haut, les données couvrent la période entre 1951 et 2007, avec 5 années d'intervalles. On peut récupérer les différentes années avec la commandeunique(gapminder$year)
(car il s'agit de chiffres et pas d'un facteur comme les variables précédentes).lifeExp
: l'espérance de vie à la naissance en annéespop
: la population de la nation à l'année de référencegdpPercap
: le produit intérieur brut en dollar international (Geary-Khamis), une mesure qui ajuste pour la différence au fil du temps. Les détails économiques ne sont pas nécessaires pour suivre le tutoriel, il suffit de retenir que cette variable représente grosso-modo la richesse d'une nation de manière standardisée et donc comparable entre pays et années.
Le paquet met à disposition également une autre référence symbolique, country_codes
: un jeu de données qui contient notamment l’abréviation de la nation (e.g.
FRA, GER, CHE, ...). Nous utiliserons ce deuxième jeu de données pour montrer des opérations qui impliquent deux tableaux. La commande str(country_codes)
affiche la structure suivante :
tibble[,3] [187 x 3] (S3: tbl_df/tbl/data.frame)
$ country : chr [1:187] "Afghanistan" "Albania" "Algeria" "Angola" ...
$ iso_alpha: chr [1:187] "AFG" "ALB" "DZA" "AGO" ...
$ iso_num : int [1:187] 4 8 12 24 32 51 533 36 40 31 ...
Chargement des paquets nécessaires à suivre le tutoriel
Pour suivre le tutoriel, il est donc nécessaire de charger au moins les deux paquets tidyverse (qui incorpore également dplyr) et gapminder. En d'autres termes, il serait bien que votre fichier de script R commence avec les lignes de code suivantes :
library(tidyverse)
library(gapminder)
Le chargement de tidyverse est recommandé, car le tutoriel propose occasionnellement des croisement avec d'autres paquets de l'écosystème Tidyverse pour montrer l'intégration de la manipulation des données avec d'autres opérations utiles.
Notation sur le style de codage utilisé
Ce tutoriel essaie de montrer les aspects les plus relevants du code avec dplyr et pour ce faire essaie de maintenir une certaine consistence dans le style de codage utilisé. Voici quelques indications qui peuvent aider à mieux suivre le code.
Utilisation du pipe
Comme indiqué dans l'introduction à Tidyverse, l'écosystème favorise l'enchaînement séquentiel des manipulations à travers l'opérateur pipe %>%
. L'utilisation de cet opérateur n'est cependant pas indispensable, car les fonctions de dplyr peuvent être utilisé également comme fonction normales. Pour rappel voici deux exemples qui obtiennent les deux seulement les observations relatives à l'année 2002 et les trient en ordre de population ascendante. Le fonctionnement des fonctions spécifiques sera abordés plus bas, pour l'instant l'attention est portée sur la syntaxe :
# Notation "classique"
classique <- arrange(filter(gapminder, year == 2002), pop)
# Notation avec pipe
avec_pipe <- gapminder %>%
filter(year == 2002) %>%
arrange(pop)
# Confirmer que c'est la même chose
identical(classique, avec_pipe) # Donne TRUE
Le résultat de l'évaluation de la dernière ligne sera TRUE
, ce qui confirme que les deux notations obtiennent exactement le même résultat. Cependant, la notation avec pipe est plus simple à lire, surtout si on utilise l'indentation et une opération par ligne, suivi par le pipe.
Pour rappel, le pipe marche selon le principe que le premier argument passé à une fonction est un data.frame ou un tibble, et que chaque fonction retourne exactement le même type d'object. De cette manière, on peut construire des chaines d'instructions comme dans l'example avec notation pipe.
|>
. Si vous préférez cette notation, vous pouvez remplacer le pipe %>%
avec |>
. Utilisation minimaliste de références symboliques
Une autre pratique utilisée dans ce tutoriel est celle d'éviter au maximum de créer des références symboliques pour leur attribuer les résultats d'une manipulation avec dplyr, et d'écrire plutôt des instructions qui sont évaluées directement. L'utilisation de références symboliques sera adopté seulement si elle apporte des avantages, par exemple en termes d'alternatives ou de traitement ultérieur. Voici du code qui explique ce principe :
# Exécution directe, sans référence symbolique
gapminder %>%
filter(year == 2007)
# Attribution à une référence symbolique "data_2007" pour utilisation ultérieur
data_2007 <- gapminder %>%
filter(year == 2007)
# Trier en ordre de population ascendant
data_2007 %>%
arrange(pop)
# trier en ordre de population descendante
data_2007 %>%
arrange(desc(pop))
Manipulations de base sur un tableau
Cette section illustre des manipulations de base qui sont souvent utiles dans le traitement de données comme :
- sélectionner un sous-groupe de variables/colonnes
- filtrer des observations/lignes selon certains critères
- trier les observations/lignes selon un ordre spécifique
- sélectionner un sous-groupe d'observations/lignes
Sélectionner un sous-groupe de variables/colonnes
Le jeu de données gapminder
contient seulement 6 variables/colonnes, mais des jeux de données peuvent prévoir un nombre beaucoup plus conséquent. Il est donc souvent utile, pour pouvoir se concentrer seulement sur les variables d'intérêt, de sélectionner seulement un sous-groupe des colonnes. dplyr permet d'exécuter cette opération à travers la fonction :
select(...)
Cette fonction accepte à son intérieur des arguments qui permettent de déterminer les critères de sélection des variables/colonnes. Voici de suite quelques applications pratiques.
Sélectionner une seule variable/colonne
Pour sélectionner une seule variable/colonne, il suffit de passer le nom de la colonne en argument :
# Sélectionner seulement la variable relative à la population
gapminder %>%
select(pop)
Le nom de la colonne peut également être écrit entre guillemets :
gapminder %>%
select("pop")
Dans les deux cas, l'opération donne une structure de données (de type data.frame ou tibble) et non pas un vecteur, même si on a une seule colonne :
# A tibble: 1,704 x 1
pop
<int>
1 8425333
2 9240934
3 10267083
...
Sélectionner plusieurs variables/colonnes par leurs noms
Pour sélectionner plusieurs variables/colonnes en même temps, on peut simplement passer leurs noms en argument séparées par une virgule :
# Sélectionner trois colonnes selon leurs noms
gapminder %>%
select(country, gdpPercap, continent)
Comme vous pouvez le voir depuis l'exemple, on peut également sélectionner les colonnes dans un ordre différent de celui d'origine (e.g., gdpPercap est sélectionné avant continent). La structure des données qui en résulte mantinent à ce moment l'ordre établi dans les arguments de sélection :
# A tibble: 1,704 x 3
country gdpPercap continent
<fct> <dbl> <fct>
1 Afghanistan 779. Asia
2 Afghanistan 821. Asia
3 Afghanistan 853. Asia
...
Sélectionner plusieurs variables/colonnes adjacentes
Souvent il est utile de sélectionner des sous-groupes de variables/colonnes qui se trouvent l'une à côté de l'autre. À ce moment, il devient rébarbative de les sélectionner toutes par leurs noms individuellement. On peut à ce moment utiliser la notation colonne_initiale:colonne_finale
pour sélectionner toutes les variables qui se trouvent entre les deux, avec les deux colonnes spécifiées qui sont incluses dans la sélection.
# Sélectionner les colonnes entre country et lifeExp (i.e. les 4 premières colonnes)
gapminder %>%
select(country:lifeExp)
Il est possible de combiner plusieurs notations séparées par une virgule :
# Combination avec nom de colonne simple
gapminder %>%
select(country, lifeExp:gdpPercap)
# Combination avec sélections adjacentes
gapminder %>%
select(country:continent, pop:gdpPercap)
Le jeu de données gapminder
contient peu de variables, donc ces notations ne sont pas très utiles, mais elles peuvent être très utiles sur des jeux de données plus larges.
Sélectionner par exclusion de variables/colonnes
Il peut arriver de vouloir sélectionner toutes les variables/colonnes, sauf quelques-unes en particulier. Dans ce cas, on peut spécifier quelles colonnes exclure à travers l'opérateur de négation !
.
# Exclure seulement le continent
gapminder %>%
select(!continent)
Si on veut exclure plusieurs variables en même temps, on peut les grouper dans un vecteur avec la notation !c(colonne1, colonne2, colonne3, ...)
:
# Exclure plusieurs variables par leurs noms
gapminder %>%
select(!c(lifeExp, gdpPercap))
Enfin, on peut combiner la négation également avec des variables adjacentes avec la notation !colonne_initiale:colonne_finale
:
# Exclure les colonnes de lifeExp à gdpPercap
gapminder %>%
select(!lifeExp:gdpPercap)
Sélectionner des variables/colonnes en utilisant des filtres composites
On peut produire des sélections plus complexes en utilisant des fonctions que dplyr met à disposition. Ces fonctions peuvent être d'ailleurs combinés avec les opérateurs logiques :
&
: AND|
: OR
Voici un exemple qui utilise les fonctions :
starts_with(...)
qui peut être utile par exemple lorsque des variables on un prefix similaireends_with
qui peut être utile par exemple lorsque des variables ont un suffixe similaire
# Utiliser des fonctions dans les critères de sélection
gapminder %>%
select(starts_with("c") | ends_with("xp")) # Sélectionne country, continent et lifeExp
gapminder %>%
select(starts_with("c") & ends_with("t")) # Sélectionne seulement continent
Une autre fonction utile est contains()
:
gapminder %>%
select(contains("nt")) # Sélectionne country et continent
Enfin, une fonction qui peut se reveler très utile, mais qu'on ne peut pas appliquer au jeu de données gapminder
, est num_range(préfixe, num_initiale:num_finale)
. Cette fonction est utile lorsque vous avez plusieurs variables qui partagent un même préfixe et se termine avec un compteur, comme par exemple question1, question2, question3, question4, ..., question 100. À ce moment on peut décider de sélectionner seulement un sous-groupe avec la notation suivante :
data %>%
select(num_range("question", 20:40)) # Sélectionne seulement les colonnes de question20 à question40
Pour d'autres critères de sélection plus avancées, voir la documentation officielle de select().
Filtrer des observations/lignes selon certains critères
Une opération indispensable dans la manipulation des données consiste à filtrer les observations/lignes selon des critères spécifiques. Cette opération s'effectue pour différentes raisons, mais qu'on peut diviser principalement en deux catégories :
- Nettoyage des données : les filtres appliqués améliorent la qualité du jeu des données, par exemple en excluant des observations manquantes, des valeurs extrêmes, des observations qui sont probablement dues à des erreurs de mesure ou de transcodage, etc.
- S'intéresser à une partie spécifique du jeu de données : les filtres servent pour extrapoler des données qui partagent des critères d'intérêts spécifiques et qui rendent ces observations propices pour une analyse. Cette opération peut se faire à plusieurs reprises, pour créer par exemple des sous-jeux de données à analyser de manière séparée ou pour les comparer entre eux.
À ces propos, dplyr met à disposition la fonction :
filter(...)
La fonction accepte des critères de sélection en tant qu'arguments. Ces critères peuvent aller du très simple au très complexe et se combiner avec les opérateurs logiques &
(AND) et |
(OR). De plus, le type de filtrer applicable dépend également du type de données en question (numérique, suite de caractères, ...). Cette section en propose quelques à titre explicatif.
Filtrer des observations/lignes par équivalence
L'une des mécanisme de filtre les plus utilisés consiste à inclure des observations dont une ou plusieurs variables correspondent exactement à un critère de sélection. L'exemple suivant retient seulement les observations relatives à l'années 1992 avec le filtre year == 1992
:
gapminder %>%
filter(year == 1992)
Il est important d'utiliser la notation avec deux symboles d'égalité ==
et non pas un seul. Mettre un seul symbole d'égalité au lieu de deux est une erreur assez fréquente au point que dplyr affiche un message d'erreur à ce sujet :
# Code volontairement erroné, avec seulement un =
gapminder %>%
filter(year = 1992)
Si vous exécutez cette fonction, vous obtenez un message d'erreur similaire à celui-ci :
Error: Problem with `filter()` input `..1`.
x Input `..1` is named.
i This usually means that you've used `=` instead of `==`.
i Did you mean `year == 1992`?
On peut combiner plusieurs filtre d'équivalence en même temps. Pour appliquer des filtres de manières conjointe (i.e. intersection de deux filtres avec AND) on peut utiliser soit la virgule, soit l'opérateur logique &
pour séparer les critères. Les deux notations reviennent au même résultat :
# Notation avec l'opérateur logique & équivalent à AND
gapminder %>%
filter(year == 1992 & continent == "Europe")
# Notation avec la virgule, équivalente à AND
gapminder %>%
filter(year == 1992, continent == "Europe")
Le filtre sur le continent concerne une donnée de type suite de caractères est l'argument du filtre, dans ce cas Europe, doit être placé entre guillemets : continent == "Europe"
On peut utiliser l'opérateur logique |
(OR) pour l'union de deux critères :
gapminder %>%
filter(year == 1992 | year == 1997)
Les filtres d'équivalence peuvent être combinés, en sachant que l'opérateur AND a la précédence sur l'opérateur OR. Donc parfois il est nécessaire d'utiliser des parenthèses pour définir l'ordre de précédence des critères. L'exemple suivant montre le même filtre sans et avec parenthèses :
# Sans parenthèses de précédence
gapminder %>%
filter(year == 1992 | year == 1997 & continent == "Europe")
# Avec paranthèses de précédence
gapminder %>%
filter((year == 1992 | year == 1997) & continent == "Europe")
Dans le premier cas, on retient tous les observations de l'années 1992 et ensuite seulement les observations de l'année 1997 qui concerne l'Europe :
# Résultat sans parenthèses : filter(year == 1992 | year == 1997 & continent == "Europe")
# A tibble: 172 x 6
country continent year lifeExp pop gdpPercap
<fct> <fct> <int> <dbl> <int> <dbl>
1 Afghanistan Asia 1992 41.7 16317921 649.
2 Albania Europe 1992 71.6 3326498 2497.
3 Albania Europe 1997 73.0 3428038 3193.
4 Algeria Africa 1992 67.7 26298373 5023.
5 Angola Africa 1992 40.6 8735988 2628.
6 Argentina Americas 1992 71.9 33958947 9308.
7 Australia Oceania 1992 77.6 17481977 23425.
8 Austria Europe 1992 76.0 7914969 27042.
9 Austria Europe 1997 77.5 8069876 29096.
10 Bahrain Asia 1992 72.6 529491 19036.
# ... with 162 more rows
Vous pouvez voir que le filtre retient en 1992 aussi les pays en dehors de l'Europe, car l'ordre d'exécution des filtres correspond à filter(year == 1992 | (year == 1992 & continent == "Europe"))
.
Au contraire, le deuxième filtre avec parenthèses ne retient que les pays en Europe pour les années 1992 et 1997, car le filtre OR et évalué avant l'opérateur AND :
# Résultat avec parenthèses : filter((year == 1992 | year == 1997) & continent == "Europe")
# A tibble: 60 x 6
country continent year lifeExp pop gdpPercap
<fct> <fct> <int> <dbl> <int> <dbl>
1 Albania Europe 1992 71.6 3326498 2497.
2 Albania Europe 1997 73.0 3428038 3193.
3 Austria Europe 1992 76.0 7914969 27042.
4 Austria Europe 1997 77.5 8069876 29096.
5 Belgium Europe 1992 76.5 10045622 25576.
6 Belgium Europe 1997 77.5 10199787 27561.
7 Bosnia and Herzegovina Europe 1992 72.2 4256013 2547.
8 Bosnia and Herzegovina Europe 1997 73.2 3607000 4766.
9 Bulgaria Europe 1992 71.2 8658506 6303.
10 Bulgaria Europe 1997 70.3 8066057 5970.
# ... with 50 more rows
L'ordre de précédence des critères nécessite d'une certaine pratique pour être maîtrisé, surtout avec plusieurs filtres en même temps.
Filtrer des observations/lignes par non-équivalence
Avec l'opérateur !=
on peut exclure les observations qui ne correspondent pas au critère de sélection, c'est-à-dire qui ne sont pas équivalente à l'argument. L'exemple suivant retient toutes les observations, sauf pour les données de la Suisse :
gapminder %>%
filter(country != "Switzerland")
Le filtre de non-équivalence peut être combiné avec tout autre type de filtre, avec les mêmes mécanismes de précédence illustrés dans la section précédente sur les filtres d'équivalence. Par exemple :
gapminder %>%
filter(year == 2007 & continent == "Europe" & country != "Switzerland")
Dans ce cas on retient toutes les observations de 2007 en Europe, sauf celles de la Suisse.
Filtrer des observations/lignes par quantité (majeur, mineur, ...)
Avec les opérateurs suivants, il est possible de filtrer par de critères de comparaison quantitative :
>
majeur que ...>=
majeur ou égale à ...<
mineur que ...<=
mineur ou égale à ...
Les filtres peuvent être combinés entre eux, ou avec les autres types de filtre disponibles. Voici quelques exemples :
# Pays avec population mineur de 100'000 habitants à un moment donné
gapminder %>%
filter(pop < 100000)
# Pays avec PIB par habitant entre 20'000 et 30'000 en 2007
gapminder %>%
filter(gdpPercap >= 20000 & gdpPercap <= 30000 & year == 2007)
On peut rendre le filtre par quantité intéressant en utilisant des valeurs de comparaison qui sont calculés directement sur la base du jeu de données, comme par exemple la moyenne. Le code suivant filtre seulement les pays dont la population est supérieur à la moyenne de l'ensemble du jeu des données :
# Pays dont la population est supérieur à la moyenne du jeu des données entier
gapminder %>%
filter(pop > mean(pop))
Filtrer des observations/lignes par valeurs manquantes
Une opération de filtrage très répandue dans le traitement des données consiste à exclure les données manquantes. Cette opération dépend de la manière dont votre jeu de données codifie les valeurs manquantes, mais en général il est de bonne pratique en R d'utiliser la valeur NA
(pour Not Available). Lorsqu'on utilise ce type de codage, on peut exploiter la fonction disponible en R de base is.na()
pour savoir si la valeur d'une certaine variable dans une observation est NA. Dans la plupart des cas, on utilise l'opérateur de négation !is.na(...)
car on veut inclure les données qui ne sont pas NA.
Le jeu de données gapminder
est conçu de manière à ce qu'il n'existe pas de valeurs manquants, donc ce type de filtrage n'a pas d'effet sur les données d'exemple. Mais on ajoute quelques exemples pour voir la notation :
# Observations qui n'ont pas le PBI par habitant
gapminder %>%
filter(is.na(gdpPercap))
# Observations qui reportent la population
gapminder %>%
filter(!is.na(pop))
Filtrer des observations/lignes avec des critères avancés
Il est possible d'appliquer des filtres plus avancés en exploitants des fonctions disponibles directement en R, ou mises à dispositions directement par dplyr. Parmi ces fonctions on trouve near()
et between()
. Les deux fonctions peuvent être considérées des raccourcis des comparaisons quantitatives vue plus haut.
La fonction near()
compare deux valeurs en spécifiant un écart de tolérance de la manière suivante :
near(variable, valeur_reference, tolérance)
Par exemple on peut filtrer les pays avec une espérance de vie autour de 80 ans, avec une tolérance de 0,5 ans :
gapminder %>%
filter(near(lifeExp, 80, 0.5))
La fonction between()
filtre par rapport à deux limites inférieur et supérieur selon la notation suivante :
between(variable, limite_inferieure, limite_superieure)
Par exemple, on peut filtrer les observations qui ont une population comprise entre 1 et 10 mio. d'habitants :
gapminder %>%
filter(between(pop, 1000000, 10000000))
Pour des filtres sur les suites de caractères, on peut exploiter les fonctions du paquet stringr qui est automatiquement chargé avec tidyverse. Le paquet est très exhaustive et la documentation bien fourni. Par conséquent, nous présentons ici seulement quelques exemples :
# Filtrer les pay qui commence par "Sw" en 2007
gapminder %>%
filter(str_starts(country, "Sw"), year == 1992) # Swaziland, Sweden, Switzerland
# Filtrer les pays qui contiennent "land" dans leur nom en 1992
gapminder %>%
filter(str_detect(country, "land"), year == 1992)
Trier les observations/lignes selon un ordre spécifique
Il est possible de trier les observations/lignes d'un jeu de données avec la fonction
arrange(...)
Cette fonction accepte une ou plusieurs noms de variables/colonnes pour déterminer l'ordre ascendant. Pour l'ordre descendant, il faut passer le nom de la variable/colonne à l'intérieur de la fonction desc()
.
Voici quelques exemples de base :
# Afficher d'abord les observations avec le plus de population
gapminder %>%
arrange(desc(pop))
# Trier selon le continent et le PBI par habitant
gapminder %>%
arrange(continent, gdpPercap)
La fonction de trie est souvent appliquée avec les filtres (voir plus haut), ou les fonctions de sélection des observations/lignes ou d'agrégation traitées plus bas. Par exemple on peut trier un jeu de données filtré par année spécifique :
# Quelles étaient les espérances de vie les plus élevées en 1952
gapminder %>%
filter(year == 1952) %>%
arrange(desc(lifeExp))
Sélectionner un sous-groupe d'observations/lignes
On peut appliquer des critères de sélection pour un sous-groupe d'observations/colonnes avec plusieurs fonctions qui partagent le préfixe slice
.
Sélectionner par position/index des observations/colonnes
La fonction de base slice()
accept l'index ou un range d'indexes qui déterminent la position de l'observation de 1 au total des lignes dans le jeu de données. Le total des lignes peut être récupéré avec la fonction n()
. Voici quelques exemples :
# Récupérer la 400ème ligne du jeu des données
gapminder %>%
slice(400)
# Récupérer les lignes de 500 à 700
gapminder %>%
slice(500:700)
# Récupérer les lignes de 700 à la fin du jeu des données
gapminder %>%
slice(700:n())
Sélectionner depuis le début/fin de l'ordre des observations/lignes
Les fonctions slice_head()
et slice_tail()
acceptent un nombre de lignes à récupérer respectivement depuis le début ou la fin de l'ordre des observations. Elles s'appliquent donc souvent avec la fonction filter()
et arrange()
. Voici quelques exemples :
# Récupérer les 10 pays les plus peuplés en 2007
gapminder %>%
filter(year == 2007) %>%
arrange(desc(pop)) %>%
slice_head(n = 10)
# Récupérer les 10 derniers pays en Europe ordonnés par PBI par habitant en 1982
gapminder %>%
filter(year == 1982, continent == "Europe") %>%
arrange(desc(gdpPercap)) %>%
slice_tail(n = 10)
Sélectionner selon les valeurs maximales/minimales d'une colonne
Avec slice_min()
et slice_max()
on peut éviter d'utiliser la fonction arrange()
, car les observations sont déjà triées en ordre ascendant ou descendant en fonction de la variable/colonne passée en argument.
Par exemple, on peur raccourcir l'instruction pour récupérer les 10 pays les plus peuplés en 2007 vue dans la section précédente de la manière suivante :
# Récupérer les 10 pays les plus peuplés en 2007
gapminder %>%
filter(year == 2007) %>%
slice_max(pop, n = 10)
À noter que s'il y avait parmi les 10 pays plusieurs pays avec exactement la même population en 2007, la fonction slice_max() aurait retenu plus des 10 lignes, en considérant les ex-equo comme une seule position. Pour éviter ce comportement, on peut passer l'argument with_ties = FALSE.
Sélectionner un nombre d'observations/lignes aléatoire
La fonction slice_sample()
permet de retenir un sous-groupe d'observations/lignes tirées au hazard parmi toutes les observations/lignes disponibles. Cette opération peut être très utile notamment en relation avec la modélisation des données.
Voici un exemple qui crée un échantillon de 10 observations :
# Échantillon de 10 observations uniques au hasard
gapminder %>%
slice_sample(n = 10)
Avec ce code, nous sommes sûrs d'avoir 10 observations différentes, c'est-à-dire, en jargon statistique, que l'échantillonnage est fait sans replacement. Par conséquence, on ne peut pas obtenir deux fois la même observation.
Si on passe l'argument replacement = TRUE, en revanche, chaque observation peut apparaître plusieurs fois dans le sous-groupe d'observations. On a dans ce cas ce qu'on appelle un échantillonnage avec replacement :
# Échantillon de 100 observations, avec répétitions potentielles
gapminder %>%
slice_sample(n = 100, replace = TRUE)
Enfin, la fonction slice_sample()
accepte un autre argument qui permet de pondérer les chances que chaque observation a d'être retenue dans l'échantillon en fonction de la valeur d'une variable/colonne. Par exemple, on peut être intéressé à avoir un échantillon de 50 observations de 1997 avec les pays dont l'espérance de vie est plus élevée qui ont plus de chances d'être retenus :
# Échantillon de 50 observations pondéré par espérance de vie
gapminder %>%
filter(year == 1997) %>%
slice_sample(n = 50, weight_by = lifeExp)
Grouper et résumer les observations
L'une des éléments les plus intéressants de dplyr concerne la possibilité d'effectuer les manipulations de base illustrées dans la section précédente à des données groupées, c'est-à-dire agrégées selon une ou plusieurs critères. Un exemple concret permet de rendre cette description plus concrète. Le code suivant permet de calculer la population moyenne des différentes continents en 2007 :
# Population moyenne par continent en 2007
gapminder %>%
filter(year == 2007) %>%
group_by(continent) %>%
summarise(avg_pop_2007 = mean(pop))
Le résultat de cette instruction est le suivant :
# A tibble: 5 x 2
continent avg_pop_2007
<fct> <dbl>
1 Africa 17875763.
2 Americas 35954847.
3 Asia 115513752.
4 Europe 19536618.
5 Oceania 12274974.
On peut bien noter que la population moyenne a été calculée pour chacun des 5 continents du jeu des données. Ce code introduit deux fonctions importantes :
group_by()
à la ligne 4 qui s'occupe de déterminer par quelle variable/colonne les données sont agrégées, c'est-à-dire quel critère détermine si deux lignes appartiennent ou pas au même groupe. Dans ce cas, les observations/colonnes sont groupées par continent.summarise()
à la ligne 5 qui s'occupe de déterminer :- Le nom de la variable qui sera créée pour compacter le groupe, dans ce cas
avg_pop_2007
- La fonction qui traitent toutes les données appartenant au même groupe, dans ce cas la fonction
mean()
appliqué à la variable pop
- Le nom de la variable qui sera créée pour compacter le groupe, dans ce cas
Le mécanisme de groupement
La fonction group_by()
permet de regrouper des observations/lignes selon une ou plusieurs variables/colonnes de référence. Nous allons analyse deux exemples. Le premier regroupe seulement à travers la variable continent, comme dans l'exemple du préambule. Le deuxième regroupe par le croisement de deux variables : continent et year.
Grouper avec une seule variable
Le code suivant s'occupe de grouper par la variable continent, sans faire d'agrégation avec la fonction summarise()
. Cela nous permet de voir ce qui se passe au jeu de données quand on effectue seulement le groupement :
gapminder %>%
group_by(continent)
Le résultat que nous obtenons depuis cette opération est le suivant (avec l'ajout du nombre des lignes du code pour favoriser la référence) :
# A tibble: 1,704 x 6
# Groups: continent [5]
country continent year lifeExp pop gdpPercap
<fct> <fct> <int> <dbl> <int> <dbl>
1 Afghanistan Asia 1952 28.8 8425333 779.
2 Afghanistan Asia 1957 30.3 9240934 821.
3 Afghanistan Asia 1962 32.0 10267083 853.
4 Afghanistan Asia 1967 34.0 11537966 836.
5 Afghanistan Asia 1972 36.1 13079460 740.
6 Afghanistan Asia 1977 38.4 14880372 786.
7 Afghanistan Asia 1982 39.9 12881816 978.
8 Afghanistan Asia 1987 40.8 13867957 852.
9 Afghanistan Asia 1992 41.7 16317921 649.
10 Afghanistan Asia 1997 41.8 22227415 635.
# ... with 1,694 more rows
On peut noter que le jeu de données n'a pas subit de modifications évidentes, car il constate encore de 1'706 lignes et 6 colonnes (ligne 1 dans le code). En revanche, le output confirme à la ligne 3 que nous avons bien grouper par continent et que le nombre de groupes et de [5], ce qui correspond effectivement aux 5 continents.
Il en résulte que la fonction group_by()
n'effectue aucune opération perceptible au niveau du jeu des données, mais préparent le jeu des données pour un traitement groupé qui peut être effectué par la suite, par exemple à travers la fonction summarise()
, vue plus haut, mais également d'autres, comme mutate()
qu'on verra plus bas dans l'article.
Si on reprend la fonction summarise()
exactement comme l'exemple dans le préambule pour calculer la moyenne (mais sans filtrer par année), on aura le code suivant :
gapminder %>%
group_by(continent) %>%
summarise(avg_pop = mean(pop))
Ce qui produit ce résultat :
# A tibble: 5 x 2
continent avg_pop
<fct> <dbl>
1 Africa 9916003.
2 Americas 24504795.
3 Asia 77038722.
4 Europe 17169765.
5 Oceania 8874672.
Groupement avec plusieurs variables
On peut passer plusieurs variables/colonnes de groupement à la fonction group_by()
en le séparant par une virgule. L'ordre des variables peut être déterminant selon le type de fonction d'agrégation utilisé. Dans le cas de cet exemple, l'impact est limité à l'ordre d'affichage des observations, mais ne modife pas les moyennes calculés.
Maintenant donc d'abord le groupement par continent comme dans l'exemple avec une seule variable, mais ajoutons cette fois-ci également le groupement par year :
gapminder %>%
group_by(continent, year)
Le résultat de cette instruction est très similaire à celui avec une seule variable, si ce n'est pour la ligne 2 mise en évidence ci-bas :
# A tibble: 1,704 x 6
# Groups: continent, year [60]
country continent year lifeExp pop gdpPercap
<fct> <fct> <int> <dbl> <int> <dbl>
1 Afghanistan Asia 1952 28.8 8425333 779.
2 Afghanistan Asia 1957 30.3 9240934 821.
3 Afghanistan Asia 1962 32.0 10267083 853.
4 Afghanistan Asia 1967 34.0 11537966 836.
5 Afghanistan Asia 1972 36.1 13079460 740.
6 Afghanistan Asia 1977 38.4 14880372 786.
7 Afghanistan Asia 1982 39.9 12881816 978.
8 Afghanistan Asia 1987 40.8 13867957 852.
9 Afghanistan Asia 1992 41.7 16317921 649.
10 Afghanistan Asia 1997 41.8 22227415 635.
# ... with 1,694 more rows
On peut voir ici que le mécanisme de groupement produit [60] groupes, et non pas [5] comme dans l'exemple avec seulement le continent. Les 60 groupes sont en effet le produit du croisement entre les 5 continents fois les 12 années de recueil des données. Lorsqu'on ajoute la même fonction summarise()
pour calculer la moyenne, le code complet est le suivant :
gapminder %>%
group_by(continent, year) %>%
summarise(
avg_pop = mean(pop)
)
Ce code produit un résultat différent par rapport à l'exemple précédent, car la moyenne de la population pour chaque continent est calculée pour chacun des 12 ans de recueil des données :
# A tibble: 60 x 3
# Groups: year [12]
year continent avg_pop
<int> <fct> <dbl>
1 1952 Africa 4570010.
2 1952 Americas 13806098.
3 1952 Asia 42283556.
4 1952 Europe 13937362.
5 1952 Oceania 5343003
6 1957 Africa 5093033.
7 1957 Americas 15478157.
8 1957 Asia 47356988.
9 1957 Europe 14596345.
10 1957 Oceania 5970988
# ... with 50 more rows
Utiliser des fonctions pour résumer des observations groupées
Comme on a vu plus haut, la fonction de dplyr summarise()
(ou l'équivalence summarize()
) permet de créer de résumer des observations selon les critères de groupement. Concrètement, la fonction summarise()
s'occupe de créer un nouveau data.frame ou tibble avec des variables qui sont calculées sur la base de fonctions selon la notation :
# Notation d'exemple
data %>%
group_by(criterion) %>%
summarise(
reference_to_newly_defined_variable = function_to_summarise(which_variable_of_data)
)
À l'intérieur de la fonction summarise()
on peut définir autant des variables qu'on veut, en séparant les associations avec des virgules. Voici un exemple qui utilise cinq fonctions pour afficher des valeurs calculées à propos de la variable lifeExp groupée par continent :
# Plusieurs indicateurs résumés par continent
gapminder %>%
group_by(continent) %>%
summarise(
avg_lifeExp = mean(lifeExp),
sd_lifeExp = sd(lifeExp),
min_lifeExp = min(lifeExp),
max_lifeExp = max(lifeExp)
)
Le code s'explique de la manière suivante :
- À la ligne 4, on calcule la moyenne de lifeExp avec
mean()
- À la ligne 5, on calcule l'écart type de lifeExp avec
sd()
- À la ligne 6, on calcule la valeur minale de lifeExp avec
min()
- À la ligne 7, on calcule la valeur maximale de lifeExp avec
max()
Le résultat de cette instruction est le suivant :
# A tibble: 5 x 5
continent avg_lifeExp sd_lifeExp min_lifeExp max_lifeExp
<fct> <dbl> <dbl> <dbl> <dbl>
1 Africa 48.9 9.15 23.6 76.4
2 Americas 64.7 9.35 37.6 80.7
3 Asia 60.1 11.9 28.8 82.6
4 Europe 71.9 5.43 43.6 81.8
5 Oceania 74.3 3.80 69.1 81.2
À noter que le nom de la variable (e.g. mean_lifeExp) est totalement arbitraire, on peut utiliser la référence symbolique que l'on souhaite. En revanche, le nom de la variable qui est passé dans la fonction qui resume les observations doit exister dans le jeu de données qu'on veut résumer (dans ce cas, l'une des variables existantes dans gapminder
.
On peut résumer plusieurs variables différentes dans la même fonction summarise()
. Dans l'exemple précédent on s'est intéressé exclusivement à lifeExp, mais on peut par exemple calculer la moyenne de lifeExp, pop, et gdpPercap dans le même sommaire :
# Différentes moyennes
gapminder %>%
group_by(continent) %>%
summarise(
avg_lifeExp = mean(lifeExp),
avg_pop = mean(pop),
avg_gdpPercap = mean(gdpPercap)
)
On pourrait même mixer différentes fonctions et différentes variables (e.g. la moyenne de lifeExp et l'écart type de pop), mais à ce moment c'est la cohérence interne du nouveau jeu de données qui détermine si ces informations ont du sense regroupées de telle manière. Comme indiqué dans l'introduction à Tidyverse selon le principe du Tidy data, chaque data.frame ou tibble devrait représenter une unité informationnelle congruente.
Fonctions utiles pour résumer des observations
Voici une liste non exhaustive des fonctions qui permettent de résumer/réduire des observations (tirée de la documentation officielle de dplyr) :
- Mesures de tendance centrale :
mean()
,median()
- Mesures de dispersion :
sd()
,IQR()
,mad()
- Mesures d'écart :
min()
,max()
,quantile()
- Mesures de position :
first()
,last()
,nth()
, - Mesures de comptage :
n()
,n_distinct()
- Fonctions logiques :
any()
,all()
Certains de ces fonctions sont utilisées dans les exemples suivants, qui utilisent souvent également des manipulations de filtrage et triage illustrées plus haut dans l'article.
Compter les nombres d'observations dans un groupe
Une action souvent utile concerne à determiner le nombre d'observations qui respectent un critère de groupement. On peut utiliser à cet effet les fonctions n()
ou n_distinct()
. La différence entre les deux, comme le nom l'implique, consiste dans le fait que n_distinct()
ne tient pas compte de valeurs dupliquées.
Voici un simple exemple qui compte le nombre de pays par continent en 2007 :
# Nombre de pays par continent
gapminder %>%
filter(year == 2007) %>%
group_by(continent) %>%
summarise(
n_countries = n()
)
Ce qui donne le résultat suivant :
# A tibble: 5 x 2
continent n_countries
<fct> <int>
1 Africa 52
2 Americas 25
3 Asia 33
4 Europe 30
5 Oceania 2
Récupérer la première/dernière observation dans un groupe
On peut exploiter les fonctions first()
et last()
pour récupérer la première ou la dernière observation pour chaque groupe. Le code suivant récupère le pay avec l'espérance de vie la plus élevée pour chaque année disponible :
# Pays avec l'espérance de vie la plus élevée pour les différents années
gapminder %>%
group_by(year) %>%
arrange(desc(lifeExp)) %>%
summarise(
country = first(country),
lifeExp = first(lifeExp)
)
La fonction first()
retient la première valeur de la variable indiquée pour chaque sous-groupe. De ce fait, le résultat du code est le suivant :
# A tibble: 12 x 3
year country lifeExp
<int> <fct> <dbl>
1 1952 Norway 72.7
2 1957 Iceland 73.5
3 1962 Iceland 73.7
4 1967 Sweden 74.2
5 1972 Sweden 74.7
6 1977 Iceland 76.1
7 1982 Japan 77.1
8 1987 Japan 78.7
9 1992 Japan 79.4
10 1997 Japan 80.7
11 2002 Japan 82
12 2007 Japan 82.6
Créer ou modifier des variables/colonnes
dplyr permet des opérations au niveaux des variables/colonnes à travers les fonctions mutate()
et transmute()
. Les deux fonctions ont un fonctionnement très similaire, si ce n'est pour une différence importante :
mutate()
garde intactes les variables/colonnes qui ne sont pas concernées par l'opérationtransmute()
crée un jeu de données avec seulement les variables/colonnes qui sont concernées par l'opération
Un exemple concret peut aider à mieux cerner la différence. Nous allons créer une nouvelle variable/colonne gdpTot qui propose le produit intérieur brute totale du pays. Pour obtenir ce résultat, nous allons tout simplement multiplier la variable/colonne gdpPercap fois la variable/colonne pop.
- Utilisation de
mutate()
# Utilisation de mutate()
gapminder %>%
mutate(gdpTot = gdpPercap * pop)
Cette opération retourne un jeu de données avec 7 variables/colonnes : les 6 variables originales de gapminder
, plus la nouvelle variable gdpTot que nous venons de créer.
- Utilisation de
transmute()
# Utilisation de transmute()
gapminder %>%
transmute(gdpTot = gdpPercap * pop)
Cette opération retourne un jeu de données avec 1 seule variable/colonne, c'est-à-dire la nouvelle variable gdpTot que nous venons de créer, tandis que les 6 variables originales de gapminder
ne sont pas retenues.
Dans la suite de la section nous utiliserons seulement mutate()
, mais le code s'applique également à transmute()
.
Créer ou modifier
Le mécanisme de création ou modification est très simple :
- Créer : pour créer une nouvelle variable, il suffit de spécifier un nom qui n'existe pas déja.
mutate(nouveau_nom = valeur)
- Modifier : pour modifier une variable existante, il faut passer le nom précis de la variable déjà existante.
mutate(nom_existant = valeur)
Les exemples de code suivants s'appliquent à la création ou à la modification selon ce simple mécanisme.
Agir sur plusieurs variables à la fois
On peut agir sur plusieurs variables à la fois (créer et/ou modifier en même temps) simplement en passant plusieurs combinaison de variable = valeur
à l'intérieur de la même fonction mutate()
. Lorsqu'on a plusieurs variables concernées, il peut être utile d'utiliser une indentation et le retour à la ligne pour favoriser la lecture du code :
# Syntaxe d'exemple
data %>%
mutate(
var1 = ...,
var2 = ...,
var3 = ...,
...
)
Attribuer à une variable une valuer calculée selon d'autres variables
Une opération fréquente avec mutate()
consiste à calculer une variable en fonction des valeurs d'une ou plusieurs autres variables disponibles dans le même jeu. On a vu un exemple de ce principe avec la création de gdpTot dans le préambule de cette section, dont le code est repris ici :
# Créer une variable avec le PBI total
gapminder %>%
mutate(gdpTot = gdpPercap * pop)
Il est donc suffisant de passer le nom précis de la variable pour accéder à sa valeur relative à chaque observation/ligne du jeu des données.
On peut combiner des références à des variables du jeu avec des valeurs littéraux. Par exemple, on peut transformer la population pour l'exprimer en millions d'habitants :
# Créer une variable avec population exprimée par mio. d'habitants
gapminder %>%
mutate(popInMio = pop / 1000000)
Attribuer à une variable une valeur selon certaines conditions
dplyr met à disposition deux fonctions utiles pour attribuer à une variable une valeur selon certaines conditions :
if_else()
case_when()
La fonction if_else()
La fonction if_else()
peut être considérée un cas particulier de case_when()
, car elle s'applique à la comparaison entre seulement deux conditions. La fonction accepte en effet trois arguments :
if_else(condition_à_evaluer, valeur_si_condition_vraie, valeur_si_condition_fausse
On peut voir le mécanisme à l'oeuvre même sans utiliser encore mutate()
:
if_else(10 < 100, "C'est vrai", "C'est faux")
Dans ce cas, 10 est inférieur à 100 est donc la valeur qui sera retenue par la fonction est "C'est vraie". On peut intégrer cette fonction à l'intérieur de mutate()
. Pour montrer cette intégration, on peut imaginer qu'un chercheur ne soit pas satisfait avec la valeur de gdpPercap. Selon lui, cette valeur est surestimée pour les années de 1952 à 1982 comprises. Il veut donc effectuer une correction qui diminue la valeur de gdpPercap de 10%, mais seulement pour les années faisant partie de cette période. Pour pouvoir comparer les deux valeurs, le chercheur décide de créer une nouvelle variable gdpPercap_corrected. Voici le code pour obtenir ce résultat :
# Calculer un nouveau gdpPercap corrigé selon l'année
gapminder %>%
mutate(
gdpPercap_corrected = if_else(year <= 1982, gdpPercap * 0.9, gdpPercap)
)
Le code d'intérêt se trouve à la ligne 4 et ce traduit de la manière suivante : si l'année est inférieur ou égale à 1982, alors multiplie la valeur actuelle de gdpPercap de 0.9 (ce qui équivaut à diminuer de 10%); autrement, passe la valeur actuelle sans modification.
La fonction case_when()
Avec case_when()
on peut évaluer plusieurs conditions à la fois, et définir une valeur de sortie qui est appliquée si aucune des conditions spécifiées n'est respectée. La notation de la fonction est la suivante :
case_when(
premiere_evaluation ~ "Valeur si vraie",
deuxieme_evaluation ~ "Valeur si vraie",
troisieme_evaluation ~ "Valeur si vraie",
...
TRUE ~ "Valeur si aucune des conditions précédente était vraie"
)
Les deux éléments à signaler sont :
- l'utilisation de
~
qui équivaut à dire si vraie, alors applique cette valeur - l'utilisation de
TRUE
dans la dernière évaluation, qui est une manière de dire cette évaluation sera vraie dans tous les cas, et par conséquent la valeur après le~
est attribué automatiquement.
Voyons le mécanisme à l'oeuvre dans un exemple. Imaginons qu'un chercher soit intéressé à diviser les populations en trois catégories :
- "Small country" si la population est inférieur à 1mio.
- "Medium country" si la population est 1mio. ou plus, mais inférieur à 10mio.
- "Big country" si la population est 10 mio. ou plus, mais inférieur à 100mio.
- "Huge country" dans tous les autres cas
Le code pour obtenir cette catégorisation dans une nouvelle variable est le suivant :
# Ajouter une catégorisation selon la population
gapminder %>%
mutate(
pop_category = case_when(
pop < 1000000 ~ "Small country",
pop < 10000000 ~ "Medium country",
pop < 100000000 ~ "Big country",
TRUE ~ "Huge country"
)
)
Attribuer à une variable une valeur selon un mécanisme de groupement
Il est possible d'utiliser mutate()
en combination avec la fonction de groupement group_by()
illustrée plus haut. Ce mécanisme est particulièrement utile si on veut attribuer des valeurs sur la base d'éléments calculés en fonction du sous-groupe. L'exemple suivant met en évidence cette logique en comparant deux attributions, une non-groupée et l'autre groupée.
Imaginons qu'un chercheur veut ajouter une variable qui calcule la variation du gdpPercap par rapport à la moyenne. Or, il peut faire ce calcule par exemple :
1. Sur la base de la moyenne globale des observations, ou 2. Sur la base de la moyenne du continent
Le code suivant montre la différence dans les deux instructions :
# Attribution non-groupée
gapminder %>%
mutate(
diff_mean = gdpPercap - mean(gdpPercap)
)
# Attribution groupée
gapminder %>%
group_by(continent) %>%
mutate(
diff_mean = gdpPercap - mean(gdpPercap)
)
)
Les premières lignes des deux jeux des données, que nous ne reportons pas ici pour brévité, sont suffisantes pour s'apercevoir que la nouvelle variable n'a pas la même valeur selon que la moyenne soit globale ou relative au continent.
Renommer une ou plusieurs colonnes
Avec dplyr on peut égalemenr renommer une ou plusieurs colonnes. La fonction rename
permet de modifier le nom d'une colonne, tandis que rename_with
permet de renommer plusieurs colonnes, en utilisant notamment une fonction pour changer le nom de manière programmée.
rename()
Avec la fonction rename()
on attribut un nouveau nom selon le pattern new_name = old_name. Par exemple :
gapminder |>
rename(
life_exp = lifeExp,
gdpPercap = gdp_percap
)
rename_with()
Avec la fonction rename_with()
on peut utiliser une fonction pour modifier le nom d'une sélection de colonnes (avec les mêmes notations de la fonction select()
) ou de toutes les colonnes à la fois (avec la fonction everything()
).
Par exemple, cette commande change tous les noms des colonnes en minuscule avec la fonction tolower()
:
gapminder |>
rename_with(~ tolower(.x), everything())
On notera comme le placeholder .x
correspond à chaque fois au nom de la colonne actuelle.
Une application utile de cette fonction est la possibilité par exemple d'appliquer des préfixes :
gapminder |>
rename_with(~ paste0("num_", .x), year:gdpPercap)
Cette fonction applique le préfixe "num_" aux colonnes de year à gdpPercap.
Manipulations sur plusieurs tables
dplyr permet d'effectuer des opérations qui concernent deux ou plus tables. Dans cette section, nous illustrerons principalement les opérations sur deux tables, mais par extension ces opérations peuvent s'effectuer aussi sur plusieurs tables. Les opérations peuvent être divisées en trois catégories :
- Combiner les données depuis deux tables pour créer un nouveau jeu de données
- Créer des filtres croisés, c'est-à-dire filtrer les données d'une table en fonction d'une autre table
- Utiliser les tables comme des ensembles (e.g. intersection, union, ...)
Combiner des données depuis plusieurs tables
Combiner par observations/lignes
Il est souvent utile de combiner deux ou plusieurs jeux de données qui proposent le même type de données, par exemple lors de l'importation depuis plusieurs fichiers qui ont la même structure, c'est-à-dire les mêmes variables/colonnes. Pour ce faire, dplyr met à disposition la fonction bind_rows()
qui accepte deux data frames ou plus à combiner. L'un des avantages de bind_rows()
concerne le fait que les jeux de données doivent avoir exactement les mêmes noms des variables/colonnes, mais pas forcément dans le même ordre.
Pour montrer le fonctionnement de bind_rows()
en restant dans l'exemple des données gapminder
, nous allons d'abord créer des jeux des données en créant des filtre du jeu de données en entier, pour ensuite les recombiner. On pourrait obtenir le même résultat avec des filtres (voir plus haut), donc les exemples ne sont pas pertinents au niveau de leur praticité, mais plutôt pour comprendre le fonctionnement.
data_switzerland <- gapminder %>%
filter(country == "Switzerland")
data_france <- gapminder %>%
filter(country == "France")
# Combiner les deux jeux de données
data_combined <- bind_rows(data_switzerland, data_france)
data_combined
contient à ce moment 24 observations/lignes, c'est-à-dire 12 observations pour les deux pays Suisse et France.
Combiner par variables/colonnes
On peut combiner deux jeux de données en fonction des variables/colonnes, c'est-à-dire ajouter une ou plusieurs variables/colonnes d'un jeu de données à un autre. L'exemple suivant crée une sorte de jeu de données hybride en ajoutant le gdpPercap de la France à lifeExp de la Suisse :
data_switzerland <- gapminder %>%
filter(country == "Switzerland") %>%
select(country, lifeExp)
data_france <- gapminder %>%
filter(country == "France") %>%
select(gdpPercap)
hybrid_nation <- bind_cols(data_switzerland, data_france)
hybrid_nation
contient à ce moment deux colonnes de la Suisse (country et lifeExp), ainsi qu'une colonne de la France (gdpPercap). Il faut noter que dans ce type de combination l'ordre des observations/lignes et important, car les données sont intégrées dans l'ordre.
Joindre deux tables
dplyr met à disposition plusieurs fonctions de type *_join()
qui permettent de joindre deux ou plusieurs tables :
inner_join()
: retient les données qui sont à la fois dans les deux tablesleft_join()
: maintient les données de la première table et, si possible, intègre les correspondantes dans la deuxième table. Cette fonction est en général la plus utiliséeright_join()
: fonction spéculaire àleft_join()
full_join()
: retient toutes les données des deux tables
Lorsque des données ne sont pas disponibles dans l'une ou l'autre table, elles sont remplacées par NA
, c'est-à-dire l'absence de valeur.
La compréhension des fonctions de fonction nécessite surtout de la pratique, car conceptuellement elles restent très abstraites. Mais elles peuvent être très utiles pour combiner différentes sources de données qui partagent une relation sémantique entre elles.
Pour illustrer un simple exemple, nous allons exploiter le jeu de données country_codes
mis à disposition par gapminder qui propose les abbréviations de chaque pays (iso_alpha), ainsi qu'un code numérique conventionnel pour identifier le pays (iso_number). On peut voir la structure de ce jeu de données avec la commande :
str(country_codes)
On peut relever que le jeu de données propose 187 observations :
tibble [187 x 3] (S3: tbl_df/tbl/data.frame)
$ country : chr [1:187] "Afghanistan" "Albania" "Algeria" "Angola" ...
$ iso_alpha: chr [1:187] "AFG" "ALB" "DZA" "AGO" ...
$ iso_num : int [1:187] 4 8 12 24 32 51 533 36 40 31 ...
Imaginons maintenant que pour des raisons d'affichage, un chercheur préfère utiliser l'abbréviation officielle de la nation plutôt que le nom complet. On peut utiliser la fonction left_join()
pour combiner les données du jeu de données gapminder
avec les abbreviations du jeu de données country_codes
. Comme on peut le noter depuis la structure, les deux jeux de données partagent en effet la variable country. Dans l'exemle suivant, le code filtre d'abord par l'année 2007 ainsi d'avoir une seule fois chaque nation dans le jeux de données combiné :
integrated_country_code <- gapminder %>%
filter(year == 2007) %>%
left_join(country_codes, by = "country")
Le résultat de cette instruction est un jeu de données de 142 observations de gapminder
, auxquelles on a ajouté les colonnes iso_alpha et iso_num depuis country_codes
. Vous pouvez remarquez que certains pays disponibles dans le jeu de données country_codes
(avec 187 observations) n'ont pas servi, car ces pays ne figurent pas dans le jeu de données gapminder
. Voici les premières observations du jeu de données créé :
# A tibble: 142 x 8
country continent year lifeExp pop gdpPercap iso_alpha iso_num
<chr> <fct> <int> <dbl> <int> <dbl> <chr> <int>
1 Afghanistan Asia 2007 43.8 31889923 975. AFG 4
2 Albania Europe 2007 76.4 3600523 5937. ALB 8
3 Algeria Africa 2007 72.3 33333216 6223. DZA 12
4 Angola Africa 2007 42.7 12420476 4797. AGO 24
5 Argentina Americas 2007 75.3 40301927 12779. ARG 32
...
Si on utilise la fonction right_join()
dans le même exemple, le résultat est un jeu de données de 187 observations, dont 45 pays (187 - 142) n'ont pas de données récoltés. On peut les voir si après la fonction de jonction on filtre par exemple pour des valeurs manquantes dans year :
integrated_country_code_right_join <- gapminder %>%
filter(year == 2007) %>%
right_join(country_codes, by = "country") %>%
filter(is.na(year))
Les premières lignes du résultat de cette fonction sont les suivantes :
# A tibble: 45 x 8
country continent year lifeExp pop gdpPercap iso_alpha iso_num
<chr> <fct> <int> <dbl> <int> <dbl> <chr> <int>
1 Armenia NA NA NA NA NA ARM 51
2 Aruba NA NA NA NA NA ABW 533
3 Azerbaijan NA NA NA NA NA AZE 31
4 Bahamas NA NA NA NA NA BHS 44
5 Barbados NA NA NA NA NA BRB 52
...
Créer des filtres croisés
Il est souvent utile dans le cadre de deux ou plusieurs jeux de données de filtrer les données en fonction de la présence ou de l'absence de correspondance dans les tables. À ce propos, dplyr met à disposition deux fonctions utiles :
semi_join()
: cette fonction retient toutes les observations dans la première table seulement si elles ont une correspondance dans la deuxième.anti_join()
: cette fonction fait le contraire, c'est-à-dire qu'elle ne retient que les observations qui n'ont pas de correspondance dans la deuxième table. Cette fonction peut-être utile par exemple pour afficher les participants exclus dans les résultats pour manque de données dans une unité informationnelle spécifique.
Voici deux exemples qui utilisent ce fonction en combinant les jeux de données country_codes
et gapminder
. Pour rappel, coutry_codes
contient 187 pays, tandis que le jeu de données gapminder
a récolté de manière consistente les données seulement de 142 pays.
# Retenir les pays avec les données
countries_with_data <- country_codes %>%
semi_join(gapminder, by = "country")
# Donne un jeu de données avec les 142 pays avec données récoltées
# Retenir les pays qui n'ont pas de données
countries_without_data <- country_codes %>%
anti_join(gapminder, by = "country")
# Donne les 45 pays dont les données ne figure pas dans gapminder
À noter que, contrairement aux fonctions de joction illustrées plus haut, dans ce cas le jeu de données maintient seulement les variables/colonnes originales de la première table, sans ajouter rien de la deuxième, car il s'agit en effet de fonctions de filtrage et pas de jonction.
Utiliser les tables comme des ensembles
Les jeux des données peuvent être considérés également des ensembles et considérés donc en fonction de la théorie mathématique des ensembles. dplyr met à disposition trois fonctions utiles à cet effet :
intersect()
qui retient les observations communes entre deux jeux de donnéesunion()
qui combine toutes les observations de deux jeux de données, mais sans dupliquer les observations communessetdiff()
qui retient les observations propres à un jeu de données et donc pas présente dans l'autre
Pour montrer le fonctionnement des troi fonctions avec le jeu de données gapminder
nous allons d'abord créer deux jeux de données de la manière suivante :
- Le premier contient des observations des pays d'Afrique et d'Europe
- Le deuxième contient des observations des pays d'Asie et d'Europe
Les données sont filtrées dans une année spécifique pour réduire le nombre d'observations, mais ce qui est important à retenir concerne la présence des données des pays d'Europe dans les deux jeux de données.
africa_and_europe <- gapminder %>%
filter(continent %in% c("Africa", "Europe")) %>%
filter(year == 1952)
asia_and_europe <- gapminder %>%
filter(continent %in% c("Asia", "Europe")) %>%
filter(year == 1952)
# Retient données communes, c'est-à-dire seulement les pays d'Europe
intersect(africa_and_europe, asia_and_europe)
# Retient les pays des trois continents, mais sans dupliquer les pays d'Europe
union(africa_and_europe, asia_and_europe)
# Retient seulement les pays d'Afrique
setdiff(africa_and_europe, asia_and_europe)
# Retient seulement les pays d'Asie (spéculaire au précédent)
setdiff(asia_and_europe, africa_and_europe)
Le paquet dbplyr
Le paquet dbplyr (avec le préfix db depuis database) permet d'appliquer les mêmes principes de dplyr aux données qui sont contenues directement dans une base de données. Le paquet s'intègre avec différents types de base de données et permet donc de maintenir une grammaire similaire pour la manipulation des données indépendamment du système utilisé par la base de données sous-jacente.
Pour plus d'informations, se référer à la documentation officielle :
Conclusion
Cet article a présenté un aperçu du paquet dplyr qui permet de manipuler des données à l'intérieur d'un ou plusieurs jeux de données, avec des opérations qui sont très fréquentes et, par conséquent, très utiles dans l'analyse des données.
Les concepts traités dans cet article nécessite d'un certain temps pour être compris et maitrisés, surtout pour des novices, pour lesquels l'utilité même de certains fonctions n'est pas évidente. En effet, pour exploiter aux maximum les potentialités de dplyr il faut avoir d'abord une bonne vision d'ensemble de données qui peuvent être organisées de différentes manières, notamment selon les principes tidy data expliqués dans l'[[introduction à tidyverse].
La possibilité d'intéroger les données, ainsi que de pouvoir les filtrer de manière complexe, sont néanmoins des éléments assez évident, qui peuvent être appliqués assez facilement même par des débutants, une fois que la grammaire et le codage deviennent de plus en plus familiers.
Ressources
Une collection de ressources pour approfondir ou consolider les connaissances de dplyr :
- Site officiel du paquet
- Manipuler les données avec dplyr : partie sur dplyr d'une ressources plus complète sur Tidyverse écrite par Julien Barnier
- Introduction to dplyr : documentation contenus dans le paquet sur CRAN
- Row-wise operations : cet article a abordé principalement des opérations sur les colonnes/variables, mais dplyr permet également d'agir sur les lignes/observations