• 1) Create Data Template
  • 2) DRP Calculation
  • 3) Table
  • 4) A little chart
    • a) Line Chart
    • b) Bar Chart

First, let’s load a few libraries :

# ETL
library(tidyverse)
library(scales)

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

# for the charts
library(highcharter)

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

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 :

  • a Product: it’s an item, a SKU (Storage Keeping Unit), or a SKU at a location, also called a DFU (Demand Forecast Unit)

  • a Period of time : for example monthly or weekly buckets

  • a Demand : could be some sales forecasts, expressed in units

  • an Opening Inventory : what we hold as available inventories at the beginning of the horizon, expressed in units

  • a Supply Plan : the supplies that we plan to receive, expressed in units

And a few new parameters added to this initial dataset :

  • SSCov : the Safety Stock Coverage, expressed in number of periods

  • DRPCovDur : the Frequency of Supply, expressed in number of periods

  • MOQ : the Multiple Order Quantity, expressed in units, 1 by default or a multiple of a Minimum Order Quantity

  • FH : defines the Frozen and Free Horizon. It has 2 values: Frozen or Free. If Frozen : no calculation of Replenishment Plan yet, the calculation starts when the period is defined as Free. We can use this parameter to consider some defined productions plans or supplies (allocations, workorders,…) in the short-term for example.

We are going to :

  • apply the drp() on this simple template

  • create 2 nice visuals of tables and charts, using the R packages reactable, reactablefmtr and highcharter.

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)
# calculate
demo_drp <- planr::drp(data = my_drp_template,
                       DFU = DFU,
                       Period = Period,
                       Demand =  Demand,
                       Opening = Opening,
                       Supply = Supply,
                       SSCov = SSCov,
                       DRPCovDur = DRPCovDur,
                       MOQ = MOQ,
                       FH = FH)
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
Projected Inventories
Period
Frozen Horizon
Demand (units)
Coverage (Periods)
Projected Inventories (units)
Replenishment (units)
2020-01-01
Frozen
360
2.7
950
0
2020-02-01
Frozen
458
1.7
492
0
2020-03-01
Frozen
300
0.7
192
0
2020-04-01
Frozen
264
-0.5
-72
0
2020-05-01
Frozen
140
-0.9
-212
0
2020-06-01
Frozen
233
7.4
2,055
2500
2020-07-01
Free
229
6.4
1,826
0
2020-08-01
Free
208
5.4
1,618
0
2020-09-01
Free
260
4.4
1,358
0
2020-10-01
Free
336
3.4
1,022
0
2020-11-01
Free
295
2.4
727
0
2020-12-01
Free
226
5
1,402
901
2021-01-01
Free
336
4
1,066
0
2021-02-01
Free
434
3
632
0
2021-03-01
Free
276
5
953
597
2021-04-01
Free
240
4
713
0
2021-05-01
Free
116
3
597
0
2021-06-01
Free
209
5
1,205
817
2021-07-01
Free
205
4
1,000
0
2021-08-01
Free
183
3
817
0
1–20 of 24 rows
Show

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
Created with Highcharts 9.3.1(DRP) Projected Inventoriesin unitsMaxminProjected Inventories2020-01-012020-02-012020-03-012020-04-012020-05-012020-06-012020-07-012020-08-012020-09-012020-10-012020-11-012020-12-012021-01-012021-02-012021-03-012021-04-012021-05-012021-06-012021-07-012021-08-012021-09-012021-10-012021-11-012021-12-01-50005001000150020002500

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  
Created with Highcharts 9.3.1Projected Inventoriesin unitsStockShortage2020-01-012020-02-012020-03-012020-04-012020-05-012020-06-012020-07-012020-08-012020-09-012020-10-012020-11-012020-12-012021-01-012021-02-012021-03-012021-04-012021-05-012021-06-012021-07-012021-08-012021-09-012021-10-012021-11-012021-12-01-50005001000150020002500

Useful links :

The R package planr provides some tools for Supply Chain managementto perform calculations related to Demand & Supply Planning or S&OP (Sales & Operations Planning) process.