Comme faire un raincloud plot avec le logiciel R ?

Table des matières

Qu'est ce que c'est un raincloud plot ?

Un raincloud plot, c’est un graphique qui réunit 3 visualisations : une courbe de densité , un boxplot et les données brutes (sous la forme de dotplot ou de points écartés (jittered points))

Pourquoi c'est bien un raincloud plot ?

Pour 3 raisons (au moins)  :

✅ Parce que les densités seules ne fournissent pas de paramètres résumés, comme les quartiles 1 et 3, et la médiane (qui est aussi le quartile 2 😉 )

✅ Parce que les boxplots seuls fournissent peu d’informations sur la distribution (on peut avoir deux boxplots identiques pour des distributions bien différentes – voir ci-dessous ) :

✅ Parce que les boxplots et les densités ne renseignent pas sur le nombre de données

distributions différentes mais mêmes boxplots
D'après "Visualizing Distributions with Raincloud Plots (and How to Create Them with ggplot2)" publié par Cédric Scherer

Pourquoi ça s'appelle un raincloud plot?

Parce que lorsque les données brutes sont représentées avec un dotplot, cela ressemble à de la pluie qui tombe 🌧️ qui tombe d’un nuage (représenté par la courbe de densité)

La recette du raincloud plot, en pas à pas 👣

