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


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

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

Я использую здесь 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 пришлось изменить двумя способами. Во-первых, часть what (т. е. вызываемые функции) должна быть символом (т. е. '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, чего я и хотел).

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


person Henrik    schedule 08.11.2010    source источник
comment
Пара пунктов по вашему отредактированному Q; i) par() будет только создавать пустой график, если в данный момент нет открытого устройства. Это безвредно, так как вы собираетесь построить на нем график, ii) вы ошибаетесь насчет аргумента 'what' - это может быть символ или функция, iii) вам не нужна оболочка list() в list("bottomleft", "bar"), потому что все приведено к общему тип --- свидетель c("foo", 1:10, list(a = 1:10, b = letters[1:3])). Хороший вопрос и последующие мысли, но хорошо, чтобы очистить ваши правки для точности.   -  person Gavin Simpson    schedule 10.11.2010
comment
@Gavin Пожалуйста, смотрите мое обновление для ответа на ваши вопросы.   -  person Henrik    schedule 11.11.2010
comment
хорошее обновление и приятно указать на такие угловые случаи. Браво! При столкновении имени функции и имени переменной вы всегда можете быть более осторожным и использовать graphics::plot и graphics::legend, чтобы быть вдвойне уверенным, что найдена правильная функция.   -  person Gavin Simpson    schedule 11.11.2010


Ответы (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 должен быть отфильтрован из lnames, чтобы избежать дублирования в вызове legend в случае, если пользователь вводит «pch», но вы поняли идею. Отредактировано Карлом В. в январе 2012 г .: «do.call» работает только с функциями в кавычках, как в обновлениях Хенрика. Я отредактировал это здесь, чтобы избежать путаницы в будущем.

person VitoshKa    schedule 08.11.2010
comment
Хорошее решение, но вам нужно гораздо больше фильтрации, чем просто pch. Существует ряд параметров, которые могут быть действительными как для сюжета, так и для легенды. Как вы относитесь к ним? - person Joris Meys; 09.11.2010
comment
@Joris, если параметр действителен для обеих функций, он будет передан обеим. Я действительно не понимаю, что вы имеете в виду. Нужно отфильтровать pch, чтобы избежать дублирования в вызове легенды. - person VitoshKa; 09.11.2010
comment
@VitoshKa: предположим, вы хотите установить этот параметр только на графике или только в легенде. Например, скажем пч. Это невозможно с функцией как есть, вы нигде не можете указать, для какой именно функции вы хотите установить параметр. - person Joris Meys; 09.11.2010
comment
@ Джорис, я понимаю, что ты имеешь в виду. В этом случае ваше решение с legend.args было бы, конечно, более подходящим. - person VitoshKa; 09.11.2010
comment
Да, отличный способ. Однако были небольшие проблемы, которые я исправил в своем вопросе. - person Henrik; 10.11.2010
comment
@ Хенрик, ты тут же с номиналом создаешь новый сюжет. Я совершенно забыл об этом :(. Однако в do.call какой аргумент может быть непосредственно функцией или символом, а второй аргумент в моем коде — это список, потому что dots — это список, а c сделает все это списком . Итак, мой код был тут же. У людей может сложиться другое впечатление от вашего обновления. - person VitoshKa; 10.11.2010
comment
@Vitoshka: (1) Когда я использовал функцию вместо символа, я всегда получал предупреждение, поэтому я использовал символ (но, как вы сказали, это сработало). (2) Однако, когда не использовалось c(list(foo),...), происходили ужасные вещи, потому что все элементы, которые не были частью оператора..., были объединены в один элемент списка (я думаю). Это было довольно сложно узнать. - person Henrik; 10.11.2010
comment
@ Хенрик, действительно странно, у меня нет ни одной из этих проблем. - person VitoshKa; 10.11.2010
comment
@Vitoshka Пожалуйста, посмотрите мое второе обновление о том, почему у меня возникли эти проблемы. Извините, что возлагаю на вас ответственность за мои проблемы :) - person Henrik; 11.11.2010

Эти вещи становятся сложными, и нет простых решений без указания дополнительных аргументов в вашей функции. Если бы у вас было ... как в вызовах 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' совпадении дважды.

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


Ответ Джориса предлагает интересное решение, которое, как я прокомментировал, напомнило мне об аргументах контрольных списков в таких функциях, как 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() - здесь я только устанавливаю значения по умолчанию для аргументов, установленных Джорисом, и объединяю любые другие. Было бы проще, если бы la.args() содержал все аргументы легенды со значениями по умолчанию.

person Gavin Simpson    schedule 08.11.2010
comment
Это больше, чем горстка, которую можно переварить сразу ;-) Тем не менее, большое спасибо за ваш обстоятельный ответ, он действительно научил меня кое-чему в передаче аргументов. На самом деле, я получил эту идею от lme и других функций, частично принимающих списки в качестве аргументов для чего-то конкретного. Спасибо за то, что показали, как обойти проблему совпадения с несколькими фактическими аргументами. - person Joris Meys; 08.11.2010
comment
@Джорис: Спасибо. Профессор Рипли обучил меня методу localFoo(), и мы использовали его в одном из пакетов, над которым я работаю. - person Gavin Simpson; 08.11.2010

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


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

person Joris Meys    schedule 08.11.2010
comment
это напоминает мне аргументы control в таких функциях, как lme(), которые используют такие аргументы, как control = lme.control(). lme.control() содержит значения по умолчанию и существует для заполнения списка подходящих аргументов. Я добавлю пример к своему ответу, так как он слишком сложен для комментария. - person Gavin Simpson; 08.11.2010
comment
Спасибо, кажется, это хороший способ сделать это. Я подожду еще некоторое время, прежде чем принять, возможно, появится какой-то другой хороший ответ. Но я уже готов принять это. - person Henrik; 08.11.2010
comment
Вы можете использовать этот подход с modifyList, чтобы иметь возможность объединить legend.args с вашим набором аргументов легенды по умолчанию. - person Charles; 08.11.2010
comment
Также вы можете сделать это другим способом, чтобы сопоставить, например, имена (формалы (легенда)), чтобы сегментировать аргументы, которые будут использоваться для каждой функции. - person Charles; 08.11.2010
comment
@Charles: дело в том, что некоторые аргументы будут соответствовать как формальным (легенда), так и формальным (сюжет). Это проблема, которую проиллюстрировал Гэвин. - person Joris Meys; 08.11.2010

Мы могли бы создать функцию 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")

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

person Moody_Mudskipper    schedule 19.09.2018
comment
Это отличное решение, поскольку оно автоматически избегает предупреждений. - person Henrik; 19.09.2018