Search code examples
rggplot2facetfacet-wrap

How can one control the number of axis ticks within `facet_wrap()`?


I have a figure created with facet_wrap visualizing the estimated density of many groups. Some of the groups have a much smaller variance than others. This leads to the x axis not being readable for some panels. Minimum reproducable example:

library(tidyverse)
x1 <- rnorm(1e4)
x2 <- rnorm(1e4,mean=2,sd=0.00001)

data.frame(x=c(x1,x2),group=c(rep("1",length(x1)),rep("2",length(x2)))) %>%
  ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group,scales="free")

enter image description here

The obvious solution to the problem is to increase the figure size, so that everything becomes readable. However, there are too many panels to make this a useful solution. My favourite solution would be to control the number of axis ticks, for example allow for only two ticks on all x-axes. Is there a way to accomplish this?


Edit after suggestions:

Adding + scale_x_continuous(n.breaks = 2) looks like it should exactly do what I want, but it actually does not:

enter image description here

Following the answer in the suggested question Change the number of breaks using facet_grid in ggplot2, I end up with two axis ticks, but undesirably many decimal points:

equal_breaks <- function(n = 3, s = 0.5, ...){
  function(x){
    # rescaling
    d <- s * diff(range(x)) / (1+2*s)
    seq(min(x)+d, max(x)-d, length=n)
  }
}

data.frame(x=c(x1,x2),group=c(rep("1",length(x1)),rep("2",length(x2)))) %>%
  ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group,scales="free")  + scale_x_continuous(breaks=equal_breaks(n=3, s=0.05), expand = c(0.05, 0))

enter image description here


Solution

  • You can add if(seq[2]-seq[1] < 10^(-r)) seq else round(seq, r) to the function equal_breaks developed here.

    By doing so, you will round your labels on the x-axis only if the difference between them is above a threshold 10^(-r).

    equal_breaks <- function(n = 3, s = 0.05, r = 0,...){
      function(x){
        d <- s * diff(range(x)) / (1+2*s)
        seq = seq(min(x)+d, max(x)-d, length=n)
        if(seq[2]-seq[1] < 10^(-r)) seq else round(seq, r)
      }
    }
    
    data.frame(x=c(x1,x2),group=c(rep("1",length(x1)),rep("2",length(x2)))) %>%
      ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group, scales="free") +
      scale_x_continuous(breaks=equal_breaks(n=3, s=0.05, r=0)) 
    

    enter image description here

    As you rightfully pointed, this answer gives only two alternatives for the number of digits; so another possibility is to return round(seq, -floor(log10(abs(seq[2]-seq[1])))), which gets the "optimal" number of digits for every facet.

    equal_breaks <- function(n = 3, s = 0.1,...){
      function(x){
        d <- s * diff(range(x)) / (1+2*s)
        seq = seq(min(x)+d, max(x)-d, length=n)
        round(seq, -floor(log10(abs(seq[2]-seq[1]))))
      }
    }
    
    data.frame(x=c(x1,x2,x3),group=c(rep("1",length(x1)),rep("2",length(x2)),rep("3",length(x3)))) %>%
      ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group, scales="free") +
      scale_x_continuous(breaks=equal_breaks(n=3, s=0.1)) 
    

    enter image description here