Подтвердить что ты не робот

Есть ли способ использовать два оператора "..." в функции из 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() без указания аргументов в заголовке функции?


Обновление от принятого ответа: Я думал, что путь VitoshKa был самым элегантным, чтобы выполнить то, что я хотел. Тем не менее, были некоторые незначительные проблемы, с которыми мне пришлось столкнуться, пока они не работали, как я хотел.

Сначала я проверил, какой из параметров, которые я хочу передать, на 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, что является тем, что я разыскивается).

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

4b9b3361

Ответ 1

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

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 должен быть отфильтрован из lnames, чтобы избежать дублирования в вызове legend, если пользователь поставляет "pch", но вы получили эту идею. Edited Jan 2012 от Carl W: "do.call" работает только с функциями в кавычках, как в обновлениях Henrik. Я отредактировал его здесь, чтобы избежать путаницы в будущем.

Ответ 2

Эти вещи становятся сложными, и нет простых решений без указания дополнительных аргументов в вашей функции. Если у вас есть ... для вызовов 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 и т.д., где у вас есть что-то вроде обертки 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, например, будет зависеть от вас - bty будет влиять на тип окна графика и тип окна легенды. Также обратите внимание, что я обработал 'pch' по-другому, потому что, если кто-то использует этот аргумент в вызове bar.plot(), вы должны быть i) с использованием разных символов в легенде/сюжете, и вы получите предупреждение или ошибку о 'pch' совпадающий дважды.

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


Joris 'Answer предоставляет интересное решение, которое, как я прокомментировал, напомнил мне аргументы контрольных списков в таких функциях, как 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)
}

Что работает так, используя второй пример вызова Jori, соответствующим образом измененный:

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

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

Ответ 3

В одном направлении используются списки аргументов в сочетании с 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, например, в списке legend.args. Вы можете обойти это с некоторыми предложениями, я оставлю это вам, чтобы продолжить игру с ним.


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