Разделите аргументы `...` и распределите их по нескольким функциям

Используя следующую функцию 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(),

5 ответов

Решение

Отдельные списки Если вы действительно хотите передать разные наборы параметров разным функциям, то, вероятно, лучше указать отдельные списки:

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:

я) 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
  1. Почему grep ошибка до sum?

    Видеть, что 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
    
  2. Как разделить ... к разным функциям?

    Один из методов (который очень подвержен ошибкам) ​​- поиск аргументов для целевых функций. Например:

    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. Из-за этого у меня нет обобщенного решения для этого (хотя я не знаю, что оно существует или не существует).

ТЛ;ДР

Рассмотрите возможность использования «трюка», реализованного в и других местах. Это проверено и проверено.

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

Важно, когда мы проходим (скажем)lwd = 6Чтобы расширить линии в области графика, необходимо убедиться, что этот аргумент не виден с помощью и , которые используются для установки ширины границы области графика и осевых линий. Следовательно:

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

Данная возможность реализована с помощью «локальных» версий подфункций.plot.window,box,axis, и . Локальные версии представляют собой просто оболочки, предназначенные для фильтрации аргументов, которые нельзя разрешить видеть подфункциям (в данном случае , , , , и ).

      > 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 <- function(..., col, bg, pch, cex, lty, lwd) title(...)

plot.defaultпередает все аргументы при вызове

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

ноtitleвидит только те аргументы, которые не названыcol,bg,pch,cex,lty, илиlwd, поскольку они включены в качестве формальных аргументовlocalTitle.

Примечательно, что этот подход к обработке имеет минимальные накладные расходы, поскольку в полной мере использует преимущества ленивых вычислений. Ни одно из выражений в...оцениваются до тех пор, пока не будут использованы подфункциями.

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

Этот ответ не отвечает непосредственно на исходный вопрос, но может быть полезен другим, у которых возникает аналогичная проблема со своими собственными функциями (в отличие от существующих функций, таких как sumа также grep).

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

Если мы позвоним, то получим ошибку: Error in nested1(x, ...) : unused argument (b = 4).

Но просто добавьте ...к формальностям nested1а также nested2и снова запустить:

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

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

В настоящее время, f(x = 2, a = 3, b = 4)дает желаемый результат: 5. Задача решена.

Другие вопросы по тегам