эффективная проблема cumsum и не может go ниже нуля - PullRequest
1 голос
/ 31 марта 2020

Я ищу способ сделать sh максимально эффективным, как я работаю с большими наборами данных (всего ~ 7 миллионов строк). Сравнение dplyr против data.table было бы очень полезно. В любой день года я хочу знать, сколько акций у того или иного поставщика. Мы знаем, сколько на складе в 1-й день года, и это вопрос создания скользящей суммы на основе того, перемещен ли запас from или to другому поставщику. Каждый ряд представляет одну часть перемещаемого запаса. Если есть NA, это просто означает, что есть внешний ход from или to и обрабатывается таким же образом. Хотя запас не может упасть ниже 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

и подсчет запаса базовой линии на 1-й день года для все поставщики:

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 и затем полностью объедините их в список в конце, но не учтите, что подсчет запасов не может go ниже 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 или выше?

спасибо

1 Ответ

1 голос
/ 01 апреля 2020

Вот вариант:

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))

Если это все еще слишком медленно, мы можем попробовать другой подход .

...