【问题标题】:Sum of most recent values across groups各组最近值的总和
【发布时间】:2016-10-10 11:18:16
【问题描述】:

对于我的每一行数据,我想计算每个 group 的最新 value 的总和:

dt = data.table(group = c('a','b','a','a','b','a'),
                value = c(10, 5, 20, 15, 15, 10),
                desired = c(10, 15, 25, 20, 30, 25))
#   group value desired
#1:     a    10      10
#2:     b     5      15
#3:     a    20      25  # latest value of a is 20, of b is 5
#4:     a    15      20  # latest value of a is 15, of b is 5
#5:     b    15      30
#6:     a    10      25

desired 列是我想要实现的目标,我可以用一个简单的循环来做到这一点,但是我的数据非常大,有很多行和组(1M+ 行,1000+ 组)。

for (i in seq_len(nrow(dt))) {
  # can use `set` to make this faster, but still too slow
  # this is just to illustrate *a* solution
  dt[i, desired1 := dt[1:i, value[.N], by = group][, sum(V1)]]
}

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    来自@eddi 的更简单的逻辑(在 cmets 下)减少了如下所示的环岛:

    dt[, incr := diff(c(0, value)), by = group][, ans := cumsum(incr)]
    

    不确定它如何扩展到更多组,但这里有一个包含 3 个组的示例数据:

    # I hope I got the desired output correctly
    require(data.table)
    dt = data.table(group = c('a','b','c','a','a','b','c','a'),
                    value = c(10, 5, 20, 25, 15, 15, 30, 10),
                    desired = c(10, 15, 35, 50, 40, 50, 60, 55))
    

    添加rleid

    dt[, id := rleid(group)]
    

    提取每个group, id的最后一行:

    last = dt[, .(value=value[.N]), by=.(group, id)]
    

    last 将拥有唯一的id。现在的想法是获取每个id 的增量,然后加入+更新回来。

    last = last[, incr := value - shift(value, type="lag", fill=0L), by=group
              ][, incr := cumsum(incr)-value][]
    

    立即加入 + 更新:

    dt[last, ans := value + i.incr, on="id"][, id := NULL][]
    #    group value desired ans
    # 1:     a    10      10  10
    # 2:     b     5      15  15
    # 3:     c    20      35  35
    # 4:     a    25      50  50
    # 5:     a    15      40  40
    # 6:     b    15      50  50
    # 7:     c    30      60  60
    # 8:     a    10      55  55
    

    我还不确定在哪里/如果这坏了..现在会仔细看看。为了让更多人关注,我马上写了。


    将 500 个组与 10,000 行与 David 的解决方案进行比较:

    require(data.table)
    set.seed(45L)
    groups = apply(matrix(sample(letters, 500L*10L, TRUE), ncol=10L), 1L, paste, collapse="")
    uniqueN(groups) # 500L
    N = 1e4L
    dt = data.table(group=sample(groups, N, TRUE), value = sample(100L, N, TRUE))
    
    arun <- function(dt) {
    
        dt[, id := rleid(group)]
        last = dt[, .(value=value[.N]), by=.(group, id)]
        last = last[, incr := value - shift(value, type="lag", fill=0L), by=group
                  ][, incr := cumsum(incr)-value][]
        dt[last, ans := value + i.incr, on="id"][, id := NULL][]
        dt$ans
    }
    
    david <- function(dt) {
        dt[, indx := .I]
        res <- dcast(dt, indx ~ group)
        for (j in names(res)[-1L]) 
            set(res, j = j, value = res[!is.na(res[[j]])][res, on = "indx", roll = TRUE][[j]])
        rowSums(as.matrix(res)[, -1], na.rm = TRUE)
    
    }
    
    system.time(ans1 <- arun(dt))  ## 0.024s
    system.time(ans2 <- david(dt)) ## 38.97s 
    identical(ans1, as.integer(ans2))
    # [1] TRUE
    

    【讨论】:

    • 这太好了,谢谢!我对 join 和 rleid 有点困惑——dt[, incr := diff(c(0, value)), by = group][, ans := cumsum(incr)] 不工作吗? (我不确定我是否遗漏了一些逻辑)
    • 哦,是的,我认为这行得通!这种迂回的方式归结为您的单线。
    【解决方案2】:

    我会为每个组创建一个列,显示该组的最新值。然后对这些列求和:

    library(zoo)
    result <- rep(0, nrow(dt))
    for(g in dt[, unique(group)]) {
      result <- result + dt[, na.fill(na.locf(ifelse(group==g, 1, NA)*value, na.rm=F), 0)]
    }
    
    all(dt[, desired] == result)
    

    【讨论】:

    • 更新了适合 1e3+ 组的答案
    【解决方案3】:

    使用dplyr,适用于多组,但数据不能是数据表。

    library(dplyr)
    library(tidyr)
    library(zoo)
    dt %>% 
      mutate(row_number = row_number()) %>%
      spread(group, value) %>%
      arrange(row_number) %>%
      mutate_each(funs(na.locf(., na.rm = FALSE))) %>%
      mutate(answer = rowSums(.[,-1:-2], na.rm = T))
    

    在示例数据上使用上述函数(注意data.frame() 不是data.table()

    dt = data.frame(group = c('a','b','a','a','b','a'),
                    value = c(10, 5, 20, 15, 15, 10),
                    desired = c(10, 15, 25, 20, 30, 25))
      desired row_number  a  b answer
    1      10          1 10 NA     10
    2      15          2 10  5     15
    3      25          3 20  5     25
    4      20          4 15  5     20
    5      30          5 15 15     30
    6      25          6 10 15     25
    
    dt = data.frame(group = c('a','b','c','a','a','b','c','a'),
                    value = c(10, 5, 20, 25, 15, 15, 30, 10),
                    desired = c(10, 15, 35, 50, 40, 50, 60, 55))
    
      desired row_number  a  b  c answer
    1      10          1 10 NA NA     10
    2      15          2 10  5 NA     15
    3      35          3 10  5 20     35
    4      50          4 25  5 20     50
    5      40          5 15  5 20     40
    6      50          6 15 15 20     50
    7      60          7 15 15 30     60
    8      55          8 10 15 30     55
    

    【讨论】:

    • 这需要太多内存/太慢了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多