0

I am attempting to make publication ready figures where the bottom axis (with tick marks) of one figure is cleanly combined with the top axis of the figure below it. Here is an example of what it might look like, although this one doesn't have tick marks on each panel:

enter image description here

Here is my attempt to do so, by simply using grid.arrange:


#Libraries:

library(ggplot2)
library(dplyr)
library(gridExtra)

#Filter to create two separate data sets:

dna1 <- DNase %>% filter(Run == 1)
dna2 <- DNase %>% filter(Run == 2)

#Figure 1:

dna1_plot <- ggplot(dna1, aes(x = conc, y = density)) + geom_point() + theme_classic() + 
  theme(axis.title.x = element_blank())

#Figure 2: 

dna2_plot <- ggplot(dna2, aes(x = conc, y = density)) + geom_point() + theme_classic() 

#Using grid.arrange to combine:

dna <- grid.arrange(dna1_plot, dna2_plot, nrow = 2)

enter image description here

And an attempt with some adjustments to the plot margins, although this didn't seem to work:


dna1_plot_round2 <- ggplot(dna1, aes(x = conc, y = density)) + geom_point() + theme_classic() + 
  theme(axis.title.x = element_blank(), 
        plot.margin = (0,0,0,0), "cm")

dna2_plot_round2 <- ggplot(dna2, aes(x = conc, y = density)) + geom_point() + theme_classic() +
  theme(plot.margin = unit(c(-0.5,-1,0,0), "cm"))

dna_round2 <- grid.arrange(dna1_plot_round2, dna2_plot_round2, nrow = 2)

enter image description here

Does anyone know the best way to stack figures like this in ggplot? Is there a better way than using grid.arrange? If possible it would be great to see how to do it with/without tick marks on each x axis as well.

Thank you!

Cameron
  • 164
  • 14
  • Other package::function options are `cowplot::plot_grid()` and `patchwork::wrap_plots()`. However I agree that facets are a good option for this use case. – neilfws Jan 18 '22 at 21:29

3 Answers3

3

You don't need any non-native ggplot stuff. Keep your data in one data frame and use facet_grid.

dna <- DNase %>% filter(Run %in% 1:2)

ggplot(dna, aes(x = conc, y = density)) + 
  geom_point() + 
  theme_bw() + 
  facet_grid(rows = vars(Run)) +
  theme(panel.spacing = unit(0, "mm"))

enter image description here

Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294
  • 1
    In something like the initial example, where the y-scales differ dramatically, you could also use `scales="free_y"` as an argument to `facet_wrap()`. And, if you don't like the strip labels, here's a [post](https://stackoverflow.com/questions/10547487/remove-facet-wrap-labels-completely) on how to remove them. – DaveArmstrong Jan 18 '22 at 21:05
  • 1
    `DNase` is a standard dataset included with R, so you do have access to it. – neilfws Jan 18 '22 at 21:12
  • I tried facet_wrap() but had trouble getting it to stack like in the example figure, and reading some supporting documentation I couldn't find a description of how to directly stack them (bottom axis of top figure contiguous with top axis of bottom figure). Is there an argument in facet_wrap that would make this happen? – Cameron Jan 19 '22 at 14:45
  • 2
    Check out the `panel_spacing.y` theme setting for ggplot: https://ggplot2.tidyverse.org/reference/theme.html. – William Gearty Jan 19 '22 at 16:06
  • 1
    Thanks to William's pointer, I've updated the answer removing the space between the plots. – Gregor Thomas Jan 19 '22 at 16:12
2

The R package deeptime has a function called ggarrange2 that can achieve this. Instead of just pasting the plots together like grid.arrange (and ggarrange), it lines up all of the axes and axis labels from all of the plots.

# remove bottom axis elements, reduce bottom margin, add panel border
dna1_plot_round2 <- ggplot(dna1, aes(x = conc, y = density)) + geom_point() + theme_classic() + 
      theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank(),
      plot.margin = margin(0,0,-.05,0, "cm"), panel.border = element_rect(fill = NA))
# reduce top margin (split the difference so the plots are the same height), add panel border
dna2_plot_round2 <- ggplot(dna2, aes(x = conc, y = density)) + geom_point() + theme_classic() +
      theme(plot.margin = margin(-.05,0,0,0, "cm"), panel.border = element_rect(fill = NA))
dna_round2 <- ggarrange2(dna1_plot_round2, dna2_plot_round2, nrow = 2)

two plots combined

You might also try the fairly recent patchwork package, although I don't have much experience with it.

Note that while Gregor's answer may be fine for this specific example, this answer might be more appropriate for other folks that come across this question (and see the example at the top of the question).

William Gearty
  • 166
  • 1
  • 8
  • I tried this package out, thank you! Unfortunately it still arranged figures in a way where the bottom axis of the top figure was not the same line as the top axis of the bottom figure, and I can't find any arguments for this function that make those lines come together. Do you know if this function does allow for that? – Cameron Jan 19 '22 at 14:46
  • 1
    I've added some additional code to my answer demonstrating how you can modify the margins and border with the ggplot theme settings. – William Gearty Jan 19 '22 at 16:16
1

For your purposes, I believe Gregor Thomas' answer is best. But if you are in a situation where facets aren't the best option for combining two plots, the newish package {{patchwork}} handles this more elegantly than any alternatives I've seen.

Patchwork also provides lots of options for adding annotations surrounding the combined plot. The readME and vignettes will get you started.

library(patchwork)
(dna1_plot / dna2_plot) +
  plot_annotation(title = "Main title for combined plots")

enter image description here

Edit to better address @Cameron's question.

According to the package creator, {{patchwork}} does not add any space between the plots. The white space in the example above is due to the margins around each individual ggplot. These margins can be adjusted using the plot.margin argument in theme(), which takes a numeric vector of the top, right, bottom, and left margins.

In the example below, I set the bottom margin of dna1_plot to 0 and strip out all the bottom x-axis ticks and text. I also set the top margin of dna2_plot to 0. Doing this nearly makes the y-axis lines touch in the two plots.

dna1_plot <- ggplot(dna1, aes(x = conc, y = density)) + geom_point() + theme_classic() + 
  theme(axis.title.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.text.x = element_blank(),
        plot.margin = unit(c(1,1,0,1), "mm"))

#Figure 2: 

dna2_plot <- ggplot(dna2, aes(x = conc, y = density)) + geom_point() + theme_classic() +
  theme(plot.margin = unit(c(0,1,1,1), "mm"))

(dna1_plot / dna2_plot)

enter image description here

John J.
  • 1,450
  • 1
  • 13
  • 28
  • I read the (very well organized) readME and vignettes page, and under "Controlling Layouts" and the other subcategories there is no documentation on how to make the bottom axis of one figure contiguous with the top axis of the figure below it. Do you know how to achieve this specifically? – Cameron Jan 19 '22 at 14:49
  • Hi @Cameron. I edited my response to better address this situation. It's not a perfect solution, but I believe it's the closest possible using patchwork. – John J. Jan 19 '22 at 22:41