如何调整facet_wrap面板内箱线图水平间距及单独偏移均值/中位数标签?
问题:ggplot2分面箱线图标签重叠与自定义位置调整
使用ggplot2绘制带facet_wrap的箱线图时,均值和中位数标签出现重叠,需完成以下调整:
- 针对4个场景(Female-No、Female-Yes、Male-No、Male-Yes)单独设置标签位置:
- Female-No:均值标签4.9上移至均值线上方并右移,中位数标签4.1右移
- Female-Yes:与Female-No设置相同
- Male-No:中位数标签8.7右移至箱线图旁的中位数线侧,均值标签8.4下移至均值线下方并右移
- Male-Yes:均值标签2.5下移至均值线下方并右移,中位数标签2.5水平右移
- 调整
facet_wrap单个面板内箱线图的水平间距,避免标签重叠
解决方案
核心思路
- 增大箱线图水平间距:修改
position_dodge的width参数拉开箱线图间距,同时缩小箱线图自身宽度,为标签预留空间。 - 自定义标签位置:在统计量数据框中添加水平/垂直偏移列,通过
case_when为每个场景单独设置偏移量,实现标签精准定位。
完整代码
library(tidyverse) datas <- structure(list(Pain = structure(c(2L, 1L, 1L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L), levels = c("No", "Yes"), class = "factor"), Sex = structure(c(1L, 1L, 2L, 1L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 1L), levels = c("Female", "Male" ), class = "factor"), `23` = c(3.7, 9.9, 18.1, 12.8, 2.3, 0, 8.7, 3.7, 1.5, 8.6, 5.7, 5.8, 2.1, 1.5, 6.1, 1.6, 0.6, 2.9, 9.1, 7.4, 0, 0, 9.5, 8.7, 4.6, 0, 0, 8.6, 1.6, 7.3, 6.3, 3, 7.8, 2.2, 0.7, 3.9, 2.8, 0.4, 6.2, 1.1, 2.3, 4.9, 4.5, 8, 8.6, 3.9, 1.5, 1.6, 0, 1.4, 3.2, 3.6, 3, 5.1, 4.1, 8.8, 9.6, 0.7)), row.names = c(NA, -58L), class = c("tbl_df", "tbl", "data.frame")) # 计算中位数并添加自定义位置偏移 medians <- datas %>% group_by(Pain, Sex) %>% summarise(m = round(median(`23`), 1), .groups = "drop") %>% mutate( x_offset = case_when( Sex == "Female" & Pain == "No" ~ 0.2, Sex == "Female" & Pain == "Yes" ~ 0.2, Sex == "Male" & Pain == "No" ~ 0.3, Sex == "Male" & Pain == "Yes" ~ 0.2, TRUE ~ 0 ), y_offset = 0 ) # 计算均值并添加自定义位置偏移 means <- datas %>% group_by(Pain, Sex) %>% summarise(m = round(mean(`23`), 1), .groups = "drop") %>% mutate( x_offset = case_when( Sex == "Female" & Pain == "No" ~ 0.2, Sex == "Female" & Pain == "Yes" ~ 0.2, Sex == "Male" & Pain == "No" ~ 0.3, Sex == "Male" & Pain == "Yes" ~ 0.2, TRUE ~ 0 ), y_offset = case_when( Sex == "Female" & Pain == "No" ~ 0.8, Sex == "Female" & Pain == "Yes" ~ 0.8, Sex == "Male" & Pain == "No" ~ -1.0, Sex == "Male" & Pain == "Yes" ~ -0.6, TRUE ~ 0 ) ) # 绘制调整后的箱线图 datas %>% select(Pain, Sex, `23`) %>% ggplot(aes(y = `23`, x = Sex, fill = Pain)) + geom_boxplot(fatten = 1, outlier.alpha = 0.50, position = position_dodge(width = 1.8), width = 0.7) + geom_text(data = medians, aes(x = as.numeric(Sex) + x_offset, y = m + y_offset, label = m), color = 'blue', size = 5) + geom_text(data = means, aes(x = as.numeric(Sex) + x_offset, y = m + y_offset, label = m), color = 'red', size = 5) + stat_boxplot(geom ='errorbar', position = position_dodge(width = 1.8)) + stat_summary(aes(ymax = ..y.., ymin = ..y.., color = 'Mean'), fun = mean, geom = 'errorbar', width = .75, linetype = 'solid', position = position_dodge(width = 1.8)) + stat_summary(aes(ymax = ..y.., ymin = ..y.., color = 'Median'), fun = median, geom = 'errorbar', width = .75, linetype = 'solid', position = position_dodge(width = 1.8)) + scale_colour_manual('Values', values = c(Median = 'blue', Mean = 'red')) + scale_fill_manual(values = c('lightblue', 'lightgreen')) + theme(legend.position = 'top', text = element_text(size = 12), strip.text = element_text(size = 11, face = "bold"), legend.text = element_text(size = 12), axis.text.x = element_text(size = 16, vjust = 1), panel.spacing.x = unit(2, "lines")) + facet_wrap(vars(Sex), scales = 'free')
代码说明
- 替换plyr为dplyr:避免包函数冲突,采用tidyverse风格的分组统计。
- 自定义偏移量:通过
x_offset和y_offset为每个场景的标签设置专属位置,解决重叠问题。 - 调整箱线图布局:增大
position_dodge(width)到1.8,缩小箱线图宽度到0.7,提升面板内空间利用率。 - 标签定位逻辑:将因子型x轴转换为数值后叠加偏移量,实现标签的精准移动。
内容的提问来源于stack exchange,提问作者Andrzej Andrzej
相关产品推荐
相关产品推荐

