简体   繁体   中英

R: How can I add global/spanning X and Y axes to a grid of ggplot plots with patchwork?

I am trying to create a global/spanning Y-axis title and a global/spanning X-axis title for a plot containing several ggplot objects arranged in a grid with clockwork . A global axis title is one that replaces the multiple axis titles along some margin of the plot with a single title, a shown in Step 2 to Step 4 below. Although this post shows how to create a global Y axis title, it does not show how to create do so for both the X and Y axes.

My issue is that my attempt at creating a global X-axis title places it far away from the other plots and shifts the Y-axis title so it is not centered in the plot. In the following reproducible examples, I show my starting point, successful creation of one global axis, and then (starting at Step 3) my failed attempts to add more alongside what I expected to produce. Does anyone know how I can add the global x-axes close to the rest of the plot without shifting the y-axis substantially toward the top of the figure?

1. X and Y axis titles for each cell

This is my "starting" plot: it has a 2x2 layout with axis titles for each cell. I'll want to replace these with fewer axis titles.

library(ggplot2)
library(patchwork)

# Create a list of 4 plots, then render
plot_list <- rep(list(ggplot(mtcars, aes(mpg, disp)) +geom_point()), 4)
wrap_plots(plot_list, nrow = 2)

2. Add a global Y-axis title

The cell-specific Y-axis titles are successfully replaced with a single, global Y-axis title. However, each cell still has its own X-axis title.

# Same list of 4 plots
plot_list <- rep(list(ggplot(mtcars, aes(mpg, disp)) +geom_point()), 4)

# Loop through list to remove each plot's Y-axis
for(i in 1:length(plot_list)){
  plot_list[[i]] <- plot_list[[i]] + theme(axis.title.y = element_blank())
}

# Create geom_text figure with Y-axis title that will fill the first column
y_lab_big <- ggplot() + 
  annotate(geom = "text", x = 1, y = 1, 
           label = "Here is some example long Y-axis text.", angle = 90) +
  coord_cartesian(clip = "off")+
  theme_void()

# Specify the text plot is NOT stacked and is much less wide
(y_lab_big | wrap_plots(plot_list, nrow = 2)) +
  plot_layout(widths = c(.1, 1))

3. Try to add a global X-axis title

I successfully replace each cell's x-axis title with a single, global x-axis title. However, it is far from the 2x2 grid and the y-axis title is now not centered on the 2x2 grid.

plot_list <- rep(list(ggplot(mtcars, aes(mpg, disp)) + geom_point()), 4)

# Loop through list to remove BOTH X-axis and Y-axis from every plot
for(i in 1:length(plot_list)){
  plot_list[[i]] <- plot_list[[i]] + theme(axis.title.y = element_blank(),
                                 axis.title.x = element_blank())
}

# Create geom_text figures for *Y-axis* title (just like Example 2)
y_lab_big <- 
  ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "Here is some example long Y-axis text.", angle = 90) +
  coord_cartesian(clip = "off")+
  theme_void()

# Create geom_text figures for *X-axis* title (different angle)
x_lab_big <- 
  ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "Big X Text", angle = 0) +
  coord_cartesian(clip = "off")+
  theme_void()

# Same as Step 2 but add x-axis title using / to indicate that it should
# be stacked below the plot_list plots and specify heights to the thirst row
# (which should be the x-axis title) is relatively small.
(y_lab_big | (wrap_plots(plot_list, nrow = 2)) / x_lab_big) +
  plot_layout(widths = c(.1, 1),
              heights = c(1,1,.1))

Ideally looks like below, with y_title centered between [1.1] and [2,1] and x_title centered between [3,2] [3,3].

matrix(c("y_title", "y_title", NA,
         "scatter1","scatter2", "x_title",
         "scatter3","scatter4","x_title"), nrow = 3, ncol = 3)
#>      [,1]      [,2]       [,3]      
#> [1,] "y_title" "scatter1" "scatter3"
#> [2,] "y_title" "scatter2" "scatter4"
#> [3,] NA        "x_title"  "x_title"

