Ответ 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")