ggplot boxplot - 对数轴

时间:2016-08-03 21:05:50

标签: r ggplot2 boxplot

我正在尝试使用ggplot2创建一个带对数轴的水平箱图。但是,胡须的长度是错误的。

最小可重复的例子:

一些数据

library(ggplot2)
library(reshape2)
set.seed(1234)
my.df <- data.frame(a = rnorm(1000,150,50), b = rnorm(1000,500,150))
my.df$a[which(my.df$a < 5)] <- 5
my.df$b[which(my.df$b < 5)] <- 5

如果我使用基础R boxplot()来绘制它,一切都很好

boxplot(my.df, log="x", horizontal=T)

enter image description here

但是使用ggplot,

my.df.long <- melt(my.df, value.name = "vals")
ggplot(my.df.long, aes(x=variable, y=vals)) +
  geom_boxplot() +
  scale_y_log10(breaks=c(5,10,20,50,100,200,500,1000), limits=c(5,1000)) +
  theme_bw() + coord_flip()

我得到了这个情节,其中胡须的长度是错误的(例如,参见胡须下方有多个额外的异常值,以及上面没有的异常值)。

enter image description here

请注意,没有对数轴,ggplot的胡须长度正确

ggplot(my.df.long, aes(x=variable, y=vals)) +
  geom_boxplot() +
  theme_bw() + coord_flip()

enter image description here

如何使用具有正确长度胡须的ggplot生成水平对数箱图?优选使晶须延伸至IQR的1.5倍。

更新

正如here所述。可以使用coord_trans(y = "log10")代替scale_y_log10,这将导致在转换数据之前计算的统计数据。 但是coord_trans不能与coord_flip结合使用。因此,这不能解决使用对数轴创建水平箱图的问题。

3 个答案:

答案 0 :(得分:2)

您可以ggplot使用boxplot.stats(基础boxplot使用的相同功能)来设置盒子和胡须以及异常值的y值。例如:

# Function to use boxplot.stats to set the box-and-whisker locations  
mybxp = function(x) {
  bxp = boxplot.stats(x)[["stats"]]
  names(bxp) = c("ymin","lower", "middle","upper","ymax")
  return(bxp)
}  

# Function to use boxplot.stats for the outliers
myout = function(x) {
  data.frame(y=boxplot.stats(x)[["out"]])
}

现在我们在stat_summary中使用这些函数来绘制箱线图,如下例所示:

ggplot(my.df.long, aes(x=variable, y=vals)) +
  stat_summary(fun.data=mybxp, geom="boxplot") +
  stat_summary(fun.data=myout, geom="point") +
  theme_bw() + coord_flip()

现在针对日志转换问题:下面的图分别显示没有坐标转换,scale_y_log10coord_trans(y="log10")。此外,我已使用geom_hline在每个盒须值上添加虚线,并添加了文本以显示实际值。为了减少混乱,我删除了异常点,并且我略微淡化了箱形图,以便其他组件更好地显示出来。

# Set up common plot elements
p = ggplot(my.df.long, aes(x=variable, y=vals)) +
  geom_hline(yintercept=mybxp(my.df$a), colour="red", lty="11", size=0.3) +
  geom_hline(yintercept=mybxp(my.df$b), colour="blue", lty="11", size=0.3) +
  stat_summary(fun.data=mybxp, geom="boxplot", colour="#000000A0", fatten=0.5) +
  #stat_summary(fun.data=myout, geom="point") +
  theme_bw() + coord_flip()

br = c(5,10,20,50,100,200,500,1000)

## Create plots

# Without log transformation
p1 = p + scale_y_continuous(breaks=br, limits=c(5,1000)) + 
  stat_summary(fun.y=mybxp, aes(label=round(..y..)), geom="text", size=3, colour="red") +
  ggtitle("No Transformation")

# With scale_y_log10
p2 = p + scale_y_log10(breaks=br, limits=c(5,1000)) + ggtitle("scale_y_log10") +
  stat_summary(fun.y=mybxp, aes(label=round(..y..,2)), geom="text", size=3, colour="red") +
  stat_summary(fun.y=mybxp, aes(label=round(10^(..y..))), geom="text", size=3, 
               colour="blue", position=position_nudge(x=0.3)) 

