Add a common Legend for combined ggplots - r

I have two ggplots which I align horizontally with grid.arrange. I have looked through a lot of forum posts, but everything I try seem to be commands that are now updated and named something else.
My data looks like this;
# Data plot 1
axis1 axis2
group1 -0.212201 0.358867
group2 -0.279756 -0.126194
group3 0.186860 -0.203273
group4 0.417117 -0.002592
group1 -0.212201 0.358867
group2 -0.279756 -0.126194
group3 0.186860 -0.203273
group4 0.186860 -0.203273
# Data plot 2
axis1 axis2
group1 0.211826 -0.306214
group2 -0.072626 0.104988
group3 -0.072626 0.104988
group4 -0.072626 0.104988
group1 0.211826 -0.306214
group2 -0.072626 0.104988
group3 -0.072626 0.104988
group4 -0.072626 0.104988
#And I run this:
library(ggplot2)
library(gridExtra)
groups=c('group1','group2','group3','group4','group1','group2','group3','group4')
x1=data1[,1]
y1=data1[,2]
x2=data2[,1]
y2=data2[,2]
p1=ggplot(data1, aes(x=x1, y=y1,colour=groups)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)
p2=ggplot(data2, aes(x=x2, y=y2,colour=groups)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)
#Combine plots
p3=grid.arrange(
p1 + theme(legend.position="none"), p2+ theme(legend.position="none"), nrow=1, widths = unit(c(10.,10), "cm"), heights = unit(rep(8, 1), "cm")))
How would I extract the legend from any of these plots and add it to the bottom/centre of the combined plot?

You may also use ggarrange from ggpubr package and set "common.legend = TRUE":
library(ggpubr)
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data = dsamp, colour = clarity)
p2 <- qplot(cut, price, data = dsamp, colour = clarity)
p3 <- qplot(color, price, data = dsamp, colour = clarity)
p4 <- qplot(depth, price, data = dsamp, colour = clarity)
ggarrange(p1, p2, p3, p4, ncol=2, nrow=2, common.legend = TRUE, legend="bottom")

