【问题标题】:efficient cumsum problem and can't go below zero有效的cumsum问题,不能低于零
【发布时间】:2020-03-31 09:02:14
【问题描述】:

我正在寻找一种方法来尽可能高效地完成以下工作,因为我正在处理大型数据集(总共约 700 万行)。 dplyrdata.table 比较会非常有帮助。对于一年中的任何一天,我都想知道给定供应商的库存量。我们知道一年中的第一天有多少库存,这是根据库存是移动from 还是to 另一个供应商来创建滚动总和的问题。每行代表一个正在移动的股票。如果有一个NA,这只是意味着有一个外部移动fromto 并且以相同的方式处理。但是,库存不能低于 0(请参阅下面的供应商a),如果任何供应商发生这种情况,累积总和必须设置为零,并且总和继续。我认为宽幅解决方案在这里没有意义,因为我有超过 100000 家供应商。

小例子:

    library(tidyverse)
library(data.table)
set.seed(100)
df <- data.frame(date = sample(seq.Date(from = as.Date("01/01/2018", "%d/%m/%Y"), 
                                 to=as.Date("30/01/2018", "%d/%m/%Y"), by = "day"), 20, replace = TRUE),
                 from = sample(letters[c(1:4, 12)], 20, replace = TRUE),
                 to = sample(letters[c(1:4, 14, 20)], 20, replace = TRUE), stringsAsFactors = FALSE) %>% 
  dplyr::arrange(date)
df[14, 2] <- NA
df[10, 3] <- NA
df[5, 3] <- NA
df[6, 2] <- NA
df
#          date from   to
# 1  2018-01-02    c    t
# 2  2018-01-04    l    c
# 3  2018-01-06    d    n
# 4  2018-01-06    d    t
# 5  2018-01-06    a <NA>
# 6  2018-01-07 <NA>    d
# 7  2018-01-07    b    t
# 8  2018-01-10    b    t
# 9  2018-01-11    l    n
# 10 2018-01-12    c <NA>
# 11 2018-01-14    b    t
# 12 2018-01-16    c    a
# 13 2018-01-19    c    n
# 14 2018-01-22 <NA>    a
# 15 2018-01-23    l    t
# 16 2018-01-23    d    a
# 17 2018-01-23    c    a
# 18 2018-01-23    l    c
# 19 2018-01-25    b    d
# 20 2018-01-26    a    c

以及所有供应商一年中第一天的基准库存计数:

base_line <- data.frame(supplier =c("l", "b", "d",  "c", "a", "n", "t"),

                            count = c(10, 20, 12, 5, 0, 2, 10))
    base_line
    #   supplier count
    # 1        l    10
    # 2        b    20
    # 3        d    12
    # 4        c     5
    # 5        a     0
    # 6        n     2
    # 7        t    10

期望产量 (每天的库存数量):

            date from   to cumsum_var supplier
1  2018-01-02    c    t         11        t
2  2018-01-06    d    t         12        t
3  2018-01-07    b    t         13        t
4  2018-01-10    b    t         14        t
5  2018-01-14    b    t         15        t
6  2018-01-23    l    t         16        t
7  2018-01-06    d    n          3        n
8  2018-01-11    l    n          4        n
9  2018-01-19    c    n          5        n
10 2018-01-06    a <NA>          0        a note 0, not -1
11 2018-01-16    c    a          1        a
12 2018-01-22 <NA>    a          2        a
13 2018-01-23    d    a          3        a
14 2018-01-23    c    a          4        a
15 2018-01-26    a    c          3        a
16 2018-01-06    d    n         11        d
17 2018-01-06    d    t         10        d
18 2018-01-07 <NA>    d         11        d
19 2018-01-23    d    a         10        d
20 2018-01-25    b    d         11        d
21 2018-01-02    c    t          4        c
22 2018-01-04    l    c          5        c
23 2018-01-12    c <NA>          4        c
24 2018-01-16    c    a          3        c
25 2018-01-19    c    n          2        c
26 2018-01-23    c    a          1        c
27 2018-01-23    l    c          2        c
28 2018-01-26    a    c          3        c
29 2018-01-07    b    t         19        b
30 2018-01-10    b    t         18        b
31 2018-01-14    b    t         17        b
32 2018-01-25    b    d         16        b
33 2018-01-04    l    c          9        l
34 2018-01-11    l    n          8        l
35 2018-01-23    l    t          7        l
36 2018-01-23    l    c          6        l

