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

如何在R的ggplot2中为柱状图添加多层下方X轴

在ggplot2中为柱状图添加多层X轴的解决方案

我在RStudio中使用ggplot2绘制柱状图时,无法在图表下方添加多层X轴,期望实现多层分类的X轴效果。

当前代码

ggplot(data=relative_trimming_subset, aes(x=Relative_trimming, y=Stage1_time, fill=Trimming)) +
    geom_bar(stat="identity", position=position_dodge(), show.legend = FALSE) + 
    scale_fill_manual(values = c("#cccccc", "#666666", "#cccccc", "#666666")) +
    scale_x_discrete(limits = level_order) +
    theme_bw() +
    labs(x = "Trimming") +                             
    labs(y = "Stage one time (seconds)") +  
    theme(axis.title.x=element_blank(),
          axis.text.x=element_blank(),
          axis.ticks.x=element_blank(),
          panel.grid.major = element_blank(), 
          panel.grid.minor = element_blank()) +
    theme(panel.border = element_rect(linewidth = 1)) +
    theme(text = element_text(size = 12),
          axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0)),
          axis.line = element_line(colour = 'black'),
          axis.title = element_text(face="bold"),
          axis.text.y = element_text(size = 10),
          axis.text = element_text(colour = 'black'))

尝试过的方法

  • 使用annotate函数添加文本,未成功实现多层X轴
  • 尝试geom_text,同样无法达到预期效果

可复现数据子集

structure(list(ID = c("TF1-05MB", "TF1-05MB", "TF1-05MB", "TF1-05MB", 
"TF1-05MB", "TF1-05MB", "TF1-05MB", "TF1-05MB", "TF1-05MB", "TF1-05MB", 
"TF1-05MB", "TF1-05MB", "TF1-05MB", "TF1-05MB", "TF1-05MB", "TF1-07MB", 
"TF1-07MB", "TF1-07MB", "TF1-07MB", "TF1-07MB", "TF1-07MB", "TF1-07MB", 
"TF1-07MB", "TF1-07MB", "TF1-07MB", "TF1-07MB", "TF1-07MB", "TF1-07MB", 
"TF1-07MB", "TF1-07MB"), Sex = c("M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M"), Trimming = c("Untrimmed_full", 
"Untrimmed_full", "Untrimmed_full", "Untrimmed_full", "Untrimmed_full", 
"Untrimmed_full", "Untrimmed_full", "Untrimmed_full", "Trimmed_full", 
"Trimmed_full", "Trimmed_full", "Trimmed_full", "Trimmed_full", 
"Trimmed_full", "Trimmed_full", "Untrimmed_control", "Untrimmed_control", 
"Untrimmed_control", "Untrimmed_control", "Untrimmed_control", 
"Untrimmed_control", "Untrimmed_control", "Untrimmed_control", 
"Trimmed_control", "Trimmed_control", "Trimmed_control", "Trimmed_control", 
"Trimmed_control", "Trimmed_control", "Trimmed_control"), Relative_trimming = c("Pre", 
"Pre", "Pre", "Pre", "Pre", "Pre", "Pre", "Pre", "Post", "Post", 
"Post", "Post", "Post", "Post", "Post", "Pre", "Pre", "Pre", 
"Pre", "Pre", "Pre", "Pre", "Pre", "Post", "Post", "Post", "Post", 
"Post", "Post", "Post"), Day = c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 
2L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 
3L, 3L, 3L, 3L, 4L, 4L, 4L), Trial_number = c(1L, 2L, 3L, 4L, 
1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 5L, 
1L, 2L, 3L, 1L, 2L, 4L, 5L, 1L, 2L, 5L), Reaction_time = c(40L, 
3L, 37L, 5L, 5L, 5L, 5L, 16L, 8L, 13L, 12L, 7L, 5L, 6L, 6L, 3L, 
6L, 6L, 6L, 6L, 17L, 13L, 13L, 9L, 14L, 13L, 12L, 5L, 6L, 5L), 
    Stage1_time = c(5L, 5L, 4L, 8L, 5L, 7L, 5L, 8L, 6L, 5L, 7L, 
    5L, 5L, 8L, 5L, 7L, 7L, 7L, 6L, 8L, 6L, 7L, 7L, 4L, 5L, 5L, 
    16L, 6L, 8L, 6L), PC1_score = c(-0.080373226, 0.032477537, 
    -0.155069158, 0.1965224, -0.139819473, -0.013115389, -0.069847159, 
    0.045855081, -0.090945817, -0.118591067, 0.028014289, -0.075510197, 
    -0.02157794, 0.118944384, -0.065654084, 0.102907134, -0.04347041, 
    -0.062195986, 0.028970608, 0.045541099, 0.025417646, 0.074033332, 
    0.117344171, -0.083718399, -0.029333778, -0.104413751, -0.133641797, 
    -0.020064955, 0.081972399, -0.014783332), S_length = c(3.274, 
    3.274, 3.274, 3.274, 3.274, 3.274, 3.274, 3.274, 3.274, 3.274, 
    3.274, 3.274, 3.274, 3.274, 3.274, 3.254, 3.254, 3.254, 3.254, 
    3.254, 3.254, 3.254, 3.254, 3.254, 3.254, 3.254, 3.254, 3.254, 
    3.254, 3.254), D_length = c(2.028, 2.028, 2.028, 2.028, 2.028, 
    2.028, 2.028, 2.028, 2.028, 2.028, 2.028, 2.028, 2.028, 2.028, 
    2.028, 1.839, 1.839, 1.839, 1.839, 1.839, 1.839, 1.839, 1.839, 
    1.839, 1.839, 1.839, 1.839, 1.839, 1.839, 1.839), A_length = c(2.278, 
    2.278, 2.278, 2.278, 2.278, 2.278, 2.278, 2.278, 2.278, 2.278, 
    2.278, 2.278, 2.278, 2.278, 2.278, 2.077, 2.077, 2.077, 2.077, 
    2.077, 2.077, 2.077, 2.077, 2.077, 2.077, 2.077, 2.077, 2.077, 
    2.077, 2.077)), class = "data.frame", row.names = c(NA, -30L
))

可行解决方案

方法1:利用facet_wrap实现分组式多层X轴

这种方法适合按大类别分组的场景,通过facet将上层分类作为标签展示在X轴上方:

library(dplyr)
library(ggplot2)

# 提取数据中的大分组(从ID中分离)
relative_trimming_subset$Group <- gsub("-.*", "", relative_trimming_subset$ID)

# 计算每组的均值(柱状图建议使用均值而非原始重复数据)
plot_data <- relative_trimming_subset %>%
  group_by(Group, Relative_trimming, Trimming) %>%
  summarise(Stage1_time_mean = mean(Stage1_time), .groups = "drop")

# 定义X轴顺序
level_order <- c("Pre", "Post")

# 绘图
ggplot(plot_data, aes(x=Relative_trimming, y=Stage1_time_mean, fill=Trimming)) +
  geom_bar(stat="identity", position=position_dodge(), show.legend = FALSE) + 
  scale_fill_manual(values = c("#cccccc", "#666666", "#cccccc", "#666666")) +
  scale_x_discrete(limits = level_order) +
  # 用facet实现上层X轴
  facet_wrap(~Group, scales = "free_x", strip.position = "bottom") +
  theme_bw() +
  labs(y = "Stage one time (seconds)") +  
  theme(
    strip.background = element_rect(fill = "white", color = "black"),
    strip.text = element_text(face = "bold"),
    axis.title.x=element_blank(),
    panel.grid.major = element_blank(), 
    panel.grid.minor = element_blank(),
    panel.border = element_rect(linewidth = 1),
    text = element_text(size = 12),
    axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0), face = "bold"),
    axis.line = element_line(colour = 'black'),
    axis.text.y = element_text(size = 10, color = "black"),
    axis.text.x = element_text(color = "black"),
    # 消除分组间的间距,让布局更紧凑
    panel.spacing.x = unit(0, "mm")
  )

方法2:手动添加多层轴标签(自定义坐标)

如果不想使用facet,可以手动计算坐标添加文本和分割线,实现自由布局的多层X轴:

library(dplyr)
library(ggplot2)

# 计算均值数据
plot_data <- relative_trimming_subset %>%
  group_by(Relative_trimming, Trimming, ID) %>%
  summarise(Stage1_time_mean = mean(Stage1_time), .groups = "drop")

# 定义各层标签的坐标
x_group_pos <- c(1.25, 3.25) # 上层分组标签的中心位置
group_labels <- unique(plot_data$ID)
subgroup_labels <- rep(c("Pre", "Post"), 2)

# 绘图
ggplot(plot_data, aes(x=interaction(ID, Relative_trimming), y=Stage1_time_mean, fill=Trimming)) +
  geom_bar(stat="identity", position=position_dodge(), show.legend = FALSE) + 
  scale_fill_manual(values = c("#cccccc", "#666666", "#cccccc", "#666666")) +
  scale_x_discrete(labels = subgroup_labels) +
  theme_bw() +
  labs(y = "Stage one time (seconds)") +  
  theme(
    axis.title.x=element_blank(),
    panel.grid.major = element_blank(), 
    panel.grid.minor = element_blank(),
    panel.border = element_rect(linewidth = 1),
    text = element_text(size = 12),
    axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0), face = "bold"),
    axis.line = element_line(colour = 'black'),
    axis.text.y = element_text(size = 10, color = "black"),
    axis.text.x = element_text(color = "black")
  ) +
  # 添加上层分组标签
  annotate("text", x = x_group_pos, y = -0.5, label = group_labels, fontface = "bold") +
  # 添加分组分割线
  annotate("segment", x = c(0.5, 2.5), xend = c(2.5, 4.5), y = -0.3, yend = -0.3, color = "black")

内容的提问来源于stack exchange,提问作者cookies5098

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 12:02:06