This notebook illustrates the usage of NewsvendorModel.jl with examples from the excellent textbook by Nahmias & Olsen 7th or 8th edition.

These notes are not self-contained but should be considered as a companion to the above text book.

You can load this notebook in Pluto from here: https://github.com/frankhuettner/NewsvendorModel.jl/blob/main/docs/src/textbook/nahmias%2Bolsen.jl

# Load the needed packages
using NewsvendorModel, Distributions,  StatsBase, Plots

Mac Magazine Store

Unit values:

  • cost per unit ordered = 75

  • selling price per unit = 25

  • salvage value = 10

The following is known about the demand distribution (empirical from previous data)

Demand and distribution fitting

# store the given data in the variable `observed_demand`
observed_demand = vec([15 19 9 12 9 22 4 7 8 11 14 11 6 11 9 18 10 0 14 12 8 9 5 4 4 17 18 14 15 8 6 7 12 15 15 19 9 10 9 16 8 11 11 18 15 17 19 14 14 17 13 12]);
D̄ = mean(observed_demand)
11.73076923076923
s = std(observed_demand)
4.740792457192357
begin
    h1 = fit(Histogram,observed_demand, nbins=10)

    plot(h1, ylabel="Observed frequency", xlabel = "Number of sales", legend=nothing)
    
    d = Normal(D̄, s)
    lo, hi = quantile.(d, [0.01, 0.99])
    x = range(lo, hi; length = 100)
    plot(twinx(), x, pdf.(d, x), legend=nothing, color = :red, yticks = false)
end

An alternative is to use MLE:

fit(Normal, observed_demand)
Normal{Float64}(μ=11.73076923076923, σ=4.694986624931324)

Newsvendor Model

# Define the model
mac = NVModel(cost = 25, price = 75, salvage = 10, demand = Normal(D̄, s)  )
Data of the Newsvendor Model
 * Demand distribution: Normal{Float64}(μ=11.73076923076923, σ=4.740792457192357)
 * Unit cost: 25.00
 * Unit selling price: 75.00
 * Unit salvage value: 10.00
# Solve the model
solve(mac)
=====================================
Results of maximizing expected profit
 * Optimal quantity: 15 units
 * Expected profit: 492.69
=====================================
This is a consequence of
 * Cost of underage:  50.00
   ╚ + Price:               75.00
   ╚ - Cost:                25.00
 * Cost of overage:   15.00
   ╚ + Cost:                25.00
   ╚ - Salvage value:       10.00
 * Critical fractile: 0.77
 * Rounded to closest integer: true
-------------------------------------
Ordering the optimal quantity yields
 * Expected sales: 11.04 units
 * Expected lost sales: 0.69 units
 * Expected leftover: 3.96 units
 * Expected salvage revenue: 39.59
-------------------------------------

Discrete Demand

h = fit(Histogram, observed_demand, nbins =22)
Histogram{Int64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}
edges:
  0.0:1.0:23.0
weights: [1, 0, 0, 0, 3, 1, 2, 2, 4, 6  …  1, 5, 5, 1, 3, 3, 3, 0, 0, 1]
closed: left
isdensity: false
epd = h.weights / 52
23-element Vector{Float64}:
 0.019230769230769232
 0.0
 0.0
 0.0
 0.057692307692307696
 0.019230769230769232
 0.038461538461538464
 ⋮
 0.057692307692307696
 0.057692307692307696
 0.057692307692307696
 0.0
 0.0
 0.019230769230769232
mac_discrete_demand = DiscreteNonParametric(0:22, epd)
DiscreteNonParametric{Int64, Float64, UnitRange{Int64}, Vector{Float64}}(
support: 0:22
p: [0.019230769230769232, 0.0, 0.0, 0.0, 0.057692307692307696, 0.019230769230769232, 0.038461538461538464, 0.038461538461538464, 0.07692307692307693, 0.11538461538461539  …  0.019230769230769232, 0.09615384615384616, 0.09615384615384616, 0.019230769230769232, 0.057692307692307696, 0.057692307692307696, 0.057692307692307696, 0.0, 0.0, 0.019230769230769232]
)
mac_discrete = NVModel(cost = 25, price = 75, salvage = 10, 
                        demand = mac_discrete_demand  )
