[ggplot2] Welcome viridis !

[ggplot2] Welcome viridis !

Accueillons comme il se doit viridis, nouvelle palette de {ggplot2}!

Viri-what ?

viridis est l’une des palettes préféré d’un membre de l’équipe (guesswho).

La palette viridis a d’abord été développée pour le paquet python matplotlib, et a été implémentée dans R depuis.

Les points forts de cette palette sont :

  • les graphiques sont beaux (ce qui est une raison suffisante)
  • les couleurs sont perçues de manière uniformes, même lorsqu’elles sont imprimées en noir et blanc (oui, les gens impriment encore en 2018….)
  • les couleurs sont distinguées par les formes les plus courantes de daltonisme (une caractéristique importante)

Cette palette est maintenant le schéma de couleurs par défaut dans le paquet python de matplotlib. Il était disponible dans R avec le package {viridisLite}, puis sous forme de scale ggplot2 dans {viridis}, et est maintenant entièrement intégré dans {ggplot2}, avec la dernière version.

Pourquoi viridis? Un peu d’histoire

Comme dit dans A Better Default Colormap for Matplotlib: “colormap are interfaces between your data and your brain” — ce qui signifie que le choix des couleurs peut avoir un effet significatif sur la façon dont vous prenez vos décisions. Nous n’entrerons pas dans les détails de la théorie des couleurs (il y a beaucoup de littérature sur ce sujet, et nous ne sommes pas les mieux placés pour en parler en profondeur), mais il est évident que les couleurs influencent la façon dont vous percevez les choses.

Par exemple, en Europe de l’Ouest (et je suppose dans de nombreux pays du monde), nous sommes culturellement conditionnés à considérer un signe rouge comme une alerte. Le vert, par contre, a tendance à indiquer quelque chose de « safe ». Si aujourd’hui je devais concevoir un schéma de couleurs pour l’erreur / avertissement / succès, j’opterais sans aucun doute pour une solution rouge / orange / vert. Je ne suis pas le seul, et le premier exemple qui me vient à l’esprit est le package {shinyalert}, qui a ce modèle exact de couleurs. Cette première approche semble simple (et c’est le cas), et il y a beaucoup d’articles et de livres sur ce paradigme appliqué au marketing et à la vente (je vous laisserai chercher ça sur Google-scholar).

Par exemple, voici une « roue des couleurs » appelée la roue des émotions de Plutchik, réalisée par le psychologue du même nom :

NB: nous ne partageons pas cette théorie simplifiée de la couleur, nous l’avons mise ici pour l’exemple 😉 

Prenons un exemple plus sérieux, emprunté à la vidéo que j’ai linké tout à l’heure. Dans un article (qui a l’air très divertissant) appelé “Evaluation of artery visualizations for heart disease diagnosis” (link), les auteurs ont montré comment une palette de couleurs peut jouer un rôle critique dans le diagnostic de la présence d’une maladie cardiaque. Je vous laisserai creuser dans les détails si vous le souhaitez, mais pour faire court, la palette de couleurs du logiciel utilisé pour visualiser les résultats a un effet sur la capacité du professionnel à lire et à détecter une condition spécifique.

Pourquoi parlons-nous de cela ? Parce que nous prêchons pour que R soit utilisé en entreprise pour visualiser les données, il est donc crucial de garder à l’esprit que le schéma de couleurs que nous choisissons d’utiliser n’est pas sans impact, que ce soit sur la lisibilité ou sur ce que les couleurs transmettent comme émotion.

Le petit nouveau

La palette de couleurs standard dans un grand nombre d’outils s’appelle la palette Jet. Vous la connaissez peut-être, car elle est implémenté dans de nombreux logiciels. Ce schéma de couleurs apparaît comme un bon choix pour beaucoup car nous avons tendance à penser que cette palette permet de distinguer facilement les couleurs (spoiler : ce n’est pas le cas).

Voici à quoi elle ressemble :

library(matlab)
## Attaching package: 'matlab'
## The following object is masked from 'package:stats':
##     reshape
## The following objects are masked from 'package:utils':
##     find, fix
## The following object is masked from 'package:base':
##     sum

Cette example est emprunté à la vignette de viridis, saupoudré d’une fonction

