Search code examples
ggplot2labelboxplotggpubr

How to modify ggboxplot (ggpubr) to suppress whiskers, but retain access to other boxplot customisations?


Originally I asked this question about suppressing the whiskers on a boxplot made by ggboxplot. (The expected way of setting a geom_boxplot option was not available.) A nice solution appeared which suited the original question. However, the broader question to address is how to suppress whiskers on the boxplot but still retain access to the nice additions in ggpubr, such as being able to automatically compute statistical test results and place these on a boxplot.

I tinkered with the solution from @Julian_Hn to get something like what I want. There are two issues that someone more knowledgeable might be able to help with, now that I've asked the broader question:

  1. Are there ways to make the solution more efficient?
  2. How can I add in the ability to change the range of x-values? (I tried various methods using ggpar and coord_cartesian, with no effect. I might be lacking knowledge of how to use commands like ggplot_build effectively.)

Here's an example where I suppress whiskers and use stat_kruskal_test to label the boxplot:

ggboxplot_whisker_opt <- function(...)
{
  opts <- list(...) # Modification of original question solution to include the original labelled ggboxplot with whiskers and stat info added
  # Check if user specified a whiskers arg and set options accordingly
  if("whisker" %in% names(opts))
  {
    whisk <- opts$whisker
    opts$whisker <- NULL
  } else {
    whisk <- TRUE
  }
  # Additional arguments that might need generalising so that other statistical tests can be used in other applications
  if ("kruskal" %in% names(opts))
  { kruskal<-opts$kruskal
    opts$kruskal <- NULL
    opt.group <- opts$kruskal.options[[1]] 
    opt.label<- opts$kruskal.options[[2]]
    opt.y <- opts$kruskal.options[[3]]
    opt.x <- opts$kruskal.options[[4]]
    opts$kruskal.options <- NULL
    }
  pl <- do.call(ggboxplot,opts) # create plot by calling ggboxplot with all user options 
  if (kruskal){ pl <- pl + stat_kruskal_test(group.by=opt.group,label=opt.label, label.y.npc=opt.y,label.x.npc=opt.x) } 
  if(!whisk)
  { pl_list <- ggplot_build(pl) # get listed version of ggboxplot object to modify
  pl_list$data[[1]]$ymin <- NA # remove the ymin/max that specify the whiskers
  pl_list$data[[1]]$ymax <- NA

  pl <- ggplot_gtable(pl_list) # convert back to ggplot object
  }
  #  return
  pl
}

Here's the application:

set.seed(123)
x <-rnorm(100)
labels <- round(runif(100,1,2))
df <- data.frame(labels=labels, value=x)

# Define the options for the stat_kruskal_test label
KO <- list("group"="labels","label"="as_detailed_italic", "label.y.npc"=0.5,"label.x.npc"=0.5,ylim=c(-1.2, 1.2))

# call the function
output.plot <- ggboxplot_whisker_opt(df, "labels","value", col="labels", legend="none", whisker=FALSE,add=c("mean"),  orientation="horizontal" kruskal=TRUE,kruskal.options=KO) 
 
# Plot the result
 plot(output.plot)

Solution

  • the issue with modifying was that the returned object was not a ggplot object anymore (wrong comment on my side) but a plot object. I have thought about it and instead of modifying the ggbuilt object, it's also possible to directly pass the coef=0 through to the geom_boxplot layer inside the object returned by ggboxplot:

    
    ggboxplot_whisker_opt <- function(...)
    {
      opts <- list(...)
      # check if user specified a whiskers argument and set options accordingly
      if("whisker" %in% names(opts))
      {
        whisk <- opts$whisker
        opts$whisker <- NULL
      } else {
        whisk <- TRUE
      }
      pl <- do.call(ggpubr::ggboxplot, opts) # create plot by calling ggboxplot with all user options
      
      if(!whisk)
      {
        pl$layers[[1]]$stat_params$coef <- 0 # modify coef param of geom_boxplot layer
      }
      # plot the ggplot and return so other ggplot parts can be added via `+`
      pl
    }
    

    This function now returns an object compatible with ggpar or adding other ggplot modifiers via +

    library(ggplot2)
    library(ggpubr)
    set.seed(123)
    x <- rnorm(100)
    labels <- round(runif(100,1,2))
    df <- data.frame(labels=labels,
                     value=x)
    
    testplot <- ggboxplot_whisker_opt(df,"labels","value",whisker=FALSE)
    ggpar(testplot,xlim=c(0.5,1.5),
          ylim=c(-0.5,0.5))
    

    enter image description here

    testplot + 
      geom_line(data=data.frame(x=c(1,2),y=c(0,0)),aes(x=x,y=y),color="red",lwd=2)
    

    enter image description here