Simultaneously applying same modification to all ggarrange subplots - r

I am in the process of finalising some ggplot figures for a journal with strict graphics requirements.
For example, I have to make sure that the text size is smaller than 7pt, and I defined the following variable, which I can add to any ggplot:
reqs <- theme(axis.text = element_text(size=5, family='sans'), text=element_text(size=7,family='sans'))
I was thinking of generalising this one step further, by writing a ggsave_reqs function which applies the required changes to simple ggplots or ggarrange objects.
According to ?ggarrange, elements from this class are either ggplots or lists of ggplots, and so I tried automatising "suplot changes' with:
lapply(ggarr, function(gg) gg + reqs )
However, this does not work, and I get the bug:
" Cannot add ggproto objects together. Did you forget to add this object to a ggplot object?"
Does anyone have any ideas on whether automatising these ggarrange subplot modifications is possible?
A minimal working example is as follows:
library(ggplot2)
library(ggpubr)
df <- data.frame(X=runif(1000, 0,1),
Y=runif(1000, 2,3),
CLASS=as.factor(rbinom(1000, size=1, p=.5)))
.g <- function(DF)
{
ggplot(DF, aes(x=X, y=Y,color=CLASS)) +
geom_point()
}
ggarr <- ggarrange( .g(df), .g(df[1:10,]))
reqs <- theme(legend.position='bottom')
# none of these works
ggarr + reqs
lapply(ggarr, function(p){ p + reqs } )
# however this works
ggarr <- ggarrange( .g(df) + reqs, .g(df[1:10,]) + reqs)

Related

ggplot - global scale_color_manual [duplicate]

