You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ggplot2中用stat_summary+geom_segment绘制密度图分位数报错问题

ggplot2密度图绘制止于曲线顶端的分位数线问题

问题描述

想要在ggplot2密度图上绘制分位数线,要求线段止于密度曲线顶端,不使用geom_vline。尝试以下代码:

dat <- data.frame(x = rnorm(1e5))

ggplot(
  data = dat,
  aes(x = x)
) + 
  geom_density() + 
  stat_summary(
    aes(y = 0,yend = after_stat(density)),
    fun = \(x) quantile(x,0.5),
    geom = "segment",
    orientation = "y"
  )

但出现报错:

Error in `stat_summary()`:
! Problem while mapping stat to aesthetics.
ℹ Error occurred in the 2nd layer.
Caused by error in `map_statistic()`:
! Aesthetics must be valid computed stats.
✖ The following aesthetics are invalid:
✖ `yend = after_stat(density)`
ℹ Did you map your stat in the wrong layer?
Run `rlang::last_trace()` to see where the error occurred.

已知可以提前在ggplot外计算密度再手动绘制线段,也知道ggridges包有相关功能但依赖脊线图。困惑点在于:将yend = after_stat(density)替换为固定值yend = 1时代码可正常运行,为何无法调用计算变量density?希望找到用stat_summary和after_stat实现需求的正确方式。

原因分析

stat_summary() 对应的统计变换(StatSummary)核心是计算数据的摘要统计量(如分位数、均值),它的内置计算变量仅包含与摘要结果相关的字段(如y、y_min、y_max),不会生成density密度统计量。

调用after_stat(density)时,ggplot在当前stat_summary图层的计算变量里找不到该字段,因此报错;而使用固定值yend=1时,不需要依赖统计变换生成的变量,自然能正常运行。此外,不同图层的统计变量相互独立,geom_density生成的density变量无法直接被stat_summary图层调用。

解决方案

方法1:结合预计算密度与stat_summary

如果坚持使用stat_summary,需要先将密度值合并到原始数据中,让stat_summary能访问到该变量:

dat <- data.frame(x = rnorm(1e5))

# 预计算x对应的密度值并合并到数据框
dens <- density(dat$x)
dat$density <- approx(dens$x, dens$y, xout = dat$x)$y

ggplot(dat, aes(x = x)) +
  geom_density() +
  stat_summary(
    aes(y = 0, yend = after_stat(max(density))),
    fun = \(x) quantile(x, 0.5),  # 此处计算中位数,可替换为其他分位数
    geom = "segment",
    orientation = "y",
    color = "red",  # 自定义线段颜色
    size = 1        # 自定义线段粗细
  )

这里after_stat(max(density))的作用是:针对分位数对应的分组(此处为全局单组),提取该分位数位置对应的最大密度值(即曲线顶端的密度)。

方法2:直接用stat_density实现更简洁

既然密度值由密度统计生成,直接用stat_density绘制线段会更高效,无需额外预处理数据:

dat <- data.frame(x = rnorm(1e5))

# 定义需要绘制的分位数
quantiles <- c(0.25, 0.5, 0.75)

ggplot(dat, aes(x = x)) +
  geom_density() +
  stat_density(
    geom = "segment",
    aes(
      x = after_stat(x),
      xend = after_stat(x),
      y = 0,
      yend = after_stat(density)
    ),
    # 筛选出分位数对应的位置
    data = ~ dplyr::filter(.x, x %in% quantile(x, quantiles)),
    color = "red",
    size = 1
  )

注:如果分位数在密度曲线插值点上无法精准匹配,可使用approx函数手动计算分位数对应的密度值,再用annotate绘制线段。

内容的提问来源于stack exchange,提问作者joran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 13:40:05