R语言 拆分'...'参数并分配给多个函数

5cg8jx4n  于 2023-03-15  发布在  其他
关注(0)|答案(5)|浏览(135)

使用下面的函数foo()作为一个简单的例子,如果可能的话,我想将...中给出的值分配给两个不同的函数。

foo <- function(x, y, ...) {
    list(sum = sum(x, ...), grep = grep("abc", y, ...))
}

在下面的例子中,我想把na.rm传递给sum(),把value传递给grep(),但是grep()中有一个未使用的参数,我得到了一个错误。

X <- c(1:5, NA, 6:10)
Y <- "xyzabcxyz"
foo(X, Y, na.rm = TRUE, value = TRUE)
# Error in grep("abc", y, ...) : unused argument (na.rm = TRUE)

看起来参数是先发送到grep()的,对吗?我认为R会先看到并计算sum(),然后返回一个错误。
此外,在尝试拆分...中的参数时,我遇到了麻烦。sum()的形式参数是NULL,因为它是.Primitive,因此不能使用

names(formals(sum)) %in% names(list(...))

我也不想假设剩下的论点

names(formals(grep)) %in% names(list(...))

将自动传递到sum()

如何安全高效地将...参数分发给多个函数,从而避免不必要的求值?

从长远来看,我希望能够将此应用于具有一长串...参数的函数,类似于download.file()scan()

prdp8dxp

prdp8dxp1#

单独的列表如果你真的想给不同的函数传递不同的参数集,那么指定单独的列表可能更简洁:

foo <- function(x, y, sum = list(), grep = list()) {
 list(sum = do.call("sum", c(x, sum)), grep = do.call("grep", c("abc", y, grep)))
}

# test

X <- c(1:5, NA, 6:10)
Y <- "xyzabcxyz"
foo(X, Y, sum = list(na.rm = TRUE), grep = list(value = TRUE))

## $sum
## [1] 55
## 
## $grep
## [1] "xyzabcxyz"

**混合列表/...**另一种方法是我们可以对其中一个使用...,然后将另一个指定为列表,特别是在其中一个经常使用而另一个不经常使用的情况下。经常使用的一个通过...传递,不经常使用的通过列表传递。例如:

foo <- function(x, y, sum = list(), ...) {
 list(sum = do.call("sum", c(x, sum)), grep = grep("abc", y, ...))
}

foo(X, Y, sum = list(na.rm = TRUE), value = TRUE)

下面是R本身的几个混合方法的例子:
i)mapply函数采用使用...MoreArgs列表的方法:

> args(mapply)
function (FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE) 
NULL

ii)nls也采用使用...control列表两者的这种方法:

> args(nls)
function (formula, data = parent.frame(), start, control = nls.control(), 
    algorithm = c("default", "plinear", "port"), trace = FALSE, 
    subset, weights, na.action, model = FALSE, lower = -Inf, 
    upper = Inf, ...) 
NULL
eyh26e7m

eyh26e7m2#

1.为什么grepsum之前出错?
可以看到sum在参数方面更加灵活:

X <- c(1:5, NA, 6:10)
sum(X, na.rm = TRUE, value = TRUE)
## [1] 56

它不会失败,因为它不关心其他命名参数,所以value = TRUE简化为TRUE,其总和为1。

sum(X, na.rm = TRUE)
## [1] 55

1.如何将...拆分为不同的函数?
一种方法(很容易出错)是查找目标函数的参数,例如:

foo <- function(x, y, ...){
    argnames <- names(list(...))
    sumargs <- intersect(argnames, names(as.list(args(sum))))
    grepargs <- intersect(argnames, names(as.list(args(grep))))
    list(sum = do.call(sum, c(list(x), list(...)[sumargs])),
         grep = do.call(grep, c(list("abc", y), list(...)[grepargs])))
}

每当args(例如S3对象)没有正确报告函数 * 使用 * 的参数时,这很容易出错。

names(as.list(args(plot)))
## [1] "x"   "y"   "..." ""   
names(as.list(args(plot.default)))
##  [1] "x"           "y"           "type"        "xlim"        "ylim"       
##  [6] "log"         "main"        "sub"         "xlab"        "ylab"       
## [11] "ann"         "axes"        "frame.plot"  "panel.first" "panel.last" 
## [16] "asp"         "..."         ""

在这种情况下,您可以替换为适当的S3函数,因此,我没有一个通用的解决方案(尽管我不知道它是否存在)。

uurity8g

uurity8g3#

如果另一个函数包含了传递给...的所有命名参数,或者它本身有一个...参数,则只能将...参数传递给另一个函数。因此对于sum,这没有问题(args(sum)返回function (..., na.rm = FALSE))。另一方面,grep既没有na.rm也没有...作为参数。

args(grep)
# function (pattern, x, ignore.case = FALSE, perl = FALSE, value = FALSE, 
#     fixed = FALSE, useBytes = FALSE, invert = FALSE)

这不包括...,也不包括命名参数na.rm。一个简单的解决方案是定义你自己的函数mygrep,如下所示:

mygrep <- function (pattern, x, ignore.case = FALSE, perl = FALSE, value = FALSE, 
                    fixed = FALSE, useBytes = FALSE, invert = FALSE, ...)
  grep(pattern, x, ignore.case, perl, value, fixed, useBytes, invert)

然后它似乎起作用了:

