使用geom_dotplot()实现点图分栏:单个x值点数超限时自动避让
实现ggplot2点图的分栏避让(单x值下限制每栏点数)
问题背景
现有如下R语言数据框:
DF <- structure(list(pub_year = c(2022, 2023, 2022, 2022, 2021, 2023, 2019, 2022, 2022, 2022, 2022, 2023, 2019, 2020, 2020, 2021, 2021, 2019, 2021, 2023), mega = c(1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1)), row.names = c(NA, -20L), class = c("tbl_df", "tbl", "data.frame"))
使用给定的ggplot2代码绘制点图时,部分x值(如2022)对应点数较多(当前为7个),希望设置单个x值下最多显示5个点为一栏,多余的点分栏避让(如分为5个点的栏和2个点的栏),达到目标图的效果。
当前绘图代码如下:
library(tidyverse) library(ggExtra) yHeight <- 10 binwidth <- 0.5 dotsize <- 0.5 DF %>% ggplot(aes(x = reorder(pub_year, -pub_year), fill = factor(mega))) + geom_dotplot(method = "histodot", dotsize = dotsize, binwidth = binwidth, stackgroups = T, binpositions = "all", stackdir = "up", stackratio = 1) + scale_fill_manual(values = c("#8FBEE5", "#DEE7EC"), name = "TEST", labels = c("≤ 40", "> 40")) + scale_y_continuous(limits=c(0, 1), expand = c(0, 0), breaks = seq(0, 1, 1/yHeight), labels = seq(0, yHeight)) + theme_minimal() + theme(axis.ticks = element_blank(), axis.title = element_blank(), #axis.text.y = element_blank(), axis.line = element_blank(), legend.position = "right", panel.grid.minor = element_blank()) + coord_fixed(ratio=binwidth*dotsize*yHeight) + ggExtra::removeGridX()
实现方法
geom_dotplot本身不支持直接设置单栏最大点数,我们可以通过预处理数据,给每个x分组内的点添加分栏偏移量,让多余的点在x轴方向偏移显示,模拟分栏效果。具体步骤如下:
- 数据预处理:按
pub_year和mega分组,给每个点分配组内序号,再根据每栏最大点数(这里设为5)计算分栏数,最后生成x轴的偏移值。 - 调整绘图映射:将原本的x变量替换为带偏移的新变量,同时保持x轴标签不变。
- 调整坐标轴和主题:确保x轴显示原始年份,不显示偏移后的数值。
修改后的完整代码
library(tidyverse) library(ggExtra) yHeight <- 10 binwidth <- 0.5 dotsize <- 0.5 max_per_col <- 5 # 设置每栏最大点数 # 预处理数据:添加分栏偏移 DF_processed <- DF %>% mutate(pub_year = factor(pub_year, levels = sort(unique(pub_year), decreasing = TRUE))) %>% group_by(pub_year, mega) %>% mutate( idx = row_number(), col = (idx - 1) %/% max_per_col, # 计算分栏编号 x_offset = as.numeric(pub_year) + col * binwidth * 1.1 # 计算x轴偏移,1.1是栏间距系数,可调整 ) %>% ungroup() # 绘制分栏点图 DF_processed %>% ggplot(aes(x = x_offset, fill = factor(mega))) + geom_dotplot(method = "histodot", dotsize = dotsize, binwidth = binwidth, stackgroups = TRUE, binpositions = "all", stackdir = "up", stackratio = 1) + scale_fill_manual(values = c("#8FBEE5", "#DEE7EC"), name = "TEST", labels = c("≤ 40", "> 40")) + scale_x_continuous( breaks = as.numeric(unique(DF_processed$pub_year)), labels = unique(DF_processed$pub_year) ) + scale_y_continuous(limits=c(0, 1), expand = c(0, 0), breaks = seq(0, 1, 1/yHeight), labels = seq(0, yHeight)) + theme_minimal() + theme(axis.ticks = element_blank(), axis.title = element_blank(), axis.line = element_blank(), legend.position = "right", panel.grid.minor = element_blank()) + coord_fixed(ratio=binwidth*dotsize*yHeight) + ggExtra::removeGridX()
代码说明
- 数据预处理部分:
- 先把
pub_year转为因子,确保排序正确; idx是每组内的点序号,col是该点所属的分栏;x_offset是原始x值加上分栏偏移,偏移量用binwidth * 1.1保证栏与栏之间有间隙,可根据需求调整系数。
- 先把
- 绘图部分:
- x轴映射改为
x_offset,但通过scale_x_continuous设置breaks和labels为原始年份,保证坐标轴显示正确; - 其余参数保持和原代码一致,确保点的样式、颜色、y轴刻度等不变。
- x轴映射改为
内容的提问来源于stack exchange,提问作者anpami
相关产品推荐
相关产品推荐

