如何基于点密度为分组条形图添加渐变着色(从每组最小值开始)
分组范围条形图的密度着色实现
需求说明
需要创建展示每组数据点范围的条形图,并用数据点的核密度(或其他密度方法)为条形着色,替代原示例中的离散点;要求密度着色从每组数据的最小值开始,而非从0起始。
原示例代码
原代码用线段展示每组数据的min-max范围,叠加抖动点展示数据分布:
library(dplyr) library(tidyr) library(ggplot2) # 生成示例数据 set.seed(100) df <- data.frame(a=rnorm(100, mean=5, sd=1), b=rnorm(100, mean=10, sd=2), c=rnorm(100, mean=15, sd=3)) %>% pivot_longer(everything(), names_to = "group", values_to = "value") # 计算每组的min和max df_minmax <- df %>% group_by(group) %>% summarize(min=min(value), max=max(value)) # 绘制基础图 ggplot() + geom_segment(data=df_minmax, aes(x=group, y=min, yend=max), size=18, color='black') + geom_jitter(data=df, aes(x=group, y=value), color='white', width=0.1)
遇到的问题
参考示例中使用stat_ydensity时,存在两个问题:
- 自定义列名(如
value而非ydat)时,报错找不到对应列; - 参考示例的密度着色从0开始,不符合“从每组最小值开始”的需求。
解决方案一:修复stat_ydensity的使用
核心是正确映射变量,调整参数确保密度覆盖从min到max的范围:
library(dplyr) library(tidyr) library(ggplot2) # 生成示例数据 set.seed(100) df <- data.frame(a=rnorm(100, mean=5, sd=1), b=rnorm(100, mean=10, sd=2), c=rnorm(100, mean=15, sd=3)) %>% pivot_longer(everything(), names_to = "group", values_to = "value") ggplot(df, aes(x = group, y = value, group = group)) + # 用stat_ydensity生成密度着色的矩形条 stat_ydensity( aes( fill = after_stat(density), xmin = as.numeric(group) - 0.4, # 控制条形宽度,适配字符型分组 xmax = as.numeric(group) + 0.4, ymin = after_stat(y) - 0.05, # 调整每个密度块的高度,根据数据缩放 ymax = after_stat(y) + 0.05 ), geom = "rect", trim = FALSE, # 不截断密度曲线,确保覆盖数据全范围 scale = 10 # 缩放密度值,让填充区域匹配数据的min-max范围 ) + # 可选:添加每组的上下限线段,强化范围展示 stat_summary( aes(y = value, yend = value), fun.min = min, fun.max = max, geom = "segment", size = 1, color = "black" ) + scale_fill_viridis_c(name = "密度") + theme_minimal()
关键说明
- 用
as.numeric(group)将字符型分组转换为数值,方便计算条形的左右边界; after_stat(y)对应映射的y变量(即value),避免列名不匹配的错误;trim=FALSE确保密度计算包含所有数据点,不会截断在分布的两端;scale参数调整密度的缩放比例,让填充区域刚好覆盖每组的min-max范围。
解决方案二:预先计算核密度再绘图
手动计算每组的核密度,完全控制密度范围从min到max,灵活性更高:
library(dplyr) library(tidyr) library(ggplot2) # 生成示例数据 set.seed(100) df <- data.frame(a=rnorm(100, mean=5, sd=1), b=rnorm(100, mean=10, sd=2), c=rnorm(100, mean=15, sd=3)) %>% pivot_longer(everything(), names_to = "group", values_to = "value") # 预先计算每组的核密度(从min到max) df_density <- df %>% group_by(group) %>% group_modify(~{ # 自定义密度计算范围:从组内最小值到最大值 dens <- density(.x$value, from = min(.x$value), to = max(.x$value), adjust = 1) tibble(y = dens$x, density = dens$y) }) %>% ungroup() # 为分组分配数值位置,方便绘制条形 group_positions <- df %>% distinct(group) %>% mutate(x_num = row_number()) # 合并密度数据与分组位置 df_density <- df_density %>% left_join(group_positions, by = "group") # 绘制图形 ggplot() + # 用geom_rect绘制密度填充的条形 geom_rect( data = df_density, aes( xmin = x_num - 0.4, xmax = x_num + 0.4, ymin = y, ymax = lead(y), # 用lead(y)生成连续的矩形块 fill = density ), color = NA # 去掉矩形边框,让填充更平滑 ) + # 添加每组的上下限线段 geom_segment( data = df %>% group_by(group) %>% summarize(min=min(value), max=max(value)) %>% left_join(group_positions, by="group"), aes(x = x_num, xend = x_num, y = min, yend = max), size = 1, color = "black" ) + # 替换x轴刻度为分组名称 scale_x_continuous(breaks = group_positions$x_num, labels = group_positions$group) + scale_fill_viridis_c(name = "密度") + theme_minimal()
关键说明
- 手动指定
density()的from和to参数,确保密度计算严格从组内最小值到最大值; - 可通过
adjust参数调整核密度的平滑度,适配不同的数据分布; - 完全控制条形的宽度、填充范围,避免stat类函数的参数适配问题。
内容的提问来源于stack exchange,提问作者Jaken
相关产品推荐
相关产品推荐

