Переслать все аргументы, переданные методу подмножества `[`, в другой объект в R.

В R у меня есть класс S4, который «обертывает» вектор. Я хочу перенаправить многие обычные методы S3 и S4 этого класса в егоvecslot, который содержит вектор. Однако у меня возникли особые проблемы с этим методом из-за его особых свойств. Изменить: я хочу переслать все аргументы, включая , , 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

Эта ошибка немного непрозрачна. Это происходит потому, что наш метод не отправляется на подпись при вызове. На практике такие случаи можно обнаружить с помощью дополнительных методов, выдающих более прозрачные ошибки.

Теперь рассмотрим второй класс, аналогичный , но чейvecslot может быть числовым вектором или любым массивом.

      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
Другие вопросы по тегам