Неожиданная ошибка log2 при определении универсальных обобщенных групп S3 для нового класса - PullRequest
0 голосов
/ 27 августа 2018

Я пытаюсь использовать S3 "Math" групповые дженерики для пользовательского класса. Однако я получаю странный результат: log() работает, а log2 и log10 выдают ошибки. Ниже приведен минимальный пример:

# simple class with just the new name
lameclass <- function(x) {
  class(x) <- append(class(x), "lame")
  x
}

# It prints something when Math generics methods are used
Math.lame <- function(x, ...) {
  print("I am lame")
  NextMethod()
}

# an object of the class
lamevector <- lameclass(1:10)

> class(lamevector)
[1] "integer" "lame"

Теперь попробуйте позвонить log:

log(lamevector)
[1] "I am lame"
[1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101 2.0794415 2.1972246 2.3025851

С основанием 2:

log(lamevector, 2)
[1] "I am lame"
[1] 0.000000 1.000000 1.584963 2.000000 2.321928 2.584963 2.807355 3.000000 3.169925 3.321928

Все выше сработало. Но теперь log2 Обертка:

log2(lamevector)
[1] "I am lame"
[1] "I am lame"
Error in log2.default(1:10, 2) :
  2 arguments passed to 'log2' which requires 1

Может быть, кто-то может помочь мне выяснить, что здесь происходит? Действительно ли log2 дважды прошел определение Math и потерпел неудачу?

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Похоже, что NextMethod не удаляет класс lame, поэтому, когда log2 вызывает log, он повторно отправляет метод lame, который теперь больше не работает, потому что он вызывает log2 с base = 2L, параметр log2 не имеет.

Чтобы заставить диспетчер работать правильно, не требуется слишком много работы - просто удалите и заново добавьте класс. (В сторону: подклассы должны быть добавлены, а не добавлены.)

lameclass <- function(x) {
    class(x) <- c("lame", class(x))    # prepend new class
    x
}

Math.lame <- function(x, ...) {
    print("I am lame")
    class(x) <- class(x)[class(x) != "lame"]    # strip lame class
    lameclass(NextMethod())    # re-add lame class to result
}

lamevector <- lameclass(1:5)

log(lamevector)
#> [1] "I am lame"
#> [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379
#> attr(,"class")
#> [1] "lame"    "numeric"
log(lamevector, 2)
#> [1] "I am lame"
#> [1] 0.000000 1.000000 1.584963 2.000000 2.321928
#> attr(,"class")
#> [1] "lame"    "numeric"
log2(lamevector)
#> [1] "I am lame"
#> [1] 0.000000 1.000000 1.584963 2.000000 2.321928
#> attr(,"class")
#> [1] "lame"    "numeric"

Я не совсем уверен почему он так отправляет. Групповые дженерики немного странны и отправляются на oldClass вместо class, что может быть или не быть частью проблемы. Это может быть просто ошибка. Идиома удаления и повторного добавления класса используется в других Math методах, возможно по этой причине:

MASS:::Math.fractions
#> function (x, ...) 
#> {
#>     x <- unclass(x)
#>     fractions(NextMethod())
#> }
#> <bytecode: 0x7ff8782a1558>
#> <environment: namespace:MASS>
0 голосов
/ 30 августа 2018

Как уже упоминалось в комментарии log2, log10 не входят в общий шаблон S3 Math. Фактически, exp, expm1, log, log10, log2 и log1p являются S4 универсальными и являются членами универсальной группы Math.

Один из способов реализовать то, что вы хотите сделать, - определить ваш класс как S4 .

setClass("lame4", slots = c(x = "numeric"))

И определить метод Math универсальная группа:

setMethod("Math","lame4",function(x) {
                x@x <- callGeneric(x@x)
                x
          }) 
## pretty print 
setMethod("show", "lame4",function(object)print(object@x))

Теперь давайте проверим это:

l1 <- new("lame4",x=1:10)

Тогда:

log2(l1)
 [1] 0.000000 1.000000 1.584963 2.000000 2.321928 2.584963 2.807355 3.000000 3.169925 3.321928
> log10(l1)
 [1] 0.0000000 0.3010300 0.4771213 0.6020600 0.6989700 0.7781513 0.8450980 0.9030900 0.9542425
[10] 1.0000000 

Это, конечно, не прямой ответ на ваш вопрос, но объясняет, почему ваша реализация не работает. Здесь я думаю, что использование S4 парадигмы - хорошая идея, потому что у вас будет более сильная типизация, что очень полезно для математики. Методы S4 также отлично работают с интерфейсом R.C / Rcpp. Но если вы новичок в этом, есть определенная кривая обучения (зависит от вашего опыта развития)

...