I would like to define a color palette for every plot built in a markdown document. In essence this would overwrite the default choices.
There are several very old answers -- thanks for the links here and here suggested by #dww -- that solve for old versions (specifically calling out a solution on 0.8.2 when the modern release several major releases ahead, currently at 3.2.x).
I'll illustrate the closest use case, setting themes. In the case of general purpose themes, this is trivial: rather than appending + theme_minimal() on every plot, I can instead set the theme which persists across all plots.
library(ggplot2)
d <- diamonds[sample(1:nrow(diamonds), 1000), ]
## without theming
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
theme_minimal() # must set this theme call for every plot
## setting theme
theme_set(theme_minimal())
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() # plot in theme, for all subsequent plots
Is there a similar, modification that exists to set the color palette throughout? For example, a theme-based replacement for calling,
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
scale_color_brewer(palette='Set2') # requesting a global option to set this for all plots
The linked solution that does not depend on old versions instead overloads the entire ggplot function. That seems risky.
ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer(palette = 'Set1')
There is a ggplot_global environment which is used internally within ggplot2 but isn't exported. You can see its structure by temporarily unlocking the bindings of a ggplot function and modifying it to return the contents of the environment as a list. You can do this non-destructively like this:
library(ggplot2)
get_ggplot_global <- function()
{
unlockBinding("theme_set", as.environment("package:ggplot2"))
backup <- body(theme_set)[[5]]
body(theme_set)[[5]] <- substitute(return(as.list(ggplot_global)))
global_list <- theme_set(theme_bw())
body(theme_set)[[5]] <- backup
lockBinding("theme_set", as.environment("package:ggplot2"))
return(global_list)
}
global_list <- get_ggplot_global()
names(global_list)
#> [1] "date_origin" "element_tree" "base_to_ggplot" "all_aesthetics"
#> [5] "theme_current" "time_origin"
By examining this you can see that ggplot global environment has an object called theme_current, which is why you can set the various line, text and axis elements globally including their colours.
When you are talking about a colour scheme in your question, you are referring to the colours defined in a scale object. This is not part of the ggplot_global environment, and you can't change the default scale object because there isn't one. When you create a new ggplot(), it has an empty slot for "scales".
You therefore have a few options:
Wrap ggplot with my_ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer()
Overwrite ggplot with the above function (as suggested by #Phil)
Create your own theme object that you add on with standard ggplot syntax
The best thing might be to just write a wrapper around ggplot. However, the third option is also quite clean and idiomatic. You could achieve it like this:
set_scale <- function(...)
{
if(!exists("doc_env", where = globalenv()))
assign("doc_env", new.env(parent = globalenv()), envir = globalenv())
doc_env$scale <- (ggplot() + eval(substitute(...)))$scales$scales[[1]]
}
my_scale <- function() if(exists("doc_env", where = globalenv())) return(doc_env$scale)
You would use this by doing (for example)
set_scale(scale_color_brewer(palette = "Set2"))
At the start of your document.
So now you can just do + my_scale() for each plot:
d <- diamonds[sample(1:nrow(diamonds), 1000), ]
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
my_scale()

Set a document-persistent ggplot2 color theme

I would like to define a color palette for every plot built in a markdown document. In essence this would overwrite the default choices.
There are several very old answers -- thanks for the links here and here suggested by #dww -- that solve for old versions (specifically calling out a solution on 0.8.2 when the modern release several major releases ahead, currently at 3.2.x).
I'll illustrate the closest use case, setting themes. In the case of general purpose themes, this is trivial: rather than appending + theme_minimal() on every plot, I can instead set the theme which persists across all plots.
library(ggplot2)
d <- diamonds[sample(1:nrow(diamonds), 1000), ]
## without theming
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
theme_minimal() # must set this theme call for every plot
## setting theme
theme_set(theme_minimal())
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() # plot in theme, for all subsequent plots
Is there a similar, modification that exists to set the color palette throughout? For example, a theme-based replacement for calling,
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
scale_color_brewer(palette='Set2') # requesting a global option to set this for all plots
The linked solution that does not depend on old versions instead overloads the entire ggplot function. That seems risky.
ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer(palette = 'Set1')
There is a ggplot_global environment which is used internally within ggplot2 but isn't exported. You can see its structure by temporarily unlocking the bindings of a ggplot function and modifying it to return the contents of the environment as a list. You can do this non-destructively like this:
library(ggplot2)
get_ggplot_global <- function()
{
unlockBinding("theme_set", as.environment("package:ggplot2"))
backup <- body(theme_set)[[5]]
body(theme_set)[[5]] <- substitute(return(as.list(ggplot_global)))
global_list <- theme_set(theme_bw())
body(theme_set)[[5]] <- backup
lockBinding("theme_set", as.environment("package:ggplot2"))
return(global_list)
}
global_list <- get_ggplot_global()
names(global_list)
#> [1] "date_origin" "element_tree" "base_to_ggplot" "all_aesthetics"
#> [5] "theme_current" "time_origin"
By examining this you can see that ggplot global environment has an object called theme_current, which is why you can set the various line, text and axis elements globally including their colours.
When you are talking about a colour scheme in your question, you are referring to the colours defined in a scale object. This is not part of the ggplot_global environment, and you can't change the default scale object because there isn't one. When you create a new ggplot(), it has an empty slot for "scales".
You therefore have a few options:
Wrap ggplot with my_ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer()
Overwrite ggplot with the above function (as suggested by #Phil)
Create your own theme object that you add on with standard ggplot syntax
The best thing might be to just write a wrapper around ggplot. However, the third option is also quite clean and idiomatic. You could achieve it like this:
set_scale <- function(...)
{
if(!exists("doc_env", where = globalenv()))
assign("doc_env", new.env(parent = globalenv()), envir = globalenv())
doc_env$scale <- (ggplot() + eval(substitute(...)))$scales$scales[[1]]
}
my_scale <- function() if(exists("doc_env", where = globalenv())) return(doc_env$scale)
You would use this by doing (for example)
set_scale(scale_color_brewer(palette = "Set2"))
At the start of your document.
So now you can just do + my_scale() for each plot:
d <- diamonds[sample(1:nrow(diamonds), 1000), ]
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
my_scale()

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)

Duplicate Same Legend Twice in Ggplot2

I am preparing a chart where I have client's requirement to put same legend on top and bottom. Using ggplot I can put it either at top or at bottom. But I am not aware about option of duplicating at both the places.
I have tried putting legend.position as c('top','bottom') but that is giving me error and I know if should give error.
Can it be done with other libraries? I want to same legend twice at top and at bottom?
Take this code for an instance
library(ggplot2)
bp <- ggplot(data=PlantGrowth, aes(x=group, y=weight, fill=group)) + geom_boxplot()
bp <- bp + theme(legend.position="bottom")
bp
You have to work with the intermediate graphic objects (grobs) that ggplot2 uses when being plotted.
I grabbed a function that was flowing around here on StackOverflow to extract the legend, and put it into a package that is now on CRAN.
Here's a solution:
library(lemon)
bp <- bp + theme(legend.position='bottom')
g <- ggplotGrob(bp)
l <- g_legend(g)
grid.arrange(g, top=l)
g_legend accepts both the grob-version (that cannot be manipulated with ggplot2 objects) and the ordinary ggplot2 objects. Using ggplotGrob is a one-way street; once converted you cannot convert it back to ggplot2. But, as in the example, we keep the original ggplot2 object. ;)
Depending on the use case, a center-aligned top legend may not be appropriate as in the contributed answer by #MrGrumble here: https://stackoverflow.com/a/46725487/5982900
Alternatively, you can copy the "guide-box" element of the ggplotGrob, append it to your grob object, and reset the coordinates to the top of the ggplot.
createTopLegend <- function(ggplot, heightFromTop = 1) {
g <- ggplotGrob(ggplot)
nGrobs <- (length(g$grobs))
legendGrob <- which(g$layout$name == "guide-box")
g$grobs[[nGrobs+ 1]] <- g$grobs[[legendGrob]]
g$layout[nGrobs+ 1,] <- g$layout[legendGrob,]
rightLeft <- unname(unlist(g$layout[legendGrob, c(2,4)]))
g$layout[nGrobs+ 1, 1:4] <- c(heightFromTop, rightLeft[1], heightFromTop, rightLeft[2])
g
}
Load the gridExtra package. From your ggplot object bp, use createTopLegend to duplicate another legend, then use grid.draw to produce your final figure. Note you may need to alter your plot margins depending on your figure.
library(ggplot2)
library(grid)
library(gridExtra)
bp <- ggplot(data=PlantGrowth, aes(x=group, y=weight, fill=group)) + geom_boxplot()
bp <- bp + theme(legend.position="bottom", plot.margin = unit(c(2,0,0,0), "lines"))
g <- createTopLegend(bp)
grid.draw(g)
# dev.off()
This will ensure the legend is aligned in the same way horizontally as it appears in your original ggplot.

