在 R 中的矩阵上实现基于规则的向量减法(缩减)的有效方法,无需循环或应用

Efficient way to implement rule-based vector subtraction (drawdown) over matrices in R without LOOPS or apply

我不需要使用循环或应用,因为这需要非常高效:

对于那些知道 LIFO 或 FIFO 是什么的人来说,这些是我正在尝试使用的规则。基本上,考虑以下库存矩阵:

基本上,给定一个库存矩阵 "C" 和一些 "drawdowns","qs":

J=2
Tp=2
C = matrix(2,J,Tp)
rownam = as.character()
colnam = as.character()
for(j in 1:J){rownam = c(rownam,paste0('prod',j))}
for(j in 1:Tp){colnam = c(colnam,paste0('vint',j))}
rownames(C) = rownam
colnames(C) = colnam

C[1,1]=C[1,1]+1
C[2,1]=C[2,1]-1

> C
      vint1 vint2
prod1     3     2
prod2     1     2

这个库存矩阵表明有两种产品,每种都有两个年份。例如,我们有 3 个单位的 1 天旧产品 1 和 2 个单位的 2 天旧产品 2。假设我们被告知要减去 3 个单位的产品 1。我们可以先从 vintage 1 或 2 中获取它。后进先出会让它首先耗尽所有年份 1,留下 0 个单位的年份 1 和 2 个单位的年份 2。FIFO 将首先取出 2 个单位的年份 2,并且由于还有一个额外的单位要完成,所以向上移动到从年份 1,留下年份 2 的 0 和年份 1 的 2。

下面,我展示了这个规则通常适用于许多 "drawdowns"(例如,产品 1 的需求 3 和单元 2 的需求 4 将是平局的 1 个示例)。

以及回撤:

qs = rbind(
  c(4, 1),  c(4,1),
  c(4, 1),  c(1, 3),
  c(3, 2),  c(4, 1),
  c(1, 2),  c(2, 0),
  c(2, 1),  c(2, 3),
  c(0, 3),  c(2, 2))



> qs
      [,1] [,2]
 [1,]    4    1
 [2,]    4    1
 [3,]    4    1
 [4,]    1    3
 [5,]    3    2
 [6,]    4    1
 [7,]    1    2
 [8,]    2    0
 [9,]    2    1
[10,]    2    3
[11,]    0    3
[12,]    2    2

回撤的每一行都是一个单独的模拟回撤,将使用 LIFO 或 FIFO 应用于矩阵。 (LIFO 意味着当满足需求 q 时,您首先拿走最新的年份(年份 2)。而 FIFO 意味着您走另一条路。)

所以我运行:

Cmat = do.call(rbind, replicate(dim(qs)[1], C, simplify=FALSE)) #matrix 

后进先出的输出应如下所示:

drawndown
      vint1 vint2
prod1     1     0
prod2     1     1
prod1     1     0
prod2     1     1
prod1     1     0
prod2     1     1
prod1     3     1
prod2     0     0
...

这是一个向量化的方法 data.table 你可以试试:

library(data.table)
draw_value <- as.vector(t(qs))      # flatten the draw down matrix as a vector
CmatDT <- data.table(Cmat, keep.rownames = T)   # convert the Cmat to data.table

CmatDT[, `:=` (vint1 = ifelse(vint2 >= draw_value, vint1, vint1 + vint2 - draw_value), 
               vint2 = ifelse(vint2 >= draw_value, vint2 - draw_value, 0))]
# mutate the vint1 and vint2 columns based on if vint2 contains enough product for the draw down.

CmatDT
#       rn vint1 vint2
# 1: prod1     1     0
# 2: prod2     1     1
# 3: prod1     1     0
# 4: prod2     1     1
# 5: prod1     1     0
# 6: prod2     1     1
# 7: prod1     3     1
# 8: prod2     0     0
# ...

更新:使用data.table的更通用的解决方案,它很长,但主要是为处理准备数据:

构建一个函数从向量中减去一个数字,这将耗尽第一个元素,然后是第二个元素,直到数量为零:

minus <- function(vec, amount) {
    if(vec[1] >= amount) c(vec[1] - amount, vec[-1]) 
    else c(0, minus(vec[-1], amount - vec[1]))
}

数据准备:重构提取矩阵和库存,将它们绑定在一起进行进一步处理

qsDT <- setNames(data.table(qs, keep.rownames = T), c("DrawId", "Prod1", "Prod2"))
longQs <- melt(qsDT, id.vars = "DrawId", value.name = "Draw", variable.name = "Product")[order(as.numeric(DrawId))] 
longQsC <- melt(cbind(longQs, C), measure.vars = c("vint1", "vint2"), value.name = "Inventory", variable.name = "Vintage")[order(as.numeric(DrawId), Product, -Vintage)]

通过从每个 DrawProduct 的库存中减去 Draw 值来创建新库存,并重塑结果:

longQsC[, NewInventory := minus(Inventory, unique(Draw)), .(DrawId, Product)]
longQsC[, dcast(.SD, Product ~ Vintage, value.var = "NewInventory"), .(DrawId)]

#   DrawId Product vint1 vint2
#1:      1   Prod1     1     0
#2:      1   Prod2     1     1
#3:      2   Prod1     1     0
#4:      2   Prod2     1     1
#5:      3   Prod1     1     0
#6:      3   Prod2     1     1
#7:      4   Prod1     3     1
#8:      4   Prod2     0     0
# ...