3.5 Différents moteurs graphiques

Prolifération des standards d’après xkcd.
Prolifération des standards d’après xkcd.

Depuis le début, l’ensemble des graphiques que nous vous avons réalisés utilise la fonction chart() du package {chart}. Cependant, il ne s’agit pas de la seule fonction permettant de réaliser des graphiques dans R, loin de là. En fait {chart} est tout récent et a été développé pour homogénéiser autant que possible les graphiques issus de trois moteurs graphiques différents : {ggplot2}, {lattice} et les graphiques de base. La fonction chart() a d’autres avantages également :

  • Un thème par défaut qui est le plus proche possible d’un rendu typique d’une publication scientifique.
  • La possibilité d’utiliser l’interface formule avec {ggplot2}.
  • La cohérence des objets graphiques obtenus qui peuvent tous être combinés en une figure composite, même si ils sont produits avec des moteurs graphiques différents.
  • Un libellé automatique des axes et autres éléments du graphique en fonction des attributs label et units des variables (pour l’instant, seulement les graphiques de type {ggplot2}).
# Importation des données
(urchin <- read("urchin_bio", package = "data.io", lang = "FR"))
#        origin diameter1 diameter2 height buoyant_weight weight solid_parts
#        <fctr>     <num>     <num>  <num>          <num>  <num>       <num>
#   1: Pêcherie       9.9      10.2    5.0             NA 0.5215      0.4777
#   2: Pêcherie      10.5      10.6    5.7             NA 0.6418      0.5891
#   3: Pêcherie      10.8      10.8    5.2             NA 0.7336      0.6770
#   4: Pêcherie       9.6       9.3    4.6             NA 0.3697      0.3438
#   5: Pêcherie      10.4      10.7    4.8             NA 0.6097      0.5587
#  ---                                                                      
# 417:  Culture      16.7      17.2    8.5         0.5674 2.4300      2.2900
# 418:  Culture      16.5      16.5    7.9         0.5472 2.3200      2.1800
# 419:  Culture      16.8      16.7    8.2         0.4864 2.2200      2.1300
# 420:  Culture      17.3      17.2    8.5         0.4864 2.5200      2.3400
# 421:  Culture      17.0      16.6    7.9         0.4357 2.0500      1.9800
#      integuments dry_integuments digestive_tract dry_digestive_tract gonads
#            <num>           <num>           <num>               <num>  <num>
#   1:      0.3658              NA          0.0525              0.0079 0.0000
#   2:      0.4447              NA          0.0482              0.0090 0.0000
#   3:      0.5326              NA          0.0758              0.0134 0.0000
#   4:      0.2661              NA          0.0442              0.0064 0.0000
#   5:      0.4058              NA          0.0743              0.0117 0.0000
#  ---                                                                       
# 417:      1.8400            1.02          0.1661              0.0229 0.0215
# 418:      1.8000            1.01          0.0977              0.0147 0.0253
# 419:      1.6300            0.88          0.1704              0.0208 0.0154
# 420:      1.7200            0.89          0.1444              0.0167 0.0237
# 421:      1.4300            0.83          0.1462              0.0212 0.0266
#      dry_gonads skeleton lantern   test spines maturity    sex
#           <num>    <num>   <num>  <num>  <num>    <int> <fctr>
#   1:     0.0000   0.1793  0.0211 0.0587 0.0995        0   <NA>
#   2:     0.0000   0.1880  0.0205 0.0622 0.1053        0   <NA>
#   3:     0.0000   0.2354  0.0254 0.0836 0.1263        0   <NA>
#   4:     0.0000   0.0630  0.0167 0.0180 0.0283        0   <NA>
#   5:     0.0000       NA      NA     NA     NA        0   <NA>
#  ---                                                          
# 417:     0.0034   0.9046  0.0750 0.3399 0.4896        0   <NA>
# 418:     0.0051   0.8965  0.0908 0.3189 0.4868        0   <NA>
# 419:     0.0020   0.7714  0.0877 0.2961 0.3876        0   <NA>
# 420:     0.0032   0.7938  0.0772 0.3077 0.4090        0   <NA>
# 421:     0.0051   0.7421  0.0723 0.2689 0.4009        0   <NA>
# Réalisation du graphique
chart(data = urchin, height ~ weight %col=% origin) + 
  geom_point() 
Graphique typique obtenu avec `chart()` : rendu par défaut publiable tel quel, et libellé automatique des axes avec les unités.

Figure 3.23: Graphique typique obtenu avec chart() : rendu par défaut publiable tel quel, et libellé automatique des axes avec les unités.

3.5.1 {ggplot2}

Le moteur graphique {ggplot2} est écrit pas Hadley Wickham, un personnage emblématique de la “révolution tidyverse” (même si ggplot est antérieur et n’utilise pas la syntaxe commune aux packages tidyverse) qui propose une surcouche moderne au-dessus de R. {ggplot2} implémente une “grammaire graphique” particulièrement puissante et flexible, imaginée et popularisée par le statisticien Leland Wilkinson. Par défaut, chart() crée en réalité un graphique {ggplot2} adapté. Voici la version {ggplot2} standard du même graphique représenté à la Fig. 3.23 :

ggplot(data = urchin, mapping = aes(x = weight, y = height, col = origin)) + 
  geom_point() 
Graphique typique obtenu avec `ggplot()` (moteur graphique {ggplot2}).

Figure 3.24: Graphique typique obtenu avec ggplot() (moteur graphique {ggplot2}).

En comparant les Figs. 3.23 et 3.24 (en faisant abstraction des instructions R utilisées pour l’instant), plusieurs éléments sautent immédiatement aux yeux :

  • Le thème par défaut de {ggplot2} est très reconnaissable avec un quadrillage blanc sur fond gris clair. On aime ou on n’aime pas, mais il est évident que (1) ce n’est pas une présentation “standard” d’un graphique scientifique, et (2) le thème tord un peu le cou à une règle importante pour réaliser un graphique de qualité : minimiser la quantité d’“encre” nécessaire pour représenter un graphique, autrement dit, plus le graphique est simple et sobre, mieux c’est. Le thème par défaut de chart() respecte mieux tout ceci16.

  • La taille des caractères est légèrement plus grande dans la Fig. 3.23 réalisée avec chart() (surtout pour les nombres sur les axes). Le manque de lisibilité des parties textuelles dans un graphique est un défaut fréquent, dépendant de la résolution et de la taille de reproduction du graphique dans le document final. Le choix de chart() recule un peu ce risque.

  • chart() est capable d’aller lire les métadonnées (libellés en français et unités des variables) et les utilise automatiquement pour proposer des libellés corrects et complets des axes par défaut. ggplot() ne peut pas le faire, et il faut utiliser la fonction labs() pour l’indiquer manuellement.

De manière générale, par rapport à ggplot(), chart() a été conçu pour produire le graphique le plus proche d’un rendu final impeccable avec tous les paramètres par défaut.

Quelques règles simples vous permettent de passer des instructions ggplot() à chart() et vice versa17 :

  1. On peut toujours remplacer ggplot() par chart() dans les instructions R (à condition que le package {chart} soit chargé bien sûr, par exemple via SciViews::R). Dans ce cas, le thème par défaut diffère, et le libellé automatique des axes (non disponible avec ggplot()) est activé.

  2. Avec chart() on peut utiliser aes() pour spécifier les “esthétiques” (éléments à visualiser sur le graphique) comme pour ggplot(), mais on peut aussi utiliser une interface formule plus compacte. Cette interface formule rapproche la version chart() des graphiques {ggplot2} d’un autre moteur de graphique dans R : {lattice}.

  3. Outre les esthétiques classiques x et y, l’interface formule de chart() permet d’en inclure d’autres directement dans la formule à l’aide d’opérateurs spécifiques %<esth>%=. Par exemple, aes(x = weight, y = height, col = origin) dans la Fig. 3.24 se traduit en la formule plus concise height ~ weight %col=% origin avec chart() (notez la position inversée de x et y dans la formule puisqu’on a y ~ x). Tous les esthétiques de {ggplot2} sont supportés de cette manière.

  4. Partout où aes() est utilisé pour les instructions {ggplot2}, on peut utiliser à la place f_aes() et y spécifier plutôt une formule de type chart().

  5. Avec ggplot() les facettes doivent être spécifiées à l’aide de facet_XXX(). À condition d’utiliser chart(), il est possible d’inclure les spécifications des facettes les plus utilisées directement dans la formule en utilisant l’opérateur |. Cette façon de procéder est identique à ce qui se fait dans {lattice} (voir plus loin).

