在 data.table 上进行枢轴,类似于 rehape melt 函数
Posted
技术标签:
【中文标题】在 data.table 上进行枢轴,类似于 rehape melt 函数【英文标题】:Pivot on data.table similar to rehape melt function 【发布时间】:2013-08-29 06:51:32 【问题描述】:我已经阅读了一些关于 SO 上类似问题的参考资料,但还没有找到解决方案,并且想知道是否有任何方法可以仅使用 data.table 来执行以下操作。
我将使用一个简化的示例,但实际上,我的数据表有 > 1000 列,类似于 var1、var2、... var1000 等。
dt <- data.table(uid=c("a","b"), var1=c(1,2), var2=c(100,200))
我正在寻找一种解决方案,可以让我获得类似于 reshape 的 melt 功能的输出 --
> melt(dt, id=c("uid"))
uid variable value
1 a var1 1
2 b var1 2
3 a var2 100
4 b var2 200
也就是说,除了 uid 之外的所有列都列在单个列下,相应的值位于相邻列中。我已经尝试过结合使用列表等,但可能会遗漏一些明显的东西。
dt 中的所有 uid 都是唯一的。
提前致谢。
【问题讨论】:
我应该补充一点,由于在数 GB 大小的数据集上运行操作需要时间,所以不能选择使用 melt。 您是否尝试过将stack
作为melt
的替代品?或者unlist
(但我认为stack
更快)。
查看this question 的答案,了解一些可能的线索。
是的,这行得通...cbind(stack(dt, select=-uid), enrolid=dt$uid)
我按照dt[, stack(.SD), by = "uid"]
的思路思考,假设其他所有内容都包含.SDCols
。
【参考方案1】:
stack
通常优于 melt
。
stack
解决此问题的直接方法是:
dt[, stack(.SD), by = "uid"]
当然,如有必要,您可以指定您的.SDcols
。然后,使用setnames()
将名称更改为您想要的任何名称。
(自我宣传提醒)
我编写了一些函数并将它们放在一个名为“splitstackshape”的包中。其中一个函数称为Stacked()
,“splitstackshape”包的in the 1.2.0 version 应该可以很快工作。
这与将所有剩余的列堆叠在 data.table
中有点不同。它更类似于基础 R 的reshape()
,而不是来自“reshape2”的melt()
。以下是Stacked()
的实际示例。
我创建了一个相当大的data.table
来做这个测试。我们要堆叠 50 个数值列,要堆叠 50 个因子列。我还进一步优化了@Andreas 的答案。
数据
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)
基准测试函数
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])
结果
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
^^ 我不确定为什么 Andreas 目前的回答在这里这么慢。我所做的“优化”基本上是针对unlist
而不使用by
,这对“varB”(因子)列产生了巨大的差异。
手动方法仍然比“splitstackshape”中的函数快,但这些是我们正在谈论的毫秒数,以及一些非常紧凑的单行代码!
样本输出
作为参考,Stacked()
的输出如下所示。这是“堆叠”data.table
s 的 list
,每个堆叠变量都有一个列表项。
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
而且,merged.stack
的输出如下所示。这与使用 base R 中的reshape(..., direction = "long")
时所得到的类似。
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
【讨论】:
@Arun,我已经更新了函数。感谢您引起我的注意。期待 data.table 1.8.12(我猜melt
方法会在这里可用)。【参考方案2】:
无耻的自我推销
您可能想从我的包Kmisc
中尝试melt_
。 melt_
本质上是对 reshape2:::melt.data.frame
的重写,大部分繁重的工作都是在 C 中完成的,并尽可能避免复制和类型强制以实现快速实现。
一个例子:
## 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'))
)
给我
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
如果运气好的话,这对于您的数据来说已经足够快了。
【讨论】:
这太酷了——我想知道@hadley 是否会对合并您的改进的拉取请求感兴趣,以便您可以让reshape2::melt 更快地做饭? @Arun 请做!另外,我没有充分的理由使用STRING_PTR
而不是SET_STRING_ELT
;崩溃与此有关吗?我可以请您将代码发布到github.com/kevinushey/Kmisc/issues 吗?【参考方案3】:
对于 data.table 重塑,请尝试以下操作:
dt[, list(variable = names(.SD), value = unlist(.SD, use.names = F)), by = uid]
语法的代价是值得的;该函数运行速度非常快!
【讨论】:
unlist
通常使用use.names = FALSE
会提高速度。不确定这是否适用于这种情况,但可能。
刚刚试过这个,比前两种方法快(几乎是堆栈的 2 倍)。非常感谢两位!
刚刚检查了 1000x1000 data.table
。将use.names = FALSE
插入unlist
比仅使用unlist
快约3 倍。以上是关于在 data.table 上进行枢轴,类似于 rehape melt 函数的主要内容,如果未能解决你的问题,请参考以下文章
在SQL Server和Postgresql中使用CASE作为类似于枢轴的函数