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

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

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

4b9b3361

Ответ 1

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

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:

i) Функция 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

Ответ 2

  • Почему ошибка 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
    
  • Как разбить ... на разные функции?

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

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

Ответ 3

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