有没有办法在R中的函数中使用两个'...'语句?

Hen*_*rik 37 r function argument-passing optional-arguments

我想编写调用两种功能:plot()legend(),这将是理想的,如果用户可以指定一些额外的参数,然后可通过要么通过plot()legend().我知道我可以使用...以下两种函数之一实现此目的:

foo.plot <- function(x,y,...) {
    plot(x,y,...)
    legend("bottomleft", "bar", pch=1)
}

foo.plot(1,1, xaxt = "n")
Run Code Online (Sandbox Code Playgroud)

这传递xaxt = "n"给情节.但是有没有办法title = "legend"legend()没有预先指定函数头中的参数的情况下将eg传递给调用?


从接受的答案中更新:我认为VitoshKa的方式是实现我想要的最优雅的方式.但是,我必须解决一些小问题,直到它按照我想要的方式工作.

首先,我检查了我要传递给legend哪个参数以及哪个参数plot.为此目的的第一步是查看哪些参数legend是独特的,legend而不是绘图和/或参数的一部分:

legend.args <- names(formals(legend))
plot.args <- c(names(formals(plot.default)), names(par()))
dput(legend.args[!(legend.args %in% plot.args)])
Run Code Online (Sandbox Code Playgroud)

dput()在这里使用,因为该行plot.args <- c(names(formals(plot.default)), names(par()))总是调用一个我不想要的新空图.所以,我使用dput了以下函数的输出.

接下来,我不得不处理重叠的参数(通过它们获取dput(largs.all[(largs.all %in% pargs.all)])).对于一些这是微不足道的(例如x,y)等获得通过这两个功能(例如,pch).但是,在我的实际应用程序中,我甚至使用其他策略(例如,不同的变量名称adj,但在此示例中未实现).

最后,do.call必须以两种方式改变功能.首先,哪个部分(即称为函数)需要是一个字符(即'plot'代替plot).并且参数列表必须构造略有不同.

foo.plot <- function(x,y,...) {
    leg.args.unique <- c("legend", "fill", "border", "angle", "density", "box.lwd", "box.lty", "box.col", "pt.bg", "pt.cex", "pt.lwd", "xjust", "yjust", "x.intersp", "y.intersp", "text.width", "text.col", "merge", "trace", "plot", "ncol", "horiz", "title", "inset", "title.col", "title.adj")
    leg.args.all <- c(leg.args.unique, "col", "lty", "lwd", "pch", "bty", "bg", "cex", "adj", "xpd")
    dots <- list(...)
    do.call('plot', c(list(x = x, y = x), dots[!(names(dots) %in% leg.args.unique)]))
    do.call('legend', c(list("bottomleft", "bar"), dots[names(dots) %in% leg.args.all]))
}


foo.plot(1,1,pch = 4, title = "legendary", ylim = c(0, 5))
Run Code Online (Sandbox Code Playgroud)

在这个例子中,pch被传递到两个plotlegend,title只传递给legend,并ylimplot.


根据Gavin Simpson的评论更新2(另见Vitoshka回答的评论):(
i)这是正确的.
(ii)它总是一个角色.但是如果你有一个与函数同名的变量,那么你需要引用函数名do.call:

min.plot <- function(x,y,plot=TRUE) if(plot == TRUE) do.call(plot, list(x = x, y = y))
min.plot(1,1)
Error in do.call(plot, list(x = x, y = y)) : 
  'what' must be a character string or a function
Run Code Online (Sandbox Code Playgroud)

(iii)您可以使用c(x = 1, y = 1, list())并且工作正常.然而,我真正做过的事情(不是在我给出的例子中,而是在我的实际函数中)如下:c(x = 1, y = 1, xlim = c(0, 2), list(bla='foo'))
请将其与以下内容进行比较:c(list(x = 1, y = 1, xlim = c(0, 2)), list(bla='foo'))
在第一种情况下,列表包含两个元素xlim,xlim1并且xlim2(每个都是标量),在后一种情况下列表只有xlim(这是长度为2的向量,这是我想要的).

所以,对于我的例子,你是对的.但是,对于我的真实函数(有更多变量),我遇到了这些问题,并希望在此处记录它们.抱歉不精确.

Vit*_*hKa 23

一种自动方式:

foo.plot <- function(x,y,...) {
    lnames <- names(formals(legend))
    pnames <- c(names(formals(plot.default)), names(par()))
    dots <- list(...)
    do.call('plot', c(list(x = x, y = x), dots[names(dots) %in% pnames]))
    do.call('legend', c("bottomleft", "bar", pch = 1, dots[names(dots) %in% lnames]))
}
Run Code Online (Sandbox Code Playgroud)

