Forma correcta / más rápida para remodelar un data.table

Tengo una tabla de datos en R:

library(data.table) set.seed(1234) DT <- data.table(x=rep(c(1,2,3),each=4), y=c("A","B"), v=sample(1:100,12)) DT xyv [1,] 1 A 12 [2,] 1 B 62 [3,] 1 A 60 [4,] 1 B 61 [5,] 2 A 83 [6,] 2 B 97 [7,] 2 A 1 [8,] 2 B 22 [9,] 3 A 99 [10,] 3 B 47 [11,] 3 A 63 [12,] 3 B 49 

Puedo sumr fácilmente la variable v por los grupos en data.table:

 out <- DT[,list(SUM=sum(v)),by=list(x,y)] out xy SUM [1,] 1 A 72 [2,] 1 B 123 [3,] 2 A 84 [4,] 2 B 119 [5,] 3 A 162 [6,] 3 B 96 

Sin embargo, me gustaría tener los grupos (y) como columnas, en lugar de filas. Puedo lograr esto usando la reshape :

 out <- reshape(out,direction='wide',idvar='x', timevar='y') out x SUM.A SUM.B [1,] 1 72 123 [2,] 2 84 119 [3,] 3 162 96 

¿Hay una manera más eficiente de remodelar los datos después de agregarlos? ¿Hay alguna manera de combinar estas operaciones en un solo paso, utilizando las operaciones data.table?

El paquete data.table implementa funciones de melt/dcast más melt/dcast (en C). También tiene características adicionales al permitir derretir y moldear columnas múltiples . Por favor, vea la nueva remodelación eficiente usando data.tables en Github.

Las funciones de fusión / difusión para data.table han estado disponibles desde v1.9.0 y las características incluyen:

  • No es necesario cargar el paquete reshape2 antes de la conversión. Pero si desea cargarlo para otras operaciones, cárguelo antes de cargar data.table .

  • dcast es también un S3 genérico. No más de dcast.data.table() . Solo usa dcast() .

  • melt :

    • es capaz de fundirse en columnas de tipo ‘lista’.

    • gana variable.factor y value.factor que por defecto son TRUE y FALSE respectivamente para la compatibilidad con reshape2 . Esto permite controlar directamente el tipo de salida de variable columnas de variable y value (como factores o no).

    • melt.data.table na.rm = TRUE está optimizado internamente para eliminar NA directamente durante la fusión y, por lo tanto, es mucho más eficiente.

    • NUEVO: melt puede aceptar una lista para measure.vars y las columnas especificadas en cada elemento de la lista se combinarán juntas. Esto se facilita aún más mediante el uso de patterns() . Ver viñeta o ?melt .

  • dcast :

    • acepta múltiples fun.aggregate y multiple value.var . Ver viñeta o ?dcast .

    • use la función rowid() directamente en la fórmula para generar una columna id, que a veces se requiere para identificar las filas de manera única. Ver? Dcast.

  • Puntos de referencia antiguos:

    • melt : 10 millones de filas y 5 columnas, 61.3 segundos reducido a 1.2 segundos.
    • dcast : 1 millón de filas y 4 columnas, 192 segundos reducidos a 3.6 segundos.

Recordatorio de Colonia (diciembre de 2013) presentación diapositiva 32: ¿Por qué no enviar una dcast extracción reshape2 para reshape2 ?

Esta característica ahora se implementa en data.table (a partir de la versión 1.8.11), como se puede ver en la respuesta anterior de Zach.

Acabo de ver este gran fragmento de código de Arun aquí en SO . Así que supongo que hay una solución de datos. Aplicado a este problema:

 library(data.table) set.seed(1234) DT <- data.table(x=rep(c(1,2,3),each=1e6), y=c("A","B"), v=sample(1:100,12)) out <- DT[,list(SUM=sum(v)),by=list(x,y)] # edit (mnel) to avoid setNames which creates a copy # when calling `names<-` inside the function out[, as.list(setattr(SUM, 'names', y)), by=list(x)] }) x AB 1: 1 26499966 28166677 2: 2 26499978 28166673 3: 3 26500056 28166650 

Esto da los mismos resultados que el enfoque de DWin:

 tapply(DT$v,list(DT$x, DT$y), FUN=sum) AB 1 26499966 28166677 2 26499978 28166673 3 26500056 28166650 

Además, es rápido:

 system.time({ out <- DT[,list(SUM=sum(v)),by=list(x,y)] out[, as.list(setattr(SUM, 'names', y)), by=list(x)]}) ## user system elapsed ## 0.64 0.05 0.70 system.time(tapply(DT$v,list(DT$x, DT$y), FUN=sum)) ## user system elapsed ## 7.23 0.16 7.39 

ACTUALIZAR

Para que esta solución también funcione para conjuntos de datos no balanceados (es decir, algunas combinaciones no existen), primero debe ingresarlas en la tabla de datos:

 library(data.table) set.seed(1234) DT <- data.table(x=c(rep(c(1,2,3),each=4),3,4), y=c("A","B"), v=sample(1:100,14)) out <- DT[,list(SUM=sum(v)),by=list(x,y)] setkey(out, x, y) intDT <- expand.grid(unique(out[,x]), unique(out[,y])) setnames(intDT, c("x", "y")) out <- out[intDT] out[, as.list(setattr(SUM, 'names', y)), by=list(x)] 

Resumen

Combinando los comentarios con lo anterior, esta es la solución de 1 línea:

 DT[, sum(v), keyby = list(x,y)][CJ(unique(x), unique(y)), allow.cartesian = T][, setNames(as.list(V1), paste(y)), by = x] 

También es fácil modificar esto para tener más que solo la sum, por ejemplo:

 DT[, list(sum(v), mean(v)), keyby = list(x,y)][CJ(unique(x), unique(y)), allow.cartesian = T][, setNames(as.list(c(V1, V2)), c(paste0(y,".sum"), paste0(y,".mean"))), by = x] # x A.sum B.sum A.mean B.mean #1: 1 72 123 36.00000 61.5 #2: 2 84 119 42.00000 59.5 #3: 3 187 96 62.33333 48.0 #4: 4 NA 81 NA 81.0 

Los objetos Data.table heredan de ‘data.frame’ para que pueda usar tapply:

 > tapply(DT$v,list(DT$x, DT$y), FUN=sum) AA BB a 72 123 b 84 119 c 162 96 

Puede usar dcast de la biblioteca reshape2 . Aquí está el código

 # DUMMY DATA library(data.table) mydf = data.table( x = rep(1:3, each = 4), y = rep(c('A', 'B'), times = 2), v = rpois(12, 30) ) # USE RESHAPE2 library(reshape2) dcast(mydf, x ~ y, fun = sum, value_var = "v") 

NOTA: la solución de tapply sería mucho más rápida.