Update 2021-Mar
This answer has still some, but mostly historic, value. Over the years since this was posted better solutions have become available via packages. You should consider the newer answers posted below.
Update 2015-Feb
See Steven's answer below
df1 <- read.table(text="group x y
group1 -0.212201 0.358867
group2 -0.279756 -0.126194
group3 0.186860 -0.203273
group4 0.417117 -0.002592
group1 -0.212201 0.358867
group2 -0.279756 -0.126194
group3 0.186860 -0.203273
group4 0.186860 -0.203273",header=TRUE)
df2 <- read.table(text="group x y
group1 0.211826 -0.306214
group2 -0.072626 0.104988
group3 -0.072626 0.104988
group4 -0.072626 0.104988
group1 0.211826 -0.306214
group2 -0.072626 0.104988
group3 -0.072626 0.104988
group4 -0.072626 0.104988",header=TRUE)
library(ggplot2)
library(gridExtra)
p1 <- ggplot(df1, aes(x=x, y=y,colour=group)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8) + theme(legend.position="bottom")
p2 <- ggplot(df2, aes(x=x, y=y,colour=group)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)
#extract legend
#https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
g_legend<-function(a.gplot){
tmp <- ggplot_gtable(ggplot_build(a.gplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend)}
mylegend<-g_legend(p1)
p3 <- grid.arrange(arrangeGrob(p1 + theme(legend.position="none"),
p2 + theme(legend.position="none"),
nrow=1),
mylegend, nrow=2,heights=c(10, 1))
Here is the resulting plot:

A new, attractive solution is to use patchwork. The syntax is very simple:
library(ggplot2)
library(patchwork)
p1 <- ggplot(df1, aes(x = x, y = y, colour = group)) +
geom_point(position = position_jitter(w = 0.04, h = 0.02), size = 1.8)
p2 <- ggplot(df2, aes(x = x, y = y, colour = group)) +
geom_point(position = position_jitter(w = 0.04, h = 0.02), size = 1.8)
combined <- p1 + p2 & theme(legend.position = "bottom")
combined + plot_layout(guides = "collect")
Created on 2019-12-13 by the reprex package (v0.2.1)

Roland's answer needs updating. See: https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
This method has been updated for ggplot2 v1.0.0.
library(ggplot2)
library(gridExtra)
library(grid)
grid_arrange_shared_legend <- function(...) {
plots <- list(...)
g <- ggplotGrob(plots[[1]] + theme(legend.position="bottom"))$grobs
legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
lheight <- sum(legend$height)
grid.arrange(
do.call(arrangeGrob, lapply(plots, function(x)
x + theme(legend.position="none"))),
legend,
ncol = 1,
heights = unit.c(unit(1, "npc") - lheight, lheight))
}
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data=dsamp, colour=clarity)
p2 <- qplot(cut, price, data=dsamp, colour=clarity)
p3 <- qplot(color, price, data=dsamp, colour=clarity)
p4 <- qplot(depth, price, data=dsamp, colour=clarity)
grid_arrange_shared_legend(p1, p2, p3, p4)
Note the lack of ggplot_gtable and ggplot_build. ggplotGrob is used instead. This example is a bit more convoluted than the above solution but it still solved it for me.

I suggest using cowplot. From their R vignette:
# load cowplot
library(cowplot)
# down-sampled diamonds data set
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
# Make three plots.
# We set left and right margins to 0 to remove unnecessary spacing in the
# final plot arrangement.
p1 <- qplot(carat, price, data=dsamp, colour=clarity) +
theme(plot.margin = unit(c(6,0,6,0), "pt"))
p2 <- qplot(depth, price, data=dsamp, colour=clarity) +
theme(plot.margin = unit(c(6,0,6,0), "pt")) + ylab("")
p3 <- qplot(color, price, data=dsamp, colour=clarity) +
theme(plot.margin = unit(c(6,0,6,0), "pt")) + ylab("")
# arrange the three plots in a single row
prow <- plot_grid( p1 + theme(legend.position="none"),
p2 + theme(legend.position="none"),
p3 + theme(legend.position="none"),
align = 'vh',
labels = c("A", "B", "C"),
hjust = -1,
nrow = 1
)
# extract the legend from one of the plots
# (clearly the whole thing only makes sense if all plots
# have the same legend, so we can arbitrarily pick one.)
legend_b <- get_legend(p1 + theme(legend.position="bottom"))
# add the legend underneath the row we made earlier. Give it 10% of the height
# of one plot (via rel_heights).
p <- plot_grid( prow, legend_b, ncol = 1, rel_heights = c(1, .2))
p

#Giuseppe, you may want to consider this for a flexible specification of the plots arrangement (modified from here):
library(ggplot2)
library(gridExtra)
library(grid)
grid_arrange_shared_legend <- function(..., nrow = 1, ncol = length(list(...)), position = c("bottom", "right")) {
plots <- list(...)
position <- match.arg(position)
g <- ggplotGrob(plots[[1]] + theme(legend.position = position))$grobs
legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
lheight <- sum(legend$height)
lwidth <- sum(legend$width)
gl <- lapply(plots, function(x) x + theme(legend.position = "none"))
gl <- c(gl, nrow = nrow, ncol = ncol)
combined <- switch(position,
"bottom" = arrangeGrob(do.call(arrangeGrob, gl),
legend,
ncol = 1,
heights = unit.c(unit(1, "npc") - lheight, lheight)),
"right" = arrangeGrob(do.call(arrangeGrob, gl),
legend,
ncol = 2,
widths = unit.c(unit(1, "npc") - lwidth, lwidth)))
grid.newpage()
grid.draw(combined)
}
Extra arguments nrow and ncol control the layout of the arranged plots:
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data = dsamp, colour = clarity)
p2 <- qplot(cut, price, data = dsamp, colour = clarity)
p3 <- qplot(color, price, data = dsamp, colour = clarity)
p4 <- qplot(depth, price, data = dsamp, colour = clarity)
grid_arrange_shared_legend(p1, p2, p3, p4, nrow = 1, ncol = 4)
grid_arrange_shared_legend(p1, p2, p3, p4, nrow = 2, ncol = 2)

If you are plotting the same variables in both plots, the simplest way would be to combine the data frames into one, then use facet_wrap.
For your example:
big_df <- rbind(df1,df2)
big_df <- data.frame(big_df,Df = rep(c("df1","df2"),
times=c(nrow(df1),nrow(df2))))
ggplot(big_df,aes(x=x, y=y,colour=group))
+ geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)
+ facet_wrap(~Df)
Another example using the diamonds data set. This shows that you can even make it work if you have only one variable common between your plots.
diamonds_reshaped <- data.frame(price = diamonds$price,
independent.variable = c(diamonds$carat,diamonds$cut,diamonds$color,diamonds$depth),
Clarity = rep(diamonds$clarity,times=4),
Variable.name = rep(c("Carat","Cut","Color","Depth"),each=nrow(diamonds)))
ggplot(diamonds_reshaped,aes(independent.variable,price,colour=Clarity)) +
geom_point(size=2) + facet_wrap(~Variable.name,scales="free_x") +
xlab("")
Only tricky thing with the second example is that the factor variables get coerced to numeric when you combine everything into one data frame. So ideally, you will do this mainly when all your variables of interest are the same type.

#Guiseppe:
I have no idea of Grobs etc whatsoever, but I hacked together a solution for two plots, should be possible to extend to arbitrary number but its not in a sexy function:
plots <- list(p1, p2)
g <- ggplotGrob(plots[[1]] + theme(legend.position="bottom"))$grobs
legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
lheight <- sum(legend$height)
tmp <- arrangeGrob(p1 + theme(legend.position = "none"), p2 + theme(legend.position = "none"), layout_matrix = matrix(c(1, 2), nrow = 1))
grid.arrange(tmp, legend, ncol = 1, heights = unit.c(unit(1, "npc") - lheight, lheight))

