Adding Statistical Test Results to Plots

A publication figure should carry its own statistics. ggpubr gives you three complementary ways to put a test result on a plot, and the right choice depends on how many groups you have and what you want the reader to see:

  • Pairwise brackets – one bracket per comparison, with a p-value or significance stars (geom_pwc(), stat_compare_means()). Best for a few groups where every pair matters.
  • Compact letter display – one letter per group; groups that share a letter are not significantly different (stat_cld()). Best for many groups, where a full set of brackets becomes unreadable.
  • An omnibus label – the single overall test (ANOVA / Kruskal-Wallis) as a subtitle (add_test_label()). Answers “is there any difference?” before the pairwise detail.

This vignette shows each, and when to reach for which. All the examples use rstatix under the hood (already a ggpubr dependency).

library(ggpubr)

data("ToothGrowth")
tg <- ToothGrowth
tg$dose <- factor(tg$dose)

Pairwise brackets

geom_pwc() (“pairwise comparisons”) adds a bracket for every pair of groups, with the adjusted p-value or significance stars. Add it to any ggpubr plot.

ggboxplot(tg, x = "dose", y = "len", color = "dose", palette = "jco") +
  geom_pwc(method = "t_test", label = "p.adj.format")

Automatic bracket packing

With several groups the brackets stack into a tall tower. pack = "auto" packs non-overlapping comparisons onto the same level, so the figure stays compact – compare the two panels below (same comparisons, packed on the right):

p <- ggboxplot(tg, x = "dose", y = "len", color = "dose", palette = "jco")

ggarrange(
  p + geom_pwc(method = "t_test", label = "p.adj.signif") +
    labs(title = "pack = \"none\" (default)"),
  p + geom_pwc(method = "t_test", label = "p.adj.signif", pack = "auto") +
    labs(title = "pack = \"auto\""),
  ncol = 2, common.legend = TRUE, legend = "bottom"
)

Hide the non-significant brackets with hide.ns = TRUE, and append an effect size with the {effsize} token:

ggboxplot(tg, x = "dose", y = "len", color = "dose", palette = "jco") +
  geom_pwc(
    method = "t_test", label = "{p.adj.signif} (d = {effsize})",
    hide.ns = TRUE
  )

Compact letter display

When you have many groups, a full set of pairwise brackets is unreadable. A compact letter display (CLD) is the standard alternative: each group gets one or more letters, and groups that share a letter are not significantly different. stat_cld() computes the letters from all-pairwise comparisons and places one label above each group.

InsectSprays has six groups – far too many for brackets, ideal for letters:

ggboxplot(InsectSprays, x = "spray", y = "count",
  color = "spray", palette = "jco") +
  stat_cld() +
  scale_y_continuous(expand = expansion(mult = c(0.05, 0.1))) +
  labs(x = "Spray", y = "Insect count")

Read it as: sprays sharing a letter (e.g. the group labelled a) are statistically indistinguishable, while sprays with no letter in common differ. Switch the underlying test with method = (e.g. "dunn_test" for a non-parametric analysis), and set a common label height with label.y.

Brackets or letters?

Situation Use
2-4 groups, every pair of interest pairwise brackets (geom_pwc())
5+ groups, or a crowded bracket tower compact letters (stat_cld())
You only need “is there any difference?” omnibus label (below)

The omnibus label

Before the pairwise detail, readers often want the single overall test. add_test_label() adds it as a subtitle – pass the plot in, get the plot back:

p <- ggboxplot(tg, x = "dose", y = "len", color = "dose", palette = "jco") +
  geom_pwc(method = "t_test", label = "p.adj.signif", hide.ns = TRUE)

add_test_label(p, method = "anova")

Use method = "kruskal" for the non-parametric Kruskal-Wallis test. When a second factor is mapped, group.by = computes a two-way ANOVA and names the effect – by default the interaction:

p2 <- ggboxplot(tg, x = "supp", y = "len", color = "dose", palette = "jco")

add_test_label(p2, group.by = "dose", type = "text")

All three in one call

ggcompare() assembles the whole figure – base plot, jittered points, group means, packed adjusted-p brackets, and the omnibus subtitle – in a single call:

ggcompare(tg, x = "dose", y = "len")

See the “Two-Way Comparison Figures with ggcompare()” vignette for the grouped (two-way) design, and ?geom_pwc, ?stat_cld, ?add_test_label for the full argument lists.