How to combine jobs to avoid nested lapply - r

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)})

Related

Combining two ggplot objects from different function calls?

I am currently trying to implement a graphing library where I need a bit more flexibility than what is currently provided by ggplot. I am interested in going in a functional programming kind of way.
Currently, I have a barchart which is defined as
make_bar <- function(data, x, n_cols)
{
#Data: Dataframe or tibble
#x: Factor singular column
#output: ggplot object
n_colors = nrow(distinct(data[x]))
if (n_colors != length(n_cols)) {
difference <- abs(n_colors - length(colors))
colors <- head(colors, difference)
}
plot <- ggplot(data, aes(x = .data[[x]],
tooltip = .data[[x]],
data_id = .data[[x]])) +
geom_bar_interactive(fill=custom_colour_palette(colors))
}
Which very nicely returns a bar chart. Now I want the functionality to write a function called "add_line" which should then be applied to the barchart if one wishes to do so. The line function as is right now is:
add_line <- function(data, x) {
data %>%
count(.data[[x]]) %>%
ggplot(aes(.data[[x]], n)) +
geom_line(group=1)
}
So now I have two lists, but is there any easy - or best practice - way to add such two lists to create one combined plot with the line overlayed on the barchart?
Code for reproducbility can be called with:
data <- mpg
h <- add_line(data, 'manufacturer')
x <- make_bar(data, 'manufacturer', 15)
# x + h ? does not work and shouldn't but such a functionality would be nice
Adding to what #MrFlick has said, here's how you return a geom object in add_line that can be added onto the base bar chart:
add_line <- function(data, x) {
geom_line(
aes_string(x = x, y = "n"),
data = count(data, .data[[x]]),
group = 1
)
}
Then the following should work:
x <- make_bar(mpg, "manufacturer", 15)
h <- add_line(mpg, "manufacturer")
x + h
The aes_string allows for using character strings rather than expressions, really useful for dynamic column choices.

Add title to multiple graph in R

I am trying to add title for all my plot in a loop, here is what I have done:
first I split the data with the split() fuction:
sep_team_season_stage <- split(data[cols],
list(data$Season,data$Stage,data$Team),drop = TRUE)
then I want to plot a bunch of radar graph: so I use a loop:
for (i in sep_team_season_stage){
print(ggRadar(i, aes(group = "Team")))
}
And then I want to add tilte for the graph: I saw "titles" produces by the split function is good so I want to recall them:
plotnames = names(sep_team_season_stage)
and added into the for loop:
for (i in sep_team_season_stage){
a=1,
print(ggRadar(i, aes(group = "Team"))+ ggtitle(plotnames[a])),
a= a+1
}
It doesnt work however, how can I fix it?
No data to test this but in such situations when you want to access the names of the list you can use imap from purrr :
plot_list <- purrr::imap(sep_team_season_stage,
~ggRadar(.x, aes(group = "Team"))+ ggtitle(.y))
use Map and pass both name and data.
plot_list <- Map(function(x, y) ggRadar(x, aes(group = "Team")) + ggtitle(y),
sep_team_season_stage, names(sep_team_season_stage))

Changing title of plots in a loop with colnames() in R

I am creating a for loop which creates a ggplot2 plot for each of the first six columns in a dataframe. Everything works except for the looping of the title names. I have been trying to use title = colnames(df[,i]) and title = paste0(colnames(df[,i]) to create the proper title but it simply ends up repeating the 2nd column name. The plots themselves produce the data correctly for each column, but the title is for some reason not looping. For the first plot it produces the correct title, but then for the second plot and beyond it just keeps on repeating the third column name, completely skipping over the second column name. I even tried creating a variable within the loop to store the respective title name to then use within the ggplot2 title labels: changetitle <- colnames(df[,i]) and then using title = changetitle but that also loops incorrectly.
Here is an example of what I have so far:
plot_6 <- list()
for(i in df[1:6]){
plot_6[i] <- print(ggplot(df, aes(x = i, ...) ...) +
... +
labs(title = colnames(df[,i]),
x = ...) +
...)
}
Thank you very much.
df[1:6] is a data frame with six columns. When used as a loop variable, this results in i being a vector of values each time through the loop. This might "work" in the sense that ggplot will prroduce a plot, but it breaks the link between the data frame provided to ggplot (df in this case) and the mapping of df's columns to ggplot's aesthetics.
Here are a few options, using the built-in mtcars data frame:
library(tidyverse)
library(patchwork)
plot_6 <- list()
for(i in 1:6) {
var = names(mtcars)[i]
plot_6[[i]] <- ggplot(mtcars, aes(x = !!sym(var))) +
geom_density() +
labs(title = var)
}
# Use column names directly as loop variable
for(i in names(mtcars)[1:6]) {
plot_6[[i]] <- ggplot(mtcars, aes(x = !!sym(i))) +
geom_density() +
labs(title = var)
}
# Use map, which directly generates a list of plots
plot_6 = map(names(mtcars)[1:6],
~ggplot(mtcars, aes(x = !!sym(.x))) +
geom_density() +
labs(title = .x)
)
Any of these produces the same list of plots:
wrap_plots(plot_6)

How to create a matrix of plots with R and ggplot2

I am trying to arrange n consecutive plots into one single matrix of plots. I get the plots in first place by running a for-loop, but I can't figure out how to arrange those into a 'plot of plots'. I have used par(mfrow=c(num.row,num.col)) but it does not work. Also multiplot(plotlist = p, cols = 4) and plot_grid(plotlist = p)
#import dataset
Survey<-read_excel('datasets/Survey_Key_and_Complete_Responses_excel.xlsx',
sheet = 2)
#Investigate how the dataset looks like
glimpse(Survey)#library dplyr
#change data types
Survey$brand <- as.factor(Survey$brand)
Survey$zipcode <- as.factor(Survey$zipcode)
Survey$elevel <- as.factor(Survey$elevel)
Survey$car <- as.numeric(Survey$car)
#Relation brand-variables
p = list()
for(i in 1:ncol(Survey)) {
if ((names(Survey[i])) == "brand"){
p[[i]]<-ggplot(Survey, aes(x = brand)) + geom_bar() +
labs(x="Brand")
} else if (is.numeric(Survey[[i]]) == "TRUE"){
p[[i]]<-ggplot(Survey, aes(x = Survey[[i]], fill=brand)) + geom_histogram() +
labs(x=colnames(Survey[i]))
} else {
p[[i]]<-ggplot(Survey, aes(x = Survey[[i]], fill = brand)) + geom_bar() +
labs(x=colnames(Survey[i]))
}
}
I think plots are appended correctly to the list but I can not plot them in a matrix form.
The problem does not appear to be with your multiple plots, but how you are calling the variable into your plot.
You've already put "Survey" into ggplot as the first argument (the data slot). In the mapping argument (the second slot), you put in aes(...) and inside that you should be specifying variable names, not data itself. So try this:
Where you have aes(x = Survey[[i]], fill=brand)) in two places,
put aes(x = names(Survey[[i]], fill=brand)) instead.
Regarding plotting multiple plots, par(mfrow... is for base R plots and cannot be used for ggplots. grid.arrange, multiplot, and plot_grid should all work once you fix the error in your plot.

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)

Resources