Как вы расширяете rbind для данных.подкласс фреймов?


Мой вопрос в том, как вы расширяете rbind() для работы с подклассом data.frame? Я не могу должным образом расширить rbind() для работы даже с очень простым подклассом. Следующий пример демонстрирует эту проблему:

Определение подкласса и метода:

new_df2 <- function(x, ...)
{
  stopifnot(is.data.frame(x))
  structure(x, class = c("df2", "data.frame"), author = "some user")
}

rbind.df2 <- function(..., deparse.level = 1)
{
  NextMethod()
}
Я понимаю, что расширение rbind() в этом случае не обязательно, но мой грандиозный план состоит в том, чтобы использовать rbind.data.frame() на моем подклассе, а затем добавить несколько дополнительных проверок/атрибутов к его результату.

Если вы вызовете далее вы получаете ошибку: Error in NextMethod() : generic function not specified.

Не работает:

t1 <- data.frame(a = 1:12, b = month.abb)
t2 <- new_df2(t1)
rbind(t2, t2)

Я также пытался использовать NextMethod(generic = "rbind"), но в этом случае вы получите эту ошибку: Error in NextMethod(generic = "rbind") : wrong value for .Method.

Также не работает:

rbind.df2 <- function(..., deparse.level = 1)
{
  NextMethod(generic = "rbind")
}

rbind(t2, t2)

Я нахожусь в тупике и догадываюсь о границах моего понимания подклассов / методов тоже. Спасибо за любую помощь.

2 4

2 ответа:

Я рассмотрю конкретный случай rbind() ниже, но сначала отмечу, что мы могли бы генерировать дополнительные примеры, показывающие, что в общем случае нет проблем с NextMethod(), Когда первый аргумент ... (относительно запроса на вознаграждение):

f <- function(..., b = 3) UseMethod("f")
f.a <- function(..., b = 3) { print("yes"); NextMethod() }
f.integer <- function(..., b = 4) sapply(list(...), "*", b)
x <- 1:10
class(x) <- c("a", class(x))
f(x)

[1] "yes"
      [,1]
 [1,]    4
 [2,]    8
 [3,]   12
 [4,]   16
 [5,]   20
 [6,]   24
 [7,]   28
 [8,]   32
 [9,]   36
[10,]   40

f(x, b = 5)

[1] "yes"
      [,1]
 [1,]    5
 [2,]   10
 [3,]   15
 [4,]   20
 [5,]   25
 [6,]   30
 [7,]   35
 [8,]   40
 [9,]   45
[10,]   50

Так почему же не rbind?работа df2?

Как оказалось, rbind() и cbind() не являются нормальными дженериками. Во-первых, они внутренне универсальны; смотрите раздел "внутренние универсалии" здесь из старого S3 Хэдли Уикхема страница на Advanced R, или эта выдержка из текущей Advanced R :

Некоторые генераторы S3, такие как [, sum () и cbind (), не называют UseMethod() поскольку они реализованы в C. Вместо этого они вызывают функции C. DispatchGroup() или DispatchOrEval ().

Этого недостаточно, чтобы причинить нам неприятности, как мы можем видеть, используя sum() в качестве примера:

sum.a <- function(x, na.rm = FALSE) { print("yes"); NextMethod() } 
sum(x)

[1] "yes"
[1] 55

Однако для rbind и cbind это еще более странно, как признается в комментариях в исходный код (начиная со строки 1025):

/* cbind(deparse.level, ...) and rbind(deparse.level, ...) : */
/* This is a special .Internal */

... (Код опущен) ...

    /* Lazy evaluation and method dispatch based on argument types are
     * fundamentally incompatible notions.  The results here are
     * ghastly.
После этого дается некоторое объяснение правил отправки, но до сих пор я не смог использовать эту информацию, чтобы заставить NextMethod() работать. В случае использования, приведенном выше, я бы последовал совету F. Privé из комментариев и сделал это:
new_df2 <- function(x, ...)
{
    stopifnot(is.data.frame(x))
    structure(x, class = c("df2", "data.frame"))
}

rbind.df2 <- function(..., deparse.level = 1)
{
    print("yes") # Or whatever else you want/need to do
    base::rbind.data.frame(..., deparse.level = deparse.level)
}

t1 <- data.frame(a = 1:12, b = month.abb)
t2 <- new_df2(t1)
rbind(t2, t2)

[1] "yes"
    a   b
1   1 Jan
2   2 Feb
3   3 Mar
4   4 Apr
5   5 May
6   6 Jun
7   7 Jul
8   8 Aug
9   9 Sep
10 10 Oct
11 11 Nov
12 12 Dec
13  1 Jan
14  2 Feb
15  3 Mar
16  4 Apr
17  5 May
18  6 Jun
19  7 Jul
20  8 Aug
21  9 Sep
22 10 Oct
23 11 Nov
24 12 Dec

Ответ состоит в том, чтобы расширить rbind2, а не rbind. На странице справки из rbind2:

" это (S4) универсальные функции с методами по умолчанию.

...

Основное использование cbind2 (rbind2) заключается в рекурсивном вызове cbind () (rbind ()), когда выполняются оба этих требования:

  • Существует по крайней мере один аргумент, который является объектом S4, и

  • Сбой диспетчеризации S3 (см. раздел диспетчеризации в разделе cbind)."