0
ggplot(df) +
      geom_bar(aes(x=Date, y=DCMTotalCV, fill=CampaignName), stat='identity', position='stack') +
      geom_line(aes(x=Date, y=DCMCPA, color=CampaignName, group=as.factor(CampaignName)), na.rm = FALSE,show.legend=NA)+ 
      scale_y_continuous(sec.axis = sec_axis(~./1000, name = "DCMTotalCV"))+
      theme_bw()+
      labs(
        x= "Date",
        y= "CPA",
        title = "Daily Performance"
      )

Hey everyone - so I have 2 y-axes i want to plot. geom_line is registering fine on the main y-axis but geom_bar is not registering properly on the right. I tried scaling but it's still not registering or plotting on that second axis. It looks like it's still appearing on the main y-axis so I'm wondering how to tell the plot to plot it on the second one? Sorry i'm kind of a newbie. Thanks!

    data <- data.frame(
  day = as.Date("2020-01-01"),
  conversions = seq(1,6)^2,
  cpa = 100000 / seq(1,6)^2
)
head(data)
str(data)
#plot
ggplot(data, aes(x=day)) +
  geom_bar( aes(y=conversions), stat='identity') + 
  geom_line( aes(y=cpa)) +
  scale_y_continuous(sec.axis = sec_axis(~./1000))
andreas niko
  • 35
  • 1
  • 6
  • 1
    Would you care to give us more perspective? We know nothing about the data, so are unable to know what it currently looks like nor what you think it should look like. It helps to include sample data and explanation about what you are getting versus what you expect to get in return from this. Refs: https://stackoverflow.com/q/5963269, [mcve], and https://stackoverflow.com/tags/r/info. – r2evans Oct 13 '20 at 02:28
  • What are the ranges of the two variables? `range(df$DCMTotalCV)` and `range(df$DCMCPA)`. My guess is that they are not the same range of numbers. Recognize that adding a second axis does nothing to change the actual y-values: you are responsible for ensuring that whatever variable is to be paired to the second axis is still *scaled* to the range of the first axis. For instance, if `ggplot(dat, aes(x, y1)) + geom_line() + geom_point(aes(y=y2))`, and `y1` is on `[0,1]` and `y2` is on `[10,99]`, then consider `y2scaled <- scales::rescale(r2, to=range(y1))` and then plotting `y2scaled` instead. – r2evans Oct 13 '20 at 02:56
  • If my answer doesn't give you the help you need, I can't think of anything that would help more than sample data from you. Please read the links I provided above in order to make this question reproducible. – r2evans Oct 13 '20 at 03:53
  • 1
    @r2evans thanks so much!! "Recognize that adding a second axis does nothing to change the actual y-values: you are responsible for ensuring that whatever variable is to be paired to the second axis is still scaled to the range of the first axis. For instance, if ggplot(dat, aes(x, y1)) + geom_line() + geom_point(aes(y=y2)), and y1 is on [0,1] and y2 is on [10,99], then consider y2scaled <- scales::rescale(r2, to=range(y1)) and then plotting y2scaled instead" --> this helped me a ton ! that's exactly what i was missing - the logic of scaling the data down to match y1. thanks! – andreas niko Oct 13 '20 at 07:55

1 Answers1

0

ggplot2::sec_axis is intended only to put up the scale itself; it does nothing to try to scale the values (that you are pairing with that axis). Why? Primarily because it knows nothing about which y variable you are intending to pair with which y-axis. (Is there anywhere in sec_axis to tell it that it should be looking at a particular variable? Nope.)

As a demonstration, let's start with some random data and plot the line.

set.seed(42)
dat <- data.frame(x = rep(1:10), y1 = sample(10), y2 = sample(100, size = 10))
dat
#     x y1 y2
# 1   1  1 47
# 2   2  5 24
# 3   3 10 71
# 4   4  8 89
# 5   5  2 37
# 6   6  4 20
# 7   7  6 26
# 8   8  9  3
# 9   9  7 41
# 10 10  3 97

ggplot(dat, aes(x, y1)) +
  geom_line() +
  scale_y_continuous(name = "Oops!")

first plot, no second axis

