如何在S4中使用data.table作为超类

Seb*_*ian 17 r s4 data.table

在R-Package data.table中,?data.table-class表示'data.table' 的手册条目可用于类定义中的继承,即在调用中的contains参数中setClass:

library("data.table")
setClass("Data.Table", contains = "data.table")
Run Code Online (Sandbox Code Playgroud)

但是,如果我创建一个Data.Table的实例,我本可以预期我可以将其视为data.table.事实并非如此.以下片段会导致错误,据我所知,这是因为该[.data.table函数无法处理S3和S4调度的混合:

dat <- new("Data.Table", data.table(x = 1))
dat[TRUE]
Run Code Online (Sandbox Code Playgroud)

我通过定义一个新的方法[并将任何Data.Table强制转换为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())
    )
  })
Run Code Online (Sandbox Code Playgroud)

一个构造函数让它感觉更舒服:

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

这对于某些场景是可以接受的,但是我放弃了data.table包中的所有get和set函数,我怀疑我销毁了其他一些功能.那么问题是如何实现一个有效的S4 data.table类?我将不胜感激

  1. 指向类似尝试/项目的指针
  2. 实施的更好/替代解决方案/想法
  3. 有关上述解决方案的性能方面的任何建议

对SO一个相关的问题,我发现,它提出了一个类似的方法.但是,我认为这将涉及太多编码以使其可行.

Rol*_*ASc 2

我认为简短的答案(这个问题仍然像提出时一样有效)是,不推荐使用 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)
Run Code Online (Sandbox Code Playgroud)

现在要检查一下,正如您在 Github 存储库data.table.R[.data.table上看到的那样,有很多代码,因此只需以最简单的虚拟方式重现相关部分即可:

# 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)))
Run Code Online (Sandbox Code Playgroud)

我们遇到了错误,试图设置row.names,但它不起作用,因为dim(ans),因此nrowNULL

所以真正的问题在于 的使用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"or class(ans) <- "Data.Table",这也会搞砸。

如果您setattr(ans, "class", class(dat2))这样做,您会发现这里一切都很好,就像 一样S3。不过,还要注意一点:

setattr(ans, "class", "data.frame")
Run Code Online (Sandbox Code Playgroud)

然后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's 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")
Run Code Online (Sandbox Code Playgroud)