foo <- function(x, y, ...){
  list(sum = sum(x, ...), grep = mygrep("abc", y, ...))
}
X <- c(1:5, NA, 6:10)
Y <- "xyzabcxyz"
foo(X, Y, na.rm = TRUE, value = TRUE)

# $sum
# [1] 56
# 
# $grep
# [1] "xyzabcxyz"
zd287kbt

zd287kbt4#

TL;DR

考虑使用在plot.default和其他地方实现的“技巧”。它是经过尝试和测试的。

foo <- function(x, y, ...) {
    localSum <- function(..., value) sum(...)
    localGrep <- function(..., na.rm) grep(...)
    list(sum = localSum(x, ...), grep = localGrep("abc", y, ...))
}

X <- c(1:5, NA, 6:10)
Y <- "xyzabcxyz"
foo(X, Y, na.rm = TRUE, value = TRUE)
## $sum
## [1] 55
## 
## $grep
## [1] "xyzabcxyz"
##

我有点震惊,plot.default使用的“技巧”还没有出现在这里。(也许它在其他地方提到过?)
回想一下,plot.default必须处理内部plot.xy(用于点和线)的可选参数 * 以及plot.windowboxaxistitle的可选参数 *,这些都是通过一个...参数处理的。
重要的是,当我们将lwd = 6传递给plot.default来加宽绘图区域中的线时,plot.default必须确保boxaxis看不到这个参数,这两个参数使用lwd来设置绘图区域边界和轴线的宽度。

> plot.default(0:1, 0:1, type = "l", lwd = 6)

plot.default的此功能是使用子函数plot.windowboxaxistitle的“本地”版本实现的。本地版本只是 Package 器,旨在过滤掉...中不允许子函数查看的参数(在这种情况下为colbgpchcexltylwd)。

> plot.default
function (x, y = NULL, type = "p", xlim = NULL, ylim = NULL, 
    log = "", main = NULL, sub = NULL, xlab = NULL, ylab = NULL, 
    ann = par("ann"), axes = TRUE, frame.plot = axes, panel.first = NULL, 
    panel.last = NULL, asp = NA, xgap.axis = NA, ygap.axis = NA, 
    ...) 
{
    localAxis <- function(..., col, bg, pch, cex, lty, lwd) Axis(...)
    localBox <- function(..., col, bg, pch, cex, lty, lwd) box(...)
    localWindow <- function(..., col, bg, pch, cex, lty, lwd) plot.window(...)
    localTitle <- function(..., col, bg, pch, cex, lty, lwd) title(...)
    xlabel <- if (!missing(x)) 
        deparse1(substitute(x))
    ylabel <- if (!missing(y)) 
        deparse1(substitute(y))
    xy <- xy.coords(x, y, xlabel, ylabel, log)
    xlab <- if (is.null(xlab)) 
        xy$xlab
    else xlab
    ylab <- if (is.null(ylab)) 
        xy$ylab
    else ylab
    xlim <- if (is.null(xlim)) 
        range(xy$x[is.finite(xy$x)])
    else xlim
    ylim <- if (is.null(ylim)) 
        range(xy$y[is.finite(xy$y)])
    else ylim
    dev.hold()
    on.exit(dev.flush())
    plot.new()
    localWindow(xlim, ylim, log, asp, ...)
    panel.first
    plot.xy(xy, type, ...)
    panel.last
    if (axes) {
        localAxis(if (is.null(y)) 
            xy$x
        else x, side = 1, gap.axis = xgap.axis, ...)
        localAxis(if (is.null(y)) 
            x
        else y, side = 2, gap.axis = ygap.axis, ...)
    }
    if (frame.plot) 
        localBox(...)
    if (ann) 
        localTitle(main = main, sub = sub, xlab = xlab, ylab = ylab, 
            ...)
    invisible()
}
<bytecode: 0x10b813010>
<environment: namespace:graphics>

让我们以localTitle为例:

localTitle <- function(..., col, bg, pch, cex, lty, lwd) title(...)

plot.default在调用时将...中的所有参数传递给localTitle

localTitle(main = main, sub = sub, xlab = xlab, ylab = ylab, ...)

但是title只会看到那些没有命名为colbgpchcexltylwd的自变量,因为这些自变量被包括作为localTitle的形式自变量。
值得注意的是,这种处理...的方法具有最小的开销,因为它充分利用了惰性求值。...中的表达式在被子函数使用之前不会被求值。

ct2axkht

ct2axkht5#

这个答案并不直接回答最初的问题,但可能对其他在 * 自己的 * 函数(而不是 * 现有的 * 函数,如sumgrep)上遇到类似问题的人有所帮助。
@shadow的回答包含了一个见解,指出了在这种情况下一个非常简单的解决方案:只要确保嵌套函数有...作为参数,就不会出现unused argument错误。
例如:

nested1 <- function(x, a) {
 x + a
}

nested2 <- function(x, b) {
 x - b
}

f <- function(x, ...) {
 if (x >= 0) {
  nested1(x, ...)
 } else {
  nested2(x, ...)
 }
}

如果我们调用f(x = 2, a = 3, b = 4),我们会得到一个错误:Error in nested1(x, ...) : unused argument (b = 4) .
但是,只需将...添加到nested1nested2的形式中,然后再次运行:

nested1 <- function(x, a, ...) {
 x + a
}

nested2 <- function(x, b, ...) {
 x - b
}

现在,f(x = 2, a = 3, b = 4)将产生所需的结果:5.问题解决了。

相关问题