How to Arrange a Variable List of Plots Using Grid.Arrange

How do I arrange a variable list of plots using grid.arrange?

How about this:

library(gridExtra)
n <- length(plist)
nCol <- floor(sqrt(n))
do.call("grid.arrange", c(plist, ncol=nCol))

Sample Image

Using grid.arrange with a list and extra arguments

Your issue is how you combine lists. You are doing this:

a <- list(1, 2)
b <- list(list(3, 4), list(5, 6))
c <- 7

c(a, b, c)

As a result, everything gets added to the a list.

What you want is this:

c(list(a), b, c)

Arrange several lists of plots using grid.arrange

No need for do.call,

library(ggplot2)
p1 = replicate(3, ggplot(), F)
p2 = replicate(5, ggplot(), F)

gridExtra::grid.arrange(grobs = c(p1, p2), ncol=2)

grid.arrange using list of plots

You could use mget like this:

plist2 <- mget(paste0("g", 1:4))
do.call(grid.arrange, plist2)

But it would be better to put the plots into a list when creating them, like this:

funs <- c(sin, tan, cos)
DF <- data.frame(x=c(0, 10))

g <- lapply(funs, function(fun, df) {
ggplot(df, aes(x)) + stat_function(fun=fun)
}, df=DF)

#g[[4]] <- tableGrob(data.frame(x = 1:10, y = 2:11, z = 3:12))
#better for programmatic use:
g <- c(g, list(tableGrob(data.frame(x = 1:10, y = 2:11, z = 3:12))))

do.call(grid.arrange, g)

R: arranging multiple plots together using gridExtra

If you want to keep the approach you are using just add

par(mfrow=c(2,2))

before all four plots.

If you want everything on the same line add instead

par(mfrow=c(1,4))

How to create a list of ggplot objects that grid.arrange will accept and draw in r

You can try to :

  1. Initialise the length of the list because growing objects in a loop is considerably slow.
  2. Use .data pronoun to subset the names so you get proper names on x-axis.
library(ggplot2)
library(gridExtra)

test_fun = function (x) {
plt_lst = vector('list', length(x))
nm <- names(x)

for(i in seq_along(x)){
plt_lst[[i]] = ggplot(data = x, aes(x = .data[[nm[i]]])) + geom_histogram()
}

return(plt_lst)
}


test_plt_lst = test_fun(df)
do.call(grid.arrange, test_plt_lst)

Sample Image

How to plot a list of plots in a grid

For grid.arrange() to work, you need to explicitly define the grobs argument.

library(ggplot2)
library(gridExtra)

df <- data.frame(x = 1:100,
y1 = runif(100),
y2 = runif(100)^2)

plot_list <- list(
plot1 = ggplot(df, aes(x, y1)) + geom_point(),
plot2 = ggplot(df, aes(x, y2)) + geom_point()
)

gridExtra::grid.arrange(grobs = plot_list)

Need help using grid.arrange to arrange two time series plots

Data

First of all, please read how to make reproducible example: dput(your_data) is the best way to make your data available for everyone who trying to help you.

dat <- read.table(
text = " yearinitiated midaction cyberattacks
1995 81 NA
1996 75 NA
1997 81 NA
1998 264 NA
1999 363 NA
2000 98 1
2001 105 7
2002 83 NA
2003 79 3
2004 52 2
2005 50 4
2006 35 8
2007 26 18
2008 39 27
2009 31 28
2010 73 15
2011 NA 27",
stringsAsFactors = F,
header = T
)

Why grid.arrange() does not work?

If you refer to the help pages, you can see that gridExtra::grid.arrange() function is designed to:

Set up a gtable layout to place multiple grobs on a page

Where, grob stands for graphical object. Very important that the function works with:

...grobs, gtables, ggplot or trellis objects...

And that is why, when you plot your data using base::plot() using gridExtra::grid.arrange() is not the best idea. Check the class of your plot1 and plot2 variables:

class(plot1)
#"NULL"
class(plot2)
#"NULL"

The output above tells you that plot() calls from your code return NULL, while the plot you see on your graphical device is only side effect of base::plot(). The function does not return graphical object you can further use in your code. You can read more about side-effects and impure functions here.

Why you don't need grid.arrange()?

You don't need it because there are other tools you can use for your purpose.

Plotting with base::plot()

If you read the help page for base::par() function you will find the description of mfrow, mfcol parameters of par():

A vector of the form c(nr, nc). Subsequent figures will be drawn in an nr-by-nc array on the device by columns (mfcol), or rows (mfrow), respectively.

Which means that if you want to plot Cyber Attacks plot above MIDs plot, you have to call par() before plotting this way:

par(
mfrow = c(2, 1),
bty = 'n', # suppress the box around the plot
col = '#000F55', # set color of the plot
col.axis = 'grey25', # make axes grey,
col.lab = 'grey25', # make labels grey
col.main = 'grey25', # make main text grey
family = 'mono', # set font family
mar = rep(2, 4), # set margins
tcl = -0.25, # set ticks length
xaxs = 'r', # apply axis style
yaxs = 'r' # same as above
)

Setting up the x limits:

XLIM <- range(dat$yearinitiated, na.rm = T)

Afterwards you can call your plots this way:

# Cyber attacks
plot(x = dat$yearinitiated,
y = dat$cyberattacks,
xlim = XLIM,
xlab = "Year",
ylab = "# of Cyber Attacks",
main = "Cyber Attacks over Time",
type = "l"
)

# MID attacks
plot(x = dat$yearinitiated,
y = dat$midaction,
xlim = XLIM,
xlab = "Year",
ylab = "# of MIDs",
main = "MIDs Attacks over Time",
type = "l"
)

# dev.off()

Which gives you the following plot:

base and par

To reset your par settings, call dev.off().

Plotting with ggplot2

You can use facet_wrap()/facet_grid() as it is suggested by @dc37.

Why do you need two plots?

Honestly I think you don't. It is much easier to compare two trends in one plot, instead of trying to compare two data sets represented by separate plots.

Using base functionality:

Using base::plot(), base::lines() and base::legend() functions you can easily plot both MID and Cyber attacks over the time in one plot:

# Plot MID attacks
plot(x = dat$yearinitiated,
y = dat$midaction,
xlim = XLIM,
ylim = range(dat[, -1], na.rm = T),
col = "skyblue",
xlab = "Year",
ylab = "Count",
main = "Cyber Attacks vs Military actions over Time",
type = "s"
)

# Add Cyber attacks
lines(
x = dat$yearinitiated,
y = dat$cyberattacks,
col = "red",
type = 's'
)

# Add legend
legend(
x = max(dat$yearinitiated, na.rm = T) - 5.5,
y = max(dat[, -1], na.rm = T),
legend = c('Cyber Attacks', 'Military actions'),
fill = c('red', 'skyblue')
)

base

Or, as an alternative to the base functionality, you can simply use ggplot2 and couple of functions from tidyverse packages:

library(tidyverse)

dat %>%
gather(key = 'Action', value = 'Count', -yearinitiated) %>%
rename('Year' = yearinitiated) %>%
ggplot(aes(x = Year, y = Count, color = Action)) +
geom_step() +
ggthemes::theme_few() +
ggtitle('Military actions vs Cyber attacks')

ggplot2



Related Topics



Leave a reply



Submit