If the legend is the same for both plots, there is a simple solution using grid.arrange(assuming you want your legend to align with both plots either vertically or horizontally). Simply keep the legend for the bottom-most or right-most plot while omitting the legend for the other. Adding a legend to just one plot, however, alters the size of one plot relative to the other. To avoid this use the heights command to manually adjust and keep them the same size. You can even use grid.arrange to make common axis titles. Note that this will require library(grid) in addition to library(gridExtra). For vertical plots:
y_title <- expression(paste(italic("E. coli"), " (CFU/100mL)"))
grid.arrange(arrangeGrob(p1, theme(legend.position="none"), ncol=1),
arrangeGrob(p2, theme(legend.position="bottom"), ncol=1),
heights=c(1,1.2), left=textGrob(y_title, rot=90, gp=gpar(fontsize=20)))
Here is the result for a similar graph for a project I was working on:

Related

Multipanel plot with ggplot2?

I have bar charts faceted by stock symbol name:
I would like to be able to add a little "subplot" of an indicator below each plot, like this:
Is this possible with ggplot2? I thought of a secondary axis, but most often there is no linear relation between the main plot axis and the indicator axis (so using sec_axis is not an option).
Thanks!
Since these appear to be categorically different types of plots, I think you'll have better luck creating separate plots and then rendering them together. Here's one solution using cowplot package:
library(ggplot2)
library(cowplot)
#sample data
df <- data.frame(x = 1:100, y = cumsum(rnorm(100)), volume = sample(1:10, 100, replace = TRUE))
p1 <- ggplot(df, aes(x,y)) +
geom_line()
p2 <- ggplot(df, aes(x,volume)) +
geom_bar(stat = "identity")
plot_grid(p1, p2, align = "v", ncol = 1, rel_heights = c(.8, .2))
Created on 2019-01-25 by the reprex package (v0.2.1)
Edit
Continuing to build on this kludgy example to support the concept of faceting. Ignore the ugly graph, it's smooshed due to image size constraints.
library(ggplot2)
library(cowplot)
library(gridExtra)
#sample data
df <- data.frame(x = 1:100, y = cumsum(rnorm(100)), volume = sample(1:10, 400, replace = TRUE), group = letters[1:4])
plots <- list()
for (j in unique(df$group)){
plot_df <- df[df$group == j, ]
p1 <- ggplot(plot_df, aes(x,y)) +
geom_line() +
facet_wrap(~group) +
xlab("")
p2 <- ggplot(plot_df, aes(x,volume)) +
geom_bar(stat = "identity")
p_out <- plot_grid(p1, p2, align = "v", ncol = 1, rel_heights = c(.7, .3))
plots[[j]] <- p_out
}
do.call(grid.arrange, plots)
Created on 2019-01-25 by the reprex package (v0.2.1)

Add a common legend

