【问题标题】:When an arguments is missing in R, how to call another function to use its default argument?当 R 中缺少参数时,如何调用另一个函数以使用其默认参数?
【发布时间】:2026-01-22 11:55:01
【问题描述】:

我有一个带有多个可选参数的函数来使用 ggplot2 修改绘图。如果缺少这些参数中的任何一个,我想在 ggplot2 中使用它们的默认值。如果只有一个参数,可以这样做:

simple_plot <- function (color) {
   p <- ggplot(mtcars, aes(cyl, mpg))

   if(!missing(color))
   {
      p <- p + geom_point(col = color)
   } else {
      p <- p + geom_point()            # use its default if the argument is missing
   }
}

但是,当有多个参数来修改绘图的点形状、填充、轴标题、中断、限制等时,这不是一个好的解决方案。有没有更好的方法?

[编辑以澄清我的问题] 感谢那些回答我问题的人。我想通过包含以下代码来澄清我的问题。将调用多个函数,并且每个函数都有多个参数。使用do.call 似乎不是一种简单的实现方式。

better_plot <- function(col.y1, shape.y1, col.y2, shape.y2,
                        title.y1, breaks.y1, title.y2, breaks.y2, title){
  
  p <- ggplot(mtcars, aes(x = cyl)) +

    # 1st y data points
    case_when(
      missing(col.y1) & missing(shape.y1) ~  geom_point(aes(y = mpg)),
      missing(col.y1) & !missing(shape.y1) ~  geom_point(aes(y = mpg), shape = shape.y1),
      !missing(col.y1) & missing(shape.y1) ~  geom_point(aes(y = mpg), col = col.y1),
      !missing(col.y1) & !missing(shape.y1) ~  geom_point(aes(y = mpg), col = col.y1, shape = shape.y1)
    ) +

    # 2nd y data points
    case_when(
      missing(col.y2) & missing(shape.y2) ~  geom_point(aes(y = hp)),
      missing(col.y2) & !missing(shape.y2) ~  geom_point(aes(y = hp), shape = shape.y2),
      !missing(col.y2) & missing(shape.y2) ~  geom_point(aes(y = hp), col = col.y2),
      !missing(col.y2) & !missing(shape.y2) ~  geom_point(aes(y = hp), col = col.y2, shape = shape.y2)
    ) +

    # y axises - labels and breaks
    case_when(
      !missing(title.y1) & !missing(breaks.y1) & !missing(title.y2) & !missing(breaks.y2) ~ 
        scale_y_continuous(
          name = title.y1, breaks = breaks.y1,
          sec.axis = sec_axis(name = title.y2, breaks = breaks.y2)),
      
      !missing(title.y1) & !missing(breaks.y1) & !missing(title.y2) & missing(breaks.y2) ~ 
        scale_y_continuous(
          name = title.y1, breaks = breaks.y1, 
          sec.axis = sec_axis(name = title.y2)),
      
      !missing(title.y1) & !missing(breaks.y1) & missing(title.y2) & missing(breaks.y2) ~ 
        scale_y_continuous(
          name = title.y1, breaks = breaks.y1),
      
      .... # the remaining combinations...
      
    )
  
  # add plot title if any
  if(!missing(title)) p <- p + ggtitle(title)

  return(p)
}

【问题讨论】:

  • 所以您希望将命名参数分配给不同的函数,例如ggplotgeom_pointscale_x_continuous 等?
  • 是的。例如,title.x 和 title.y 用于轴标题。如果缺少这些参数中的任何一个,则轴标题将由 ggplot2 分配,默认情况下是 x 和 y 变量的名称。

标签: r ggplot2 arguments


【解决方案1】:

您可以使用do.call 调用带有参数列表的函数。因此,您需要做的就是将您的论点收集到一个列表中,并忽略缺失的那些。

只要您的参数与相应的“ggplot2”函数中的参数名称相同,只需一个简单的match.call()