with_palette <- function(palette) {
  x <- y <- seq(-8 * pi, 8 * pi, len = 40)
  r <- sqrt(outer(x^2, y^2, "+"))
  filled.contour(cos(r^2) * exp(-r / (2 * pi)),
    axes = FALSE,
    color.palette = palette,
    asp = 1
  )
}
with_palette(jet.colors)

Comparons les mêmes données avec viridis.:

library(viridis)
## Loading required package: viridisLite
with_palette(viridis)

S’il vous semble évident que la palette viridis est plus facile à lire…. c’est parce qu’elle l’est.

Comment implémenter une nouvelle palette de couleurs

Avec viridis, le but était d’avoir quelque chose qui est :

  • coloré
  • beau
  • séquentiel
  • « perceptually uniform »
  • fonctionnel en noir et blanc
  • accessible aux daltoniens

Alors, comment peut-on faire cela ? Comment mettre en place une nouvelle palette répondant à ces exigences ? Avant de répondre à cette question, analysons comment les données vont de votre ordinateur à votre cerveau.

Pour être projetées, les données sont transformées en notation RGB (pour « Red Green Blue »), soit en hex (#FFCC99), soit en pourcentage (rgb(100%,80%,60%) : 100% rouge, 80% vert, 60% bleu) ou en nombre entre 0 et 255 (rgb(255,204,153)). Toutes ces notations sont les mêmes : la notation hexadécimale étant utilisée pour représenter les nombres de 0 à 255 avec 16 symboles (de 0-9 & A-F, 16^2 étant 256).

Dans beaucoup de cas, avec la couleur, il y a deux symboles ajoutés à l’extrémité de l’hex, utilisés pour indiquer la transparence. Par exemple :

viridis(2)
## [1] "#440154FF" "#FDE725FF"

Note : ceci est rendu possible par le fait qu’un pixel est codé en 32 bits, et qu’une couleur n’est représentée que 24 de ces 32 bits (3*8), laissant de la place pour 8 bits supplémentaires.

Note bis : vous vous rappelez que la notation hexagonale permet 256 combinaisons, ce qui correspond au nombre de combinaisons possibles pour 8 bits (2^8).

Ainsi, une fois que vous avez la couleur la plus basse et la plus haute, vous pouvez utiliser un générateur de palette de couleurs pour obtenir une gamme de couleurs de l’une à l’autre. C’est fait, par exemple, par la fonctioncolorRampPalette de {grDevices} :

colfunc <- colorRampPalette(c("black", "white"))

colfunc(10)
[1] "#000000" "#1C1C1C" "#383838" "#555555" "#717171" "#8D8D8D" "#AAAAAA" "#C6C6C6"
[9] "#E2E2E2" "#FFFFFF"

Le calcul de cette échelle est hors de la portée de cet article, mais vous obtenez l’idée 🙂 Une fois que nous avons décidé à quoi doit ressembler notre palette, il est alors possible d’adapter vos données à votre palette : comme votre palette va d’un point de l’échelle de couleurs à un autre en passant par une série de tons intermédiaires, vous pouvez donner à chaque valeur de vos données un point dans votre carte de couleurs. La valeur la plus basse de vos données est mappée à la valeur la plus basse de la palette, et la plus haute à la plus haute.

viridis

Maintenant que nous avons la théorie, passons à la grande question : comment pouvons-nous choisir une palette qui est « daltonisme-compatible » ? Pour ce faire, la solution trouvée par viridis est d’éviter le rouge et de passer du bleu au jaune, car ces couleurs peuvent être vues par la plupart des daltoniens.

Simulons le daltonisme avec le package{dichromat} :

library(dichromat)

Voyons à quoi ressemble la palette jet lorsque vous simulez plusieurs cas de daltonisme :

library(purrr)
with_palette(
  compose(
    partial(dichromat, type = "deutan"),
    jet.colors
    )
)

with_palette(
  compose(
    partial(dichromat, type = "protan"),
    jet.colors
    )
)

with_palette(
  compose(
    partial(dichromat, type = "tritan"),
    jet.colors
    )
)

Et avec viridis:

with_palette(
  compose(
    partial(dichromat, type = "deutan"),
    viridis
    )
)

with_palette(
  compose(
    partial(dichromat, type = "protan"),
    viridis
    )
)

with_palette(
  compose(
    partial(dichromat, type = "tritan"),
    viridis
    )
)

Comme vous pouvez le constater, le contraste reste lisible dans toutes les situations.

Enfin, pour répondre au besoin d’impression, la palette devait passer du foncé à la lumière. Et la gamme plus large allait du bleu foncé au jaune vif. Comparons deux exemples :

with_palette(
  compose(
    colorspace::desaturate,
    jet.colors
    )
)

with_palette(
  compose(
    colorspace::desaturate,
    viridis
    )
)

De retour à R

Donc, assez de théorie, retour à R.

viridis comme 📦

Imaginons un instant que nous utilisons l’ancienne version de ggplot2 (et beaucoup le font encore sûrement à l’heure où nous écrivons ces lignes).

Lors de l’utilisation de cette (maintenant ancienne) version de {ggplot2}, nous avions besoin du paquet {viridis} pour utiliser cette palette de couleurs.

library(ggplot2)
library(viridis)
ggplot(iris) + 
  aes(Sepal.Length, Sepal.Width, color = Petal.Length) + 
  geom_point() + 
  scale_colour_viridis() # from the viridis package

Une autre façon de le faire était d’obtenir un vecteur de couleurs hexadécimales, avec le paquet {viridisLite} (notez que les fonctions de {viridis} appellent les fonctions de {viridisLite}).

pal <- viridisLite::viridis(3)
pal
## [1] "#440154FF" "#21908CFF" "#FDE725FF"
ggplot(iris) + 
  aes(Sepal.Length, Sepal.Width, color = Species) + 
  geom_point() + 
  scale_color_manual(values = pal)

 

viridis comme scale ggplot

Mais maintenant, grâce au nouveau ggplot2 (version 3.0.0.0), vous pouvez appeler directement la palette viridis avec les fonctions ad hoc.

ggplot(iris) +

    aes(Sepal.Length, Sepal.Width, color = Species) +

    geom_point() +

    scale_color_viridis_d()#From ggplot2

Il existe deux échelles pour les entrées numériques : discrètes (scale_color_viridis_d) et continues (scale_color_viridis_c).

Et il y a bien sûr la contrepartie avec fill :

ggplot(faithfuld) +
  aes(waiting, eruptions, fill = density) + 
  geom_tile() + 
  scale_fill_viridis_c()

Notez la différence de lisibilité par rapport à la palette par défaut :

ggplot(faithfuld) +
  aes(waiting, eruptions, fill = density) + 
  geom_tile() 

De plus, viridis est la nouvelle palette par défaut pour les facteurs ordonnés :

diamonds %>%
  dplyr::count(cut, color) %>%
  ggplot() +
  aes(cut, n, fill = color) +
  geom_col()

Enfin, et je ne les ai pas présentés jusqu’à présent, mais il y a 4 autres colormaps disponibles : « magma » (ou « A »), « inferno » (ou « B »), « plasma » (ou « C »), et « cividis » (ou « E »), qui peuvent être passés comme argument option dans les différentes échelles :

ggplot(mtcars) + 
  aes(mpg, disp, color = cyl) + 
  geom_point() + 
  scale_color_viridis_c(option = "A")

ggplot(mtcars) + 
  aes(mpg, disp, color = cyl) + 
  geom_point() + 
  scale_color_viridis_c(option = "B")

ggplot(mtcars) + 
  aes(mpg, disp, color = cyl) + 
  geom_point() + 
  scale_color_viridis_c(option = "C")

ggplot(mtcars) + 
  aes(mpg, disp, color = cyl) + 
  geom_point() + 
  scale_color_viridis_c(option = "E")

Donc, pour conclure : 💛💚💜💙

En conclusion, voici pourquoi l’inclusion du viridis comme scale native dans ggplot2 est une bonne nouvelle :

  • De plus belles parcelles (mais en fin de compte, c’est secondaire)
  • Des produits finaux meilleurs et plus inclusifs : plus faciles à lire pour les personnes daltoniennes ou ayant des problèmes de vision

NB : bien sûr, c’était déjà possible avant, mais le fait que viridis soit natif le rendra plus utilisé et plus (re)connu.