vacio - Cómo usar data.table como super clase en S4
visual basic crear un datatable (1)
Creo que la respuesta corta (el problema sigue siendo tan válido como lo fue cuando se planteó) es que usar data.table como una súper clase en S4 no es recomendable y no es posible sin un esfuerzo considerable y ciertos riesgos de inestabilidad.
Tampoco está del todo claro cuál debería haber sido el objetivo con el caso que nos ocupa, pero supongamos que no había otra alternativa como forzar y modificar el paquete data.table
existente.
Luego, para ilustrar el caso mencionado anteriormente con [
, primero inicialicemos el ejemplo:
# 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)
Ahora, para comprobar [.data.table
, que es un montón de código como se puede ver en el repositorio de datos de data.table.R , reproduciendo la parte relevante de la manera más simple:
# 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)))
Y ahí tenemos el error, tratando de establecer la row.names
. row.names
, que no funciona porque dim(ans)
y por nrow
tanto nrow
es NULL
.
Entonces, el verdadero problema está aquí con el uso de setattr(ans, "class", class(dat))
, que no funciona bien (intente con isS4(ans)
o print(ans)
justo después). De hecho, de la ?class
podemos leer sobre S4 :
La versión de reemplazo de la función establece la clase en el valor proporcionado. Para las clases que tienen una definición formal, la sustitución directa de la clase de esta manera está en desuso . La expresión como (objeto, valor) es la forma de obligar a un objeto a una clase en particular.
data.table''s
setattr
, que a través de C
usa R''s
función setAttrib
R''s
, es similar a llamar a attr(ans, "class") <- "Data.Table"
o class(ans) <- "Data.Table"
, que se class(ans) <- "Data.Table"
como bien.
Si lo hace setattr(ans, "class", class(dat2))
, verá que todo está bien aquí, como debería ser con S3
. Sin embargo, una advertencia más:
setattr(ans, "class", "data.frame")
y luego print(ans)
o dim(ans)
puede que no se vea muy bien para usted ... (aunque ans$x
está bien).
setattr()
de una buena manera tampoco es trivial y tal enfoque probablemente no lo llevará más lejos que el enfoque que ha descrito anteriormente. El resultado podría ser algo como:
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")
En la tabla de datos de R-Package, la entrada manual para ?data.table-class
dice que ''data.table'' se puede usar para la herencia en una definición de clase, es decir, en el argumento contiene en una llamada a setClass
:
library("data.table")
setClass("Data.Table", contains = "data.table")
Sin embargo, si creo una instancia de una tabla de datos, habría esperado que pudiera tratarla como una tabla de datos. Esto no es así. El siguiente fragmento de código generará un error, que, según tengo entendido, se debe a que la función [.data.table
no puede manejar la combinación de despacho S3 y S4:
dat <- new("Data.Table", data.table(x = 1))
dat[TRUE]
Resolví esto, definiendo un nuevo método para [
y coaccionando cualquier Data.Table a una data.table antes de evaluarla.
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())
)
})
Y una función constructora para sentirse más cómodo con ella:
Data.Table <- function(...) new("Data.Table", data.table(...))
dat <- Data.Table(x = 1, key = "x")
dat[1]
Esto es aceptable para algunos escenarios, pero pierdo todas las funciones de obtención y configuración del paquete data.table y sospecho que destruí algunas otras características. Entonces, la pregunta es ¿cómo implementar una clase de datos S4 en funcionamiento? Agradecería
- Punteros a intentos / proyectos similares
- Mejores / soluciones alternativas / ideas para una implementación.
- Cualquier consejo sobre lo que pierdo con respecto al rendimiento con la solución anterior
He encontrado una pregunta relacionada con SO , que presenta un enfoque similar. Sin embargo, creo que implicaría demasiada codificación para ser factible.