Uso de lapply.SD en data.table R
(1)
No tengo muy claro el uso de
.SD
y
by
.
Por ejemplo, ¿significa el fragmento a continuación: ''cambiar todas las columnas en
DT
a factor excepto
A
y
B
?''
También dice en el manual
data.table
: "
.SD
refiere al Subconjunto de
data.table
para cada grupo (excluyendo las columnas de agrupación)" - ¿entonces las columnas
A
y
B
están excluidas?
DT = DT[ ,lapply(.SD, as.factor), by=.(A,B)]
Sin embargo, también leí eso
by
medios como ''agrupar por'' en SQL cuando haces la agregación.
Por ejemplo, si quisiera sumar (como
colsum
en SQL) sobre todas las columnas excepto
A
y
B
¿sigo usando algo similar?
O, en este caso, ¿significa el siguiente código tomar la suma y agrupar por valores en las columnas
A
y
B
?
(tome la suma y agrupe por
A,B
como en SQL)
DT[,lapply(.SD,sum),by=.(A,B)]
Entonces, ¿cómo hago un simple
colsum
sobre todas las columnas excepto
A
y
B
?
Solo para ilustrar los comentarios anteriores con un ejemplo, tomemos
set.seed(10238)
# A and B are the "id" variables within which the
# "data" variables C and D vary meaningfully
DT = data.table(A = rep(1:3, each = 5), B = rep(1:5, 3),
C = sample(15), D = sample(15))
DT
# A B C D
# 1: 1 1 14 11
# 2: 1 2 3 8
# 3: 1 3 15 1
# 4: 1 4 1 14
# 5: 1 5 5 9
# 6: 2 1 7 13
# 7: 2 2 2 12
# 8: 2 3 8 6
# 9: 2 4 9 15
# 10: 2 5 4 3
# 11: 3 1 6 5
# 12: 3 2 12 10
# 13: 3 3 10 4
# 14: 3 4 13 7
# 15: 3 5 11 2
Compare lo siguiente:
#Sum all columns
DT[ , lapply(.SD, sum)]
# A B C D
# 1: 30 45 120 120
#Sum all columns EXCEPT A, grouping BY A
DT[ , lapply(.SD, sum), by = A]
# A B C D
# 1: 1 15 38 43
# 2: 2 15 30 49
# 3: 3 15 52 28
#Sum all columns EXCEPT A
DT[ , lapply(.SD, sum), .SDcols = !"A"]
# B C D
# 1: 45 120 120
#Sum all columns EXCEPT A, grouping BY B
DT[ , lapply(.SD, sum), by = B, .SDcols = !"A"]
# B C D
# 1: 1 27 29
# 2: 2 17 30
# 3: 3 33 11
# 4: 4 23 36
# 5: 5 20 14
Algunas notas
-
Dijiste "hace el fragmento de abajo ... cambia todas las columnas en
DT
..."
La respuesta es
no
, y esto es muy importante para
data.table
.
El objeto devuelto es un
nuevo
data.table
, y todas las columnas en
DT
son exactamente como estaban antes de ejecutar el código.
- Mencionaste querer cambiar los tipos de columna
Refiriéndose nuevamente al punto anterior, tenga en cuenta que su código (
DT[ , lapply(.SD, as.factor)]
) devuelve un
nuevo
data.table
y no cambia
DT
en absoluto.
Una forma (
incorrecta
) de hacer esto, que se realiza con
data.frame
s en la
base
, es sobrescribir la antigua
data.table
con la nueva
data.table
que ha devuelto, es decir,
DT = DT[ , lapply(.SD, as.factor)]
.
Esto es un desperdicio porque implica crear copias de
DT
que pueden ser un asesino de eficiencia cuando
DT
es grande.
El enfoque correcto de
data.table
para este problema es actualizar las columnas por referencia usando
`:=`
, por ejemplo,
DT[ , names(DT) := lapply(.SD, as.factor)]
, que no crea copias de su datos.
Consulte
la viñeta semántica de referencia de
data.table
para obtener más información al respecto.
-
Usted mencionó comparar la eficiencia de
lapply(.SD, sum)
con la decolSums
.sum
está optimizado internamente endata.table
(puede observar que esto es cierto a partir de la salida de agregar el argumentoverbose = TRUE
dentro de[]
); para ver esto en acción,DT
un poco suDT
y ejecutemos un punto de referencia:
Resultados:
library(data.table)
set.seed(12039)
nn = 1e7; kk = seq(100L)
DT = as.data.table(replicate(26L, sample(kk, nn, TRUE)))
DT[ , LETTERS[1:2] := .(sample(100L, nn, TRUE), sample(100L, nn, TRUE))]
library(microbenchmark)
microbenchmark(times = 100L,
colsums = colSums(DT[ , !c("A", "B"), with = FALSE]),
lapplys = DT[ , lapply(.SD, sum), .SDcols = !c("A", "B")])
# Unit: milliseconds
# expr min lq mean median uq max neval
# colsums 1624.2622 2020.9064 2028.9546 2034.3191 2049.9902 2140.8962 100
# lapplys 246.5824 250.3753 252.9603 252.1586 254.8297 266.1771 100