ggplot boxplot - length of whiskers with logarithmic axis

痞子三分冷 提交于 2019-11-29 16:05:25
eipi10

You can have ggplot use boxplot.stats (the same function used by base boxplot) to set the y-values for the box-and-whiskers and the outliers. For example:

# 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"]])
}

Now we use those functions in stat_summary to draw the boxplot, as in the example below:

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()

Now for the log transformation issue: The plots below show, respectively, no coordinate transformation, scale_y_log10, and coord_trans(y="log10"). In addition, I've used geom_hline to add dotted lines at each of the box-and-whisker values and I've added text to show the actual values. To reduce clutter, I've removed the outlier points, and I've faded out the boxplots a bit so that the other components will show up better.

# 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')")

The three plots are shown below. Note that the last plot, using coord_trans is not flipped, because coord_trans overrides coord_flip. You can probably use something like the code in this SO answer to flip the plot, but I haven't done that here.

The first plot, with no transformations, shows the correct values.

The third plot, using coord_trans also has everything in the correct locations. Note that coord_trans is actually changing the y-coordinate system of the plot without changing the values of the plotted points. It's the space itself that's been "distorted" to a log scale.

Now, note that in the second plot, using scale_y_log10, the boxes are in the correct locations but the ends of the whiskers are in the wrong locations. On the other hand, comparison with the other two plots shows that the location of all the geom_hlines is correct. Also note that, unlike coord_trans, scale_y_log10 takes the log of the points themselves and just relabels the y-axis breaks with the unlogged values, while leaving the "space" in the which the points are plotted unchanged. You can see this by looking at the values in red text. The values in blue text are the unlogged values.

See @dww's answer for an explanation of why scale_y_log10 results only in the whisker ends being transformed incorrectly, while the box values are plotted in the right place.

dww

The problem is due to the fact that scale_y_log10 transforms the data before calculating the stats. This does not matter for the median and percentile points, because e.g. 10^log10(median) is still the median value, which will be plotted in the correct location. But it does matter for the whiskers which are calculated using 1.5*IQR, because 10^(1.5*IQR(log10(x)) is not equal to 1.5*IQR(x). So the calculation fails for the whiskers.

This error becomes evident if we compare

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

In which we see that the median and percentile ppoints are identical, but the whisker ends (1st and last elements of the stats vector) differ

This detailed and useful answer by @eipi10, shows how to calculate the stats yourself and force ggplot to use these user-defined stats rather than its internal (and incorrect) algorithm. Using this approach, it becomes relatively simple to calculate the correct statistics and use these instead.

# 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") 

Which produces the correct plot

A note on using coord_trans as an alternative approach:

Using coord_trans(y = "log10") instead of scale_y_log10, causes the stats to be calculated (correctly) on the untransformed data. However, coord_trans cannot be used in combination with coord_flip. So, this does not solve the issue of creating horizontal boxplots with a log axis. The suggestion here to use ggdraw(switch_axis_position()) from the cowplot package to flip the axes after using coord_trans did not work, but throws an error (cowplot v0.4.0 with 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!