如何判断点是否在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
相关产品推荐
相关产品推荐

