You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ggplot2多分组多分面柱状图中添加显著性标记

解决多分面柱状图内添加组间显著性标记的问题

你的核心问题是直接用Type1/Type2作为geom_bracket的xmin/xmax是错误的——因为你的x轴是Age因子,每个Type是在同一个Age分组内的 dodge 柱子,它们的实际x坐标是基于Age的数值位置加上偏移量的,不是直接用Type的名称。下面是完整的解决方案:


完整代码实现

首先加载所需工具包,然后处理坐标和显著性标记,最后绘制图形:

library(ggplot2)
library(ggpubr)
library(dplyr)

# 原始数据
df <- data.frame(
  factor(rep(c("A1", "A2"), each = 12), levels = c("A1", "A2")),
  factor(rep(c("G", "M", "B"), each = 4), levels = c("G", "M", "B")),
  factor(rep(c("0-2", "3-5", "6-12", "13-17"), 6), levels = c("0-2", "3-5", "6-12", "13-17")),
  c(160, 162, 169, 108, 110, 111, 76, 73, 76, 45, 41, 38, 175, 177, 173, 174, 167, 172, 176, 162, 166, 143, 130, 143))
colnames(df) <- c("Class", "Type", "Age", "Coefficient")

p.val.df <- data.frame(
  factor(rep(c("A1", "A2"), each = 12), levels = c("A1", "A2")),
  factor(rep(c("G", "M", "G"), each = 4), levels = c("G", "M")),
  factor(rep(c("B", "B", "M"), each = 4), levels = c("B", "M")),
  factor(rep(c("0-2", "3-5", "6-12", "13-17"), 6), levels = c("0-2", "3-5", "6-12", "13-17")),
  c(0.635, 0.584, 0.268, 0.051, 0.163, 0.779, 0.302, 0.361, 0.055, 0.425, 0.998, 0.055, 0.707, 0.230, 0.000, 0.002, 0.418, 0.313, 0.211, 0.037, 0.675, 0.764, 0.011, 0.881))
colnames(p.val.df) <- c("Class", "Type1", "Type2", "Age", "p.value")

# 1. 计算每个Type在对应Age下的实际x坐标
pos_map <- df %>%
  distinct(Age, Type) %>%
  mutate(
    age_num = as.numeric(Age),  # 将Age因子转为ggplot内部使用的数值位置
    # 3个Type平分默认的dodge宽度(0.9),每个的偏移量为-0.3/0/+0.3
    type_offset = case_when(
      Type == "G" ~ -0.3,
      Type == "M" ~ 0,
      Type == "B" ~ 0.3
    ),
    x_pos = age_num + type_offset
  )

# 2. 给p值数据框添加xmin/xmax坐标
p.val.df <- p.val.df %>%
  left_join(pos_map %>% select(Age, Type, x_pos), by = c("Age", "Type1" = "Type")) %>%
  rename(xmin = x_pos) %>%
  left_join(pos_map %>% select(Age, Type, x_pos), by = c("Age", "Type2" = "Type")) %>%
  rename(xmax = x_pos)

# 3. 计算每个分组的动态y位置(避免固定值导致的遮挡/位置不当)
y_pos_df <- df %>%
  group_by(Class, Age) %>%
  summarise(y_max = max(Coefficient) + 20, .groups = "drop")

p.val.df <- p.val.df %>%
  left_join(y_pos_df, by = c("Class", "Age"))

# 4. 格式化p值标签(遵循学术惯例)
p.val.df <- p.val.df %>%
  mutate(
    p_label = case_when(
      p.value < 0.001 ~ "***",
      p.value < 0.01 ~ "**",
      p.value < 0.05 ~ "*",
      TRUE ~ signif(p.value, 2) %>% as.character()
    )
  )

# 5. 绘制图形
ggplot(df, aes(x = Age, y = Coefficient, fill = Type)) +
  geom_bar(position = position_dodge(width = 0.9), stat = "identity") +
  labs(y = "Coefficient", x = "Age", fill = "Type") +
  facet_wrap( ~ Class, scales = "free") +
  theme_classic() +
  # 添加显著性括号
  geom_bracket(
    data = p.val.df,
    aes(xmin = xmin, xmax = xmax, label = p_label),
    y.position = p.val.df$y_max,
    step.increase = 0.1,
    inherit.aes = FALSE,  # 禁用继承主图的x映射,避免冲突
    bracket.size = 0.5,
    label.size = 3
  )

关键步骤解释

  1. x坐标计算:ggplot会把Age因子的每个水平映射为1、2、3、4这样的数值,我们根据position_dodge的默认宽度(0.9),给3个Type分配对应的偏移量,得到每个柱子的实际中心坐标。
  2. 动态y位置:不再使用固定的250,而是根据每个Class+Age组的最大柱子高度加20,让括号位置更贴合数据,避免遮挡柱子或位置过高。
  3. p值格式化:按照学术常用规则用星号标记显著性,非显著的p值保留两位有效数字,比直接显示原始数值更直观。
  4. inherit.aes=FALSE:必须设置这个参数,因为主图的x映射是Age,而geom_bracket使用的是我们计算的xmin/xmax,避免两者冲突导致的错误。

内容的提问来源于stack exchange,提问作者Denys D.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:17:57