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 }
)
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)),
)
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))
)
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?
The problem here is due to the combination of two factors:
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.
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:
c(1, .5, .1, .01, .001, 0)
in the question, this will be (-1, 0)
).(-1.05, 0.05)
).x^4
on the limits yields (1.215506, 0.000006)
).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)
).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)
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
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With