library(rstatix)
data("PlantGrowth")
PlantGrowth <- PlantGrowth %>%
reorder_levels(group, order = c("ctrl", "trt1", "trt2"))
levels(PlantGrowth$group)[1] "ctrl" "trt1" "trt2"
Trouvez quelles paires de groupes diffèrent après une ANOVA — pairwise_t_test() de rstatix et base R, avec l’ajustement des p-values qui contrôle les comparaisons multiples
Apprenez à réaliser des t-tests par paires entre les niveaux d’une variable de regroupement dans R — le flux de travail de comparaisons multiples après une ANOVA significative. Utilisez pairwise_t_test() de rstatix (indépendant et apparié, écart-type poolé ou non) et pairwise.t.test() de base R à ses côtés, choisissez un ajustement de la p-value (bonferroni, holm, BH), lisez les p-values ajustées pour voir quelles paires diffèrent, et affichez les crochets de signification sur une boîte à moustaches ggpubr.
12 juillet 2026
12 juillet 2026
pairwise_t_test() de rstatix pour un tableau tidy (une ligne par paire, avec la p-value ajustée) ou base R pairwise.t.test() pour la matrice de p-value classique. Les deux réalisent les mêmes t-tests.bonferroni (strict), holm (uniformément meilleur que Bonferroni) ou BH (contrôle le taux de fausses découvertes — moins conservateur quand vous avez beaucoup de paires).pool.sd = FALSE pour utiliser la variance propre à chaque paire (équivalent à des appels t.test() séparés) ; par défaut pool.sd = TRUE emprunte une variance commune à tous les groupes — plus puissant quand les groupes partagent réellement une variance. Ajoutez paired = TRUE pour des mesures répétées sur les mêmes sujets.add_xy_position() + stat_pvalue_manual() de ggpubr — les crochets de signification au-dessus d’une boîte à moustaches.Vous avez réalisé une ANOVA à un facteur, elle est ressortie significative — donc au moins une moyenne de groupe diffère. Mais le F-test omnibus ne vous dit jamais quelles paires diffèrent. Un t-test par paires (aussi appelé comparaison par paires ou comparaison multiple) répond exactement à cela : il réalise un t-test sur chaque paire de niveaux de groupe et rapporte une p-value par comparaison.
Le piège, c’est la multiplicité. Avec trois groupes vous réalisez trois tests ; avec cinq groupes, dix. Chaque test a sa propre chance de faux positif, donc tester de nombreuses paires au seuil brut de 0.05 trouve des « différences » qui ne sont que du bruit. La solution est d’ajuster les p-values pour le nombre de comparaisons — et choisir cet ajustement est la véritable compétence qu’enseigne cette leçon.
Cette leçon déroule tout le flux de travail de deux façons — le rstatix pairwise_t_test() moderne et adapté au pipe et le classique pairwise.t.test() de base R — pour des groupes indépendants (PlantGrowth intégré) et des mesures appariées (selfesteem de datarium), et montre comment placer les crochets de signification sur le graphique.
Ce que teste un t-test par paires. Pour chaque paire de groupes, Nulle : les deux moyennes de groupe sont égales ; Alternative : elles diffèrent. La p-value ajustée est celle que vous comparez à 0.05 — elle tient compte du nombre de paires testées pour que le taux d’erreur global (par famille) reste contrôlé.
Les deux comparent toutes les paires après une ANOVA, alors quand choisir l’un ou l’autre ? Une petite carte avant le flux de travail :
| Situation | Utiliser |
|---|---|
| Post-hoc standard toutes-paires après une ANOVA à un facteur classique (variances égales) | Tukey HSD (tukey_hsd()) |
| Vous voulez une correction spécifique — Holm, Benjamini-Hochberg (FDR), Bonferroni | t-test par paires (pairwise_t_test()) |
| Mesures appariées / répétées sur les mêmes sujets | t-test par paires avec paired = TRUE |
| Les groupes ont des variances inégales et vous voulez des variances par paire | t-test par paires avec pool.sd = FALSE (ou Games-Howell) |
Tukey est le choix tidy par défaut pour le cas équilibré à variances égales ; le t-test par paires est l’outil flexible quand vous devez contrôler la méthode d’ajustement, travailler avec des données appariées ou abandonner l’hypothèse de variance commune. Le reste de cette leçon porte sur le t-test par paires.
Nous utilisons les données intégrées PlantGrowth — le weight séché de plants cultivés sous une condition témoin et deux conditions de traitement (ctrl, trt1, trt2), 10 plants par groupe. Fixez explicitement l’ordre du facteur avec reorder_levels() pour que le témoin apparaisse en premier dans chaque tableau et graphique :
[1] "ctrl" "trt1" "trt2"
Mettez des chiffres derrière le graphique avec get_summary_stats() — les effectifs par groupe, les moyennes et les écarts-types que vous rapporterez avec le test :
# A tibble: 3 × 5
group variable n mean sd
<fct> <fct> <dbl> <dbl> <dbl>
1 ctrl weight 10 5.03 0.583
2 trt1 weight 10 4.66 0.794
3 trt2 weight 10 5.53 0.443
Le témoin est en moyenne autour de 5.03, trt1 se situe un peu en dessous (4.66) et trt2 est le plus élevé (5.53). L’écart qui paraît le plus grand est trt1 vs trt2 — le test par paires nous dira s’il survit à la correction pour comparaisons multiples.
Tracez toujours les groupes avant de les tester. Une boîte à moustaches de weight par group avec les points bruts et la moyenne de groupe montre les trois distributions côte à côte :

