Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ggplot2 missing labels after custom scaling of axis

I am attempting to apply a custom scaling of my x-axis using ggplot2 and scales::trans_new(). However, when I do some of the axis labels go missing. Can someone help me figure out why?

Setup:

library(tidyverse)

# the data
ds <- tibble(
  myx = c(1, .5, .1, .01, .001, 0),
  myy = 1:6
)

# the custom transformation
forth_root_trans_rev <- scales::trans_new(
  name = "sign_fourth_root_rev",
  transform = function (x) { - abs(x)^(1/4) },
  inverse = function (x) { x^4 }
)

Plot 1:

When I try and plot this the label for x = 0 gets lost.

# plot - missing x-label at `0`
ggplot(ds, aes(x = myx, y = myy)) + 
  geom_line() + 
  geom_point() + 
  scale_x_continuous(
    trans = forth_root_trans_rev,
    breaks = sort(unique(ds$myx)),
  )

missing x-label at <code>0</code>

Plot 2

When I add some space on both sides of the graph, even more x-labels get lost.

# plot - missing x-labels below 0.5
ggplot(ds, aes(x = myx, y = myy)) + 
  geom_line() + 
  geom_point() +
  scale_x_continuous(
    trans = forth_root_trans_rev,
    breaks = sort(unique(ds$myx)),
    expand = expand_scale(mult = c(.1, .6))
  )

missing x-labels below 0.5

I presume this is related to this old issue: https://github.com/tidyverse/ggplot2/issues/980. Nevertheless, I can't figure out how to apply this transformation and retain all x-labels.

Where am I going wrong?

like image 678
crlwbm Avatar asked May 14 '19 12:05

crlwbm


1 Answers

The problem here is due to the combination of two factors:

  1. Your x-axis values (after transformation) fall in the [-1, 0] range, so any expansion (whether additive or multiplicative) will nudge the final range to cover both positive and negative values.

  2. Your custom transformation is not one-to-one in the [<some negative number>, <some positive number>] region.

How it occurred

Somewhere deep inside the all code used to build the ggplot object (you can run ggplot2:::ggplot_build.ggplot before printing the plot & step into layout$setup_panel_params(), but I don't recommend this for casual users... the rabbit hole goes really deep down there), x-axis breaks are calculated in the following manner:

  1. Obtain limits for the transformed values (for c(1, .5, .1, .01, .001, 0) in the question, this will be (-1, 0)).
  2. Add expansion to the limits, if applicable (default expansion for a continuous axis is 5% on either side, so the limits become (-1.05, 0.05)).
  3. Apply the inverse transformation on the limits (taking x^4 on the limits yields (1.215506, 0.000006)).
  4. Apply the transformation on both user-inputted breaks & limits (for breaks, c(1, .5, .1, .01, .001, 0) becomes (-1.0000000, ..., 0.0000000), but for limits, (1.215506, 0.000006) now becomes (-1.05, -0.05), which is narrower than (-1.05, 0.05)).
  5. Breaks beyond the limit's range are dropped (since the limits now stop at -0.05, the break at 0 is dropped).

How to get around this

You can modify your transformation with the use of sign() to preserve positive / negative values, such that the transformation is one-to-one in the full range, as suggested by Hadley in the discussion on the GH issue you linked. For example:

# original
forth_root_trans_rev <- scales::trans_new(
  name = "sign_fourth_root_rev",
  transform = function (x) { - abs(x)^(1/4) },
  inverse = function (x) { x^4 }
)

# new
forth_root_trans_rev2 <- scales::trans_new(
  name = "sign_fourth_root_rev",
  transform = function (x) { -sign(x) * abs(x)^(1/4) },
  inverse = function (x) { -sign(x) * abs(x)^4 }
)

library(dplyr)
library(tidyr)

# comparison of two transformations
# y1 shows a one-to-one mapping in either (-Inf, 0] or [0, Inf) but not both;
# y2 shows a one-to-one mapping in (-Inf, Inf)
data.frame(x = seq(-1, 1, 0.01)) %>%
  mutate(y1 = x %>% forth_root_trans_rev$transform() %>% forth_root_trans_rev$inverse(),
         y2 = x %>% forth_root_trans_rev2$transform() %>% forth_root_trans_rev2$inverse()) %>%
  gather(trans, y, -x) %>%
  ggplot(aes(x, y, colour = trans)) +
  geom_line() +
  geom_vline(xintercept = 0, linetype = "dashed") +
  facet_wrap(~trans)

comparison plot

Usage

p <- ggplot(ds, aes(x = myx, y = myy)) + 
  geom_line() + 
  geom_point() + 
  theme(panel.grid.minor = element_blank())

p + 
  scale_x_continuous(
    trans = forth_root_trans_rev2,
    breaks = sort(unique(ds$myx))
  )
p + 
  scale_x_continuous(
    trans = forth_root_trans_rev2,
    breaks = sort(unique(ds$myx)),
    expand = expand_scale(mult = c(.1, .6)) # with different expansion factor, if desired
  )

plots

like image 165
Z.Lin Avatar answered Oct 12 '22 20:10

Z.Lin