Ignore x-label alignment with multiple plots in patchwork: is this possible?

Viewed 1418

I really like the patchwork package, alignment of multiple plots is often better and easier to realize than in other packages (cowplot/gridextra).

However, one thing I cannot solve: Is it possible to ignore x-axis alignment in a patchwork, and only align all other elements? Or to adjust this x-axis alignment afterwords in the patchwork manually? See attached figure: I want the x-axis title from patch B and C (Petal.Length) closer to the x-axis, if possible.

enter image description here

Code that produced the image:

library(ggplot2)
library(patchwork)

plot.1 <- ggplot(iris, aes(x = Species, y = Petal.Width)) +
  geom_boxplot() +
  scale_x_discrete(labels = c("A very\nvery\nlong\nlabel", "","")) +
  labs(x = element_blank())

plot.2 <- ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
  geom_point()

(plot.1 |plot.2)/(plot.2|plot.1) + 
  plot_annotation(tag_levels = "A")
2 Answers

OP example

library(ggplot2)
library(patchwork)

plot.1 <- ggplot(iris, aes(x = Species, y = Petal.Width)) +
  geom_boxplot() +
  scale_x_discrete(labels = c("A very\nvery\nlong\nlabel", "","")) +
  labs(x = element_blank())

plot.2 <- ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
  geom_point()

(plot.1 | plot.2) / 
  (plot.2 | plot.1) + 
  plot_annotation(tag_levels = "A")

reshow OP figure

Use negative margins

It took me a while to realize that the issue derives from the need to align a mixture of axis.texts and axis.titles. Even though they are grammatically different elements, the categorical axis text serves most or all of the practical function that the continuous axis title does. Titles will always be grid-aligned outside of the aligned text region. Justification alone can't solve that problem, but negative margins can.

Notes:

  • The negative margin value you will want depends on other scaling factors in the plot, so this is one of those polish at the very end solutions. -50 worked in this case, but I've needed to use other values
  • It also works for y-axis
  • I messed around with parentheses but otherwise the plot spec is identical other than the extra theme() layer
  • You normally will not want to include this negative margin theme snippet in the individual plot definition, because it will appear to cause a bug there and you won't be able to iteratively refine the margin value that way, at least not easily if your script/Rmd is long and/or complex. This really is an issue only at the level of assembling the plots with patchwork, so it is probably best practice to keep a polishing tweak like this where you're defining the patchwork assembly, as I've shown here.
(plot.1 | (
  plot.2 +
    theme(axis.title.x = element_text(margin = margin(t = -50, unit = "pt"))))
) / (
  (plot.2 +
     theme(axis.title.x = element_text(margin = margin(t = -50, unit = "pt")))) | 
    plot.1
) + 
  plot_annotation(tag_levels = "A")

enter image description here

I found a hack. If you use the function patchwork::patchworkGrob, the plots will be stored in a gtable that can be edited. You just have to find the right layout values in the table. Every layout part has 't r l b' dimensions that you can edit (I believe top, right, left, bottom). In my case, I want to change the bottom value of "xlab-b". After changing the values, the gtable can be plotted with grid::grid.draw(). If needed y-axis position can be changed in a similar manner In a similar manner (ylab-l).

library(grid) # should be loaded already, just to be sure.

# Unadjusted patchwork:
plots <- (plot.1 |plot.2)/(plot.2|plot.1) + 
  plot_annotation(tag_levels = "A")

# create gTable object
plots.gtable <- patchworkGrob(plots)

# edit gtable object:
# tables are found in the list ‘plots.gtable$grobs’
# Second and fourth grobs in the list are the TableGrobs to edit:
# First edit panel B:
plots.gtable$grobs[[2]]$layout[
  plots.gtable$grobs[[2]]$layout$name == "xlab-b",
  ]$b[2] <- 12 # change this number, for me 12 worked fine
# Ends with $b[2] because we only want to adjust position of 
# the x-axis title of panel B.
# To adjust the position of the x-axis title of both plot A and B, 
# just leave out the [2].

# Now access lower two patchwork TableGrobs:
# edit panel C:
plots.gtable$grobs[[4]]$layout[
  plots.gtable$grobs[[4]]$layout$name == "xlab-b",
  ]$b[1] <- 12

# plot the adjusted figure:
grid::grid.newpage()
grid::grid.draw(plots.gtable)

edited-patchwork-result

edit 29-01-2021: I think the recent answer from Paul McMurdie is easier than the one I mention here.

Related