当 R 中缺少参数时,如何调用另一个函数以使用其默认参数?

When an arguments is missing in R, how to call another function to use its default argument?

我有一个带有多个可选参数的函数,可以使用 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)
}

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

您可以使用 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:

Currently missing can only be used in the immediate body of the function that defines the argument, not in the body of a nested function or a local call. This may change in the future.

This is a ‘special’ primitive function: it must not evaluate its argument.

(强调我的)。你不能给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 创建

您可以使用 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 可能包含 未计算的表达式 ,因此计算需要在调用环境中发生(在上述两种情况下都已完成)。如果你想注入你自己的函数局部名称,你需要在添加它们之前评估它们,否则它们将找不到。