La réalisation du raincloud peut être divisé en 4 parties

  1. réalisation de la courbe de densité
  2. réalisation du boxplot
  3. ajout des données brutes (avec un dot plot ou des points écartés (jittered points)
  4. embellissement du graphique 

La courbe de densité est réalisé à l’aide de la fonction stat_halfeye() du package ggdist, en fixant certains arguments (voir la description dans le code).

Les valeurs de ces arguments sont celles employées par Cédric Scherer (grand spécialiste de la data visualisation).
Pour voir leur impact, vous pouvez les modifier tour à tour.

library(palmerpenguins)
data(penguins)

library(ggplot2)
library(ggdist)

library(tidyverse)

# partie courbe de densité
g1 <- penguins %>% 
  ggplot(aes(x=species, y=flipper_length_mm, fill=species))+
  stat_halfeye(adjust = 0.5, # permet de régler le lissage
               width=0.5, # permet de gérer la hauteur des courves
               justification = -0.2, # permet de déplacer les courbes sur la droite
               .width = 0, point_colour = NA) # permet de supprimer l'affichage d'un intervalle présentt par défaut


plot(g1) # affichage le plot 
courbes de densité

La seconde étape consiste à ajouter des boxplots, à l’aide de la fonction geom_boxplot() du package ggplot2 :

# ajout des boxplots
g2 <- g1 +
  geom_boxplot(
    width = 0.12, # gère la largeur des boites
    outlier.color ="purple", # passe les outliers en orange
    alpha = 0.5) # ajoute une transparence dans les boites (elles sont plus claires que l'aire des courbes de densités)
 
plot(g2) # affichage du plot 

La troisième étape consiste à ajouter les données brutes. Pour cela, on peut utiliser une visualisation en dotplot (avec la fonction stat_dots() du pacakge ggdist), ou une visualisation avec des points décalés (jittered) (avec la fonction geom_point() du package ggplot2 en précisant une position de type “jittered”) :

g3 <- g2+ 
    stat_dots(
       dotsize=0.5, # diminue la taille des points par 2
       side = "left", # permet de placer les points du coté opposé à la courbe de densité
     justification = 1.1, # permet d'éloigner les points du boxplot
     binwidth = 1 # permet de regrouper les points (ici par pas de 1 mm de flipper length, la taille des points s'adapte)
  ) 

plot(g3) 

La même chose avec des points décalés, en employant la fonction geom_point() du package ggplot2 :

g3bis <- g2+ 
    geom_point(aes(colour=species), # permet d'ajouter une couleur sur le spoints
    size = 1.3, # gère la taille des points
    alpha = .3, #ajoute de la transparence sur les points
    position = position_jitter( # permet d'obtenir des points décalés
      seed = 1, # permet de toujours obtenir la même représnetation aléatoire des points 
      width = .09 # permet de gérer la largeur du décalage
    ))

plot(g3bis) 

Pour finir, nous pouvons embellir cette visualisation, en :

  •  ajoutant un titre
  • renversant le graphique grâce à la fonction coord_flip(),
  • et en ajoutant un thème (en employant les couches du package ggthemes)
  • supprimant la légende (qui ne sert à rien)
library(ggthemes)

g4 <- g3 +
  ggtitle("Distribution de la longueur de la nageoire\n par espèce (Raincloud Plot)")+
  coord_flip() + # permet de faire une rotation du plot
  theme_solarized()+
  scale_colour_solarized('blue')+
  theme(legend.position = "none") # permet de supprimer la légende

plot(g4) 
raincloud final

Vous la voyez le nuage de pluie 🌧️là ,  non ?

Vous pouvez changer de thème en remplaçant les lignes 

 theme_solarized()+
  scale_colour_solarized('blue' 

par les lignes suivantes : 

scale_fill_fivethirtyeight() +
theme_fivethirtyeight() +

# ou
theme_economist() + 
scale_colour_economist()+

# ou (il y en a d'autres)
theme_gdocs() + 
  scale_colour_gdocs() 

Et si vous avez besoin de modifier l’ordre des modalités de votre variable catégorielle,  👉  consultez l’article Comment modifier l’ordre d’affichage dans un plot ?

Un raincloud plot en version ultra-express 🚀

Voici un petit hack pour réaliser un raincloud ultra facilement : utilisez la fonction ggbetweenstats() du package ggstatsplot.

Par défaut, la fonction ggbetweenstats() renvoie de nombreuses informations sur le plot, mais il est très facile de les supprimer.

library(ggstatsplot)

ggbetweenstats(
  data = penguins,
  x = species,
  y = flipper_length_mm
)
 
un raincloud automatisé

Pour supprimer, les informations de comparaisons et de taille d’effet, on utilise les arguments results.subtitle = FALSE et pairwise.comparisons = FALSE.

ggbetweenstats(
  data = penguins,
  x = species,
  y = flipper_length_mm,
  results.subtitle=FALSE,
  pairwise.comparisons = FALSE

) 
suppressions des informations inutiles

Ce n’est pas que je sois complètement adepte du moindre effort, mais il faut bien avouer que la solution ggbetweenstats() est diablement 👿 efficace !

Et vous, vous êtes plutôt perfectionniste ou plutôt les pieds sur le bureau….?

👉 Dites le moi en commentaire !!

Pour aller plus loin

Si vous voulez approfondir vos connaissances, sur les raincloud,  je vous conseille ces 3 ressources :

Vous souhaitez soutenir mon travail ?​

C’est possible en faisant un don  sur la page Tipeee du blog  👇👇👇

 

20 réponses

  1. Merci beaucoup pour ce post toujours très intéressant, comme d’habitude ! Je me demande du coup quelle est la différence entre le raincloud plot (version rapide en particulier) et le violin plot auquel on ajoute le boxplot + les données ?
    Merci pour votre éclairage.
    Sophie

    1. Bonjour Sophie,

      Ouh la question piège…..je tente quand même une réponse. Pour moi la différence est surtout esthétique : dans la version de ggbetweenstats, les 3 graphes sont superposés (les uns sur les autres) alors que dans la version manuelle, ils sont juxtaposés (les un à côté des autres); ce qui rend les données plus lisibles, et le graphique plus élégant.

      Le violin plot c’est une courbe de densité en miroir. Lorsqu’on utilise ggplot, on peut faire différents réglages (Cedric Scherer en parle dans son article : https://www.cedricscherer.com/2021/06/06/visualizing-distributions-with-raincloud-plots-and-how-to-create-them-with-ggplot2/). Je ne sais pas quel est le réglage par défaut de celui réalisé par ggbetweenstats.

      La version manuelle, permet probablement des réglages plus fins, qui peuvent surement s’avérer très utiles pour mieux représenter les données, dans certaines situations.

      J’espère que cela vous aide. Et vous qu’en pensez-vous ?

  2. cette représentation graphique semble en effet très intéressante… mais en utilisant vos ligne de code je ne fait pas apparaître les graphiques que vous présentez…

    1. Bonjour,

      je viens d’essayer, cela semble bien fonctionner.
      Pouvez-vous, s’il vous plait, êtes plus précis, et me dire quel graphique ne correspond pas ?
      Merci.

  3. Bonjour
    La version hack de ggbetweenstats() est moins esthétique que la version “manuelle” et la distribution de densité semble remplacée par une représentation en violon.
    Y aurait il un équivalent de esquisse/esquisseR avec quelques fonctions plus avancées permettant ce très esthétique graphe en nuage pluvieux.
    Merci de toutes vos informations
    Lionel

    1. Bonjour Lionel,

      je suis bien d’accord avec vous ggbetweenstats() est moins élégante, mais beaucoup plus rapide. Je suis convaincue qu’elle peut être suffisante dans de nombreuses situations.
      Je viens de regarder sous esquisse, à priori, on ne peut pas combiner plusieurs graphiques, j’arrive seulement à ajouter les points jittered aux boxplots….
      Bonne continuation

  4. Merci beaucoup pour cette ressource,
    Moi j’ai trouvé des graphique sans couleurs et à la fin au lieu d’avoir des graphique en position horizontale, les graphiques sont restés vertical

  5. Bonjour Claire,
    Avant tout merci pour cet article qui m’est très utile et qui m’a permis de découvrir ce genre de représentations graphique.
    J’ai une question concernant l’espace que l’on peut appliquer sur l’axe x entre les catégories. En effet dans mon cas j’ai 10 catégories à représenter sur celui ci et le problème est que les points se chevauchent d’une catégorie à l’autre (beaucoup de points avec la même valeur). J’ai bien essayer de chercher une solution en changeant la taille des points, en réduisant l’épaisseur des formes et en essayant de personnaliser l’axe mais rien à faire je bloque !
    Auriez-vous une idée pour éviter ce chevauchement ? (autre que la représentation des points proposées en g3bis dans votre exemple)
    Je vous remercie d’avance pour votre réponse.

      1. Bonjour Claire,

        Merci pour votre réponse.
        Oui c’est ce que j’ai fais dans un premier temps mais rien à faire, les points deviennent trop petits pour être discerné et donc le raincloud plot perds de son intérêt. Ce qu’il faudrait c’est vraiment pouvoir indiquer à R d’agrandir l’espacement sur l’axe x entre les différentes catégories. Pensez-vous que ce soit possible avec ggplot2 ?

        1. Je ne sais pas le faire, regardez du côté de la fonction scale_x_discrete().
          Sinon, vous pouvez diminuer la hauteur de la courbe avec l’argument width de la fonction stat_halfeye(). Et aussi diminuer la largeur des boites du boxplot avec l’argument width de la couche geom_bxplot().
          Tenez-nous informé 🙏si vous trouvez la solution, je suis certaine que cela aidera d’autres lecteurs et lectrice du blog.

  6. Bonjour Claire,
    Merci beaucoup pour cet article vraiment très utile pour la visualisation d’une distribution.
    Néanmoins, je suis confronté à un problème que je ne parviens pas à traiter : mes données sont trop nombreuses (42 000 données sur ma variable à 3 modalités). Du coup, la création du graphique 3 prend beaucoup de temps et le résultat est inexploitable. Il y a tellement de points qu’ils chevauchent les courbes de densité et recouvrent même toute la surface de l’image …
    Existe-t-il une technique pour extraire un échantillon plus petit des données tout en conservant les caractéristiques de la distribution ?
    Merci pour votre éclairage
    Fabien

    1. Bonjour Fabien,

      Pour extraire un échantillon tout en conservant les caractéristiques de la distribution, vous pouvez employer une méthode d’échantillonnage aléatoire.
      Par exemple :
      # Fixer la graine aléatoire pour assurer la reproductibilité
      set.seed(123)

      # Extraire un échantillon aléatoire de taille 100
      sample_vec <- sample(population_vec, size = 100, replace = FALSE)

      La fonction **`sample()`** est utilisée pour extraire un échantillon aléatoire (de taille 100 dabs l'exemmple) à partir du vecteur "population_vec" (vos 42 000 valeurs). L'argument **`size`** est utilisé pour spécifier la taille de l'échantillon et l'argument **`replace`** est réglé sur **`FALSE`** pour garantir que chaque observation de la population ne sera échantillonnée qu'une seule fois. La fonction **`set.seed()`** est utilisée pour fixer la graine aléatoire et assurer que le même échantillon sera extrait à chaque exécution du code.

      Je vous conseille de vérifier que l'échantillon est représentatif de la population et qu'il n'y a pas de biais dans le processus d'échantillonnage.
      Pour cela vous pouvez calculer les moyennes et sd de l'échantillon et du vecteur source. Vous pouvez aussi réaliser des courbes de densité (de l'échantillon et de la population) pour vérifier qu'elles ont bien les mêmes formes.

      J'espère que cela vous aide.
      Bonne continuation

  7. Merci Claire,
    J’y dû m’y reprendre à plusieurs fois, mais c’est bon, ça marche
    En fait, je misais sur un échantillon trop petit et mes moyennes et sd étaient trop éloignés de la distribution d’origine
    Au final, je suis monté à un échantillon de 4300 (pour plus de 42000 dans la distribution d’origine) pour retrouver les bonnes caractéristiques
    Je me demande si ce ne sont pas les données manquantes qui jouaient trop d’influence dans les petits échantillons
    Un grand merci à vous pour cette fonction que je ne connaissais pas, je l’ai notée dans mon cahier à spirales 😉
    Vous êtes vraiment trop forte !
    Encore un grand merci
    Fabien

  8. Super article ! (as usual).

    Une remarque, si comme moi vous utilisez des labels pour les axes x et y, il faut penser à rajouter
    ‘+ theme(axis.title = element_text())’ lorsque l’on utilise certains thèmes qui par défaut ne les affiche pas.

    Exemple :
    scale_fill_fivethirtyeight() +
    theme_fivethirtyeight()

  9. Une seconde remarque, si on souhaite ajoute la pvalue avec stat_pvalue_manual(), il faut spécifier l’argument aes(fill = ma_variable) pour chaque construction, et non au départ via ggplot().
    (explication ici : https://github.com/kassambara/ggpubr/issues/266 )

    #good
    stat.test % wilcox_test(V2 ~ V1)
    dat%>%
    ggplot(aes(x= V1, y=V2))+
    stat_halfeye(aes(fill = V1)) +
    geom_boxplot(aes(fill = V1)) +
    stat_dots(aes(fill = V1)) +
    stat_pvalue_manual(stat.test)

    #not good
    stat.test % wilcox_test(V2 ~ V1)
    dat%>%
    ggplot(aes(x= V1, y=V2, fill = V1))+
    stat_halfeye() +
    geom_boxplot() +
    stat_dots() +
    stat_pvalue_manual(stat.test)

Laisser un commentaire

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

Bonjour !

vous venez souvent ?

Identifiez-vous pour avoir accès à toutes les fontionnalités !

Aide mémoire off'R ;)

Enregistrez vous pour recevoir gratuitement mes fiches “aide mémoire” (ou cheat sheets) qui vous permettront de réaliser facilement les principales analyses biostatistiques avec le logiciel R et pour être informés des mises à jour du site.