4. Adding more than one global X-axis title

Here I attempt to add a second global X-axis title so that each column receives its own x-axis title but all rows share the same axis title. The issue remains the same.

plot_list <- rep(list(ggplot(mtcars, aes(mpg, disp)) + geom_point()), 4)

# Loop through list to remove BOTH X-axis and Y-axis from every plot
for(i in 1:length(plot_list)){
  plot_list[[i]] <- plot_list[[i]] + theme(axis.title.y = element_blank(),
                                           axis.title.x = element_blank())
}

# Create geom_text figures for *Y-axis* title (just like Example 2)
y_lab_big <-  ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "Here is some example long Y-axis text.", angle = 90) +
  coord_cartesian(clip = "off")+
  theme_void()

# Create geom_text figures for *X-axis* title (different angle)
x_lab_big <- ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "X Text 1", angle = 0) +
  coord_cartesian(clip = "off")+
  theme_void()

x_lab_big2 <- ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "X Text 2", angle = 0) +
  coord_cartesian(clip = "off")+
  theme_void()

# Same as previous but add x-axis title using / to indicate that it should
# be stacked below the plot_list plots and specify heights to the thirst row
# (which should be the x-axis title) is relatively small.
(y_lab_big | (wrap_plots(plot_list, nrow = 2)) / (x_lab_big | x_lab_big2)) +
  plot_layout(widths = c(.1, 1),
              heights = c(1, .1))

Ideally looks like below, with y_title centered between [1,1] and [2,1], x_title1 centered in [3,2], and x_title3 centered in [3,3].

matrix(c("y_title", "y_title", NA,
         "scatter1","scatter2", "x_title1",
         "scatter3","scatter4","x_title2"), nrow = 3, ncol = 3)
#>      [,1]      [,2]       [,3]      
#> [1,] "y_title" "scatter1" "scatter3"
#> [2,] "y_title" "scatter2" "scatter4"
#> [3,] NA        "x_title1" "x_title2"

Although there are alternative solutions ( ggplot: how to add common x and y labels to a grid of plots ) I am trying to stay with using patchwork .

Hopefully patchwork will gain this feature in one of the next releases. In the meanwhile one option would be to stick with patchwork and get some support from cowplot . Adapting my answer on R ggplot2 patchwork common axis labels to your case(s):

Note: That works nice if there is no legend. With a legend it gets probably more fiddling.

library(ggplot2)
library(patchwork)
library(cowplot)

p <- ggplot(mtcars, aes(mpg, disp)) +
  geom_point()
plot_list <- rep(list(p + labs(x = NULL, y = NULL)), 4)

p_axis <- p + labs(x = "Big X Text", y = "Here is some example long Y-axis text.")
x_axis <- cowplot::get_plot_component(p_axis, "xlab-b")
y_axis <- cowplot::get_plot_component(p_axis, "ylab-l")

design = "
FAB
FCD
#EE
"

c(plot_list, list(x_axis, y_axis)) |> 
  wrap_plots() + 
  plot_layout(heights = c(20, 20, 1), widths = c(1, 25, 25), design = design)

And for the eg two separate x titles you could do:

Note: For that case another option would be to remove the axis titles for only the top two plots.


p_axis1 <- p + labs(x = "X Text 1", y = "Here is some example long Y-axis text.")
p_axis2 <- p + labs(x = "X Text 2", y = "Here is some example long Y-axis text.")
x_axis1 <- cowplot::get_plot_component(p_axis1, "xlab-b")
x_axis2 <- cowplot::get_plot_component(p_axis2, "xlab-b")

design = "
GAB
GCD
#EF
"


c(plot_list, list(x_axis1, x_axis2, y_axis)) |> 
  wrap_plots() + 
  plot_layout(heights = c(20, 20, 1), widths = c(1, 25, 25), design = design)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM