Better transformation for secondary y-axis in ggplot2

Viewed 234

I am trying to add a secondary y axis to some data for ggplot2. My data p looks like this:

  aspect  yhat Count
    <dbl> <dbl> <dbl>
 1   1.37 0.329   144
 2  16.9  0.329     5
 3  32.3  0.330     5
 4  47.8  0.331     0
 5  63.3  0.333    57
 6  78.8  0.333    67
 7  94.3  0.332    13
 8 110.   0.332     0
 9 125.   0.332     0
10 141.   0.331     0

and I attempt to plot it like this:

 #get the information to for using a secondary y axis
    ylim.prim <- c(min(p$yhat), max(p$yhat))
    ylim.sec <- c(min(p$Count, na.rm = T), max(p$Count, na.rm = T))

    b <- diff(ylim.prim)/diff(ylim.sec)
    a <- b*(ylim.prim[1] - ylim.sec[1])

    #now make plot
    p1 = ggplot(p, aes(x = get(col), y = yhat)) +
      geom_line() +
      labs(x = col) +
      geom_line(aes(y = a + Count*b), color = "red", linetype = 2) +
      scale_y_continuous(name = "Mean Response", sec.axis = sec_axis(~ (. - a)/b, name = "Number of Pixels")) +
      theme_light() +
      theme(axis.line.y.right = element_line(color = "red"),
            axis.ticks.y.right = element_line(color = "red"),
            axis.text.y.right = element_text(color = "red"),
            axis.title.y.right = element_text(color = "red")
      ) +
      theme(legend.title = element_blank()) +
      theme(text=element_text(size=22))

which returns this plot:

enter image description here

The problem I am having is that there is so much empty space between the black and the red lines that is unused which makes it a little difficult to interpret the values, and I am wondering if I can somehow use a better transformation to bring the lines closer together, or even overlap them, but I can't figure out how.

I think this may have something to do with the fact that identical yhat values can have different count values. If so is there a way around this?

1 Answers

In general, I agree with the premise that multiple (disparate) axes can easily be confusing to the viewer. Using colors and different objects (points vs lines) helps differentiate the two values, but it's a mitigation.

Here's a stab, using points (as suggested):

count_slope_intercept <- c(m = diff(range(p$Count)), b = min(p$Count))
yhat_slope_intercept <- c(m = diff(range(p$yhat)), b = min(p$yhat))

p$Count2 <- yhat_slope_intercept["b"] +
  yhat_slope_intercept["m"] * (p$Count - count_slope_intercept["b"]) / count_slope_intercept["m"]

ggplot(p, aes(x = aspect, y = yhat)) +
  geom_line() +
  labs(x = "aspect") +
  geom_point(aes(y = Count2), color = "red") +
  scale_y_continuous(
    name = "Mean Response",
    sec.axis = sec_axis(~ count_slope_intercept["b"] + count_slope_intercept["m"] *
                          (. - yhat_slope_intercept["b"]) / yhat_slope_intercept["m"])
  ) +
  theme_light() +
  theme(axis.line.y.right = element_line(color = "red"),
        axis.ticks.y.right = element_line(color = "red"),
        axis.text.y.right = element_text(color = "red"),
        axis.title.y.right = element_text(color = "red")
        ) +
  theme(legend.title = element_blank()) +
  theme(text=element_text(size=22))

ggplot2 with two y-axes

(There's likely a smoother way to do that using scales or similar.)


Edit: yup, same thing with scales:

p$Count3 <- scales::rescale(p$Count, to = range(p$yhat), from = range(p$Count))

ggplot(p, aes(x = aspect, y = yhat)) +
  geom_line() +
  labs(x = "aspect") +
  geom_point(aes(y = Count3), color = "red") +
  scale_y_continuous(
    name = "Mean Response",
    sec.axis = sec_axis(~ scales::rescale(., to = range(p$Count), from = range(p$yhat)))
  ) +
  theme_light() +
  theme(axis.line.y.right = element_line(color = "red"),
        axis.ticks.y.right = element_line(color = "red"),
        axis.text.y.right = element_text(color = "red"),
        axis.title.y.right = element_text(color = "red")
        ) +
  theme(legend.title = element_blank()) +
  theme(text=element_text(size=22))

(While I'm being explicit above and including both of the to= and from= arguments, from defaults to range(x), so the code can be shortened somewhat. I thought it better for this example to be explicit and show what's coming from where and to where.)

Related