Tutoriel : comment faire des boxplots appariés (paired boxplot)

Dans de nombreuses études, il est courant de procéder à des mesures répétitives d’une variables, sur les mêmes individus, à différents moments. La visualisation de telles données nécessite de réaliser des graphiques permettant de mettre en évidence les changements individuels au fil du temps (autrement dit : les trajectoires individuelles). À cet égard, les “spaghetti plots” se révèlent être un choix judicieux.

Cependant, il est souvent également intéressant d’explorer l’évolution collective des données pour l’ensemble des participants, ainsi que de comprendre la variabilité des réponses. Pour répondre à ces besoins, les boxplots appariés, également connus sous le nom de paired boxplots, offrent une solution particulièrement efficace.

Dans ce tutoriel, je vous montre en pas à pas, 3 solutions pour réaliser facilement ces paired boxplots avec R.

La première consiste à les créer de toutes parts avec le package ggplot2, alors que les deux suivantes consistent à utiliser des packages facilitant leur réalisation ggpubr et ggstatsplots

En bonus, vous montre également comment visualiser vos données appariées avec le logiciel d’analyses JASP.

Table des matières

Les données

Pour ce tutoriel, nous allons employer une partie des données ChickWeight du package dataset (chargé par défaut à chaque session R). Pour cela, nous allons filter les données pour :

  • ne conserver qu’un seul régime : la Diet 1
  • ne conserver que les temps de mesure 6, 14 et 21
  • ne conserver que les animaux ayant des données complètes (3 mesures)

Les boxplot appariés que nous allons réaliser permetront de visualiser l’évolution individuelle et globale du poids des poulets au cours du temps.

library(dplyr)
CW <- ChickWeight %>% 
  filter(Diet==1 & Time %in% c(6,14, 21)) %>% 
  filter(! Chick %in% c(16,15,8 )) %>% 
  mutate(Time = as.factor(Time))%>% 
  droplevels() %>% 
  as_tibble() 


head(CW)
# A tibble: 6 × 4
  weight Time  Chick Diet 
   <dbl> <fct> <ord> <fct>
1     64 6     1     1    
2    125 14    1     1    
3    205 21    1     1    
4     72 6     2     1    
5    138 14    2     1    
6    215 21    2     1    
 

Ici les données sont en format “long”, puisque les poids observés aux différents temps sont dans la même colonne.

Dans le format wide les poids observés aux temps 6, 14 et 21 seraient dans 3 colonnes différentes (voir plus loin).

format wide et format long

Pour en savoir plus sur ces format long et wide, vous pouvez consulter l’article Format wide et long : pourquoi, et comment ? :

A noter que les fonctions décrites dans l’article ont été remplacées par les fonctions pivot_longer() et pivot_wider().

Réaliser des boxplots appariés avec ggplot2

Le code ci-dessous emploi des données au format long :

