34

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))
2
  • 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. Commented Jul 27, 2017 at 23:02
  • 3
    I raised an issue over at ggplot to support sec.axis natively in scale_y/x_discrete(). Commented Mar 3, 2019 at 16:29

2 Answers 2

29

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

Sign up to request clarification or add additional context in comments.

5 Comments

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
Just found this again. Is there still no better way to do it? Anyone knows an easier way by now?
@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.
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 :-)
There is actually an open issue on Github, put a comment there to let the team know you're still interested: github.com/tidyverse/ggplot2/issues/3171
0

Starting with ggplot2 4.0.0, discrete scales can be duplicated. Therefore, the following code works now:

library(ggplot2)

ggplot(mtcars, aes(cyl, as.factor(mpg)))  + 
  geom_point() +
  scale_y_discrete(sec.axis = dup_axis())

The example by Brian can be achieved like this:

library(ggplot2)

secondary_labels <- c("foo", "bar", "buzz")

ggplot(iris, aes(Sepal.Length, Species)) +
  geom_point() +
  scale_y_discrete(
    sec.axis = dup_axis(labels = secondary_labels)
  )

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.