Annotate combined subplots (patchworks?) as single plots using patchwork - r

I'm trying to figure out how to annotate combined patchworks as if they were individual plots.
I've got one patchwork consisting of three combined plots and another single plot. The final composite plot is the first patchwork on top and the individual plot on the bottom. I have no problem getting the layout I want, but when I use plot_annotation, it gives letters to every plot, whereas what I'd like to see is an A for the top plot (patchwork of three subplots) and a B for the bottom one (just a single plot)
Here's what I'm currently doing:
library(ggplot2)
library(patchwork)
p1 <- ggplot(mtcars) +
geom_point(aes(mpg, disp)) +
ggtitle('Plot 1')
p2 <- ggplot(mtcars) +
geom_boxplot(aes(gear, disp, group = gear)) +
ggtitle('Plot 2')
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
ggtitle('Plot 3')
p4 <- ggplot(mtcars) +
geom_bar(aes(gear)) +
facet_wrap(~cyl) +
ggtitle('Plot 4')
top_plot = (p1 + p2 + p3)
bottom_plot = p4
combined_plot <- (top_plot / bottom_plot) + plot_annotation(tag_levels="A")
combined_plot
What I'd like to see, rather than A-D annotations, is A for the top plot (plots 1-3) and a B for the bottom one (plot 4). Is there a way to do this?

One solution is to create two complete patchwork plots, each with its own annotation. You have to put them each inside wrap_elements to declare them as complete patchworks for this to work. Thereafter you can combine them as you would combine ggplots:
# Set theme for annotations
thm <- theme(plot.title = element_text(face = 2, size = 16))
top_plot <- wrap_elements((p1 + p2 + p3) +
plot_annotation(title = "A", theme = thm))
bottom_plot <- wrap_elements(p4 + plot_annotation(title = "B", theme = thm))
top_plot / bottom_plot

Related

specify specific position for collected legend in patchwork

I am using the patchwork package in r to create panels of plots like:
panel<- (p1+ plot_spacer()+p2 + p3)+
plot_layout(ncol = 2) +
plot_layout(guides = "collect")
panel
I want to specify the legend to go to the empty top-right panel, more or less like this
Appreciate any pointers
For this use case patchwork provides guide_area() which could be used to place the legend:
library(patchwork)
library(ggplot2)
p1 <- p2 <- p3 <- ggplot(mtcars, aes(hp, mpg, color = factor(cyl))) +
geom_point()
p1 + guide_area() + p2 + p3 +
plot_layout(ncol = 2) +
plot_layout(guides = "collect")
as suggested by #kat, one solution is to use cowplot in combination to patchwork
library("cowplot")
library("patchwork")
legend_grob <-cowplot::get_legend(p1) #get legend
#emove legend in the original plots
p1 <- p1+theme(legend.position = "none")
p2 <- p2+theme(legend.position = "none")
p3 <- p3+theme(legend.position = "none")
# now patchwork it
p1+legend_grob+p2+p3)
plot_layout(ncol = 2)

Global x and y labels for plots arranged using ggplot and plot_layout with textGrob

I have created a plot with many elements using ggplot and plot_layout. I want to add common x and y axes. I would like to use textGrob to keep a common look with other plots I created this way.
MWE
library(patchwork)
library(ggplot2)
library(grid)
library(gridExtra)
p1 <- ggplot(mtcars) +
geom_point(aes(mpg, disp, color = mpg)) +
ggtitle('Plot 1')
p2 <- ggplot(mtcars) +
geom_boxplot(aes(gear, disp, group = gear)) +
ggtitle('Plot 2')
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
ggtitle('Plot 3')
design <- "
1111
223#
"
myplt <- (p1 + p2 + p3 + plot_layout(design=design, guides = "collect") &
theme(legend.position = 'right',
legend.direction = 'vertical'))
y.grob <- textGrob("My Y label",
gp=gpar(fontface="bold", fontsize=15), rot=90)
x.grob <- textGrob("My X label",
gp=gpar(fontface="bold", fontsize=15))
grid.arrange(arrangeGrob(myplt, left = y.grob, bottom = x.grob))
The above worked well for plots I arranged using plot_grid (i.e. adding the common labels). However, with the above I get either a blank plot with the correct labels, or with the MWE above the correct labels but only with Plot 3.
I also tried adding on
myplt
grid::grid.draw(grid::textGrob(y.grob))
grid::grid.draw(grid::textGrob(x.grob))
because of this answer, but that just put text[GRID text 26826] in the middle of my plot.
I did manage to get their other idea working, but the spacing was horrible and I couldn't get the font details to match what I have for other plots, so would prefer to find a solution using the textGrobs already created.
EDIT: The design had an extra row that was blank that I removed
To make grid.arrange work with a patch you have to convert it to a grob first using patchwork::patchworkGrob. Additionally there is no need for grid.arrange(arrangeGrob(...)). Just use grid.arrange:
grid.arrange(patchworkGrob(myplt), left = y.grob, bottom = x.grob)

