Search code examples
rggplot2time-seriesvisualizationfacet-wrap

R ggplot facet_wrap with different y-axis labels, one values, one percentages


I'm plotting a time series value with its percentages using facet_wrap in ggplot:

For the plot below, the upper plot is the value, and the lower plot is percentage change. And I would like the y-axis in the lower plot to be "%". Normally in ggplot I would do something like

+ scale_y_continuous(labels = scales::percent)

But since I'm using facet_wrap, how do I specify that I only want one of the 2 plots' y-axis label to be percentages?

enter image description here

P.S. Here is the code to generate this plot:

library(data.table)
library(ggplot2)
library(scales)
library(dplyr)

pct <- function(x) {x/lag(x)-1}
Dates = seq(from = as.Date("2000-01-01"), 
            to =as.Date("2018-10-01"), 
            by = "1 month")
set.seed(1024)
this_raw = data.frame(CM = Dates,
                      value = rnorm(n = length(Dates)),
                      variable = rep("FAKE",length(Dates)))
this_diff = na.omit(as.data.table(this_raw %>% 
                                    group_by(variable) %>%
                                    mutate_each(funs(pct), c(value))))
this_diff$type = "PerCng"
this_raw$type = "RAW"
plot_all = rbindlist(list(this_raw,this_diff))
plot_all$type = factor(plot_all$type, levels = c("RAW", "PerCng"))

out_gg = plot_all %>%
  ggplot(aes(x=CM, y=value)) + 
  geom_line(color = "royalblue3") +
  theme(legend.position='bottom')+
  ggtitle("FAKE DATA") + 
  facet_wrap(~ type, scale = "free_y", nrow = 2,
             strip.position = "left", 
             labeller = as_labeller(c(RAW = "Original", PerCng = "% Change") ) )+
  scale_x_date(date_breaks = "12 month", date_labels = "%Y-%m", 
               date_minor_breaks = "3 month")+
  ylab("")+
  theme(plot.title = element_text(hjust = 0.5,size = 12),  
        axis.text.x = element_text(size = 6,angle = 45, hjust = 1),
        axis.text.y = element_text(size = 6),  
        axis.title.y = element_text(size = 6)) + 
  theme(strip.background = element_blank(),
        strip.placement = "outside")+
  theme(legend.title=element_blank())
print(out_gg)

Solution

  • I agree with the above comments that facets are really not intended for this use case. Aligning separate plots is the orthodox way to go.

    That said, if you already have a bunch of nicely formatted ggplot objects, and really don't want to refactor the code just for axis labels, you can convert them to grob objects and dig underneath the hood:

    library(grid)
    
    # Convert from ggplot object to grob object
    gp <- ggplotGrob(out_gg)
    
    # Optional: Plot out the grob version to verify that nothing has changed (yet)
    grid.draw(gp)
    
    # Also optional: Examine the underlying grob structure to figure out which grob name 
    # corresponds to the appropriate y-axis label. In this case, it's "axis-l-2-1": axis 
    # to the left of plot panels, 2nd row / 1st column of the facet matrix.
    gp[["layout"]]
    gtable::gtable_show_layout(gp)
    
    # Some of gp's grobs only generate their contents at drawing time.
    # Using grid.force replaces such grobs with their drawing time content (if you check 
    # your global environment, the size of gp should increase significantly after running 
    # the grid.force line).
    # This step is necesary in order to use gPath() to generate the path to nested grobs 
    # (& the text grob for y-axis labels is nested rather deeply inside the rabbit hole).
    gp <- grid.force(gp)
    path.to.label <- gPath("axis-l-2", "axis", "axis", "GRID.text")
    
    # Get original label
    old.label <- getGrob(gTree = gp,
                         gPath = path.to.label,
                         grep = TRUE)[["label"]]
    
    # Edit label values
    new.label <- percent(as.numeric(old.label))
    
    # Overwrite ggplot grob, replacing old label with new
    gp = editGrob(grob = gp,
                  gPath = path.to.label,
                  label = new.label,
                  grep = TRUE)
    
    # plot
    grid.draw(gp)
    

    modified grob