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

如何判断点是否在ggplot的stat_ellipse生成的置信椭圆内?

判断点是否在ggplot2 stat_ellipse绘制的椭圆内的解决方案

问题核心

你需要判断给定坐标点是否处于ggplot2 stat_ellipse绘制的95%容忍椭圆内,但自行编写的函数存在边界判断误差,且尝试car::dataEllipse生成的椭圆与stat_ellipse绘制的形状不一致。

关键原因:椭圆计算逻辑差异

stat_ellipse默认使用多元t分布的容忍椭圆(type="t"),而car::dataEllipse默认生成的是多元正态分布的置信椭圆(对应stat_ellipse的type="norm"),两者的缩放因子计算逻辑不同,导致椭圆形状不一致:

  • stat_ellipse(type="t"):缩放因子基于F分布,公式为sqrt(2 * qf(level, 2, n-1))(n为样本量)
  • stat_ellipse(type="norm")/car::dataEllipse:缩放因子基于卡方分布,公式为sqrt(qchisq(level, 2))

解决方案1:提取stat_ellipse的椭圆路径,用空间判断(最准确)

直接提取stat_ellipse实际绘制的椭圆坐标,转为sf多边形后进行空间相交判断,完全匹配绘图结果:

library(ggplot2)
library(sf)
library(dplyr)

# 1. 获取stat_ellipse的椭圆数据(以mtcars的cyl分组、disp和hp为变量为例)
p <- ggplot(mtcars, aes(x = disp, y = hp, color = factor(cyl))) +
  stat_ellipse(type = "t", level = 0.95)  # 保持默认type="t"

ellipse_coords <- ggplot_build(p)$data[[1]]

# 2. 将每个分组的椭圆转为sf多边形
ellipse_polygons <- ellipse_coords %>%
  group_by(group) %>%
  summarise(geometry = st_polygon(list(cbind(x, y)))) %>%
  st_sf() %>%
  mutate(cyl = factor(c(4, 6, 8)))  # 对应group的分组顺序

# 3. 准备测试点(含你提到的(310,300))
test_points <- tibble(
  disp = c(310, 100, 200, 400),
  hp = c(300, 100, 150, 250),
  cyl = factor(c(8, 4, 6, 8))  # 指定每个点对应的分组
) %>%
  st_as_sf(coords = c("disp", "hp"))

# 4. 判断每个点是否在对应分组的椭圆内
test_points <- test_points %>%
  left_join(ellipse_polygons, by = "cyl") %>%
  mutate(in_ellipse = st_within(geometry.x, geometry.y, sparse = FALSE)[,1])

# 查看结果
select(test_points, disp = geometry.x[[1]][1], hp = geometry.x[[1]][2], cyl, in_ellipse)

运行后,点(310,300)会被正确判定为FALSE(不在8缸组的椭圆内)。

解决方案2:复刻stat_ellipse的计算逻辑,编写判断函数

如果不想依赖sf包,可以直接按照stat_ellipse的计算逻辑编写函数,手动计算点的马氏距离并与缩放因子比较:

point_in_stat_ellipse <- function(point, data, x_col, y_col, group_col, level = 0.95, type = "t") {
  # 筛选对应分组的数据
  group_data <- data %>% filter({{group_col}} == point[[as_label(enquo(group_col))]])
  n <- nrow(group_data)
  
  # 计算均值和协方差矩阵
  vars <- group_data %>% select({{x_col}}, {{y_col}})
  mu <- colMeans(vars)
  sigma <- cov(vars)
  
  # 根据type计算缩放因子的平方
  scale_sq <- switch(type,
    "norm" = qchisq(level, 2),
    "t" = 2 * qf(level, 2, n - 1),
    "euclid" = level,
    stop("Invalid type: choose 'norm', 't', or 'euclid'")
  )
  
  # 计算点的马氏距离平方
  point_vec <- unlist(point %>% select({{x_col}}, {{y_col}}))
  mahalanobis_sq <- t(point_vec - mu) %*% solve(sigma) %*% (point_vec - mu)
  
  # 判断是否在椭圆内
  mahalanobis_sq <= scale_sq
}

# 测试点(310,300)(属于cyl=8组)
test_point <- tibble(disp = 310, hp = 300, cyl = 8)
point_in_stat_ellipse(test_point, mtcars, disp, hp, cyl)  # 返回FALSE

统一car::dataEllipse与stat_ellipse的方法

如果坚持要用car::dataEllipse,只需将stat_ellipse的type参数设为"norm",两者的椭圆就会一致:

# stat_ellipse用type="norm"
ggplot(mtcars, aes(x = disp, y = hp, color = factor(cyl))) +
  stat_ellipse(type = "norm", level = 0.95)

# car::dataEllipse生成对应椭圆
library(car)
dataEllipse(mtcars$disp, mtcars$hp, groups = mtcars$cyl, level = 0.95, robust = FALSE)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 19:15:14