2

I am using the plot_grid() function of the cowplot package to draw ggplots in a grid and would like to know if there is a way to draw plots by column instead of by row?

library(ggplot2)
library(cowplot)

df <- data.frame(
    x = c(3,1,5,5,1),
    y = c(2,4,6,4,2)
)

# Create plots: say two each of path plot and polygon plot 
p <- ggplot(df)
p1 <- p + geom_path(aes(x,y)) + ggtitle("Path 1")
p2 <- p + geom_polygon(aes(x,y)) + ggtitle("Polygon 1")
p3 <- p + geom_path(aes(y,x)) + ggtitle("Path 2")
p4 <- p + geom_polygon(aes(y,x)) + ggtitle("Polygon 2")

plots <- list(p1,p2,p3,p4)
plot_grid(plotlist=plots, ncol=2) # plots are drawn by row

I would like to have plots P1 and P2 in the first column and p3 and p4 in the second column, something like:

plots <- list(p1, p3, p2, p4) # plot sequence changed
plot_grid(plotlist=plots, ncol=2)

Actually I could have 4, 6, or 8 plots. The number of rows in the plot grid will vary but will always have 2 columns. In each case I would like to fill the plot grid by column (vertically) so my first 2, 3, or 4 plots, as the case maybe, appear over each other. I would like to avoid hardcode these different permutations if I can specify something like par(mfcol = c(n,2)).

Dicky
  • 63
  • 1
  • 1
  • 4

2 Answers2

2

As you have observed, plot_grid() draws plots by row. I don't believe there's any way to change that, so if you want to maintain using plot_grid() (which would be probably most convenient), then one approach could be to change the order of the items in your list of plots to match what you need for plot_grid(), given knowledge of the number of columns.

Here's a function I have written that does that. The basic idea is to:

  • create a list of indexes for number of items in your list (i.e. 1:length(your_list)),
  • put the index numbers into a matrix with the specified number of rows,
  • read back that matrix into another vector of indexes by column
  • reorder your list according to the newly ordered indexes

I've tried to build in a way to make this work even if the number of items in your list is not divisible by the intended number of columns (like a list of 8 items arranged in 3 columns).

reorder_by_col <- function(myData, col_num) {
  x <- 1:length(myData)   # create index vector
  length(x) <- prod(dim(matrix(x, ncol=col_num)))  # adds NAs as necessary
  temp_matrix <- matrix(x, ncol=col_num, byrow = FALSE)
  new_x <- unlist(split(temp_matrix, rep(1:ncol(temp_matrix), each=row(temp_matrix))))
  names(new_x) <- NULL # not sure if we need this, but it forces an unnamed vector
  return(myData[new_x])
}

This all was written with a little help from Google and specifically answers to questions posted here and here.

You can now see the difference without reordering:

plots <- list(p1,p2,p3,p4)
plot_grid(plotlist=plots, ncol=2)

enter image description here

... and with reordering using the new method:

newPlots <- reorder_by_col(myData=plots, col_num=2)
plot_grid(plotlist=newPlots, ncol=2)

enter image description here

chemdork123
  • 12,369
  • 2
  • 16
  • 32
  • Thanks chemdork123. I tried your function. It works well with four plots but not with six or eight plots. To be clear, when my plotlist has six plots, I'd like the plot_grid() function to plot the first three in the first column and the last three in the second column. Similarly for eight plots in the plotlist, the plot_grid() function should plot the first four plots in the first column and the remaining four in the second column. – Dicky Jul 26 '20 at 00:59
  • When my plotlist contains six plots {p1,p2,p3,p5,p6,p7}, the variable new_x in your function returns {1,3,5,2,4,6}. However it should ideally return {1,5,2,6,3,7} for plot_grid() to plot P1 through P3 in the first column and P5 through P7 in the second column. Similarly for eight plots in the plotlist {p1,p2,p3,p4,p5,p6,p7,p8}, your new_x returns {1,3,5,7,2,4,6,8} instead of desired {1,5,2,6,3,7,4,8}. – Dicky Jul 26 '20 at 01:00
  • So I modified the function a bit with Reduce() as follows: `reorder_by_col <- function(myData, col_num) { x <- 1:length(myData) # create index vector temp_matrix <- matrix(x, ncol=col_num, byrow = FALSE) new_x <- Reduce(c,split(temp_matrix, 1:(length(myData)/col_num))) return(myData[new_x]) } ` This gives me the desired output, at least when the desired number of columns is 2 and the plotlist contains an even number of plots. – Dicky Jul 26 '20 at 01:04
  • I wish plot_grid() provides an additional parameter 'byRow' (TRUE/FALSE) to plot by row or by column! – Dicky Jul 26 '20 at 01:04
  • That would be simpler, wouldn't it? – chemdork123 Jul 26 '20 at 11:49
  • Definitely, is there a way we can request this functionality in the plot_grid() function? – Dicky Jul 27 '20 at 22:01
  • I'd say you can post it in the github forums for the `cowplot` package. – chemdork123 Jul 28 '20 at 20:04
  • 1
    Done, added a new issue: https://github.com/wilkelab/cowplot/issues/161 – Dicky Jul 30 '20 at 00:57
1

The argument, byrow, has now been added to plot_grid.

In the case where you would like to have num_plots < nrow * ncol the remaining spots will be empty.

You can now call:

library(ggplot2)

df <- data.frame(
  x = 1:10, y1 = 1:10, y2 = (1:10)^2, y3 = (1:10)^3, y4 = (1:10)^4
)

p1 <- ggplot(df, aes(x, y1)) + geom_point()
p2 <- ggplot(df, aes(x, y2)) + geom_point()
p3 <- ggplot(df, aes(x, y3)) + geom_point()

cowplot::plot_grid(p1, p2, p3, byrow = FALSE)
BrianLang
  • 831
  • 4
  • 14