如何用ggplot2叠加不同x轴范围的核密度图与单箱直方图?
解决ggplot2合并核密度图与0值单箱直方图的问题
我来帮你搞定这三个核心问题,同时调整图形比例让展示更合理:
问题拆解
你的需求是同时呈现两类数据的分布:
- 数值>0部分的核密度估计曲线
- 数值=0部分的单箱直方图
但当前实现存在三个明显问题:
- 核密度线会沿整个x轴(包括0左侧的无效区域)绘制
- 直方图与核密度图的比例严重失调
- 调整核平滑因子时出现重复的密度线条
解决方案代码
values <- c(25.222222, 6.000000, 2.057143, 0.000000, 2.142857, 0.000000, 73.666667, 4.081081, 43.133333, 18.937500, 60.822222, 23.379310, 54.954412, 8.492308, 67.646250, 15.885000, 38.585859, 46.810606, 31.565152, 39.813889, 40.620000, 25.958000, 54.821429, 9.000000, 33.040476, 50.329670, 43.525641, 33.508696, 34.265385, 57.003544, 36.690434, 48.074074, 70.372222, 77.602564, 29.997436, 71.739683, 11.320000, 2.938776, 10.101562, 35.037956) df <- data.frame(variable = "TH_part", value=values) library(ggplot2) # 提前计算非0数据量和直方图组距,用于y轴转换 n_nonzero <- nrow(subset(df, value != 0)) bin_width <- 10 ggplot() + # 绘制0值的单箱直方图,精准包裹0值 geom_histogram(data = subset(df, value == 0), aes(x = value, y = ..count..), breaks = c(-5, 5), fill = "#619CFF", alpha = 0.4) + # 绘制>0数据的核密度线,转换为计数刻度匹配直方图 geom_density(data = subset(df, value != 0), aes(x = value, y = ..density.. * n_nonzero * bin_width), color = "#F8766D", adjust = 0.5, size = 1) + # 限制x轴显示范围,避免密度线延伸到无效区域 coord_cartesian(xlim = c(-10, 200)) + # 调整坐标轴标签和主题 labs(x = "Value", y = "Count") + theme_bw() + theme(legend.position = "none")
关键调整说明
1. 解决核密度线过度延伸
- 用
coord_cartesian(xlim = c(-10, 200))替代scale_x_continuous(limits = ...):前者只是限制显示范围,不会截断核密度的计算数据,同时避免密度线跑到0左侧的无效区域。 - 给0值直方图设置
breaks = c(-5,5),让箱子刚好包裹0值,不会出现多余的空箱子。
2. 解决比例失调问题
- 将核密度的y轴从密度转换为计数:
..density.. * n_nonzero * bin_width,其中:n_nonzero是非0数据的总数量bin_width是直方图的组距(这里设为10)
这样核密度线的高度就能和直方图的计数刻度完全匹配,比例协调。
3. 解决重复核密度线问题
- 以空的
ggplot()为基础,分别给geom_histogram和geom_density指定独立的数据源,避免继承全局aes导致的重复绘制。 - 单独给
geom_density设置adjust = 0.5,不会出现重复线条。
可选方案:保留密度y轴
如果你需要用密度刻度而非计数,只需调整0值直方图的y轴计算逻辑:
ggplot() + geom_histogram(data = subset(df, value == 0), aes(x = value, y = ..density..), breaks = c(-5, 5), fill = "#619CFF", alpha = 0.4) + geom_density(data = subset(df, value != 0), aes(x = value, y = ..density..), color = "#F8766D", adjust = 0.5, size = 1) + coord_cartesian(xlim = c(-10, 200)) + labs(x = "Value", y = "Density") + theme_bw() + theme(legend.position = "none")
这种情况下,0值箱子的密度是基于整个数据集计算的,能准确反映0值在整体中的占比。
内容的提问来源于stack exchange,提问作者karla
相关产品推荐
相关产品推荐

