Переслать все аргументы, переданные методу подмножества `[`, в другой объект в R.
В R у меня есть класс S4, который «обертывает» вектор. Я хочу перенаправить многие обычные методы S3 и S4 этого класса в егоvec
slot, который содержит вектор. Однако у меня возникли особые проблемы с этим методом из-за его особых свойств. Изменить: я хочу переслать все аргументы, включая , ,
drop
и т.д. к этому внутреннему вектору точно так же, как они передаются внешнему объекту-оболочке .
Вот простой пример моего кода и того, что я пробовал. Во-первых, мы начнем с определения этого простого класса и создания его экземпляра:
> Foo = setClass("Foo", slots=c(vec = "numeric"))
> foo = Foo(vec=c(1, 2, 3))
> foo
An object of class "Foo"
Slot "vec":
[1] 1 2 3
Пока все хорошо, далее мы пытаемся перенаправить все аргументы на использование обычных точек:
> setMethod("[", signature=c(x="Foo"), function(x, ...){
+ print(list(...))
+ x@vec[...]
+ })
> foo[2]
list()
[1] 1 2 3
Очевидно, что это не работает, потому что это вообще не подмножество вектора. Но не только это, оператор печати показывает, что ничего не фиксируется, хотяi=2
здесь проходит. Поэтому я отказываюсь от использования .
Затем я пытаюсь явно использоватьi
и :
> setMethod("[", signature=c(x="Foo"), function(x, i, j, ..., drop = TRUE){
+ x@vec[i, j, drop=drop]
+ })
> foo[2]
Error in x@vec[i, j, drop = drop] : incorrect number of dimensions
Это не работает, потому что я всегда прохожу мимоj
, но в R нет механизма выборочной передачи аргументов, поэтому я здесь немного застрял.
Далее я пробую:
> setMethod("[", signature=c(x="Foo"), function(x, ...){
+ call = as.list(match.call())
+ fun = call[[1]]
+ str(fun)
+ args = call[-1]
+ str(args)
+ do.call(fun, args)
+ })
> foo[2]
symbol [
List of 2
$ x: symbol foo
$ i: num 2
Error in do.call(fun, args) :
'what' must be a function or character string
Это интересно, потому что доказывает, чтоmatch.call
захватывает дополнительные аргументы, где...
не сделал, а потому чтоx
и фиксируются как символы, а не как их реальные значения, это не так просто сделать, и неясно, как их решить.
Как тогда я могу переслать все аргументы, переданные в[
метод для другого объекта?
2 ответа
Многие из этих вопросов можно понять, внимательно прочитав?setMethod
. Я рассмотрю ваши предложения одно за другим, а затем добавлю свои.
1)
setMethod("[", signature = c(x = "Foo"),
function(x, ...) {
print(list(...))
x@vec[...]
})
Формальные аргументы метода не совпадают с аргументами универсальной функции. исправляет их автоматически, без предупреждения (вздох). Следовательно, ваш фактический метод выглядит так:
getMethod("[", signature = c(x = "Foo"))
Method Definition:
function (x, i, j, ..., drop = TRUE)
{
print(list(...))
x@vec[...]
}
Signatures:
x
target "Foo"
defined "Foo"
Когда мы звонимfoo[2]
, мы находим этоx
Матчиfoo
, Матчи2
, и...
ничего не соответствует. Следовательноlist(...)
пусто иx@vec[...]
оценивается какx@vec
.
2)
setMethod("[", signature = c(x = "Foo"),
function(x, i, j, ..., drop = TRUE) {
x@vec[i, j, drop = drop]
})
Формальные аргументы этого метода хороши, но вы индексируете вектор как массив, что является ошибкой. Вы можете игнорировать все, кроме и просто вернутьсяx@vec[i]
, но там еще есть ловушки (например,i
может отсутствовать).
3)
setMethod("[", signature = c(x = "Foo"),
function(x, ...) {
call <- as.list(match.call())
fun <- call[[1]]
str(fun)
args <- call[-1]
str(args)
do.call(fun, args)
})
Это неверно, потому что (опять же) формальные аргументы не соответствуют аргументам общей функции, и по указанной вами причине:fun
является символом иargs
представляет собой список языковых объектов (и, возможно, констант), а это не то, чтоdo.call
надеется. В любом случае, мое мнение, что подходы, основанные на этом, слишком сложны и хрупки.
Как бы я это сделал
Ваше определение класса подразумевает, чтоfoo@vec
не имеетdim
атрибут. Это всегда числовой вектор, а не массив.
Foo <- setClass("Foo", slots = c(vec = "numeric"))
foo.v <- Foo(vec = c(1, 2, 3))
foo.a <- Foo(vec = toeplitz(1:6))
## Error in validObject(.Object) :
## invalid class "Foo" object: invalid object for slot "vec" in class "Foo": got class "matrix", should be or extend class "numeric"
Для этого векторного класса я бы определил:
setMethod("[", signature = c(x = "Foo", i = "ANY", j = "missing", drop = "missing"),
function(x, i, j, ..., drop = TRUE) {
if (nargs() > 2L)
stop("'x' of class Foo must be indexed as x[i]")
else if (missing(i))
x@vec
else x@vec[i]
})
Это позволяетx[]
илиx[i]
при этом запрещая всеx[drop=]
,x[i, ]
,x[i, , drop=]
,x[i, j]
, иx[i, j, drop=]
, что не имеет смысла для векторных классов.
foo.v[]
## [1] 1 2 3
foo.v[-2L]
## [1] 1 3
foo.v[1L, 1L, drop = FALSE]
## Error in foo.v[1L, 1L, drop = FALSE] : object of type 'S4' is not subsettable
Эта ошибка немного непрозрачна. Это происходит потому, что наш метод не отправляется на подпись при вызове. На практике такие случаи можно обнаружить с помощью дополнительных методов, выдающих более прозрачные ошибки.
Теперь рассмотрим второй класс, аналогичный , но чейvec
slot может быть числовым вектором или любым массивом.
setClassUnion("vectorOrArray", c("numeric", "array"))
Bar <- setClass("Bar", slots = c(vec = "vectorOrArray"))
bar.v <- Bar(vec = c(1, 2, 3))
bar.a <- Bar(vec = toeplitz(1:6))
Для этого класса, подобного вектору или массивуBar
, я бы определил:
setMethod("[", signature = c(x = "Bar", i = "ANY", j = "ANY", drop = "ANY"),
function(x, i, j, ..., drop = TRUE) {
x <- x@vec
callGeneric()
})
где реализуется то, что вы пытались реализовать с помощьюmatch.call
иeval
но более надежным способом.
bar.v[]
## [1] 1 2 3
bar.v[-2L]
## [1] 1 3
bar.v[1L, 1L, drop = FALSE]
## Error in x[i = i, j = j, drop = drop] : incorrect number of dimensions
bar.a[1L, 1L, drop = FALSE]
## [,1]
## [1,] 1
Можно также определить метод дляFoo
на основе , но для чисто векторного класса накладные расходыcallGeneric
может быть барьером. Более ранний метод, использующий толькоnargs
иmissing
намного быстрее, и нам часто нужны методы для[
должны быть тщательно оптимизированы, поскольку они имеют тенденцию вызываться в циклах.
microbenchmark::microbenchmark(foo.v[-2L], bar.v[-2L], times = 1000L)
## Unit: nanoseconds
## expr min lq mean median uq max neval
## foo.v[-2L] 861 1148 1385.062 1271 1476 11808 1000
## bar.v[-2L] 12382 14842 15671.225 15498 16031 77613 1000
Примечание
ДваsetMethod
вызовы выше следуют двум передовым практикам:
- Формальные аргументы методов совпадают с аргументами универсальной функции.
- Для ясности сигнатуры указывают класс каждого формального аргумента. Пропущенные формальные аргументы получают класс
"ANY"
, без предупреждения (вздох), что удивляет многих новичков в S4.
Я нашел одно рабочее решение, которое мне не очень нравится, — это фактически отредактироватьcall
:
> setMethod("[", signature=c(x="Foo"), function(x, ...){
+ call = match.call()
+ # Edit the actual expression to make R think the user
+ # put `foo@vec` instead of `foo`
+ call[[2]] = quote(x@vec)
+ eval(call)
+ })
> foo[2]
[1] 2
> foo[3]
[1] 3