ggplot: overlay two plots - r

Is it possible to overlay two plots with gridExtra (or other package)?
I want to rescale one plot and overlay it to a second one (specifying rescaling and coordinates)
require(ggplot2)
require(gridExtra)
df <- data.frame(value=rnorm(10), date=1:10)
p1 <- ggplot(data.frame(df), aes(value,date)) + geom_line()
p2 <- ggplot(data.frame(df), aes(value,date)) + geom_point()
to obtain something like this

Look at gtable package in combination with gridExtra. You can specify size and coordinates of plot as you like.
require(gtable)
p1 <- ggplotGrob(p1)
p2 <- ggplotGrob(p2)
gt <- gtable(widths = unit(c(1, 2), "null"), heights = unit(c(.2, 1, 1), "null"))
gt <- gtable_add_grob(gt, p2, t = 1, b = 3, l = 1, r = 2)
gt <- gtable_add_grob(gt, p1, t = 2, l = 2)
grid.draw(gt)

Related

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)

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))

y-axis for each subplot using facet_grid

I can't get the answer to this question to work.
What both me and that user want is to add axis ticks and labels to all columns when using facet_grid().
Display y-axis for each subplot when faceting
When I run the reproducable example and the solution (after adding abc=as.data.frame(abc) to fix the initial error) I receive an error message
Error in gtable_add_grob(g, grobs = list(segmentsGrob(1, 0, 1, 1),
segmentsGrob(1, : Not all inputs have either length 1 or same
length same as 'grobs
I made my own reproducible example because the original one is ehhm, a bit odd :-). It results in the same error message
require(ggplot2)
require(reshape)
require(grid)
require(gtable)
data(iris)
iris$category=rep(letters[1:4],length.out=150)
plot1=ggplot(data=iris,aes(x=1,y=Sepal.Width))+geom_boxplot()+facet_grid(Species~category)
The answer should be this:
g <- ggplotGrob(plot1)
require(gtable)
axis <- gtable_filter(g, "axis-l")[["grobs"]][[1]][["children"]][["axis"]][,2]
segment <- segmentsGrob(1,0,1,1)
panels <- subset(g$layout, name == "panel")
g <- gtable_add_grob(g, grobs=list(axis, axis), name="ticks",
t = unique(panels$t), l=tail(panels$l, -1)-1)
g <- gtable_add_grob(g, grobs=list(segmentsGrob(1,0,1,1),
segmentsGrob(1,0,1,1)),
t = unique(panels$t), l=tail(panels$l, -1)-1,
name="segments")
The answer you refer to does not apply to your situation.
To get nice placement of the tick marks and tick mark labels, I would add columns to the gtable to take the axis material. The new columns have the same width as the original y axis.
You might want to add more margin space between the panels. Do so with theme(panel.margin.x = unit(1, "lines")).
require(ggplot2)
require(grid)
require(gtable)
data(iris)
iris$category = rep(letters[1:4], length.out = 150)
plot1 = ggplot(data = iris, aes(x = 1, y = Sepal.Width))+
geom_boxplot()+
facet_grid(Species~category)
# Get the ggplot grob
g <- ggplotGrob(plot1)
# Get the yaxis
yaxis <- gtable_filter(g, "axis-l")
# Get the width of the y axis
Widths = yaxis$widths
# Add columns to the gtable to the left of the panels,
# with a width equal to yaxis width
panels <- g$layout[grepl("panel", g$layout$name), ]
pos = rev(unique(panels$l)[-1] - 1)
for(i in pos) g = gtable_add_cols(g, Widths, i)
# Add y axes to the new columns
panels <- g$layout[grepl("panel", g$layout$name), ]
posx = rev(unique(panels$l)[-1] - 1)
posy = unique(panels$t)
g = gtable_add_grob(g, rep(list(yaxis), length(posx)),
t = rep(min(posy), length(posx)), b = rep(max(posy), length(posx)), l = posx)
# Draw it
grid.newpage()
grid.draw(g)
Alternatively, place the axis in a viewport of the same width as the original y axis, but with right justification. Then, add the resulting grob to the existing margin columns between the panels, adjusting the width of those columns to suit.
require(ggplot2)
require(grid)
require(gtable)
data(iris)
iris$category = rep(letters[1:4], length.out = 150)
plot1 = ggplot(data = iris, aes(x = 1, y = Sepal.Width))+
geom_boxplot() +
facet_grid(Species ~ category )
# Get the ggplot grob
g <- ggplotGrob(plot1)
# Get the yaxis
axis <- gtable_filter(g, "axis-l")
# Get the width of the y axis
Widths = axis$width
# Place the axis into a viewport,
# of width equal to the original yaxis material,
# and positioned to be right justified
axis$vp = viewport(x = unit(1, "npc"), width = Widths, just = "right")
# Add y axes to the existing margin columns between the panels
panels <- g$layout[grepl("panel", g$layout$name), ]
posx = unique(panels$l)[-1] - 1
posy = unique(panels$t)
g = gtable_add_grob(g, rep(list(axis), length(posx)),
t = rep(min(posy), length(posx)), b = rep(max(posy), length(posx)), l = posx)
# Increase the width of the margin columns
g$widths[posx] <- unit(25, "pt")
# Or increase width of the panel margins in the original construction of plot1
# Draw it
grid.newpage()
grid.draw(g)
This is what I came up (using ggplot2_2.1.0):
g <- ggplotGrob(plot1)
axis <- gtable_filter(g, "axis-l")
newG <- gtable_add_grob(g, list(axis, axis, axis),
t = rep(4, 3), b = rep(8, 3), l = c(5, 7, 9))
grid.draw(newG)
..Which looks like this:
This is the process I went through:
g <- ggplotGrob(plot1) Create a gtable.
print(g) Look over the elements of the gtable...I'm looking for the names of the grobs that I want to mess around with. Here, it is the three grobs called "axis-l".
axis <- gtable_filter(g, "axis-l") I select my three grobs from the larger gtable object, g, and save them in a gtable called axis. Note that gtable_filter is actually selecting the grobs, not filtering them from g.
gtable_show_layout(g) Look over the layout of g so I can figure out where I want to put axis in relationship to the overall plot.
gtable_add_grob, etc. Now that I know where I'm going with it, I can append the original plot with axis.
I think that those steps are a pretty common workflow when it comes to gtable. Of course you'll have other stuff that you may what to mess around with. For example, the space that is given for all but the left-most y axis labels is not sufficient in this case. So maybe just:
newG$widths[c(5, 7, 9)] <- grid:::unit.list(axis$widths) # you won't need to wrap this in grid
grid.draw(newG)

Adding white space after ggplots using grid.arrange

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)

Juxtapose tableGrob with ggplot2 y-axis

Is there an elegant way to align the tableGrob rows with the axis breaks?
I would like to juxtapose a tableGrob and ggplot chart in R (I need to reproduce some SAS output used in previous versions of a public report). Like this minimal reproducible example:
This post got me pretty far --- the tableGrob is in the same gtable row as the body of the chart; however, it requires lots of manual fiddling to get the rows in the tableGrob to line up with the axis labels.
I also found this post. Since I'm Sweaving a public report, I would prefer not to use code that isn't readily available in a package on CRAN. That being said, the experimental version of tableGrob appears to accept heights as an argument. If this code will do the trick, and I do choose to use this experimental version, how would I calculate the appropriate row heights?
If there is not an elegant way of doing this, I found these tricks to be helpful:
set fontsize AND cex in tableGrob to match ggplot2
set padding.v to space table rows in tableGrob
modify coordinate limits to accomodate column labels and align with bottom of last row
My MRE code:
library(ggplot2)
library(gridExtra)
library(gtable)
theme_set(theme_bw(base_size = 8))
df <- head(mtcars,10)
df$cars <- row.names(df)
df$cars <- factor(df$cars, levels=df$cars[order(df$disp, decreasing=TRUE)], ordered=TRUE)
p <- ggplot(data=df, aes(x=hp, y=cars)) +
geom_point(aes(x=hp, y=cars)) +
scale_y_discrete(limits=levels(df$cars))+
theme(axis.title.y = element_blank()) +
coord_cartesian(ylim=c(0.5,length(df$cars)+1.5))
t <- tableGrob(df[,c("mpg","cyl","disp","cars")],
cols=c("mpg","cyl","disp","cars"),
gpar.coretext = gpar(fontsize = 8, lineheight = 1, cex = 0.8),
gpar.coltext = gpar(fontsize = 8, lineheight = 1, cex = 0.8),
show.rownames = FALSE,
show.colnames = TRUE,
equal.height = TRUE,
padding.v = unit(1.65, "mm"))
g <- NULL
g <- ggplotGrob(p)
g <- gtable_add_cols(g, unit(2,"in"), 0)
g <- gtable_add_grob(g, t, t=3, b=3, l=1, r=1)
png('./a.png', width = 5, height = 2, units = "in", res = 100)
grid.draw(g)
dev.off()
I have left the car names on the y-axis breaks for troubleshooting purposes, but ultimately I will remove them.
There's now this experimental version of gtable_table
table <- gtable_table(df[,c("mpg","cyl","disp","cars")],
heights = unit(rep(1,nrow(df)), "null"))
g <- ggplotGrob(p)
g <- gtable_add_cols(g, sum(table$widths), 0)
g <- gtable_add_grob(g, table, t=3, b=3, l=1, r=1)
grid.newpage()
grid.draw(g)
#Baptiste's answer expanded to demonstrate column labels and cell parameters:
library(ggplot2)
library(gridExtra)
## I manually changed the dependency on
install.packages(".//gtable_0.2.tar.gz", repos = NULL, type="source")
## The forked version of gtable requires R version 3.2.0
## which is currently in development (as of 9/17/2014) due to change in grid
## (https://github.com/wch/r-source/commit/850a82c30e91feb47a0b6385adcbd82988d90413)
## I have not installed the development version.
## However, I was able, in limited testing, to get this to work with R 3.1.0
## and ggplot2_1.0.0
## YRMV
## The following code, commented out, may be more useful with release of R 3.2.0
## library(devtools)
## devtools::install_github("baptiste/gtable")
library(gtable)
theme_set(theme_bw(base_size = 10))
df <- mtcars
df$cars <- row.names(df)
df <- head(df[,c("mpg","cyl","disp","cars")],10)
df$cars <- factor(df$cars, levels=df$cars[order(df$disp, decreasing=TRUE)], ordered=TRUE)
p <- ggplot(data=df, aes(x=disp, y=cars)) +
geom_point(aes(x=disp, y=cars)) +
scale_y_discrete(limits=levels(df$cars))+
theme(axis.title.y = element_blank()) +
coord_cartesian(ylim = c(0.5,nrow(df)+1))
core <- gtable_table(df[order(df$disp, decreasing=FALSE),],
fg.par = list(fontsize=c(8)),
bg.par = list(fill=c(rep("lightblue",4),rep("white",4)), alpha=0.5),
heights = unit(rep(1,nrow(df)), "null"))
colHead <- gtable_table(t(colnames(df)),
fg.par = list(fontsize=c(8)),
bg.par = list(fill=c(1), alpha=0.2),
heights = unit(0.5, "null"))
table1 <- rbind(colHead, core)
g <- ggplotGrob(p)
g <- gtable_add_cols(g, sum(table1$widths), 0)
g <- gtable_add_grob(g, table1, t=3, b=3, l=1, r=1)
grid.newpage()
grid.draw(g)

Resources