R中离散数据密度计算:stats::density与ggplot2结果差异咨询
差异核心原因
- 计算逻辑本质不同:
ggplot2::geom_histogram()生成的..density..是分箱直方图密度,计算规则为(单个分箱内样本数 / 总样本数) / 分箱宽度。你的代码中设置binwidth=1,输出结果就是每个整数取值对应的样本占比,完全匹配离散取整后的数据分布。而stats::density()是核密度估计(KDE)函数,默认使用高斯核做连续平滑,会将相邻点的概率质量做加权扩散,属于连续分布的平滑估计,和直方图的离散分箱计数逻辑完全不同,结果自然存在明显差异。 - 函数调用参数设置错误:你调用
density()时设置from = -3, to = 6,直接截断了x<-3和x>6区间的密度估计,但生成的round(rnorm(1000))样本本身存在超出[-3,6]范围的值,截断会导致密度总积分不等于1,结果整体偏移。同时设置n = length(-3:6)仅生成10个估计网格点,网格密度不足以覆盖数据实际范围,后续用geom_col()绘图时也没有做分箱对齐,进一步放大了视觉差异。 - 场景错配:如果你的目标是预先计算和
..density..一致的直方图密度值(适配大数据量场景),不需要调用核密度估计函数,直接通过分箱计数计算即可,计算效率远高于KDE。如果你需要的是核密度平滑结果,应该用geom_line()绘制连续曲线而非geom_col()画离散柱形,同时调整density()的取值范围覆盖全量数据、增加网格点数量,结果会和geom_density()输出对齐。
修正方案
直接调用基础R的hist()函数并关闭绘图模式,提前计算分箱密度,结果和ggplot2内置计算的..density..完全一致,大数据量下计算速度极快:
library(tidyverse) library(patchwork) set.seed(123) # 固定随机种子保证结果可复现 df <- data.frame(A = round(rnorm(1000)), B = round(rnorm(1000)), C = round(rnorm(1000))) %>% pivot_longer(cols = everything(), names_to = "group") # 预计算直方图密度,和ggplot2内置计算逻辑完全对齐 dens_df <- df %>% group_by(group) %>% group_modify(~{ # 分箱边界设为半整数,匹配整数数据binwidth=1的分箱规则,覆盖x从-3到10的展示范围 h_res <- hist(.x$value, breaks = seq(-3.5, 10.5, 1), plot = FALSE) data.frame(density.x = h_res$mids, density.y = h_res$density) }) plot_precomputed <- dens_df %>% ggplot()+ aes(x = density.x, y = density.y) %>% geom_col(width = 1, col = "white")+ # 柱宽设为1和分箱宽度一致 scale_x_continuous(breaks = seq(-3,10,1), limits = c(-3,10))+ stat_function(fun = dnorm, n = 14, args = list(mean = 0, sd = 1), geom = "point", col = "red") + stat_function(fun = dnorm, n = 14, args = list(mean = 0, sd = 1), geom = "point", size = 2, col = "red") + stat_function(fun = dnorm, n = 14, args = list(mean = 0, sd = 1), geom = "line", col = "red") + facet_wrap(~group)+ labs(title = "precomputed histogram density") plot_ggplot <- df %>% ggplot()+ aes(x = value, y = ..density..) %>% geom_histogram( binwidth = 1, col = "white")+ scale_x_continuous(breaks = seq(-3,10,1), limits = c(-3,10))+ stat_function(fun = dnorm, n = 14, args = list(mean = 0, sd = 1), geom = "point", col = "red") + stat_function(fun = dnorm, n = 14, args = list(mean = 0, sd = 1), geom = "point", size = 2, col = "red") + stat_function(fun = dnorm, n = 14, args = list(mean = 0, sd = 1), geom = "line", col = "red") + facet_wrap(~group)+ labs(title = "using ggplot2 ..density..") plot_precomputed + plot_ggplot
运行上述代码后,两个子图的柱形高度会完全一致。
内容的提问来源于stack exchange,提问作者CyG
相关产品推荐
相关产品推荐