我的方法filter 基于供应商的两个数据集,执行 cumsum 然后在最后的列表中完全合并,但不包含库存计数可以的警告' t 低于 0(请参阅我的输出中 a 的问题)。

 base_line2 <- data.frame(date = rep(as.Date("31/12/2017", "%d/%m/%Y"), 7),
                         from = c("l", "b", "d",  "c", "a", "n", "t"),
                         from_new = c(10, 20, 12, 5, 0, 2, 10), stringsAsFactors = FALSE)
#get all suppliers (in real dataset >100000)
vars2 <- c("l", "b", "d",  "c", "a", "n", "t")
#function
my_fun <- function(x) {

  df %>% 
    filter_at(vars(from, to), any_vars(. == {{x}})) %>% 
    mutate(from_new = ifelse(from == {{x}}, -1, 0),
           to_new = ifelse(to == {{x}}, 1, 0)) %>% 
    bind_rows({base_line2 %>% filter(from == {{x}})}) %>% 
    dplyr::arrange(date) %>% 
    mutate(count_test = rowSums(select(., from_new, to_new), na.rm = T),
           cumsum_var = cumsum(count_test))

}

#use function over list
tmp <- lapply(vars2, my_fun)
output = rbindlist(tmp)
output
output %>% 
  filter(date > as.Date("2017-12-31")) 

#          date from   to from_new to_new count_test cumsum_var
# 1  2018-01-04    l    c       -1      0         -1          9
# 2  2018-01-11    l    n       -1      0         -1          8
# 3  2018-01-23    l    t       -1      0         -1          7
# 4  2018-01-23    l    c       -1      0         -1          6
# 5  2018-01-07    b    t       -1      0         -1         19
# 6  2018-01-10    b    t       -1      0         -1         18
# 7  2018-01-14    b    t       -1      0         -1         17
# 8  2018-01-25    b    d       -1      0         -1         16
# 9  2018-01-06    d    n       -1      0         -1         11
# 10 2018-01-06    d    t       -1      0         -1         10
# 11 2018-01-07 <NA>    d       NA      1          1         11
# 12 2018-01-23    d    a       -1      0         -1         10
# 13 2018-01-25    b    d        0      1          1         11
# 14 2018-01-02    c    t       -1      0         -1          4
# 15 2018-01-04    l    c        0      1          1          5
# 16 2018-01-12    c <NA>       -1     NA         -1          4
# 17 2018-01-16    c    a       -1      0         -1          3
# 18 2018-01-19    c    n       -1      0         -1          2
# 19 2018-01-23    c    a       -1      0         -1          1
# 20 2018-01-23    l    c        0      1          1          2
# 21 2018-01-26    a    c        0      1          1          3
# 22 2018-01-06    a <NA>       -1     NA         -1         -1
# 23 2018-01-16    c    a        0      1          1          0
# 24 2018-01-22 <NA>    a       NA      1          1          1
# 25 2018-01-23    d    a        0      1          1          2
# 26 2018-01-23    c    a        0      1          1          3
# 27 2018-01-26    a    c       -1      0         -1          2
# 28 2018-01-06    d    n        0      1          1          3
# 29 2018-01-11    l    n        0      1          1          4
# 30 2018-01-19    c    n        0      1          1          5
# 31 2018-01-02    c    t        0      1          1         11
# 32 2018-01-06    d    t        0      1          1         12
# 33 2018-01-07    b    t        0      1          1         13
# 34 2018-01-10    b    t        0      1          1         14
# 35 2018-01-14    b    t        0      1          1         15
# 36 2018-01-23    l    t        0      1          1         16

我认为这里的data.table 方法可能会提高效率或完全更好的dplyr 方法?有没有人建议有效地将库存保持在 0 或以上?

谢谢

【问题讨论】:

  • 对不起,好地方,只是缺少ls,现在更新,ls 现在在底部
  • 注意供应商ls 在我的“期望输出”的底部,但在我自己的方法输出的顶部。只要将它们组合在一起,最终输出中的供应商顺序就无关紧要!

标签: r performance dplyr data.table cumsum


【解决方案1】:

这是一个选项:

setDT(base_line)[, date := as.Date("2017-12-31")]
DT <- rbindlist(list(
        base_line,
        df[!is.na(from), .(date, supplier=from, count=-1L)],
        df[!is.na(to), .(date, supplier=to, count=1L)]),
    use.names=TRUE)
