one item

First, let’s load a few libraries :

# ETL
library(tidyverse)

# for the tables
library(reactable)
library(reactablefmtr)

# for the charts
library(highcharter)

# for the Demand & Supply Planning calculations : the library planr
library(planr)

# Others
library(htmltools)

We’re going to present here how to use the drp() function from the R package planr.

More info on : https://github.com/nguyennico/planr

This function allows to calculate a Replenishment Plan, also called DRP (Distribution Requirement Planning).

We need a dataset with the 5 classic variables to use this function :

And a few new parameters added to this initial dataset :

We are going to :

1) Create Data Template

First, let’s create a dataset which contains the basic features, i.e. the 5 variables :

  • Period

  • Demand

  • Opening

  • Suply

  • DFU

We’ll call it my_demand_and_suppply.

Period <- c(
"1/1/2020", "2/1/2020", "3/1/2020", "4/1/2020", "5/1/2020", "6/1/2020", "7/1/2020", "8/1/2020", "9/1/2020", "10/1/2020", "11/1/2020", "12/1/2020","1/1/2021", "2/1/2021", "3/1/2021", "4/1/2021", "5/1/2021", "6/1/2021", "7/1/2021", "8/1/2021", "9/1/2021", "10/1/2021", "11/1/2021", "12/1/2021")

Demand <- c(360, 458,300,264,140,233,229,208,260,336,295,226,336,434,276,240,116,209,205,183,235,312,270,201)

Opening <- c(1310,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)

Supply <- c(0,0,0,0,0,2500,0,0,0,0,0,0,0,0,0,2000,0,0,0,0,0,0,0,0)


# assemble
my_demand_and_suppply <- data.frame(Period,
                                    Demand,
                                    Opening,
                                    Supply)

# let's add a Product
my_demand_and_suppply$DFU <- "Product A"

# format the Period as a date
my_demand_and_suppply$Period <- as.Date(as.character(my_demand_and_suppply$Period), format = '%m/%d/%Y')


# let's have a look at it
head(my_demand_and_suppply)
      Period Demand Opening Supply       DFU
1 2020-01-01    360    1310      0 Product A
2 2020-02-01    458       0      0 Product A
3 2020-03-01    300       0      0 Product A
4 2020-04-01    264       0      0 Product A
5 2020-05-01    140       0      0 Product A
6 2020-06-01    233       0   2500 Product A

Now, let’s add the 4 new parameters, that we will use to calculate the DRP :

  • SSCov : here 2 periods, means that we want to keep always, as a safety stocks, 2 months of coverage

  • DRPCovDur : here 3 periods, means that we want to replenish every 3 periods (it’s the frequency of replenishment)

  • MOQ : here 1 unit, by default, so we keep a flexible Minimum Order Quantity

  • FH : here 6 first periods defined as Frozen, which means that the DRP calculation will start from the Period n°7, mentioned as Free

df1 <- my_demand_and_suppply

df1$SSCov <- 2
df1$DRPCovDur <- 3
df1$MOQ <- 1
df1$FH <- c("Frozen", "Frozen", "Frozen", "Frozen","Frozen","Frozen","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free","Free")


# get Results
my_drp_template <- df1

head(my_drp_template)
      Period Demand Opening Supply       DFU SSCov DRPCovDur MOQ     FH
1 2020-01-01    360    1310      0 Product A     2         3   1 Frozen
2 2020-02-01    458       0      0 Product A     2         3   1 Frozen
3 2020-03-01    300       0      0 Product A     2         3   1 Frozen
4 2020-04-01    264       0      0 Product A     2         3   1 Frozen
5 2020-05-01    140       0      0 Product A     2         3   1 Frozen
6 2020-06-01    233       0   2500 Product A     2         3   1 Frozen

2) DRP Calculation

Now let’s apply the drp() function to this dataset :

  • the initial dataset, my_drp_template, has 9 variables

  • the new one, called below demo_drp, will have 15 variables

The 5 new variables are :

  • Safety.Stocks : the projected safety stocks, in units

  • Maximum.Stocks : the projected maximum stocks, in units.

    • Maximum Stock = Safety Stocks (SSCov) + Frequency of Supply (DRPCovDur)
  • DRP.Calculated.Coverage.in.Periods : the calculated projected inventories, expressed in periods of coverage

  • DRP.Projected.Inventories.Qty : the calculated projected inventories, based on the [DRP.plan]

  • DRP.plan : the calculated Replenishment Plan

    • in the Frozen Horizon : the existing plan, which is “frozen”
    • in the Free Horizon : the calculated plan, based on the parameters (SSCov, DRPCovDur, MOQ)
# set a working df
df1 <- my_drp_template

# calculate drp
demo_drp <- planr::drp(data = df1,
                       DFU = DFU,
                       Period = Period,
                       Demand =  Demand,
                       Opening = Opening,
                       Supply = Supply,
                       SSCov = SSCov,
                       DRPCovDur = DRPCovDur,
                       MOQ = MOQ,
                       FH = FH)

# formatting
demo_drp <- as.data.frame(demo_drp)

glimpse(demo_drp)
Rows: 24
Columns: 15
$ DFU                                <chr> "Product A", "Product A", "Product …
$ Period                             <date> 2020-01-01, 2020-02-01, 2020-03-01…
$ Demand                             <dbl> 360, 458, 300, 264, 140, 233, 229, …
$ Opening                            <dbl> 1310, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ Supply                             <dbl> 0, 0, 0, 0, 0, 2500, 0, 0, 0, 0, 0,…
$ SSCov                              <dbl> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,…
$ DRPCovDur                          <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,…
$ Stock.Max                          <dbl> 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,…
$ MOQ                                <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
$ FH                                 <chr> "Frozen", "Frozen", "Frozen", "Froz…
$ Safety.Stocks                      <dbl> 758, 564, 404, 373, 462, 437, 468, …
$ Maximum.Stocks                     <dbl> 1395, 1166, 1074, 1070, 1266, 1328,…
$ DRP.Calculated.Coverage.in.Periods <dbl> 2.7, 1.7, 0.7, -0.5, -0.9, 7.4, 6.4…
$ DRP.Projected.Inventories.Qty      <dbl> 950, 492, 192, -72, -212, 2055, 182…
$ DRP.plan                           <dbl> 0, 0, 0, 0, 0, 2500, 0, 0, 0, 0, 0,…

3) Table

First, let’s create a function status_PI.Index()

# create a function status.PI.Index
status_PI.Index <- function(color = "#aaa", width = "0.55rem", height = width) {
  span(style = list(
    display = "inline-block",
    marginRight = "0.5rem",
    width = width,
    height = height,
    backgroundColor = color,
    borderRadius = "50%"
  ))
}

Now let’s create a table, using the packages reactable and reactablefmtr :

# set a working df
df1 <- demo_drp

# keep only the needed columns
df1 <- df1 |> select(Period, FH, Demand, DRP.Calculated.Coverage.in.Periods, DRP.Projected.Inventories.Qty, DRP.plan)


# replace missing values by zero
df1$DRP.plan <- df1$DRP.plan |> replace_na(0)
df1$DRP.Projected.Inventories.Qty <- df1$DRP.Projected.Inventories.Qty |> replace_na(0)

# create a f_colorpal field
df1 <- df1 |> mutate(f_colorpal = case_when( DRP.Calculated.Coverage.in.Periods > 8 ~ "#FFA500",
                                              DRP.Calculated.Coverage.in.Periods > 2 ~ "#32CD32",
                                              DRP.Calculated.Coverage.in.Periods > 0 ~ "#FFFF99",
                                              TRUE ~ "#FF0000" ))