ggploting multiple graphs from a data list

I would like to do something along the lines of this post: R: saving ggplot2 plots in a list
The problem is I can't get it to work. I seem to be able to get the individual graphs but the facet_wrap throws out an error. I would be content with just outputting all the graphs and then saving them to disk as a jpg or something, so I can scroll through them later.
for(n in 1:5){
pdata <- data.frame(mt1[n])
library(ggplot2)
p <-ggplot(pdata, aes(x=variable, y=value, color=Legend, group=Legend))+ geom_line()+ facet_wrap(~ color)
}
Link to a dput of the data : mt1
Edit:
Added the whole correct file, its a bit long
If we omit the facet error due to a missing variable in your data frames, you can generate and save your plots in different files this way using ggsave :
for(n in 1:5){
pdata <- data.frame(mt1[n]) # better to use mt1[[n]]
p <-ggplot(pdata, aes(x=variable, y=value, color=Legend, group=Legend))+ geom_line()
ggsave(paste0("plot",n,".jpg"), p)
}
Some suggestions for improvement:
First, as #Dason points out, your library(ggplot2) call should be outside your loop.
Second, if you access an element of list by [.], then the result will still be a list. You should do instead: [[.]] which will render the data.frame(.) call unnecessary (as commented above in the code).
Third is a suggestion to use *apply family of functions. Here, using lapply.
To summarise all these points in code:
require(ggplot2) # load package outside once
o <- lapply(seq_along(mtl), function(idx) {
p <- ggplot(mtl[[idx]], aes(x = variable, y = value,
color = Legend, group = Legend))+ geom_line()
ggsave(paste0("plot",idx,".jpg"), p)
})

Resources