setorder(DT, supplier, date)


library(Rcpp)
cppFunction("
IntegerVector csreset (IntegerVector grp, IntegerVector v) {
    int sz = v.size(), cs = 0;
    IntegerVector res(sz);

    cs = v[0];
    res[0] = cs;
    for (int i=1; i<sz; i++) {
        if (grp[i] != grp[i-1]) {
            cs = 0;
        }

        if (cs + v[i] < 0) {
            cs = 0;
        } else {
            cs += v[i];
        }
        res[i] = cs;
    }

    return(res);
}
")

DT[, .(supplier, date, csreset(rleid(supplier), count))]

输出:

    supplier       date count
 1:        a 2017-12-31     0
 2:        a 2018-01-06     0
 3:        a 2018-01-16     1
 4:        a 2018-01-22     2
 5:        a 2018-01-23     3
 6:        a 2018-01-23     4
 7:        a 2018-01-26     3
 8:        b 2017-12-31    20
 9:        b 2018-01-07    19
10:        b 2018-01-10    18
11:        b 2018-01-14    17
12:        b 2018-01-25    16
13:        c 2017-12-31     5
14:        c 2018-01-02     4
15:        c 2018-01-04     5
16:        c 2018-01-12     4
17:        c 2018-01-16     3
18:        c 2018-01-19     2
19:        c 2018-01-23     1
20:        c 2018-01-23     2
21:        c 2018-01-26     3
22:        d 2017-12-31    12
23:        d 2018-01-06    11
24:        d 2018-01-06    10
25:        d 2018-01-07    11
26:        d 2018-01-23    10
27:        d 2018-01-25    11
28:        l 2017-12-31    10
29:        l 2018-01-04     9
30:        l 2018-01-11     8
31:        l 2018-01-23     7
32:        l 2018-01-23     6
33:        n 2017-12-31     2
34:        n 2018-01-06     3
35:        n 2018-01-11     4
36:        n 2018-01-19     5
37:        t 2017-12-31    10
38:        t 2018-01-02    11
39:        t 2018-01-06    12
40:        t 2018-01-07    13
41:        t 2018-01-10    14
42:        t 2018-01-14    15
43:        t 2018-01-23    16
    supplier       date count

数据:

library(data.table)
df <- fread("date from   to
2018-01-02    c    t
2018-01-04    l    c
2018-01-06    d    n
2018-01-06    d    t
2018-01-06    a   NA
2018-01-07   NA    d
2018-01-07    b    t
2018-01-10    b    t
2018-01-11    l    n
2018-01-12    c   NA
2018-01-14    b    t
2018-01-16    c    a
2018-01-19    c    n
2018-01-22   NA    a
2018-01-23    l    t
2018-01-23    d    a
2018-01-23    c    a
2018-01-23    l    c
2018-01-25    b    d
2018-01-26    a    c")[, date := as.Date(date, format="%Y-%m-%d")]

base_line <- data.frame(supplier =c("l", "b", "d",  "c", "a", "n", "t"),
    count = c(10, 20, 12, 5, 0, 2, 10))

如果还是太慢,我们可以尝试其他方法。

【讨论】:

  • 谢谢,这看起来像我所追求的解决方案类型,除了我认为 cumsum 不能正常工作,如果我们将 base_line 中的 b 更改为 0 并重新运行它,它没有给出预期的答案base_line &lt;- data.frame(supplier =c("l", "b", "d", "c", "a", "n", "t"), count = c(10, 0, 12, 5, 0, 2, 10))b 的答案在每个日期都应该是 0
  • 是的,这也是我担心的。这些会有帮助吗? stackoverflow.com/questions/40996306/…
  • @user63230 我添加了一个 Rcpp 函数来解决问题的递归性质和速度问题
  • 感谢这个出色的答案,一个小问题是输出中的第一个计数(第一行)设置为零,所以如果我们将 base_line a 更改为 10,第一行是0 而不是10?只是一个小问题,因为我在这个例子中不需要那个值
  • 对不起,我错过了 1 行。已经添加回来了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-02
  • 2022-01-24
  • 1970-01-01
  • 2010-10-13
  • 1970-01-01
  • 1970-01-01
  • 2011-06-30
相关资源
最近更新 更多