# With coord_trans
p3 = p + scale_y_continuous(breaks=br, limits=c(5,1000)) +
  stat_summary(fun.y=mybxp, aes(label=round(..y..)), geom="text", size=3, colour="red") +
  coord_trans(y="log10") + ggtitle("coord_trans(y='log 10')")

三个图如下所示。请注意,使用coord_trans的最后一个图不会被翻转,因为coord_trans会覆盖coord_flip。您可以使用this SO answer中的代码来翻转图表,但我还没有在这里完成。

没有变换的第一个图显示正确的值。

使用coord_trans的第三个图也将所有内容都放在正确的位置。请注意,coord_trans实际上是在不更改绘制点的值的情况下更改绘图的y坐标系。它的空间本身已被扭曲&#34;到对数刻度。

现在,请注意,在第二个图中,使用scale_y_log10,框位于正确的位置,但是胡须的末端位于错误的位置。另一方面,与其他两个图的比较表明,所有geom_hline的位置都是正确的。另请注意,与coord_trans不同,scale_y_log10获取点本身的日志,只是重新标记y轴中断与未记录的值,同时保留&#34;空格&#34;其中点数不变。你可以通过查看红色文本中的值来看到这一点。蓝色文本中的值是未记录的值。

请参阅@dww's answer,了解为什么scale_y_log10结果仅在胡须末端被错误转换时的结果,而框值则绘制在正确的位置。

enter image description here

答案 1 :(得分:1)

问题是由于scale_y_log10在计算统计数据之前转换数据。这与中位数和百分点无关,因为例如10 ^ log10(中位数)仍然是中值,它将被绘制在正确的位置。但确实对于使用1.5 * IQR计算的晶须很重要,因为10 ^(1.5 * IQR(log10(x))不等于1.5 * IQR(x)。因此计算失败为胡须。

如果我们比较

,这个错误就会变得很明显
boxplot.stats(my.df$b)$stats
# [1] 117.4978 407.3983 502.0460 601.2937 873.0992
10^boxplot.stats(log10(my.df$b))$stats
# [1] 231.1603 407.3983 502.0459 601.2935 975.1906

我们在其中看到中位数和百分位数ppoints是相同的,但是晶须结束(统计数据向量的第一个和最后一个元素)不同

This detailed and useful answer by @eipi10,显示了如何自己计算统计数据并强制ggplot使用这些用户定义的统计数据而不是内部(和不正确的)算法。使用这种方法,计算正确的统计数据并使用它们变得相对简单。

# Function to use boxplot.stats to set the box-and-whisker locations  
mybxp = function(x) {
  bxp = log10(boxplot.stats(10^x)[["stats"]])
  names(bxp) = c("ymin","lower", "middle","upper","ymax")
  return(bxp)
}  

# Function to use boxplot.stats for the outliers
myout = function(x) {
  data.frame(y=log10(boxplot.stats(10^x)[["out"]]))
}

ggplot(my.df.long, aes(x=variable, y=vals)) + theme_bw() + coord_flip() +
  scale_y_log10(breaks=c(5,10,20,50,100,200,500,1000), limits=c(5,1000)) + 
  stat_summary(fun.data=mybxp, geom="boxplot") +
  stat_summary(fun.data=myout, geom="point") 

产生正确的情节

enter image description here

关于使用coord_trans作为替代方法的说明:

使用coord_trans(y = "log10")代替scale_y_log10会导致在未转换的数据上(正确地)计算统计数据。 但是coord_trans不能与coord_flip结合使用。因此,这不能解决使用对数轴创建水平箱图的问题。在使用ggdraw(switch_axis_position())之后使用cowplot包中的coord_trans来翻转轴的建议here不起作用,但会抛出错误(cowplot v0.4.0 with ggplot2 v2.1.0)

  

Ops.unit中的错误(gyl $ x,grid :: unit(0.5,“npc”)):两个操作数   必须是单位

     

另外:警告消息:axis.ticks.margin是   弃用。请设置margin的{​​{1}}属性

答案 2 :(得分:1)

我认为,如果不需要使箱形图水平,那么最简单的答案是使用坐标10 part number代替10 description columns来变换坐标系而不是改变比例。