如何用ggplot2绘制带双X轴(日历周+月份)的服务销量柱状图?
解决X轴同时标注日历周与月份的柱状图绘制问题
先贴出匹配场景的示例数据:
library(tidyverse) set.seed(123) sales_data <- tibble( week = 1:7, month = c("1月", "1月", "2月", "2月", "2月", "2月", "2月"), service_A = sample(50:100, 7), service_B = sample(40:90, 7), service_C = sample(60:110, 7) ) %>% pivot_longer(cols = starts_with("service"), names_to = "service", values_to = "sales")
你的问题原因
- annotate报错:annotate默认只支持添加单个图形元素,直接传入长度不匹配的x和label向量必然报错,它不支持批量循环添加标签,得换用geom_text或者批量生成annotate列表。
- facet_wrap重复标签:facet_wrap默认每个分面都会显示完整X轴刻度,要实现不重复得换用facet_grid并调整参数。
两种可行解决方案
方案一:在同一X轴上叠加月份标签
通过计算每个月份对应的周数中点,用geom_text在X轴下方标注月份,完美解决长度不匹配问题:
# 先计算每个月份的周数中点,用来放置标签 month_pos <- sales_data %>% group_by(month) %>% summarise(x = mean(week)) ggplot(sales_data, aes(x = week, y = sales, fill = service)) + geom_col(position = position_dodge(width = 0.8), width = 0.7) + # 添加月份标签,y=-5是标签垂直位置,可按需调整 geom_text(data = month_pos, aes(x = x, y = -5, label = month), size = 4, color = "black", inherit.aes = FALSE) + # 设置X轴刻度为日历周 scale_x_continuous(breaks = 1:7) + # 扩展Y轴范围,避免标签被截断 scale_y_continuous(expand = expansion(mult = c(0.05, 0.1))) + labs(x = "日历周", y = "销量", fill = "服务类型") + theme_minimal() + theme( axis.text.x = element_text(vjust = 1), plot.margin = margin(b = 20, unit = "pt") # 给底部标签留空间 )
方案二:用分面分组,避免X轴标签重复
如果想用分面区分月份,换用facet_grid而非facet_wrap,配合参数控制刻度显示:
ggplot(sales_data, aes(x = week, y = sales, fill = service)) + geom_col(position = position_dodge(width = 0.8), width = 0.7) + # 按月份分面,scales="free_x"让每个分面只显示对应周数,space匹配宽度 facet_grid(. ~ month, scales = "free_x", space = "free_x") + labs(x = "日历周", y = "销量", fill = "服务类型") + theme_minimal() + theme( strip.text.x = element_text(size = 10, face = "bold"), # 月份标签加粗 axis.text.x.bottom = element_text(vjust = 0.5), panel.spacing.x = unit(0.5, "cm") # 调整分面间距 )
方案选择
- 方案一适合需要紧凑布局,在同一轴上同时展示周和月份的场景;
- 方案二适合月份跨度大、周数多,需要明确分组区分的场景。
内容的提问来源于stack exchange,提问作者romina
相关产品推荐
相关产品推荐

