C’est quoi, le tidyverse ?

le tidyverse
Tags : Autour de R, Ressources
Date :

On en entend de plus en plus parler, son utilisation devient la norme quand on code en R, et quand on met le nez dedans, on ne peut plus s’en passer ! Que ce soit pour importer, mettre en forme, manipuler, visualiser ou modéliser les données, le tidyverse saura vous donner une solution élégante et répondra à (presque) tous vos besoins.

Introduction au tidyverse

On vous en parlait déjà en 2016 à l’occasion de son avènement, le tidyverse, qui s’appelait à l’origine le hadleyverse, n’a cessé d’évoluer depuis !

Alors ok, on peut faire tout un tas de choses grâce au tidyverse, mais de quoi on parle exactement ? “tidyverse”, contrction de “tidy” et de “universe”, c’est un univers bien rangé si l’on traduit mot à mot. Dans les faits, il s’agit d’un ensemble de packages R qui sont conçus pour fonctionner ensemble et suivant de ce fait une même logique de code et une grammaire commune. Aujourd’hui, ces packages sont officiellement au nombre de 26, pas moins ! Cet univers est en constante évolution, en témoignent les 36 repositories Git, alimentés par 21 contributeurs majeurs dont le fameux Hadley Wickham qui est à l’origine de cet ambitieux projet.

Installation et chargement

L’installation de l’ensemble des packages du tidyverse se fait via la ligne de commande suivante :

install.packages("tidyverse")

Par ailleurs, lorsque l’on charge le tidyverse via :

library(tidyverse)

les packages du tidyverse les plus communément utilisés sont chargés dans votre session :

  • {ggplot2}
  • {dplyr}
  • {tidyr}
  • {readr}
  • {purrr}
  • {tibble}
  • {stringr}
  • {forcats}

Les autres packages seront à charger de manière indépendante en fonction de vos besoins. Pour avoir la liste exhaustive des packages du tidyverse :

tidyverse::tidyverse_packages()
##  [1] "broom"      "cli"        "crayon"     "dbplyr"     "dplyr"      "forcats"    "ggplot2"   
## [15] "pillar"     "purrr"      "readr"      "readxl"     "reprex"     "rlang"      "rstudioapi"
## [22] "rvest"      "stringr"    "tibble"     "tidyr"      "xml2"       "tidyverse"

Note : certains packages, comme par exemple {glue} qui nous propose une alternative à la fonction paste(), ne sont pas encore dans cette liste, et ne sont pas installés avec les autres packages du tidyverse, mais ils sont tout de même considérés comme en faisant partie.

{magrittr} ou le règne du pipe

Le pipe, ou %>%, c’est l’un des éléments centraux du tidyverse. Il permet de structurer des séquences d’opérations en minimisant la création d’objets intermédiaires et en facilitant l’ajout d’une étape n’importe où dans cette séquence. Il est utilisé PARTOUT dans le tidyverse, à tel point que la fonction %>% est exportée par tous ces packages, d’où le fait que {magrittr} ne soit pas automatiquement chargé avec les autres packages principaux.

Bon du coup, comment on l’utilise, ce fameux pipe ? Déjà, on le lit de gauche à droite : “fais ça, et puis ça, et ensuite ça, …”. Autrement dit : x %>% f() sera équivalent à f(x), et de la même manière, x %>% f() %>% g() sera équivalent à g(f(x)). Bien plus lisible avec le pipe non ? un exemple un peu plus complet :

library(magrittr) 
# le chargement de magrittr n'est pas utile ici si on a déjà chargé un autre package du tidyverse
iris %>%
  arrange(Sepal.Length) %>% 
  filter(Petal.Length > 5) %>% 
  head(5) %>% 
  write_csv2("mon_iris.csv")

En ‘pseudo code’ le %>% permet d’écrire ce genre de choses :

