Dual y-axis in ggplot

Double vertical axis is a fairly common requirement by the analysts. They often use plots with double y-axis to visualize two different variables where x-axis is common to both of them. While ggplot supports most type of graph, this particular kind is not supported. Hadley believes this type of graphs are fundamentally flawed and even went as far as saying he will never build such a graph in ggplot2 package because:

  1. the points on such a graph are not invertible
  2. those plots often create confusion
  3. there are better ways to compare variables with same horizontal axis

You can see the full discussion here. https://stackoverflow.com/a/3101876/7860688

But, if you are not the one deciding what kind of graphs to build or do not want to lecture your client about plotting theory, here is a neat trick you can you.

# having a fill is important for bar and area if you want legends
# If you don't get legends in the final plot after grid.draw(),
# check if you are getting legends in p1 and p2 independently
p1 <- ggplot(df_processed_Gen, 
              aes(x = FYE, y = SumGen, fill = "#4B92DB")) + 
              geom_bar(stat = 'identity') + 
              labs(x="FYE", y="Sum of Generation") +
              scale_fill_identity(name="", guide="legend",
                                  labels=c("sum(generation)")) +
              theme(legend.position="bottom") +
              ggtitle('Generator and CF information')


p2 <- ggplot(df_processed_CF, 
              aes(x = FYE, y = SumCF, colour = 'sum(CF)')) + 
              geom_line(stat = 'identity') + 
              labs(x="FYE", y="Sum of Capacity Factor") +
              theme_few() %+replace% 
              theme(panel.background = element_rect(fill = NA)) +
              theme(legend.position="bottom",
                    legend.title=element_blank())

# hack for ggplots as it does not support dual axis
# due to the conpect being inherently flawed in plotting thoery
# See the discussion : https://stackoverflow.com/a/3101876/7860688

library(ggplot2)
library(gtable)
library(grid)


# extract gtable
g1 <- ggplot_gtable(ggplot_build(p1))
g2 <- ggplot_gtable(ggplot_build(p2))

# overlap the panel of 2nd plot on that of 1st 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)

# axis tweaks
# depending on platform, layout name may change.
# use print(g$layout) to find the correct one

ia <- which(g2$layout$name == "axis-l")   
ga <- g2$grobs[[ia]]                      
ax <- 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)

# depending on platform, this may change.
# use print(g$layout) to find the correct name
ia2 <- which(g2$layout$name == "ylab")   
ga2 <- g2$grobs[[ia2]]                   
ga2$rot <- 90

g <- gtable_add_cols(g, 
                     g2$widths[g2$layout[ia2, ]$l], 
                     length(g$widths) - 1)

g <- gtable_add_grob(g, ga2, 
                     pp$t, 
                     length(g$widths) - 1, 
                     pp$b)


# Extract legend. 
# Legends may not be present if they are not produced from ggplot() in p1 and p2
# In that case, go back and edit your ggplot to produce legends. 
# Otherwise, omit the following.   
leg1 <- g1$grobs[[which(g1$layout$name == "guide-box")]]
leg2 <- g2$grobs[[which(g2$layout$name == "guide-box")]]

g$grobs[[which(g$layout$name == "guide-box")]] <- 
  gtable:::cbind_gtable(leg1, leg2, "first")


# g is the grob that should be used to draw the plot. 
# We need grid package for that.
library(grid)
grid.newpage()
grid.draw(g)
comments powered by Disqus