Reputation: 4201
I want avoid repeating code for plotting, so I wrote a simple function for ggplot
. I specify several preferences using theme()
. However, if I want to apply an external theme (e.g., theme_bw()
), it overrides my specific theme()
preferences. If I had been writing code not in a user defined function, the solution would've been simple: rearrange the structure of ggplot
's code so that theme_bw()
comes before theme()
. But I don't know how to address this in the case of a custom function.
df <-
data.frame(
x_names = c("asia", "europe", "america", "africa", "australia"),
y_values = 1:5
)
## x_names y_values
## 1 asia 1
## 2 europe 2
## 3 america 3
## 4 africa 4
## 5 australia 5
library(ggplot2)
plot_from_data <- function(data_input, x_col, y_col) {
p_barplot <-
ggplot(data = data_input, aes(x = {{ x_col }}, y = {{ y_col }}, fill = as_factor({{ x_col}} ))) +
geom_bar(stat = "identity") +
labs(caption = "caption blah") +
theme(plot.title = element_text(hjust = 0.5, size = 14), ## I have a bunch
axis.text.x=element_text(angle = -60, hjust = 0), ## of preferences
axis.title.x = element_blank(), ## I set up using
legend.title = element_blank(), ## `theme()`
panel.grid = element_blank(),
panel.grid.major=element_blank(),
plot.caption = element_text(hjust = 0, size = 8),
legend.position = "none")
return(p_barplot)
}
p <- plot_from_data(data_input = df,
x_col = x_names,
y_col = y_values)
p
p + theme_bw()
Yikes. This is undesired. I didn't want a legend and didn't want x axis title, etc.
So one solution is to go back to plot_from_data
's code and add theme_bw()
before theme()
. But this is not a good solution because I want to be able to flexibly apply different themes on top of plot_from_data
's plot, without having to re-edit the function's code for each plot.
Another solution would be to keep both bw_theme()
and theme()
outside of plot_from_data
. Then, for each plot I generate I'd add p
+ theme_bw()
+ theme(...)
. But this is undesired as well because theme()
is full of preferences I don't want to repeat each time.
How could I solve this? One idea I have, but don't know how to apply, is having a "theme placeholder" within plot_from_data
, before theme()
. Something like:
plot_from_data <- function(data_input, x_col, y_col) {
p_barplot <-
ggplot(data = data_input, aes(x = {{ x_col }}, y = {{ y_col }}, fill = as_factor({{ x_col}} ))) +
geom_bar(stat = "identity") +
labs(caption = "caption blah") +
***theme_placeholder*** + ################################## <----------------------- here
theme(plot.title = element_text(hjust = 0.5, size = 14),
axis.text.x=element_text(angle = -60, hjust = 0),
axis.title.x = element_blank(),
legend.title = element_blank(),
panel.grid = element_blank(),
panel.grid.major=element_blank(),
plot.caption = element_text(hjust = 0, size = 8),
legend.position = "none")
return(p_barplot)
}
However, I don't know how to make this flexible enough. One problem I can immediately think of is with ggthemes
library that sometimes requires two components for a theme (e.g., theme_economist()
and scale_color_economist()
).
Any ideas?
Upvotes: 3
Views: 727
Reputation: 93871
You could add an optional theme as an argument to the function. If you need to add a color scale, you can tack this on after the function runs, since the scale function won't override any theme
elements.
library(tidyverse)
library(ggthemes)
plot_from_data <- function(data_input, x_col, y_col, my_theme=NULL) {
my_theme = list(my_theme)
p_barplot <- ggplot(data = data_input, aes(x = {{ x_col }}, y = {{ y_col }}, fill = as_factor({{ x_col}} ))) +
geom_col() +
labs(caption = "caption blah") +
my_theme +
theme(plot.title = element_text(hjust = 0.5, size = 14),
axis.text.x=element_text(angle = -60, hjust = 0),
axis.title.x = element_blank(),
legend.title = element_blank(),
panel.grid = element_blank(),
panel.grid.major=element_blank(),
plot.caption = element_text(hjust = 0, size = 8),
legend.position = "none")
return(p_barplot)
}
plot_from_data(iris, Species, Petal.Width)
plot_from_data(iris, Species, Petal.Width, theme_bw())
plot_from_data(iris, Species, Petal.Width, theme_economist()) +
scale_fill_economist()
You could even add the scale function in the my_theme
argument if you enter the argument as a list:
plot_from_data(iris, Species, Petal.Width,
list(theme_economist(), scale_fill_economist())
Another option (maybe overkill) would be to allow the user to optionally add the name of a fill scale as a string. For example:
plot_from_data <- function(data_input, x_col, y_col, my_theme=NULL, my_fill_scale=NULL) {
my_theme = list(my_theme)
p_barplot <- ggplot(data = data_input, aes(x = {{ x_col }}, y = {{ y_col }}, fill = as_factor({{ x_col}} ))) +
geom_col() +
labs(caption = "caption blah") +
my_theme +
theme(plot.title = element_text(hjust = 0.5, size = 14),
axis.text.x=element_text(angle = -60, hjust = 0),
axis.title.x = element_blank(),
legend.title = element_blank(),
panel.grid = element_blank(),
panel.grid.major=element_blank(),
plot.caption = element_text(hjust = 0, size = 8),
legend.position = "none")
if(!is.null(my_fill_scale)) {
p_barplot = p_barplot +
match.fun(paste0("scale_fill_", my_fill_scale))()
}
return(p_barplot)
}
plot_from_data(iris, Species, Petal.Width, theme_economist())
plot_from_data(iris, Species, Petal.Width, theme_economist(), "economist")
plot_from_data(iris, Species, Petal.Width, theme_economist(), "fivethirtyeight")
Going even further, you could use the same trick to enter the theme as a string and also set up the function to automatically pick the corresponding scale, where appropriate:
plot_from_data <- function(data_input, x_col, y_col, my_theme=NULL, my_fill_scale=NULL) {
p <- ggplot(data = data_input, aes(x ={{ x_col }}, y={{ y_col }}, fill=as_factor({{ x_col}} ))) +
geom_bar(stat = "identity") +
labs(caption = "caption blah")
if(!is.null(my_theme)) {
p = p + match.fun(paste0("theme_", my_theme))()
}
p = p + theme(plot.title = element_text(hjust = 0.5, size = 14),
axis.text.x=element_text(angle = -60, hjust = 0),
axis.title.x = element_blank(),
legend.title = element_blank(),
panel.grid = element_blank(),
panel.grid.major=element_blank(),
plot.caption = element_text(hjust = 0, size = 8),
legend.position = "none")
if(!is.null(my_fill_scale)) {
p = p +
match.fun(paste0("scale_fill_", my_fill_scale))()
}
if(!is.null(my_theme)) {
if(my_theme %in% c("economist","fivethirtyeight","few") &
is.null(my_fill_scale)) {
p = p + match.fun(paste0("scale_fill_", my_theme))()
}
}
return(p)
}
plot_from_data(iris, Species, Petal.Width, "economist")
plot_from_data(iris, Species, Petal.Width, "economist", "viridis_d")
plot_from_data(iris, Species, Petal.Width, "economist", "few")
plot_from_data(iris, Species, Petal.Width, "fivethirtyeight")
Upvotes: 2