Seleccionar/asignar a data.table cuando los nombres de las variables se almacenan en un vector de caracteres
¿Cómo se hace referencia a las variables en a data.table
si los nombres de las variables se almacenan en un vector de caracteres? Por ejemplo, esto funciona para data.frame
:
df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
# col1
# 1 4
# 2 5
# 3 6
¿Cómo puedo realizar esta misma operación para una tabla de datos, ya sea con o sin :=
notación? Lo obvio de dt[ , list(colname)]
no funciona (ni esperaba que lo hiciera).
Dos formas de seleccionar variables mediante programación:
with = FALSE
:DT = data.table(col1 = 1:3) colname = "col1" DT[, colname, with = FALSE] # col1 # 1: 1 # 2: 2 # 3: 3
..
Prefijo 'punto punto' ( ):DT[, ..colname] # col1 # 1: 1 # 2: 2 # 3: 3
Para obtener una descripción más detallada de la notación 'punto punto' ( ..
), consulte Nuevas funciones en 1.10.2 (actualmente no se describe en el texto de ayuda).
Para asignar a variable(s), ajuste el LHS de :=
entre paréntesis:
DT[, (colname) := 4:6]
# col1
# 1: 4
# 2: 5
# 3: 6
Este último se conoce como columna plonk , porque reemplaza todo el vector de columna por referencia. Si i
estuviera presente un subconjunto, se subasignaría por referencia. Los pares (colname)
son una abreviatura introducida en la versión v1.9.4 en CRAN en octubre de 2014. Aquí está la noticia :
El uso
with = FALSE
de with:=
ahora está obsoleto en todos los casos, dado que:=
durante algún tiempo se ha preferido envolver el LHS de con paréntesis.
colVar = "col1"
DT[, (colVar) := 1] # please change to this
DT[, c("col1", "col2") := 1] # no change
DT[, 2:4 := 1] # no change
DT[, c("col1","col2") := list(sum(a), mean(b))] # no change
DT[, `:=`(...), by = ...] # no change
Consulte también la sección Detalles en ?`:=`
:
DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol
Y para responder más preguntas en el comentario, aquí hay una forma (como siempre, hay muchas formas):
DT[, colname := cumsum(get(colname)), with = FALSE]
# col1
# 1: 4
# 2: 9
# 3: 15
O puede que le resulte más fácil leer, escribir y depurar solo en eval
un archivo paste
, similar a construir una declaración SQL dinámica para enviar a un servidor:
expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"
eval(parse(text=expr))
# col1
# 1: 4
# 2: 13
# 3: 28
Si haces eso con frecuencia, puedes definir una función auxiliar EVAL
:
EVAL = function(...)eval(parse(text=paste0(...)),envir=parent.frame(2))
EVAL("DT[,",colname,":=cumsum(",colname,")]")
# col1
# 1: 4
# 2: 17
# 3: 45
Ahora que data.table
1.8.2 optimiza automáticamente j
la eficiencia, puede ser preferible utilizar este eval
método. El get()
in j
impide algunas optimizaciones, por ejemplo.
O lo hay set()
. Una forma funcional y de bajo costo :=
, que estaría bien aquí. Ver ?set
.
set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
# col1
# 1: 4
# 2: 21
# 3: 66
*En realidad, esta no es una respuesta, pero no tengo suficiente credibilidad para publicar comentarios :/
De todos modos, para cualquiera que esté buscando crear una nueva columna en una tabla de datos con un nombre almacenado en una variable, tengo lo siguiente para funcionar. No tengo ni idea de su rendimiento. ¿Alguna sugerencia de mejora? ¿Es seguro asumir que una nueva columna sin nombre siempre recibirá el nombre V1?
colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))
Observe que puedo hacer referencia a él muy bien en sum() pero parece que no puedo lograr que se asigne en el mismo paso. Por cierto, la razón por la que necesito hacer esto es que colname se basará en la entrada del usuario en una aplicación Shiny.
Recupere varias columnas de data.table mediante una variable o función:
library(data.table)
x <- data.table(this=1:2,that=1:2,whatever=1:2)
# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]
# === indirect via variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)
x[, ..mycols]
x[, .SD, .SDcols=mycols]
# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]
que todos rinden
that whatever
1: 1 1
2: 2 2
El camino me parece .SDcols
el más elegante.
Con la versión de desarrollo 1.14.3,tabla de datosha obtenido una nueva interfaz para programación en data.table , consulte el elemento 10 en Nuevas funciones . Utiliza el nuevo env =
parámetro.
library(data.table) # development version 1.14.3 used
dt <- data.table(col1 = 1:3)
colname <- "col1"
dt[, cn := cn + 3L, env = list(cn = colname)][]
col1 <int> 1: 4 2: 5 3: 6
Para varias columnas y una función aplicada a los valores de las columnas.
Al actualizar los valores de una función, el RHS debe ser un objeto de lista, por lo que usar un bucle .SD
con lapply
será suficiente.
El siguiente ejemplo convierte columnas enteras en columnas numéricas
a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class) # show classes of columns
# a b c1
# "integer" "integer" "character"
# column name character vector
nm <- c("a", "b")
# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]
sapply(a1, class)
# a b c1
# "numeric" "numeric" "character"