最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

r - Pass an optional argument to a custom function for use both directly and in an ellipsis construct - Stack Overflow

programmeradmin1浏览0评论

Sometimes I write functions that involve renaming/relabeling a lot of variables in different places, using a relabeller. The relabeller is used in multiple contexts, both directly in my code (e.g. for titles) and passed to other functions (e.g. facet_wrap). This is fine for typical uses:

library(ggplot2)
library(dplyr)

plotfcn <- function(df, x, y, facet, fancy_labs){
  # Strings of x & y (for axis titles)
  xStr <- fancy_labs(deparse(substitute(x)))
  yStr <- fancy_labs(deparse(substitute(y)))
  
  #base plot
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point()
  
  #add facets (with labels) and axis labels
  p + facet_wrap(vars({{facet}}), labeller=fancy_labs) +
    labs(x=xStr, y=yStr)
}

mylabs <- as_labeller(c(
  'compact' = 'Small Family Car',
  'midsize' = 'Medium Family Car',
  'minivan' = 'For the Kids',
  'pickup' = 'For the Stuff',
  'displ' = 'Engine Displacement (L)',
  'hwy' = 'Highway Mileage (mpg)'))

classlist <- c('compact', 'midsize', 'minivan', 'pickup')

mpg_lim <- mpg %>% filter(class %in% classlist) 

mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs)

Now I'd like to make the labeller argument optional. Wrapping anything involving labels in an if/else statement is easy:

plotfcn <- function(df, x, y, facet, fancy_labs=NULL){
  # Strings of x & y (for axis titles)
  if(!is.null(fancy_labs)) {
    xStr <- fancy_labs(deparse(substitute(x)))
    yStr <- fancy_labs(deparse(substitute(y)))
  } 
  
  #base plot
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() 
  
  #optional: add labels using labeller
  if(!is.null(fancy_labs)) { 
    p + 
      facet_wrap(vars({{facet}}), labeller=fancy_labs) +
      labs(x=xStr, y=yStr)
    } else { 
      p + facet_wrap(vars({{facet}}))  
    }
}

mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs)

mpg_lim %>% plotfcn(displ, hwy, class)

However, if possible I'd like to avoid putting the facet_wrap() argument into the if/else statement. (In my real code, it's already in an if/else sequence, so adding this additional if/else will add a lot of branches and repeated copy/pasting, which is cumbersome to work with. ) This answer suggests using an ellipse construct to pass the labeller argument to facet_wrap, which works great - except that I can't figure out how to then call the same labeller function for the axis labels. Would I need to pass the same argument into my code twice under two different names, as shown below, or is there any way to call the labeller argument both through the ellipses construct and independently in my code?

plotfcn <- function(df, x, y, facet, fancy_labs=NULL, ...){
  # Strings of x & y (for axis titles)
  if(!is.null(fancy_labs)) {
    xStr <- fancy_labs(deparse(substitute(x)))
    yStr <- fancy_labs(deparse(substitute(y)))
  } 
  
  #base plot - calls labeller using ...
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() +
    facet_wrap(vars({{facet}}), ...)
  
  #optional: add labels using labeller
  if(!is.null(fancy_labs)) { 
    p + labs(x=xStr, y=yStr)
    } else { 
      p
    }
}

#this works, but involves calling the same parameter (mylabs) twice under two different names, which is annoying
mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs, labeller=mylabs)
mpg_lim %>% plotfcn(displ, hwy, class)

If I set the function up with one labeller argument function(df, x, y, facet, labeller = NULL, ...), the ellipsis construct doesn't trigger, and my facets are unlabelled. But if I set it up with just the ellipses function(df, x, y, facet, ...), I can't check for its presence using is.null() or missing() to trigger my if statements. Is there any way to get the if statements to recognize the presence/absence of the labeller argument?

Sometimes I write functions that involve renaming/relabeling a lot of variables in different places, using a relabeller. The relabeller is used in multiple contexts, both directly in my code (e.g. for titles) and passed to other functions (e.g. facet_wrap). This is fine for typical uses:

library(ggplot2)
library(dplyr)

plotfcn <- function(df, x, y, facet, fancy_labs){
  # Strings of x & y (for axis titles)
  xStr <- fancy_labs(deparse(substitute(x)))
  yStr <- fancy_labs(deparse(substitute(y)))
  
  #base plot
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point()
  
  #add facets (with labels) and axis labels
  p + facet_wrap(vars({{facet}}), labeller=fancy_labs) +
    labs(x=xStr, y=yStr)
}

