ggplot boxplot - 具有对数轴的晶须长度
ggplot boxplot - length of whiskers with logarithmic axis
我正在尝试使用 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()
我得到了这个图,其中的胡须长度错误(例如,参见胡须下方和上方 none 有多少额外的异常值)。
请注意,在没有对数轴的情况下,ggplot 的胡须长度正确
ggplot(my.df.long, aes(x=variable, y=vals)) +
geom_boxplot() +
theme_bw() + coord_flip()
如何使用具有正确长度晶须的 ggplot 生成水平对数箱线图?最好是晶须延伸到 IQR 的 1.5 倍。
N.B. 如 here 所述。可以使用 coord_trans(y = "log10")
而不是 scale_y_log10
,这将导致在 转换数据之前计算统计信息 。 但是、coord_trans
不能与coord_flip
组合使用。所以这并不能解决创建带有对数轴的水平箱线图的问题。
您可以让 ggplot
使用 boxplot.stats
(base 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
。您可能可以使用类似 中的代码来翻转情节,但我在这里没有这样做。
第一个图没有转换,显示了正确的值。
第三个图,使用 coord_trans
也有正确位置的所有内容。请注意,coord_trans
实际上是在不更改绘制点的值的情况下更改绘图的 y 坐标系。正是 space 本身 "distorted" 达到了对数标度。
现在请注意,在第二个图中,使用 scale_y_log10
,方框位于正确的位置,但胡须的末端位于错误的位置。另一方面,与其他两个图的比较表明所有 geom_hline
的位置都是正确的。另请注意,与 coord_trans
不同,scale_y_log10
获取点本身的日志,并仅使用未记录的值重新标记 y 轴中断,同时将 "space" 留在其中绘制不变。您可以通过查看红色文本中的值来了解这一点。蓝色文本中的值是未记录的值。
请参阅 以了解为什么 scale_y_log10
只会导致胡须末端被错误地转换,而框值绘制在正确的位置。
问题是由于 scale_y_log10
在计算统计数据之前转换了数据。这对于中位数和百分位点无关紧要,因为例如10^log10(median)
仍然是中值,将绘制在正确的位置。但是对于使用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 相同,但胡须末端(统计向量的第一个和最后一个元素)不同
,展示了如何自己计算统计数据并强制 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
组合使用。因此,这并不能解决创建带有对数轴的水平箱线图的问题。建议 在使用 coord_trans
后使用 cowplot 包中的 ggdraw(switch_axis_position())
翻转轴没有用,但会抛出错误(cowplot v0.4.0 和 ggplot2 v2.1.0)
Error in Ops.unit(gyl$x, grid::unit(0.5, "npc")) : both operands
must be units
In addition: Warning message: axis.ticks.margin
is
deprecated. Please set margin
property of axis.text
instead
我认为如果不需要使箱线图水平,最简单的答案是变换坐标系而不是改变比例,使用 coord_trans(y = "log10")
而不是 scale_y_log10()
。
我正在尝试使用 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()
我得到了这个图,其中的胡须长度错误(例如,参见胡须下方和上方 none 有多少额外的异常值)。
请注意,在没有对数轴的情况下,ggplot 的胡须长度正确
ggplot(my.df.long, aes(x=variable, y=vals)) +
geom_boxplot() +
theme_bw() + coord_flip()
如何使用具有正确长度晶须的 ggplot 生成水平对数箱线图?最好是晶须延伸到 IQR 的 1.5 倍。
N.B. 如 here 所述。可以使用 coord_trans(y = "log10")
而不是 scale_y_log10
,这将导致在 转换数据之前计算统计信息 。 但是、coord_trans
不能与coord_flip
组合使用。所以这并不能解决创建带有对数轴的水平箱线图的问题。
您可以让 ggplot
使用 boxplot.stats
(base 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
。您可能可以使用类似
第一个图没有转换,显示了正确的值。
第三个图,使用 coord_trans
也有正确位置的所有内容。请注意,coord_trans
实际上是在不更改绘制点的值的情况下更改绘图的 y 坐标系。正是 space 本身 "distorted" 达到了对数标度。
现在请注意,在第二个图中,使用 scale_y_log10
,方框位于正确的位置,但胡须的末端位于错误的位置。另一方面,与其他两个图的比较表明所有 geom_hline
的位置都是正确的。另请注意,与 coord_trans
不同,scale_y_log10
获取点本身的日志,并仅使用未记录的值重新标记 y 轴中断,同时将 "space" 留在其中绘制不变。您可以通过查看红色文本中的值来了解这一点。蓝色文本中的值是未记录的值。
请参阅 scale_y_log10
只会导致胡须末端被错误地转换,而框值绘制在正确的位置。
问题是由于 scale_y_log10
在计算统计数据之前转换了数据。这对于中位数和百分位点无关紧要,因为例如10^log10(median)
仍然是中值,将绘制在正确的位置。但是对于使用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 相同,但胡须末端(统计向量的第一个和最后一个元素)不同
# 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
组合使用。因此,这并不能解决创建带有对数轴的水平箱线图的问题。建议 coord_trans
后使用 cowplot 包中的 ggdraw(switch_axis_position())
翻转轴没有用,但会抛出错误(cowplot v0.4.0 和 ggplot2 v2.1.0)
Error in Ops.unit(gyl$x, grid::unit(0.5, "npc")) : both operands must be units
In addition: Warning message:
axis.ticks.margin
is deprecated. Please setmargin
property ofaxis.text
instead
我认为如果不需要使箱线图水平,最简单的答案是变换坐标系而不是改变比例,使用 coord_trans(y = "log10")
而不是 scale_y_log10()
。