---
title: "Introduction au tidyverse"
author: "Sébastien Déjean"
date: "www.math.univ-toulouse.fr/~sdejean"
output:
  pdf_document:
    toc: yes
  html_document:
    toc: yes
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```


## Tout savoir sur tidyverse

C'est ici : [www.tidyverse.org](https://www.tidyverse.org) avec en complément le livre **R for Data Science** de Hadley Wickham, disponible gratuitement en ligne ici : [r4ds.had.co.nz](https://r4ds.had.co.nz).

## Installation et chargement

La commande suivante installe plusieurs packages.

```{r eval=FALSE}
install.packages("tidyverse")
```

Et on peut même charger tous ces packages en même temps.

```{r}
library(tidyverse)
```

Mais on pourrait les charger un par un.

```{r eval=FALSE}
library(dplyr)
library(tidyr)
```

On va donc survoler ces packages (sauf `ggplot2` vu par ailleurs) pour voir ce qu'ils ont à nous proposer.

## `tibble`

```{r}
iris.tib <- as_tibble(iris)
iris.tib
has_rownames(iris)
has_rownames(mtcars)
remove_rownames(mtcars)
rownames_to_column(mtcars)
mtcars_tbl <- as_tibble(rownames_to_column(mtcars))
mtcars_tbl
head(column_to_rownames(mtcars_tbl))
head(column_to_rownames(mtcars_tbl, "rowname"))
glimpse(iris)
```

Une des différences majeures avec les `data.frame` réside dans l'affichage qui est limité aux 10 premières lignes.

A noter également que l'affichage tient compte du nombre de colonnes à afficher. Réduisez la largeur de la fenêtre Console dans RStudio et observez le résultat de `mtcars_tbl` par exemple... Impressionnant non ?

## `dplyr`

### `mutate()` / `transmute()`

Pour ajouter des variables à un data.frame (ou à un tibble bien sûr) soit en conservant les autres (`mutate`) soit en les supprimant (`transmute`).

```{r}
mutate(mtcars, cyl2 = cyl*2)
mtcars.mute <- mutate(mtcars, cyl2 = cyl*2)
head(mtcars.mute)
class(mtcars.mute)

mtcars.transmute <- transmute(mtcars, cyl2 = cyl*2)
head(mtcars.transmute)

mtcars %>% mutate(cyl3 = cyl^3)
mtcars
mtcars.mute3 <- mtcars %>% mutate(cyl3 = cyl^3)
```

Euh, c'est quoi le truc `%>%` ?

### `select()`

La fonction `select` offre des possibilités pour  sélectionner simplement des **variables** dans un data.frame (ou un tibble).

```{r}
select(iris, starts_with("Petal"))
select(iris.tib, starts_with("Petal"))
select(iris, contains("Width"))

iris.tib %>% select(contains("Width"))
```

Noter au passage qu'on commence à prendre goût au fait que le tibble s'affiche plus proprement, non ?

Euh oui, et sinon, c'est quoi le `%>%` ?

### `filter()`

La fonction `filter` agit dans le même esprit que `select` mais cette fois-ci sur les observations d'un data.frame (ou d'un vous savez quoi).

```{r}
filter(mtcars, cyl==6)
filter(mtcars, between(qsec,16,18))

mtcars %>% filter(between(qsec,16,18)) %>%
  arrange(qsec)
```

Et... `%>%` ? On en reparle plus loin... regardez bien, il y a une section qui s'appelle comme ça.

### `summarise()`

Pour calculer quelques indicateurs statistiques sur une ou plusieurs variables.

```{r}
summarise(mtcars_tbl, mean=mean(disp))
summarise(mtcars_tbl, mean=mean(disp), median= median(disp))
```

Pour calculer ces mêmes indicateurs sur des sous-groupes dépendant d'une variable catégorielle, on peut utiliser au préalable la fonction `group_by`.

```{r}
mtcar_by_cyl <- group_by(mtcars_tbl, cyl)
mtcar_by_cyl <- group_by(mtcars, cyl)
summarise(mtcar_by_cyl, mean=mean(disp), median=median(disp))
```

Notons au passage que même si `group_by` prend un data.frame en paramètres, le résultat est un `grouped tbl` (qui peut être "dégrouper" avec la fonction... `ungroup`.

```{r}
ungroup(mtcar_by_cyl)
``` 

Et même si on ne doit pas encore en parler
```{r}
mtcars_tbl %>% group_by(cyl) %>% summarise(mean=mean(disp))
```

### `arrange()`

Pour ranger les lignes d'un data.frame (ou ...) dans l'ordre croissant ou décroissant d'une variable.

```{r}
arrange(mtcars, disp)
arrange(mtcars_tbl, disp)
arrange(mtcars, hp)
arrange(mtcars, desc(mpg))

mtcars_tbl %>% arrange(disp)
```

`%>%` ???? On en reparle plus loin...

## `readr`

A voir avec ses propres fichiers. Notez que la sortie des fonctions `read_delim`, `read_csv` et autres sera un... `tibble` bien sûr.

Ces 3 exemples issus de l'aide de la fonction `read_delim` illustrent le fait que cette fonction décompresse automatiquement des fichiers zip et bz2 avant de les importer.

```{r}
read_csv(readr_example("mtcars.csv"))
read_csv(readr_example("mtcars.csv.zip"))
read_csv(readr_example("mtcars.csv.bz2"))
```

## `stringr`

Le package contient tout un tas de fonctions pour faciliter la manipulation des chaînes de caractères. En voici une illustration

### La longueur d'une chaîne

```{r}
DesCaracteres <- "Oh les beaux caracteres !"
str_length(DesCaracteres)
```

### Concaténer des chaînes

```{r}
str_c(DesCaracteres, "C'est bien vrai.")
str_c(DesCaracteres, "C'est bien vrai.", sep=" ")

str_c("Oh, le joli ", c("a","b","c"), ". C'est vrai qu'il est beau.")
```

### Sélectionner des parties

```{r}
str_sub(DesCaracteres, start = 8, end = 12)
str_sub(DesCaracteres, start = 1, end = 6)
str_sub(DesCaracteres, end = 6)
str_sub(DesCaracteres, start = 8)
```

Et vous pensez que l'on peut... oui.

```{r}
DesCaracteres %>% str_sub(start=10)
```


### Détecter des chaînes...

... qui contiennent une caractère donné

```{r}
DesFruits <- c("Pomme", "Poire", "Peche", "Abricot", "Melon")
str_detect(DesFruits,"o")
```

... qui commencent ou finissent par un caractère donné
```{r}
str_detect(DesFruits,"^P")
str_detect(DesFruits,"e$")
```

Pas futé sur cette exemple, le résultat est le même pour les deux ! Trop beau, je le laisse quand même.

```{r}
str_detect(DesFruits,"t$")
```

Et pour revenir un peu en arrière avec `str_sub` et un peu en avant avec `%>%`.

```{r}
DesFruits %>% str_sub(1, 4)
```

### Passer de minuscules à majuscules ou inversement

```{r}
str_to_upper(DesCaracteres)
str_to_lower(DesCaracteres)
```


Il y a encore tout plein de fonctions à explorer dans le package `stringr`. Commencez à taper `str_` dans la console RStudio et regardez...

Un petit dernier quand même

```{r}
str_view(DesCaracteres, "eau")
str_view(DesFruits, "^P")
```

## `forcats`

Le package `forcats` propose de nombreuses fonctions pour faciliter la manipulation des `factors` (`factors` <-> `forcats`, vous saisissez ?).

### Ré-ordonner les niveaux d'un facteur

Pour illustrer l'intérêt de la fonction `fct_reorder`, le plus simple est de passer par un graphique comme cela est suggéré dans l'aide de la fonction.

Regardons des boxplots pour chaque modalité du facteur `Species` du fameux jeu de données `iris`

```{r}
ggplot(iris, aes(x=Species, y = Sepal.Width)) + geom_boxplot()
```

Ce serait sympa (même si pas forcément très utile ici) de ré-ordonner les boxplots par médiane croissante par exemple.

```{r}
ggplot(iris,
       aes(x=fct_reorder(Species, Sepal.Width), y = Sepal.Width)) +
  geom_boxplot()
```

Et voilà, trop facile. On n'a précisé la médiane nulle part car c'est la fonction utilisée par défaut. Mais si on veut modifier le critère de tri, c'est dans l'argument `.fun` que ça se passe.

Pour trier selon l'écart-type croissant, par exemple, on fait :

```{r}
ggplot(iris,
       aes(x=fct_reorder(Species, Sepal.Width, .fun=sd),
           y = Sepal.Width)) +
  geom_boxplot()
```

Le changement n'est pas flagrant vu que l'ordre est le même que par médiane croissante. Et si on veut selon un critère décroissant, on complète avec `.desc`.

```{r}
ggplot(iris,
       aes(x=fct_reorder(Species, Sepal.Width, .fun=sd, .desc=TRUE),
           y = Sepal.Width)) +
  geom_boxplot()
```

### Manipuler des facteurs ordonnés

Les facteurs ordonnées sont utiles pour gérer des variables qualitatives ordonnées comme par exemple "Un peu" qui est une modalité inférieure à "Beaucoup" elle même inférieure à "Passionnément".


```{r}
f <- factor(c("b", "b", "a", "c", "c", "c"))
fct_infreq(f, ordered = TRUE)
fct_inorder(f, ordered = TRUE) #  # match the order of the first appearance in the data.
f <- factor(c(7,4,1,8,10,10,3,7,8,4,3,4))
fct_inseq(f, ordered = TRUE)
fct_inorder(f, ordered = TRUE)
fct_infreq(f, ordered = TRUE)
```

Dans les exemple ci-dessus, on voit que la fonction

- `fct_inseq` réordonne les niveaux d'un facteur en fonction de l'ordre numérique

- `fct_inorder` réordonne les niveaux d'un facteur en fonction de leur ordre d'apparition dans les données

- `fct_infreq` les niveaux d'un facteur en fonction de leur effectif

Et bien entendu :

```{r}
f %>% fct_infreq(ordered=TRUE)
```

### Calculer les effectifs par modalité d'un facteur

[Exemples tirés de l'aide de la fonction `fct_count`]

Pour calculer les effectifs par modalité d'un facteur, on connaît la fonction `table`, pas de souci particulier de ce côté-là.

```{r}
f <- factor(sample(letters)[rpois(1000, 10)])
table(f)
```

Mais regardons quand même ce que nous propose la fonction `fct_count`.

```{r}
fct_count(f)
```

OK, on reconnaît un `tibble`, pourquoi pas, mais rien de révolutionnaire.

Explorons un peu plus :
```{r}
fct_count(f, sort = TRUE)
```

Pourquoi pas, mais on peut aussi s'en sortir avec 
```{r}
sort(table(f), decreasing = TRUE)
```

Un petit dernier peut-être :

```{r}
fct_count(f, sort = TRUE, prop = TRUE)
```

OK, on pourrait aussi s'en sortir sans la fonction `fct_count`, mais c'est quand même pas mal.

### Combiner les niveaux de facteurs pour créer un nouveau facteur

Pour certaines analyses statistiques, il peut être utile de croiser différents facteurs pour en faire un seul. Cela est quasiment immédiat avec la fonction `fct_cross`.

```{r}
Genotype <- factor(c("WT","Mut","WT","Mut","WT","Mut"))
Condition <- factor(c("CTRL","CTRL", "CTRL", "Trait", "Trait", "Trait"))
Temperature <- c("Low","High","High", "High", "High", "Low")
fct_cross(Genotype, Condition)
fct_cross(Genotype, Condition, sep="_")
fct_cross(Genotype, Condition, Temperature, sep="_")
fct_cross(Genotype, Condition, Temperature, sep="_", keep_empty = TRUE)
```

### Ajouter des niveaux à un facteur

Pour ajouter un ou des niveaux à un facteur existant, on peut utiliser la fonction `fct_expand`.

```{r}
fct_expand(Condition, "Trait2")
fct_expand(Condition, "Trait2", "Trait3")
```

On arriverait à des résultat similaire en jouant avec la fonction `levels`, mais bon, on a mis les pieds dans le tidyverse, alors on essaie de jouer le jeu.

### Regrouper certains niveaux d'un facteur

Où l'on apprend que **to lump** signifie **mettre en bloc, en masse, en tas** et que **to lump things together** signifie **réunir des choses ensemble**.

Donc, on va commencer par créer un facteur avec des modalités plus ou moins fréquentes. L'aide en ligne de la fonction `fct_lump` nous propose par exemple :

```{r}
f <- factor(rep(LETTERS[1:9], times = c(40, 10, 5, 27, 1, 1, 1, 1, 1)))
f %>% fct_count()
f %>% fct_count(sort=TRUE)
```

On a ainsi 40 fois la modalité `A`, 10 fois `B`, 5 fois `C`, 27 fois `D` et une fois chaque lettre suivante jusqu'à `I` (merci `fct_count` quand même).

Voyons ce que nous propose cette fonction `fct_lump`

```{r}
fct_lump(f)
f %>% fct_lump()
```

OK, on voit des `Other` là où il y avait d'autres choses, mais qui, quoi, comment, pourquoi ???? Essayons d'y voir plus clair.

```{r}
f %>% fct_lump() %>% fct_count()
```

Tiens, c'est pas mal ce truc `%>%`.

Bon alors, il a gardé les 2 modalités les plus présentes et il a regroupé les autres dans `Other`, d'accord mais pourquoi les 2 premières seulement.

Parce que : *If both n and prop are missing, fct_lump lumps together the least frequent levels into "other", while ensuring that "other" is still the smallest level*.

Effectivement, je n'ai précisé ni `n` ni `prop`, donc il a regroupé des modalités tout en s'assurant que `Other` soit le niveau le moins représenté. OK, pourquoi pas.

On va jouer avec `n` et `prop` pour voir...

```{r}
fct_lump(f, n=3)
fct_count(fct_lump(f, n=3))
```

Bouh, c'est lourd cette dernière commande, et si... oui, vas-y, *just do it*.

```{r}
f %>% fct_lump(n=3) %>% fct_count()
```

Ouais, pas mal quand même. Et donc, `fct_lump` a gardé ici les 3 modalités les plus représentées et a regroupé les autres dans `Other`. Et pour `prop`, on commence à deviner :

```{r}
f %>% fct_lump(prop=.3) %>% fct_count()
f %>% fct_lump(prop=.1) %>% fct_count()
```

La sélection se fait sur la proportion que représente chaque modalité.

Si vous voulez modifier le nom de cette modalité "autre", c'est possible.

```{r}
f %>% fct_lump(n=3, other_level = "Autre") %>% fct_count()
```

Une façon plus "autoritaire" de réaliser des choses assez similaires consiste à utiliser la fonction `fct_other`.

```{r}
MonFact <- fct_other(f, keep=c("A","E","I"))
MonFact %>% fct_count()
MonFactBis <- fct_other(f, drop=c("B","F","G"))
fct_count(MonFactBis)
```

On décide ainsi de conserver (`keep`) ou de laisser tomber (`drop`) les modalités de notre choix sans aucune considération des effectifs ou des proportions.

Il y a encore tout plein de fonctions à explorer dans le package `forcats`. Commencez à taper `fct_` dans la console RStudio et regardez... J'ai l'impression d'avoir déjà lu ça quelque part.

## `tidyr`

L'utilisation du package `tidyr` vise à obtenir des données **tidy**. Des données **tidy** sont caractérisées par 3 règles :

- chaque variable doit avoir sa propre colonne
- chaque observation doit avoir sa propre ligne
- chaque valeur doit avoir sa propre cellule

Tous les packages du `tidyverse` sont conçus pour travailler avec des données `tidy`. Quand les jeux de données que l'on manipule ne le sont pas, les fonctions du package `tidyr` vont nous permettre de les **tidyser** ou **tidyfier** (choisissez votre néologisme préféré).

Il est difficile de passer en revue tous les cas de figure de données non **tidy** à rendre **tidy** car **Tidy datasets are all alike, but every messy dataset is messy in its own way** (HW) , mais essayons quand même d'illustrer quelques cas.

### `pivot_longer`

```{r}
relig_income
relig_income %>%
 pivot_longer(-religion, names_to = "income", values_to = "count")
