我正在尝试使用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)
但是使用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()
我得到了这个情节,其中胡须的长度是错误的(例如,参见胡须下方有多个额外的异常值,以及上面没有的异常值)。
请注意,没有对数轴,ggplot的胡须长度正确
ggplot(my.df.long, aes(x=variable, y=vals)) +
geom_boxplot() +
theme_bw() + coord_flip()
如何使用具有正确长度胡须的ggplot生成水平对数箱图?优选使晶须延伸至IQR的1.5倍。
更新
正如here所述。可以使用coord_trans(y = "log10")
代替scale_y_log10
,这将导致在转换数据之前计算的统计数据。 但是,coord_trans
不能与coord_flip
结合使用。因此,这不能解决使用对数轴创建水平箱图的问题。
答案 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_log10
和coord_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
结果仅在胡须末端被错误转换时的结果,而框值则绘制在正确的位置。
答案 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")
产生正确的情节
关于使用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
来变换坐标系而不是改变比例。