【问题标题】:row-wise first/last occurrences from column series in data.tabledata.table 中列系列的逐行第一次/最后一次出现
【发布时间】:2016-03-04 16:08:06
【问题描述】:

作为这个老歌但好人的后续行动:efficient row-wise operations on a data.table

我有一些数据(不幸的是)看起来像:

library('data.table')
set.seed(1234)
m <- 5
n <- 7
rb <- function() runif(m,1000,2000) * rbinom(m,1,0.5)   
series_col_nms <- paste0('YearNo',1:n)    
rev <- data.table(cust_id = paste0('CustNo',1:m), 
                other_stuff = sample(letters,m, replace=TRUE))
for(col in series_col_nms){
  set(rev, j=col, value=rb())
}
setkey(rev, cust_id)

每个客户一行,包含不同列,包括第 1 年、第 2 年、...的年收入

我想获取每个客户的任何收入的第一年和最后一年的年度指数。

我可以产生想要的结果,但加入有点麻烦:

years_active <- rev[, which(.SD>0), .SDcols = series_col_nms, 
                    keyby=cust_id][, .(min_year_active = min(V1),
                                       max_year_active = max(V1)), keyby=cust_id]
years_active[rev]

这些获取最小索引的尝试失败:

rev[, apply(.SD, 1, function(x) min(which(x>0))), .SDcols=series_col_nms, by=cust_id] # returns data type error    
rev[, do.call(pmin, lapply(.SD, function(x) which(x>0))), .SDcols=series_col_nms, by=cust_id] # returns empty

data.table 的方法是什么?

【问题讨论】:

    标签: r data.table time-series


    【解决方案1】:

    当你想逐行对多列进行操作时,通常的做法是先melt你的数据集,然后对单个列进行操作。

    在你的情况下,一个相对简单的解决方案可能是这样的

    res <- melt(rev, id = 1:2)[, 
             as.list({
                temp <- value != 0
                if (any(temp)) range(which(temp)) else rep(NA_integer_, 2)
             }), 
            by = cust_id]
    
    rev[, c("Min", "Max") := res[, .(V1, V2)]]
    rev
    #    cust_id other_stuff  YearNo1 YearNo2  YearNo3  YearNo4  YearNo5  YearNo6  YearNo7 Min Max
    # 1: CustNo1           c 1640.311       0    0.000 1759.671    0.000 1503.933    0.000   1   6
    # 2: CustNo2           q 1009.496       0    0.000 1201.248    0.000    0.000 1308.095   1   7
    # 3: CustNo3           p    0.000       0    0.000    0.000 1484.991    0.000    0.000   5   5
    # 4: CustNo4           q 1666.084       0 1831.345 1992.150 1243.929    0.000 1051.647   1   7
    # 5: CustNo5           w    0.000       0    0.000    0.000    0.000    0.000    0.000  NA  NA
    

    一个更干净但带有警告的版本可能是

    melt(rev, id = 1:2)[, as.list(as.integer(range(which(value != 0)))), by = cust_id]
    

    【讨论】:

    • 我不会把 with warning 版本称为更干净的版本,而是脏的版本;)
    【解决方案2】:

    重塑我会以长格式存储数据:

    mrev = melt(rev, 
      id=c("cust_id","other_stuff"), 
      variable.name="YearNo", 
      value.name="revenue")[revenue > 0]
    

    您在 revenue &gt; 0 条件下失去了客户 5,但我怀疑这是否重要。

    然后根据需要收集汇总统计信息:

    mrev[ , list(first = YearNo[1], last = YearNo[.N]), by=cust_id]
    
    #    cust_id   first    last
    # 1: CustNo1 YearNo1 YearNo6
    # 2: CustNo2 YearNo1 YearNo7
    # 3: CustNo4 YearNo1 YearNo7
    # 4: CustNo3 YearNo5 YearNo5
    

    当然,从你一直使用的字符串中解析出数字很简单。


    ma​​x.col 我认为这是一个糟糕的组合,但是......

    max.col(rev[,!c("cust_id","other_stuff"),with=FALSE] > 0, "first")
    max.col(rev[,!c("cust_id","other_stuff"),with=FALSE] > 0, "last")
    

    您必须返回并分别填写全零的特殊情况(客户 5)。

    【讨论】:

    • 谢谢弗兰克和@DavidArenburg——这几乎证实了我原来的方法,虽然可能显式熔化更快——你有什么方法可以使用逐行操作(pmin.SD等)为此?
    • @C8H10N4O2 我认为如果速度是一个问题,你应该坚持使用熔化/长数据,哪个 data.table 更好。 (这也可以帮助您避免将数据笨拙地放入列名。)pmin 恰好是可用于链接问题的 hack,但我不会打赌这种快捷方式通常可用。
    • 谢谢(和 +1)——如果我能找到一个按行的解决方案,我会对其进行基准测试,但很高兴知道我在正确的轨道上。我将通过range() 接受@DavidArenburg 对他的想法的解决方案(否则就是平局)
    • @C8H10N4O2 行式解决方案在 R 中通常是个坏主意,除非完全矢量化,这仅仅是因为 R 的设计目的不是对行进行操作,而是对向量进行操作。 melt 是从对行进行操作到对向量进行操作的自然转换。 data.table 开发人员开发了 melt 以进行全面优化,因此即使对于庞大的数据集,这种转换也会显得更少。
    • pmin 是矢量化的,因此不是按行操作进行的。请重新阅读我的评论。在您的情况下,没有我们可以识别的简单的pmin 解决方案,因此诸如apply(df, 1,...) 之类的逐行操作将是最糟糕的解决方案。此外,pmin 专门用于查找最小值,仅此而已。通过首先使用melt,您可以应用any函数后缀,因此与特定hack相比,这是一个通用的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2020-08-30
    • 2019-12-07
    • 2020-01-03
    • 2013-06-06
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    相关资源
    最近更新 更多