【问题标题】:Overlay normal curve to histogram in R将正态曲线叠加到R中的直方图
【发布时间】:2013-12-03 09:25:54
【问题描述】:

我已经设法在网上找到如何将正常曲线叠加到 R 中的直方图上,但我想保留直方图的正常“频率”y 轴。请参阅下面的两个代码段,并注意在第二个代码段中,y 轴如何替换为“密度”。我怎样才能将 y 轴保持为“频率”,就像在第一个图中一样。

作为奖励:我还想在密度曲线上标记 SD 区域(最多 3 个 SD)。我怎样才能做到这一点?我试过abline,但这条线延伸到图的顶部,看起来很丑。

g = d$mydata
hist(g)

g = d$mydata
m<-mean(g)
std<-sqrt(var(g))
hist(g, density=20, breaks=20, prob=TRUE, 
     xlab="x-variable", ylim=c(0, 2), 
     main="normal curve over histogram")
curve(dnorm(x, mean=m, sd=std), 
      col="darkblue", lwd=2, add=TRUE, yaxt="n")

在上图中,y 轴是“密度”。我想让它成为“频率”。

【问题讨论】:

  • 您可以通过应用this answer中列出的策略来完成此操作
  • 虽然我应该补充一点,连续密度曲线的“频率”的解释真的很不清楚。
  • 我明白,我没问题。您给我的链接效果很好,只是它没有给出正态分布,而是给出了具有多个拐点的密度曲线。我想在上面的情节中得到一个正常的。有什么想法吗?
  • 查看 here 获取 ggplot2 选项。

标签: r plot histogram gaussian


【解决方案1】:

您需要找到正确的乘数来将密度(曲线下方面积为 1 的估计曲线)转换为计数。这可以很容易地从hist 对象中计算出来。

myhist <- hist(mtcars$mpg)
multiplier <- myhist$counts / myhist$density
mydensity <- density(mtcars$mpg)
mydensity$y <- mydensity$y * multiplier[1]

plot(myhist)
lines(mydensity)

一个更完整的版本,具有正态密度和远离平均值(包括平均值)的每个标准差处的线条:

myhist <- hist(mtcars$mpg)
multiplier <- myhist$counts / myhist$density
mydensity <- density(mtcars$mpg)
mydensity$y <- mydensity$y * multiplier[1]

plot(myhist)
lines(mydensity)

myx <- seq(min(mtcars$mpg), max(mtcars$mpg), length.out= 100)
mymean <- mean(mtcars$mpg)
mysd <- sd(mtcars$mpg)

normal <- dnorm(x = myx, mean = mymean, sd = mysd)
lines(myx, normal * multiplier[1], col = "blue", lwd = 2)

sd_x <- seq(mymean - 3 * mysd, mymean + 3 * mysd, by = mysd)
sd_y <- dnorm(x = sd_x, mean = mymean, sd = mysd) * multiplier[1]

segments(x0 = sd_x, y0= 0, x1 = sd_x, y1 = sd_y, col = "firebrick4", lwd = 2)

【讨论】:

  • 太棒了!我一直在寻找这个解决方案。现在我意识到问题出在密度的 y 尺度上。
  • 我之前见过这样的:法线曲线有一个凹凸并且是对称的,这个有两个凹凸。
  • 是的,这个答案只使用了核密度估计,没有假设正态性。
【解决方案2】:

只需删除prob = T,让它保持默认即F

【讨论】:

  • 这肯定会将直方图作为频率/计数,但密度曲线仍将在概率尺度上,因此它无法实现密度曲线和直方图计数很好重叠的目标.
【解决方案3】:

这是前面提到的StanLe's anwer 的实现,也解决了他的答案在使用密度时不会产生曲线的情况。

这将替换现有但隐藏的hist.default() 函数,只添加normalcurve 参数(默认为TRUE)。

前三行是为了支持roxygen2进行包构建。

