使用ggplot2绘制直方图,并且带有密度曲线,使其总和为1。

14

绘制一个密度曲线总和为1的非标准化数据的直方图是非常困难的。已经有很多关于此的问题,但它们的解决方案都不适用于我的数据。需要一个简单的解决方案,而且必须有效。我找不到一个简单有效的答案。

以下是一些例子:

仅适用于标准正态数据的解决方案 ggplot2:叠加直方图和密度曲线

使用离散数据且没有密度曲线 ggplot2:带宽=.5、垂直线和中心位置的密度直方图

无答案 使用自定义bin在ggplot2上叠加密度和直方图图形

我的数据密度总和不为1 在ggplot2中创建密度直方图?

我的数据中总和不为1 使用自定义分 bin 的 ggplot2 密度直方图 这里有一个长的解释和示例,但是我的数据密度不为1 在垂直轴为频率(也称为计数)或相对频率的直方图上叠加“密度”曲线?

--

一些示例代码:
#Example code
set.seed(1)
t = data.frame(r = runif(100))

#first we try the obvious simple solution that should work
ggplot(t, aes(r)) + 
  geom_histogram() + 
  geom_density()

enter image description here

因此,很明显密度不会总和为1。

#maybe geom_histogram needs a ..density.. ?
ggplot(t, aes(r)) + 
  geom_histogram(aes(y = ..density..)) + 
  geom_density()

enter image description here

它确实改变了一些东西,但不正确。

#maybe geom_density needs a ..density.. too ?
ggplot(t, aes(r)) + 
  geom_histogram(aes(y = ..density..)) + 
  geom_density(aes(y = ..density..))

没有变化。

#maybe binwidth = 1?
ggplot(t, aes(r)) + 
  geom_histogram(aes(y = ..density..), binwidth=1) + 
  geom_density(aes(y = ..density..))

enter image description here

密度曲线仍然不正确,现在直方图也不正确。

确保了,我花了4个小时尝试各种 ..count..、..sum.. 和 ..density.. 的组合,但由于找不到任何关于它们应该如何工作的文档,这是半盲目的试错。

所以我放弃了使用ggplot2来总结数据。

因此,首先我们需要获得正确的比例数据框,这并不简单:

get_prop_table = function(x, breaks_=20){
  library(magrittr)
  library(plyr)
  x_prop_table = cut(x, 20) %>% table(.) %>% prop.table %>% data.frame
  colnames(x_prop_table) = c("interval", "density")
  intervals = x_prop_table$interval %>% as.character
  fetch_numbers = str_extract_all(intervals, "\\d\\.\\d*")
  x_prop_table$means = laply(fetch_numbers, function(x) {
    x %>% as.numeric %>% mean
  })
  return(x_prop_table)
}

t_df = get_prop_table(t$r)

这提供了我们想要的摘要数据:
> head(t_df)
          interval density    means
1 (0.00859,0.0585]    0.06 0.033545
2   (0.0585,0.107]    0.09 0.082750
3    (0.107,0.156]    0.07 0.131500
4    (0.156,0.205]    0.10 0.180500
5    (0.205,0.254]    0.08 0.229500
6    (0.254,0.303]    0.03 0.278500

现在我们只需要绘制它。应该很容易...
ggplot(t_df, aes(means, density)) + 
  geom_histogram(stat = "identity") +
  geom_density(stat = "identity")

enter image description here

嗯,不完全是我想要的。为了确定,我尝试在 geom_density 中不使用 stat = "identity",此时它会抱怨没有 y。

#lets try adding ..density.. then
ggplot(t_df, aes(means, density)) + 
  geom_histogram(stat = "identity") +
  geom_density(aes(y = ..density..))

enter image description here

更加奇怪了。

好的,也许我们应该放弃从摘要数据中获取密度曲线的想法。也许我们需要把方法结合起来使用一下...

#adding together
ggplot(t_df, aes(means, density)) +
  geom_bar(stat = "identity") +
  geom_density(data=t, aes(r, y = ..density..), stat = 'density')

enter image description here

好的,至少形状现在是正确的。现在,我们需要想办法将其缩小。

