Adding white space after ggplots using grid.arrange - r

I'm creating ggplots in a loop and then using grid.arrange to plot each of my figures on one page in a lattice-type graph. The problem I have is that I have a border around each figure and they merge together when I plot them. Does anyone know how to add white space between the figures. I've looked for information about figure padding and also toyed around with trying to add blank geom_rect between my plots, but so far no luck. Some simplified code is provided below. Thanks for any help you can offer.
p = vector("list", 3) #List for arranging grid
for(ii in 1:3){
p[[ii]] = ggplot(mtcars, aes(x = wt, y = mpg))+
geom_point()+
theme(plot.background = element_rect(colour = 'black', size = 2))
}
do.call("grid.arrange", c(p, ncol=1))

I tried quite a few different efforts to get the viewports to be smaller within a 3 x 1 layout and finally realized that just adding some blank space with narrow heights in the 5 x 1 layout was pretty easy:
Layout <- grid.layout(nrow = 5, ncol = 1,
heights=c(1, .1, 1, .1, 1) )
# could have written code to alternate heights or widths with gaps
grid.show.layout(Layout)
vplayout <- function(...) { # sets up new page with Layout
grid.newpage()
pushViewport(viewport(layout = Layout))
}
subplot <- function(x, y) viewport(layout.pos.row = x,
layout.pos.col = y)
mmplot <- function(p=p) { # could make more general
vplayout()
print(p[[1]], vp = subplot(1, 1 ))
print(p[[2]], vp = subplot(3, 1))
print(p[[3]], vp = subplot(5, 1 ))
}
mmplot(a, z)

alternatively, this experimental version of gtable offers a similar interface to grid.arrange,
library(ggplot2)
library(gtable)
lp <- replicate(3, qplot(rnorm(10), rnorm(10)) +
theme(plot.background=element_rect(size = 3, colour="black")),
simplify = FALSE)
lg <- lapply(lp, ggplotGrob)
g <- do.call(gtable_arrange, c(lg, ncol=1, draw=FALSE))
g <- gtable_add_rows(g, heights = unit(1, "line"), pos = 1)
g <- gtable_add_rows(g, heights = unit(1, "line"), pos = 3)
grid.newpage()
grid.draw(g)

Related

Remove white space of textGrob from grid.arrange

I'm new to grobbing and I am trying to create a simple grid.arrange object but cannot figure out how to might a compact/tight layout.
Below is a simple example of what I'm trying to run and the grob that I get.
library(grid)
library(gridExtra)
name = textGrob("My Name", gp=gpar(fontsize = 20, fontface = "bold"))
name2 = textGrob("Second Name", gp=gpar(fontsize = 16))
tbl = tableGrob(head(iris))
grid.arrange(name, name2, tbl)
UPDATE:
Using the answer found here I was able to get the text compact but I am still struggling to get the table to be right under the text.
library(grid)
library(gridExtra)
name = textGrob("My Name", gp=gpar(fontsize = 20, fontface = "bold"))
name2 = textGrob("Second Name", gp=gpar(fontsize = 16))
tbl = tableGrob(head(iris))
margin = unit(0.5, "line")
grid.newpage()
grid.arrange(name, name2, tbl,
heights = unit.c(grobHeight(name) + 1.2*margin,
grobHeight(name2) + margin,
unit(1,"null")))
Typically you'd use the top= argument for a single grob. With two grobs like this, it might be easiest to combine them in a table; the major hurdle is that gtable doesn't consider justification so you have to adjust the positions yourself,
library(gtable)
justify <- function(x, hjust="center", vjust="top", draw=FALSE){
w <- sum(x$widths)
h <- sum(x$heights)
xj <- switch(hjust,
center = 0.5,
left = 0.5*w,
right=unit(1,"npc") - 0.5*w)
yj <- switch(vjust,
center = 0.5,
bottom = 0.5*h,
top=unit(1,"npc") - 0.5*h)
x$vp <- viewport(x=xj, y=yj)
if(draw) grid.draw(x)
return(x)
}
title <- gtable_col('title', grobs = list(name,name2),
heights = unit.c(grobHeight(name) + 1.2*margin,
grobHeight(name2) + margin))
grid.newpage()
grid.arrange(justify(title, vjust='bottom'), justify(tbl))

Add axes to grid of ggplots

