I am trying to align multiple plots with facets. My problem is somewhat minor but irratating: I can make a plot so that the plot areas are aligned and the facets themselves are aligned, but the facet strips are not all the same width. If the labels of the facets are different lengths, then the facet strips are sized so that the text can fit within the facets. I am unable so far to find a way to make all facet strips the same width when aligning multiple plots.
Here is an example of the type of plots I want to align and my efforts to align them:
library(data.table)
library(ggplot2)
library(foreach)
library(stringr)
library(cowplot)
# example data to show how aligning faceted plots is not quite right
plotvars = c(paste0("plot1_var", 1:7), paste0("plot2_var",1:5), paste0("plot3_var",1:10))
data =
foreach(p=plotvars,.combine = "rbind") %do% {
d = data.table(plot = rep(str_extract(p,pattern = "plot[[:digit:]]"),2),
plot_variables = rep(p,2),
fill_categories = c("fill1","fill2"),
number = sample(1:1000, size = 2))
d[, facet_variables := ifelse(plot=="plot1",
rep(sample(paste0("facet",1:3),size=1),2),
ifelse(plot=="plot2",
rep(sample(paste0("facet_title",1:3),size=1),2),
ifelse(plot=="plot3",
rep(sample(paste0("facet_title_longer",1:3),size=1),2),
NA)))]
d
}
# function to make stacked barplots with facets + coord_flip
make_plot = function(data, plot_var) {
ggplot(data[plot==plot_var],
aes(x=plot_variables,
y=number,
fill=fill_categories))+
geom_bar(stat="identity")+
coord_flip()+
facet_grid(facet_variables ~ .,
space="free",
scales="free")+
theme(strip.text.y = element_text(angle=0),
legend.position = "none")
}
p1 = make_plot(data=data,plot_var="plot1")
p1
p2 = make_plot(data=data,plot_var="plot2")
p2
p3 = make_plot(data=data,plot_var = "plot3")
p3
# using 'cowplot::plot_grid' gives strange re-sizing of individual bars
cowplot::plot_grid(p1,p2,p3, ncol=1,nrow=3,align = "hv")
# try gtable_rbind version
g1=ggplotGrob(p1)
g2=ggplotGrob(p2)
g3=ggplotGrob(p3)
# this plot keeps the bar widths the correct size, but the facets are still incorrectly different widths.
ggdraw(gridExtra::gtable_rbind(g1,g2,g3))
How can I make the facet strips the same width across plots?
You can achieve something like this with a labeller function that inserts a second row of blank spaces of whatever length you want. Using mtcars...
#define a function to add a second line of spaces after a given label
#and a blank line before to maintain the centre vertical alignment
#you might need to play with the appropriate value to get the width right
widen <- function(x) paste(" \n", x, "\n", paste0(rep(" ", 20), collapse=""))
mtcars %>% ggplot(aes(x = mpg)) +
geom_histogram() +
facet_grid(cyl ~ ., labeller = labeller(cyl = widen)) +
coord_flip() +
theme(strip.text.y = element_text(angle = 0))
The facet strips are wrapped inside another table, and you need to adjust the widths there. The following seems to work.
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
g3 <- ggplotGrob(p3)
# g3 has the widest strips, so get the width from there and copy over
# to the other plots
stripwidth <- g3$grobs[[13]]$widths
g1$grobs[[13]]$widths <- stripwidth
g1$grobs[[14]]$widths <- stripwidth
g1$grobs[[15]]$widths <- stripwidth
g2$grobs[[13]]$widths <- stripwidth
g2$grobs[[14]]$widths <- stripwidth
g2$grobs[[15]]$widths <- stripwidth
ggdraw(gridExtra::gtable_rbind(g1,g2,g3))
Change this part
facet_grid(facet_variables ~ .,
space="free",
scales="free")+
to
facet_grid(facet_variables ~ .,
space="fixed", # This is the difference
scales="free")+
Related
Consider the following plot:
library(ggplot2)
p <- ggplot(diamonds,
aes(x = carat, fill = cut)) +
geom_density(position = "stack") +
facet_wrap(~ color)
The facet_wrap function wraps a sequence of faceted panels into a roughly rectangular display of nrow rows and ncol columns. However, depending on the data, the actual number of panels is often a few panels short of nrow * ncol, which leaves a chunk of wasted space in the plot.
If the plot includes legend(s), the situation is exacerbated, because now we have even more wasted space due to the legend, whether it's on the right (default legend position), or one of the other three directions.
To save space, I would like to shift the legend(s) into the space created by unfilled facets.
The following works as a space-saving measure, but the legend is anchored to a corner of the plot area, with potentially a lot of space left on one side, creating an imbalanced look:
p +
theme(legend.position = c(1, 0),
legend.justification = c(1, 0))
Shifting a legend towards the centre of the blank space area by manually adjusting the legend.position/legend.justification values is a matter of trial and error, and difficult to scale if one has many faceted plots to work on.
In summary, I want a method that:
Shifts the legend(s) of a faceted plot into the space created due to empty facets.
Results in a reasonably nice-looking plot.
Is easily automated to handle many plots.
This is a recurring use case for me, and I've decided to post it along with my working solution here in case anyone else finds it useful. I haven't seen this scenario asked/answered elsewhere on Stack Overflow. If anyone has, please leave a comment and I'll be happy to answer there instead or have this marked as a duplicate, as the case may be.
The following is an extension to an answer I wrote for a previous question about utilising the space from empty facet panels, but I think it's sufficiently different to warrant its own space.
Essentially, I wrote a function that takes a ggplot/grob object converted by ggplotGrob(), converts it to grob if it isn't one, and digs into the underlying grobs to move the legend grob into the cells that correspond to the empty space.
Function:
library(gtable)
library(cowplot)
shift_legend <- function(p){
# check if p is a valid object
if(!"gtable" %in% class(p)){
if("ggplot" %in% class(p)){
gp <- ggplotGrob(p) # convert to grob
} else {
message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
return(p)
}
} else {
gp <- p
}
# check for unfilled facet panels
facet.panels <- grep("^panel", gp[["layout"]][["name"]])
empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
empty.facet.panels <- facet.panels[empty.facet.panels]
if(length(empty.facet.panels) == 0){
message("There are no unfilled facet panels to shift legend into. Returning original plot.")
return(p)
}
# establish extent of unfilled facet panels (including any axis cells in between)
empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
empty.facet.panels <- list(min(empty.facet.panels[["t"]]), min(empty.facet.panels[["l"]]),
max(empty.facet.panels[["b"]]), max(empty.facet.panels[["r"]]))
names(empty.facet.panels) <- c("t", "l", "b", "r")
# extract legend & copy over to location of unfilled facet panels
guide.grob <- which(gp[["layout"]][["name"]] == "guide-box")
if(length(guide.grob) == 0){
message("There is no legend present. Returning original plot.")
return(p)
}
gp <- gtable_add_grob(x = gp,
grobs = gp[["grobs"]][[guide.grob]],
t = empty.facet.panels[["t"]],
l = empty.facet.panels[["l"]],
b = empty.facet.panels[["b"]],
r = empty.facet.panels[["r"]],
name = "new-guide-box")
# squash the original guide box's row / column (whichever applicable)
# & empty its cell
guide.grob <- gp[["layout"]][guide.grob, ]
if(guide.grob[["l"]] == guide.grob[["r"]]){
gp <- gtable_squash_cols(gp, cols = guide.grob[["l"]])
}
if(guide.grob[["t"]] == guide.grob[["b"]]){
gp <- gtable_squash_rows(gp, rows = guide.grob[["t"]])
}
gp <- gtable_remove_grobs(gp, "guide-box")
return(gp)
}
Result:
library(grid)
grid.draw(shift_legend(p))
Nicer looking result if we take advantage of the empty space's direction to arrange the legend horizontally:
p.new <- p +
guides(fill = guide_legend(title.position = "top",
label.position = "bottom",
nrow = 1)) +
theme(legend.direction = "horizontal")
grid.draw(shift_legend(p.new))
Some other examples:
# example 1: 1 empty panel, 1 vertical legend
p1 <- ggplot(economics_long,
aes(date, value, color = variable)) +
geom_line() +
facet_wrap(~ variable,
scales = "free_y", nrow = 2,
strip.position = "bottom") +
theme(strip.background = element_blank(),
strip.placement = "outside")
grid.draw(shift_legend(p1))
# example 2: 2 empty panels (vertically aligned) & 2 vertical legends side by side
p2 <- ggplot(mpg,
aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
geom_point(size = 3) +
facet_wrap(~ class, dir = "v") +
theme(legend.box = "horizontal")
grid.draw(shift_legend(p2))
# example 3: facets in polar coordinates
p3 <- ggplot(mtcars,
aes(x = factor(1), fill = factor(cyl))) +
geom_bar(width = 1, position = "fill") +
facet_wrap(~ gear, nrow = 2) +
coord_polar(theta = "y") +
theme_void()
grid.draw(shift_legend(p3))
Nice Q&A!
I found something similar at this link. So, I thought that it would have been a nice addition to your function.
More precisely the function reposition_legend() from lemon seems to be quite what you needed, except that it doesn't look for the empty spaces.
I took inspiration from your function to find the names of the empty panels that are passed to reposition_legend() with the panel arg.
Example data and libraries:
library(ggplot2)
library(gtable)
library(lemon)
p <- ggplot(diamonds,
aes(x = carat, fill = cut)) +
geom_density(position = "stack") +
facet_wrap(~ color) +
theme(legend.direction = "horizontal")
Of course, I removed all the checks (if cases, which should be the same) just to concentrate on the important stuff.
shift_legend2 <- function(p) {
# ...
# to grob
gp <- ggplotGrob(p)
facet.panels <- grep("^panel", gp[["layout"]][["name"]])
empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
empty.facet.panels <- facet.panels[empty.facet.panels]
# establish name of empty panels
empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
names <- empty.facet.panels$name
# example of names:
#[1] "panel-3-2" "panel-3-3"
# now we just need a simple call to reposition the legend
reposition_legend(p, 'center', panel=names)
}
shift_legend2(p)
Note that this might still need some tweaking, I just thought it was something worth to be shared.
At the moment the behaviour seems OK, and the function is a few lines shorter.
Other cases.
First example:
p1 <- ggplot(economics_long,
aes(date, value, color = variable)) +
geom_line() +
facet_wrap(~ variable,
scales = "free_y", nrow = 2,
strip.position = "bottom") +
theme(strip.background = element_blank(),
strip.placement = "outside")
shift_legend2(p1)
Second example:
p2 <- ggplot(mpg,
aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
geom_point(size = 3) +
facet_wrap(~ class, dir = "v") +
theme(legend.box = "horizontal")
#[1] "panel-2-3" "panel-3-3" are the names of empty panels in this case
shift_legend2(p2)
Third example:
p3 <- ggplot(mtcars,
aes(x = factor(1), fill = factor(cyl))) +
geom_bar(width = 1, position = "fill") +
facet_wrap(~ gear, nrow = 2) +
coord_polar(theta = "y") +
theme_void()
shift_legend2(p3)
Complete function:
shift_legend2 <- function(p) {
# check if p is a valid object
if(!(inherits(p, "gtable"))){
if(inherits(p, "ggplot")){
gp <- ggplotGrob(p) # convert to grob
} else {
message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
return(p)
}
} else {
gp <- p
}
# check for unfilled facet panels
facet.panels <- grep("^panel", gp[["layout"]][["name"]])
empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]),
USE.NAMES = F)
empty.facet.panels <- facet.panels[empty.facet.panels]
if(length(empty.facet.panels) == 0){
message("There are no unfilled facet panels to shift legend into. Returning original plot.")
return(p)
}
# establish name of empty panels
empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
names <- empty.facet.panels$name
# return repositioned legend
reposition_legend(p, 'center', panel=names)
}
I think lemon::reposition_legend() identified by #RLave is the most elegant solution. However, it does hinge on knowing the names of empty facets. I wanted to share a succinct way of finding these, thus proposing yet another version of shift_legend():
shift_legend3 <- function(p) {
pnls <- cowplot::plot_to_gtable(p) %>% gtable::gtable_filter("panel") %>%
with(setNames(grobs, layout$name)) %>% purrr::keep(~identical(.x,zeroGrob()))
if( length(pnls) == 0 ) stop( "No empty facets in the plot" )
lemon::reposition_legend( p, "center", panel=names(pnls) )
}
The R package patchwork offers an elegant solution when combining multiple plots (slightly different than a single facetted ggplot). If one has three ggplot objects, p1, p2, p3, then the syntax is very straightforward:
using the + operator, "add" the plots together in facets
using the guide_area() command, specify which facet should contain the guide
if all three plots have the same legend, save space by telling patchwork to "collect" the legends with the command plot_layout(guides = 'collect').
See the code below for the essential syntax and the link below for a fully reproducible example.
library(patchwork)
# guide_area() puts legend in empty fourth facet
p1 + p2 + p3 + guide_area() +
plot_layout(guides = 'collect')
https://patchwork.data-imaginist.com/articles/guides/layout.html#controlling-guides
I would like to show in the same plot interpolated data and a histogram of the raw data of each predictor. I have seen in other threads like this one, people explain how to do marginal histograms of the same data shown in a scatter plot, in this case, the histogram is however based on other data (the raw data).
Suppose we see how price is related to carat and table in the diamonds dataset:
library(ggplot2)
p = ggplot(diamonds, aes(x = carat, y = table, color = price)) + geom_point()
We can add a marginal frequency plot e.g. with ggMarginal
library(ggExtra)
ggMarginal(p)
How do we add something similar to a tile plot of predicted diamond prices?
library(mgcv)
model = gam(price ~ s(table, carat), data = diamonds)
newdat = expand.grid(seq(55,75, 5), c(1:4))
names(newdat) = c("table", "carat")
newdat$predicted_price = predict(model, newdat)
ggplot(newdat,aes(x = carat, y = table, fill = predicted_price)) +
geom_tile()
Ideally, the histograms go even beyond the margins of the tileplot, as these data points also influence the predictions. I would, however, be already very happy to know how to plot a histogram for the range that is shown in the tileplot. (Maybe the values that are outside the range could just be added to the extreme values in different color.)
PS. I managed to more or less align histograms to the margins of the sides of a tile plot, using the method of the accepted answer in the linked thread, but only if I removed all kind of labels. It would be particularly good to keep the color legend, if possible.
EDIT:
eipi10 provided an excellent solution. I tried to modify it slightly to add the sample size in numbers and to graphically show values outside the plotted range since they also affect the interpolated values.
I intended to include them in a different color in the histograms at the side. I hereby attempted to count them towards the lower and upper end of the plotted range. I also attempted to plot the sample size in numbers somewhere on the plot. However, I failed with both.
This was my attempt to graphically illustrate the sample size beyond the plotted area:
plot_data = diamonds
plot_data <- transform(plot_data, carat_range = ifelse(carat < 1 | carat > 4, "outside", "within"))
plot_data <- within(plot_data, carat[carat < 1] <- 1)
plot_data <- within(plot_data, carat[carat > 4] <- 4)
plot_data$carat_range = as.factor(plot_data$carat_range)
p2 = ggplot(plot_data, aes(carat, fill = carat_range)) +
geom_histogram() +
thm +
coord_cartesian(xlim=xrng)
I tried to add the sample size in numbers with geom_text. I tried fitting it in the far right panel but it was difficult (/impossible for me) to adjust. I tried to put it on the main graph (which would anyway probably not be the best solution), but it didn’t work either (it removed the histogram and legend, on the right side and it did not plot all geom_texts). I also tried to add a third row of plots and writing it there. My attempt:
n_table_above = nrow(subset(diamonds, table > 75))
n_table_below = nrow(subset(diamonds, table < 55))
n_table_within = nrow(subset(diamonds, table >= 55 & table <= 75))
text_p = ggplot()+
geom_text(aes(x = 0.9, y = 2, label = paste0("N(>75) = ", n_table_above)))+
geom_text(aes(x = 1, y = 2, label = paste0("N = ", n_table_within)))+
geom_text(aes(x = 1.1, y = 2, label = paste0("N(<55) = ", n_table_below)))+
thm
library(egg)
pobj = ggarrange(p2, ggplot(), p1, p3,
ncol=2, widths=c(4,1), heights=c(1,4))
grid.arrange(pobj, leg, text_p, ggplot(), widths=c(6,1), heights =c(6,1))
I would be very happy to receive help on either or both tasks (adding sample size as text & adding values outside plotted range in a different color).
Based on your comment, maybe the best approach is to roll your own layout. Below is an example. We create the marginal plots as separate ggplot objects and lay them out with the main plot. We also extract the legend and put it outside the marginal plots.
Set-up
library(ggplot2)
library(cowplot)
# Function to 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) }
thm = list(theme_void(),
guides(fill=FALSE),
theme(plot.margin=unit(rep(0,4), "lines")))
xrng = c(0.6,4.4)
yrng = c(53,77)
Plots
p1 = ggplot(newdat, aes(x = carat, y = table, fill = predicted_price)) +
geom_tile() +
theme_classic() +
coord_cartesian(xlim=xrng, ylim=yrng)
leg = g_legend(p1)
p1 = p1 + thm[-1]
p2 = ggplot(diamonds, aes(carat)) +
geom_line(stat="density") +
thm +
coord_cartesian(xlim=xrng)
p3 = ggplot(diamonds, aes(table)) +
geom_line(stat="density") +
thm +
coord_flip(xlim=yrng)
plot_grid(
plot_grid(plotlist=list(p2, ggplot(), p1, p3), ncol=2,
rel_widths=c(4,1), rel_heights=c(1,4), align="hv", scale=1.1),
leg, rel_widths=c(5,1))
UPDATE: Regarding your comment about the space between the plots: This is an Achilles heel of plot_grid and I don't know if there's a way to fix it. Another option is ggarrange from the experimental egg package, which doesn't add so much space between plots. Also, you need to save the output of ggarrange first and then lay out the saved object with the legend. If you run ggarrange inside grid.arrange you get two overlapping copies of the plot:
# devtools::install_github('baptiste/egg')
library(egg)
pobj = ggarrange(p2, ggplot(), p1, p3,
ncol=2, widths=c(4,1), heights=c(1,4))
grid.arrange(pobj, leg, widths=c(6,1))
Since I have updated to ggplot2 2.0.0, I cannot arrange charts propperly using gridExtra. The issue is that the faceted charts will get compressed while other will expand. The widths are basically messed up. I want to arrange them similar to the way these single facet plots are: left align two graph edges (ggplot)
I put a reproducible code
library(grid) # for unit.pmax()
library(gridExtra)
plot.iris <- ggplot(iris, aes(Sepal.Length, Sepal.Width)) +
geom_point() +
facet_grid(. ~ Species) +
stat_smooth(method = "lm")
plot.mpg <- ggplot(mpg, aes(x = cty, y = hwy, colour = factor(cyl))) +
geom_point(size=2.5)
g.iris <- ggplotGrob(plot.iris) # convert to gtable
g.mpg <- ggplotGrob(plot.mpg) # convert to gtable
iris.widths <- g.iris$widths # extract the first three widths,
mpg.widths <- g.mpg$widths # same for mpg plot
max.widths <- unit.pmax(iris.widths, mpg.widths)
g.iris$widths <- max.widths # assign max. widths to iris gtable
g.mpg$widths <- max.widths # assign max widths to mpg gtable
grid.arrange(g.iris,g.mpg,ncol=1)
As you will see, the top chart, the first facet is expanded while the other 2 get compressed at the right. Bottom chart does not cover all width.
Could it be that the new ggplot2 version is messing with the gtable widths?
Anyone know a workaround?
Thank you very much
EDIT: Added picture of chart
I'm looking for something like:
one option is to massage each plot into a 3x3 gtable, where the central cell wraps all the plot panels.
Using the example from #SandyMuspratt
# devtools::install_github("baptiste/egg")
grid.draw(egg::ggarrange(plots=plots, ncol=1))
the advantage being that once in this standardised format, plots may be combined in various layouts much more easily, regardless of number of panels, legends, axes, strips, etc.
grid.newpage()
grid.draw(ggarrange(plots=list(p1, p4, p2, p3), widths = c(2,1), debug=TRUE))
I'm not sure if you're still looking for a solution, but this is fairly general. I'm using ggplot 2.1.0 (now on CRAN). It's based on this solution. I break the problem into two parts. First, I deal with the left side of the plots, making sure the widths for the axis material are the same. This has already been done by others, and there are solutions on SO. But I don't think the result looks good. I would prefer the panels to align on the right side as well. So second, the procedure makes sure the widths of the columns to the right of the panels are the same. It does this by adding a column of appropriate width to the right of each of the plots. (There's possibly neater ways to do it. There is - see #baptiste solution.)
library(grid) # for pmax
library(gridExtra) # to arrange the plots
library(ggplot2) # to construct the plots
library(gtable) # to add columns to gtables of plots without legends
mpg$g = "Strip text"
# Four fairly irregular plots: legends, faceting, strips
p1 <- ggplot(mpg, aes(displ, 1000*cty)) +
geom_point() +
facet_grid(. ~ drv) +
stat_smooth(method = "lm")
p2 <- ggplot(mpg, aes(x = hwy, y = cyl, colour = factor(cyl))) +
geom_point() +
theme(legend.position=c(.8,.6),
legend.key.size = unit(.3, "cm"))
p3 <- ggplot(mpg, aes(displ, cty, colour = factor(drv))) +
geom_point() +
facet_grid(. ~ drv)
p4 <- ggplot(mpg, aes(displ, cty, colour = factor(drv))) +
geom_point() +
facet_grid(g ~ .)
# Sometimes easier to work with lists, and it generalises nicely
plots = list(p1, p2, p3, p4)
# Convert to gtables
g = lapply(plots, ggplotGrob)
# Apply the un-exported unit.list function for grid package to each plot
g.widths = lapply(g, function(x) grid:::unit.list(x$widths))
## Part 1: Make sure the widths of left axis materials are the same across the plots
# Get first three widths from each plot
g3.widths <- lapply(g.widths, function(x) x[1:3])
# Get maximum widths for first three widths across the plots
g3max.widths <- do.call(unit.pmax, g3.widths)
# Apply the maximum widths to each plot
for(i in 1:length(plots)) g[[i]]$widths[1:3] = g3max.widths
# Draw it
do.call(grid.arrange, c(g, ncol = 1))
## Part 2: Get the right side of the panels aligned
# Locate the panels
panels <- lapply(g, function(x) x$layout[grepl("panel", x$layout$name), ])
# Get the position of right most panel
r.panel = lapply(panels, function(x) max(x$r)) # position of right most panel
# Get the number of columns to the right of the panels
n.cols = lapply(g.widths, function(x) length(x)) # right most column
# Get the widths of these columns to the right of the panels
r.widths <- mapply(function(x,y,z) x[(y+1):z], g.widths, r.panel, n.cols)
# Get the sum of these widths
sum.r.widths <- lapply(r.widths, sum)
# Get the maximum of these widths
r.width = do.call(unit.pmax, sum.r.widths)
# Add a column to the right of each gtable of width
# equal to the difference between the maximum
# and the width of each gtable's columns to the right of the panel.
for(i in 1:length(plots)) g[[i]] = gtable_add_cols(g[[i]], r.width - sum.r.widths[[i]], -1)
# Draw it
do.call(grid.arrange, c(g, ncol = 1))
Taking off these two lines and keeping the rest, it worked just fine.
g.iris$widths <- max.widths # assign max. widths to iris gtable
g.mpg$widths <- max.widths # assign max widths to mpg gtable
Probably it was limiting the width of them.
This is ugly but if you're under a time pressure this hack will work (not generalizable and dependent upon plot window size). Basically make the top plot 2 columns with a blank plot on the right and guess at the widths.
grid.arrange(
grid.arrange(plot.iris, ggplot() + theme_minimal(),ncol=2, widths = c(.9, .1)),
plot.mpg,
ncol=1
)
Is there any way to line up the points of a line plot with the bars of a bar graph using ggplot when they have the same x-axis? Here is the sample data I'm trying to do it with.
library(ggplot2)
library(gridExtra)
data=data.frame(x=rep(1:27, each=5), y = rep(1:5, times = 27))
yes <- ggplot(data, aes(x = x, y = y))
yes <- yes + geom_point() + geom_line()
other_data = data.frame(x = 1:27, y = 50:76 )
no <- ggplot(other_data, aes(x=x, y=y))
no <- no + geom_bar(stat = "identity")
grid.arrange(no, yes)
Here is the output:
The first point of the line plot is to the left of the first bar, and the last point of the line plot is to the right of the last bar.
Thank you for your time.
Extending #Stibu's post a little: To align the plots, use gtable (Or see answers to your earlier question)
library(ggplot2)
library(gtable)
data=data.frame(x=rep(1:27, each=5), y = rep(1:5, times = 27))
yes <- ggplot(data, aes(x = x, y = y))
yes <- yes + geom_point() + geom_line() +
scale_x_continuous(limits = c(0,28), expand = c(0,0))
other_data = data.frame(x = 1:27, y = 50:76 )
no <- ggplot(other_data, aes(x=x, y=y))
no <- no + geom_bar(stat = "identity") +
scale_x_continuous(limits = c(0,28), expand = c(0,0))
gYes = ggplotGrob(yes) # get the ggplot grobs
gNo = ggplotGrob(no)
plot(rbind(gNo, gYes, size = "first")) # Arrange and plot the grobs
Edit To change heights of plots:
g = rbind(gNo, gYes, size = "first") # Combine the plots
panels <- g$layout$t[grepl("panel", g$layout$name)] # Get the positions for plot panels
g$heights[panels] <- unit(c(0.7, 0.3), "null") # Replace heights with your relative heights
plot(g)
I can think of (at least) two ways to align the x-axes in the two plots:
The two axis do not align because in the bar plot, the geoms cover the x-axis from 0.5 to 27.5, while in the other plot, the data only ranges from 1 to 27. The reason is that the bars have a width and the points don't. You can force the axex to align by explicitly specifying an x-axis range. Using the definitions from your plot, this can be achieved by
yes <- yes + scale_x_continuous(limits=c(0,28))
no <- no + scale_x_continuous(limits=c(0,28))
grid.arrange(no, yes)
limits sets the range of the x-axis. Note, though, that the alginment is still not quite perfect. The y-axis labels take up a little more space in the upper plot, because the numbers have two digits. The plot looks as follows:
The other solution is a bit more complicated but it has the advantage that the x-axis is drawn only once and that ggplot makes sure that the alignment is perfect. It makes use of faceting and the trick described in this answer. First, the data must be combined into a single data frame by
all <- rbind(data.frame(other_data,type="other"),data.frame(data,type="data"))
and then the plot can be created as follows:
ggplot(all,aes(x=x,y=y)) + facet_grid(type~.,scales = "free_y") +
geom_bar(data=subset(all,type=="other"),stat="identity") +
geom_point(data=subset(all,type=="data")) +
geom_line(data=subset(all,type=="data"))
The trick is to let the facets be constructed by the variable type which was used before to label the two data sets. But then each geom only gets the subset of the data that should be drawn with that specific geom. In facet_grid, I also used scales = "free_y" because the two y-axes should be independent. This plot looks as follows:
You can change the labels of the facets by giving other names when you define the data frame all. If you want to remove them alltogether, then add the following to your plot:
+ theme(strip.background = element_blank(), strip.text = element_blank())
I would like two separate plots. I am using them in different frames of a beamer presentation and I will add one line to the other (eventually, not in example below). Thus I do not want the presentation to "skip" ("jump" ?) from one slide to the next slide. I would like it to look like the line is being added naturally. The below code I believe shows the problem. It is subtle, but not how the plot area of the second plot is slightly larger than of the first plot. This happens because of the y axis label.
library(ggplot2)
dfr1 <- data.frame(
time = 1:10,
value = runif(10)
)
dfr2 <- data.frame(
time = 1:10,
value = runif(10, 1000, 1001)
)
p1 <- ggplot(dfr1, aes(time, value)) + geom_line() + scale_y_continuous(breaks = NULL) + scale_x_continuous(breaks = NULL) + ylab(expression(hat(z)==hat(gamma)[1]*time+hat(gamma)[4]*time^2))
print(p1)
dev.new()
p2 <- ggplot(dfr2, aes(time, value)) + geom_line() + scale_y_continuous(breaks = NULL) + scale_x_continuous(breaks = NULL) + ylab(".")
print(p2)
I would prefer to not have a hackish solution such as setting the size of the axis label manually or adding spaces on the x-axis (see one reference below), because I will use this technique in several settings and the labels can change at any time (I like reproducibility so want a flexible solution).
I'm searched a lot and have found the following:
Specifying ggplot2 panel width
How can I make consistent-width plots in ggplot (with legends)?
https://groups.google.com/forum/#!topic/ggplot2/2MNoYtX8EEY
How can I add variable size y-axis labels in R with ggplot2 without changing the plot width?
They do not work for me, mainly because I need separate plots, so it is not a matter of aligning them virtically on one combined plot as in some of the above solutions.
haven't tried, but this might work,
gl <- lapply(list(p1,p2), ggplotGrob)
library(grid)
widths <- do.call(unit.pmax, lapply(gl, "[[", "widths"))
heights <- do.call(unit.pmax, lapply(gl, "[[", "heights"))
lg <- lapply(gl, function(g) {g$widths <- widths; g$heights <- heights; g})
grid.newpage()
grid.draw(lg[[1]])
grid.newpage()
grid.draw(lg[[2]])
How about using this for p2:
p2 <- ggplot(dfr2, aes(time, value)) + geom_line() +
scale_y_continuous(breaks = NULL) +
scale_x_continuous(breaks = NULL) +
ylab(expression(hat(z)==hat(gamma)[1]*time+hat(gamma)[4]*time^2)) +
theme(axis.title.y=element_text(color=NA))
This has the same label as p1, but the color is NA so it doesn't display. You could also use color="white".