Подтвердить что ты не робот

Как использовать data.table в качестве суперкласса в S4

В таблице данных R-пакета в ручном вводе для ?data.table-class указано, что "data.table" может использоваться для наследования в определении класса, то есть в аргументе contains в вызове setClass:

library("data.table")
setClass("Data.Table", contains = "data.table")

Однако, если я создам экземпляр Data.Table, я бы ожидал, что я могу рассматривать его как таблицу данных. Это не так. Следующий фрагмент приведет к ошибке, которая, насколько я понимаю, связана с тем, что функция [.data.table не может обрабатывать соединение отправки S3 и S4:

dat <- new("Data.Table", data.table(x = 1))
dat[TRUE]

Я решил это, определив новый метод для [ и принудительно применив любой Data.Table к таблице данных перед его оценкой.

setMethod(
  "[", 
  "Data.Table", 
  function(x, i, j, ..., drop = TRUE) {
    mc <- match.call()
    mc$x <- substitute(S3Part(x, strictS3 = TRUE))
    Data.Table(
      eval(mc, envir = parent.frame())
    )
  })

И конструктор, чтобы чувствовать себя более комфортно с ним:

Data.Table <- function(...) new("Data.Table", data.table(...))
dat <- Data.Table(x = 1, key = "x")
dat[1]

Это приемлемо для некоторых сценариев, но я теряю все функции get и set из пакета data.table, и я подозреваю, что я уничтожил некоторые другие функции. Итак, вопрос в том, как реализовать рабочий класс S4 data.table? Я был бы признателен

  • Указатели на аналогичные попытки/проекты
  • Лучшие/альтернативные решения/идеи для реализации
  • Любые советы по поводу того, что я теряю в отношении производительности с вышеупомянутым решением.

Существует один связанный вопрос на SO. Я нашел, что представляет собой аналогичный подход. Однако, я думаю, что это потребует слишком много кодирования, чтобы быть выполнимым.

4b9b3361

Ответ 1

Я думаю, что короткий ответ (проблема по-прежнему столь же актуальна, как и при поднятии) заключается в том, что использование data.table в качестве суперкласса в S4 не рекомендуется и невозможно без значительных усилий и определенных рисков нестабильности.

Также не совсем понятно, какова была цель в данном случае, но предположим, что альтернативы не было, как форкировка и изменение существующего пакета data.table.

Затем, чтобы проиллюстрировать упомянутый выше случай с [, сначала инициализируйте пример:

# replicating some code from above
library("data.table")
Data.Table <- setClass("Data.Table", contains = "data.table")

dat <- Data.Table(data.table(x = 1))
dat[1]
> Error in if (n > 0) c(NA_integer_, -n) else integer() : 
    argument is of length zero

dat2 <- data.table(x = 1)

Теперь, чтобы проверить [.data.table, который представляет собой много кода, как вы можете видеть в репозитории Github data.table.R, так что просто воспроизводя соответствующую часть простейшим манекеном:

# initializing output
ans = vector("list", 1)
# data (just one line of code as we have just one value in our example).
# desired subscript is row 1, but we have just one column as well.
ans[[1]] <- dat[[1]][1]
# add 'names' attribute
setattr(ans, "names", "x")
# set 'class' attribute
setattr(ans, "class", class(dat))
# set 'row.names'
setattr(ans, "row.names", .set_row_names(nrow(ans)))

И там мы имеем ошибку, пытаясь установить row.names, который не работает, потому что dim(ans) и поэтому nrow есть NULL.

Таким образом, настоящая проблема заключается в использовании setattr(ans, "class", class(dat)), который не работает (попробуйте isS4(ans) или print(ans) сразу после этого). Фактически, из ?class мы можем прочитать о S4:

Заменяемая версия функции устанавливает класс в предоставленное значение. Для классов, которые имеют формальное определение, непосредственная замена класса таким образом сильно устарела. Выражение as (object, value) - это способ принуждения объекта к определенному классу.

data.table's setattr, который через C использует функцию R's setAttrib, похож на вызов attr(ans, "class") <- "Data.Table" или class(ans) <- "Data.Table", который тоже испортится.

Если вы сделаете setattr(ans, "class", class(dat2)), вы увидите, что здесь все хорошо, как и с S3. Еще одно слово осторожности:

setattr(ans, "class", "data.frame")

а затем print(ans) или dim(ans) может показаться вам не очень приятным... (хотя ans$x в порядке).


Переопределение setattr() в хорошем смысле также не является тривиальным, и такой подход, вероятно, не приведет вас дальше, чем подход, описанный выше. Результат может быть примерно таким:

setattr_new <- function(x, name, value) {
  if (name == "class" && "Data.Table" %in% value) {
    value <- c("data.table", "data.frame")
  }
  if (name == "names" && is.data.table(x) && length(attr(x, "names")) && !is.null(value))
    setnames(x, value)
  else {
    ans = .Call(Csetattrib, x, name, value)
    if (!is.null(ans)) {
      warning("Input is a length=1 logical that points to the same address as R global TRUE value. Therefore the attribute has not been set by reference, rather on a copy. You will need to assign the result back to a variable. See https://github.com/Rdatatable/data.table/issues/1281 for more.")
      x = ans
    }
  }
  if (name == "levels" && is.factor(x) && anyDuplicated(value)) 
    .Call(Csetlevels, x, (value <- as.character(value)), unique(value))
  invisible(x)
}

godmode:::assignAnywhere("setattr", setattr_new)

identical(dat[1], dat2[1])
[1] TRUE

# then possibly convert back to S4 class if desired for further processing at the end
as(dat[1], "Data.Table")