r - ggplot2: incorrect boxplot width when facetting with facets of different scales -
i need facetted boxplot. x-axis plots quantitative variable, , want reflect information on plot. scale of abscissa different among facets.
my problem widths of boxes small facet large scale.
a possible explanation width of boxes same facets, whereas should ideally determined xlims of each facet individually.
i grateful 2 inputs:
- do think bug , should reported ?
- do have solution ?
thanks in advance !
remark: transforming abscissa categorical variable 1 solution, not perfect result in loss of information.
minimal working example:
library(tidyverse) c(1:4,7) %>% c(.,10*.) %>% # create abscissa on 2 different scales lapply(fun = function(x) {tibble(x = x, y = rnorm(50), idx = ifelse(test = x<8, yes = 'a', no = 'b'))}) %>% # create sample (y) , label (idx) bind_rows() %>% ggplot(aes(x = x, y = y, group = x)) + geom_boxplot() + facet_wrap(~idx, scales = 'free')
result:
a cumbersome solution redraw boxplot scratch, not satisfying:
draw_boxplot = function(locations, width, ymin, lower, middle, upper, ymax, idx){ local_df = tibble(locations = locations, width = width, ymin = ymin, lower = lower, middle = middle, upper = upper, ymax = ymax, idx = idx) ggplot(data = local_df) + geom_rect(aes(xmin = locations - width/2, xmax = locations + width/2, ymin = lower, ymax = upper), fill = 'white', colour = 'black') + geom_segment(aes(x = locations - width/2, xend = locations + width/2, y = middle, yend = middle), size = 0.8) + geom_segment(aes(x = locations, xend = locations, y = upper, yend = ymax)) + geom_segment(aes(x = locations, xend = locations, y = lower, yend = ymin)) + facet_wrap(~idx, scales = 'free_x') } make_boxplot = function(to_plot){ to_plot %>% cmp_boxplot %>% (function(x){ draw_boxplot(locations = x$x, width = x$width, ymin = x$y0, lower = x$y25, middle = x$y50, upper = x$y75, ymax = x$y100, idx = x$idx) }) } cmp_boxplot = function(to_plot){ to_plot %>% group_by(idx) %>% mutate(width = 0.6*(max(x) - min(x))/length(unique(x))) %>% #hand specified width group_by(x) %>% mutate(y0 = min(y), y25 = quantile(y, 0.25), y50 = median(y), y75 = quantile(y, 0.75), y100 = max(y)) %>% select(-y) %>% unique() } c(1:4,7) %>% c(.,10*.) %>% lapply(fun = function(x) {tibble(x = x, y = rnorm(50), idx = ifelse(test = x<8, yes = 'a', no = 'b'))}) %>% bind_rows() %>% make_boxplot
result:
since geom_boxplot
doesn't allow varying width
aesthetic, have write own. fortunately it's not complicated.
bp_custom <- function(vals, type) { bp = boxplot.stats(vals) if(type == "whiskers") { y = bp$stats[1] yend = bp$stats[5] return(data.frame(y = y, yend = yend)) } if(type == "box") { ymin = bp$stats[2] ymax = bp$stats[4] return(data.frame(ymin = ymin, ymax = ymax)) } if(type == "median") { y = median(vals) yend = median(vals) return(data.frame(y = y, yend = yend)) } if(type == "outliers") { y = bp$out return(data.frame(y = y)) } else { return(warning("type must 1 of 'whiskers', 'box', 'median', or 'outliers'.")) } }
this function computation , returns dataframes suitable use in stat_summary
. call in several different layers construct various bits of boxplot. note need compute width of boxplot per group of facet, done below using dplyr
in pipe. calculated width such range of x gets split equal segments based on number of unique x values, each box gets 1/2 width of segment. data may need different adjustment.
library(dplyr) c(1:4,7) %>% c(.,10*.) %>% # create abscissa on 2 different scales lapply(fun = function(x) { tibble(x = x, y = rnorm(50), idx = ifelse(test = x<8, yes = 'a', no = 'b')) }) %>% bind_rows() %>% group_by(idx) %>% # note line mutate(width = 0.25*diff(range(x))/length(unique(x))) %>% # note line ggplot(aes(x = x, y = y, group = x)) + stat_summary(fun.data = bp_custom, fun.args = "whiskers", geom = "segment", aes(xend = x)) + stat_summary(fun.data = bp_custom, fun.args = "box", geom = "rect", aes(xmin = x - width, xmax = x + width), fill = "white", color = "black") + stat_summary(fun.data = bp_custom, fun.args = "median", geom = "segment", aes(x = x - width, xend = x + width), size = 1.5) + stat_summary(fun.data = bp_custom, fun.args = "outliers", geom = "point") + facet_wrap(~idx, scales = 'free')
as reporting bug (actually desired feature), think it's infrequent enough use case won't prioritize it. if wrap code custom geom
(based on here) , submit pull-request, might more luck.
Comments
Post a Comment