#' @noRd
#' @exportMethod hist.default
#' @export
hist.default <- function(x,
                         breaks = "Sturges",
                         freq = NULL,
                         include.lowest = TRUE,
                         normalcurve = TRUE,
                         right = TRUE,
                         density = NULL,
                         angle = 45,
                         col = NULL,
                         border = NULL,
                         main = paste("Histogram of", xname),
                         ylim = NULL,
                         xlab = xname,
                         ylab = NULL,
                         axes = TRUE,
                         plot = TRUE,
                         labels = FALSE,
                         warn.unused = TRUE,
                         ...)  {

  # https://*.com/a/20078645/4575331
  xname <- paste(deparse(substitute(x), 500), collapse = "\n")

  suppressWarnings(
    h <- graphics::hist.default(
      x = x,
      breaks = breaks,
      freq = freq,
      include.lowest = include.lowest,
      right = right,
      density = density,
      angle = angle,
      col = col,
      border = border,
      main = main,
      ylim = ylim,
      xlab = xlab,
      ylab = ylab,
      axes = axes,
      plot = plot,
      labels = labels,
      warn.unused = warn.unused,
      ...
    )
  )

  if (normalcurve == TRUE & plot == TRUE) {
    x <- x[!is.na(x)]
    xfit <- seq(min(x), max(x), length = 40)
    yfit <- dnorm(xfit, mean = mean(x), sd = sd(x))
    if (isTRUE(freq) | (is.null(freq) & is.null(density))) {
      yfit <- yfit * diff(h$mids[1:2]) * length(x)
    }
    lines(xfit, yfit, col = "black", lwd = 2)
  }

  if (plot == TRUE) {
    invisible(h)
  } else {
    h
  }
}

快速示例:

hist(g)

日期有点不同。供参考:

#' @noRd
#' @exportMethod hist.Date
#' @export
hist.Date <- function(x,
                      breaks = "months",
                      format = "%b",
                      normalcurve = TRUE,
                      xlab = xname,
                      plot = TRUE,
                      freq = NULL,
                      density = NULL,
                      start.on.monday = TRUE,
                      right = TRUE,
                      ...)  {

  # https://*.com/a/20078645/4575331
  xname <- paste(deparse(substitute(x), 500), collapse = "\n")

  suppressWarnings(
    h <- graphics:::hist.Date(
      x = x,
      breaks = breaks,
      format = format,
      freq = freq,
      density = density,
      start.on.monday = start.on.monday,
      right = right,
      xlab = xlab,
      plot = plot,
      ...
    )
  )

  if (normalcurve == TRUE & plot == TRUE) {
    x <- x[!is.na(x)]
    xfit <- seq(min(x), max(x), length = 40)
    yfit <- dnorm(xfit, mean = mean(x), sd = sd(x))
    if (isTRUE(freq) | (is.null(freq) & is.null(density))) {
      yfit <- as.double(yfit) * diff(h$mids[1:2]) * length(x)
    }
    lines(xfit, yfit, col = "black", lwd = 2)
  }

  if (plot == TRUE) {
    invisible(h)
  } else {
    h
  }
}

【讨论】:

  • 很好,这已经在某个地方实现了吗?我需要更新 {graphics} 才能获得这个吗?
  • 不,很遗憾,这在基础 R 中不可用。随意将其添加到包中并将其发布到 CRAN :)
【解决方案4】:

这是我找到的一个不错的简单方法:

h <- hist(g, breaks = 10, density = 10,
          col = "lightgray", xlab = "Accuracy", main = "Overall") 
xfit <- seq(min(g), max(g), length = 40) 
yfit <- dnorm(xfit, mean = mean(g), sd = sd(g)) 
yfit <- yfit * diff(h$mids[1:2]) * length(g) 

lines(xfit, yfit, col = "black", lwd = 2)

【讨论】:

  • 不错!你也可以在hist中使用freq = FALSE来摆脱yfit的缩放。
  • 使用 h$mids[1:2] 代替整个向量有什么意义?
  • 我相信 h$mids[1:2] 的意义只是它用于计算 bin 的大小。由于它们的大小都相同,因此只需找出前两个之间的差异就可以了。如果每个 bin 的范围为 1,则根本不需要这样做。
  • 如果这个代码示例可以被其他人运行就好了。
  • @baxx 请参阅下面的答案以了解实现。它围绕现有的hist() 函数。