simple_plot = function (color, shape, fill) {
    args = as.list(match.call())[-1L]
    ggplot(mtcars, aes(cyl, mpg)) + do.call('geom_point', args, envir = parent.frame())
}

对于更复杂的功能(例如参数名称不匹配),您可以在将列表传递给 do.call 之前对其进行操作;您还可以使用as.call 构造一个未评估的调用并在以后评估它:

the_call = as.call(c(as.name('geom_point'), args))
# … evaluate it:
eval.parent(the_call)

请注意,args 可能包含未计算的表达式,因此需要在调用环境中进行计算(在上述两种情况下都已完成)。如果你想注入你自己的函数本地名称,你需要在添加它们之前评估它们,否则它们将找不到。

【讨论】:

  • 感谢您的回答。我添加了一些代码来澄清我的问题。像scale_y_continuous(name = title.y1, breaks = breaks.y1, sec.axis = sec_axis(name = title.y2, breaks = breaks.y2)) 这样的嵌套函数调用怎么样?
  • @HiCLH 嵌套调用并没有真正改变任何东西。在您的代码中,您当然会添加对参数列表的一些处理,因为并非所有参数都被分派给同一个函数——因此您需要根据其名称拆分列表。但除此之外,同样的方法也有效。
【解决方案2】:

如果你调用一个没有参数的函数,它将使用默认值,所以如果你真的想在你的包装函数中包含其中一些参数,一个选择是复制默认值,作为新的默认值。

例如,您可以使用formals() 提取函数的默认值

formals(ggplot2::geom_point)
#> $mapping
#> NULL
#> 
#> $data
#> NULL
#> 
#> $stat
#> [1] "identity"
#> 
#> $position
#> [1] "identity"
#> 
#> $...
#> 
#> 
#> $na.rm
#> [1] FALSE
#> 
#> $show.legend
#> [1] NA
#> 
#> $inherit.aes
#> [1] TRUE

请注意,col 不是其中之一; col 是通过 ... 传递的,因此更难处理。

您可以检索这些默认值,但要遍历您想要使用missing() 的包装器中缺少哪些默认值,这有其自身的复杂性...来自?missing

目前缺少的只能在定义参数的函数的直接主体中使用,不能在嵌套函数或本地调用的主体中使用。这在未来可能会改变。

这是一个“特殊”的原始函数:它不能评估它的参数

(强调我的)。你不能将符号传递给missing(),它需要字符串,所以你不能做sapply(args, missing)之类的事情,它不起作用。

不过,如果不需要,您可以通过不评估自己的论点来保留缺失。例如

my_fun <- function(na.rm = formals(ggplot2::geom_point)[["na.rm"]], 
                   position = formals(ggplot2::geom_point)[["position"]], 
                   ...) {
  ggplot2::geom_point(na.rm = na.rm, position = position, ...)
}

my_fun(na.rm = TRUE, position = "jitter")
#> geom_point: na.rm = TRUE
#> stat_identity: na.rm = TRUE
#> position_jitter

my_fun(position = "jitter")
#> geom_point: na.rm = FALSE
#> stat_identity: na.rm = FALSE
#> position_jitter

my_fun(na.rm = TRUE)
#> geom_point: na.rm = TRUE
#> stat_identity: na.rm = TRUE
#> position_identity

all.equal(ggplot2::geom_point(), my_fun())
#> [1] TRUE

如果您指定它们,这将使用命名参数(如果您真的需要,您甚至可以更改它们的名称),否则使用默认值。请注意,我没有包装 all 默认值,只是我希望在我的函数中包含的那些。您仍然可以将其他参数传递给...,否则采用默认值。

ggplot2::ggplot(mtcars, ggplot2::aes(cyl, hp)) + my_fun(position = "jitter", col = "red")

reprex package (v0.3.0) 于 2020 年 12 月 28 日创建

【讨论】: