为 ggplot 编写自定义函数:添加外部主题时如何避免覆盖 `theme()` 设置

Writing a custom function for ggplot: How to avoid overriding `theme()` settings when adding external themes

我想避免重复绘图代码,所以我为 ggplot 写了一个简单的函数。我使用 theme() 指定了几个首选项。但是,如果我想应用外部主题(例如 theme_bw()),它会覆盖我特定的 theme() 首选项。 如果我一直在用户定义的函数中编写代码而不是,解决方案会很简单:重新排列ggplot的结构的代码,以便 theme_bw() 出现在 theme() 之前。但我不知道如何在自定义函数的情况下解决这个问题。

数据

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

哎呀。这是不希望的。我不想要图例,也不想要 x 轴标题等。 所以一种解决方案是回到plot_from_data的代码并在theme()之前添加theme_bw()。但这不是一个好的解决方案,因为我希望能够在 plot_from_data 的情节之上灵活地应用不同的主题 ,而不必 re-edit 函数的每个地块的代码。

另一个解决方案是将 bw_theme()theme() 都放在 plot_from_data 之外。然后,对于我生成的每个图,我将添加 p + theme_bw() + theme(...)。但这也是不希望的,因为 theme() 充满了我不想每次都重复的偏好。

我该如何解决这个问题?我有一个想法,但不知道如何应用,在 theme() 之前的 plot_from_data 中有一个“主题占位符”。类似于:

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

但是,我不知道如何让它足够灵活。我能立即想到的一个问题是 ggthemes 库有时需要一个主题的两个组件(例如,theme_economist()scale_color_economist())。

有什么想法吗?

您可以添加一个可选主题作为函数的参数。如果您需要添加色标,可以在函数运行后添加,因为色标函数不​​会覆盖任何 theme 元素。

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

如果您将参数作为列表输入,您甚至可以在 my_theme 参数中添加缩放函数:

plot_from_data(iris, Species, Petal.Width, 
               list(theme_economist(), scale_fill_economist())

另一个选项(可能有点矫枉过正)是允许用户有选择地将填充比例的名称添加为字符串。例如:

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

更进一步,您可以使用相同的技巧将主题作为字符串输入,并设置函数以在适当的地方自动选择相应的音阶:

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