#lets try dividing by the number of bins
ggplot(t_df, aes(means, density)) +
  geom_bar(stat = "identity") +
  geom_density(data=t, aes(r, y = ..density../20), stat = 'density')

enter image description here

看起来我们有一个获胜者。但是这个数字是硬编码的。

#removing the hardcoding?
divisor = nrow(t_df)
ggplot(t_df, aes(means, density)) +
  geom_bar(stat = "identity") +
  geom_density(data=t, aes(r, y = ..density../divisor), stat = 'density')

Error in eval(expr, envir, enclos) : object 'divisor' not found

好的,我几乎期望它能够正常工作。现在我尝试在这里和那里添加一些“..”,还有“..count..”和“..sum..”,第一个给出了另一个错误的结果,第二个抛出了一个错误。我还尝试使用乘法器(使用1/20),但没有成功。

#salvation with get()
divisor = nrow(t_df)
ggplot(t_df, aes(means, density)) +
  geom_bar(stat = "identity") +
  geom_density(data=t, aes(r, y = ..density../get("divisor", pos = 1)), stat = 'density')

enter image description here

所以,我终于得到了正确的数字(我想; 我希望)。

请告诉我有更简单的方法来做这件事。

附注: get() 技巧显然无法在函数内部使用。我本来想在此处放置一个可用的函数供将来使用,但那也不是那么容易。


2
你的 runif 数据下曲线下面积总和为1。你试图解决什么问题? - hrbrmstr
你为什么认为 aes(y = ..density..) 是错误的?你没有描述问题是什么。 - hadley
请提供需要翻译的文本。 - CoderGuy123
2
你在阐述你的观点时做得不够好。从一个更简单的例子开始,手动计算一下,然后再与ggplot2绘制的结果进行比较。 - hadley
那是因为我误解了问题的确切所在。抱歉。 - CoderGuy123
显示剩余2条评论
1个回答

6
首先,阅读R中的密度Wickham,注意每个包/函数的缺点和特点。
密度总和为1,但这并不意味着曲线/点不会超过1。
以下内容展示了这一点以及(至少)默认density与例如KernSmooth::bkde相比的不准确性(使用基本绘图简洁地输入):
library(KernSmooth)
library(flux)
library(sfsmisc)

# uniform dist
set.seed(1)
dat <- runif(100)

d1 <- density(dat)
d1_ks <- bkde(dat)

par(mfrow=c(2,1))
plot(d1)
plot(d1_ks, type="l")

enter image description here

auc(d1$x, d1$y)
## [1] 1.000921

integrate.xy(d1$x, d1$y)
## [1] 1.000921

auc(d1_ks$x, d1_ks$y)
## [1] 1

integrate.xy(d1_ks$x, d1_ks$y)
## [1] 1

将beta分布做同样的操作:
# beta dist
set.seed(1)
dat <- rbeta(100, 0.5, 0.1)

d2 <- density(dat)
d2_ks <- bkde(dat)

par(mfrow=c(2,1))
plot(d2)
plot(d2_ks, typ="l")

enter image description here

auc(d2$x, d2$y)
## [1] 1.000187

integrate.xy(d2$x, d2$y)
## [1] 1.000188

auc(d2_ks$x, d2_ks$y)
## [1] 1

integrate.xy(d2_ks$x, d2_ks$y)
## [1] 1

"

aucintegrate.xy都使用梯形法则,但我运行它们来展示这一点,并展示两个不同函数的结果。

关键是,尽管y轴值让你认为它们不会,但密度确实总和为1。我不确定您试图通过操作解决什么问题。

"

1
密度曲线必须与比例直方图相符合(如我工作结束时的图形所示)。这就是我想要的。你发布的那些也没有做到这一点。你是对的,AUC不是直接问题,但它是相关的。 - CoderGuy123
然后使用KernSmooth :: bkde函数获取点,手动绘制直方图(或使用hist的数字输出),相应地进行缩放并绘制它们。或者使用基础库。你真正遇到的问题是你想要两个y轴,这与“错误”的密度完全不同。 - hrbrmstr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接