mylabs <- as_labeller(c(
  'compact' = 'Small Family Car',
  'midsize' = 'Medium Family Car',
  'minivan' = 'For the Kids',
  'pickup' = 'For the Stuff',
  'displ' = 'Engine Displacement (L)',
  'hwy' = 'Highway Mileage (mpg)'))

classlist <- c('compact', 'midsize', 'minivan', 'pickup')

mpg_lim <- mpg %>% filter(class %in% classlist) 

mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs)

Now I'd like to make the labeller argument optional. Wrapping anything involving labels in an if/else statement is easy:

plotfcn <- function(df, x, y, facet, fancy_labs=NULL){
  # Strings of x & y (for axis titles)
  if(!is.null(fancy_labs)) {
    xStr <- fancy_labs(deparse(substitute(x)))
    yStr <- fancy_labs(deparse(substitute(y)))
  } 
  
  #base plot
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() 
  
  #optional: add labels using labeller
  if(!is.null(fancy_labs)) { 
    p + 
      facet_wrap(vars({{facet}}), labeller=fancy_labs) +
      labs(x=xStr, y=yStr)
    } else { 
      p + facet_wrap(vars({{facet}}))  
    }
}

mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs)

mpg_lim %>% plotfcn(displ, hwy, class)

However, if possible I'd like to avoid putting the facet_wrap() argument into the if/else statement. (In my real code, it's already in an if/else sequence, so adding this additional if/else will add a lot of branches and repeated copy/pasting, which is cumbersome to work with. ) This answer suggests using an ellipse construct to pass the labeller argument to facet_wrap, which works great - except that I can't figure out how to then call the same labeller function for the axis labels. Would I need to pass the same argument into my code twice under two different names, as shown below, or is there any way to call the labeller argument both through the ellipses construct and independently in my code?

plotfcn <- function(df, x, y, facet, fancy_labs=NULL, ...){
  # Strings of x & y (for axis titles)
  if(!is.null(fancy_labs)) {
    xStr <- fancy_labs(deparse(substitute(x)))
    yStr <- fancy_labs(deparse(substitute(y)))
  } 
  
  #base plot - calls labeller using ...
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() +
    facet_wrap(vars({{facet}}), ...)
  
  #optional: add labels using labeller
  if(!is.null(fancy_labs)) { 
    p + labs(x=xStr, y=yStr)
    } else { 
      p
    }
}

#this works, but involves calling the same parameter (mylabs) twice under two different names, which is annoying
mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs, labeller=mylabs)
mpg_lim %>% plotfcn(displ, hwy, class)

If I set the function up with one labeller argument function(df, x, y, facet, labeller = NULL, ...), the ellipsis construct doesn't trigger, and my facets are unlabelled. But if I set it up with just the ellipses function(df, x, y, facet, ...), I can't check for its presence using is.null() or missing() to trigger my if statements. Is there any way to get the if statements to recognize the presence/absence of the labeller argument?

Share Improve this question asked Feb 10 at 17:30 JakenJaken 5432 silver badges10 bronze badges 1
  • 3 Maybe you could have an if statement inside the facet_wrap layer, replicating the default value if the fancy labs aren't present: facet_wrap(vars({{facet}}), labeller = if(!is.null(fancy_labs)) fancy_labs else "label_value") – Gregor Thomas Commented Feb 10 at 17:48
Add a comment  | 

1 Answer 1

Reset to default 4

You can use list(...) to pull out and work with specific arguments passed to ...:

library(ggplot2)
library(dplyr)

plotfcn <- function(df, x, y, facet, ...){
  # get `labeller` arg if present
  fancy_labs <- list(...)$labeller

  # Strings of x & y (for axis titles)
  if(!is.null(fancy_labs)) {
    xStr <- fancy_labs(deparse(substitute(x)))
    yStr <- fancy_labs(deparse(substitute(y)))
  } 
  
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() +
    facet_wrap(vars({{facet}}), ...)
  
  if(!is.null(fancy_labs)) { 
    p + labs(x=xStr, y=yStr)
    } else { 
      p
    }
}

mpg_lim %>% plotfcn(displ, hwy, class, labeller=mylabs)

mpg_lim %>% plotfcn(displ, hwy, class)

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论