Есть ли способ использовать два оператора ' ... ' в функции в R?

я хочу написать функцию, которая называет как plot() и legend() и было бы идеально, если бы пользователь мог указать ряд дополнительных аргументов, которые затем передаются либо plot() или legend(). Я знаю, что могу достичь этого для одной из двух функций, используя ...:

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

foo.plot(1,1, xaxt = "n")

мимо xaxt = "n" на участок. Но есть ли способ, например, пройти, например title = "legend" до legend() звонок без prespecifying аргументов в функцию заголовок?


обновление из принятого ответа:

сначала я проверил, какой из параметров я хочу передать legend и для plot. Первым шагом к этой цели было увидеть, какие аргументы legend уникальные для legend и не часть сюжета и / или par:

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

я использую 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))

в этом примере pch передается как plot и legend, title передается только в legend и ylim только plot.


обновление 2 на основе комментария Гэвина Симпсона (см. также комментарии в ответе Витошки):
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

(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, что я и хотел).

Итак, вы правы во всех своих пунктах для моего примера. Но для моей реальной функции (с гораздо большим количеством переменных) я столкнулся с этими проблемами и хотел документировать их здесь. Извините за то, что неточный.

4 ответов


автоматический способ:

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]))
}

pch должен быть отфильтрован из имен, чтобы избежать дублирования в legend вызов в случае, если пользователь поставляет "pch", но у вас есть идея. Отредактировано Jan 2012 Carl W: "do.вызов " работает только с функциями в кавычках, как в обновлениях Хенрика. Я отредактировал его здесь, чтобы избежать путаницы в будущем.


эти вещи получить сложно, и нет простых решений без указания дополнительных аргументов в функции. Если бы ты ... --6--> и в plot и legend звонки вы бы в конечном итоге получать предупреждения при прохождении в legend-конкретные аргументы. Например, с помощью:

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

вы получаете следующие предупреждения:

> 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

есть способы обойти эту проблему, см. plot.default и его локальные функции, определенные как обертки вокруг функций, таких как axis, box etc. где у вас было бы что-то вроде 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, ...)
}

(совсем почему то 'plot' аргумент нуждается в дефолте вне меня, но он не будет работать, не давая ему по умолчанию TRUE.)

теперь это работает без предупреждений:

bar.plot(1, 1, xjust = 0.5, title = "foobar", pch = 3)

как вы обрабатываете графические параметры, такие как будет - bty повлияет на тип поля графика и легенда типа коробки. Обратите внимание также, что я обработал 'pch' по-другому, потому что если кто-то использовать этот аргумент в bar.plot() вызов, вы бы i) используя различные символы в легенде/сюжете, и вы получите предупреждение или ошибку о 'pch' в два раза соответствующие.

как вы можете видеть, это становится довольно сложно...


ответ Джориса дает интересное решение, которое я прокомментировал, напомнило мне о аргументах контрольных списков в таких функциях, как lme(). Вот мой версия ответа Джориса, реализующая идею этого контрольного списка:

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)
}

который работает так, используя второй пример вызова Джори, соответствующим образом модифицированный:

foo.plot(1,1, xaxt = "n", legend.args=la.args(bg = "yellow", title = "legend"))

вы можете быть как вам нравится при настройке la.args() function-здесь я устанавливаю только значения по умолчанию для Аргументов Joris и объединяю любые другие. Было бы легче, если бы la.args() содержит все аргументы Легенды со значениями по умолчанию.


один из способов-использовать списки аргументов в сочетании с 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"))

один недостаток заключается в том, что вы не можете указать, например, pch=2, например, в легенде.список аргументов. Вы можете обойти это с некоторыми предложениями if, я оставлю это вам, чтобы продолжить возиться с ним.


Edit: см. ответ Гэвина Симпсона для лучшей версии этой идеи.


мы могли бы построить formalize функция, которая сделает любую функцию совместимой с точками:

formalize <- function(f){
  # add ... to formals
  formals(f) <- c(formals(f), alist(...=))
  # release the ... in the local environment
  body(f)    <- substitute({x;y},list(x = quote(list2env(list(...))),y = body(f)))
  f
}

foo.plot <- function(x,y,...) {
  legend <- formalize(legend) # definition is changed locally
  plot(x,y,...)
  legend("bottomleft", "bar", pch = 1, ...)
}    

foo.plot(1,1, xaxt = "n", title = "legend")

он показывает предупреждения, хотя, потому что сюжет получает аргументы, которые он не ожидает.

мы могли бы использовать suppressWarnings на вызов сюжета (но он будет подавлять все предупреждения, очевидно), или мы могли бы разработать другую функцию, чтобы сделать plot функция (или любая функция) локально более толерантна к точкам:

casualize <- function(f){
  f_name <- as.character(substitute(f))
  f_ns   <- getNamespaceName(environment(f))
  body(f) <- substitute({
      # extract all args
      args <- as.list(match.call()[-1]);
      # relevant args only
      args <- args[intersect(names(formals()),names(args))]
      # call initial fun with relevant args
      do.call(getExportedValue(f_ns, f_name),args)})
  f
}

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

foo.plot(1,1, xaxt = "n", title = "legend")

теперь он работает нормально!