Evaluar tanto el nombre de la columna como el valor objetivo dentro de la expresión `j` dentro de` data.table`
(1)
Aquí hay una posible alternativa.
target <- "vs"
value <- 1
dt <- as.data.table(head(mtcars))
En términos de código, no es necesariamente más simple, pero podemos configurar una llamada no evaluada
cl
definida fuera del alcance de
dt
que se evaluará dentro del entorno de la tabla de datos.
cl <- substitute(
x == y,
list(x = as.name(target), y = value)
)
Puede ser necesario
substitute()
para expresiones más largas.
Pero en este caso,
call()
acortaría el código y crearía el mismo resultado de
cl
.
Y entonces
cl
también podría ser
cl <- call("==", as.name(target), value)
Ahora podemos evaluar
cl
dentro de
dt
.
En su ejemplo, esto parece funcionar bien.
dt[, NEWCOL := sum(eval(cl)), by = am][]
# mpg cyl disp hp drat wt qsec vs am gear carb NEWCOL
# 1: 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 1
# 2: 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 1
# 3: 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 1
# 4: 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 2
# 5: 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 2
# 6: 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1 2
Después de pensar en esto por un minuto, no estoy seguro de que
value
necesario sustituir el valor y, por lo tanto, lo siguiente también funciona.
Pero como señala David, el primer enfoque es más eficiente en el tiempo.
dt[, eval(as.name(target)) == value]
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
Considerar
target <- "vs"
value <- 1
library(data.table)
dt <- as.data.table(head(mtcars))
Así que estoy tratando de pasar el nombre de la columna y un valor como variables en la expresión
j
dentro del entorno
data.table
, algo que sería equivalente a
dt[, vs == 1]
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
Si solo el valor es la variable, funciona bien
dt[, vs == value]
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
También podemos llamar a la columna dentro del alcance data.table cuando es una variable
dt[, target, with = FALSE]
# vs
# 1: 0
# 2: 0
# 3: 1
# 4: 1
# 5: 0
# 6: 1
Pero no puedo entender cómo combinar los dos de una manera simple
Nota: Soy consciente de que simplemente puedo hacer:
dt[[target]] == value
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
Pero lo necesito dentro del alcance de la tabla de datos para poder modificar otras columnas por referencia, algo así como
dt[, NEWCOL := sum(vs == 1), by = am]
Así que aquí están mis intentos cuando tanto el nombre de la columna como el valor son variables
dt[, target == value, with = FALSE]
# Null data.table (0 rows and 0 cols)
dt[, target == value]
# [1] FALSE
dt[, (target) == value]
# [1] FALSE
dt[, .(target == value)]
# V1
# 1: FALSE
dt[, eval(target) == value]
# [1] FALSE
dt[target %in% value]
## Empty data.table (0 rows) of 11 cols: mpg,cyl,disp,hp,drat,wt...
Finalmente se me ocurrió
dt[, .SD[[target]] == value]
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
pero es muy ineficiente, aquí hay un punto de referencia simple
set.seed(123)
n <- 1e6
dt <- data.table(vs = sample(1L:30L, n, replace = TRUE), am = seq_len(n))
system.time(dt[, NEWCOL := sum(.SD[[target]] == value), by = am])
# user system elapsed
# 13.00 0.02 13.12
system.time(dt[, NEWCOL2 := sum(vs == value), by = am])
# user system elapsed
# 0.82 0.00 0.83
Pregunta : ¿Hay alguna forma mejor de hacer esto que me estoy perdiendo aquí? Algo más idiomático o mucho más eficiente.
Editar
Originalmente estaba buscando algo idiomático, así que pensé que la solución simple de @GGrothendieck usando
get
era la correcta, pero sorprendentemente todas las versiones de @Richard están superando incluso la versión que
no
evalúa el nombre de la columna.
set.seed(123)
n <- 1e7
dt <- data.table(vs = sample(1L:30L, n, replace = TRUE), am = seq_len(n))
cl <- substitute(
x == y,
list(x = as.name(target), y = value)
)
cl2 <- call("==", as.name(target), value)
system.time(dt[, NEWCOL := sum(vs == value), by = am])
# user system elapsed
# 0.83 0.00 0.82
system.time(dt[, NEWCOL1 := sum(.SD[[target]] == value), by = am])
# user system elapsed
# 8.97 0.00 8.97
system.time(dt[, NEWCOL2 := sum(get(target) == value), by = am])
# user system elapsed
# 2.35 0.00 2.37
system.time(dt[, NEWCOL3 := sum(eval(cl)), by = am])
# user system elapsed
# 0.69 0.02 0.71
system.time(dt[, NEWCOL4 := sum(eval(cl2)), by = am])
# user system elapsed
# 0.76 0.00 0.77
system.time(dt[, NEWCOL5 := sum(eval(as.name(target)) == value), by = am])
# user system elapsed
# 0.78 0.00 0.78