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:

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:

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

enter image description here

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

Popular posts from this blog

angular - Ionic slides - dynamically add slides before and after -

minify - Minimizing css files -

Add a dynamic header in angular 2 http provider -