【问题标题】:Pivot a large data.table透视大数据表
【发布时间】:2023-04-11 09:48:01
【问题描述】:

我在 R 中有一个大数据表:

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,这样 Category 就变成了一个列。不幸的是,由于组内类别的数量不是恒定的,我不能使用this answer

任何想法我可以如何做到这一点?

/edit:根据 joran 的 cmets 和 flodel 的回答,我们真的在重塑以下 data.table

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

这种重塑可以通过多种方式完成(到目前为止,我已经得到了一些很好的答案),但我真正想要的是能够很好地扩展到具有数百万行和数百行的 data.table 的东西数以千计的类别。

【问题讨论】:

  • 您的意思是用Qty 填充表格的主体吗?总结任何重复的组合?
  • @joran:在我的示例中,有重复的组合,但为了论证,我们假设没有。我想要的是 Category 字段的每个值都有一个不同的列,NA 或 0 表示缺失的组合。
  • @joran 我认为您的问题的正确答案是肯定的:我希望 Category 成为一列,每列中有 Qty,缺少类别的 NA 或 0,重复项应该相加(但它是在我们重塑之前进行求和是公平的)。

标签: r data.table


【解决方案1】:

喜欢吗?

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

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

【讨论】:

    【解决方案2】:

    没有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
    

    【讨论】:

    • 我会在我的示例 data.table 上试试这个,然后告诉你会发生什么。
    【解决方案3】:

    编辑

    我发现了这个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
    

    【讨论】:

    • 我会用 200,000 个 ID 和 1,000 个类别尝试这两种方法,然后让您知道结果如何。我怀疑fun_DT 会爆炸,但fun_reshape 可能会起作用。
    • @Zach 告诉我,那会很有趣。为什么你认为run_DT 会爆炸?我认为无论如何都必须以一种或另一种方式创建这些附加字段,所以我不希望这样。希望我做对了。另外,请参阅我的更新。代码现在更干净了。
    • 200,000 个 ID * 12 个月 * 1,000 个类别 = 2,400,000,000 行的完整 data.frame,大于 R 中的最大大小 data.frame (2,147,483,648)。
    • 是的,但是当您将类别放在列中时,这并没有真正改变,是吗?基本上,你只是转换信息,而不是减少它。我看到的唯一优点是您保存了识别列(即,您没有包含大量冗余信息的 24 亿行的“类别”列)。并且可能会遇到硬编码限制(例如您编写的 data.frames )。
    • 我不知道,但既然它继承自他们,我想是的。
    【解决方案4】:

    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
    

    【讨论】:

      猜你喜欢
      • 2022-10-19
      • 2018-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多