Me gustan estas cosas de data.table, tanto por su velocidad de ejecución como por su forma parsimoniosa de secuencias de comandos.
Lo uso incluso en mesas pequeñas también.
Regularmente hago subconjuntos de tablas de esta manera: DT[, .(id1, id5)]
y no así: DT[, c("id1", "id5")]
Hoy medí la velocidad de los dos y me ha sorprendido la diferencia de velocidad en mesas pequeñas. El método parsimonioso es mucho más lento.
¿Esta diferencia es algo intencionado?
¿Hay aspiración de hacer que la vía parsimoniosa converja en velocidad de ejecución a la otra?
(Cuenta cuando tengo que crear subconjuntos de varias tablas pequeñas de forma repetitiva).
Ubuntu 18.04
R versión 3.5.3 (2019-03-11)
data.table 1.12.0
RAM 32GB
CPU Intel® Core™ i7-8565U a 1,80 GHz × 8
library(data.table)
library(microbenchmark)
N <- 2e8
K <- 100
set.seed(1)
DT <- data.table(
id1 = sample(sprintf("id%03d", 1:K), N, TRUE), # large groups (char)
id2 = sample(sprintf("id%03d", 1:K), N, TRUE), # large groups (char)
id3 = sample(sprintf("id%010d", 1:(N/K)), N, TRUE), # small groups (char)
id4 = sample(K, N, TRUE), # large groups (int)
id5 = sample(K, N, TRUE), # large groups (int)
id6 = sample(N/K, N, TRUE), # small groups (int)
v1 = sample(5, N, TRUE), # int in range [1,5]
v2 = sample(5, N, TRUE), # int in range [1,5]
v3 = sample(round(runif(100, max = 100), 4), N, TRUE) # numeric e.g. 23.5749
)
microbenchmark(
DT[, .(id1, id5)],
DT[, c("id1", "id5")]
)
Unit: seconds
expr min lq mean median uq max neval
DT[, .(id1, id5)] 1.588367 1.614645 1.929348 1.626847 1.659698 12.33872 100
DT[, c("id1", "id5")] 1.592154 1.613800 1.937548 1.628082 2.184456 11.74581 100
N <- 2e5
DT2 <- data.table(
id1 = sample(sprintf("id%03d", 1:K), N, TRUE), # large groups (char)
id2 = sample(sprintf("id%03d", 1:K), N, TRUE), # large groups (char)
id3 = sample(sprintf("id%010d", 1:(N/K)), N, TRUE), # small groups (char)
id4 = sample(K, N, TRUE), # large groups (int)
id5 = sample(K, N, TRUE), # large groups (int)
id6 = sample(N/K, N, TRUE), # small groups (int)
v1 = sample(5, N, TRUE), # int in range [1,5]
v2 = sample(5, N, TRUE), # int in range [1,5]
v3 = sample(round(runif(100, max = 100), 4), N, TRUE) # numeric e.g. 23.5749
)
microbenchmark(
DT2[, .(id1, id5)],
DT2[, c("id1", "id5")]
)
Unit: microseconds
expr min lq mean median uq max neval
DT2[, .(id1, id5)] 1405.042 1461.561 1525.5314 1491.7885 1527.8955 2220.860 100
DT2[, c("id1", "id5")] 614.624 640.617 666.2426 659.0175 676.9355 906.966 100
Puedes arreglar el formato de tu publicación usando una sola línea de tres acentos graves antes y después del fragmento de código:
```
code
```
Cuenta cuando tengo que crear subconjuntos de varias tablas pequeñas de forma repetitiva.
Supongo que seleccionar repetidamente columnas de tablas pequeñas es algo que debería y, en la mayoría de los casos, puede evitarse... Debido a que j
en DT[i, j, by]
admite y optimiza una variedad tan amplia de entradas, creo que es natural que haya algunos gastos generales al analizarlo.
Con respecto a otras formas de abordar su problema (y tal vez esto encajaría mejor con Stack Overflow si desea hablar más al respecto) ... Dependiendo de qué más quiera hacer con la tabla, podría eliminar las otras columnas , DT[, setdiff(names(DT), cols) := NULL]
y continúe usando DT directamente.
Si aún prefiere tomar el subconjunto, capturar punteros de columna es mucho más rápido que cualquiera de las opciones que consideró aquí, aunque de esta manera las ediciones del resultado afectarán la tabla original:
library(data.table)
library(microbenchmark)
N <- 2e8
K <- 100
set.seed(1)
DT <- data.table(
id1 = sprintf("id%03d", 1:K), # large groups (char)
id2 = sprintf("id%03d", 1:K), # large groups (char)
id3 = sprintf("id%010d", 1:(N/K)), # small groups (char)
id4 = sample(K), # large groups (int)
id5 = sample(K), # large groups (int)
id6 = sample(N/K), # small groups (int)
v1 = sample(5), # int in range [1,5]
v2 = sample(5), # int in range [1,5]
v3 = round(runif(100, max = 100), 4), # numeric e.g. 23.5749
row = seq_len(N)
)
cols = c("id1", "id5")
microbenchmark(times = 3,
expression = DT[, .(id1, id5)],
index = DT[, c("id1", "id5")],
dotdot = DT[, ..cols],
oddball = setDT(lapply(setNames(cols, cols), function(x) DT[[x]]))[],
oddball2 = setDT(unclass(DT)[cols])[]
)
Unit: microseconds
expr min lq mean median uq max neval
expression 1249753.580 1304355.3415 1417166.9297 1358957.103 1500873.6045 1642790.106 3
index 1184056.302 1191334.4835 1396372.3483 1198612.665 1502530.3715 1806448.078 3
dotdot 1084521.234 1240062.2370 1439680.6980 1395603.240 1617260.4300 1838917.620 3
oddball 92.659 171.8635 568.5317 251.068 806.4680 1361.868 3
oddball2 66.582 125.9505 150.7337 185.319 192.8095 200.300 3
(Eliminé la aleatorización de su ejemplo y reduje # veces en el punto de referencia porque estaba impaciente).
Nunca encontré una manera de llamar directamente al subconjunto de la lista de R (que se usa después de los unclass
anteriores).
Con respecto a "las ediciones del resultado modificarán la tabla original", quiero decir:
myDT = data.table(a = 1:2, b = 3:4)
# standard way
res <- myDT[, "a"]
res[, a := 0]
myDT
# a b
# 1: 1 3
# 2: 2 4
# oddball, grabbing pointers
res2 <- setDT(unclass(myDT)["a"])
res2[, a := 0]
myDT
# a b
# 1: 0 3
# 2: 0 4
Ok, he aprendido algo nuevo y rápido (los bichos raros) hoy y he estado tomando nota de que existe una compensación entre la velocidad y la codificación parsimoniosa. ¡Así que el vaso está medio lleno! ¡Gracias!
Supongo que # 852 relacionado
Comentario más útil
Puedes arreglar el formato de tu publicación usando una sola línea de tres acentos graves antes y después del fragmento de código:
Supongo que seleccionar repetidamente columnas de tablas pequeñas es algo que debería y, en la mayoría de los casos, puede evitarse... Debido a que
j
enDT[i, j, by]
admite y optimiza una variedad tan amplia de entradas, creo que es natural que haya algunos gastos generales al analizarlo.Con respecto a otras formas de abordar su problema (y tal vez esto encajaría mejor con Stack Overflow si desea hablar más al respecto) ... Dependiendo de qué más quiera hacer con la tabla, podría eliminar las otras columnas ,
DT[, setdiff(names(DT), cols) := NULL]
y continúe usando DT directamente.Si aún prefiere tomar el subconjunto, capturar punteros de columna es mucho más rápido que cualquiera de las opciones que consideró aquí, aunque de esta manera las ediciones del resultado afectarán la tabla original:
(Eliminé la aleatorización de su ejemplo y reduje # veces en el punto de referencia porque estaba impaciente).
Nunca encontré una manera de llamar directamente al subconjunto de la lista de R (que se usa después de los
unclass
anteriores).Con respecto a "las ediciones del resultado modificarán la tabla original", quiero decir: