Iterating over a list using ggarrange - r

I have the following bit of code and don't understand why the for loop isn't working. I'm new to this, so excuse me if this is obvious, but it's not actually producing a combined set of graphs (as the more brute force method does below), it just prints out each graph individually
library(ggpubr)
graphs <- lapply(names(hemi_split), function(i){
ggplot(data=hemi_split[[i]], aes(x=type, y=shoot.mass))+
geom_point()+
facet_wrap(.~host, scales="free")+
theme_minimal()+
labs(title=i)
});graphs
for (i in 1:length(graphs)) {
ggarrange(graphs[[i]])
} ##not working
## this works, and is the desired output
ggarrange(graphs[[1]], graphs[[2]], graphs[[3]],
graphs[[4]], graphs[[5]], graphs[[6]],
graphs[[7]], graphs[[8]], graphs[[9]],
graphs[[10]], graphs[[11]])
thank you!

You can use do.call to provide all of the list elements of graphs as arguments of ggarrange:
library(ggpubr)
graphs <- lapply(names(mtcars)[2:5],function(x){
ggplot(mtcars,aes_string(x = x, y = "mpg")) +
geom_point()})
do.call(ggarrange,graphs)

another solution using purrr
library(tidyverse)
ggraphs <- map(names(mtcars)[2:5],
~ ggplot(mtcars,aes_string(x = .x, y = "mpg")) +
geom_point())
ggarrange(plotlist = ggraphs)

Related

How to combine jobs to avoid nested lapply

I have a data frame where I would like to perform multiple operations with. Here I give you an example to illustrate it, for example to create a list of plots:
library(tidyverse)
plot_fun = function(data, geom) {
plot = ggplot(data, aes(x = factor(0), y = Sepal.Length))
if (geom == 'bar') {
plot = plot + geom_col()
} else if (geom == 'box') {
plot = plot + geom_boxplot()
}
plot +
labs(x = unique(data$Species)) +
theme_bw() +
theme(axis.text.x = element_blank())
}
As you can see, this function takes a data frame, and perform two types of plots depending the geom parameter.
In my real problem, I have to split the data frame by one or multiple factors, and do the job. Do not take care about this specific example (I know I can put iris$Species on x-axis)
iris_ls = split(iris, iris$Species)
geom_ls = c('bar', 'box')
lapply(geom_ls, function(g) {
lapply(iris_ls, function(x) {
plot_fun(x, g)
})
})
My problem is due if I want to create both types of plots, I have to write a nested lapply (bad performance for parallelization cases).
So my question is, how should I avoid nested lapply procedure?
Should I multiplicate length of iris_ls by the length of geom_ls vector?
I do not know how to asses this. Imagine I have multiple geom like parameters in my function.
PS: Using drop = TRUE on split function, does not drop factor levels for each element of the list, I don't not know if it's the correct way to do it. I have to use another lapply to do it
Use the purrr package :
cross_ls <- purrr::cross(list(iris = split(iris, iris$Species),
geom = list('bar', 'box')))
cross_ls %>% purrr::map(~{plot_fun(.x$iris,.x$geom)})
or in its parallel version :
library(furrr)
plan(multiprocess)
cross_ls %>% furrr::future_map(~{plot_fun(.x$iris,.x$geom)})

ggplot on grid with a grobList in R

