user11130854
user11130854

Reputation: 373

Missing ticks in custom axis transformation

When plotting the ratio between two variables, their relative order is often of no concern, yet depending on which variable is in the numerator, its relative size is constrained either to (0,1) or (1, Inf), which is somewhat unintuitive and breaks symmetry. I want to plot ratios "symmetrically", without resorting to symmetric log-scale, by having a y-axis that goes like 1/4, 1/3, 1/2, 1, 2, 3, 4 or, equivalently, 4^-1, 3^-1, 2^-1, 1, 2, 3, 4 in regular intervals. I've come up with the following:


symmult <- function(x){
              isf <- is.finite(x) & (x>0)
              xf <- x[isf]
              xf <- ifelse(xf>=1,
                      xf-1,
                      1-(1/xf))
              x[isf] <- xf
              x[!isf] <- NA
              x[!is.finite(x)] <- NA
              return(x)
}

symmultinv <- function(x){
              isf <- is.finite(x)
              xf <- x[isf]
              xf <- ifelse(x[isf]>=0,
                      x[isf]+1,
                      -1/(x[isf]-1))
              x[isf] <- xf
              x[!isf] <- NA
              x[!is.finite(x)] <- NA
              return(x)
              }

sym_mult_trans = function(){trans_new("sym_mult", symmult, symmultinv )}

x <- c(-4:-2, 1:4)
x[x<1] <- 1/abs(x[x<1])


ggplot() +
  geom_point(aes(x=x, y=x)) +
  scale_y_continuous(trans="sym_mult")

Symmetric ratio axis

The transformation works, but I cannot get the axis labels etc. to work for any 0<x<1, without setting them manually. Any help would be greatly appreciated.

Upvotes: 1

Views: 81

Answers (1)

Allan Cameron
Allan Cameron

Reputation: 174546

You can create bespoke 'breaks' and 'format' functions that you can use inside trans_new (or pass to scale_y_continuous directly via its breaks and labels parameters).

For the breaks function, remember it will take as input a length-two numeric vector representing the range of the y axis. You must then convert this to a number of appropriate breaks. Here, if the minimum of the range is less than one, we take its reciprocal, find the pretty breaks between one and that number, then take the reciprocal of the output. We concatenate that onto pretty breaks between 1 and our range maximum:

# Define breaks function
symmult_breaks <- function(x) {
  
  c(1 / extended_breaks(5)(c(1/x[x < 1], 1)), 
    extended_breaks(5)(c(1, x[x >= 1])))
}

For the labelling function, remember, it needs to take as input the vector of numbers produced by our breaks function. We can paste a 1/ in front of the reciprocal of numbers less than one, but leave numbers of 1 or more unaltered:

# Define labelling function
symmult_labs <- function(x) {
  
  labs <- character(length(x))
  labs[x >= 1] <- as.character(x[x >= 1])
  labs[x < 1]  <- paste("1", as.character(1/x[x < 1]), sep = "/")
  labs
}

So your full new transformation becomes:

# Use our four functions to define the whole transformation:
sym_mult_trans <- function() {
   
  trans_new(name      = "sym_mult", 
            transform = symmult, 
            inverse   = symmultinv,
            breaks    = symmult_breaks,
            format    = symmult_labs)
}

And your plot becomes:

ggplot() +
  geom_point(aes(x = x, y = x)) +
  scale_y_continuous(trans  = "sym_mult")

enter image description here

Upvotes: 2

Related Questions