【问题标题】:Plotting multiple normal curves with ggplot2 without hardcoding means and standard deviations使用 ggplot2 绘制多条正态曲线,无需硬编码均值和标准差
【发布时间】:2024-12-19 14:50:02
【问题描述】:

我有一个均值和标准差向量,我想使用ggplot2 在同一个图中绘制与这些均值和标准差对应的密度。我使用mapplygather 来解决这个问题,但是对于我认为应该是微不足道的事情,它的代码行相当多:

library(dplyr)
library(tidyr)
library(ggplot2)

# generate data
my_data <- data.frame(mean =  c(0.032, 0.04, 0.038, 0.113, 0.105, 0.111),
                      stdev = c(0.009, 0.01, 0.01, 0.005, 0.014, 0.006), 
                      test = factor(c("Case_01", "Case_02", "Case_03", "Case_04",
                                      "Case_05", "Case_06")))

# points at which to evaluate the Gaussian densities
x <- seq(-0.05, 0.2, by = 0.001)

# build list of Gaussian density vectors based on means and standard deviations
pdfs <- mapply(dnorm, mean = my_data$mean, sd = my_data$stdev, MoreArgs = list(x = x),
               SIMPLIFY = FALSE)

# add group names
names(pdfs) <- my_data$test

# convert list to dataframe
pdfs <- do.call(cbind.data.frame, pdfs)
pdfs$x <- x

# convert dataframe to tall format
tall_df <- gather(pdfs, test, density, -x)

# build plot
p <- ggplot(tall_df, aes(color = test, x = x, y = density)) +
  geom_line() +
  geom_segment(data = my_data, aes(color = test, x = mean, y = 0, 
                                   xend = mean, yend = 100), linetype = "dashed") +
  coord_cartesian(ylim = c(-1, 100))
print(p)

这很像:

Plot multiple normal curves in same plot

事实上,the accepted answer 使用了mapply,这证实了我走在正确的轨道上。但是,我不喜欢这个答案是它硬编码了mapply 调用中的意思和标准偏差。这在我的用例中不起作用,因为我从磁盘读取了真实数据(当然,在 MRE 中,为了简单起见,我跳过了数据读取部分)。是否可以在不牺牲可读性的情况下简化我的代码,并且无需在 mapply 调用中对均值和标准差向量进行硬编码?

EDIT也许可以通过使用包mvtnorm 来避免调用mapply,但我认为这并不能提供任何真正的简化。我的大部分代码都是在调用 mapply 之后出现的。

【问题讨论】:

    标签: r ggplot2 dplyr normal-distribution mapply


    【解决方案1】:

    您可以使用purrr::pmap_df 节省一些编码,它会在为每个mean-stdev 对构造数据框后自动执行行绑定:

    假设my_data 的输入列按顺序排列,或者mean, stdev, testtest 属于字符类。

    library(purrr)
    tall_df2 <- pmap_df(my_data, ~ data_frame(x = x, test = ..3, density = dnorm(x, ..1, ..2)))
    

    有数据:

    my_data <- data.frame(mean =  c(0.032, 0.04, 0.038, 0.113, 0.105, 0.111),
                          stdev = c(0.009, 0.01, 0.01, 0.005, 0.014, 0.006), 
                          test = c("Case_01", "Case_02", "Case_03", "Case_04", "Case_05", "Case_06"), 
                          stringsAsFactors = F)
    

    剧情:

    p <- ggplot(tall_df2, aes(color = factor(test), x = x, y = density)) + 
          geom_line() +
          geom_segment(data = my_data, aes(color = test, x = mean, y = 0, 
                                           xend = mean, yend = 100), linetype = "dashed") +
          coord_cartesian(ylim = c(-1, 100))
    
    print(p)
    

    给予:

    【讨论】:

    • 酷!你能解释一下pmap_df 的目标是什么吗?我不熟悉purrr
    • 另外,我不确定你为什么必须使用..i sintax。当然,tall_df2 &lt;- pmap_df(my_data, ~ data_frame(x = x, test = test, density = dnorm(x, mean, stdev))) 不起作用,因此您的语法是正确的。但我不明白为什么
    • pmap_dfpmap + bind_rows 的组合,pmapmapply 做类似的事情,但语法不同。所以不用mapply(f, a, b, c) 你做pmap(list(a, b, c),f),如果你已经把数据放在一起,这很方便,在你的情况下是一个数据框。最后它调用bind_rows 为你保存do.call(..., rbind)。有关语法的更多信息,请参阅?pmap。如果要按名称匹配参数,则需要显式构造函数pmap_df(my_data, function(mean, stdev, test) data_frame(x = x, test = test, density = dnorm(x, mean, stdev)))
    • 太棒了!我将使用按名称匹配的方法——IMO 更具可读性,并且也不对列的顺序做出假设。由于在实际用例中我从磁盘读取数据,我无法确定列顺序 - 但我知道列名。
    • 为清楚起见,x 参数可能应替换为问题中的序列 (seq(-0.05, 0.2, by = 0.001))。