Home > Software design >  Indented Bill of Materials multiplication in R
Indented Bill of Materials multiplication in R

Time:01-08

I am trying to determine the Qty Needed for a forecast on an indented bill of materials (BOM) to do some inventory control. The BOM has multiple levels for subassemblies so the quantity needed for the parent item needs to get multiplied by each level. For example:

Item Level Quantity.Per Parent Item Parent Qty Forecast Qty Needed
Finished Item 0 1 Finished Item 10 (1x10) = 10
Subassembly 1 1 2 Finished Item 10 (1x2x10)=20
Component 1 Subassembly 1 2 5 Finished Item 10 (1 x 2 x 5 x 10) = 100
Subassembly 2 1 4 Finished Item 10 (1 x 4 x 10 ) = 40
Component 1 Subassembly 2 2 2 Finished Item 10 (1 x 4 x 2 x 10 ) = 80
Component 1 Component 1 Subassembly 2 3 2 Finished Item 10 (1 x 4 x 2 x 2 x 10 ) = 160

Is there a way to calculate the qty needed in R for each line of BOM by bringing in the top level quantity per all the way up to the finished item?

CodePudding user response:

Up front: this is fragile: if row order changes at all, it will break ... silently. If you have any better way to identify groups and subgroups, it would be good to use them.

However, try this

dplyr

library(dplyr)
dat %>%
  group_by(Parent.Item) %>%
  group_by(L1 = cumsum(Level == 1), .add = TRUE) %>%
  mutate(
    Qty = Parent.Qty.Forecast * sapply(Level, \(lvl) prod(Quantity.Per[Level <= lvl]))
  ) %>%
  ungroup()
# # A tibble: 6 x 7
#   Item                                  Level Quantity.Per Parent.Item   Parent.Qty.Forecast    L1   Qty
#   <chr>                                 <int>        <int> <chr>                       <int> <int> <dbl>
# 1 Finished Item                             0            1 Finished Item                  10     0    10
# 2 Subassembly 1                             1            2 Finished Item                  10     1    20
# 3 Component 1 Subassembly 1                 2            5 Finished Item                  10     1   100
# 4 Subassembly 2                             1            4 Finished Item                  10     2    40
# 5 Component 1 Subassembly 2                 2            2 Finished Item                  10     2    80
# 6 Component 1 Component 1 Subassembly 2     3            2 Finished Item                  10     2   160

base R

dat$L1 <- with(dat, ave(Level == 1, Parent.Item, FUN = cumsum))
dat$Qty <- with(dat, ave(seq_len(nrow(dat)), list(Parent.Item, L1), FUN = \(rn) {
  Parent.Qty.Forecast[rn] * sapply(Level[rn], \(lvl) prod(Quantity.Per[rn][Level[rn] <= lvl]))
}))

data.table

library(data.table)
# should use setDT(dat) instead
as.data.table(dat
  )[, L1 := cumsum(Level == 1), by = .(Parent.Item) 
    ][, Qty := Parent.Qty.Forecast * sapply(Level, \(lvl) prod(Quantity.Per[Level <= lvl])),
      by = .(Parent.Item, L1) ]

Data

### without 'Qty.Needed'
dat <- structure(list(Item = c("Finished Item", "Subassembly 1", "Component 1 Subassembly 1", "Subassembly 2", "Component 1 Subassembly 2", "Component 1 Component 1 Subassembly 2"), Level = c(0L, 1L, 2L, 1L, 2L, 3L), Quantity.Per = c(1L, 2L, 5L, 4L, 2L, 2L), Parent.Item = c("Finished Item", "Finished Item", "Finished Item", "Finished Item", "Finished Item", "Finished Item"), Parent.Qty.Forecast = c(10L, 10L, 10L, 10L, 10L, 10L)), row.names = c(NA, -6L), class = "data.frame")
### with 'Qty.Needed`
dat <- structure(list(Item = c("Finished Item", "Subassembly 1", "Component 1 Subassembly 1", "Subassembly 2", "Component 1 Subassembly 2", "Component 1 Component 1 Subassembly 2"), Level = c(0L, 1L, 2L, 1L, 2L, 3L), Quantity.Per = c(1L, 2L, 5L, 4L, 2L, 2L), Parent.Item = c("Finished Item", "Finished Item", "Finished Item", "Finished Item", "Finished Item", "Finished Item"), Parent.Qty.Forecast = c(10L, 10L, 10L, 10L, 10L, 10L), Qty.Needed = c("(1x10) = 10", "(1x2x10)=20", "(1 x 2 x 5 x 10) = 100", "(1 x 4 x 10 ) = 40",  "(1 x 4 x 2 x 10 ) = 80", "(1 x 4 x 2 x 2 x 10 ) = 160")), row.names = c(NA, -6L), class = "data.frame")
  •  Tags:  
  • Related