Now you determine that you want to add the y2 variable in there, but because its values are on a completely different scale, you think to just add them (I'll use geom_text here) and then set a second axis.

ggplot(dat, aes(x, y1)) +
  geom_line() +
  geom_text(aes(y = y2, label = y2)) +
  scale_y_continuous(name = "Oops!", sec.axis = sec_axis(~ . * 10, name = "Quux!"))

second plot, incorrect second axis

Two things wrong with this:

  1. The primary (left) y-axis now scales from 0 to 100, scrunching the primary y values to the bottom of the plot; and
  2. Related, the secondary (right) y-axis scales from 0 to 1000?!? This is because the only thing that the secondary axis "knows" is the values that go into the primary axis ... and the primary axis is scaling to fit all of the y* variables it is told to plot.

That last point is important: this is giving y values that scale from 0 to 100, so the axis will reflect that. You can do lims(y=c(0,10)), but realize you'll be truncating y2 values ... that's not the right approach.

Instead, you need to scale the second values to be within the same range of values as the primary axis variable y1. Though not required, I'll use scale::rescale for this.

dat$y2scaled <- scales::rescale(dat$y2, range(dat$y1))
dat
#     x y1 y2  y2scaled
# 1   1  1 47  5.212766
# 2   2  5 24  3.010638
# 3   3 10 71  7.510638
# 4   4  8 89  9.234043
# 5   5  2 37  4.255319
# 6   6  4 20  2.627660
# 7   7  6 26  3.202128
# 8   8  9  3  1.000000
# 9   9  7 41  4.638298
# 10 10  3 97 10.000000

Notice how y2scaled is now proportionately within y1's range?

We'll use that to position each of the text objects (though we'll still show the y2 as the label here).

ggplot(dat, aes(x, y1)) +
  geom_line() +
  geom_text(aes(y = y2scaled, label = y2)) +
  scale_y_continuous(name = "Oops!", sec.axis = sec_axis(~ . * 10, name = "Quux!"))

third plot, correct second axis

Are we strictly required to make sure that the points pairing with the secondary axis perfectly fill the range of values of the primary axis? No. We could easily have thought to keep the text labels only on the bottom half of the plot, so we'd have to scale appropriately.

dat$y2scaled2 <- scales::rescale(dat$y2, range(dat$y1) / c(1, 2))
dat
#     x y1 y2  y2scaled y2scaled2
# 1   1  1 47  5.212766  2.872340
# 2   2  5 24  3.010638  1.893617
# 3   3 10 71  7.510638  3.893617
# 4   4  8 89  9.234043  4.659574
# 5   5  2 37  4.255319  2.446809
# 6   6  4 20  2.627660  1.723404
# 7   7  6 26  3.202128  1.978723
# 8   8  9  3  1.000000  1.000000
# 9   9  7 41  4.638298  2.617021
# 10 10  3 97 10.000000  5.000000

ggplot(dat, aes(x, y1)) +
  geom_line() +
  geom_text(aes(y = y2scaled2, label = y2)) +
  scale_y_continuous(name = "Oops!", sec.axis = sec_axis(~ . * 20, name = "Quux!"))

fourth plot, alternative second axis, scrunched down a little

Notice that not only did I change how the y-axis values were scaled (now ranging from 0 to 5 in y2scaled2), but I also had to change the transformation within sec_axis to be *20 instead of *10.

Sometimes getting these transformations correct can be confusing, and it is easy to mess them up. However ... realize that it took many years to even get this functionality into ggplot2, mostly due to the lead developer(s) belief that even when plotted well, they can be confusing to the viewer, and potentially provide misleading takeaways. I find that they can be useful sometimes, and there are techniques one can use to encourage correct interpretation, but ... it's hard to get because it's easy to get wrong.

As an example of one technique that helps distinguish which axis goes with which data, see this:

ggplot(dat, aes(x, y1)) +
  geom_line(color = "blue") +
  geom_text(aes(y = y2scaled2, label = y2), color = "red") +
  scale_y_continuous(name = "Oops!", sec.axis = sec_axis(~ . * 20, name = "Quux!")) +
  theme(
    axis.ticks.y.left = element_line(color = "blue"),
    axis.text.y.left = element_text(color = "blue"),
    axis.title.y.left = element_text(color = "blue"),
    axis.ticks.y.right = element_line(color = "red"),
    axis.text.y.right = element_text(color = "red"),
    axis.title.y.right = element_text(color = "red")
  )

(One might consider colors from viridis for a more color-blind palette.) last plot, axis labels and plot objects are matching colors

r2evans
  • 141,215
  • 6
  • 77
  • 149