Data of the Newsvendor Model
 * Demand distribution: DiscreteNonParametric{Int64, Float64, UnitRange{Int64}, Vector{Float64}}(
support: 0:22
p: [0.019230769230769232, 0.0, 0.0, 0.0, 0.057692307692307696, 0.019230769230769232, 0.038461538461538464, 0.038461538461538464, 0.07692307692307693, 0.11538461538461539  …  0.019230769230769232, 0.09615384615384616, 0.09615384615384616, 0.019230769230769232, 0.057692307692307696, 0.057692307692307696, 0.057692307692307696, 0.0, 0.0, 0.019230769230769232]
)

 * Unit cost: 25.00
 * Unit selling price: 75.00
 * Unit salvage value: 10.00

This yields the following critical fractile for the 1st stage:

solve(mac_discrete)
=====================================
Results of maximizing expected profit
 * Optimal quantity: 15 units
 * Expected profit: 493.75
=====================================
This is a consequence of
 * Cost of underage:  50.00
   ╚ + Price:               75.00
   ╚ - Cost:                25.00
 * Cost of overage:   15.00
   ╚ + Cost:                25.00
   ╚ - Salvage value:       10.00
 * Critical fractile: 0.77
 * Rounded to closest integer: true
-------------------------------------
Ordering the optimal quantity yields
 * Expected sales: 11.06 units
 * Expected lost sales: 0.67 units
 * Expected leftover: 3.94 units
 * Expected salvage revenue: 39.42
-------------------------------------

Some exercises

The book offers exercises with solutions. Here is a small selection that illustrates the convinience of Distributions.jl and NewsvendorModel.jl (everything is very brief and a detailed explanation can be found in the book).

Mac's thesaurus

c = 1.15
1.15
p = 2.75
2.75
thesaurus_nvm = NVModel(cost = c, 
                        price = p, 
                        demand = truncated(Normal(18, 6), 0, Inf),
                        backorder = 0.5,
                        substitute = p - c,
                        salvage = c,
                        holding = c * 0.2 / 12,
)
Data of the Newsvendor Model
 * Demand distribution: Truncated(Normal{Float64}(μ=18.0, σ=6.0); lower=0.0)
 * Unit cost: 1.15
 * Unit selling price: 2.75
 * Unit salvage value: 1.15
 * Unit holding cost: 0.02
 * Unit backorder penalty: 0.50
 * Unit substitute profit: 1.60
solve(thesaurus_nvm)
=====================================
Results of maximizing expected profit
 * Optimal quantity: 29 units
 * Expected profit: 28.59
=====================================
This is a consequence of
 * Cost of underage:  0.50
   ╚ + Price:               2.75
   ╚ - Cost:                1.15
   ╚ + Backorder penalty:   0.50
   ╚ - Substitute profit:   1.60
 * Cost of overage:   0.02
   ╚ + Cost:                1.15
   ╚ - Salvage value:       1.15
   ╚ + Holding cost:        0.02
 * Critical fractile: 0.96
 * Rounded to closest integer: true
-------------------------------------
Ordering the optimal quantity yields
 * Expected sales: 17.95 units
 * Expected lost sales: 0.08 units
 * Expected leftover: 11.05 units
 * Expected salvage revenue: 12.71
 * Expected backorder penalty: 0.04
 * Expected holding cost: 0.21
 * Expected substitute profit: 0.13
-------------------------------------
thesaurus_with_competition_nvm = NVModel(cost = c, 
                                            price = p, 
                                            demand = truncated(Normal(18, 6), 0, Inf),
                                            backorder = 0.5,
                                            # substitute = p - c,
                                            salvage = c,
                                            holding = c * 0.2 / 12,
                                )
Data of the Newsvendor Model
 * Demand distribution: Truncated(Normal{Float64}(μ=18.0, σ=6.0); lower=0.0)
 * Unit cost: 1.15
 * Unit selling price: 2.75
 * Unit salvage value: 1.15
 * Unit holding cost: 0.02
 * Unit backorder penalty: 0.50
solve(thesaurus_with_competition_nvm)
=====================================
Results of maximizing expected profit
 * Optimal quantity: 32 units
 * Expected profit: 28.53
=====================================
This is a consequence of
 * Cost of underage:  2.10
   ╚ + Price:               2.75
   ╚ - Cost:                1.15
   ╚ + Backorder penalty:   0.50
 * Cost of overage:   0.02
   ╚ + Cost:                1.15
   ╚ - Salvage value:       1.15
   ╚ + Holding cost:        0.02
 * Critical fractile: 0.99
 * Rounded to closest integer: true
-------------------------------------
Ordering the optimal quantity yields
 * Expected sales: 18.01 units
 * Expected lost sales: 0.02 units
 * Expected leftover: 13.99 units
 * Expected salvage revenue: 16.09
 * Expected backorder penalty: 0.01
 * Expected holding cost: 0.27
-------------------------------------

References