trt2 se situe nettement au-dessus de trt1, avec le témoin entre les deux — un motif qui mérite d’être confirmé paire par paire.
pairwise_t_test() prend les données et une formule outcome ~ group et renvoie un tibble tidy — une ligne par paire, avec les p-values brutes et ajustées. Passez p.adjust.method pour contrôler la correction (ici Bonferroni) :
# A tibble: 3 × 9
.y. group1 group2 n1 n2 p p.signif p.adj p.adj.signif
* <chr> <chr> <chr> <int> <int> <dbl> <chr> <dbl> <chr>
1 weight ctrl trt1 10 10 0.194 ns 0.583 ns
2 weight ctrl trt2 10 10 0.0877 ns 0.263 ns
3 weight trt1 trt2 10 10 0.00446 ** 0.0134 *
Lisez les colonnes : group1/group2 sont la paire, n1/n2 les tailles de groupe, p la p-value brute, p.adj la p-value ajustée par Bonferroni, et p.adj.signif son étoile. Comparez p.adj — pas p — à 0.05 : seul trt1 vs trt2 est significatif après ajustement (p.adj = 0.013), tandis qu’aucun traitement ne diffère significativement du témoin. Par défaut, pairwise_t_test() utilise un écart-type poolé (pool.sd = TRUE), en empruntant une estimation de variance commune à tous les groupes.
pairwise.t.test() réalise les mêmes comparaisons et affiche la matrice triangulaire classique de p-values ajustées — une cellule par paire :
Pairwise comparisons using t tests with pooled SD
data: PlantGrowth$weight and PlantGrowth$group
ctrl trt1
trt1 0.583 -
trt2 0.263 0.013
P value adjustment method: bonferroni
Même conclusion, mêmes chiffres : la seule cellule sous 0.05 est trt2 vs trt1 (p ≈ 0.013). base R utilise un SD poolé par défaut lui aussi, si bien que les deux moteurs concordent exactement. Le tibble rstatix est plus facile à passer dans un graphique ; la matrice de base est l’affichage compact que vous reconnaîtrez des manuels.
L’argument pool.sd décide d’où chaque t-test tire sa variance :
pool.sd = TRUE (par défaut) estime un écart-type commun à partir de tous les groupes et l’utilise dans chaque test par paires. C’est plus puissant quand les groupes partagent réellement une variance (l’hypothèse d’égalité des variances de l’ANOVA), car les données de chaque groupe aident à estimer le bruit.pool.sd = FALSE donne à chaque paire sa propre variance — identique au fait de réaliser un t.test() de Welch/Student séparé sur chaque paire. Préférez-le quand les variances de groupe diffèrent, pour qu’un groupe bruité ne fausse pas les tests des autres.Désactivez-le et les p-values changent, car chaque comparaison n’utilise plus que la dispersion de ses deux groupes :
# A tibble: 3 × 10
.y. group1 group2 n1 n2 statistic df p p.adj p.adj.signif
* <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl> <chr>
1 weight ctrl trt1 10 10 1.19 16.5 0.250 0.751 ns
2 weight ctrl trt2 10 10 -2.13 16.8 0.0479 0.144 ns
3 weight trt1 trt2 10 10 -3.01 14.1 0.00930 0.0279 *
Avec des variances par paire, trt1 vs trt2 reste la seule paire significative (p.adj = 0.028), mais les p-values ajustées se déplacent — le non poolé est le choix par défaut le plus sûr quand vous n’êtes pas certain que les variances sont égales. Vérifiez l’hypothèse d’égalité des variances avec le test de Levene avant de vous fier à la version poolée.
La correction est tout l’intérêt d’une comparaison par paires. p.adjust.method accepte toutes les méthodes que p.adjust() de R prend en charge ; trois couvrent presque tous les cas :
"bonferroni" — multiplie chaque p-value par le nombre de comparaisons. Le plus strict ; contrôle le taux d’erreur par famille mais perd de la puissance à mesure que le nombre de paires augmente."holm" — un raffinement descendant de Bonferroni qui est uniformément plus puissant et contrôle le même taux d’erreur par famille, si bien qu’il y a rarement une raison de préférer le simple Bonferroni (Holm, 1979)."BH" (Benjamini-Hochberg) — contrôle le taux de fausses découvertes (la proportion attendue de faux positifs parmi les résultats significatifs) plutôt que l’erreur par famille. Bien moins conservateur quand vous testez de nombreuses paires, au prix d’autoriser une fraction contrôlée de faux positifs (Benjamini & Hochberg, 1995).Comparez les trois sur les t-tests poolés — mêmes p-values brutes, corrections différentes :
library(rstatix)
data("PlantGrowth")
PlantGrowth <- PlantGrowth %>%
reorder_levels(group, order = c("ctrl", "trt1", "trt2"))
# One row per pair per method, keeping just the adjusted p-value
methods <- c("bonferroni", "holm", "BH")
res <- lapply(methods, function(m) {
out <- PlantGrowth %>% pairwise_t_test(weight ~ group, p.adjust.method = m)
data.frame(method = m, pair = paste(out$group1, "vs", out$group2),
p.adj = round(out$p.adj, 3))
})
do.call(rbind, res) method pair p.adj
1 bonferroni ctrl vs trt1 0.583
2 bonferroni ctrl vs trt2 0.263
3 bonferroni trt1 vs trt2 0.013
4 holm ctrl vs trt1 0.194
5 holm ctrl vs trt2 0.175
6 holm trt1 vs trt2 0.013
7 BH ctrl vs trt1 0.194
8 BH ctrl vs trt2 0.132
9 BH trt1 vs trt2 0.013
Lisez-le : Bonferroni est le plus sévère (ctrl vs trt2 = 0.263), Holm et BH tirent vers le bas les paires limites, et les trois s’accordent sur le seul vrai signal — trt1 vs trt2 à 0.013. Quand plusieurs paires se situent près du seuil, Holm ou BH récupèrent la puissance que Bonferroni gaspille.
Quelle correction ? Utilisez Holm comme choix par défaut sûr pour une poignée de paires (il domine Bonferroni). Utilisez BH quand vous avez de nombreuses comparaisons et pouvez tolérer un taux de fausses découvertes contrôlé (fréquent en criblage et en génomique). Réservez le simple Bonferroni au cas où un relecteur le demande explicitement.
Rassemblez en une seule phrase les comparaisons par paires significatives, leurs p-values ajustées et la méthode de correction — par exemple :
Des t-tests par paires avec une variance commune et une correction de Bonferroni ont comparé le poids des plants entre les trois groupes de traitement (n = 10 chacun). La seule différence significative se situait entre trt1 (4.66 ± 0.79) et trt2 (5.53 ± 0.44), p.adj = 0.013 ; aucun traitement ne différait significativement du témoin.
Rapportez quelles paires diffèrent, la p-value ajustée et la méthode d’ajustement que vous avez utilisée — pas seulement « certains groupes diffèrent ». Nommer la correction est essentiel : les mêmes données donnent des conclusions différentes selon les méthodes.
La figure prête à publier place les crochets par paires significatifs sur les boîtes. add_xy_position() calcule où se situe chaque crochet (ses xmin/xmax/y.position), puis stat_pvalue_manual() les trace ; hide.ns = TRUE masque les paires non significatives pour que seule la vraie différence soit annotée :
library(rstatix)
library(ggpubr)
data("PlantGrowth")
PlantGrowth <- PlantGrowth %>%
reorder_levels(group, order = c("ctrl", "trt1", "trt2"))
pwc <- PlantGrowth %>%
pairwise_t_test(weight ~ group, p.adjust.method = "bonferroni") %>%
add_xy_position(x = "group")
ggboxplot(PlantGrowth, x = "group", y = "weight", fill = "group", palette = "jco",
add = c("mean", "jitter"), xlab = "Treatment", ylab = "Plant weight") +
stat_pvalue_manual(pwc, hide.ns = TRUE) +
labs(caption = get_pwc_label(pwc))
Voici la figure à rapporter : les boîtes avec la moyenne et les points bruts, et la seule paire significative (trt1 vs trt2) signalée par une étoile de signification (sa p-value ajustée figure dans le tableau ci-dessus) ; la légende de figure indique le test et la correction.
Quand les mêmes sujets sont mesurés dans chaque condition — un plan intra-sujets — les groupes sont appariés, et le test par paires doit en tenir compte avec paired = TRUE. Nous utilisons les données selfesteem de datarium : un score d’estime de soi pour 10 personnes à trois instants de mesure (t1, t2, t3).
Les données arrivent au format large (une ligne par sujet, une colonne par instant), mais rstatix a besoin du format long (une ligne par mesure). Remodelez-les avec base R — en empilant les trois colonnes d’instants dans une seule colonne score :
# A tibble: 3 × 4
id t1 t2 t3
<int> <dbl> <dbl> <dbl>
1 1 4.01 5.18 7.11
2 2 2.56 6.91 6.31
3 3 3.24 4.44 9.78
id time score
1 1 t1 4.005027
2 2 t1 2.558124
3 3 t1 3.244241
Réalisez maintenant la comparaison par paires avec paired = TRUE — elle apparie les mesures de chaque sujet, si bien que la variation inter-sujets disparaît et que le test gagne en puissance :
library(rstatix)
data("selfesteem", package = "datarium")
selfesteem <- data.frame(
id = factor(rep(selfesteem$id, times = 3)),
time = factor(rep(c("t1", "t2", "t3"), each = nrow(selfesteem))),
score = c(selfesteem$t1, selfesteem$t2, selfesteem$t3)
)
selfesteem %>%
pairwise_t_test(score ~ time, paired = TRUE, p.adjust.method = "bonferroni")# A tibble: 3 × 10
.y. group1 group2 n1 n2 statistic df p p.adj p.adj.signif
* <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl> <chr>
1 score t1 t2 10 10 -4.97 9 7.72e-4 2.32e-3 **
2 score t1 t3 10 10 -13.2 9 3.34e-7 1.00e-6 ****
3 score t2 t3 10 10 -4.87 9 8.86e-4 2.66e-3 **
Chaque paire diffère significativement ici : l’estime de soi augmente sur les trois instants de mesure (tous les p.adj ≤ 0.003). Notez le df = 9 — le test apparié a n − 1 = 9 degrés de liberté (un par paire de sujets), et non les df plus élevés d’un test indépendant. Annotez la comparaison appariée de la même façon :
library(rstatix)
library(ggpubr)
data("selfesteem", package = "datarium")
selfesteem <- data.frame(
id = factor(rep(selfesteem$id, times = 3)),
time = factor(rep(c("t1", "t2", "t3"), each = nrow(selfesteem))),
score = c(selfesteem$t1, selfesteem$t2, selfesteem$t3)
)
pwc <- selfesteem %>%
pairwise_t_test(score ~ time, paired = TRUE, p.adjust.method = "bonferroni") %>%
add_xy_position(x = "time")
ggboxplot(selfesteem, x = "time", y = "score", fill = "time", palette = "jco",
add = c("mean", "jitter"), xlab = "Time point", ylab = "Self-esteem score") +
stat_pvalue_manual(pwc) +
labs(caption = get_pwc_label(pwc))
Les t-tests par paires appariés sont le post-hoc standard après une ANOVA à mesures répétées significative — l’analogue intra-sujets du fait de faire suivre une ANOVA à un facteur de t-tests par paires indépendants.
Réalisez le flux de travail complet sur un autre jeu de données — ToothGrowth (len de la dent par dose, trois niveaux 0.5/1/2). Changez la méthode d’ajustement, basculez pool.sd, ou modifiez les colonnes et relancez ; le bac à sable démarre au premier Run.
Demandez à Prova « J’ai réalisé une ANOVA et elle est significative — lance les bons t-tests par paires sur mes données, choisis un ajustement de la p-value, et dis-moi quels groupes diffèrent » — elle répond avec du code rstatix que vous pouvez exécuter sur vos propres données, puis vous aide à lire les p-values ajustées et à placer les crochets sur le graphique. The runtime is the judge. Demander à Prova →
Chaque p brut est significatif mais pas la colonne p.adj (ou l’inverse). Vous comparez la mauvaise colonne. Jugez toujours la significativité sur p.adj, pas le p brut — la p-value brute ignore les comparaisons multiples et surdéclare les différences. En cas de désaccord, fiez-vous à p.adj.
paired = TRUE donne une erreur ou un résultat absurde. Les tests appariés ont besoin des données au format long avec un identifiant de sujet, et chaque sujet doit apparaître dans chaque groupe dans le même ordre. Remodelez de large→long comme montré ci-dessus (id, le facteur, le score) et faites de id un facteur ; un sujet manquant ou mal aligné casse l’appariement.
Les crochets ne s’alignent pas avec les boîtes. stat_pvalue_manual() a besoin des positions issues de add_xy_position() avec la même variable x que le graphique. Exécutez add_xy_position(x = "group") sur le résultat avant de tracer, et utilisez step.increase = pour écarter les crochets empilés s’ils se chevauchent.
Un t-test par paires compare chaque paire de niveaux d’une variable de regroupement avec un t-test à deux échantillons, puis ajuste les p-values pour le nombre de comparaisons. C’est l’étape post-hoc qui identifie quels groupes diffèrent après qu’une ANOVA à un facteur significative vous indique qu’au moins un diffère.
Deux façons. rstatix : mydata %>% pairwise_t_test(outcome ~ group, p.adjust.method = "bonferroni") renvoie un tableau tidy avec une ligne par paire et la p-value ajustée. base R : pairwise.t.test(mydata$outcome, mydata$group, p.adjust.method = "bonferroni") affiche la matrice de p-value classique. Les deux réalisent les mêmes tests ; comparez la p-value ajustée à 0.05.
Les deux comparent toutes les paires après une ANOVA. Tukey HSD est le post-hoc standard toutes-paires pour une ANOVA à un facteur équilibrée à variances égales et utilise sa propre correction de l’étendue studentisée. Le t-test par paires est plus flexible : vous choisissez l’ajustement (Holm, Benjamini-Hochberg, Bonferroni), vous pouvez utiliser paired = TRUE pour des mesures répétées, et vous pouvez abandonner l’hypothèse de variance commune avec pool.sd = FALSE.
Utilisez Holm comme choix par défaut sûr — il contrôle le taux d’erreur par famille et est uniformément plus puissant que Bonferroni. Utilisez Benjamini-Hochberg (BH) quand vous avez de nombreuses comparaisons et pouvez accepter un taux de fausses découvertes contrôlé (criblage, génomique). Réservez le simple Bonferroni aux cas où il est explicitement exigé — c’est le plus strict et il perd de la puissance à mesure que le nombre de paires augmente.
pool.sd = TRUE (par défaut) estime un écart-type commun à partir de tous les groupes et l’utilise dans chaque test par paires — plus puissant quand les groupes partagent réellement une variance. pool.sd = FALSE donne à chaque paire sa propre variance, ce qui est identique au fait de réaliser des appels t.test() séparés et est plus sûr quand les variances de groupe diffèrent.
len de la dent par dose dans ToothGrowth avec une correction Holm. Quelles paires de doses diffèrent ?p brute, et qu’est-ce qui change quand vous passez de Bonferroni à Benjamini-Hochberg ?Remplissez les blancs avec le résultat len, le facteur dose, et la méthode "holm". Regardez la colonne p.adj (pas p) pour la significativité. Pour la question 2, rappelez-vous que chacun des six tests porte son propre risque de faux positif, et que Bonferroni contrôle l’erreur par famille tandis que BH contrôle le taux de fausses découvertes.
Les trois paires diffèrent : des doses plus élevées donnent des dents plus longues à chaque étape.
Pour la question 2 : la colonne p brute ignore que vous avez réalisé six tests, donc au seuil de 0.05 vous vous attendriez à environ un faux positif par le seul hasard — vous devez lire le p.adj corrigé pour la multiplicité. Passer de Bonferroni (qui contrôle l’erreur par famille — le risque de tout faux positif) à Benjamini-Hochberg (qui contrôle le taux de fausses découvertes — la proportion attendue de faux positifs parmi les résultats positifs) est moins conservateur : BH signalera généralement davantage de paires comme significatives, approprié quand quelques fausses découvertes contrôlées sont acceptables.
Vous savez maintenant réaliser et interpréter un t-test par paires en R de deux façons : le pairwise_t_test() tidy de rstatix et le classique pairwise.t.test() de base R. Choisissez délibérément l’ajustement de la p-value (Holm ou BH plutôt que le simple Bonferroni dans la plupart des cas), décidez de la variance poolée ou non poolée d’après l’hypothèse d’égalité des variances, ajoutez paired = TRUE pour des mesures répétées, et jugez toujours la significativité sur la p-value ajustée. Placez les crochets significatifs sur une boîte à moustaches ggpubr avec add_xy_position() + stat_pvalue_manual() pour rapporter le résultat en un coup d’œil.
Prouvez que vous savez le faire. Maîtrisez toute la série ANOVA en R — suivez votre parcours, construisez des projets et obtenez un certificat.
Passez à Pro — Prova illimité sur vos propres données et un certificat vérifiable qui atteste la compétence.
dès 15 $/mois facturé annuellement
✓ Vous êtes Pro — continuez. The runtime is the judge.
Prêt à passer à la vitesse supérieure ?
Recevez les nouvelles leçons R & Python par e-mail
Pratique, reproductible, sans spam. Désinscription à tout moment.
Double opt-in. Nous ne partageons jamais votre e-mail.
Chaque résultat de cette page a été produit par le code montré, exécuté au moment du build contre un environnement R figé — modifiez n’importe quel bloc et faites Run pour le reproduire vous-même.
@online{2026,
author = {},
title = {Test t par paires en R : comparer les niveaux de groupe},
date = {2026-07-12},
url = {https://www.datanovia.com/learn/biostatistics/anova/pairwise-t-test},
langid = {fr}
}