Создать функцию, которая вычисляет два столбца со вторым вычислением из первого столбца?

#r

#r

Вопрос:

Я пытаюсь сделать следующее в data.table или создать функцию в замене цикла for. Однако я не уверен, как вернуть два столбца с одним в зависимости от вычисления другого. Набор данных содержит единицы продаж и доставки для каждого «места» по месяцам, однако, только начальный инвентарь за первый месяц. Мне нужно рассчитать начальный запас каждого периода, сначала рассчитав конечный запас за последний месяц в этом месте. Конечные запасы для каждого места равны начальным запасам минус единицы продаж плюс единицы доставки.

Вот как я сейчас вычисляю:

 data <- data.table(place = c('a','b'),
                 month = c(1,1,2,2,3,3,4,4,5,5,6,6),
                 sales = c(20,2,3,5,6,7,8,1,5,1,5,3),
                 delivery = c(1,1,1,1,1,1,1,1,1,1,1,1),
                 starting_inv = c(100,100,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA),
                 ending_inv = c(81,99,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA) ) 

print(data)

   place month sales delivery starting_inv ending_inv
 1:     a     1    20        1          100         81
 2:     b     1     2        1          100         99
 3:     a     2     3        1           NA         NA
 4:     b     2     5        1           NA         NA
 5:     a     3     6        1           NA         NA
 6:     b     3     7        1           NA         NA
 7:     a     4     8        1           NA         NA
 8:     b     4     1        1           NA         NA
 9:     a     5     5        1           NA         NA
10:     b     5     1        1           NA         NA
11:     a     6     5        1           NA         NA
12:     b     6     3        1           NA         NA

dt <- data[order(place,month)]

print(dt)

    place month sales delivery starting_inv ending_inv
 1:     a     1    20        1          100         81
 2:     a     2     3        1           NA         NA
 3:     a     3     6        1           NA         NA
 4:     a     4     8        1           NA         NA
 5:     a     5     5        1           NA         NA
 6:     a     6     5        1           NA         NA
 7:     b     1     2        1          100         99
 8:     b     2     5        1           NA         NA
 9:     b     3     7        1           NA         NA
10:     b     4     1        1           NA         NA
11:     b     5     1        1           NA         NA
12:     b     6     3        1           NA         NA

for (i in 1:nrow(dt)) {


  if (dt[i]$month != 1) {
  dt$starting_inv[i] <- dt[i-1]$ending_inv
  dt$ending_inv[i] <- dt[i]$starting_inv - dt[i]$sales    dt[i]$delivery 
  }
  

}

print(dt)

   place month sales delivery starting_inv ending_inv
 1:     a     1    20        1          100         81
 2:     a     2     3        1           81         79
 3:     a     3     6        1           79         74
 4:     a     4     8        1           74         67
 5:     a     5     5        1           67         63
 6:     a     6     5        1           63         59
 7:     b     1     2        1          100         99
 8:     b     2     5        1           99         95
 9:     b     3     7        1           95         89
10:     b     4     1        1           89         89
11:     b     5     1        1           89         89
12:     b     6     3        1           89         87

  

Я хотел бы избежать шага, который требует сортировки таблицы по месту и месяцу. Затем вычисление этого в таблице с гораздо большим количеством данных занимает слишком много времени, и у меня возникают проблемы с преобразованием этого в векторизованную функцию.

Ответ №1:

Итерация фиксируется совокупной суммой, остальное затем может быть векторизовано, поэтому должно быть быстрым.

 data[, starting_inv := cumsum(shift(delivery-sales, fill = starting_inv[1])), place]
data[, ending_inv := starting_inv delivery-sales]

data
#>     place month sales delivery starting_inv ending_inv
#>  1:     a     1    20        1          100         81
#>  2:     b     1     2        1          100         99
#>  3:     a     2     3        1           81         79
#>  4:     b     2     5        1           99         95
#>  5:     a     3     6        1           79         74
#>  6:     b     3     7        1           95         89
#>  7:     a     4     8        1           74         67
#>  8:     b     4     1        1           89         89
#>  9:     a     5     5        1           67         63
#> 10:     b     5     1        1           89         89
#> 11:     a     6     5        1           63         59
#> 12:     b     6     3        1           89         87
  

Это предполагает, что фактические данные, с которыми вы имеете дело, упорядочены по month . Если это не так, то вставьте order(month) после первой квадратной скобки в первой строке.

Ответ №2:

Вот один вариант с accumulate2 из purrr

 library(purrr)
library(dplyr)
library(tidyr)
dt %>%
     group_by(place) %>%
     dplyr::mutate(starting_inv = accumulate2(delivery, sales, 
        ~ ..1 - ..3   ..2 , .init = first(starting_inv))[-n()]) %>% 
     unnest(c(starting_inv)) %>%
     mutate(ending_inv = lead(starting_inv))
# A tibble: 12 x 6
# Groups:   place [2]
#   place month sales delivery starting_inv ending_inv
#   <chr> <dbl> <dbl>    <dbl>        <dbl>      <dbl>
# 1 a         1    20        1          100         81
# 2 a         2     3        1           81         79
# 3 a         3     6        1           79         74
# 4 a         4     8        1           74         67
# 5 a         5     5        1           67         59
# 6 a         6     5        1           59         NA
# 7 b         1     2        1          100         99
# 8 b         2     5        1           99         95
# 9 b         3     7        1           95         89
#10 b         4     1        1           89         89
#11 b         5     1        1           89         87
#12 b         6     3        1           87         NA
  

Это также можно использовать вместе с data.table

 dt[, starting_inv := unlist(accumulate2(delivery, sales, 
     function(x, y, z) x - z   y ,
   .init = first(starting_inv))[-.N]), place][, ending_inv := 
         shift(starting_inv, type = 'lead'), place]