Custom minor ticks in ggplots2

47 views Asked by At

I´m trying to add custom minor ticks to the y-axis of some graphs and I can´t find out how to do it.

Considering the min and max value of each variable, if the difference between two consecutive values of the y-axis is 1, don´t draw anything. If the differences es >2, draw a minor tick (mt) each (this is: e.g. 32, mt, 34, mt, 36.. or, e.g., 30, mt, mt, mt, mt, 35).

I have written the following function

# Definir las marcas menores para el eje y
minor_breaks <- function(data) {
   min_val <- min(data)
   max_val <- max(data)
   breaks <- seq(min_val, max_val, by = 1)
   if (length(breaks) > 2) {
      breaks <- breaks[seq(1, length(breaks), by = 2)]
   }
   return(breaks)
}

A reproducible example of my data frame

stackoverflow <- structure(list(fecha = c(1990, 1991, 1992, 1993, 1994, 1995, 
1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 
2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 
2018, 2019, 2020, 2021), `Trachinus draco` = c(26, 25, 25, 24, 
26, 26, 27, 28, 28, 27, 27, 26, 25, 27, 26, 26, 26, 26, 27, 29, 
27, 26, 26, 27, 25, 26, 27, 27, 27, 26, 27, 28), `Trachurus trachurus` = c(32, 
35, 34, 31, 20, 32, 31, 33, 32, 31, 31, 30, 33, 31, 29, 29, 31, 
30, 26, 25, 31, 29, 14, 20, 25, 25, 26, 28, 23, 22, 25, 32), 
    `Chelidonichthys lucerna` = c(44, 41, 38, 39, 53, 64, 39, 
    46, 61, 47, 46, 37, 37, 39, 39, 37, 44, 52, 45, 40, 68, 44, 
    38, 42, 49, 41, 46, 47, 57, 49, 63, 61), `Trisopterus luscus` = c(34, 
    29, 27, 35, 27, 28, 30, 29, 27, 22, 28, 28, 28, 29, 30, 28, 
    30, 30, 27, 33, 28, 29, 27, 27, 32, 16, 29, 30, 31, 27, 29, 
    30), `Trisopterus minutus` = c(22, 12, 18, 20, 20, 18, 19, 
    22, 20, 20, 20, 20, 21, 20, 17, 20, 21, 22, 19, 11, 18, 22, 
    22, 21, 21, 19, 19, 21, 22, 19, 22, 23), `Zeus faber` = c(44, 
    54, 54, 41, 51, 45, 50, 33, 42, 50, 50, 50, 49, 53, 53, 53, 
    51, 53, 47, 47, 46, 41, 33, 46, 48, 47, 43, 44, 54, 48, 47, 
    48), `Buglossidium luteum` = c(11, 11, 11, 11, 11, 11, 12, 
    12, 12, 13, 13, 13, 12, 13, 13, 13, 13, 13, 12, 12, 13, 12, 
    12, 13, 13, 12, 12, 12, 12, 11, 11, 11), `Lesueurigobius friesii` = c(7, 
    7, 7, 8, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 
    7, 6, 6, 6, 7, 7, 7, 6, 7, 7, 7, 6), `Serranus cabrilla` = c(21, 
    20, 20, 22, 23, 16, 17, 24, 21, 20, 22, 23, 22, 23, 25, 24, 
    23, 22, 25, 23, 22, 22, 21, 21, 22, 24, 21, 22, 22, 22, 23, 
    22)), row.names = c(NA, -32L), class = "data.frame")

And here it is the code I´m using to try to get the figures. The scale_y_continuos doesn´t work as expected

spp <- colnames(stackoverflow[, -1])

# Crear gráficas
for (i in 1:length(spp)) {
   
   p <- ggplot(stackoverflow, aes(x = fecha, y = stackoverflow[, i + 1])) + #evitamos con +1 el año
      geom_point(shape = 23, fill = "darkgreen", color = "black", size = 1.4) +
      geom_line(color = "grey11", linetype = "dashed") +
      geom_smooth(method = "lm", se = FALSE, color = color, show.legend = TRUE) +
      theme_bw() +
      labs(title = spp[i], 
           x = "Año",
           y = "P95 (cm)",
           face = "bold") +
      theme(plot.title = element_text(face = "italic", hjust = 0.5)) + 
      scale_x_continuous(breaks = seq(1990, 2021, by = 1),
                         labels = c(1990, rep("", 4), 1995, rep("", 4), 2000,
                                    rep("", 4), 2005, rep("", 4), 2010, 
                                    rep("", 4), 2015, rep("", 4), 2020,
                                    rep("", 1)))
   p <- p + scale_y_continuous(minor_breaks = minor_breaks())
     print(p)
}    

Any help will be more than welcome.

Thanks. Regards

Update

@Allan Cameron, I hope this makes my question clearer.

This graph is correct: when y-axis values are consecutive, no minor ticks

enter image description here

This is the graph I get with my code (no minor ticks between major ticks) enter image description here

Desired graph (minor ticks between two major ticks) enter image description here

Please, note that major ticks are whole numbers. I have rounded values to zero to avoid to work with decimals numbers.

1

There are 1 answers

8
Allan Cameron On

It may be best to store the output of your current loop as a list rather than printing each iteration. This gives you the freedom to print or manipulate each plot individually. The recommended way to pass different columns to aes is to use the .data pronoun rather than a subset of the original data frame.

That aside, your minor_breaks function doesn't work here because it should output a sequence of numeric values based on the data argument, which you are not passing. The result of passing the appropriate values looks like this:

library(ggplot2)
library(patchwork)

plots <- lapply(colnames(stackoverflow[, -1]), function(sp) {
  
  ggplot(stackoverflow, aes(x = fecha, y = .data[[sp]])) +
      geom_point(shape = 23, fill = "darkgreen", color = "black", size = 1.4) +
      geom_line(color = "grey11", linetype = "dashed") +
      geom_smooth(method = "lm", se = FALSE, color = 'black', 
                  show.legend = TRUE) +
      theme_bw() +
      labs(title = sp, x = "Año", y = "P95 (cm)", face = "bold") +
      theme(plot.title = element_text(face = "italic", hjust = 0.5)) + 
      scale_x_continuous(breaks = seq(1990, 2021, by = 1),
                         labels = c(1990, rep("", 4), 1995, rep("", 4), 2000,
                                    rep("", 4), 2005, rep("", 4), 2010, 
                                    rep("", 4), 2015, rep("", 4), 2020,
                                    rep("", 1))) + 
      scale_y_continuous(minor_breaks = minor_breaks(stackoverflow[[sp]]))
})

wrap_plots(plots, ncol = 3, nrow = 3)

enter image description here

However, I don't think your function does what you expect it to do. If we look at the help file for scale_y_continuous, we will see that minor_breaks should be one of:

  • NULL for no minor breaks
  • waiver() for the default breaks (one minor break between each major break)
  • A numeric vector of positions
  • A function that given the limits returns a vector of minor breaks. Also accepts rlang lambda function notation. When the function has two arguments, it will be given the limits and major breaks.

Currently your method relies on producing a numeric version of positions, but it would be better to use the last method here, a function which takes the limits and major breaks as arguments and outputs the correct values based on them. That, we we get the minor ticks you have described in your question:

minor_breaks <- function(limits, major_breaks) {
  delta <- mean(diff(major_breaks))
  if(delta <= 1) return(major_breaks) else seq(min(limits), max(limits), 1)
}

plots <- lapply(colnames(stackoverflow[, -1]), function(sp) {
  
  ggplot(stackoverflow, aes(x = fecha, y = .data[[sp]])) +
      geom_point(shape = 23, fill = "darkgreen", color = "black", size = 1.4) +
      geom_line(color = "grey11", linetype = "dashed") +
      geom_smooth(method = "lm", se = FALSE, color = 'black', 
                  show.legend = TRUE) +
      theme_bw() +
      labs(title = sp, x = "Año", y = "P95 (cm)", face = "bold") +
      theme(plot.title = element_text(face = "italic", hjust = 0.5)) + 
      scale_x_continuous(breaks = seq(1990, 2021, by = 1),
                         labels = c(1990, rep("", 4), 1995, rep("", 4), 2000,
                                    rep("", 4), 2005, rep("", 4), 2010, 
                                    rep("", 4), 2015, rep("", 4), 2020,
                                    rep("", 1))) + 
      scale_y_continuous(minor_breaks = minor_breaks)
})

wrap_plots(plots, ncol = 3, nrow = 3)

enter image description here