ggplot faceting with different specifications of ylims

410 views Asked by At

I want to create a ggplot figure with six panels in R. The first five facets should represent five different subsets of data in bar charts, and the final facet should represent the whole data. I further want to have a fixed y-axis scale across the first five facets, but a different scale in the final facet. I am aware that it is currently not possible to specify individual ylims for each facet within the ggplot functionality (https://github.com/hadley/ggplot2/issues/187), but am wondering if I can do something similar using grid and possibly gtable packages, neither of which I'm very familiar with at the moment.

The following is my attempt. I replace the final facet with a facet in another figure.

library("ggplot2")
library("dplyr")
library("grid")

# create data
set.seed(1)
d1 <- data_frame(
  value = rnorm(3 * 5, mean = 30, sd = 10),
  f = rep(LETTERS[1:3], 5),
  p = rep(paste("Panel", 1:5), each = 3)
)
d2 <- d1 %>%
  mutate(p = "Total") %>%
  rbind(d1)

# make initial figures
plot1 <- ggplot(d2, aes(f, value)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ p) +
  coord_cartesian(ylim = c(0, 50))
plot2 <- ggplot(d2, aes(f, value)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ p, scales = "free_y")

# extract their grobs
g1 <- ggplotGrob(plot1)
g2 <- ggplotGrob(plot2)

# replace the final facet of plot1 with the final facet of plot2
g1[["grobs"]][[7]] <- g2[["grobs"]][[7]]
g1[["grobs"]][[19]] <- g2[["grobs"]][[19]]
g1[["grobs"]][[25]] <- g2[["grobs"]][[25]]

# draw the figure
grid.newpage()
grid.draw(g1)

And here's what I get. enter image description here

As can be seen, however, the y-axis label of the final facet overlaps with the preceding facet. Does anyone know a way to avoid the overlap e.g., by making the final facet smaller?

1

There are 1 answers

1
Sandy Muspratt On BEST ANSWER

One approach is to extract the "Total" plot from "g2", then insert it into "g1", but first remove the "Total" plot from "g1". But you will notice that the x-axis tick mark labels do not align across the facets.

# Load packages
library(ggplot2)
library(dplyr)
library(gtable)
library(grid)

# create data
set.seed(1)
d1 <- data.frame(
  value = rnorm(3 * 5, mean = 30, sd = 10),
  f = rep(LETTERS[1:3], 5),
  p = rep(paste("Panel", 1:5), each = 3)
)
d2 <- d1 %>%
  mutate(p = "Total") %>%
  rbind(d1)

# make initial figures
plot1 <- ggplot(d2, aes(f, value)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ p) +
  coord_cartesian(ylim = c(0, 50))
plot2 <- ggplot(d2, aes(f, value)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ p, scales = "free_y")

# Get the ggplot grobs
g1 <- ggplotGrob(plot1)
g2 <- ggplotGrob(plot2)

# Extract "Total" plot from g2
keep = g2$layout$name %in% c("panel-3-2", "axis-b-3-2", "axis-l-2-3", "strip-t-3-2")
pos = subset(g2$layout, keep, c(t,l,b,r))
g2 = g2[c(min(pos$t):max(pos$b)), c(min(pos$l):max(pos$r))]

# Remove "Total" plot from g1
keep = !g1$layout$name %in% c("panel-3-2", "axis-b-3-2", "strip-t-3-2")
pos = subset(g1$layout, !keep, c(t,l,b,r))
g1$grobs <- g1$grobs[keep]
g1$layout <- g1$layout[keep, ]

# Insert g2 into g1
g1 = gtable_add_grob(g1, g2, t=min(pos$t), b=max(pos$b), l=min(pos$l), r=max(pos$r))

# Draw it
grid.newpage()
grid.draw(g1)

enter image description here

Another approach is to extract the "Total" plot from "g2" as before, but to move its y-axis to the right side of the plot (using code borrowed from here. (I tweaked your "plot2" so that the tick mark labels are better aligned in the final plot.) In this way, the "Total" panel takes as much space as the other panels, and thus the x-axis tick mark labels align, but the y-axis for the "Total" panel sticks out to the right.

# Make initial figures
plot1 <- ggplot(d2, aes(f, value)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ p) +
  coord_cartesian(ylim = c(0, 50))
plot2 <- ggplot(d2, aes(f, value)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ p, scales = "free_y") +
  theme(axis.text.y = element_text(hjust = 0))    ## For better formatting of labels

# extract their grobs
g1 <- ggplotGrob(plot1)
g2 <- ggplotGrob(plot2)

# Extract "Total" plot from g2
keep = g2$layout$name %in% c("panel-3-2", "axis-b-3-2", "axis-l-2-3", "strip-t-3-2")
pos = subset(g2$layout, keep, c(t,l,b,r))
g2 = g2[c(min(pos$t):max(pos$b)), c(min(pos$l):max(pos$r))]


# Get the position of the panel in the layout
panel <- c(subset(g2$layout, grepl("panel", g2$layout$name), se = t:r))

# Get the row number of the y-axis in the layout
rn <- which(grepl("axis-l", g2$layout$name))

# Extract the axis (tick marks and axis text from the gtable)
axis.grob <- g2$grobs[[rn]]
axisl <- axis.grob$children[[2]]  # Two children - get the second
axisl  # Note: two grobs - tick marks and text

# Reverse the grobs and the widths
axisl$widths <- rev(axisl$widths)
axisl$grobs <- rev(axisl$grobs)

axisl$grobs[[1]]$x <- axisl$grobs[[1]]$x - unit(1, "npc") + unit(2.75, "pt")

axisl$grobs[[2]]$children[[1]]$x = unit(.15, "npc")    

# Remove the column containing the left axis
g2 <- g2[, -(panel$r-1)]

## remove empty panels
keep = !g1$layout$name %in% c("panel-3-2", "axis-b-3-2", "strip-t-3-2")
pos = subset(g1$layout, !keep, c(t,l,b,r))
g1$grobs <- g1$grobs[keep]
g1$layout <- g1$layout[keep, ]

# Insert g2 into g1
g1 = gtable_add_grob(g1, g2, t = min(pos$t), b = max(pos$b), l = min(pos$l), r = max(pos$r))

# Add a new column to g1, and add the revised axisl grob to the new column.
pos = subset(g1$layout, grepl("panel", g1$layout$name), c(t,l,b,r)) # position of bottom right panel
g1 <- gtable_add_cols(g1, axisl$widths, max(pos$r))
g1 <- gtable_add_grob(g1, axisl, t = max(pos$b), l = max(pos$r)+1,  r = max(pos$r)+2)

# Draw it
grid.newpage()
grid.draw(g1)

enter image description here