Le point (5) mérite une petite démonstration pour comparaison :

a <- chart(data = urchin, height ~ weight | origin) + 
  geom_point() 

b <- ggplot(data = urchin, mapping = aes(x = weight, y = height)) + 
  geom_point() +
  facet_grid( ~ origin)

combine_charts(list(a, b))
Graphique à facettes. A. version `chart()`, B. version `ggplot()`.

Figure 3.25: Graphique à facettes. A. version chart(), B. version ggplot().

Enfin, pour ceux qui n’aiment pas la notation utilisant le + (car elle fait trop penser à une addition, ce qu’elle ne fait pas ici), vous pouvez aussi utiliser la fonction Sgg() du package {chart}. Cette fonction transforme toutes les commandes {ggplot2} / {chart} qui doivent être combinées à l’aide de + en commandes pouvant être combinées à l’aide de l’opérateur de pipe de R de base |>. Ce dernier opérateur est typiquement utilisé pour composer une instruction complexe à l’aide de plusieurs instructions simples (les blocs de construction). Il est donc plus logique ici. De l’aveu même de son auteur, {ggplot2} aurait utilisé |> à la place de + si cet opérateur existait à l’époque où ggplot a été conçu. Donc, Sgg() vient en quelque sorte corriger le tir. Pour utiliser Sgg(), vous remplacez le + par |>, et vous débutez le nom de votre fonction {ggplot2} par Sgg$ (seule exception: ggtitle() ne devient pas Sgg$ggtitle(), mais Sgg$title()). C’est tout. Donc, vous écrirez :

chart(data = urchin, height ~ weight) |>
  Sgg$geom_point()
Graphique réalisé en utilisant `gg$`.

Figure 3.26: Graphique réalisé en utilisant gg$.

De plus, vous bénéficiez pleinement de l’aide via la complétion de RStudio avec Sgg(). En effet, lorsque vous avez entré Sgg$ dans l’éditeur ou à la fenêtre console de RStudio, une liste apparaît avec toutes les options possibles.

Exercez-vous

Pour vous exercer à réaliser des graphiques chart()/ggplot(), lancez votre machine virtuelle dans Saturn Cloud. Fermer le projet s’il y en a un d’ouvert. Créer un script R. Chargez les données selon votre envie et réalisez trois graphiques inédits (qui n’ont pas été vu jusqu’ici). Consultez les liens suivants pour vous inspirer :

À vous de jouer !

Complétez votre projet de groupe en réalisant des graphiques de distribution pertinents.

Réalisez en groupe le travail A02Ga_analysis, partie II.

Travail en groupe de 4 pour les étudiants inscrits au cours de Science des Données Biologiques I : visualisation à l’UMONS à terminer avant le 2024-12-17 23:59:59.

Initiez votre projet GitHub Classroom

Voyez les explications dans le fichier README.md, partie II.

Pour en savoir plus

La suite de cette section est facultative : elle est importante pour comprendre les différents types de graphiques que vous allez rencontrer avec R. Cependant, si vous vous cantonnez aux graphiques chart()/ggplot() vous pouvez déjà réaliser énormément de visualisations différentes sans forcément connaitre les autres moteurs graphiques existants dans R.

3.5.2 {lattice}

Autant {ggplot2} est complètement modulable en ajoutant littéralement à l’aide de l’opérateur + des couches successives sur le graphique, autant {lattice} vise à réaliser les graphiques en une seule instruction. {lattice} utilise également abondamment l’interface formule pour spécifier les variables à utiliser dans le graphique. La version {lattice} du graphique d’exemple est présentée à la Fig. 3.27.

xyplot(height ~ weight, data = urchin, groups = origin, auto.key = TRUE)
Graphique exemple réalisé avec **lattice**.

Figure 3.27: Graphique exemple réalisé avec lattice.

Et voici la version chart() utilisant le moteur {lattice}. Notez la façon d’appeler la fonction xyplot() de {lattice} via chart$xyplot() :

theme_sciviews_lattice(n = 2)
a <- chart$xyplot(height ~ weight, data = urchin, groups = origin,
  auto.key = list(space = "right", title = "Origine", cex.title = 1, columns = 1),
  ylab = "Hauteur du test [mm]", xlab = "Masse totale [g]",
  par.settings = list(superpose.symbol = list(col = scales::hue_pal()(2))))

b <- chart(data = urchin, height ~ weight %col=% origin) + 
  geom_point() 

combine_charts(list(a, b))
Graphique exemple réalisé avec `chart()` A. avec le moteur **lattice**, B. avec le moteur **ggplot2**.

Figure 3.28: Graphique exemple réalisé avec chart() A. avec le moteur lattice, B. avec le moteur ggplot2.

La quantité d’instructions nécessaires pour rendre la version {lattice} proche de la version {ggplot2} devrait disparaître dans les prochaines versions de chart(). Un autre objectif est aussi de gommer le plus possible les différences entre les rendus des différents moteurs de graphiques R, et en particulier entre {ggplot2} et {lattice}. Comparez la Fig. 3.28A avec la Fig. 3.27 pour apprécier le gain déjà obtenu en matière d’homogénéisation.

Par rapport à {ggplot2}, les graphiques {lattice} sont moins flexibles du fait qu’ils doivent être spécifiés en une seule instruction. Cependant, ils sont parfois plus rapides à générer (appréciable quand il y a beaucoup de points à tracer) ! {lattice} offre également quelques types de graphiques non supportés par {ggplot2} comme les graphiques en 3D à facettes, par exemple.

Voici un graphique à facettes réalisé avec chart() et le moteur {lattice}. Notez que la formule utilisée est identique à celle employée pour la version {ggplot2} avec chart().

chart$xyplot(data = urchin, height ~ weight | origin,
  scales = list(alternating = 1),
  xlab = "Masse totale [g]", ylab = "Hauteur du test [mm]")
Graphique à facettes, avec `chart()` version {lattice}.

Figure 3.29: Graphique à facettes, avec chart() version {lattice}.

Mis à part les instructions additionnelles encore nécessaires dans cette version de chart(), l’appel et le rendu sont très similaires par rapport à la version {ggplot2} du même graphique avec chart() :

chart$xyplot(data = urchin, height ~ weight | origin,
  scales = list(alternating = 1),
  ylab = "Hauteur du test [mm]", xlab = "Masse totale [g]")
Graphique à facettes, avec `chart()` version {lattice}.

Figure 3.30: Graphique à facettes, avec chart() version {lattice}.

Vous noterez que pour réaliser un graphique {lattice} à l’aide de chart(), vous devez rajouter $nom_de_function_lattice après chart$. Aussi, le thème spécifique SciViews et les labels et unités automatiques ne sont pas encore supportés. Les graphiques {lattice} ne supportant pas l’opérateur + pour ajouter des couches comme pour {ggplot2}, vous devez spécifier l’ensemble des options comme arguments de la fonction chart$fun(). Cela peut devenir pénible s’il y en a beaucoup. Toutefois {lattice} peut rendre d’énormes services pour des graphiques très compliqués, grâce à sa vitesse nettement supérieure à {ggplot2}.

3.5.3 Graphiques de base

Comme son nom le suggère, le moteur graphique de base est celui qui est implémenté de manière native dans R. Il est donc utilisé un peu partout. Il est vieillissant et est plus difficile à manipuler que {ggplot2} certainement, et même que {lattice}. Néanmoins, il est très flexible et rapide, et encore très utilisé… mais son rendu par défaut n’est plus vraiment au goût du jour. Voici notre graphique d’exemple rendu avec le moteur graphique R de base :

plot(urchin$weight, urchin$height,
  col = c("red", "darkgreen")[urchin$origin], pch = 1)
legend(x = 80, y = 10, legend = c("Culture", "Pêcherie"),
  col = c("red", "darkgreen"), pch = 1)
Graphique exemple réalisé avec le moteur graphique R de base.

Figure 3.31: Graphique exemple réalisé avec le moteur graphique R de base.

Vous rencontrerez très fréquemment la fonction plot(). C’est une fonction dite générique dont le comportement change en fonction de l’objet fourni en premier argument. Ainsi, elle réalise le graphique le plus pertinent à chaque fois en fonction du contexte. Notez tout de suite les instructions un peu confuses nécessaires pour spécifier la couleur souhaitée en fonction de l’origine des oursins. Le moteur graphique de base ne gère pas automatiquement des aspects plus complexes du graphique, tels que le positionnement d’une légende. Donc, à moins d’avoir prévu la place suffisante avant de tracer le graphique, nous ne pouvons que l’inclure à l’intérieur du cadre du graphique dans un second temps à l’aide de la fonction legend(). Comme cette dernière ne comprend rien à ce qui a été réalisé jusqu’ici, il faut lui respécifier les couleurs, formes et tailles de points utilisés ! C’est un des aspects pénibles du moteur graphique R de base.

Voici maintenant une version chart() de ce graphique de base :

chart$base({
  par(mar = c(5.1, 4.1, 4.1, 6.1))
  plot(urchin$weight, urchin$height,
    col = scales::hue_pal()(2)[urchin$origin], pch = 19, cex = 0.8,
    xlab = "Masse totale [g]", ylab = "Hauteur du test [mm]")
  legend(x = 105, y = 20, legend = c("Culture", "Pêcherie"), title = "Origine",
    col = scales::hue_pal()(2), pch = 19, bty = "n", cex = 0.8, y.intersp = 2)
})
Graphique exemple réalisé avec le moteur graphique de base et la fonction `chart()`.

Figure 3.32: Graphique exemple réalisé avec le moteur graphique de base et la fonction chart().

Vous ne le voyez pas dans le bookdown, mais vous le réaliserez si vous utilisez ce genre de code dans vos propres documents Quarto ou R Markdown : le graphique est en réalité généré deux fois : une première fois dans un format propre aux graphiques R de base, et ensuite, il est traduit en une forme compatible avec les autres graphiques {ggplot2} et {lattice} (et au passage, il gagne la grille en traits grisés). Dans le chunck, nous devons spécifier fig.keep = 2 si nous voulons éviter d’imprimer la première version dans le rapport lorsqu’on utilise chart$base().

Pour l’instant, le seul avantage de chart() avec les graphiques de base est qu’il les convertit en une forme combinable avec les autres graphiques dans une figure composite (sinon, ce n’est pas possible). À part cela, il faut fournir à chart$base() tout le code nécessaire pour tracer et personnaliser le graphique. Comme on peut le voir sur cet exemple, cela demande une quantité considérable de code. C’est aussi un autre aspect pénible de ce moteur graphique : il est très flexible, mais l’interface n’est pas optimale. Pour finir, les graphiques de base ont plus de mal avec les facettes, mais ils peuvent quand même générer les versions les plus simples, par exemple à l’aide de la fonction coplot() qui accepte une formule très similaire à ce que nous avons employé jusqu’ici, mais avec un rendu différent :

coplot(data = urchin, height ~ weight | origin)
Graphique à facettes avec le moteur graphique de base.

Figure 3.33: Graphique à facettes avec le moteur graphique de base.

À l’issue de cette comparaison, vous pourrez décider du moteur graphique que vous préférerez utiliser. Dans le cadre de ce cours, nous n’utiliserons en tous cas que quasi exclusivement des graphiques {ggplot2} créés à l’aide la fonction chart().

Pour en savoir plus

  1. Notez que plusieurs thèmes existent dans ggplot2. Il est facile d’en changer et de les personnaliser… mais c’est toujours appréciable d’avoir un rendu impeccable dès le premier essai.↩︎

  2. Étant donné l’abondante littérature écrite sur {ggplot2}, il est utile de pouvoir convertir des exemples {ggplot2} en graphiques chart(), si vous êtes convaincu par cette nouvelle interface.↩︎