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
Nahmias & Olsen: Production and Operations Analysis, 7th edition