rggplot2plotboxplot

how can I get a double y-axis (twiny) on a facet-wrap in ggplot boxplots?


I have a ggplot figure with a couple of boxplots and I want the last boxplot in each group (in my example all boxplots c) to be shown on a different y-axis on the right hand side (and preferably mark that by the color of the boxplot). The boxplots c show a different measurement which can drastically vary with regard to order of magnitude and are measured on a different unit, so it doesn't make sense for them to share the axis with a and b. (In my example I changed the definition of a boxplot with the functions o and f.)

My working example:

library(ggplot2)

a1 <- rnorm(n=100, mean=5, sd=20)
a2 <- rnorm(n=100, mean=6, sd=20)
a3 <- rnorm(n=100, mean=8, sd=20)

b <- rnorm(n=100, mean=11, sd=10)

c1 <- rnorm(n=100, mean=500, sd=80)
c2 <- rnorm(n=100, mean=600, sd=80)
c3 <- rnorm(n=100, mean=800, sd=80)

letters <- c(rep("a", 300), rep("b", 300), rep("c", 300))
type <- rep(c(rep(1,100), rep(2,100), rep(3,100)),3)

dat <- data.frame(y=c(a1,a2,a3,b,b,b,c1,c2,c3), letters, type)


f <- function(x) {
  r <- quantile(x, probs = c(0.05, 0.25, 0.5, 0.75, 0.95))
  names(r) <- c("ymin", "lower", "middle", "upper", "ymax")
  r
}
o <- function(x) {
  subset(x, x < quantile(x, 0.05) | quantile(x, 0.95) < x)
}
ggplot(dat, aes(x=letters, y=y, fill=type, group=letters)) + 
  stat_summary(fun.data = f, geom="boxplot", fill="white") +
  stat_summary(fun = o, geom="point") + facet_wrap(~type, nrow=1) +  
  guides(fill="none")

Result

What I want

Of course, the axis ranges will change once the axis isn't shared anymore and I want that, but that was too hard to draw. (It would be okay if both axes started at zero, but the maximum should vary with the data.)

To provide some context: a, b, and c represent criteria on which to judge the validity of each type. I would like the plot to be represented in this way so I can judge each option (type 1,2,3) seperately easily (therefore the letters for these are right next to each other) and then also be able to compare the different types and make a tradeoff. Ideally you will choose one type at the end based on the plot.

I only found one way for a twin y in ggplot which is a transformation of the existing y-axis. This could maybe work, but I don't know how to make it work only for the w-boxplots.


Solution

  • I wasn't entirely sure, but perhaps something like this? I've scaled the "c" data (since secondary axes in ggplot2 are just decoration) and made the secondary axis reflect the opposite transformation.

    ggplot(dat, 
           aes(x=letters,
               y = y * ifelse(letters == "c", 1/50, 1), 
               color = ifelse(letters == "c", "red", "black"),
               fill=type, group=letters)) + 
      stat_summary(fun.data = f, geom="boxplot", fill="white") +
      stat_summary(fun = o, geom="point") + facet_wrap(~type, nrow=1) +  
      scale_y_continuous(sec.axis = sec_axis(transform = ~.*50, 
                                             breaks = scales::breaks_width(30*50))) +
      scale_color_identity() +
      guides(fill="none") +
      labs(y = "y") +
      theme(axis.text.y.right = element_text(color = "red"))
    

    enter image description here

    If you want more visual detail for the "c" data, we can scale it less, but offset with a different baseline:

    ggplot(dat, 
           aes(x=letters,
               y = ifelse(letters == "c", y/5 - 120, y), 
               color = ifelse(letters == "c", "red", "black"),
               fill=type, group=letters)) + 
      stat_summary(fun.data = f, geom="boxplot", fill="white") +
      stat_summary(fun = o, geom="point") + facet_wrap(~type, nrow=1) +  
      scale_y_continuous(sec.axis = sec_axis(transform = ~(.+120)*5,
                                             breaks = (seq(-50,50,50) + 120)*5)) +
      scale_color_identity() +
      guides(fill="none") +
      labs(y = "y") +
      theme(axis.text.y.right = element_text(color = "red"))
    

    enter image description here