30

I want to duplicate the left-side Y-axis on a ggplot2 plot onto the right side, and then change the tick labels for a discrete (categorical) axis.

I've read the answer to this question, however as can be seen on the package's repo page, the switch_axis_position() function has been removed from the cowplot package (the author cited (forthcoming?) native functionality in ggplot2).

I've seen the reference page on secondary axes in ggplot2, however all the examples in that document use scale_y_continuous rather than scale_y_discrete. And, indeed, when I try to use the discrete function, I get the error:

Error in discrete_scale(c("y", "ymin", "ymax", "yend"), "position_d",  : 
unused argument (sec.axis = <environment>)

Is there anyway to do this with ggplot2? Even a completely hacked solution will suffice for me. Thanks in advance. (MREs below)

library(ggplot2)

# Working continuous plot with 2 axes
ggplot(mtcars, aes(cyl, mpg))  + 
    geom_point() + 
    scale_y_continuous(sec.axis = sec_axis(~.+10))


# Working discrete plot with 1 axis
ggplot(mtcars, aes(cyl, as.factor(mpg)))  + 
    geom_point() 


# Broken discrete plot with 2 axes
ggplot(mtcars, aes(cyl, as.factor(mpg)))  + 
    geom_point() +
    scale_y_discrete(sec.axis = sec_axis(~.+10))
Alex P. Miller
  • 2,128
  • 1
  • 23
  • 20
  • 2
    looking at the source of `scale_y_discrete` there is no option / argument for specifying the secondary axis. So any solution will probably have to be a hack. – SymbolixAU Jul 27 '17 at 23:02
  • 1
    I raised an [issue](https://github.com/tidyverse/ggplot2/issues/3171) over at ggplot to support `sec.axis` natively in `scale_y/x_discrete()`. – maxheld Mar 03 '19 at 16:29

1 Answers1

25

Take your discrete factor and represent it numerically. Then you can mirror it and relabel the ticks to be the factor levels instead of numbers.

library(ggplot2)

irislabs1 <- levels(iris$Species)
irislabs2 <- c("foo", "bar", "buzz")

ggplot(iris, aes(Sepal.Length, as.numeric(Species))) +
  geom_point() +
  scale_y_continuous(breaks = 1:length(irislabs1),
                     labels = irislabs1,
                     sec.axis = sec_axis(~.,
                                         breaks = 1:length(irislabs2),
                                         labels = irislabs2))

Then fiddle with the expand = argument in the scale as needed to more closely imitate the default discrete scale.

enter image description here

Brian
  • 7,900
  • 1
  • 27
  • 41
  • 3
    as per ggplot help "The defaults are c(0.05, 0) for continuous variables, and c(0, 0.6) for discrete variables." for me expand = c(0,0.6) gave really nice results – TobiO Mar 08 '18 at 13:57
  • 2
    Just found this again. Is there still no better way to do it? Anyone knows an easier way by now? – TobiO Apr 14 '20 at 18:22
  • @TobiO I haven't followed development on ggplot for a couple years, but you can always file a bug report on their github repo with a good reprex. – Brian Apr 14 '20 at 18:25
  • 1
    I found this again, because I was looking for a solution and only found a feature request from a few weeks after this was done, with a similar suggestion as yours :-) – TobiO Apr 14 '20 at 18:26
  • 4
    There is actually an open issue on Github, put a comment there to let the team know you're still interested: https://github.com/tidyverse/ggplot2/issues/3171 – Brian Apr 14 '20 at 18:27