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))
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 :
- Initialise the length of the list because growing objects in a loop is considerably slow.
- 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)
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:
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')
)
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')
Related Topics
Relative Frequencies/Proportions With Dplyr
How to Convert a List Consisting of Vector of Different Lengths to a Usable Data Frame in R
Plot Two Graphs in Same Plot in R
How to Use R'S Ellipsis Feature When Writing Your Own Function
How to Create a Lag Variable Within Each Group
Data.Table VS Dplyr: Can One Do Something Well the Other Can't or Does Poorly
Split Data Frame String Column into Multiple Columns
Fastest Way to Replace Nas in a Large Data.Table
Convert Row Names into First Column
Removing Duplicate Combinations (Irrespective of Order)
Convert Data.Frame Columns from Factors to Characters
"For" Loop Only Adds the Final Ggplot Layer
Replace a Value in a Data Frame Based on a Conditional ('If') Statement
R: Rjava Package Install Failing
Position Geom_Text on Dodged Barplot
Cleaning Up Factor Levels (Collapsing Multiple Levels/Labels)