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

如何用ggplot2合并带共同Y轴的两个箱线图并添加Tukey标记字母

问题需求

现有两个独立箱线图,分别展示直链淀粉、支链淀粉含量,二者共享图例与Y轴范围(0-100%干基)。此前用patchwork将其合并为并排子图,但希望将二者整合到同一幅图中(每个Location-Year分组下同时展示两种淀粉的箱线图),同时在图上方添加Tukey多重比较的显著性标记字母。

当前效果图:左右并排的两个子图,左侧为直链淀粉含量箱线图,右侧为支链淀粉含量箱线图,右侧子图已隐藏Y轴标签与刻度。

解决方案

核心思路是将宽格式数据转换为长格式,让ggplot可以通过分组变量区分两种淀粉类型,再结合position_dodge实现同一分组下的箱线图并排,最后分别添加两类淀粉的Tukey标记。

步骤1:数据格式转换

将原宽格式的汇总数据转换为长格式,提取淀粉类型、均值、标准差、最值等关键字段。

步骤2:封装Tukey标记计算函数

避免重复代码,封装函数批量计算两类淀粉的显著性字母及标记位置。

步骤3:绘制整合式箱线图

利用长格式数据,设置分组与位置偏移,添加双系列的Tukey标记,统一图例与Y轴。

完整代码

library(dplyr)
library(multcompView)
library(ggplot2)
library(tidyr)

# 加载原始数据并创建locyr列
totalam <- read.csv("barleyaap1.csv", header = TRUE, stringsAsFactors = TRUE)
totalam$locyr <- as.factor(paste(totalam$location, totalam$year, sep = " "))

# 加载汇总数据并转换为长格式
totalam2 <- read.csv("barleyaap2.csv", header = TRUE, stringsAsFactors = TRUE)
totalam_long <- totalam2 %>%
  pivot_longer(
    cols = -locyr,
    names_to = c(".value", "starch_type"),
    names_pattern = "(avg|sd|min|max)(am|ap)"
  ) %>%
  mutate(starch_type = recode(starch_type, am = "Amylose", ap = "Amylopectin"))

# 封装Tukey字母计算函数
get_tukey_letters <- function(response_var, data, group_var = "locyr") {
  aov_model <- aov(reformulate(group_var, response_var), data = data)
  tukey_res <- TukeyHSD(aov_model)[[group_var]]
  letters_df <- data.frame(Letter = multcompView::multcompLetters(tukey_res[,4])$Letters)
  letters_df[[group_var]] <- rownames(letters_df)
  
  # 计算字母标记位置(取对应组的上四分位数+偏移)
  placement <- data %>%
    group_by(!!sym(group_var)) %>%
    summarise(Placement.Value = quantile(!!sym(response_var), 0.75) + 5)
  
  left_join(letters_df, placement, by = group_var)
}

# 分别计算两类淀粉的Tukey字母
am_letters <- get_tukey_letters("adry", totalam) %>% mutate(starch_type = "Amylose")
ap_letters <- get_tukey_letters("apdry", totalam) %>% mutate(starch_type = "Amylopectin")
all_letters <- bind_rows(am_letters, ap_letters)

# 绘制整合后的箱线图
ggplot(totalam_long, aes(x = locyr, y = avg, fill = locyr)) +
  geom_boxplot(
    aes(lower = avg - sd, upper = avg + sd, middle = avg, ymin = min, ymax = max),
    stat = "identity",
    position = position_dodge(width = 0.8),
    width = 0.7
  ) +
  # 添加Tukey标记字母
  geom_text(
    data = all_letters,
    aes(x = locyr, y = Placement.Value, label = Letter),
    position = position_dodge(width = 0.8),
    size = 4, fontface = "bold", hjust = 0.5
  ) +
  scale_y_continuous("% (dry basis)", limits = c(0, 100), breaks = seq(0, 100, 5)) +
  scale_x_discrete("Location-Year") +
  guides(fill = guide_legend(title = "Location-Year")) +
  theme_classic() +
  theme(
    axis.title = element_text(size = 14, color = "black"),
    axis.text = element_text(size = 10, color = "black"),
    legend.title = element_text(size = 14),
    legend.text = element_text(size = 10),
    plot.title = element_text(size = 16, hjust = 0.5)
  ) +
  ggtitle("Starch Content by Location-Year")

代码说明

  1. 长格式转换:用pivot_longer将原数据中直链淀粉(am后缀)和支链淀粉(ap后缀)的列整合,生成starch_type分组变量,让ggplot能识别两类数据。
  2. Tukey函数封装:通过函数复用逻辑,分别计算两类淀粉的显著性字母,并将标记位置设置为组内上四分位数+固定偏移,确保字母显示在箱线图上方。
  3. 箱线图绘制:使用position_dodge实现同一locyr下两个箱线图的并排展示,同时将两类淀粉的Tukey字母对应偏移,保证位置匹配。
  4. 若仍希望保留类似原patchwork的分面布局,可添加facet_wrap(~starch_type, nrow = 1)实现左右分面且共享Y轴。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 13:10:24