【问题标题】:How to mimic geom_boxplot() with outliers using geom_boxplot(stat = "identity")如何使用 geom_boxplot(stat = "identity") 模拟带有异常值的 geom_boxplot()
【发布时间】:2015-07-16 17:04:33
【问题描述】:

我想预先计算按变量汇总的数据(使用plyr 并传递quantile 函数),然后使用geom_boxplot(stat = "identity") 进行绘图。这很好用,除了它 (a) 不会将异常值绘制为点,并且 (b) 将“胡须”扩展到正在绘制的数据的最大值和最小值。

例子:

library(plyr)
library(ggplot2)

set.seed(4)
df <- data.frame(fact = sample(letters[1:2], 12, replace = TRUE),
                 val  = c(1:10, 100, 101))
df
#    fact val
# 1     b   1
# 2     a   2
# 3     a   3
# 4     a   4
# 5     b   5
# 6     a   6
# 7     b   7
# 8     b   8
# 9     b   9
# 10    a  10
# 11    b 100
# 12    a 101

by.fact.df <- ddply(df, c("fact"), function(x) quantile(x$val))

by.fact.df
#   fact 0%  25% 50%  75% 100%
# 1    a  2 3.25 5.0 9.00  101
# 2    b  1 5.50 7.5 8.75  100

# What I can do...with faults (a) and (b) above
ggplot(by.fact.df, 
       aes(x = fact, ymin = `0%`, lower = `25%`, middle = `50%`, 
           upper = `75%`,  ymax = `100%`)) +
  geom_boxplot(stat = "identity")

# What I want...
ggplot(df, aes(x = fact, y = val)) +
  geom_boxplot()

我能做什么......上面提到的错误(a)和(b):

我想获得什么,但仍然通过plyr(或其他方法)利用预计算:

初步想法:也许有一些方法可以预先计算出没有异常值的胡须的真实端点?然后,对异常值的数据进行子集化并将它们作为geom_point()?

动机:在处理更大的数据集时,我发现利用 plyrdplyr 和/或 data.table 来预先计算统计数据,然后绘制它们而不是让ggplot2 进行计算。

更新

我可以使用dplyrplyr 代码的以下组合提取我需要的内容,但我不确定这是否是最有效的方法:

df %>%
  group_by(fact) %>%
  do(ldply(boxplot.stats(.$val), data.frame))

Source: local data frame [6 x 3]
Groups: fact

  fact   .id X..i..
1    a stats      2
2    a stats      4
3    a stats     10
4    a stats     13
5    a stats     16
6    a     n      9

【问题讨论】:

  • 使用 dplyr(比 plyr 快)并查找 ?boxplot.stats 并将其合并到您的函数中
  • @infominer 这很有帮助,关于如何处理boxplot.stats 输出异常值的任何想法?也就是说,每个因素都可能有不同长度的异常值?
  • 只是写下我的答案! @shadow 的回答很好,它负责 boxplot 的 gggplot 实现

标签: r ggplot2 plyr dplyr


【解决方案1】:

这是我的答案,使用内置函数 quantileboxplot.stats

geom_boxplot 对箱线图的计算与boxplot.stats 略有不同。阅读?geom_boxplot?boxplot.stats 以了解我在下面的实现

#Function to calculate boxplot stats to match ggplot's implemention as in geom_boxplot.
my_boxplot.stats <-function(x){
        quantiles <-quantile(x, c(0, 0.25, 0.5, 0.75, 1))
        labels <-names(quantile(x))
        #replacing the upper whisker to geom_boxplot
        quantiles[5] <-boxplot.stats(x)$stats[5]
        res <-data.frame(rbind(quantiles))
        names(res) <-labels
        res$out <-boxplot.stats(x)$out
        return(res)
    }

计算统计数据并绘制它的代码

library(dplyr)
df %>% group_by(fact) %>% do(my_boxplot.stats(.$val)) %>% 
      ggplot(aes(x=fact, y=out, ymin = `0%`, lower = `25%`, middle = `50%`,
                 upper = `75%`,  ymax = `100%`)) +
      geom_boxplot(stat = "identity") + geom_point()

【讨论】:

  • 这是一个比我更好的答案。但现在,这只适用于每组只有一个异常值。如果您有多个异常值,您可以在添加异常值之前将结果放入列表res &lt;- as.list(quantiles)。然后你转换成data.frame。如果您有很多异常值,实际上可能会更好地计算带有主要统计数据的 data.frame 和带有异常值的单独的 data.frame
【解决方案2】:

要获得正确的统计数据,您必须进行更多计算,而不仅仅是查找分位数。带有stat = "identity"geom_boxplot 函数不会绘制异常值。所以你必须计算没有异常值的统计数据,然后使用geom_point单独绘制异常值。以下函数(基本上是stat_boxplot 的简化版本)可能不是最有效的,但它给出了预期的结果:

box.df <- df %>% group_by(fact) %>% do({
  stats <- as.numeric(quantile(.$val, c(0, 0.25, 0.5, 0.75, 1)))
  iqr <- diff(stats[c(2, 4)])
  coef <- 1.5
  outliers <- .$val < (stats[2] - coef * iqr) | .$val > (stats[4] + coef * iqr)
  if (any(outliers)) {
    stats[c(1, 5)] <- range(c(stats[2:4], .$val[!outliers]), na.rm=TRUE)
  }
  outlier_values = .$val[outliers]
  if (length(outlier_values) == 0) outlier_values <- NA_real_
  res <- as.list(t(stats))
  names(res) <- c("lower.whisker", "lower.hinge", "median", "upper.hinge", "upper.whisker")
  res$out <- outlier_values
  as.data.frame(res)
})
box.df
## Source: local data frame [2 x 7]
## Groups: fact
## 
##   fact lower.whisker lower.hinge median upper.hinge upper.whisker out
## 1    a             2        3.25    5.0        9.00            10 101
## 2    b             1        5.50    7.5        8.75             9 100

ggplot(box.df, aes(x = fact, y = out, middle = median,
                   ymin = lower.whisker, ymax = upper.whisker,
                   lower = lower.hinge, upper = upper.hinge)) +
  geom_boxplot(stat = "identity") + 
  geom_point()

【讨论】:

  • 谢谢@shadow +1 - 我讨厌重新发明轮子 - 也许有办法利用 boxplot.stats - 查看我的更新和 @infominer 的评论。
猜你喜欢
  • 2023-03-31
  • 2018-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-09
  • 2015-08-03
  • 2018-06-29
  • 1970-01-01
相关资源
最近更新 更多