在 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)]
通过从每个 Draw
和 Product
的库存中减去 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
# ...
我不需要使用循环或应用,因为这需要非常高效:
对于那些知道 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)]
通过从每个 Draw
和 Product
的库存中减去 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
# ...