pch必须从lnames中过滤掉,以避免在legend用户提供'pch'的情况下重复调用,但你明白了.2012年1月由Carl W编辑:"do.call"仅适用于引号中的函数,如Henrik的更新.我在这里编辑它以避免将来混淆.


Rei*_*son 18

这些事情变得棘手,如果没有在函数中指定额外的参数,就没有简单的解决方案.如果您...同时使用plotlegend调用,则在传递legend特定参数时最终会收到警告.例如,用:

foo.plot <- function(x,y,...) {
    plot(x,y,...)
    legend("bottomleft", "bar", pch = 1, ...)
}
Run Code Online (Sandbox Code Playgroud)

您收到以下警告:

> foo.plot(1, 1, xjust = 0.5)
Warning messages:
1: In plot.window(...) : "xjust" is not a graphical parameter
2: In plot.xy(xy, type, ...) : "xjust" is not a graphical parameter
3: In axis(side = side, at = at, labels = labels, ...) :
  "xjust" is not a graphical parameter
4: In axis(side = side, at = at, labels = labels, ...) :
  "xjust" is not a graphical parameter
5: In box(...) : "xjust" is not a graphical parameter
6: In title(...) : "xjust" is not a graphical parameter
Run Code Online (Sandbox Code Playgroud)

有一些解决这个问题的方法,请参见plot.default和它的本地函数定义为围绕函数等的包装axis,box其中你有类似localPlot()包装器,内联函数和调用而不是plot()直接调用.

bar.plot <- function(x, y, pch = 1, ...) {
    localPlot <- function(..., legend, fill, border, angle, density,
                          xjust, yjust, x.intersp, y.intersp,
                          text.width, text.col, merge, trace, plot = TRUE, ncol,
                          horiz, title, inset, title.col, box.lwd,
                          box.lty, box.col, pt.bg, pt.cex, pt.lwd) plot(...)
    localPlot(x, y, pch = pch, ...)
    legend(x = "bottomleft", legend = "bar", pch = pch, ...)
}
Run Code Online (Sandbox Code Playgroud)

(为什么'plot'参数需要默认值超出我的范围,但如果不给它默认值,它将不起作用TRUE.)

现在这没有警告:

bar.plot(1, 1, xjust = 0.5, title = "foobar", pch = 3)
Run Code Online (Sandbox Code Playgroud)

你如何处理图形参数bty将取决于你 - bty将影响绘图框类型和图例框类型.另请注意,我的处理方式'pch'不同,因为如果有人在bar.plot()调用中使用该参数,那么您将i)在图例/图中使用不同的字符,并且您会收到有关'pch'匹配两次的警告或错误.

正如你所看到的,这开始变得非常棘手......


Joris的答案提供了一个有趣的解决方案,我评论过它提醒我控制列表中的参数如lme().这是我的Joris'答案版本实现了这个控制列表的想法:

la.args <- function(x = "bottomleft", legend = "bar", pch = 1, ...)
    c(list(x = x, legend = legend, pch = pch), list(...))

foo.plot <- function(x,y, legend.args = la.args(), ...) {
    plot(x, y, ...)
    do.call(legend, legend.args)
}
Run Code Online (Sandbox Code Playgroud)

这是有效的,使用Jori的第二个示例调用,适当修改:

foo.plot(1,1, xaxt = "n", legend.args=la.args(bg = "yellow", title = "legend"))
Run Code Online (Sandbox Code Playgroud)

在设置la.args()函数时,您可以尽可能完整- 这里我只为Joris设置的参数设置默认值,并连接其他任何参数.如果la.args()包含所有带默认值的图例参数会更容易.


Jor*_*eys 6

一种方法是结合使用参数列表do.call.它不是最美丽的解决方案,但确实有效.

foo.plot <- function(x,y,legend.args,...) {
    la <- list(
        x="bottomleft",
        legend="bar",
        pch=1
    )
    if (!missing(legend.args)) la <- c(la,legend.args)
    plot(x,y,...)
    do.call(legend,la)
}
foo.plot(1,1, xaxt = "n")    
foo.plot(1,1, xaxt = "n",legend.args=list(bg="yellow",title="legend"))
Run Code Online (Sandbox Code Playgroud)

一个缺点是您无法在legend.args列表中指定例如pch = 2.你可以通过一些if条款绕过它,我会留给你进一步摆弄它.


编辑:请参阅Gavin Simpson的答案以获得更好的理念.