ggplot2
Lors de cette séance nous allons apprendre à visualiser des jeux de
données tabulaires avec ggplot2
.
Pour cette formation, nous allons utiliser une version légèrement modifiée du jeu de données publié par Burghard et al 2015.
Il s’agit d’une expérience de phénotypage de plantes d’Arabidopsis thaliana qui ont poussé dans différentes conditions:
Des plantes de différents génotypes ont été analysées: des mutants et sauvages et des écotypes différents.
Pour chaque plante dans chaque condition plusieurs phénotypes ont été mesurés (voir illustration ci-dessous pour mieux comprendre les termes):
Il est possible de représenter les données de différentes manières et ggplot2 offre de nombreuses options de visualisation.
Parmi les plus connues:
Et encore plus. Pour toutes les découvrir, voir cette galerie de graphiques avec ggplot2
Ouvrez avec Rstudio
le fichier
Script_ggplot_lesson.R
qui est dans le répertoire
session2_ggplot/materiel
.
Lors de cette séance nous allons utiliser plusieurs packages qui
contiennent les fonctions dont nous avons besoin:
- tidyverse
qui contient plusieurs packages dont
ggplot2
- visdat
qui permet une représentation rapide des
données
- plotly
pour faire des graphiques interactifs
Ces packages sont normalement déjà installés. Pour les importer dans
votre session, utilisez la fonction library()
:
library(tidyverse)
library(visdat)
library(plotly)
Vous devez aussi vous assurer que le répertoire de travail de R est
bien le dossier qui contient le matériel de la formation. Le chemin vers
ce dossier va être différent en fonction de votre système
opérateur.
Pour spécifier le répertoire de travail de R utilisez la fonction
setwd()
:
setwd("~/Desktop/2021_L3_R/session2_ggplot/materiel")
Ouvrez avec Rstudio
le fichier
Script_ggplot_session.R
qui est dans le répertoire
session2_ggplot/materiel
. Nous allons coder ensemble dans
ce script, qui est quasiment vide pour le moment.
La version simplifiée des données est dans le dossier
session2_ggplot/data
(burghardt_et_al_2015_expt1.txt
). Il s’agit de données pour
des phénotypes associés au temps nécessaire à des plantes de différents
génotypes pour fleurir dans différentes conditions.
Comme notre répertoire de travail est le dossier
materiel
, nous devons importer les données ainsi:
# Import des données et chargement dans l'objet expt1
expt1 <- read_tsv("../data/burghardt_et_al_2015_expt1.txt")
## Rows: 957 Columns: 15
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: "\t"
## chr (6): genotype, background, fluctuation, vernalization, bolt.survival, bolt
## dbl (9): plant_nb, temperature, day.length, days.to.bolt, days.to.flower, ro...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
La fonction read_tsv()
imprime un message indiquant quel
type de données sont contenues dans les différentes colonnes du
fichier.
Dans notre cas, certaines colonnes contiennent des données de type “character” (du texte) et d’autres des données numériques (“double” en présence de décimale, “integer” en absence de décimale).
Pour regarder rapidement les données, tapez le nom de l’objet où sont
les données (expt1
).
expt1
## # A tibble: 957 × 15
## plant_nb genotype background temperature fluctuation day.length vernalization
## <dbl> <chr> <chr> <dbl> <chr> <dbl> <chr>
## 1 1 Col Ama Col 12 Con 16 NV
## 2 2 Col Ama Col 12 Con 16 NV
## 3 3 Col Ama Col 12 Con 16 NV
## 4 4 Col Ama Col 12 Con 16 NV
## 5 5 Col Ama Col 12 Con 16 NV
## 6 6 Col Ama Col 12 Con 16 NV
## 7 7 Col Ama Col 12 Con 16 NV
## 8 8 Col Ama Col 12 Con 16 NV
## 9 1 Col Ama Col 12 Con 8 NV
## 10 2 Col Ama Col 12 Con 8 NV
## # ℹ 947 more rows
## # ℹ 8 more variables: bolt.survival <chr>, bolt <chr>, days.to.bolt <dbl>,
## # days.to.flower <dbl>, rosette.leaf.num <dbl>, cauline.leaf.num <dbl>,
## # blade.length.mm <dbl>, total.leaf.length.mm <dbl>
Cela va montrer les 10 premières lignes du tableau ainsi que les colonnes qui rentrent dans l’écran.
Combien y a-t-il de lignes et colonnes dans les données?
Une autre option est d’utiliser la fonction View()
pour
accéder à une table interactive où il est possible de trier et filtrer
les données sans modifier l’objet:
View(expt1)
glimpse()
permet d’avoir une idée de la
structure des données:glimpse(expt1)
## Rows: 957
## Columns: 15
## $ plant_nb <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1…
## $ genotype <chr> "Col Ama", "Col Ama", "Col Ama", "Col Ama", "Col …
## $ background <chr> "Col", "Col", "Col", "Col", "Col", "Col", "Col", …
## $ temperature <dbl> 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1…
## $ fluctuation <chr> "Con", "Con", "Con", "Con", "Con", "Con", "Con", …
## $ day.length <dbl> 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8,…
## $ vernalization <chr> "NV", "NV", "NV", "NV", "NV", "NV", "NV", "NV", "…
## $ bolt.survival <chr> "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y",…
## $ bolt <chr> "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y",…
## $ days.to.bolt <dbl> 28, 29, 31, 31, 32, 33, 34, 35, 69, 72, 76, 79, 8…
## $ days.to.flower <dbl> 43, 44, 43, 42, 44, 47, 47, 49, 90, 91, 97, 99, 1…
## $ rosette.leaf.num <dbl> 18, 15, 13, 17, 19, 14, 15, 18, 53, 49, 51, 55, 5…
## $ cauline.leaf.num <dbl> 6, 5, 4, 5, 4, 4, 3, 5, 6, 5, 6, 9, 6, 9, 8, 10, …
## $ blade.length.mm <dbl> 12.9, 10.5, 13.2, 14.6, 13.3, 14.7, 13.0, 17.8, 2…
## $ total.leaf.length.mm <dbl> 21.1, 19.1, 23.4, 27.2, 20.4, 25.3, 23.2, 31.3, 3…
Quel est le type des variables dans les données ?
dim()
indique les dimensions du jeu de données (nombre
de lignes et colonnes).dim(expt1)
## [1] 957 15
summary()
permet d’obtenir des stats de base pour chaque colonne.summary(expt1)
## plant_nb genotype background temperature
## Min. : 1.000 Length:957 Length:957 Min. :12.00
## 1st Qu.: 2.000 Class :character Class :character 1st Qu.:12.00
## Median : 4.000 Mode :character Mode :character Median :12.00
## Mean : 4.589 Mean :16.98
## 3rd Qu.: 7.000 3rd Qu.:22.00
## Max. :12.000 Max. :22.00
##
## fluctuation day.length vernalization bolt.survival
## Length:957 Min. : 8.00 Length:957 Length:957
## Class :character 1st Qu.: 8.00 Class :character Class :character
## Mode :character Median :16.00 Mode :character Mode :character
## Mean :12.01
## 3rd Qu.:16.00
## Max. :16.00
##
## bolt days.to.bolt days.to.flower rosette.leaf.num
## Length:957 Min. : 15.00 Min. : 21.00 Min. : 5.00
## Class :character 1st Qu.: 38.00 1st Qu.: 46.00 1st Qu.: 24.00
## Mode :character Median : 57.00 Median : 66.00 Median : 40.00
## Mean : 66.04 Mean : 71.59 Mean : 39.71
## 3rd Qu.: 85.00 3rd Qu.: 92.00 3rd Qu.: 53.00
## Max. :162.00 Max. :182.00 Max. :112.00
## NA's :83 NA's :95
## cauline.leaf.num blade.length.mm total.leaf.length.mm
## Min. : 1.000 Min. : 7.10 Min. : 9.00
## 1st Qu.: 5.000 1st Qu.:18.00 1st Qu.:29.10
## Median : 8.000 Median :20.95 Median :34.60
## Mean : 7.208 Mean :21.11 Mean :34.69
## 3rd Qu.: 9.000 3rd Qu.:24.30 3rd Qu.:40.27
## Max. :17.000 Max. :59.00 Max. :66.30
## NA's :96 NA's :327 NA's :303
Nous avons déjà utilisé de nombreuses fonctions:
install.packages()
library()
read_tsv()
View()
glimpse()
summary()
dim()
Il est bien sûr difficile de ce souvenir du nom de toutes ces
fonctions, ce qu’elles font et comment les utiliser. Heureusement, pour
nous aider, une aide est disponible dans R en tapant le nom d’une
fonction précédé de ?
?summary
Bien sur, une recherche sur internet est aussi une solution très efficace pour trouver de l’aide!
Que fait la fonction
head()
?
Comment regarder les dernières lignes de le notre jeu de données? (indice:
?tail
)
Pour avoir une vue d’ensemble du jeu de données et détecter des
problèmes, nous allons utiliser la fonction vis_dat()
.
vis_dat(expt1)
Quel est le type de données le plus courant dans le jeu de données ? Y a-t-il des problème?
Le gris dans la figure générée par vis_dat()
sont des
données manquantes. Plusieurs stratégies peuvent être utilisées:
Pour la formation, nous allons enlever les lignes contenant des données manquantes.
expt1 <- drop_na(expt1)
Combien de lignes nous reste-il?
Maintenant que nous avons vérifié la qualité de notre jeu de donnée, nous pouvons générer des graphiques afin d’en apprendre plus sur les données générées par l’expérience.
Nous allons utiliser le package R ggplot2
, qui utilise
le principe de “grammar or graphics”. Il s’agit des briques du
graphique. Cette méthode permettant de combiner et superposer
différentes couches de briques:
Les trois premières briques sont essentielles: data, aesthetics et objet géométrique.
aes()
.geom_object
,
et peut en avoir plusieurs. Par exemple:
geom_point
pour des scatter plots, dot
plots)geom_line
pour des tendance, séries
chronologiques)Pour plus d’information sur la création de graphique avec
ggplot2
, voir cette
antisèche très utile.
Commençons avec un boxplot: Dessinons le days.to.flower
pour les différents génotypes.
La fonction ggplot()
permet de commencer le graphique.
Il faut aussi indiquer les données à utiliser:
ggplot(expt1)
Mais cela ne produit qu’un canevas gris!
Il nous manque des briques. En l’occurrence nous devons indiquer
quelle aesthetics (c’est la terminologie de
ggplot2
) nous voulons sur ce canevas gris. Nous devons
indiquer quelles sont les variables x et y du
boxplot.
ggplot(expt1, aes(x = genotype, y = days.to.flower))
ggplot
indique maintenant les variables
genotype
et days.to.flower
de nos données sur
les axes x et y du graphique.
Mais toujours pas de graphique. C’est parce que nous pas indiqué à
ggplot
quelle géométrie nous voulons dessiner sur
le canevas. Pour faire un boxplot, nous ajoutons au canevas
(littéralement avec un +
) geom_boxplot()
:
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_boxplot()
Exercice 1: Pouvez vous dessiner un violin plot? (indice:
?geom_violin
)
BONUS Quel autre type de géométrie pouvez vous utilisez avec des données de ce type (catégories sur l’axe x et chiffres sur l’axe y)? Aide: Utilisez cet arbre de décision pour choisir les géométries appropriées.
Nous allons maintenant ajouter d’autres geom_objects
au
même graphique. Par exemple, des points pour les valeurs des individus
en plus du boxplot:
ggplot(expt1, aes(genotype, rosette.leaf.num)) +
geom_jitter() +
geom_boxplot()
Exercice 2: Modifiez le graphique pour que les points soient devant plutôt que derrière les boxplots.
BONUS: Ajouter la géométrie dotplot plutôt que jitter. Utilisez les exemples dans l’aide (avec ?nom de fonction) pour savoir comment l’utiliser, ou lisez cette page de blog
Changeons la couleur des boxplots:
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_boxplot(colour = "red")
Ou la couleur à l’intérieur ( fill) des boxplots:
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_boxplot(colour = "red", fill = "royalblue")
Ou même leur transparence:
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_boxplot(colour = "red", fill = "royalblue", alpha = 0.5)
C’est sympa, mais cela ne nous apporte pas d’information en plus sur les données.
Par exemple, ajoutons une couleur qui change en fonction d’un des
traitements auquel les plantes ont été exposées (par exemple
fluctuation
). En langage ggplot2
, nous voulons
lier la valeur de la variable fluctuation
à la couleur dans
l’ aesthetic du graphique.
Nous devons donc inclure cette information dans
aes()
:
ggplot(expt1, aes(genotype, days.to.flower, colour = fluctuation)) +
geom_boxplot()
Wow! ggplot a automatiquement séparé les données de chaque génotype
en deux groupes en fonction de fluctuation
et leur a
attribué une couleur.
Imaginons que nous voulons ajouter les points pour les valeurs des individus, sans couleur, derrière les boxplots colorés:
ggplot(expt1, aes(genotype, days.to.flower, colour = fluctuation)) +
geom_jitter() +
geom_boxplot(alpha = 0.5)
Ce n’est pas ce que nous voulons. L’aesthetique colour
a
été liée à toutes les géométries du graphique. C’est parce
qu’elle a été définie dans la fonction ggplot()
, qui
affecte tous les geom_object
qui viennent après.
Mais nous pouvons aussi définir l’aesthetique dans chaque géométrie:
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_jitter() +
geom_boxplot(aes(fill = fluctuation), alpha = 0.5)
Exercice 3: Nous voulons étudier la relation entre le nombre de feuille de rosette et la longueur la limbe des feuilles (en mm) pour les génotypes.
Pour cela, dessinez un scatter plot (
geom_point()
) entreblade.length.mm
etrosette.leaf.num
en colorant les points en fonction degenotype
.Que se passe t-il si les points sont colorés en fonction de
days.to.bolt
?
BONUS: Dessinez un scatter plot (
geom_point()
) entreblade.length.mm
etrosette.leaf.num
en colorant les points en fonction devernalization
, et changez la couleur des points pour que “NV” soit en bleu et “V” soit en vert. Aide: Trouvez la solution en ligne (par exemple en tapant “change color points ggplot R”)
Souvent trop de variables, ou de groupes, sont dans nos données pour pouvoir uniquement utiliser les couleurs pour les discriminer.
C’est le cas par exemple du scatterplot produit dans l’exercice précédent, où les points sont colorés en fonction du génotype. Il est difficile de bien discerner les données pour chaque génotype car ils sont très rapprochés. L’idéal serait d’avoir un scatterplot par génotype.
Ceci est assez facile à faire avecggplot2
, il suffit
d’ajouter une couche au graphique appelée “facet”:
facet_grid()
- qui permet d’organiser les panneaux du
graphique en lignes et/ou colonnesfacet_wrap()
- qui permet d’organiser les panneaux du
graphique à la suite les uns des autres qui reviennent à la ligne après
un certain nombre de panneaux.Voyons comment facet_grid()
fonctionne:
ggplot(expt1, aes(blade.length.mm, rosette.leaf.num, colour = genotype)) +
geom_point() +
facet_grid(genotype ~ temperature)
Dans facet_grid()
, nous utilisons la notation
(ligne ~ colonne)
pour définir les variables qui sont
utilisées pour séparer les panneaux en ligne et colonnes.
Exercice 4: Les couleurs n’apportent plus d’information supplémentaire. Pensez à une utilisation plus intéressante des couleurs dans ce graphique.
Il est possible de ne préciser qu’une variable dans
facet_grid()
:
# Faceter en ligne
ggplot(expt1, aes(blade.length.mm, rosette.leaf.num, colour = fluctuation)) +
geom_point() +
facet_grid(genotype ~ .)
# Faceter en colonne
ggplot(expt1, aes(blade.length.mm, rosette.leaf.num, colour = fluctuation)) +
geom_point() +
facet_grid(. ~ genotype)
Il est aussi possible d’utiliser facet_wrap()
si l’on
n’utilise qu’une variable pour séparer les données :
ggplot(expt1, aes(blade.length.mm, rosette.leaf.num, colour = fluctuation)) +
geom_point() +
facet_wrap( ~ genotype)
Exercice 5: Modifiez le graphique précédent afin de séparer les données (facet) en fonction de
fluctuation
en ligne, etday.length
en colonne et de colorer les points en fonction du génotype.
En conclusion, il est possible de représenter de nombreuses informations en combinant efficacement facets, couleurs et autres aesthetiques!
Exercice 6: Reproduisez le graphique suivant (ou quelque chose d’approchant):
.
Indice: facet le graphique avec
day.length
ettemperature
et colorer l’intérieur des boxplot en fonction defluctuation
.
Tous les éléments d’un ggplot sont modifiables. Les thèmes permettent de modifier l’apparence du graphique. Voici quelques exemples.
# Example de thèmes existants dans ggplot2
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_boxplot() +
theme_bw() +
labs(title = "Black and white theme")
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_boxplot() +
theme_classic() +
labs(title = "Classic theme")
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Minimal theme")
La fonction theme()
peut être utilisée pour modifier des
éléments en particulier du graphique. Il y a tellement de possibilités
que le mieux est de rechercher sur internet la modification que vous
voulez faire.
Par exemple, en recherchant “vertical labels x axis ggplot2” nous obtenons cette solution:
ggplot(expt1, aes(genotype, days.to.flower)) +
geom_boxplot() +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
Rechercher “altering plot colours ggplot2” donne ceci, qui apporte cette solution:
ggplot(expt1, aes(genotype, days.to.flower, fill = fluctuation)) +
geom_boxplot() +
scale_fill_brewer(palette="Dark2")
Parfois nous voulons crée une figure contenant plusieurs graphiques, c’est le cas dans la plupart des articles scientifiques.
Par exemple le graphique que vous avez reproduit hier correspond aux panels B et C d’une figure de Burghard et al 2015
Nous allons faire quelque chose de similaire avec un scatterplot en panel A, et des boxplots en panel B.
Pour cela nous allons utiliser la fonction ggarrange()
qui est dans le package ggpubr
.
Tout d’abord, nous devons charger cette librairie dans R:
library(ggpubr)
Ensuite, faisons chaque graphique indépendamment et sauvons les dans un objet:
panelA <- ggplot(expt1, aes(rosette.leaf.num, days.to.bolt, colour = fluctuation)) +
geom_point()
panelB <- ggplot(expt1, aes(genotype, days.to.bolt, fill = fluctuation)) +
geom_boxplot() +
facet_grid(. ~ temperature) +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
Nous pouvons maintenant utiliser la fonction
grid.arrange()
pour combiner les graphiques ensemble:
ggarrange(panelA, panelB, nrow=2, labels =c("A", "B"),heights = c(1.5, 2))
Si nous postons les graphiques en ligne, cela peut apporter un plus
d’inclure une animation dans le graphique, à condition que cela apporte
des informations supplémentaires. C’est possible avec le package
gganimate
!
Tout d’abord, chargeons le package gganimate
:
library(gganimate)
## No renderer backend detected. gganimate will default to writing frames to separate files
## Consider installing:
## - the `gifski` package for gif output
## - the `av` package for video output
## and restarting the R session
Pour créer une animation, il faut utiliser la fonction
transition_states()
à notre graphique (avec un
+)
ggplot(expt1, aes(rosette.leaf.num, days.to.bolt, colour = fluctuation)) +
geom_point() +
transition_states(states = genotype, wrap = FALSE, transition_length = 1, state_length = 3) +
ease_aes('linear') +
labs(subtitle = "{closest_state}")
## NULL
L’animation peut ensuite être sauvée en utilisant la fonction anim_save()
Pour aller plus loin, il est même possible de créer des graphiques
interactifs en utilisant le package plotly
.
library(plotly)
Il faut d’abord mettre le graphique dans un objet, puis utiliser cet
objet dans la fonction ggplotly()
.
# Mettre le graphique dans l'objet p1
p1 <- ggplot(expt1, aes(rosette.leaf.num, days.to.bolt, colour = fluctuation)) +
geom_point() +
facet_wrap(~genotype)
# Utiliser la fonction ggplotly pour faire un graphique intéractif
ggplotly(p1)
Plutôt que d’avoir un graphique par génotype, il est possible d’ajouter un curseur pour montrer les données des génotypes de manière interaction en utilisant l’argument ‘frame’:
# Mettre le graphique dans l'objet p1
p2 <- ggplot(expt1, aes(rosette.leaf.num, days.to.bolt, colour = fluctuation, frame=genotype)) +
geom_point()
# Utiliser la fonction ggplotly pour faire un graphique intéractif
ggplotly(p2)
D’autres packages qui peuvent apporter un plus à
ggplot2
: