Pivote en data.table similar a la función de fusión de rehape
reshape (3)
Auto-promoción descarada
Es posible que desee probar melt_
desde mi paquete Kmisc
. melt_
es esencialmente una reescritura de reshape2:::melt.data.frame
con la mayor parte del trabajo reshape2:::melt.data.frame
realizado en C, y evita la mayor cantidad de copias y coerciones de tipo posible para una implementación rápida.
Un ejemplo:
## devtools::install_github("Kmisc", "kevinushey")
library(Kmisc)
library(reshape2)
library(microbenchmark)
n <- 1E6
big_df <- data.frame( stringsAsFactors=FALSE,
x=sample(letters, n, TRUE),
y=sample(LETTERS, n, TRUE),
za=rnorm(n),
zb=rnorm(n),
zc=rnorm(n)
)
all.equal(
melt <- melt(big_df, id.vars=c(''x'', ''y'')),
melt_ <- melt_(big_df, id.vars=c(''x'', ''y''))
)
## we don''t convert the ''variable'' column to factor by default
## if we do, we see they''re identical
melt_$variable <- factor(melt_$variable)
stopifnot( identical(melt, melt_) )
microbenchmark( times=5,
melt=melt(big_df, id.vars=c(''x'', ''y'')),
melt_=melt_(big_df, id.vars=c(''x'', ''y''))
)
me da
Unit: milliseconds
expr min lq median uq max neval
melt 916.40436 931.60031 999.03877 1102.31090 1160.3598 5
melt_ 61.59921 78.08768 90.90615 94.52041 182.0879 5
Con un poco de suerte, esto será lo suficientemente rápido para sus datos.
He leído algunas referencias a problemas similares aquí en SO, pero aún no he podido encontrar una solución y me pregunto si hay alguna forma de hacer lo siguiente utilizando solo data.table.
Usaré un ejemplo simplificado, pero en la práctica, mi tabla de datos tiene> 1000 columnas similares a var1, var2, ... var1000, etc.
dt <- data.table(uid=c("a","b"), var1=c(1,2), var2=c(100,200))
Estoy buscando una solución que me permita obtener una salida similar a la función de fusión de remodelación:
> melt(dt, id=c("uid"))
uid variable value
1 a var1 1
2 b var1 2
3 a var2 100
4 b var2 200
Es decir, todas las columnas, excepto el uid, se enumeran en una sola columna con los valores correspondientes en una columna contigua. He intentado esto con una combinación de lista, etc., pero podría faltar algo que sea obvio.
Todos los uids en dt son únicos.
Gracias por adelantado.
Para una remodelación de data.table, intente lo siguiente:
dt[, list(variable = names(.SD), value = unlist(.SD, use.names = F)), by = uid]
El costo de la sintaxis vale la pena; ¡La función se ejecuta muy rápidamente!
stack
general, la stack
supera al melt
.
Un acercamiento directo a este problema con la stack
sería:
dt[, stack(.SD), by = "uid"]
Por supuesto, puedes especificar tus .SDcols
si es necesario. Y luego, usa setnames()
para cambiar los nombres a lo que quieras.
(Alerta de autopromoción)
Escribí algunas funciones y las puse en un paquete llamado "splitstackshape". Una de las funciones se llama Stacked()
, y en la versión 1.2.0 del paquete "splitstackshape", debería funcionar muy rápido.
Es un poco diferente de solo apilar todas las columnas restantes en una data.table
. Es más análogo a la reshape()
de R de la base que a la melt()
de "reshape2". Aquí hay un ejemplo de Stacked()
en acción.
He creado una data.table
de data.table
decentemente grande para hacer esta prueba. Hay 50 columnas numéricas que queremos apilar, y 50 columnas de factores que queremos apilar. También he optimizado aún más la respuesta de @Andreas.
Los datos
set.seed(1)
m1 <- matrix(rnorm(10000*50), ncol = 50)
m2 <- matrix(sample(LETTERS, 10000*50, replace = TRUE), ncol = 50)
colnames(m1) <- paste("varA", sprintf("%02d", 1:50), sep = "_")
colnames(m2) <- paste("varB", sprintf("%02d", 1:50), sep = "_")
dt <- data.table(uid = 1:10000, m1, m2)
Las funciones para el benchmarking.
test1 <- function() Stacked(dt, "uid", c("varA", "varB"), "_")
## merged.stack
test2 <- function() merged.stack(dt, "uid", c("varA", "varB"), "_")
## unlist(..., use.names = TRUE) -- OPTIMIZED
test3 <- function() {
list(cbind(dt[, "uid", with = FALSE],
dt[, list(variable = rep(names(.SD), each = nrow(dt)),
value = unlist(.SD)),
.SDcols = 2:51]),
cbind(dt[, "uid", with = FALSE],
dt[, list(variable = rep(names(.SD), each = nrow(dt)),
value = unlist(.SD)),
.SDcols = 52:101]))
}
## unlist(..., use.names = FALSE) -- OPTIMIZED
test4 <- function() {
list(cbind(dt[, "uid", with = FALSE],
dt[, list(variable = rep(names(.SD), each = nrow(dt)),
value = unlist(.SD, use.names = FALSE)),
.SDcols = 2:51]),
cbind(dt[, "uid", with = FALSE],
dt[, list(variable = rep(names(.SD), each = nrow(dt)),
value = unlist(.SD, use.names = FALSE)),
.SDcols = 52:101]))
}
## Andreas''s current answer
test5 <- function() {
list(dt[, list(variable = names(.SD),
value = unlist(.SD, use.names = FALSE)),
by = uid, .SDcols = 2:51],
dt[, list(variable = names(.SD),
value = unlist(.SD, use.names = FALSE)),
by = uid, .SDcols = 52:101])
}
Los resultados
library(microbenchmark)
microbenchmark(Stacked = test1(), merged.stack = test2(),
unlist.namesT = test3(), unlist.namesF = test4(),
AndreasAns = test5(), times = 3)
# Unit: milliseconds
# expr min lq median uq max neval
# Stacked 391.3251 393.0976 394.8702 421.4185 447.9668 3
# merged.stack 764.3071 769.6935 775.0799 867.2638 959.4477 3
# unlist.namesT 1680.0610 1761.9701 1843.8791 1881.9722 1920.0653 3
# unlist.namesF 215.0827 242.7748 270.4669 270.6944 270.9218 3
# AndreasAns 16193.5084 16249.5797 16305.6510 16793.3832 17281.1154 3
^^ No estoy seguro de por qué la respuesta actual de Andreas es tan lenta aquí. La "optimización" que hice fue básicamente para unlist
sin usar by
, lo que marcó una gran diferencia en las columnas "varB" (factor).
El enfoque manual es aún más rápido que las funciones de "splitstackshape", pero estamos hablando de milisegundos, ¡y un código bastante compacto de una sola línea!
Salida de muestra
Para referencia, aquí es cómo se ve la salida de Stacked()
. Es una list
de data.table
de datos "apiladas", un elemento de lista para cada variable apilada.
test1()
# $varA
# uid .time_1 varA
# 1: 1 01 -0.6264538
# 2: 1 02 -0.8043316
# 3: 1 03 0.2353485
# 4: 1 04 0.6179223
# 5: 1 05 -0.2212571
# ---
# 499996: 10000 46 -0.6859073
# 499997: 10000 47 -0.9763478
# 499998: 10000 48 0.6579464
# 499999: 10000 49 0.7741840
# 500000: 10000 50 0.5195232
#
# $varB
# uid .time_1 varB
# 1: 1 01 D
# 2: 1 02 A
# 3: 1 03 S
# 4: 1 04 L
# 5: 1 05 T
# ---
# 499996: 10000 46 A
# 499997: 10000 47 W
# 499998: 10000 48 H
# 499999: 10000 49 U
# 500000: 10000 50 W
Y, aquí es cómo se merged.stack
salida de merged.stack
. Es similar a lo que obtendrías al usar reshape(..., direction = "long")
desde la base R.
test2()
# uid .time_1 varA varB
# 1: 1 01 -0.6264538 D
# 2: 1 02 -0.8043316 A
# 3: 1 03 0.2353485 S
# 4: 1 04 0.6179223 L
# 5: 1 05 -0.2212571 T
# ---
# 499996: 10000 46 -0.6859073 A
# 499997: 10000 47 -0.9763478 W
# 499998: 10000 48 0.6579464 H
# 499999: 10000 49 0.7741840 U
# 500000: 10000 50 0.5195232 W