This question has been answered, but the general problem of adding a secondary axis and secondary scale to the right-hand side of a ggplot object is one that comes up all the time. I would like to report below my own tweak on the problem, based on several elements given by various answers in this thread as well as in several other threads (see a partial list of references below).
I have a need for mass production of dual-y-axis plots, so I built a function ggplot_dual_axis(). Here are the features of potential interest:
The code displays gridlines for both the y-left and y-right axes (this is my main contribution though it is trivial)
The code prints a euro symbol and embeds it into the pdf (something I saw there: Plotting Euro Symbol € in ggplot2?)
The code attempts to avoid printing certain elements twice ('attempts' suggests I doubt it fully succeeds)
Unanswered questions:
Is there a way to modify the ggplot_dual_axis() function to remove one of the geom_line() or geom_point() or whatever it may be without throwing errors if such geom elements are not present. In pseudo-code something like if has(geom_line) ...
How can I call the g2$grobs[[7]] by keyword rather than index? This is what it returns: text[axis.title.y.text.232] My interest in the question stems from my failed attempts to grab the grid lines by applying a similar trick. I think the grid lines are hidden somewhere inside g2$grobs[[4]], but I'm not sure how to access them.
Edit. Question I was able to answer myself: How can I increase the plot margin on the right-hand side, where the 'Euro' label is? Answer: theme(plot.margin = unit(c(1,3,0.5,0.8), "lines")) will do the trick, for instance.
Please do point out any obvious problems or suggest improvements.
Now the code: hopefully it will be useful to someone. As I said, I don't claim originality, it's a combination of things others have already shown.
##' function named ggplot_dual_axis() ##' Takes 2 ggplot plots and makes a dual y-axis plot ##' function takes 2 compulsory arguments and 1 optional argument ##' arg lhs is the ggplot whose y-axis is to be displayed on the left ##' arg rhs is the ggplot whose y-axis is to be displayed on the right ##' arg 'axis.title.y.rhs' takes value "rotate" to rotate right y-axis label ##' The function does as little as possible, namely: ##' # display the lhs plot without minor grid lines and with a ##' transparent background to allow grid lines to show ##' # display the rhs plot without minor grid lines and with a ##' secondary y axis, a rotated axis label, without minor grid lines ##' # justify the y-axis label by setting 'hjust = 0' in 'axis.text.y' ##' # rotate the right plot 'axis.title.y' by 270 degrees, for symmetry ##' # rotation can be turned off with 'axis.title.y.rhs' option ##' ggplot_dual_axis <- function(lhs, rhs, axis.title.y.rhs = "rotate") { # 1. Fix the right y-axis label justification rhs <- rhs + theme(axis.text.y = element_text(hjust = 0)) # 2. Rotate the right y-axis label by 270 degrees by default if (missing(axis.title.y.rhs) | axis.title.y.rhs %in% c("rotate", "rotated")) { rhs <- rhs + theme(axis.title.y = element_text(angle = 270)) } # 3a. Use only major grid lines for the left axis lhs <- lhs + theme(panel.grid.minor = element_blank()) # 3b. Use only major grid lines for the right axis # force transparency of the backgrounds to allow grid lines to show rhs <- rhs + theme(panel.grid.minor = element_blank(), panel.background = element_rect(fill = "transparent", colour = NA), plot.background = element_rect(fill = "transparent", colour = NA)) # Process gtable objects # 4. Extract gtable library("gtable") # loads the grid package g1 <- ggplot_gtable(ggplot_build(lhs)) g2 <- ggplot_gtable(ggplot_build(rhs)) # 5. Overlap the panel of the rhs plot on that of the lhs plot pp <- c(subset(g1$layout, name == "panel", se = t:r)) g <- gtable_add_grob(g1, g2$grobs[[which(g2$layout$name == "panel")]], pp$t, pp$l, pp$b, pp$l) # Tweak axis position and labels ia <- which(g2$layout$name == "axis-l") ga <- g2$grobs[[ia]] ax <- ga$children[["axis"]] # ga$children[[2]] ax$widths <- rev(ax$widths) ax$grobs <- rev(ax$grobs) ax$grobs[[1]]$x <- ax$grobs[[1]]$x - unit(1, "npc") + unit(0.15, "cm") g <- gtable_add_cols(g, g2$widths[g2$layout[ia, ]$l], length(g$widths) - 1) g <- gtable_add_grob(g, ax, pp$t, length(g$widths) - 1, pp$b) g <- gtable_add_grob(g, g2$grobs[[7]], pp$t, length(g$widths), pp$b) # Display plot with arrangeGrob wrapper arrangeGrob(g) library("gridExtra") grid.newpage() return(arrangeGrob(g)) }
And below some fake data and two plots that are intended to be in dollar and euro units. Wouldn't it be cool to have a package that would allow you to make one plot and wrap around it a call to a dual-y-axis plot like so ggplot_dual_axis_er(ggplot_object, currency = c("dollar", "euro")) and it would automatically fetch the exchange rates for you! :-)
# Set directory: if(.Platform$OS.type == "windows"){ setwd("c:/R/plots") } else { setwd("~/R/plots") } # Load libraries library("ggplot2") library("scales") # Create euro currency symbol in plot labels, simple version # avoids loading multiple libraries # avoids problems with rounding of small numbers, e.g. .0001 labels_euro <- function(x) {# no rounding paste0("€", format(x, big.mark = ",", decimal.mark = ".", trim = TRUE, scientific = FALSE)) } labels_dollar <- function(x) {# no rounding: overwrites dollar() of library scales paste0("$", format(x, big.mark = ",", decimal.mark = ".", trim = TRUE, scientific = FALSE)) } # Create data df <- data.frame( Year = as.Date(c("2001", "2002", "2003", "2004", "2005", "2006", "2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017", "2018"), "%Y"), Dollar = c(0, 9000000, 1000000, 8000000, 2000000, 7000000, 3000000, 6000000, 4000000, 5000000, 5000000, 6000000, 4000000, 7000000, 300000, 8000000, 2000000, 9000000)) # set Euro/Dollar exchange rate at 0.8 euros = 1 dollar df <- cbind(df, Euro = 0.8 * df$Dollar) # Left y-axis p1 <- ggplot(data = df, aes(x = Year, y = Dollar)) + geom_line(linestyle = "blank") + # manually remove the line theme_bw(20) + # make sure font sizes match in both plots scale_x_date(labels = date_format("%Y"), breaks = date_breaks("2 years")) + scale_y_continuous(labels = labels_dollar, breaks = seq(from = 0, to = 8000000, by = 2000000)) # Right y-axis p2 <- ggplot(data = df, aes(x = Year, y = Euro)) + geom_line(color = "blue", linestyle = "dotted", size = 1) + xlab(NULL) + # manually remove the label theme_bw(20) + # make sure font sizes match in both plots scale_x_date(labels = date_format("%Y"), breaks = date_breaks("2 years")) + scale_y_continuous(labels = labels_euro, breaks = seq(from = 0, to = 7000000, by = 2000000)) # Combine left y-axis with right y-axis p <- ggplot_dual_axis(lhs = p1, rhs = p2) p # Save to PDF pdf("ggplot-dual-axis-function-test.pdf", encoding = "ISOLatin9.enc", width = 12, height = 8) p dev.off() embedFonts(file = "ggplot-dual-axis-function-test.pdf", outfile = "ggplot-dual-axis-function-test-embedded.pdf")

Partial list of references:
- Display two parallel axes on a ggplot (R)
- Dual y axis in ggplot2 for multiple panel figure
- How can I put a transformed scale on the right side of a ggplot2?
- Preserve proportion of graphs using grid.arrange
- The perils of aligning plots in ggplot
- https://github.com/kohske/ggplot2