rggplot2

How can I make a discontinuous axis in R with ggplot2?


I have a dataframe (dat) with two columns 1) Month and 2) Value. I would like to highlight that the x-axis is not continuous in my boxplot by interrupting the x-axis with two angled lines on the x-axis that are empty between the angled lines.

Example Data and Boxplot

library(ggplot2)
set.seed(321)
dat <- data.frame(matrix(ncol = 2, nrow = 18))
x <- c("Month", "Value")
colnames(dat) <- x
dat$Month <- rep(c(1,2,3,10,11,12),3)
dat$Value <- rnorm(18,20,2)

ggplot(data = dat, aes(x = factor(Month), y = Value)) +
  geom_boxplot() +
  labs(x = "Month") +
  theme_bw() +
  theme(panel.grid = element_blank(),
        text = element_text(size = 16),
        axis.text.x = element_text(size = 14, color = "black"),
        axis.text.y = element_text(size = 14, color = "black"))

The ideal figure would look something like below. How can I make this discontinuous axis in ggplot?

enter image description here


Solution

  • You could make use of the extended axis guides in the ggh4x package. Alas, you won't easily be able to create the "separators" without a hack similar to the one suggested by user Zhiqiang Wang

    guide_axis_truncated accepts vectors to define lower and upper trunks. This also works for units, by the way, then you have to pass the vector inside the unit function (e.g., trunc_lower = unit(c(0,.45), "npc") !

    library(ggplot2)
    library(ggh4x)
    
    set.seed(321)
    dat <- data.frame(matrix(ncol = 2, nrow = 18))
    x <- c("Month", "Value")
    colnames(dat) <- x
    dat$Month <- rep(c(1,2,3,10,11,12),3)
    dat$Value <- rnorm(18,20,2)
    
    # this is to make it slightly more programmatic
    x1end <- 3.45
    x2start <- 3.55
    
    p <-
    ggplot(data = dat, aes(x = factor(Month), y = Value)) +
      geom_boxplot() +
      labs(x = "Month") +
      theme_classic() +
      theme(axis.line = element_line(colour = "black"))
    
    p +
      guides(x = guide_axis_truncated(
        trunc_lower = c(-Inf, x2start),
        trunc_upper = c(x1end, Inf)
      ))
    

    Created on 2021-11-01 by the reprex package (v2.0.1)

    The below is taking user Zhiqiang Wang's hack a step further. You will see I am using simple trigonometry to calculate the segment coordinates. in order to make the angle actually look as it is defined in the function, you would need to set coord_equal.

    # a simple function to help make the segments
    add_separators <- function(x, y = 0, angle = 45, length = .1){
      add_y <-  length * sin(angle * pi/180)
      add_x <- length * cos(angle * pi/180)
      ## making the list for your segments
      myseg <- list(x = x - add_x, xend = x + add_x, 
                    y = rep(y - add_y, length(x)), yend = rep(y + add_y, length(x)))
      ## this function returns an annotate layer with your segment coordinates
      annotate("segment", 
               x = myseg$x, xend = myseg$xend,
               y = myseg$y, yend = myseg$yend) 
    }
    
    # you will need to set limits for correct positioning of your separators
    # I chose 0.05 because this is the expand factor by default 
    y_sep <- min(dat$Value) -0.05*(min(dat$Value))
    
    p +
      guides(x = guide_axis_truncated(
        trunc_lower = c(-Inf, x2start),
        trunc_upper = c(x1end, Inf)
      )) +
      add_separators(x = c(x1end, x2start), y = y_sep, angle = 70) +
      # you need to set expand to 0
      scale_y_continuous(expand = c(0,0)) +
      ## to make the angle look like specified, you would need to use coord_equal()
      coord_cartesian(clip = "off", ylim = c(y_sep, NA))