Manipuler des données avec dplyr

De EduTech Wiki
Aller à la navigation Aller à la recherche

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 :

Cet article propose un survol des manipulations les plus fréquentes, ainsi que des ressources complémentaires.

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 :

  1. 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 commande levels(gapminder$country)
  2. 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 commande levels(gapminder$continent)
  3. 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 commande unique(gapminder$year) (car il s'agit de chiffres et pas d'un facteur comme les variables précédentes).
  4. lifeExp : l'espérance de vie à la naissance en années
  5. pop : la population de la nation à l'année de référence
  6. gdpPercap : 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.

À partir de la version de R 4.1, il est possible également d'utiliser le pipe disponible directement dans le langage de base |>. 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 similaire
  • ends_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)

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ération
  • transmute() 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)