I'm trying to plot multiple plots on a grid using ggplot2 in a for loop, followed by grid.arrange. But all the plots are identical afterwards.
library(ggplot2)
library(grid)
test = data.frame(matrix(rnorm(320), ncol=16 ))
names(test) = sapply(1:16, function(x) paste0("var_",as.character(x)))
plotlist = list()
for (i in 1:(dim(test)[2]-1)){
plotlist[[i]] = ggplot(test) +
geom_point(aes(get(x=names(test)[dim(test)[2]]), y=get(names(test)[i])))
}
pdf("output.pdf")
do.call(grid.arrange, list(grobs=plotlist, nrow=3))
dev.off(4)
When running this code, it seems like the get() calls are only evaluated at the time of the grid.arrange call, so all of the y vectors in the plot are identical as "var_15". Is there a way to force get evaluation immediately, so that I get 15 different plots?
Thanks!
Here are two ways that use purrr::map functions instead of a for-loop. I find that I have less of a clear sense of what's going on when I try to use loops, and since there are functions like the apply and map families that fit so neatly into R's vector operations paradigm, I generally go with mapping instead.
The first example makes use of cowplot::plot_grid, which can take a list of plots and arrange them. The second uses the newer patchwork package, which lets you add plots together—like literally saying plot1 + plot2—and add a layout. To do all those additions, I use purrr::reduce with + as the function being applied to all the plots.
library(tidyverse)
set.seed(722)
test = data.frame(matrix(rnorm(320), ncol=16 ))
names(test) = sapply(1:16, function(x) paste0("var_",as.character(x)))
# extract all but last column
xvars <- test[, -ncol(test)]
By using purrr::imap, I can map over all the columns and apply a function with 2 arguments: the column itself, and its name. That way I can set an x-axis label that specifies the column name. I can also easily access the column of data without having to use get or any tidyeval tricks (although for something for complicated, a tidyeval solution might be better).
plots <- imap(xvars, function(variable, var_name) {
df <- data_frame(x = variable, y = test[, ncol(test)])
ggplot(df, aes(x = x, y = y)) +
geom_point() +
xlab(var_name)
})
cowplot::plot_grid(plotlist = plots, nrow = 3)
library(patchwork)
# same as plots[[1]] + plots[[2]] + plots[[3]] + ...
reduce(plots, `+`) + plot_layout(nrow = 3)
Created on 2018-07-22 by the reprex package (v0.2.0).
Try this:
library(ggplot2)
library(grid)
library(gridExtra)
set.seed(1234)
test = data.frame(matrix(rnorm(320), ncol=16 ))
names(test) = sapply(1:16, function(x) paste0("var_",as.character(x)))
plotlist = list()
for (i in 1:(dim(test)[2]-1)) {
# Define here the dataset for the i-th plot
df <- data.frame(x=test$var_16, y=test[, i])
plotlist[[i]] = ggplot(data=df, aes(x=x, y=y)) + geom_point()
}
grid.arrange(grobs=plotlist, nrow=3)

Histograms using ggplot2 within loop

I would like to create a grid of histograms using a loop and ggplot2. Say I have the following code:
library(gridExtra)
library(ggplot2)
df<-matrix(NA,2000,5)
df[,1]<-rnorm(2000,1,1)
df[,2]<-rnorm(2000,2,1)
df[,3]<-rnorm(2000,3,1)
df[,4]<-rnorm(2000,4,1)
df[,5]<-rnorm(2000,5,1)
df<-data.frame(df)
out<-NULL
for (i in 1:5){
out[[i]]<-ggplot(df, aes(x=df[,i])) + geom_histogram(binwidth=.5)
}
grid.arrange(out[[1]],out[[2]],out[[3]],out[[4]],out[[5]], ncol=2)
Note that all of the plots appear, but that they all have the same mean and shape, despite having set each of the columns of df to have different means.
It seems to only plot the last plot (out[[5]]), that is, the loop seems to be reassigning all of the out[[i]]s with out[[5]].
I'm not sure why, could someone help?
I agree with #GabrielMagno, facetting is the way to go. But if for some reason you need to work with the loop, then either of these will do the job.
library(gridExtra)
library(ggplot2)
df<-matrix(NA,2000,5)
df[,1]<-rnorm(2000,1,1)
df[,2]<-rnorm(2000,2,1)
df[,3]<-rnorm(2000,3,1)
df[,4]<-rnorm(2000,4,1)
df[,5]<-rnorm(2000,5,1)
df<-data.frame(df)
out<-list()
for (i in 1:5){
x = df[,i]
out[[i]] <- ggplot(data.frame(x), aes(x)) + geom_histogram(binwidth=.5)
}
grid.arrange(out[[1]],out[[2]],out[[3]],out[[4]],out[[5]], ncol=2)
or
out1 = lapply(df, function(x){
ggplot(data.frame(x), aes(x)) + geom_histogram(binwidth=.5) })
grid.arrange(out1[[1]],out1[[2]],out1[[3]],out1[[4]],out1[[5]], ncol=2)
I would recommend using facet_wrap instead of aggregating and arranging the plots by yourself. It requires you to specify a grouping variable in the data frame that separates the values for each distribution. You can use the melt function from the reshape2 package to create such new data frame. So, having your data stored in df, you could simply do this:
library(ggplot2)
library(reshape2)
ggplot(melt(df), aes(x = value)) +
facet_wrap(~ variable, scales = "free", ncol = 2) +
geom_histogram(binwidth = .5)
That would give you something similar to this:

Combining two ecdf plots with different

