在R中只需要一组多个参数

时间:2017-10-16 02:34:53

标签: r function arguments argument-matching

我正在开发R中的框架,并希望我的一个功能是多用途的。我想通过要求传递一组参数来实现这一点。换句话说,我想编写一个函数foo,它需要参数xy或参数a。如果既未提供集合,如果集合不完整,或者如果提供了两个集合,则应抛出错误。

实现此目的的一种方法是仅使用可选参数,然后使用if语句。这在下面说明。但是,我想更优雅地做到这一点。

foo <- function(x, y, a){
  if (!missing(a) & missing(x) & missing(y)){
    a
    return("Only a provided")
  } else if (missing(a) & !missing(x) & !missing(y)){
    x; y
    return("x and y provided")
  } else {
  stop("No complete and/or distinct argument set provided")
}

该功能应如下工作:

> foo(a = 1)
[1] "Only a provided"

> foo(x = 1, y = 2)
[1] "x and y provided"

> foo()
Error in foo() : No complete and/or distinct argument set provided

> foo(x = 1)
Error in foo(x = 1) : No complete and/or distinct argument set provided

> foo(x = 1, y = 2, a = 3)
Error in foo(x = 1, y = 2, a = 3) : 
  No complete and/or distinct argument set provided

额外功劳还包括可以处理任意数量的任意数量的参数集的通用答案。

除此之外:上面的示例使用missing()并且没有参数默认值,但这绝不是必需的。我可以灵活地使用各种格式,只要它们为手头的问题提供了一个很好的解决方案。

2 个答案:

答案 0 :(得分:1)

从我的评论中,有两个想法。

missing

这基本上是你的方法,略有修改(主要是为了风格和/或可读性,只是美学):

foo1 <- function(x, y, z, a, b) {
  # first argument set
  allFirst <- ! any(missing(x), missing(y), missing(z))
  anyFirst <- any(! missing(x), ! missing(y), ! missing(z))
  # second argument set
  allSecond <- ! any(missing(a), missing(b))
  anySecond <- any(! missing(a), ! missing(b))

  if ( (allFirst && anySecond) ||
         (allSecond && anyFirst))
    stop("provide either arguments x,y,z or a,b", call. = FALSE)
  if ( (anyFirst && ! allFirst) ||
         (anySecond && ! allSecond) )
    stop("no complete and/or distinct argument set provided", call. = FALSE)

  if (allFirst) {
    return("x,y,z provided")
  } else if (allSecond) {
    return("a,b provided")
  } else {
    stop("nothing provided", call. = FALSE)
  }  
}
foo1(a = 1, b = 2)
# [1] "a,b provided"
foo1(x = 1, y = 2, z = 3)
# [1] "x,y,z provided"
foo1()
# Error: nothing provided
foo1(x = 1)
# Error: no complete and/or distinct argument set provided
foo1(a = 1)
# Error: no complete and/or distinct argument set provided
foo1(x = 1, b = 2)
# Error: no complete and/or distinct argument set provided

S3方法调度

仅当参数集由差异类区分时才有效。例如,如果xdata.framealist,那么......

请注意,第一个定义(启用其他定义)设置公共参数,因此所有函数都需要使用x作为第一个参数:

foo2 <- function(x, ...) UseMethod("foo2", x)
foo2.data.frame <- function(x, y, z) {
  if (missing(y) || missing(z)) stop("no complete and/or distinct argument set provided for 'x'", call. = FALSE)
  return("x,y,z provided")
}
foo2.list <- function(x, b, ...) {
  if (missing(b)) stop("no complete and/or distinct argument set provided for 'a'", call. = FALSE)
  return("a,b provided")
}

...所以我们不能在正式定义中使用function(a, b)

foo2(x = data.frame(), y = 1, z = 2)
# [1] "x,y,z provided"
foo2(x = list(), b = 1)
# [1] "a,b provided"
foo2(data.frame())
# Error: no complete and/or distinct argument set provided for 'x'
foo2(x = list())
# Error: no complete and/or distinct argument set provided for 'a'
foo2()
# Error in foo2() (from #1) : argument "x" is missing, with no default
foo2(x=data.frame(), b=2)
# Error in foo2.data.frame(x = data.frame(), b = 2) (from #1) : 
#   unused argument (b = 2)

需要在第一个函数中使用省略号...,但在其他两个函数中它有点风格,可能没有必要,因为它允许将一些参数传递给其他伴随/依赖函数

错误消息在这里应该更具描述性,因为(至少)所有函数都将假设x的第一个参数(而不是a)。

此选项正在执行称为多态的操作,其中函数根据提供的数据类别的行为有很大不同。如果它总是返回相同类型的对象,这会减少一点,但即便如此,有些人也会发现它不受欢迎。

请注意,许多标准R函数都使用此调度,包括cprintstr

答案 1 :(得分:1)

@ r2evans显示的方法的另一个选择是使用...来发送:

foo <- function(...) {
    args <- list(...)
    if("a" %in% names(args) && "x" %in% names(args) && y %in% names(args))
        stop("need either 'a' or 'x' and 'y' arguments.")
    if("a" %in% names(args)) return(foo_a(a=args[["a"]]))
    if("x" %in% names(args) && "y" %in% names(args)) return(foo_xy(x=args[["x"]], y=args[["y"]])
    stop("need either 'a' or 'x' and 'y' arguments.")
}

您需要定义foo_afoo_xy来进行实际计算。这种方法的缺点是它只适用于命名参数;调用foo(2,3)而不是foo(x=2, y=3)会导致错误。这可以通过查看上面代码中args的长度来解决,但如果处理越来越多的参数,这会很快变得混乱。

另一种选择是将参数集收集到(S3或S4)对象中,并在参数集类上进行调度,如下所示

xy_arg <- function(x,y) {
    ans <- list(x=x, y=y)
    class(ans) <- "xy_arg"
    return(ans)
}
a_arg - function(a) {
    ans <- list(a=a)
    class(ans) <- "a_arg"
    return(ans)
}
foo <- function(x, ...) UseMethod("foo", x)
foo.xy_arg <- function(x, ...) { 
    #compute for argument set where x and y is given
    print(x[["x"]], x[["y"]])
}
foo.a_arg <- function(x, ...) 
    print(x[["a"]])
}
foo(xy_arg(x=1, y=2))
foo(a_arg(a=3))

这看起来更复杂,但它允许以系统的方式定义更多的参数集。 `

还可以设想将foo定义为仅在一个参数集上工作,并使用xy_arga_arg来构建规范化的接口对象,即从(x)执行问题转换,y)或(a)规范问题。