rggplot2intervalsxticksdiscrete

How to shift ticks and labels in-between grouped boxplots with a discrete x scale using ggplot2?


Surely already asked, but I'm struggling to find a clear solution that fits my case.

How to shift ticks between grouped boxplots (rather than centered on them) so that to define hours intervals?

Initial data:
By default, hours are centered on grouped boxplots

Desired output:
Hours positionned in-between grouped boxplots, i.e. to define hours intervals

Data:

dat0 <- 
structure(list(group = c("A", "B", "A", "B", "A", "B", "A", "B", 
"A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", 
"B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", 
"A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", 
"B"), hour = structure(c(1L, 1L, 2L, 2L, 3L, 3L, 4L, 4L, 5L, 
5L, 6L, 6L, 7L, 7L, 8L, 8L, 9L, 9L, 10L, 10L, 11L, 11L, 12L, 
12L, 13L, 13L, 14L, 14L, 15L, 15L, 16L, 16L, 17L, 17L, 18L, 18L, 
19L, 19L, 20L, 20L, 21L, 21L, 22L, 22L, 23L, 23L, 24L, 24L), levels = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", 
"14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"
), class = "factor"), q05 = c(38, 42, 38, 42, 34, 42, 35, 40, 
39, 40, 42, 49, 50, 56, 53, 61, 55, 59, 50, 58, 50, 58, 47, 55, 
46, 54, 48, 54, 48, 50, 46, 50, 46, 49, 44, 46, 44, 48, 41, 45, 
42, 44, 39, 45, 38, 42, 38, 41), q25 = c(48, 55, 47, 53, 44, 
53, 43, 51, 48, 53, 54, 64, 60, 74, 80, 96, 72, 81, 64, 77, 63, 
78, 61, 77, 60, 75, 62, 73, 60, 65, 58, 63, 59, 63, 55, 61, 53, 
61, 51, 55, 51, 59, 47, 58, 46, 54, 46, 53), q50 = c(56, 67, 
55, 66, 50, 65, 51, 63, 58, 65, 68, 84, 71, 94, 122, 135, 93, 
102, 81, 97, 80, 99, 80, 98, 75, 96, 82, 95, 74, 84, 72, 82, 
70, 79, 69, 74, 64, 76, 62, 70, 62, 76, 56, 71, 56, 69, 55, 66
), q75 = c(74, 88, 70, 88, 60, 83, 65, 84, 84, 88, 101, 129, 
93, 125, 167, 184, 131, 139, 117, 126, 111, 134, 112, 132, 124, 
139, 115, 128, 99, 112, 98, 113, 90, 105, 92, 100, 84, 102, 95, 
120, 86, 100, 73, 87, 76, 90, 70, 84), q95 = c(147, 135, 172, 
150, 124, 144, 136, 165, 320, 189, 265, 256, 239, 249, 268, 307, 
215, 262, 201, 215, 200, 216, 201, 233, 225, 237, 176, 233, 193, 
205, 165, 186, 170, 194, 160, 170, 222, 223, 203, 210, 166, 162, 
178, 152, 142, 167, 145, 129)), row.names = c(NA, -48L), class = "data.frame")    

# plot
ggplot(dat0, aes(x = hour)) +
  theme(
    legend.position="top",
    legend.title = element_blank(),
    panel.grid.major = element_blank()
  ) +
  geom_boxplot(
    aes(y = q50,
        fill = factor(group, levels = c('A', 'B')),
        ymin = q05, lower = q25, middle = q50, upper = q75, ymax = q95),
    stat = "identity",
    width = 0.8,
    position = position_dodge2(width = 1)
  )

Thanks for help


Solution

  • I got the following result by changing your hour column to be numeric.

    library(tidyverse)
    dat0 <- 
      structure(list(group = c("A", "B", "A", "B", "A", "B", "A", "B", 
                               "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", 
                               "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", 
                               "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", 
                               "B"), 
                     hour = structure(c(1L, 1L, 2L, 2L, 3L, 3L, 4L, 4L, 5L, 
                                        5L, 6L, 6L, 7L, 7L, 8L, 8L, 9L, 9L, 10L, 10L, 11L, 11L, 12L, 
                                        12L, 13L, 13L, 14L, 14L, 15L, 15L, 16L, 16L, 17L, 17L, 18L, 18L, 
                                        19L, 19L, 20L, 20L, 21L, 21L, 22L, 22L, 23L, 23L, 24L, 24L), levels = c("1", 
                                                                                                                "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", 
                                                                                                                "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"
                                        ), class = "factor"), 
                     q05 = c(38, 42, 38, 42, 34, 42, 35, 40, 
                             39, 40, 42, 49, 50, 56, 53, 61, 55, 59, 50, 58, 50, 58, 47, 55, 
                             46, 54, 48, 54, 48, 50, 46, 50, 46, 49, 44, 46, 44, 48, 41, 45, 
                             42, 44, 39, 45, 38, 42, 38, 41), 
                     q25 = c(48, 55, 47, 53, 44, 
                             53, 43, 51, 48, 53, 54, 64, 60, 74, 80, 96, 72, 81, 64, 77, 63, 
                             78, 61, 77, 60, 75, 62, 73, 60, 65, 58, 63, 59, 63, 55, 61, 53, 
                             61, 51, 55, 51, 59, 47, 58, 46, 54, 46, 53), 
                     q50 = c(56, 67, 55, 66, 50, 65, 51, 63, 58, 65, 68, 84, 71, 94, 122, 135, 93, 
                             102, 81, 97, 80, 99, 80, 98, 75, 96, 82, 95, 74, 84, 72, 82, 
                             70, 79, 69, 74, 64, 76, 62, 70, 62, 76, 56, 71, 56, 69, 55, 66
                     ), 
                     q75 = c(74, 88, 70, 88, 60, 83, 65, 84, 84, 88, 101, 129, 
                             93, 125, 167, 184, 131, 139, 117, 126, 111, 134, 112, 132, 124, 
                             139, 115, 128, 99, 112, 98, 113, 90, 105, 92, 100, 84, 102, 95, 
                             120, 86, 100, 73, 87, 76, 90, 70, 84), 
                     q95 = c(147, 135, 172, 150, 124, 144, 136, 165, 320, 189, 265, 256, 239, 249, 268, 307, 
                             215, 262, 201, 215, 200, 216, 201, 233, 225, 237, 176, 233, 193, 
                             205, 165, 186, 170, 194, 160, 170, 222, 223, 203, 210, 166, 162, 
                             178, 152, 142, 167, 145, 129)), row.names = c(NA, -48L), class = "data.frame")    
    
    dat0 <- dat0 |> mutate(hour = as.numeric(hour))
    # plot
    ggplot(dat0, aes(x = hour)) +
      theme(
        legend.position="top",
        legend.title = element_blank(),
        panel.grid.major = element_blank()
      ) +
      geom_boxplot(
        aes(y = q50, group = interaction(hour, group),
            fill = group,
            ymin = q05, lower = q25, middle = q50, upper = q75, ymax = q95),
        stat = "identity",
        width = 0.8,
        position = position_dodge2(width = 1)
      ) +
      scale_x_continuous(breaks = (1:25)-0.5, labels = 0:24)
    

    Boxplot

    Created on 2024-11-23 with reprex v2.1.1