At the moment I`m writing my bachelor thesis and all of my plots are created with ggplot2. Now I need a plot of two ecdfs but my problem is that the two dataframes have different lengths. But by adding values to equalize the length I would change the distribution, therefore my first thought isn't possible. But a ecdf plot with two different dataframes with a different length is forbidden.
daten <- peptidPSMotherExplained[peptidPSMotherExplained$V3!=-1,]
daten <- cbind ( daten , "scoreDistance"= daten$V2-daten$V3 )
daten2 <- peptidPSMotherExplained2[peptidPSMotherExplained2$V3!=-1,]
daten2 <- cbind ( daten2 , "scoreDistance"= daten2$V2-daten2$V3 )
p <- ggplot(daten, aes(x = scoreDistance)) + stat_ecdf()
p <- p + geom_point(aes(x = daten2$lengthDistance))
p
with the normal plot function of R it is possible
plot(ecdf(daten$scoreDistance))
plot(ecdf(daten2$scoreDistance),add=TRUE)
but it looks different to all of my other plots and I dislike this.
Has anybody a solution for me?
Thank you,
Tobias
Example:
df <-data.frame(scoreDifference = rnorm(10,0,12))
df2 <- data.frame(scoreDifference = rnorm(5,-3,9))
plot(ecdf(df$scoreDifference))
plot(ecdf(df2$scoreDifference),add=TRUE)
So how can I achieve this kind of plot in ggplot?
I don't know what geom one should use for such plots, but for combining two datasets you can simply specify the data in a new layer,
ggplot(df, aes(x = scoreDifference)) +
stat_ecdf(geom = "point") +
stat_ecdf(data=df2, geom = "point")
I think, reshaping your data in the right way will probably make ggplot2 work for you:
df <-data.frame(scoreDiff1 = rnorm(10,0,12))
df2 <- data.frame(scoreDiff2 = rnorm(5,-3,9))
library('reshape2')
data <- merge(melt(df),melt(df2),all=TRUE)
Then, with data in the right shape, you can simply go on to plot the stuff with colour (or shape, or whatever you wish) to distinguish the two datasets:
p <- ggplot(daten, aes(x = value, colour = variable)) + stat_ecdf()
Hope this is what you were looking for!?

Pass by value in R

When trying to call grid.arrange to put multiple plots on a same ggplot2 graph, I first build a list of the plots I want. Then I build the corresponding argument list to call grid.arrange, as was explained in a previous question. This is my code (my dataframe is called manip):
args.list <- NULL;
plot.list <- NULL;
for (m in names(manip[2:10])) {
plot.list <- c(plot.list, list(qplot(manip$side, y=manip[,m],ylab=m))
}
args.list <- c(plot.list, 1, 9)
names(args.list) <- c(names(manip)[2:10], list("nrow","ncol"))
do.call(grid.arrange, args.list)
This works, except that the 9 graphs are exactly the same! After checking, it turns out that the data is always the one corresponding to m=10. So my guess was that the value of m is not assigned in the loop, but evaluated later. However, the label ylab=m is assigned correctly and is different for all the graphs.
So I don't really get what the difference is and how the interpreter chooses when to evaluate m for the plots. Can someone explain?
The behavior is due to the lazy evaluation of R.
Here is a minimal(?) example:
d <- 1:3
args.list <- NULL;
plot.list <- NULL;
for (m in 1:3) {
plot.list <- c(plot.list, list(qplot(d[m], d[m], ylab=letters[m])))
}
args.list <- c(plot.list, nrow=1, ncol=3)
do.call(grid.arrange, args.list)
in this case, d[m] is evaluated at the call of do.call. so m is 3 for all panel.
here is a workaround:
d <- 1:3
args.list <- NULL;
plot.list <- NULL;
for (m in 1:3) {
plot.list <- c(plot.list,
list(qplot(d, d, data=data.frame(d=d[m]), ylab=letters[m])))
}
args.list <- c(plot.list, nrow=1, ncol=3)
do.call(grid.arrange, args.list)
in this case, d[m] is evaluated at the call of qplot, and the d[m] is stored in the output object of qplot.
so, the simple solution is to pass data to qplot() or ggplot().
I will first answer your question and then show an alternative using a facet plot.
Edited
The following, much simplified, code seems to work:
library(gridExtra)
manip <- mtcars
plot.list <- lapply(2:11,
function(x)qplot(manip$mpg, y=manip[, x],
ylab=names(manip)[x]))
do.call(grid.arrange, c(plot.list, nrow=10))
It produces this ugly plot:
Without knowing your objectives, it is dangerous to try and give advice, I know. Nonetheless, have you considered using facets for your plot instead?
The following code is much simpler, executes quiker and produces a graph that is easier to interpret:
library(reshape2)
manip <- mtcars
mmanip <- melt(manip, id.vars="mpg")
str(mmanip)
ggplot(mmanip, aes(x=mpg, y=value)) +
geom_point(stat="identity") +
facet_grid(.~variable, scales="free")
Perhaps it would be better to melt then data and use faceting?
library(ggplot2)
manip <- data.frame(car = row.names(mtcars), mtcars)
manip.m <- melt(manip)
qplot(car, value, data = manip.m) + facet_wrap(~variable, scales = "free_y")
It need some polishing in the xlab
last_plot() + opts(axis.text.x = theme_text(angle = 90))
HTH

Resources