Разделите аргументы `...` и распределите по нескольким функциям
Используя следующую функцию 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()
.
Ответы
Ответ 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"