```

Oups, ça m'a échappé, je voulais bien sûr écrire

```{r}
pivot_longer(relig_income, -religion,
             names_to = "income", values_to = "count")
```

On passe ainsi d'un tableau de dimension 18 x 11 à un tableau de dimension 180 x 3. Le tableau en sortie étant plus long (plus de lignes) que le tableau en entrée, c'est la fonction `pivot_longer` qui a permis de rendre **tidy** le jeu de données qui avait initialement une variable en nom de colonnes.

### `pivot_wider`

A l'opposé, pour générer un tableau ayant à la sortie plus de colonnes qu'en entrée, c'est la fonction `pivot_wider` qui est utilisée.

```{r}
fish_encounters
fish_encounters %>%
  pivot_wider(names_from = station, values_from = seen)
```

On passe ici d'un tableau 19 x 12 à un tableau 114 x 3.

Le package `tidyr` a connu une évolution importante ces derniers temps. Celles et ceux qui l'avaient déjà utilisé connaissait peut-être les fonctions `gather` et `spread`. Ces fonctions existent toujours, mais les développeurs suggèrent d'utiliser maintenant les fonctions `pivot_longer` et `pivot_wider` qui leur ont succédé.


## `purrr`

Le package `purrr` (au fait **purr** signifie ronronner ce qui explique le chat que l'on voit confortablement installé dans le **hex sticker** du package) est peut-être l'un des plus puissants du tidyverse. En contrepartie, il est d'accès un peu délicat. En bref, on peut le voir comme une extension de la famille des `*apply` de R base. Voyons quelques exemples sachant qu'une découverte approfondie de ce package prendrait un temps certain. Notons également qu'en date du 4 mars 2020, ce package est en  encore en cours d'évolution (version 0.3.3) et qu'il est susceptible de connaître des changements dans les mois à venir (voir par exemple l'aide de la fonction `rerun` qui précise que cette fonction **is in the questioning lifecycle stage because we are no longer convinced NSE functions are a good fit for purrr**).

### `detect` et `detect_index`

Ces fonctions permettent de trouver la valeur (`detect`) ou la position  (`detect_index`) d'un élément vérifiant une condition.

Regardons des exemples tirés de l'aide en ligne de ces fonctions. Cela commence avec la définition d'une fonction qui teste la parité d'un nombre avec l'opérateur `%%` qui renvoie le reste de la division entière de 2 nombres. Pour obtenir de l'aide sur cet opérateur : `?"%%"`.

```{r}
is_even <- function(x) x %% 2 == 0
vect <- 5:12
vect
detect(vect, is_even)
detect_index(vect, is_even)
```

Dans le vecteur `5:12`, le premier nombre pair est 6 et il est en position 2 dans le vecteur.

Il va de soi que l'on obtient les mêmes résultats avec vous savez quoi.

```{r}
vect %>% detect(is_even)
vect %>% detect_index(is_even)
```

### `keep` et `discard`

Sur la base du même exemple que précédemment, on peut vouloir conserver toutes les éléments qui vérifient une condition. On utilise pour cela la fonction `keep`. A contrario, si on veut les supprimer, c'est la fonction `discard` qui fait le travail.

```{r}
keep(vect, is_even)
discard(vect, is_even)
```

OK, on peut très bien s'en sortir avec nos vieilles habitudes de *R user* de la première heure.

```{r}
vect[is_even(vect)]
setdiff(vect, vect[is_even(vect)])
```

Mais, en toute sincérité, reconnaissons que la version **purrresque** est quand même plus lisible, non ? Et en plus `%>%` !

```{r}
vect %>% keep(is_even)
```

### `flatten`

La fonction `flatten` est assez similaire à la fonction `unlist` pour "délister" un objet `list`

```{r}
(x_list <- list(1:4, 10:15))
flatten(x_list) # équivalent à vous savez quoi
unlist(x_list)
```

Elle est accompagnée d'autres fonctions (`flatten_*`) qui permettent de contrôler le type de l'objet en sortie.

```{r}
x_list %>% flatten_int()
x_list %>% flatten_chr()
```

### `map`

Un des intérêts principaux du package `purrr` réside dans les fonctions `map` et `map_*`. Elles visent à rendre plus clair et plus lisible un code nécessitant de reproduire la même action sur un ensemble de choses.

Considérons l'exemple tiré de [r4ds.had.co.nz](https://r4ds.had.co.nz) (section Iteration).

```{r}
(MyData <- tibble(
    a = rnorm(10),
    b = rnorm(10),
    c = rnorm(10),
    d = rnorm(10)
))

MyData %>% map(mean)
MyData %>% map_dbl(mean)
MyData %>% map_dbl(sd)
```

OK, on sait très bien faire ça à coup de `apply`.

```{r}
apply(MyData, 2, mean)
```

Et on peut retrouver la plupart des résultat fournis par la fonction `map` en utilisant la famille des fonctions `apply` : `lapply`, `sapply`, `mapply`. Mais, encore une fois, l'intérêt de `map` réside essentiellement dans la clarté du code qu'elle permet d'écrire.

A noter également, les sympathiques fonctions `map_if` et `map_at` qui réalisent un `map` sous condition.

```{r}
map_if(vect, is_even, log2)
map_if(vect, is_even, log2) %>% flatten_dbl()
map_at(vect, c(2,5), exp) %>% flatten_dbl()
```

Le package `purrr` contient bien d'autres fonctions que vous aurez l'occasion de découvrir selon vos besoins...

## Mais quel est donc ce symbole `%>%` ?

Finalement, est-il toujours utile d'en parler ?

Bon, si juste un dernier petit truc

```{r}
library(ggplot2)
mtcars %>% ggplot(aes(x=disp, y=drat)) +
  geom_point() +
  facet_wrap(~cyl)
```

Et oui, ça marche aussi pour les graphiques. Bienvenue dans le *tidyverse* !

