Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Align multiple ggplot graphs with and without legends [duplicate]

Tags:

r

ggplot2

I'm trying to use ggplot to draw a graph comparing the absolute values of two variables, and also show the ratio between them. Since the ratio is unitless and the values are not, I can't show them on the same y-axis, so I'd like to stack vertically as two separate graphs with aligned x-axes.

Here's what I've got so far:

enter image description here

library(ggplot2)
library(dplyr)
library(gridExtra)

# Prepare some sample data.
results <- data.frame(index=(1:20))
results$control <- 50 * results$index
results$value <- results$index * 50 + 2.5*results$index^2 - results$index^3 / 8
results$ratio <- results$value / results$control

# Plot absolute values
plot_values <- ggplot(results, aes(x=index)) +
  geom_point(aes(y=value, color="value")) +
  geom_point(aes(y=control, color="control"))

# Plot ratios between values
plot_ratios <- ggplot(results, aes(x=index, y=ratio)) +
  geom_point()

# Arrange the two plots above each other
grid.arrange(plot_values, plot_ratios, ncol=1, nrow=2)

The big problem is that the legend on the right of the first plot makes it a different size. A minor problem is that I'd rather not show the x-axis name and tick marks on the top plot, to avoid clutter and make it clear that they share the same axis.

I've looked at this question and its answers:

Align plot areas in ggplot

Unfortunately, neither answer there works well for me. Faceting doesn't seem a good fit, since I want to have completely different y scales for my two graphs. Manipulating the dimensions returned by ggplot_gtable seems more promising, but I don't know how to get around the fact that the two graphs have a different number of cells. Naively copying that code doesn't seem to change the resulting graph dimensions for my case.

Here's another similar question:

The perils of aligning plots in ggplot

The question itself seems to suggest a good option, but rbind.gtable complains if the tables have different numbers of columns, which is the case here due to the legend. Perhaps there's a way to slot in an extra empty column in the second table? Or a way to suppress the legend in the first graph and then re-add it to the combined graph?

like image 803
Weeble Avatar asked Oct 02 '14 11:10

Weeble


2 Answers

An alternative & quite easy solution is as follows:

# loading needed packages
library(ggplot2)
library(dplyr)
library(tidyr)

# Prepare some sample data
results <- data.frame(index=(1:20))
results$control <- 50 * results$index
results$value <- results$index * 50 + 2.5*results$index^2 - results$index^3 / 8
results$ratio <- results$value / results$control

# reshape into long format
long <- results %>% 
  gather(variable, value, -index) %>%
  mutate(facet = ifelse(variable=="ratio", "ratio", "values"))
long$facet <- factor(long$facet, levels=c("values", "ratio"))

# create the plot & remove facet labels with theme() elements
ggplot(long, aes(x=index, y=value, colour=variable)) +
  geom_point() +
  facet_grid(facet ~ ., scales="free_y") +
  scale_colour_manual(breaks=c("control","value"), values=c("green", "red", "blue")) +
  theme(axis.title.y=element_blank(), strip.text=element_blank(), strip.background=element_blank())

which gives: enter image description here

like image 196
Jaap Avatar answered Sep 18 '22 06:09

Jaap


Try this:

library(ggplot2)
library(gtable)
library(gridExtra)

AlignPlots <- function(...) {
  LegendWidth <- function(x) x$grobs[[8]]$grobs[[1]]$widths[[4]]

  plots.grobs <- lapply(list(...), ggplotGrob)

  max.widths <- do.call(unit.pmax, lapply(plots.grobs, "[[", "widths"))
  plots.grobs.eq.widths <- lapply(plots.grobs, function(x) {
    x$widths <- max.widths
    x
  })

  legends.widths <- lapply(plots.grobs, LegendWidth)
  max.legends.width <- do.call(max, legends.widths)
  plots.grobs.eq.widths.aligned <- lapply(plots.grobs.eq.widths, function(x) {
    if (is.gtable(x$grobs[[8]])) {
      x$grobs[[8]] <- gtable_add_cols(x$grobs[[8]],
                                      unit(abs(diff(c(LegendWidth(x),
                                                      max.legends.width))),
                                           "mm"))
    }
    x
  })

  plots.grobs.eq.widths.aligned
}

df <- data.frame(x = c(1:5, 1:5),
                 y = c(1:5, seq.int(5,1)),
                 type = factor(c(rep_len("t1", 5), rep_len("t2", 5))))

p1.1 <- ggplot(diamonds, aes(clarity, fill = cut)) + geom_bar()
p1.2 <- ggplot(df, aes(x = x, y = y, colour = type)) + geom_line()
plots1 <- AlignPlots(p1.1, p1.2)
do.call(grid.arrange, plots1)

p2.1 <- ggplot(diamonds, aes(clarity, fill = cut)) + geom_bar()
p2.2 <- ggplot(df, aes(x = x, y = y)) + geom_line()
plots2 <- AlignPlots(p2.1, p2.2)
do.call(grid.arrange, plots2)

Produces this: With legendsWithout one legend

// Based on multiple baptiste's answers

like image 43
StilRiv Avatar answered Sep 21 '22 06:09

StilRiv