I have made a function:
mtcars_plot_fun <- function(cyl_in, gear_in) {
car_data <- mtcars %>%
filter(cyl == cyl_in,
gear == gear_in)
p <- car_data %>% ggplot(aes(x = disp, y = qsec, fill = gear)) +
geom_point()
print(p)
}
Then a loop that makes plots and stores them in the list object gg_obj
cyls <- c(4, 6)
gears <- c(3, 4)
for (cyl in cyls) {
for (gear in gears) {
gg_obj <- mtcars_plot_fun(cyl_in = cyl, gear_in = gear)
}
}
Now I want to extract the plots I need from gg_obj and use grid.arrange(), gridExtra or similar to plot them.
How can I pull out the plots I want, and store them as unique objects to plot in a grid?
You can use do.call to use your list of plots as arguments for grid.arrange.
For example:
do.call(grid.arrange, list.of.plots, ncol=2)
Related
I've created two dataframes as a result of running two different models on the same data. Each dataframe contains a list-column with one plot per row. I would eventually like to print all plots to a PDF, but pair the plots with the same grouping variable on a single page of a multi-page PDF. For example, in the simplified example below, I would like the plot from model_figs1 where cyl == "6" to be on the same page to the left of the plot from model_figs2' where cyl == "6"`.
Here is some example data:
library(tidyverse)
# I am sure you could combine model_figs1 & model_figs2 into one process/code chunk,
# but this is an overly simplified example and should be kept separate:
model_figs1 <-
mtcars %>%
group_by(cyl) %>%
nest() %>%
mutate(plot = map(data, ~ggplot(data = .x,
aes(x = hp, y = mpg)) +
geom_point() +
geom_smooth(method = "lm") +
labs(subtitle = cyl)))
model_figs2 <-
mtcars %>%
group_by(cyl) %>%
nest() %>%
mutate(plot = map(data, ~ggplot(data = .x,
aes(x = hp, y = mpg)) +
geom_point() +
geom_smooth(method = "loess") +
labs(subtitle = cyl)))
# Bind the dfs together
model_figs <-
bind_rows(model_figs1, model_figs2, .id = "id")
How do I pair plots with the same grouping variable value onto a single PDF page while printing all plots to a multi-page PDF at once?
If there are only two plots per group, then we can use ggsave with marrangeGrob after arrangeing by the group column
library(gridExtra)
library(dplyr)
model_figs <- model_figs %>%
arrange(cyl)
ggsave(filename = file.path(getwd(), "Downloads/newplots.pdf"),
plot = marrangeGrob(model_figs$plot, ncol = 2, nrow = 1))
I'm looking for help in order to merge two plots and respect their respective scale size.
Here is a reproductible example:
data1<- subset(mtcars, cyl = 4)
data1$mpg <- data1$mp*5.6
data2<- subset(mtcars, cyl = 8)
p1 <- ggplot(data1, aes(wt, mpg, colour = cyl)) + geom_point()
p2 <- ggplot(data2, aes(wt, mpg, colour = cyl)) + geom_point()
grid.arrange(p1, p2, ncol = 2)
But what I'm looking for is to merge the two plots and respect the scale size and get something like :
It would be nice to not use a package which need to define the ratio since it's difficult to known how much I should reduce the second plot compared to the first one... And event more difficult when we have more than 2 plots.
I think what you are trying to achieve is something like this:
library(tidyverse)
mtcars %>%
filter(cyl %in% c(4, 8)) %>%
mutate(mpg = ifelse(cyl == 4, mpg * 5.6, mpg)) %>%
ggplot(aes(x = wt, y = mpg, col = as.factor(cyl))) +
geom_point(show.legend = FALSE) +
facet_wrap(~ cyl)
NOTE: I see some bugs in your original code. For example, if you want to use subset() to subset your data, you have to change your code from:
data1 <- subset(mtcars, cyl = 4)
to:
data1 <- subset(mtcars, cyl == 4)
subset(mtcars, cyl = 4) does not do anything.
I created a function that splits a dataframe by group variable 'gear' and makes plots for each new df. How do I change the title for each plot then?
rank20 <- function(df){
sdf <- df
clusterName <- paste0("cluster",sdf$gear)
splitData <- split(sdf,clusterName)
plot <- lapply(splitData, function (x) {ggplot(x, aes(mpg, cyl)) + geom_point()+
labs(x="mpg", y="cyl",
title="This Needs To Be Changed") +
theme_minimal()})
do.call(grid.arrange,plot)
}
rank20(mtcars)
I want the following titles: gear3, gear4, etc. (corresponding to their gear value)
UPD: both results are right if using mtcars. But in my real case, I transform my initial df. So, I should have put my question in the different way. I need to take the titles from splitData df name itself rather than from the column.
I find it easier with lapply to use the indexes as the input. Provides more flexibility if you need to link the list to another element eg the name of the list:
rank20 <- function(df, col="gear"){
sdf <- df
clusterName <- paste0("cluster", sdf[[col]])
splitData <- split(sdf, clusterName)
# do the apply across the splitData indexes instead and pull out cluster
# name from the column
plot <- lapply(seq_along(splitData), function(i) {
X <- splitData[[i]]
i_title <- paste0(col, X[[col]][1])
## to use clusterName instead eg cluster3 instead of gear3:
#i_title <- paste0(col, names(splitData)[i])
ggplot(X, aes(mpg, cyl)) +
geom_point() +
labs(x="mpg", y="cyl", title=i_title) +
theme_minimal()
})
do.call(grid.arrange,plot)
}
rank20(mtcars)
As you say you wanted the name to be gear2, gear3 I've gone with this but hashed out the alternative i_title that uses the clusterName value instead.
In the input of the function you can change the col value to switch to a different column so gear isn't hard-coded
May I suggest a slightly different approach (yet also using the loop over indices as suggested in user Jonny Phelps answer). I am creating a list of plots and then using patchwork::wrap_plots for plotting. I find it smoother.
library(tidyverse)
library(patchwork)
len_ind <- length(unique(mtcars$cyl))
ls_plot <-
mtcars %>%
split(., .$cyl) %>%
map2(1:len_ind, ., function(x, y) {
ggplot(y, aes(mpg, cyl)) +
geom_point() +
labs(x = "mpg", y = "cyl",
title = names(.)[x]
) +
theme_minimal()
})
wrap_plots(ls_plot) + plot_layout(ncol = 1)
Just noticed this was the wrong column - using cyl instead of gear. Oops. It was kind of fun to wrap this into a function:
plot_col <- function(x, col, plotx, ploty){
len_ind <- length(unique(mtcars[[col]]))
x_name <- deparse(substitute(plotx))
y_name <- deparse(substitute(ploty))
ls_plot <-
mtcars %>%
split(., .[[col]]) %>%
map2(1:len_ind, ., function(x, y) {
ggplot(y, aes({{plotx}}, {{ploty}})) +
geom_point() +
labs(x = x_name, y = y_name,
title = names(.)[x]
) +
theme_minimal()
})
wrap_plots(ls_plot) + plot_layout(ncol = 1)
}
plot_col(mtcars, "gear", mpg, cyl)
I want to arrange N ggplot (each one is facetted) on a grid with grid.arrange.
library(tidyverse)
library(ggplot2)
library(gridExtra)
plots <- lapply(unique(mtcars$cyl), function(cyl) {
data <- mtcars %>% filter(cyl == cyl)
ggplot(data, aes(x=mpg, y=hp))+
geom_point(color = "blue")+
facet_wrap(.~carb)}) %>%
do.call(grid.arrange, .)
do.call(grid.arrange, plots )
The problem is that all the plots are based on the entire dataset and they render the same plot, while they shuold be different as I filter them in line
data <- mtcars %>% filter(cyl == cyl).
filter deals with cyl too letteral and treated as a string, therefore cyl==cyl is TRUE for the entire dataset. You can solve this by unquote cyl using !! or use another variable name in the function e.g. x.
#Option 1
data <- mtcars %>% filter(cyl == !!cyl)
#Option 2
... function(x) {
data <- mtcars %>% filter(cyl == x)
...
Here is a tidyverse approach
library(tidyverse)
group_plots <- mtcars %>%
group_split(cyl) %>%
map(~ggplot(., aes(x = mpg, y = hp))+
geom_point(color = "blue") +
facet_wrap(.~carb))
do.call(gridExtra::grid.arrange, group_plots)
Try use split() first:
library(tidyverse)
library(gridExtra)
l <- split(mtcars, mtcars$cyl) # divide based on cyl in a list
plots <- lapply(l, function(x) {
ggplot(x, aes(x=mpg, y=hp)) +
geom_point(color = "blue") +
facet_wrap(.~carb)
}) # call lapply() on each element
do.call(grid.arrange, plots)
I would like to understand how the apply functions or anything similar can be used to make plots for several subsets of a data frame. Let's assume I want to use the mtcars data frame and I want to make a plot for each combination of "gear" and "carb".
I read here, that one can just write a function and then use lapply to create subsets of a data frame and plots for each of them. So how could I rewrite this to use lapply or anything similar?
gg_test = function(data) {
title = paste("gear",data[,"gear"][1],"carb",data[,"carb"][1])
require(ggplot2)
gg1 = ggplot(data, aes(x = mpg, y = cyl, color = disp)) + geom_point() +
ggtitle(title)
print(gg1)
}
for (g in unique(mtcars[,"gear"])){
for(ca in unique(mtcars[,"carb"])){
df_sub = subset(mtcars, gear == g & carb == ca)
gg_test(df_sub)
}
}
You can use split and interaction to divide a data.frame into chunks that you can then apply your plot function to:
gg_test = function(data) {
title = paste("gear",data[,"gear"][1],"carb",data[,"carb"][1])
require(ggplot2)
ggplot(data, aes(x = mpg, y = cyl, color = disp)) + geom_point() +
ggtitle(title)
}
I modified your plot function to return the plot instead of print it out.
plots <- lapply(split(mtcars, interaction(mtcars[,c("gear","carb")]), drop = TRUE), gg_test)
Then I store each plot in another object that can be printed on demand.