I have a grid composed of several ggplots and want to add an x axis, where axis ticks and annotations are added between the plots. I could not came up with a better solution than to create a custom plot for the axis and adding it below with arrangeGrob. But they do not align with the plots (I draw arrows where the numbers should be). Also there is a large white space below which I don't want.
I will also need an analogue for the y-axis.
library(ggplot2)
library(gridExtra)
library(ggpubr)
library(grid)
# Create a grid with several ggplots
p <-
ggplot(mtcars, aes(wt, mpg)) +
geom_point() +
theme_transparent() +
theme(plot.background = element_rect(color = "black"))
main.plot <- arrangeGrob(p, p, p, p, p, p, p, p, ncol = 4, nrow = 2)
# grid.draw(main.plot)
# Now add an x axis to the main plot
x.breaks <- c(0, 1, 2.5, 8, 10)
p.axis <- ggplot() +
ylim(-0.1, 0) +
xlim(1, length(x.breaks)) +
ggpubr::theme_transparent()
for (i in seq_along(x.breaks)) {
p.axis <- p.axis +
geom_text(aes_(x = i, y = -0.01, label = as.character(x.breaks[i])), color = "red")
}
# p.axis
final.plot <- arrangeGrob(main.plot, p.axis, nrow = 2)
grid.draw(final.plot)
Any help appreciated.
Note: In the code below, I assume each plot in your grid has equal width / height, & used equally spaced label positions. If that's not the case, you'll have to adjust the positions yourself.
Adding x-axis to main.plot:
library(gtable)
# create additional row below main plot
# height may vary, depending on your actual plot dimensions
main.plot.x <- gtable_add_rows(main.plot, heights = unit(20, "points"))
# optional: check results to verify position of the new row
dev.off(); gtable_show_layout(main.plot.x)
# create x-axis labels as a text grob
x.axis.grob <- textGrob(label = x.breaks,
x = unit(seq(0, 1, length.out = length(x.breaks)), "npc"),
y = unit(0.75, "npc"),
just = "top")
# insert text grob
main.plot.x <- gtable_add_grob(main.plot.x,
x.axis.grob,
t = nrow(main.plot.x),
l = 1,
r = ncol(main.plot.x),
clip = "off")
# check results
dev.off(); grid.draw(main.plot.x)
You can do the same for the y-axis:
# create additional col
main.plot.xy <- gtable_add_cols(main.plot.x, widths = unit(20, "points"), pos = 0)
# create y-axis labels as a text grob
y.breaks <- c("a", "b", "c") # placeholder, since this wasn't specified in the question
y.axis.grob <- textGrob(label = y.breaks,
x = unit(0.75, "npc"),
y = unit(seq(0, 1, length.out = length(y.breaks)), "npc"),
just = "right")
# add text grob into main plot's gtable
main.plot.xy <- gtable_add_grob(main.plot.xy,
y.axis.grob,
t = 1,
l = 1,
b = nrow(main.plot.xy) - 1,
clip = "off")
# check results
dev.off(); grid.draw(main.plot.xy)
(Note that the above order of x-axis followed by y-axis should not be switched blindly. If you are adding rows / columns, it's good habit to use gtable_show_layout() frequently to check the latest gtable object dimensions, & ensure that you are inserting new grobs into the right cells.)
Finally, let's add some buffer on all sides, so that the labels & plot borders don't get cut off:
final.plot <- gtable_add_padding(main.plot.xy,
padding = unit(20, "points"))
dev.off(); grid.draw(final.plot)

Ploting 3 maps in 2 rows using R tmap package

I'm trying to save a plot using 3 maps made by the tmap package, with the larger one at the top, and the other 2 at the bottom like the example above:
But using tmap_arrange() provided by the package for this kind of procedure, it gives me the followig:
data(World)
p1 <- tm_shape(World)+tm_polygons()
p2 <- tm_shape(World[World$continent=='South America',])+tm_polygons()
p3 <- tm_shape(World[World$name=='Brazil',])+tm_polygons()
tmap_arrange(p1,p2,p3,nrow=2)
I've tried to use many options, like export the maps as images and then import again to R to compose a full image using par() and/or split.screen(), but also doesn't work properly.
There is any way to work around this and get the wanted result?
Thanks in advance!
One hackish way would be to use the grid package functionality. Grab the output of each plot/map and store it as a gTree object and then try to arrange the new objects in a grid.
library(tmap)
library(cowplot) # for plot_grid() function - good to arrange multiple plots into a grid
library(grid)
library(gridGraphics)
data(World)
tm_shape(World) + tm_polygons()
g1 <- grid.grab()
tm_shape(World[World$continent == 'South America', ]) + tm_polygons()
g2 <- grid.grab()
tm_shape(World[World$name == 'Brazil', ]) + tm_polygons()
g3 <- grid.grab()
# Try to arrange the plots into a grid using cowplot::plot_grid().
# First bind the p2 and p3 as one plot;
# adjust distance between them by forcing a NULL plot in between.
p23 <- plot_grid(g2, NULL, g3, rel_widths = c(1, -0.7, 1), nrow = 1)
plot_grid(g1, p23, nrow = 2, scale = c(0.8, 1))
I could not figure it out how to make it respond to the align argument though :/ But maybe this puts you in some exploring direction or others can edit/improve this answer.
# Save the plot
ggsave(filename = "tmap-arrange-grid-1.png",
width = 10, height = 6, units = "cm", dpi = 150)
Note that, initially I thought that I could explore with adding a NULL object to tmap_arrange like tmap_arrange(p1, NULL, p2, p3, nrow = 2), but unfortunately, it does not accept it.
Another approach, inspired from this related question could be something along these lines:
library(grid)
grid.newpage()
pushViewport(viewport(layout = grid.layout(nrow = 2, ncol = 2)))
print(p1, vp = viewport(layout.pos.row = 1, layout.pos.col = 1:2))
print(p2, vp = viewport(layout.pos.row = 2, layout.pos.col = 1))
print(p3, vp = viewport(layout.pos.row = 2, layout.pos.col = 2))
Again, here, I didn't have the time to explore with aligning the plots perfectly, but others might improve this answer.

Positioning of grobs

I want to plot data for a linear model in a main plot and a plot of the effects (forest plot) as a subplot using arrangeGrob.
Here are the data:
set.seed(1)
main.df <- data.frame(sample=c(paste("E.plus.A.plus",1:3,sep="_"),paste("E.minus.A.plus",1:3,sep="_"),paste("E.plus.A.minus",1:3,sep="_"),paste("E.minus.A.minus",1:3,sep="_")),
replicate=rep(1:3,4),cpm=c(rnorm(12)),
factor.level=factor(c(rep("E.plus.A.plus",3),rep("E.minus.A.plus",3),rep("E.plus.A.minus",3),rep("E.minus.A.minus",3)),
levels=c("E.plus.A.plus","E.minus.A.plus","E.plus.A.minus","E.minus.A.minus")))
effects.df <- data.frame(factor.level=c("E.plus.A.plus-E.minus.A.plus","E.plus.A.plus-E.plus.A.minus","E.plus.A.plus-E.minus.A.minus",
"E.minus.A.plus-E.plus.A.minus","E.minus.A.plus-E.minus.A.minus","E.plus.A.minus-E.minus.A.minus"),
effect=rnorm(6),effect.df=runif(6,0,0.5),p.value=runif(6,0,1),y=1:6+0.2)
effects.df$effect.high <- effects.df$effect+effects.df$effect.df
effects.df$effect.low <- effects.df$effect-effects.df$effect.df
effects.df$factor.level <- factor(effects.df$factor.level,levels=effects.df$factor.level)
The ggplots:
require(ggplot2)
require(grid)
require(gridExtra)
main.plot <- ggplot(main.df,aes(x=replicate,y=cpm,color=factor.level))+geom_point(size=3)+
facet_wrap(~factor.level,ncol=length(levels(main.df$factor.level)))+
labs(x="replicate",y="cpm")+scale_x_continuous(breaks=unique(main.df$replicate))+theme_bw()+
theme(legend.key=element_blank(),panel.border=element_blank(),strip.background=element_blank(),axis.title=element_text(size=8),plot.title=element_text(size=9,hjust=0.5))
Which is:
sub.plot <- ggplot(effects.df,aes(x=effect,y=factor.level,color=factor.level))+geom_point(size=2.5,shape=19)+geom_errorbarh(aes(xmax=effect.high,xmin=effect.low),height=0.1)+
geom_vline(xintercept=0,linetype="longdash",colour="black",size=0.25)+theme_bw()+theme(legend.key=element_blank(),panel.border=element_blank(),strip.background=element_blank(),axis.title=element_text(size=7),axis.text=element_text(size=7),legend.text=element_text(size=7),legend.title=element_text(size=7))+
geom_text(aes(x=effects.df$effect,y=effects.df$y,label=format(signif(effects.df$p.value,2),scientific=T)),size=2.5)
And is:
And here's how I try to combine them into a single plot:
if(!is.null(dev.list())) dev.off()
blank <- grid.rect(gp = gpar(col = "white"))
sub.plot.grob <- arrangeGrob(blank,sub.plot,ncol=1)
combined.plot <- arrangeGrob(main.plot,sub.plot,ncol=2,widths=c(1,1))
grid.arrange(combined.plot)
which gives:
How do I adjust the position and dimensions so that sub.plot is smaller (all layers, e.g., text are reduced proportionally), and is positioned below the legend of main.plot?
I strongly recommend the package cowplot for this sort of task. Here, I am building three nested sets (the main plot to the left, then the two legends together at the top right, then the sub plot at the bottom right). Note the wonderful get_legend function that make pulling the legends incredibly easy.
plot_grid(
main.plot + theme(legend.position = "none")
, plot_grid(
plot_grid(
get_legend(main.plot)
, get_legend(sub.plot)
, nrow = 1
)
, sub.plot + theme(legend.position = "none")
, nrow = 2
)
, nrow = 1
)
gives:
Obviously I'd recommend changing one (or both) of the color palettes, but that should give what you want.
If you really want the legend with the sub.plot, instead of with the other legend, you could skip the get_legend.
You can also adjust the width/height of the sets using rel_widths and rel_heights if you want something other than the even sizes.
As an additional note, cowplot sets its own default theme on load. I generally revert to what I like by running theme_set(theme_minimal()) right after loading it.
here's a grid.arrange solution,
grid.arrange(grobs = replicate(4, ggplot(), simplify = FALSE),
layout_matrix = cbind(c(1,1), c(3,2), c(4, 2)),
widths = c(2,1,1))
with those bits and pieces,
get_legend <- function(p) {
g <- ggplotGrob(p)
id <- grep("guide", g$layout$name)
g$grobs[[id]]
}
leg1 <- get_legend(main.plot); leg2 <- get_legend(sub.plot)
gl <- list(main.plot + theme(legend.position = "none"),
sub.plot + theme(legend.position = "none"), leg1, leg2)
grid.arrange(grobs = gl,
layout_matrix = cbind(c(1,1), c(3,2), c(4, 2)),
widths = c(2,1,1))

R; plotting scatter plot and heat map side by side

I am trying to create an image showing a scatter plot and a heat map side by side. I create the scatter plot with geom_point and the heatmap with heatmap.2. I then use grid.draw to put them in the same image HOWEVER I cannot get the images to be the same size. How can I make sure they are the same height (this is important as they are ordered the same way and match each other)?
The code I have is:
grab_grob <- function(){
grid.echo()
grid.grab()
}
g1 <- ggplot(x, aes(x=VIPscore, y=reorder(metabolite, VIPscore))) + geom_point(colour="blue") + labs(y="", x="VIP score")
heatmap.2(xhm, cexRow=0.5, cexCol=1, Colv=FALSE, Rowv = FALSE, keep.dendro = FALSE, trace="none", key=FALSE, lwid = c(0.5, 0.5), col=heat.colors(ncol(xhm)))
g2 <- grab_grob()
grid.newpage()
lay <- grid.layout(nrow = 1, ncol=2)
pushViewport(viewport(layout = lay))
print(g1,vp=viewport(layout.pos.row = 1, layout.pos.col = 1))
grid.draw(editGrob(g2, vp=viewport(layout.pos.row = 1, layout.pos.col = 2, clip=TRUE)))
upViewport(1)
I have also tried the geom_tile (instead of heatmap.2) followed by grid.arrange; although the images now match in size colors are awful - they look flat across my data set.
A package called plotly might be of help here. Check out their API docs
library(plotly)
df <- data.frame(x = 1:1000,
y = rnorm(1000))
p1 <- plot_ly(df, x = x, y = y, mode = "markers")
p2 <- plot_ly(z = volcano, type = "heatmap")%>% layout(title = "Scatterplot and Heatmap Subplot")
subplot(p1, p2)
A drop-in solution could be to use the package "ComplexHeatmap".
https://bioconductor.org/packages/release/bioc/vignettes/ComplexHeatmap/inst/doc/s4.heatmap_annotation.html

Resources