library(ggplot2)
ggplot(CW, aes(x=Time, y=weight, colour=Time))+
  geom_point()+
  theme(legend.position="none")+
  geom_boxplot(alpha=0)+
  geom_line(aes(group=Chick), colour="grey70")+
  theme_classic() 
  • ggplot(CW, aes(x=Time, y=weight, colour=Time)): Cette instruction crée l’objet de base du graphique ggplot. La fonction aes()définit les esthétiques du graphique, en indiquant que Time sera sur l’axe des x, weight sur l’axe des y, et que la couleur des points sera également déterminée par Time`. Cela signifie que les points seront colorés différemment en fonction de leur valeur Time.

  • geom_point(): Ajoute des points au graphique, chaque point représentant une observation dans le dataframe CW.

  • theme(legend.position="none"): Supprime la légende du graphique.

  • geom_boxplot(alpha=0): Ajoute un boxplot au graphique, mais le rend entièrement transparent (alpha=0).

  • geom_line(aes(group=Chick), colour="grey70"): Trace des lignes pour connecter les points, avec les lignes groupées par la variable Chick. La couleur des lignes est définie comme un gris clair (“grey70”).

  • theme_classic(): Applique un thème classique au graphique, qui modifie l’apparence générale en éliminant l’arrière-plan gris et en mettant en place des axes et des polices plus traditionnels.

Réaliser des boxplots appariés avec ggpubr

 La fonction ggpaired()permet de réaliser des boxplots appariés avec des données au format long et au format wide (mais uniquement avec 2 modalités pour le format wide)

Boxplots appariés avec ggpubr sur des données au format long

library(ggpubr)
ggpaired(CW, 
         x = "Time", 
         y = "weight",
         id="Chick",
         color = "Time", 
         line.color = "gray80", 
         line.size = 0.4,
         palette = "npg") 

Boxplots appariés avec ggpubr sur des données au format wide

La fonction ggpaired() du package ggpubr peut également être employée pour réaliser des boxplots appariés lorsque les données sont au format wide. Néanmoins, dans cette situation, à ma connaissance, seulement deux conditions peuvent être représentées.

library(tidyverse)

# création d'un dataset avec uniquement 2 temps d'observations (on retire le temps 6)
CW2 <- CW %>% 
  filter(Time != 6)

# passage en format wide avec la fonction pivot_wider()
CW2.wide <- pivot_wider(CW2,
                       id_cols= Chick,
                       names_from = Time,
                       values_from = weight)

head(CW2.wide)
# A tibble: 6 × 3
  Chick  `14`  `21`
  <ord> <dbl> <dbl>
1 1       125   205
2 2       138   215
3 3       138   202
4 4       108   157
5 5       164   223
6 6       148   157

# boxplot apparié
ggpaired(CW2.wide, 
         cond1 = "14", 
         cond2="21", 
         fill="condition", 
         palette="jco",
         line.color="grey50") 

L’argument fill="condition" permet d’appliquer une couleur par condition. Pour plus d’information sur les arguments de cette fonction, consultez l’aide.

Réaliser des boxplots appariés avec ggstatsplot

Pour cela, nous allons employer la fonction ggwithinstats(). Cette fonction emploi des données au format long, et à ma connaissance seules 2 conditions peuvent être représentées.

ici, j’aurai pu directement employer les données CW2 qui ne contiennent que 2 temps de mesure, mais j’ai préféré vous montrer comment réaliser un filtre à l’intéreur de la fonction.

library(ggstatsplot)
ggwithinstats(
  data = filter(CW,Time %in% c("14","21")),
  x = Time,
  y = weight,
  results.subtitle=FALSE,
) 
  • data = filter(CW, Time %in% c("14", "21")) : Cette ligne filtre le jeu de données CW pour inclure seulement les observations pour lesquelles la variable Time est égale à “14” ou “21”. Cela signifie que le graphique qui sera généré se basera uniquement sur les données filtrées.

  • x = Time : Spécifie que la variable Time sera utilisée sur l’axe des abscisses (axe X) du graphique.

  • y = weight : Indique que la variable weight (poids) sera utilisée sur l’axe des ordonnées (axe Y) du graphique.

  • results.subtitle=FALSE, : Indique que le sous-titre affichant les résultats des tests statistiques (comme la valeur p) ne devrait pas être affiché sur le graphique. Cela peut être utile pour des raisons esthétiques ou si l’on préfère interpréter les résultats séparément.

Si vous le souhaitez, vous pouvez associer un test statistique et afficher les résultats sur le boxplot apparié. Pour cela, vous devez utiliser les arguments suivants de la fonction ggwithinstats() :

  • type qui permet de spécifier le type de comparaison appariée. Vous avez le choix entre “parametric”, “nonparametric”, “robust” et “bayes

  • conf.level = 0.95 qui permet de spécifier le niveau de confiance de l’intervalle associé aux paramètres qui seront affichés.

  • results.subtitle = TRUE : ce qui permet d’afficher les résultats associés à la comparaisons (statistique du test, p-value etc…).
 
ggwithinstats(
  data = filter(CW, Time %in% c("14", "21")),
  x = Time,
  y = weight,
  type = "parametric",
  conf.level = 0.95,
  results.subtitle = TRUE,
) 

A propos de l'ordre des données

Pour la fonction ggwithinstats()

Dans la fonction ggwithinstats() de ggstatsplot,  il n’y a pas d’argument permettant d’indiquer comment relier les points entre eux. Malgré cela, l’ordre des données n’a pas d’influence. Cela m’a intrigué alors j’ai vérifié.

Dans le fichier CW2, les données sont ordonnées selon les numéros d’identification des animaux, avec alors une alternance des mesures à J14 puis à J21.

CW2
# A tibble: 32 × 4
   weight Time  Chick Diet 
    <dbl> <fct> <ord> <fct>
 1    125 14    1     1    
 2    205 21    1     1    
 3    138 14    2     1    
 4    215 21    2     1    
 5    138 14    3     1    
 6    202 21    3     1    
 7    108 14    4     1    
 8    157 21    4     1    
 9    164 14    5     1    
10    223 21    5     1    
# ℹ 22 more rows 

Voici le boxplot apparié obtenu :

ggwithinstats(
  data = CW2,
  x = Time,
  y = weight,
  results.subtitle=FALSE,
) 

J’ai alors créé un fichier CW3, en l’ordonnant en fonction du temps. Ainsi on retrouve dans la première partie toutes les données de J14, puis, dans la seconde partie, toutes celles de J21 :

CW3 <- CW2 %>% 
  arrange(Time)
CW3
# A tibble: 32 × 4
   weight Time  Chick Diet 
    <dbl> <fct> <ord> <fct>
 1    125 14    1     1    
 2    138 14    2     1    
 3    138 14    3     1    
 4    108 14    4     1    
 5    164 14    5     1    
 6    148 14    6     1    
 7    174 14    7     1    
 8     92 14    9     1    
 9     96 14    10    1    
10    177 14    11    1    
# ℹ 22 more rows 

Comme vous pouvez le voir, les boxplots appariés obtenus sont identiques.

ggwithinstats(
  data = CW3,
  x = Time,
  y = weight,
  results.subtitle=FALSE,
) 

Pour la fonction t.test()

J’ai alors voulu explorer si l’ordre des données avait un impact sur les résultats du test de student apparié, lorsque les données sont en format long. Il apparait que quel que soit l’ordre des données, les résultats du test appariés sont identiques, et identique à ceux obtenus avec le format wide. OUF !!

t.test(CW3$weight~CW3$Time, paired=TRUE)
    Paired t-test

data:  CW3$weight by CW3$Time
t = -5.8586, df = 15, p-value = 3.145e-05
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
 -68.78734 -32.08766
sample estimates:
mean difference 
       -50.4375 
t.test(CW2$weight~CW2$Time, paired=TRUE)


    Paired t-test

data:  CW2$weight by CW2$Time
t = -5.8586, df = 15, p-value = 3.145e-05
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
 -68.78734 -32.08766
sample estimates:
mean difference 
       -50.4375  
t.test(CW2.wide$'14' , CW2.wide$'21', paired=TRUE)



    Paired t-test

data:  CW2.wide$"14" and CW2.wide$"21"
t = -5.8586, df = 15, p-value = 3.145e-05
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
 -68.78734 -32.08766
sample estimates:
mean difference 
       -50.4375  

BONUS : Visualiser des données appariées avec le logiciel JASP

Si vous travaillez avec JASP, vous pouvez également obtenir un graphique permettant de visualiser vos données appariées. Pour cela, vos données doivent être au format wide.

Dans l’exemple ci dessous, j’ai importé sous JASP les données CW2.wide, puis j’ai choisi Test t (dans le menu du haut) puis Test t pour échantillons appariés et Graphique Rainclound

Je n’ai pas trouvé comment obtenir un graphqiue similaire sous Jamovi

Remarque JASP est un logiciel statistique open source, qui représente, à mon avis, une alternative intéressante à R, notamment pour celles et ceux qui réalisent des analyses statistiques de temps en temps. Le logiciel dispose d’une interface intuitive qui permet d’importer des données, de renommer des variables, de filtrer des lignes, etc…et de faire les principales analyses statistqiues Vous pouvez le télécharger ici : https://jasp-stats.org/download/

Conclusion

Les boxplots appariés sont très efficaces pour visualiser des données répétées sur les mêmes sujets ou entités à travers différents moments ou conditions. Ils offrent une vue claire des variations individuelles, des tendances centrales mais aussi de la dispersion au sein d’un ensemble de données.

Plusieurs approches sont disponibles sous R pour créer ces boxplots appariés, notamment avec les packages ggplot2ggpubr et ggstatsplot:

  • ggplot2 se distingue par sa flexibilité et sa capacité à superposer différentes couches de données, permettant une visualisation détaillée et personnalisée.
  • ggpubr simplifie le processus en fournissant une fonction dédiée aux boxplots appariés, et en permettant d’employer des données au format long ou wide
  • ggstatsplot, permet, quant à lui, d’intégrer des éléments statistiques aux boxplots appariés, mais en se limitant à deux conditions.

 

En outre, l’utilisation de JASP pour les analyses statistiques et la visualisation offre une alternative conviviale pour ceux qui préfèrent une interface graphique à la ligne de commande.

Chaque méthode a ses avantages et ses inconvénients, et le choix entre elles dépendra du nombre de conditions à comparer , du format de vos données et du niveau de détail souhaité dans vos visualisations.

Voici un récapitualif des packages utilisables en fonction du nombre de conditions à comparer, et du format des données.

 

 2 conditionsPlus de 2 conditions
Format Long

ggplot2

ggpubr

ggstats plot

ggplot2

ggpubr

Format Wideggpubrr 

 

N’oubliez pas que votre feedback est très important ! Si vous avez des questions, des suggestions, ou si vous souhaitez partager vos propres expériences et astuces sur l’utilisation des boxplots appariés, n’hésitez pas à laisser un commentaire ci-dessous. Vos contributions enrichiront la discussion et aideront le plus grand nombre !

5 Responses

  1. Bonjour Claire,

    Je vous remercie pour billet de blog, une fois encore très éclairant. J’aurais quelques questions.
    – jusqu’à quel effectif pensez-vous qu’il soit pertinent de faire ce type de graphique ?
    – quel méthode utiliser pour tester la normalité lorsque les données répétées sont appariées (3 mesures de pression artérielle avant/après une intervention)

    Merci beaucoup par avance

    1. Bonjour Jérôme,

      Tant que ça reste lisible, ça me semble pertinent de faire ce graphique.
      Pour ce qui concerne la normalité, j’ai l’habitude de faire un test de Shapiro-Wilks sur les résidus. Je réalise aussi une évaluation de la sphéricié (variance des différences entre chaque paires) par un test de Mauchly.
      J’espère que cela vous aide.
      Bonne continuation

Laisser un commentaire

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

Vous souhaitez vous former à R, ou aux statistiques ?

Retrouver le planning et les programmes de  mes formations ici  👇👇👇 

Vous avez besoin d'un assitance pour analyser vos données ?

Retrouver mes propositions de services ici  👇👇👇 

Vous souhaitez soutenir mon travail ?​

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

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.