I was trying to do a multiplot with ggplot2.
This was my initial code
nucmer_s1 <- ggarrange(eight_uniform, ten_uniform, twelve_uniform, fourteen_uniform, sixteen_uniform,
ncol=3, nrow=2, common.legend = TRUE, legend="bottom")
getting this error
Error in plot$scales : $ operator is invalid for atomic vectors
then.
annotate_figure(nucmer_s1,
top = text_grob("Genomas validados con distribución de datos equilibrada",
color = "black", face = "bold", size = 12))
however I obtain the graphic
But I need to put a title in the each plot a title so I changed to this one
nucmer_s1 <-grid.arrange(
eight_uniform + ggtitle("8 genomas"),
ten_uniform + ggtitle("10 genomas"),
twelve_uniform + ggtitle("12 genomas"),
fourteen_uniform + ggtitle("14 genomas"),
sixteen_uniform + ggtitle("16 genomas"),
ncol=3, nrow=2, common.legend = TRUE, legend="bottom")
but I got
Error in gList(list(grobs = list(list(x = 0.5, y = 0.5, width = 1, height = 1, :
only 'grobs' allowed in "gList"
Además: Warning messages:
1: In grob$wrapvp <- vp : Realizando coercion de LHD a una lista
2: In grob$wrapvp <- vp : Realizando coercion de LHD a una lista
so I erase the common.legend part
and got this plot
So I have two questions:
Is there a way to put a title in each plot with the grey box without using facet_grid (cause I don't have that info in the data)? and
Is there any way to put the legend in the blank side of a multi-plot?
Thank so much for your help
lemon or cowplot packages have really nice built-in functions to deal with shared legend between plots
example from lemon package
library(ggplot2)
library(grid)
library(gtable)
library(lemon)
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
d <- ggplot(dsamp, aes(carat, price)) +
geom_point(aes(colour = clarity)) +
theme(legend.position = c(0.06, 0.75))
d3 <- d +
facet_wrap(~cut, ncol=3) +
scale_color_discrete(guide=guide_legend(ncol=3))
# Use gtable_show_names to display the names of the facetted panels
gtable_show_names(d3)
# So to place the legend in a specific panel, give its name:
reposition_legend(d3, 'center', panel='panel-3-2')
example from cowplot package
library(cowplot)
# Make three plots.
# We set left and right margins to 0 to remove unnecessary spacing in the
# final plot arrangement.
p1 <- qplot(carat, price, data=dsamp, colour=clarity) +
theme(plot.margin = unit(c(6,0,6,0), "pt"))
p2 <- qplot(depth, price, data=dsamp, colour=clarity) +
theme(plot.margin = unit(c(6,0,6,0), "pt")) + ylab("")
p3 <- qplot(color, price, data=dsamp, colour=clarity) +
theme(plot.margin = unit(c(6,0,6,0), "pt")) + ylab("")
# arrange the three plots in a single row
prow <- plot_grid( p1 + theme(legend.position="none"),
p2 + theme(legend.position="none"),
p3 + theme(legend.position="none"),
align = 'vh',
labels = c("A", "B", "C"),
hjust = -1,
nrow = 1
)
# extract the legend from one of the plots
# (clearly the whole thing only makes sense if all plots
# have the same legend, so we can arbitrarily pick one.)
legend <- get_legend(p1)
# add the legend to the row we made earlier. Give it one-third of the width
# of one plot (via rel_widths).
p <- plot_grid( prow, legend, rel_widths = c(3, .3))
p
Created on 2018-04-14 by the reprex package (v0.2.0).

ggplot faceting with different specifications of ylims

I want to create a ggplot figure with six panels in R. The first five facets should represent five different subsets of data in bar charts, and the final facet should represent the whole data. I further want to have a fixed y-axis scale across the first five facets, but a different scale in the final facet. I am aware that it is currently not possible to specify individual ylims for each facet within the ggplot functionality (https://github.com/hadley/ggplot2/issues/187), but am wondering if I can do something similar using grid and possibly gtable packages, neither of which I'm very familiar with at the moment.
The following is my attempt. I replace the final facet with a facet in another figure.
library("ggplot2")
library("dplyr")
library("grid")
# create data
set.seed(1)
d1 <- data_frame(
value = rnorm(3 * 5, mean = 30, sd = 10),
f = rep(LETTERS[1:3], 5),
p = rep(paste("Panel", 1:5), each = 3)
)
d2 <- d1 %>%
mutate(p = "Total") %>%
rbind(d1)
# make initial figures
plot1 <- ggplot(d2, aes(f, value)) +
geom_bar(stat = "identity") +
facet_wrap(~ p) +
coord_cartesian(ylim = c(0, 50))
plot2 <- ggplot(d2, aes(f, value)) +
geom_bar(stat = "identity") +
facet_wrap(~ p, scales = "free_y")
# extract their grobs
g1 <- ggplotGrob(plot1)
g2 <- ggplotGrob(plot2)
# replace the final facet of plot1 with the final facet of plot2
g1[["grobs"]][[7]] <- g2[["grobs"]][[7]]
g1[["grobs"]][[19]] <- g2[["grobs"]][[19]]
g1[["grobs"]][[25]] <- g2[["grobs"]][[25]]
# draw the figure
grid.newpage()
grid.draw(g1)
And here's what I get.
As can be seen, however, the y-axis label of the final facet overlaps with the preceding facet. Does anyone know a way to avoid the overlap e.g., by making the final facet smaller?
One approach is to extract the "Total" plot from "g2", then insert it into "g1", but first remove the "Total" plot from "g1". But you will notice that the x-axis tick mark labels do not align across the facets.
# Load packages
library(ggplot2)
library(dplyr)
library(gtable)
library(grid)
# create data
set.seed(1)
d1 <- data.frame(
value = rnorm(3 * 5, mean = 30, sd = 10),
f = rep(LETTERS[1:3], 5),
p = rep(paste("Panel", 1:5), each = 3)
)
d2 <- d1 %>%
mutate(p = "Total") %>%
rbind(d1)
# make initial figures
plot1 <- ggplot(d2, aes(f, value)) +
geom_bar(stat = "identity") +
facet_wrap(~ p) +
coord_cartesian(ylim = c(0, 50))
plot2 <- ggplot(d2, aes(f, value)) +
geom_bar(stat = "identity") +
facet_wrap(~ p, scales = "free_y")
# Get the ggplot grobs
g1 <- ggplotGrob(plot1)
g2 <- ggplotGrob(plot2)
# Extract "Total" plot from g2
keep = g2$layout$name %in% c("panel-3-2", "axis-b-3-2", "axis-l-2-3", "strip-t-3-2")
pos = subset(g2$layout, keep, c(t,l,b,r))
g2 = g2[c(min(pos$t):max(pos$b)), c(min(pos$l):max(pos$r))]
# Remove "Total" plot from g1
keep = !g1$layout$name %in% c("panel-3-2", "axis-b-3-2", "strip-t-3-2")
pos = subset(g1$layout, !keep, c(t,l,b,r))
g1$grobs <- g1$grobs[keep]
g1$layout <- g1$layout[keep, ]
# Insert g2 into g1
g1 = gtable_add_grob(g1, g2, t=min(pos$t), b=max(pos$b), l=min(pos$l), r=max(pos$r))
# Draw it
grid.newpage()
grid.draw(g1)
Another approach is to extract the "Total" plot from "g2" as before, but to move its y-axis to the right side of the plot (using code borrowed from here. (I tweaked your "plot2" so that the tick mark labels are better aligned in the final plot.) In this way, the "Total" panel takes as much space as the other panels, and thus the x-axis tick mark labels align, but the y-axis for the "Total" panel sticks out to the right.
# Make initial figures
plot1 <- ggplot(d2, aes(f, value)) +
geom_bar(stat = "identity") +
facet_wrap(~ p) +
coord_cartesian(ylim = c(0, 50))
plot2 <- ggplot(d2, aes(f, value)) +
geom_bar(stat = "identity") +
facet_wrap(~ p, scales = "free_y") +
theme(axis.text.y = element_text(hjust = 0)) ## For better formatting of labels
# extract their grobs
g1 <- ggplotGrob(plot1)
g2 <- ggplotGrob(plot2)
# Extract "Total" plot from g2
keep = g2$layout$name %in% c("panel-3-2", "axis-b-3-2", "axis-l-2-3", "strip-t-3-2")
pos = subset(g2$layout, keep, c(t,l,b,r))
g2 = g2[c(min(pos$t):max(pos$b)), c(min(pos$l):max(pos$r))]
# Get the position of the panel in the layout
panel <- c(subset(g2$layout, grepl("panel", g2$layout$name), se = t:r))
# Get the row number of the y-axis in the layout
rn <- which(grepl("axis-l", g2$layout$name))
# Extract the axis (tick marks and axis text from the gtable)
axis.grob <- g2$grobs[[rn]]
axisl <- axis.grob$children[[2]] # Two children - get the second
axisl # Note: two grobs - tick marks and text
# Reverse the grobs and the widths
axisl$widths <- rev(axisl$widths)
axisl$grobs <- rev(axisl$grobs)
axisl$grobs[[1]]$x <- axisl$grobs[[1]]$x - unit(1, "npc") + unit(2.75, "pt")
axisl$grobs[[2]]$children[[1]]$x = unit(.15, "npc")
# Remove the column containing the left axis
g2 <- g2[, -(panel$r-1)]
## remove empty panels
keep = !g1$layout$name %in% c("panel-3-2", "axis-b-3-2", "strip-t-3-2")
pos = subset(g1$layout, !keep, c(t,l,b,r))
g1$grobs <- g1$grobs[keep]
g1$layout <- g1$layout[keep, ]
# Insert g2 into g1
g1 = gtable_add_grob(g1, g2, t = min(pos$t), b = max(pos$b), l = min(pos$l), r = max(pos$r))
# Add a new column to g1, and add the revised axisl grob to the new column.
pos = subset(g1$layout, grepl("panel", g1$layout$name), c(t,l,b,r)) # position of bottom right panel
g1 <- gtable_add_cols(g1, axisl$widths, max(pos$r))
g1 <- gtable_add_grob(g1, axisl, t = max(pos$b), l = max(pos$r)+1, r = max(pos$r)+2)
# Draw it
grid.newpage()
grid.draw(g1)

Is there a way to have a barplot and a stacked barplot on the same graph using barplot or ggplot?

I have two pieces of data that I want to overlay onto the same plot. I've looked at several ggplot articles and I don't think it's possible within ggplot. So I have been using barplot. I have 5 tiers and I'm plotting total dollars by tier as a solid bar.
Then I have another piece of data that represents the number of tasks within those tiers by two different types of workers. I have this as a stacked bar plot. But I want to show them on the same graph with the total dollar amount as one bar and then the corresponding stacked bar next to it.
Here are the plots:
The data for the first graph looks like this (it's a table):
1 2 3 4 5
0 9 340 97 812 4271
1 1 417 156 3163 11314
The data for the second graph looks like this (this is a dataset):
Tier variable value
1 1 Opp_Amt 16200.00
2 2 Opp_Amt 116067.50
3 3 Opp_Amt 35284.12
4 4 Opp_Amt 278107.10
5 5 Opp_Amt 694820.29
I want to put the graphs on top of each other but the bars keep overlapping and I want them to appear side by side by tier.
Code for what I have so far.
par(mar=c(2.5, 4, 4, 4)+2)
## Plot first set of data and draw its axis
barplot(data1$value, axes=FALSE,ylim=c(0,700000), xlab="", ylab="",
col="black",space=-10,main="Work Score")
axis(2, ylim=c(0,700000),col="black",las=1) ## las=1 makes horizontal labels
mtext("Total Opportunity Amount",side=2,line=3.5)
box()
## Allow a second plot on the same graph
par(new=TRUE)
## Plot the second plot and put axis scale on right
m <- barplot(counts, xlab="", ylab="", ylim=c(0,16000),axes=FALSE, col=c("red","darkblue"),space=3,width=0.5,density=20)
## a little farther out (line=4) to make room for labels
mtext("Task Ratio: Outbound to AE",side=4,col="red",line=3.5)
axis(4, ylim=c(0,16000), col="red",col.axis="black",las=1)
And it gives me this
Using ggplot, I would do something like one of these. They plot the two sets of data separately. The first arranges the data into one dataframe, then uses facet_wrap() to position the plots side-by-side. The second generates the two plot objects separately, then combines the two plots and the legend into a combined plot.
But if you really need the "dual y-axis" approach, then with some fiddling, and using the plots' layouts and gtable functions, it can be done (using code borrowed from here).
Like this:
library(ggplot2)
library(gtable)
library(plyr)
df1 <- data.frame(Tier = rep(1:5, each = 2),
y = c(9, 1, 340, 417, 97, 156, 812, 3063, 4271, 11314),
gp = rep(0:1, 5))
df2 <- read.table(text = "
Tier variable value
1 Opp_Amt 16200.00
2 Opp_Amt 116067.50
3 Opp_Amt 35284.12
4 Opp_Amt 278107.10
5 Opp_Amt 694820.29", header = TRUE)
dfA = df1
dfB = df2
names(dfA) = c("Tier", "Value", "gp")
dfA$var = "Task Ratio"
dfB = dfB[,c(1,3)]
dfB$gp = 3
dfB$var = "Total Opportunity Amount"
names(dfB) = names(dfA)
df = rbind(dfA, dfB)
df$var = factor(df$var)
df$var = factor(df$var, levels = rev(levels(df$var)))
ggplot(df, aes(Tier, Value, fill = factor(gp))) +
geom_bar(position = "stack", stat = "identity") +
facet_wrap( ~ var, scale = "free_y") +
scale_fill_manual("Group", breaks = c("0","1"), values = c("#F8766D", "#00BFC4", "black")) +
theme_bw() +
theme(panel.spacing = unit(2, "lines"),
panel.grid = element_blank())
Or this:
p1 <- ggplot(df1, aes(factor(Tier), y, fill = factor(gp))) +
geom_bar(position = "stack", stat = "identity") +
#guides(fill = FALSE) +
scale_y_continuous("Task Ratio",
limit = c(0, 1.1*max(ddply(df1, .(Tier), summarise, sum = sum(y)))),
expand = c(0,0)) +
scale_x_discrete("Tier") +
theme_bw() +
theme(panel.grid = element_blank())
p2 <- ggplot(df2, aes(factor(Tier), value)) +
geom_bar(stat = "identity") +
scale_y_continuous("Total Opportunity Amount", limit = c(0, 1.1*max(df2$value)), expand = c(0,0)) +
scale_x_discrete("Tier") +
theme_bw() +
theme(panel.grid = element_blank())
# Get the ggplot grobs,
# And get the legend from p1
g1 <- ggplotGrob(p1)
leg = gtable_filter(g1, "guide-box")
legColumn = g1$layout[which(g1$layout$name == "guide-box"), "l"]
g1 = g1[,-legColumn]
g2 <- ggplotGrob(p2)
# Make sure the width are the same in g1 and g2
library(grid)
maxWidth = unit.pmax(g1$widths, g2$widths)
g1$widths = as.list(maxWidth)
g2$widths = as.list(maxWidth)
# Combine g1, g2 and the legend
library(gridExtra)
grid.arrange(arrangeGrob(g2, g1, nrow = 1), leg,
widths = unit.c(unit(1, "npc") - leg$width, leg$width), nrow=1)
Or the dual y-axis approach (But not recommended for reasons given in #Phil's post):
width1 = 0.6 # width of bars in p1
width2 = 0.2 # width of bars in p2
pos = .5*width1 + .5*width2 # positioning bars in p2
p1 <- ggplot(df1, aes(factor(Tier), y, fill = factor(gp))) +
geom_bar(position = "stack", stat = "identity", width = width1) +
guides(fill = FALSE) +
scale_y_continuous("",
limit = c(0, 1.1*max(ddply(df1, .(Tier), summarise, sum = sum(y)))),
expand = c(0,0)) +
scale_x_discrete("Tier") +
theme_bw() +
theme(panel.grid = element_blank(),
axis.text.y = element_text(colour = "red", hjust = 0, margin = margin(l = 2, unit = "pt")),
axis.ticks.y = element_line(colour = "red"))
p2 <- ggplot(df2, aes(factor(Tier), value)) +
geom_blank() +
geom_bar(aes(x = Tier - pos), stat = "identity", width = width2) +
scale_y_continuous("", limit = c(0, 1.1*max(df2$value)), expand = c(0,0)) +
theme_bw() +
theme(panel.grid = element_blank())
# Get ggplot grobs
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
# Get locations of the panels in g1
pp1 <- c(subset(g1$layout, name == "panel", se = t:r))
## Get bars from g2 and insert them into the panel in g1
g <- gtable_add_grob(g1, g2$grobs[[which(g2$layout$name == "panel")]][[4]][[4]], pp1$t, pp1$l)
# Grab axis from g1, reverse elements, and put it on the right
index <- which(g1$layout$name == "axis-l")
grob <- g1$grobs[[index]]
axis <- grob$children[[2]]
axis$widths <- rev(axis$widths)
axis$grobs <- rev(axis$grobs)
axis$grobs[[1]]$x <- axis$grobs[[1]]$x - unit(1, "npc") + unit(3, "pt")
g <- gtable_add_cols(g, g1$widths[g1$layout[index, ]$l], pp1$r)
g <- gtable_add_grob(g, axis, pp1$t, pp1$l+1)
# Grab axis from g2, and put it on the left
index <- which(g2$layout$name == "axis-l")
grob <- g2$grobs[[index]]
axis <- grob$children[[2]]
g <- gtable_add_grob(g, rectGrob(gp = gpar(col = NA, fill = "white")), pp1$t-1, pp1$l-1, pp1$b+1)
g <- gtable_add_grob(g, axis, pp1$t, pp1$l-1)
# Add axis titles
# right axis title
RightAxisText = textGrob("Task Ratio", rot = 90, gp = gpar(col = "red"))
g <- gtable_add_cols(g, unit.c(unit(1, "grobwidth", RightAxisText) + unit(1, "line")), 5)
g <- gtable_add_grob(g, RightAxisText, pp1$t, pp1$r+2)
# left axis title
LeftAxisText = textGrob("Total Opportunity Amount", rot = 90)
g <- gtable_add_grob(g, LeftAxisText, pp1$t, pp1$l-2)
g$widths[2] <- unit.c(unit(1, "grobwidth", LeftAxisText) + unit(1, "line"))
# Draw it
grid.newpage()
grid.draw(g)
It appears you are trying to plot two variables on two different y scales on to one chart. I recommend against this, and this is considered bad practice. See, for example, #hadley 's (the author of ggplot2) answer here about a similar issue: https://stackoverflow.com/a/3101876/3022126
It is possible to plot two variables on one y axis if they have comparable scales, but the range of your two datasets do not greatly overlap.
Consider other visualisations, perhaps using two separate charts.
Try looking at the add parameter for barplot.
## Function to create alpha colors for illustration.
col2alpha <- function(col, alpha = 0.5) {
tmp <- col2rgb(col)
rgb(tmp[1]/255, tmp[2]/255, tmp[3]/255, alpha)
}
## Some fake data
dat1 <- data.frame(id = 1:4, val = c(10, 8, 6, 4))
dat2 <- data.frame(id = 1:4, val = c(4, 6, 8, 10))
barplot(dat1$val, col = col2alpha("blue"))
barplot(dat2$val, col = col2alpha("red"), add = TRUE)

Arrange many plots using gridExtra

I have spent many hours trying to fit 11 graphs in one plot and arrange them using gridExtra but I have failed miserably, so I turn to you hoping you can help.
I have 11 classifications of diamonds (call it size1) and other 11 classifications (size2) and I want to plot how the median price for each increasing size1 and increasing clarity (from 1 to 6) varies by increasing size2 of the diamonds, and plot all the 11 plots in the same graph.
I tried using gridExtra as suggested in other posts but the legend is far away to the right and all the graphs are squished to the left, can you please help me figure out how the "widths" for the legend in gridExtra has to be specified? I cannot find any good explanations. Thank you very much for your help, I really appreciate it...
I have been trying to find a good example to recreate my data frame but failed in this as well. I hope this data frame helps understand what I am trying to do, I could not get it to work and be the same as mine and some plots don't have enough data, but the important part is the disposition of the graphs using gridExtra (although if you have other comments on other parts please let me know):
library(ggplot2)
library(gridExtra)
df <- data.frame(price=matrix(sample(1:1000, 100, replace = TRUE), ncol = 1))
df$size1 = 1:nrow(df)
df$size1 = cut(df$size1, breaks=11)
df=df[sample(nrow(df)),]
df$size2 = 1:nrow(df)
df$size2 = cut(df$size2, breaks=11)
df=df[sample(nrow(df)),]
df$clarity = 1:nrow(df)
df$clarity = cut(df$clarity, breaks=6)
# Create one graph for each size1, plotting the median price vs. the size2 by clarity:
for (c in 1:length(table(df$size1))) {
mydf = df[df$size1==names(table(df$size1))[c],]
mydf = aggregate(mydf$price, by=list(mydf$size2, mydf$clarity),median);
names(mydf)[1] = 'size2'
names(mydf)[2] = 'clarity'
names(mydf)[3] = 'median_price'
assign(paste("p", c, sep=""), qplot(data=mydf, x=as.numeric(mydf$size2), y=mydf$median_price, group=as.factor(mydf$clarity), geom="line", colour=as.factor(mydf$clarity), xlab = "number of samples", ylab = "median price", main = paste("region number is ",c, sep=''), plot.title=element_text(size=10)) + scale_colour_discrete(name = "clarity") + theme_bw() + theme(axis.title.x=element_text(size = rel(0.8)), axis.title.y=element_text(size = rel(0.8)) , axis.text.x=element_text(size=8),axis.text.y=element_text(size=8) ))
}
# Couldnt get some to work, so use:
p5=p4
p6=p4
p7=p4
p8=p4
p9=p4
# Use gridExtra to arrange the 11 plots:
g_legend<-function(a.gplot){
tmp <- ggplot_gtable(ggplot_build(a.gplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend)}
mylegend<-g_legend(p1)
grid.arrange(arrangeGrob(p1 + theme(legend.position="none"),
p2 + theme(legend.position="none"),
p3 + theme(legend.position="none"),
p4 + theme(legend.position="none"),
p5 + theme(legend.position="none"),
p6 + theme(legend.position="none"),
p7 + theme(legend.position="none"),
p8 + theme(legend.position="none"),
p9 + theme(legend.position="none"),
p10 + theme(legend.position="none"),
p11 + theme(legend.position="none"),
main ="Main title",
left = ""), mylegend,
widths=unit.c(unit(1, "npc") - mylegend$width, mylegend$width), nrow=1)
I had to change the qplot loop call slightly (i.e. put the factors in the data frame) as it was throwing a mismatched size error. I'm not including that bit since that part is obviously working in your environment or it was an errant paste.
Try adjusting your widths units like this:
widths=unit(c(1000,50),"pt")
And you'll get something a bit closer to what you were probably expecting:
And, I can paste code now a few months later :-)
library(ggplot2)
library(gridExtra)
df <- data.frame(price=matrix(sample(1:1000, 100, replace = TRUE), ncol = 1))
df$size1 = 1:nrow(df)
df$size1 = cut(df$size1, breaks=11)
df=df[sample(nrow(df)),]
df$size2 = 1:nrow(df)
df$size2 = cut(df$size2, breaks=11)
df=df[sample(nrow(df)),]
df$clarity = 1:nrow(df)
df$clarity = cut(df$clarity, breaks=6)
# Create one graph for each size1, plotting the median price vs. the size2 by clarity:
for (c in 1:length(table(df$size1))) {
mydf = df[df$size1==names(table(df$size1))[c],]
mydf = aggregate(mydf$price, by=list(mydf$size2, mydf$clarity),median);
names(mydf)[1] = 'size2'
names(mydf)[2] = 'clarity'
names(mydf)[3] = 'median_price'
mydf$clarity <- factor(mydf$clarity)
assign(paste("p", c, sep=""),
qplot(data=mydf,
x=as.numeric(size2),
y=median_price,
group=clarity,
geom="line", colour=clarity,
xlab = "number of samples",
ylab = "median price",
main = paste("region number is ",c, sep=''),
plot.title=element_text(size=10)) +
scale_colour_discrete(name = "clarity") +
theme_bw() + theme(axis.title.x=element_text(size = rel(0.8)),
axis.title.y=element_text(size = rel(0.8)),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8) ))
}
# Use gridExtra to arrange the 11 plots:
g_legend<-function(a.gplot){
tmp <- ggplot_gtable(ggplot_build(a.gplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend)}
mylegend<-g_legend(p1)
grid.arrange(arrangeGrob(p1 + theme(legend.position="none"),
p2 + theme(legend.position="none"),
p3 + theme(legend.position="none"),
p4 + theme(legend.position="none"),
p5 + theme(legend.position="none"),
p6 + theme(legend.position="none"),
p7 + theme(legend.position="none"),
p8 + theme(legend.position="none"),
p9 + theme(legend.position="none"),
p10 + theme(legend.position="none"),
p11 + theme(legend.position="none"),
top ="Main title",
left = ""), mylegend,
widths=unit(c(1000,50),"pt"), nrow=1)
Edit (16/07/2015): with gridExtra >= 2.0.0, the main parameter has been renamed top.

Resources