【问题标题】:R: add normal fits to grouped histograms in ggplot2R:为ggplot2中的分组直方图添加正常拟合
【发布时间】:2026-01-14 17:35:01
【问题描述】:

我正在寻找在ggplot2 的分组直方图中叠加正态分布拟合的最优雅方法。我知道这个问题之前已经被问过很多次了,但是没有一个建议的选项,比如this onethis one 让我觉得非常优雅,至少除非stat_function 可以在每个特定小节上工作数据。

将正态分布拟合叠加到我确实遇到的非分组直方图上的一种相对优雅的方法是使用 geom_smoothmethod="nls"(除了它不是自启动函数并且启动必须指定值):

library(ggplot2)
myhist = data.frame(size = 10:27, counts = c(1L, 3L, 5L, 6L, 9L, 14L, 13L, 23L, 31L, 40L, 42L, 22L, 14L, 7L, 4L, 2L, 2L, 1L) )
ggplot(data=myhist, aes(x=size, y=counts)) + geom_point() + 
     geom_smooth(method="nls", formula = y ~ N * dnorm(x, m, s), se=F, 
                 start=list(m=20, s=5, N=300)) 

我想知道这种方法是否也可以用于向分组直方图添加正态分布拟合,如

library(devtools)
install_github("tomwenseleers/easyGgplot2",type="source")
library("easyGgplot2") # load weight data
ggplot(weight,aes(x = weight)) + 
+     geom_histogram(aes(y = ..count.., colour=sex, fill=sex),alpha=0.5,position="identity")

我还想知道是否有任何可能为ggplot2 定义+ stat_distrfit()+ stat_normfit() 的包(有可能进行分组)? (我真的找不到任何东西,但这似乎是一个足够常见的任务,所以我只是想知道)

我希望代码尽可能短的原因是这是为了一门课程,我想让事情尽可能简单......

PS geom_density 不适合我的目标,我还想绘制计数/频率而不是密度。我也想让它们在同一个面板中,避免使用facet_wrap

【问题讨论】:

标签: r ggplot2 histogram normal-distribution


【解决方案1】:

像这样?

## simulate your dataset - could not get easyGplot2 to load....
set.seed(1)     # for reproducible example
weight <- data.frame(sex=c("Female","Male"), weight=rnorm(1000,mean=c(65,67),sd=1))

library(ggplot2)
library(MASS)       # for fitdistr(...)
get.params <- function(z) with(fitdistr(z,"normal"),estimate[1:2])
df <- aggregate(weight~sex, weight, get.params)
df <- data.frame(sex=df[,1],df[,2])
x  <- with(weight, seq(min(weight),max(weight),len=100))
gg <- data.frame(weight=rep(x,nrow(df)),df)
gg$y <- with(gg,dnorm(x,mean,sd))
gg$y <- gg$y * aggregate(weight~sex, weight,length)$weight * diff(range(weight$weight))/30

ggplot(weight,aes(x = weight, colour=sex)) + 
  geom_histogram(aes(y = ..count.., fill=sex), alpha=0.5,position="identity") +
  geom_line(data=gg, aes(y=y))  

我想“优雅”在旁观者的眼中。使用stat_function(...) 的问题是不能使用aes(...) 映射args=... 列表,正如cmets 中的帖子所解释的那样。因此,您必须创建一个辅助 data.frame(在此示例中为 gg),其中包含拟合分布的 x 和 y 值,并使用 geom_line(...)

上面的代码使用MASS 包中的fitdistr(...) 来计算数据的均值和标准差的最大似然估计值,按性别分组,基于正态假设(如果有意义,您可以使用不同的分布)。然后它通过将weight 中的范围划分为 100 个增量来创建 x 轴,并为适当的平均值和标准差计算 dnorm(x,...)。由于结果是密度,我们必须使用以下方法进行调整:

gg$y <- gg$y * aggregate(weight~sex, weight,length)$weight * diff(range(weight$weight))/30

因为您想将此映射到计数数据。请注意,这假设您使用 geom_histogram 中的默认分箱(它将 x 中的范围划分为 30 个相等的增量)。最后,我们使用gg 作为层特定的数据集添加对geom_line(...) 的调用。

【讨论】:

  • 非常感谢 - 是的,这就是我想要的! stat_function() 无法映射仍然有点令人惊讶 - 我真的看不出有任何内在原因为什么迟早不能实现......我会尝试将它包装在 ggplot2.normhist()在我的 easyGgplot2 fork 中使用函数来为我的学生节省一些代码...... :-)