# create reactable
reactable(df1, resizable = TRUE, showPageSizeOptions = TRUE,

              striped = TRUE, highlight = TRUE, compact = TRUE,
              defaultPageSize = 20,

              columns = list(
                
                
                FH = colDef(
                  name = "Frozen Horizon",
                  
                  cell = function(value) {
                    color <- switch(
                      value,
                      Free = "hsl(154, 64%, 50%)",
                      Frozen = "hsl(3, 69%, 50%)"
                    )
                    PI.Index <- status_PI.Index(color = color)
                    tagList(PI.Index, value)
                  }),

                Demand = colDef(
                  name = "Demand (units)",

                  cell = data_bars(df1,
                                   fill_color = "#3fc1c9",
                                   text_position = "outside-end"
                  )

                ),

              DRP.Calculated.Coverage.in.Periods = colDef(
                name = "Coverage (Periods)",
                maxWidth = 90,
                cell= color_tiles(df1, color_ref = "f_colorpal")
              ),

              f_colorpal = colDef(show = FALSE), # hidden, just used for the coverages

                `DRP.Projected.Inventories.Qty`= colDef(
                  name = "Projected Inventories (units)",
                  format = colFormat(separators = TRUE, digits=0),

                  style = function(value) {
                    if (value > 0) {
                      color <- "#008000"
                    } else if (value < 0) {
                      color <- "#e00000"
                    } else {
                      color <- "#777"
                    }
                    list(color = color
                         #fontWeight = "bold"
                    )
                  }
                ),

              DRP.plan = colDef(
                name = "Replenishment (units)",
                cell = data_bars(df1,
                                 fill_color = "#3CB371",
                                 text_position = "outside-end"
                                 )
                )

              ), # close columns lits

              columnGroups = list(
                colGroup(name = "Projected Inventories", columns = c("DRP.Calculated.Coverage.in.Periods",
                                                                     "DRP.Projected.Inventories.Qty"))

              )

    ) # close reactable

4) A little chart

We can also visualize the result through a chart, using the library highcharter.

a) Line Chart

# set a working df
df1 <- demo_drp

# Chart
p <- highchart() |> 
  
  hc_add_series(name = "Max", color = "crimson", data = df1$Maximum.Stocks) |> 
  
  hc_add_series(name = "min", color = "lightblue", data = df1$Safety.Stocks) |> 
  
  hc_add_series(name = "Projected Inventories", color = "gold", data = df1$DRP.Projected.Inventories.Qty) |> 
  
  hc_title(text = "(DRP) Projected Inventories") |>
  hc_subtitle(text = "in units") |> 
  hc_xAxis(categories = df1$Period) |> 
  #hc_yAxis(title = list(text = "Sales (units)")) %>% 
  hc_add_theme(hc_theme_google())

p

b) Bar Chart

With Positive & Negative Projected Inventories.

# set a working df
df1 <- demo_drp

# keep only the needed columns
df1 <- df1 |> select(Period, DRP.Projected.Inventories.Qty)

# create a value.index
df1$Value.Index <- if_else(df1$DRP.Projected.Inventories.Qty < 0, "Shortage", "Stock")
    
    
# spread
df1 <- df1 |> spread(Value.Index, DRP.Projected.Inventories.Qty)
    
    
#----------------------------------------------------
# Chart


u <- highchart() |> 
  hc_title(text = "Projected Inventories") |>
  hc_subtitle(text = "in units") |> 
  hc_add_theme(hc_theme_google()) |>
  
  hc_xAxis(categories = df1$Period) |> 
  
  hc_add_series(name = "Stock", 
                color = "#32CD32",
                #dataLabels = list(align = "center", enabled = TRUE),
                data = df1$Stock) |> 
  
  hc_add_series(name = "Shortage", 
                color = "#dc3220",
                #dataLabels = list(align = "center", enabled = TRUE),
                data = df1$Shortage) |> 
  
  hc_chart(type = "column") |> 
  hc_plotOptions(series = list(stacking = "normal"))

u