如何绘制轮廓线,显示95%的值落在R和ggplot2中的位置

时间:2014-05-02 21:10:12

标签: r plot ggplot2

说我们有:

x <- rnorm(1000)
y <- rnorm(1000)

如何使用ggplot2生成包含以下两个geom的图:

  1. 两个系列值的双变量期望
  2. 显示95%估算值落在哪里的等高线?
  3. 我知道如何做第一部分:

     df <- data.frame(x=x, y=y)
     p <- ggplot(df, aes(x=x, y=y))
     p <- p + xlim(-10, 10) + ylim(-10, 10) # say
     p <- p + geom_point(x=mean(x), y=mean(y))
    

    我也知道ggplot2中的stat_contour()和stat_density2d()函数。

    我也知道有&#39; bins&#39; stat_contour中的选项。

    但是,我想我需要的是像分位数中的probs参数,但是超过两个维度而不是一个维度。

    我还在图形包中看到了一个解决方案。但是,我想在ggplot中这样做。

    非常感谢,

    乔恩

5 个答案:

答案 0 :(得分:8)

这很有效,但效率很低,因为你实际上必须三次计算内核密度估计值。

set.seed(1001)
d <- data.frame(x=rnorm(1000),y=rnorm(1000))
getLevel <- function(x,y,prob=0.95) {
    kk <- MASS::kde2d(x,y)
    dx <- diff(kk$x[1:2])
    dy <- diff(kk$y[1:2])
    sz <- sort(kk$z)
    c1 <- cumsum(sz) * dx * dy
    approx(c1, sz, xout = 1 - prob)$y
}
L95 <- getLevel(d$x,d$y)
library(ggplot2); theme_set(theme_bw())
ggplot(d,aes(x,y)) +
   stat_density2d(geom="tile", aes(fill = ..density..),
                  contour = FALSE)+
   stat_density2d(colour="red",breaks=L95)

(在http://comments.gmane.org/gmane.comp.lang.r.ggplot2/303的帮助下)

更新:使用最新版本的ggplot2(2.1.0),似乎无法将breaks传递给stat_density2d(或至少我不知道如何),但下面geom_contour的方法似乎仍有效......

通过计算一次内核密度估算并绘制同一网格中的切片和轮廓,可以提高效率:

kk <- with(dd,MASS::kde2d(x,y))
library(reshape2)
dimnames(kk$z) <- list(kk$x,kk$y)
dc <- melt(kk$z)
ggplot(dc,aes(x=Var1,y=Var2))+
   geom_tile(aes(fill=value))+
   geom_contour(aes(z=value),breaks=L95,colour="red")
  • kk网格执行95%级别计算(将内核计算次数减少到1)留作练习
  • 我不确定为什么stat_density2d(geom="tile")geom_tile会给出稍微不同的结果(前者是平滑的)
  • 我没有添加双变量平均值,但annotate("point",x=mean(d$x),y=mean(d$y),colour="red")之类的东西应该有用。

答案 1 :(得分:7)

不幸的是,目前接受的答案在Error: Unknown parameters: breaksggplot2 2.1.0失败了。我根据this answer中的代码拼凑了一种替代方法,该方法使用ks包来计算内核密度估计值:

library(ggplot2)

set.seed(1001)
d <- data.frame(x=rnorm(1000),y=rnorm(1000))

kd <- ks::kde(d, compute.cont=TRUE)
contour_95 <- with(kd, contourLines(x=eval.points[[1]], y=eval.points[[2]],
                                    z=estimate, levels=cont["5%"])[[1]])
contour_95 <- data.frame(contour_95)

ggplot(data=d, aes(x, y)) +
  geom_point() +
  geom_path(aes(x, y), data=contour_95) +
  theme_bw()

结果如下:

enter image description here

提示ks包依赖于rgl包,这可能是手动编译的痛苦。即使您使用Linux,也可以更轻松地获得预编译版本,例如:如果您已设置相应的CRAN存储库,则在Ubuntu上sudo apt install r-cran-rgl

答案 2 :(得分:6)

重复Ben Bolker的答案,一个可以处理多个级别并与ggplot 2.2.1一起使用的解决方案:

library(ggplot2)
library(MASS)
library(reshape2)
# create data:
set.seed(8675309)
Sigma <- matrix(c(0.1,0.3,0.3,4),2,2)
mv <- data.frame(mvrnorm(4000,c(1.5,16),Sigma))

# get the kde2d information: 
mv.kde <- kde2d(mv[,1], mv[,2], n = 400)
dx <- diff(mv.kde$x[1:2])  # lifted from emdbook::HPDregionplot()
dy <- diff(mv.kde$y[1:2])
sz <- sort(mv.kde$z)
c1 <- cumsum(sz) * dx * dy

# specify desired contour levels:
prob <- c(0.95,0.90,0.5)

# plot:
dimnames(mv.kde$z) <- list(mv.kde$x,mv.kde$y)
dc <- melt(mv.kde$z)
dc$prob <- approx(sz,1-c1,dc$value)$y
p <- ggplot(dc,aes(x=Var1,y=Var2))+
  geom_contour(aes(z=prob,color=..level..),breaks=prob)+
  geom_point(aes(x=X1,y=X2),data=mv,alpha=0.1,size=1)
print(p)

结果:

joint contour plot

答案 3 :(得分:4)

我有一个示例,MASS::kde2d()带宽规格不够灵活,所以我最终使用了ks包和ks::kde()函数,例如,{{{ 1}}用于估计更好地捕获平滑度的灵活带宽。这种计算可能有点慢,但在某些情况下它具有更好的性能。以下是该示例的上述代码的一个版本:

ks::Hscv()

对于此特定示例,差异很小,但在带宽规范需要更大灵活性的示例中,此修改可能很重要。请注意,95%轮廓是使用set.seed(1001) d <- data.frame(x=rnorm(1000),y=rnorm(1000)) getLevel <- function(x,y,prob=0.95) { kk <- MASS::kde2d(x,y) dx <- diff(kk$x[1:2]) dy <- diff(kk$y[1:2]) sz <- sort(kk$z) c1 <- cumsum(sz) * dx * dy approx(c1, sz, xout = 1 - prob)$y } L95 <- getLevel(d$x,d$y) library(ggplot2); theme_set(theme_bw()) ggplot(d,aes(x,y)) + stat_density2d(geom="tile", aes(fill = ..density..), contour = FALSE)+ stat_density2d(colour="red",breaks=L95) ## using ks::kde hscv1 <- Hscv(d) fhat <- ks::kde(d, H=hscv1, compute.cont=TRUE) dimnames(fhat[['estimate']]) <- list(fhat[["eval.points"]][[1]], fhat[["eval.points"]][[2]]) library(reshape2) aa <- melt(fhat[['estimate']]) ggplot(aa, aes(x=Var1, y=Var2)) + geom_tile(aes(fill=value)) + geom_contour(aes(z=value), breaks=fhat[["cont"]]["50%"], color="red") + geom_contour(aes(z=value), breaks=fhat[["cont"]]["5%"], color="purple") 指定的,我发现它有点反直觉,因为它在这里被称为“5%轮廓”。

答案 4 :(得分:1)

仅从上方混合答案,以更tidyverse友好的方式放置它们,并允许多个轮廓级别。我在这里geom_path(group=probs)中使用,手动添加它们geom_text。另一种方法是使用geom_path(colour=probs),它将自动将轮廓标记为图例。

library(ks)
library(tidyverse)

set.seed(1001)

## data
d <- MASS::mvrnorm(1000, c(0, 0.2), matrix(c(1, 0.4, 1, 0.4), ncol=2)) %>% 
  magrittr::set_colnames(c("x", "y")) %>% 
  as_tibble() 

## density function
kd <- ks::kde(d, compute.cont=TRUE, h=0.2)

## extract results
get_contour <- function(kd_out=kd, prob="5%") {
  contour_95 <- with(kd_out, contourLines(x=eval.points[[1]], y=eval.points[[2]],
                                      z=estimate, levels=cont[prob])[[1]])
  as_tibble(contour_95) %>% 
    mutate(prob = prob)
}

dat_out <- map_dfr(c("10%", "20%","80%", "90%"), ~get_contour(kd, .)) %>% 
  group_by(prob) %>% 
  mutate(n_val = 1:n()) %>% 
  ungroup()

## clean kde output
kd_df <- expand_grid(x=kd$eval.points[[1]], y=kd$eval.points[[2]]) %>% 
  mutate(z = c(kd$estimate %>% t))

ggplot(data=kd_df, aes(x, y)) +
  geom_tile(aes(fill=z)) +
  geom_point(data = d, alpha = I(0.4), size = I(0.4), colour = I("yellow")) +
  geom_path(aes(x, y, group = prob), 
            data=filter(dat_out, !n_val %in% 1:3), colour = I("white")) +
  geom_text(aes(label = prob), data = 
              filter(dat_out, (prob%in% c("10%", "20%","80%") & n_val==1) | (prob%in% c("90%") & n_val==20)),
            colour = I("black"), size =I(3))+
  scale_fill_viridis_c()+
  theme_bw() +
  theme(legend.position = "none")

reprex package(v0.3.0)于2019-06-25创建