Add a title to the top of multiple plots

How can I add a title at the top of a plot of 6 combined ggplots?
I have currently used this code to add a title which places it in the centres of my 6 plots, how would I move it to the top?
Each res_plot here is just a standard ggplot:
all_plots = res_plot1 + res_plot2 + res_plot3 + res_plot4 + res_plot5
all_plots + ggtitle("Residual Plots")
I am assuming from your syntax that you are using the patchwork package. In that case you can use plot_annotation:
library(patchwork)
res_plot1 <- res_plot2 <- res_plot3 <- res_plot4 <- res_plot5 <-
ggplot(mtcars, aes(wt, mpg)) + geom_point()
all_plots <- res_plot1 + res_plot2 + res_plot3 + res_plot4 + res_plot5
all_plots + plot_annotation(title = "Residual Plots")

How to place multiple boxplots in the same column with ggplot(geom_boxplot)

I would like to built a boxplot in which the 4 factors (N1:N4) are overlaid in the same column. For example with the following data:
df<-data.frame(N=N,Value=Value)
Q<-c("C1","C1","C2","C3","C3","C1","C1","C2","C2","C3","C3","Q1","Q1","Q1","Q1","Q3","Q3","Q4","Q4","Q1","Q1","Q1","Q1","Q3","Q3","Q4","Q4")
N<-c("N2","N3","N3","N2","N3","N2","N3","N2","N3","N2","N3","N0","N1","N2","N3","N1","N3","N0","N1","N0","N1","N2","N3","N1","N3","N0","N1")
Value<-c(4.7,8.61,8.34,5.89,8.36,1.76,2.4,5.01,2.12,1.88,3.01,2.4,7.28,4.34,5.39,11.61,10.14,3.02,9.45,8.8,7.4,6.93,8.44,7.37,7.81,6.74,8.5)
with the following (usual) code, the output is 4 box-plots displayed in 4 columns for the 4 variables
ggplot(df, aes(x=N, y=Value,color=N)) + theme_bw(base_size = 20)+ geom_boxplot()
many thanks
Updated Answer
Based on your comment, here's a way to add marginal boxplots. We'll use the built-in mtcars data frame.
First, some set-up:
library(cowplot)
# Common theme elements
thm = list(theme_bw(),
guides(colour=FALSE, fill=FALSE),
theme(plot.margin=unit(rep(0,4),"lines")))
Now, create the three plots:
# Main plot
p1 = ggplot(mtcars, aes(wt, mpg, colour=factor(cyl), fill=factor(cyl))) +
geom_smooth(method="lm") + labs(colour="Cyl", fill="Cyl") +
scale_y_continuous(limits=c(10,35)) +
thm[-2] +
theme(legend.position = c(0.85,0.8))
# Top margin plot
p2 = ggplot(mtcars, aes(factor(cyl), wt, colour=factor(cyl))) +
geom_boxplot() + thm + coord_flip() + labs(x="Cyl", y="")
# Right margin plot
p3 = ggplot(mtcars, aes(factor(cyl), mpg, colour=factor(cyl))) +
geom_boxplot() + thm + labs(x="Cyl", y="") +
scale_y_continuous(limits=c(10,35))
Lay out the plots and add the legend:
plot_grid(plotlist=list(p2, ggplot(), p1, p3), ncol=2,
rel_widths=c(5,1), rel_heights=c(1,5), align="hv")
Original Answer
You can overlay all four boxplots in a single column, but the plot will be unreadable. The first example below removes N as the x coordinate, but keeps N as the colour aesthetic. This results in the four levels of N being plotted at a single tick mark (which I've removed by setting breaks to NULL). However, the plots are still dodged. To plot them one on top of the other, set the dodge width to zero, as I've done in the second example. However, the plots are not readable when they are overlaid.
ggplot(df, aes(x="", y=Value,color=N)) +
theme_bw(base_size = 20) +
geom_boxplot() +
scale_x_discrete(breaks=NULL) +
labs(x="")
ggplot(df, aes(x="", y=Value,color=N)) +
theme_bw(base_size = 20) +
geom_boxplot(position=position_dodge(0)) +
scale_x_discrete(breaks=NULL) +
labs(x="")

Separate y-axis labels by facet OR remove legend but keep the space

Ok, I'm stumped on a home-brew ggplot.
What I would like to do is have a three row, one column faceted plot with a different y-axis label for each facet. The units of the y-axis are all the same. This would be the most convenient, but googling tells me it may not be possible.
Alternatively, I found this solution using grid.arrange, which seems like it will work. However, I want to keep a legend only for one plot and remove it from the other two, but maintain the spacing as if it were still there so that everything lines up nice. Someone had the same problem a few years ago, but the suggested solution is depreciated and I can't sort out how to make it work in modern ggplot.
Any help is appreciated! Using facets would be easiest!
Edited to add copy of plot after using user20560's gridArrange solution below. Very nearly there, just would like to get back the box around the top and bottom facet panels!
I have assumed (possibly wrongly) that you are wanting to add separate y-axis titles rather than axis labels. [If it is the labels you want different you can use the scales argument in facet_grid]
There will be a ggplot way to do this but here are a couple of ways you could tweak the grobs yourself.
So using mtcars dataset as example
library(ggplot2)
library(grid)
library(gridExtra)
One way
p <- ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() +
facet_grid(gear ~ .)
# change the y axis labels manually
g <- ggplotGrob(p)
yax <- which(g$layout$name=="ylab")
# define y-axis labels
g[["grobs"]][[yax]]$label <- c("aa","bb", "cc")
# position of labels (ive just manually specified)
g[["grobs"]][[yax]]$y <- grid::unit(seq(0.15, 0.85, length=3),"npc")
grid::grid.draw(g)
Or using grid.arrange
# Create a plot for each level of grouping variable and y-axis label
p1 <- ggplot(mtcars[mtcars$gear==3, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="aa") + theme_bw()
p2 <- ggplot(mtcars[mtcars$gear==4, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="bb") + theme_bw()
p3 <- ggplot(mtcars[mtcars$gear==5, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="cc") + theme_bw()
# remove legends from two of the plots
g1 <- ggplotGrob(p1)
g1[["grobs"]][[which(g1$layout$name=="guide-box")]][["grobs"]] <- NULL
g3 <- ggplotGrob(p3)
g3[["grobs"]][[which(g3$layout$name=="guide-box")]][["grobs"]] <- NULL
gridExtra::grid.arrange(g1,p2,g3)
If it is the axis titles you want to add I should ask why you want a different titles - can the facet strip text not do?
Following the comments by Axeman and aosmith (thank you), here's a way to do this using the facet labels using ggplot2 version 2.2.0
library(ggplot2) # From sessionInfo(): ggplot2_2.2.0
ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() +
facet_grid(gear ~ ., switch = 'y') +
theme( axis.title.y = element_blank(), # remove the default y-axis title, "wt"
strip.background = element_rect(fill = 'transparent'), # replace the strip backgrounds with transparent
strip.placement = 'outside', # put the facet strips on the outside
strip.text.y = element_text(angle=180)) # rotate the y-axis text (optional)
# (see ?ggplot2::theme for a list of theme elements (args to theme()))
I know this is an old post, but after finding it, I could not get #user20560's response to work.
I've edited #user20560's grid.extra approach as follows:
library(ggplot2)
library(gridExtra)
library(grid)
# Create a plot for each level of grouping variable and y-axis label
p1 <- ggplot(mtcars[mtcars$gear==3, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="aa") + theme_bw()
p2 <- ggplot(mtcars[mtcars$gear==4, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="bb") + theme_bw()
p3 <- ggplot(mtcars[mtcars$gear==5, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="cc") + theme_bw()
# get the legend as a grob
legend <- ggplotGrob(p1)
legend <- legend$grobs[[which(legend$layout$name=="guide-box")]]
lheight <- sum(legend$height)
lwidth <- sum(legend$width)
# remove the legend from all the plots
p1 <- p1 + theme(legend.position = 'none')
p2 <- p2 + theme(legend.position = 'none')
p3 <- p3 + theme(legend.position = 'none')
# force the layout to the right side
layoutMat <- matrix(c(1,2,3,4,4,4),ncol = 2)
grid.arrange(p1,p2,p3,legend, layout_matrix = layoutMat, ncol = 2,
widths = grid::unit.c(unit(1,'npc') - lwidth, lwidth))
This example is somewhat specific to this particular layout. There is a more general approach on the ggplot2 wiki.
I too had trouble getting the first approach in the answer of user20560 (above) to work. This is probably because the internals of ggplot2 have evolved, and there is no guarantee that these internals should stay the same. In any case, here is a version that currently works:
library(ggplot2) # From sessionInfo(): ggplot2_2.1.0
library(grid)
p <- ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() + facet_grid(gear ~ .)
g <- ggplotGrob(p)
yax <- which(g$layout$name == "ylab")
g[["grobs"]][[yax]]$children[[1]]$label <- c('fo','bar','foobar')
g[["grobs"]][[yax]]$children[[1]]$y <- grid::unit(seq(0.15, 0.85, length=3), "npc")
grid.draw(g)
Note that this is the approach that keeps the facets and does not repeat the x-axes.

Resources