moi %>% 
  je_me_leve(horaire = "8h00") %>% 
  je_m_habille(haut = "chemise", bas = "jeans") %>% 
  prend_le_petit_dej()

ce qui est toujours plus lisible que :

prend_le_petit_dej(je_m_habille(je_me_leve(moi, horaire = "8h00"),
                                haut = "chemise", bas = "jeans"))

non ?

Le %>% classique connait quelques variations, comme %T>% ou %<>%. On ne résiste pas à l’envie de vous les présenter, même si on peut vivre une vie épanouie sur R sans connaître ou utiliser ces pipes alternatifs (presque de l’ordre de la curiosité), c’est toujours intéressant pour la culture-R.

Un autre genre de pipe qui peut être intéressant et qui nous vient également de {magrittr}, c’est le %T>% : on l’utilise dans le cas où l’une de nos instructions ne renvoie rien, comme par exemple plot(), mais que l’on souhaite tout de même garder un résultat intermédiaire de notre séquence d’instructions afin de lui appliquer une autre instruction. %T>% gardera ce qu’il y a avant son appel :

iris %>% 
  select(Petal.Length, Petal.Width) %T>%
  plot() %>% 
  summary()

##   Petal.Length    Petal.Width   
##  Min.   :1.000   Min.   :0.100  
##  1st Qu.:1.600   1st Qu.:0.300  
##  Median :4.350   Median :1.300  
##  Mean   :3.758   Mean   :1.199  
##  3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :6.900   Max.   :2.500

Et comme ils n’ont fait pas les choses à moitié, ce qu’on appelle le “diamond pipe” et qui est un combo pipe + assignation est matérialisé dans {magrittr} par %<>%. Comment ça marche ? C’est très simple : au lieu d’écrire :

iris <- iris %>%
  filter(Petal.Length > mean(Petal.Length))

On écrira :

iris %<>%
  filter(Petal.Length > mean(Petal.Length))

Pretty cool non ?! (Bon en vrai, en termes de bonnes pratiques, c’est pas génial, mais c’est cool 🙂 )

Et enfin, si vous avez besoin d’utiliser une fonction à laquelle on ne peut passer en paramètre que des vecteurs dans votre série d’instructions, vous pourrez toujours utiliser le pipe %$%. Petite démo :

iris %$%
  cor(Sepal.Length, Sepal.Width)
## [1] 0.3315529

On évite ainsi l’erreur suivante :

iris %>% 
  cor(Sepal.Length, Sepal.Width)

Note : seul le pipe %>% est exporté par les autres packages du tidyverse, pour les autres, il faudra explicitement charger {magrittr} via library(magrittr).

L’import des données

De nombreux packages du tidyverse sont dédiés à la lecture et à l’écriture des données depuis R ; à chaque package correspond un type particulier de données, ces dernières étant importées sous forme de tibbles.

Le package {readr}

Il permet d’importer et écrire une large gamme de fichiers plats :

Lecture Ecriture
read_lines() write_lines()
read_file() write_file()
les fichiers plats en général, ayant un séparateur read_delim() write_delim()
les csv read_csv() (séparateur ,) et read_csv2() (séparateur ;) write_csv() et read_csv2()
les fichiers avec un séparateur Tab read_tsv() write_tsv()
les fichiers avec un séparateur Espace, simple, double, ou plus read_table() et read_table2()
les fichiers log read_log()
es fichiers à largeur fixe (toutes les lignes ont le même nombre de caractères) read_fwf()

La cheat sheet de {readr} c’est ici !

Le package {readxl}

Il est propre à l’import de fichiers Excel :

  • read_xls() pour les fichiers .xls
  • read_xlsx() pour les fichiers .xlsx
  • read_excel() pour les deux types de fichiers

Le package {xml2}

Il permet de parser, télécharger et écrire :

  • des fichiers XML, via les fonctions read_xml(), download_xml(), write_xml()
  • des fichiers HTML, via les fonctions read_html(), downlad_html(), write_html()

Le package {httr}

Il s’agit d’une surcouche du package {curl}, permettant de requêter des APIs grâce aux fonctions GET(), HEAD(), PATCH(), PUT(), DELETE() et POST().

Le package {jsonlite}

Il est spécifique à la lecture et à l’écriture de fichiers JSON, ainsi qu’à la conversion vers et depuis R :

  • parse_json(), read_json() et fromJSON() : JSON -> R
  • write_json() et toJSON() : R -> JSON

Le package {haven}

Surcouche du package écrit en C ReadStat, {haven} permet l’import de données issues d’autres langages/logiciels statistiques :

  • SAS : la fonction read_sas() importe les données depuis des fichiers .sas7bdat et .sas7bcat, et read_xpt() les fichiers .xpt (SAS transport files)
  • SPSS : les fonctions read_sav() et write_sav() permettent, comme leur nom l’indique, la lecture et l’écriture de fichiers .sav, et la fonction read_por() lis les anciens fichiers .por
  • Stata : les fonctions read_dta() et write_dta() permetten quant à elles la lecture et l’écriture des fichiers .dta

Le package {rvest}

Nous vous en avions déjà parlé en 2017, en long, en large et en travers. C’est LE package pour faire du webscraping.

Après avoir importé vos données avec la fonction read_html() de {xml2}, les fonctions de {rvest} vous permettront d’accéder aux différentes parties du document HTML via

  • les selecteurs CSS grâce à la fonction html_nodes()
  • les noms de tags html grâce à la fonction html_name()

html_text() donne accès au texte, html_attr() au contenu d’un attribut, et html_attrs() à tous les attributs.

Il en est de même pour les fichiers parsés avec read_xml(), sur lesquels on pourra appeler les fonctions similaires aux précédentes : xml_nodes(), xml_name(), xml_attr() et xml_attrs().

Vous pourrez également :

  • parser des tables en data frames avec html_table()
  • extraire, modifier et soumettre des formulaires avec html_form(), set_values() et submit_form()
  • détecter et corriger des problèmes d’encoding avec guess_encoding() et repair_encoding()
  • naviguer sur une page web avec html_session(), jump_to(), follow_link(), back() ou encore forward()

La mise en forme et la manipulation des données

Maintenant que nous avons tous les outils pour importer nos données, sous quelque format dans lequel elles sont enregistrées, nous allons pouvoir les manipuler, les mettre en forme, les tordre dans tous les sens.

Le package {tibble}

Je vous disais plus haut que les données importées via les différents packages du tidyverse étaient de la classe tibble notée tbl_df. Mais c’est quoi un tibble au juste ? C’est la version moderne du data.frame, à quelques différences près :

  • les colonnes ne sont pas renommées automatiquement (si vous avez des espaces dans les noms de vos colonnes par exemple, ils ne seront pas remplacés par des points). Mais les colonnes ayant un nom identique seront suffixées pour les rendre uniques.
  • les types des variables ne seront pas modifiés : finito les stringsAsFactor = FALSE que l’on devait se coltiner (sur R < 4.0) à chaque import de données contenant des chaînes de caractères !
  • les tibbles disposent également de leur propre version de print() permettant un affichage beaucoup plus agréable à lire mais aussi plus complet. Seules les premières lignes sont affichés, mais on a par ailleurs la classe associée à chaque colonne, sous le nom de cette dernière. Par exemple, avec les données iris :
iris %>% as_tibble()
## # A tibble: 42 x 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species   
##           <dbl>       <dbl>        <dbl>       <dbl> <fct>     
##  1          6           2.7          5.1         1.6 versicolor
##  2          6.3         3.3          6           2.5 virginica 
##  3          5.8         2.7          5.1         1.9 virginica 
##  4          7.1         3            5.9         2.1 virginica 
##  5          6.3         2.9          5.6         1.8 virginica 
##  6          6.5         3            5.8         2.2 virginica 
##  7          7.6         3            6.6         2.1 virginica 
##  8          7.3         2.9          6.3         1.8 virginica 
##  9          6.7         2.5          5.8         1.8 virginica 
## 10          7.2         3.6          6.1         2.5 virginica 
## # ... with 32 more rows

Le passage de data.frame à tibble et inversement se fait simplement via les fonctions as_tibble() de {tibble} et as.data.frame() de R {base}.

Avec cette nouvelle classe, de nouvelles manières de subsetter les éléments, mais comme les tibbles sont également des data.frames, les fonctions old school seront toujours valides :

iris_tbl <- iris %>% as_tibble()
# extraction de la 2eme colonne de iris par son nom
iris_tbl$Sepal.Length
iris_tbl %>% .$Sepal.Length # version tidy
# ou bien
iris_tbl[["Sepal.Length"]]
iris_tbl %>% .[["Sepal.Length"]] # version tidy
# extraction de la 2eme colonne de iris par son indice
iris_tbl[[2]]
iris_tbl %>% .[[2]]  # version tidy

Et pour créer des tibbles from scratch, deux fonctions du package {tibble} :

  • tibble() pour créer des tibbles par colonne, de la même manière que l’on crée des data.frames :
tibble(
  v1 = rnorm(10),
  v2 = letters[1:10],
  v3 = as.Date("2020-01-01") + 0:9
)
## # A tibble: 10 x 3
##        v1 v2    v3        
##  1 -0.646 a     2020-01-01
##  2 -1.52  b     2020-01-02
##  3 -0.144 c     2020-01-03
##  4 -0.656 d     2020-01-04
##  5  0.364 e     2020-01-05
##  6  2.18  f     2020-01-06
##  7  0.322 g     2020-01-07
##  8 -0.750 h     2020-01-08
##  9 -0.760 i     2020-01-09
## 10 -0.310 j     2020-01-10
  • tribble() pour créer des tibbles par ligne, les noms de colonnes sont indiqués via des ~ :
tribble(
  ~colonne1, ~colonne2, ~colonne3,
  1, "a", as.Date("2020-01-01"),
  4, "b", as.Date("2020-01-02"),
  7, "c", as.Date("2020-01-03")
)
## # A tibble: 3 x 3
##   colonne1 colonne2 colonne3  
## 1        1 a        2020-01-01
## 2        4 b        2020-01-02
## 3        7 c        2020-01-03

Pour en savoir plus sur les tibbles, vous pouvez lire le chapitre dédié aux tibbles du Livre R for Datascience.

Mettre en forme ses données avec la package {tidyr}

{tidyr}, c’est LE package pour retourner ses données dans tous les sens, grâce à ses fonctions pivot_longer() et pivot_wider(). Je vous en avais déjà parlé à l’occasion de l’article Tout ce que vous voulez savoir sur le pivot, allez y faire un petit tour pour savoir ce que sont des données tidy et comment les obtenir en devenant un as du pivoting !

Mais dans ce package il y a aussi tout un tas de petites fonctions bien sympathiques, comme par exemple :

  • expand(), expand_grid(), crossing(), complete() et nesting() qui permettent de créer des combinaisons de listes ou de vecteurs
  • drop_na() qui supprime les lignes contenant des NA
  • separate(), extract() qui permettent de splitter une colonne de classe char en plusieurs colonnes et permettant d’utiliser des expressions régulières
  • ou au contraire unite() pour joindre des colonnes.

Manipuler ses données avec le package {dplyr}

Alors {dplyr}, c’est un peu la base du {tidyverse}. Ce package nous permet un grand nombre d’opérations indispensables à l’exploration, la manipulation et les
calculs sur les données :

  • mutate() pour ajouter des nouvelles variables/colonnes, ou modifier les existantes, en fonction ou non pas autres variables de la table
  • select() pour sélectionner des variables/colonnes par leur nom
  • filter() pour séletionner des lignes conditionnellement aux valeurs d’une ou plusieurs colonnes
  • summarise() pour résumer les lignes
  • arrange() pour ordonner les lignes
  • et bien d’autres…

Et pour un peu de pratique en live, je vous propose la vidéo du Meetup Raddict dans laquelle Romain François nous parle de quelques fonctions majeures de {dplyr}.

La cheat sheet de {dplyr} c’est ici!

Manipuler les chaînes de caractères avec {stringr}

{stringr} nous vient avec une pléthore de fonctions str_() nous permettant une manipulation facilitée des chaînes de caractères :

  • pour détecter des patterns, avec str_detect(), str_which(), str_count() et str_locate()
  • pour extraire des patterns, avec str_sub(), str_subset(), str_extract() et str_match()
  • pour gérer les longueurs de chaînes, avec str_length(), str_pad(), str_trunc() et str_trim()
  • pour modifier les chaînes de caractères avec str_sub(), str_replace(), str_to_lower(), str_to_upper() et str_to_title()
  • pour concaténer et splitter avec str_c(), str_dup(), str_split_fixed(), str_glue() et str_glue_data()

Vous aurez également besoin dans certains cas de maitriser les expressions régulières, et pour cela de lire notre article sur les expressions régulières.

La cheat sheet de {stringr} c’est ici!

  • déterminer le type de jour (semaine, weekend ?), année (bissextile ?), heure (matin, après-midi, jour, nuit ?), …
  • gérer les différentes timezones

Il y a tellement de fonctions que c’est compliqué de tout décrire ici, mais la cheat sheet est vraiment bien faite :

Le package {hms} permet quant à lui de manipuler les heures – minutes – secondes avec une classe dédiée : hms. Par exemple :

hms::hms(56, 34, 12)
## 12:34:56

Et là attention, deux choses :

  • l’ordre des paramètres, contrairement à ce que pourrait nous faire penser le nom de la fonction, n’est pas heure, minute, seconde, mais seconde, minute, heure. Oui je sais, mais c’est comme ça.

Manipuler les facteurs avec {forcats}

La cheat sheet de {forcats} c’est ici!

Itérer grâce à {purrr}

C’est encore une fois un package dont on vous a déjà parlé plusieurs fois :

Avec les fonctions map_() et accumulate(), vous n’aurez plus aucune boucle for dans vos codes !

La cheat sheet de {purrr} c’est ici!

tidy-modéliser avec {modelr} ?

{modelr}, je vous le dis tout de suite, c’est déjà dépassé. En effet, pas de nouveautés sur le repo Git du package depuis 6 mois. Alors on fait quoi quand on souhaite faire nos modèles tout en conservant notre tidy-grammaire ? C’est simple : on se tourne vers les tidymodels ! Les packages du tidymodels ne faisant pas partie du tidyverse, je ne m’étendrai pas dessus dans cet article, mais je vous invite à aller voir de plus près la page des tidymodels, vous y trouverez des informations sur les packages qui le composent.

Et les autres packages du tidyverse :

La tidy évaluation avec {rlang}

Nous vous parlerons bientôt dans un nouvel article de ce package et des nouveautés côté tidy évaluation, car les choses ont pas mal évolué depuis notre aticle sur la tidyeval ! Les doubles moustaches n’auront bientôt plus de secrets pour vous, patience !

En attendant, la cheat sheet de {rlang} c’est ici!

Pour aller plus loin

Ils sont prolifiques chez RStudio vous ne trouvez pas ? Afin de suivre les évolutions nombreuses et fréquentes du tidyverse, je vous invite à jeter un oeil régulièrement au blog du tidyverse qui regorge d’informations, d’articles, de news, …

Si un package en particulier vous intéresse et que vous souhaitez que nous en parlions plus en détail, faites-le nous savoir !


À propos de l'auteur


Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *


À lire également