3
votes

This question has been raised a number of times on StackOverflow over the years (see here and here), however I'm yet to come across a way that I'm satisfied with for easily adding unlabelled minor ticks to my ggplot axes.

Let's generate some dummy data to play around with:

df <- data.frame(x = rnorm(1000, mean = 25, sd = 5),
            y = rnorm(1000, mean = 23, sd = 3))

There are two methods I've come across for adding unlabelled minor ticks.

Method 1 - Manually construct axis label vectors

Concatenate the values that you would like to appear at major ticks with empty spaces defined using "". If you would like to add just one unlabelled minor tick in-between major tick values, you can construct the vector of axis labels like so:

axis_values <- c(0, "", 10, "", 20, "", 30, "", 40, "", 50)

Or if you'd like n unlabelled minor ticks:

# Where n = 2 and for an axis range [0, 50]

axis_values <- c(0, rep("", 2), 15, rep("", 2), 30, rep("", 2), 45, "")

The user can then supply this vector to the 'labels' argument in the ggplot2::scale_x_continuous or ggplot2::scale_y_continuous functions as long as the length of the vector of labels matches the length of the vector supplied to the 'breaks' argument in the same functions.

ggplot(df, aes(x = x, y = y)) +
  geom_point() +
  scale_x_continuous(breaks = seq(0, 50, 5), labels = axis_values, limits = c(0, 50)) +
  scale_y_continuous(breaks = seq(0, 50, 5), labels = axis_values, limits = c(0, 50))

Method 2 - Define your own function for generating axis label vectors

This post describes a function to which the user can supply a vector of values to appear at major ticks, along with the number of unlabelled minor ticks desired:

insert_minor <- function(major_labs, n_minor) {
  labs <- c( sapply( major_labs, function(x) c(x, rep("", n_minor) ) ) )
  labs[1:(length(labs)-n_minor)]
}

# Generate plot

ggplot(df, aes(x = x, y = y)) +
  geom_point() +
  scale_x_continuous(breaks = seq(0, 50, 5), labels = insert_minor(major_labs = seq(0, 50, 10),
                                                                   n_minor = 1), limits = c(0, 50)) +
  scale_y_continuous(breaks = seq(0, 50, 5), labels = insert_minor(major_labs = seq(0, 50, 10),
                                                                   n_minor = 1), limits = c(0, 50))

Method 2 is the best way of generating unlabelled minor ticks I've seen yet. However drawbacks are:

  1. Not dummy-proof - Users need to make sure that the value given to the 'n_minor' argument is compatible with the data supplied to the 'breaks' and 'major_labs' arguments. Call me lazy, but I don't want to think about this when I'm trying to produce plots quickly.
  2. Function management required - When you want to use this function in another script, you have to retrieve it from the last script you used it in, or alternatively perhaps you can package it up in a library to call in future scripts.

In my eyes, the ideal solution is for the ggplot2 developers to add an argument to scale_x_continuous or scale_y_continuous ggplot2 functions that takes a user-defined value for the number of unlabelled minor ticks the user would like to add to their plot axes, which then takes the vector supplied to the 'breaks' argument and determines 'major_labs' in the background out of the user's sight.

Has anyone else found any other way of computing unlabelled minor ticks in ggplot2?

1

1 Answers

2
votes

A quick, simple, and kinda sleek solution would be to define this one-liner labelling function that only shows breaks that occur at your chosen multiples:

label_at <- function(n) function(x) ifelse(x %% n == 0, x, "")

So you could do:

ggplot(df, aes(x = x, y = y)) +
  geom_point() +
  scale_x_continuous(breaks = seq(0, 50, 5),  labels = label_at(10), 
                     limits = c(0, 50)) +
  scale_y_continuous(breaks = seq(0, 50, 5), labels = label_at(5), 
                     limits = c(0, 50))

enter image description here

Which you can easily take to extremes:

ggplot(df, aes(x = x, y = y)) +
  geom_point() +
  scale_x_continuous(breaks = 1:50,  labels = label_at(10), limits = c(0, 50)) +
  scale_y_continuous(breaks = 1:50, labels = label_at(10), limits = c(0, 50))

enter image description here