枢轴大data.table

枢轴大data.table

问题描述:

我有一个大的数据表中R:枢轴大data.table

library(data.table) 
set.seed(1234) 
n <- 1e+07*2 
DT <- data.table(
    ID=sample(1:200000, n, replace=TRUE), 
    Month=sample(1:12, n, replace=TRUE), 
    Category=sample(1:1000, n, replace=TRUE), 
    Qty=runif(n)*500, 
    key=c('ID', 'Month') 
) 
dim(DT) 

我想枢此data.table,使得分类成为一列。不幸的是,由于类别的数量在组内不固定,我不能使用this answer

任何想法我可能会这样做?

/编辑:基于joran的意见和flodel的答案,我们真正重塑以下data.table

agg <- DT[, list(Qty = sum(Qty)), by = c("ID", "Month", "Category")] 

这种重塑可以实现多种方式(我已经得到了一些很好的答案为止),但我真正想要的是能够很好地适应data.table的行为,其中包含数百万行和数百到数千个类别。

+1

你的意思是用'Qty'填充表格的正文吗?总结任何重复的组合? – joran 2013-04-04 22:19:48

+0

@joran:在我的例子中有重复的组合,但为了争论,让我们假设没有。我想要的是对于类别字段的每个值都有一个不同的列,其中NA或0表示缺失的组合。 – Zach 2013-04-04 22:23:33

+0

@joran我认为你的问题的正确答案是肯定的:我希望类别成为一列,每列中包含数量,缺失类别为NAs或0,重复数据应该总结(但在我们之前进行总结是公平的)重塑)。 – Zach 2013-04-05 14:30:02

data.table实现的melt/dcast data.table具体方法(在C)更快的版本。它还增加了熔化和铸造多列的附加功能。请参阅Efficient reshaping using data.tables小插图。

请注意,我们不需要加载reshape2包。

library(data.table) 
set.seed(1234) 
n <- 1e+07*2 
DT <- data.table(
    ID=sample(1:200000, n, replace=TRUE), 
    Month=sample(1:12, n, replace=TRUE), 
    Category=sample(1:800, n, replace=TRUE), ## to get to <= 2 billion limit 
    Qty=runif(n), 
    key=c('ID', 'Month') 
) 
dim(DT) 

> system.time(ans <- dcast(DT, ID + Month ~ Category, fun=sum)) 
# user system elapsed 
# 65.924 20.577 86.987 
> dim(ans) 
# [1] 2399401  802 

那样?

agg <- DT[, list(Qty = sum(Qty)), by = c("ID", "Month", "Category")] 

reshape(agg, v.names = "Qty", idvar = c("ID", "Month"), 
     timevar = "Category", direction = "wide") 

没有data.table特定的宽度调整方法。

这是一种可行的方法,但它相当宽松。

有一个功能要求#2619 Scoping for LHS in :=,以帮助使这个更简单。

下面是一个简单的例子

# a data.table 
DD <- data.table(a= letters[4:6], b= rep(letters[1:2],c(4,2)), cc = as.double(1:6)) 
# with not all categories represented 
DDD <- DD[1:5] 
# trying to make `a` columns containing `cc`. retaining `b` as a column 
# the unique values of `a` (you may want to sort this...) 
nn <- unique(DDD[,a]) 
# create the correct wide data.table 
# with NA of the correct class in each created column 
rows <- max(DDD[, .N, by = list(a,b)][,N]) 
DDw <- DDD[, setattr(replicate(length(nn), { 
        # safe version of correct NA 
        z <- cc[1] 
         is.na(z) <-1 
        # using rows value calculated previously 
        # to ensure correct size 
         rep(z,rows)}, 
        simplify = FALSE), 'names', nn), 
      keyby = list(b)] 
# set key for binary search 
setkey(DDD, b, a) 
# The possible values of the b column 
ub <- unique(DDw[,b]) 
# nested loop doing things by reference, so should be 
# quick (the feature request would make this possible to 
# speed up using binary search joins. 
for(ii in ub){ 
    for(jj in nn){ 
    DDw[list(ii), {jj} := DDD[list(ii,jj)][['cc']]] 
    } 
} 

DDw 
# b d e f 
# 1: a 1 2 3 
# 2: a 4 2 3 
# 3: b NA 5 NA 
# 4: b NA 5 NA 
+0

我会在我的示例data.table上试试这个,并让你知道发生了什么。 – Zach 2013-04-05 14:27:18

编辑

我发现这个SO post,其中包括更好的方式向 缺少的行插入到data.table。功能fun_DT相应地调整 。现在代码更清洁了;虽然我没有看到速度改善 。

看到其他职位我更新。 Arun的解决方案也适用,但您必须手动插入缺失的组合。由于你在这里有更多的标识符列(ID,Month),所以我在这里只提出了一个肮脏的解决方案(首先创建一个ID2,然后创建所有的ID2-Category组合,然后填充data.table,然后进行重塑)。

我敢肯定这是不是最好的解决办法,但如果this FR是内置的,这些步骤可以自动完成。

的解决方案是大致相同的速度明智的,尽管它会看到如何扩展(我的机器太慢很有趣,所以我不希望任何进一步增加n个...电脑死机往往已经;-)

library(data.table) 
library(rbenchmark) 

fun_reshape <- function(n) { 

    DT <- data.table(
    ID=sample(1:100, n, replace=TRUE), 
    Month=sample(1:12, n, replace=TRUE), 
    Category=sample(1:10, n, replace=TRUE), 
    Qty=runif(n)*500, 
    key=c('ID', 'Month') 
) 
    agg <- DT[, list(Qty = sum(Qty)), by = c("ID", "Month", "Category")] 
    reshape(agg, v.names = "Qty", idvar = c("ID", "Month"), 
      timevar = "Category", direction = "wide") 
} 

#UPDATED! 
fun_DT <- function(n) { 

    DT <- data.table(
    ID=sample(1:100, n, replace=TRUE), 
    Month=sample(1:12, n, replace=TRUE), 
    Category=sample(1:10, n, replace=TRUE), 
    Qty=runif(n)*500, 
    key=c('ID', 'Month') 
) 

    agg <- DT[, list(Qty = sum(Qty)), by = c("ID", "Month", "Category")] 
    agg[, ID2 := paste(ID, Month, sep="_")] 

    setkey(agg, ID2, Category) 
    agg <- agg[CJ(unique(ID2), unique(Category))] 

    agg[, as.list(setattr(Qty, 'names', Category)), by=list(ID2)] 

} 

library(rbenchmark) 

n <- 1e+07 
benchmark(replications=10, 
      fun_reshape(n), 
      fun_DT(n)) 
      test replications elapsed relative user.self sys.self user.child sys.child 
2  fun_DT(n)   10 45.868  1 43.154 2.524   0   0 
1 fun_reshape(n)   10 45.874  1 42.783 2.896   0   0 
+0

我会用200,000个ID和1,000个类别来尝试这些,并让你知道它是怎么回事。我怀疑'fun_DT'会爆炸,但'fun_reshape'可能会起作用。 – Zach 2013-04-05 14:26:36

+0

@Zach让我知道,这将是有趣的。你为什么认为'run_DT'会爆炸?无论如何,我认为这些附加领域必须以某种方式创建,所以我不会期望这一点。希望我说得对。另请参阅我的更新。现在代码更清洁。 – 2013-04-06 01:22:02

+0

200,000个ID * 12个月* 1,000个类别= 2,400,000,000行的完整数据帧,大于R(2,147,483,648)中的最大数据量。 – Zach 2013-04-06 01:33:48