6. Inventory 1: Working, Economic, and One-Time Safety Stock¶
ISE 754, Fall 2024
Package Used: No new packages used.
One-Time Safety Stock¶
Ex: Lee Pharm's Bakery¶
Lee Pharm owns a bakery and has to decide each morning how many loaves of bread to bake for the day. Any loaves unsold at the end of the day are donated to the local homeless shelter. He would like some help with how to best decide the number of loaves to bake each day. He has available the number of loaves sold for each of the last 13 days:
216, 214, 161, 146, 216, 159, 216, 216, 139, 216, 152, 98, and 116.
In looking at the data, the first thing noticeable is that the number 216 appears quite often. After talking with Lee, it turns out that he typically makes 216 loaves per day, which corresponds to 18 trays of a dozen loaves. Since there was likely unmet demand any day that 216 loaves were sold, after checking with Lee, it turns out that he also has been recording the number of customers asking for bread each day after it was sold out. After adding in this unmet demand, the adjusted potential demand is:
237, 214, 161, 146, 331, 159, 423, 332, 139, 327, 152, 98, and 116.
After confirming with Lee that these past 13 days of demand are reasonably representative of typical demand, the only other information needed for you to provide some guidance is the average selling price of each loaf and the cost to bake each loaf, which turns out to be \$5 and \$1, respectively. Also, Lee agrees that it is reasonable to assume that any demand for a day that is not filled is lost; this is because most customers consume their bread the day of purchase since it is made without preservatives.
Step 1: Display the data¶
It is a good idea to look at the data to see if there is a pattern to the data and check to see if there are any anomalous, outlier data that could be erroneous. If anything is found, it might be possible to ask whoever provided the data about the issue before doing any further analysis. If some data is in error, then it can be deleted; outlier data should not automatically be eliminated unless it is a real error. A line plot is used because the 13 days of demand represents a time series.
using CairoMakie
d = [237, 214, 161, 146, 331, 159, 423, 332, 139, 327, 152, 98, 116]
lines(d)
Don't see any trend or seasonality that would need to be accounted for in any model of the data. Most importantly, the variability does not show a long-term pattern and so will assume the data is stationary. There are statistical tests for startionarity, but these would not typically be used for so few data points. Since stationarity implies the lack of trend/seasonality, the data can be randomly re-arranged (or permuted), and it should look about the same. We can do this to verify stationarity:
using Random
Random.seed!(2523)
lines(d[randperm(length(d))])
lines(d[randperm(length(d))])
The two plots of a random permutation of the demands also look stationary, lacking trend/seasonality.
Step 2: Determine profit as a function of loaves baked and daily demand¶
The following determines the daily operating profit if $q$ loaves are baked that morning (i.e., the production rate) and demand turns out to be $d_i$ that day:
$ \quad \begin{eqnarray*} \mbox{Profit: } \pi(q,d_i) &=& \begin{cases} pd_i - cq, & \mbox{if } q > d_i \\ pq - cq = (p - c)\,q, & \mbox{otherwise } \end{cases} \\ &=& p \min \bigl\{ q,d_i \bigr\} - cq \end{eqnarray*}$
p = 5 # Unit price
c = 1 # Unit cost
fπ(q, di) = p*min(q, di) - c*q # Profit per day (fπ ⟹ f\pi<TAB>)
fΠ(q, d) = sum(fπ(q, di) for di in d) # Total profit (fΠ ⟹ f\Pi<TAB>)
q = 0:500
lines(q, q -> fΠ(q, d), axis=(xlabel="Production Rate", ylabel="Total Profit"))
Step 3. Establish a baseline and upper bound on total profits¶
In order to be able to compare the effectiveness of any procedure, a baseline and upper bound on total profit can be determined. The baseline is the total profit associated with using the current practice of baking 216 loaves per day. An upper bound can be determined by assuming that the number of loaves baked each day exactly matches that day's demand (perfect prediction).
Πᴮᴸ = fΠ(216, d)
Πᵁᴮ = (p - c)*sum(d)
Πᵁᴮ = sum(fπ(di, di) for di in d)
Πᴮᴸ, Πᵁᴮ, Πᴮᴸ/Πᵁᴮ
(8517, 11340, 0.7510582010582011)
Step 4. Determine number of loaves that maximize total profits¶
Since the demand data is assumed stationary, will not try to predict each day's demand; instead, we will assume historical demand data is representative of the variability of any day's demand and will, as a result, use all of the data to determine the same (optimal) number of loaves to bake each day. Note: using 500 as UB for search since it is likely that daily demand might exceed the maximum (423) found using just the 13 days of data.
using Optim
dcf() = display(current_figure())
qᵒ = optimize(q -> -fΠ(q,d), 0, 500).minimizer
scatter!([qᵒ],[fΠ(qᵒ,d)])
dcf()
qᵒ, fΠ(qᵒ,d), fΠ(qᵒ,d)/Πᵁᴮ, fΠ(qᵒ,d)/Πᴮᴸ
(330.9999976747946, 9406.999995349586, 0.8295414457980235, 1.104496888029774)
The solution recommends that Lee bake 331 loaves each morning, which should result in an over 10% increase in total profits as compared to baking only 216 loaves. The procedure works because the data is stationary, lacking trend/seasonality. If the data has a trend/seasonality, techniques exist that can try to remove the trend/seasonality so that all of the data can be used; otherwise, for non-stationary data, you would use only the most recent data in any estimate. If, for example, sales have significantly increased in the past week and are expected to remain at this higher level for at least the next few days, then the nonlinear optimization could be rerun using just the last week's data.
Mean and Median: By way of comparison, what the mean or median values for demand were used to determine the number of loaves to bake each morning:
using Statistics
mean(d), fΠ(mean(d),d), median(d), fΠ(median(d),d)
(218.07692307692307, 8541.923076923078, 161.0, 7592.0)
After checking with Lee, the fact that the mean value of 218 is close to the 216 loaf value he was using is no accident: it turns out that he averaged an earlier set of historical demand values to determine the 216 value.
Linear Regression¶
Lee is quite pleased with the prospect of a 10% increase in profits and happens to mention that a large number of his customers preorder their purchases and that the number of orders that he has received each morning seems positively correlated with the number of sales he sees that day. He provides the number of orders available each morning for the past 13 days:
121, 143, 63, 80, 198, 52, 160, 162, 85, 106, 129, 141, and 106
During each day, additional walk-in demand occurs, and some orders are canceled; as a result, the final demand at the end of the day differs from the amount ordered at the start of the day. Nevertheless, it is thought that using each day's preorders to help determine the number of loaves to bake that day can increase total profits.
d = [237, 214, 161, 146, 331, 159, 423, 332, 139, 327, 152, 98, 116]
o = [121, 143, 63, 80, 198, 52, 160, 162, 85, 106, 129, 141, 106]
lines(o, label="Orders")
lines!(d, label="Demand")
axislegend()
dcf();
scatter(o, d, axis=(xlabel="Orders", ylabel="Demand"))
The correlation of the two time series can be calculated to see how likely the order data may help in estimating demand:
using Statistics
cor(o, d)
0.5994685344117657
An almost 60% correlation is promising, and so it is likely to be beneficial to include the order data in the analysis. This can be done through linear regression by making the demand a function of order data. Previously, profit was maximized using just the prior demand data. One can think of the demand estimates used previously as single parameter estimation models; linear regression can be used to provide a two-parameter estimation model.
Previousely:
fπ(q, di) = p*min(q, di) - c*q # Profit per day
fΠ(q, d) = sum([fπ(q, di) for di in d]); # Total profit
The total profit function needs to change since a different size ($q_i$) is associated with each different demand ($d_i$). In the single-parameter models considered previously, the same size ($q$) was associated with each different demand ($d_i$).
fΠ(q, d) = sum(fπ(qi, di) for (qi,di) in zip(q, d)); # Total profit (using ZIP function)
Profit using just order data: What if just the number of orders received by each morning were used to determine the number of loaves to bake that day:
fΠ(o,d)
5969
Linear regression using orders and profit maximization as objective:
fd̂(α, o) = α[1] .+ α[2]o
αΠᵒ = optimize(α -> -fΠ(fd̂(α, o),d), [0. 1.]).minimizer
println(αΠᵒ)
lines!([0, maximum(o)], [fd̂(αΠᵒ, 0), fd̂(αΠᵒ, maximum(o))])
dcf()
fΠ(fd̂(αΠᵒ, o), d), fΠ(fd̂(αΠᵒ, o), d)/Πᵁᴮ, fΠ(fd̂(αΠᵒ, o), d)/Πᴮᴸ
[77.21818171600432 1.5727272746809993]
(9853.636363517973, 0.8689273689169289, 1.1569374619605464)
Question: How would the number of loaves to bake for day 14 be determined if 120 loaves have been ordered by the morning of day 14:
o14 = 120
q14 = fd̂(αΠᵒ, o14)
265.94545467772423
Multiple Regression¶
After further discussion, Lee mentions that he also can access each morning the number of unique visitors to his bakery's website over the past day. We can see if this additinal data can help:
v = [69, 72, 56, 72, 82, 80, 72, 64, 50, 65, 49, 51, 66] # Unique visitors
scatter(v, d, axis=(xlabel="visitors", ylabel="Demand"))
cor(v, d)
0.46082474901378706
A 46% correlation indicates a moderate relationship that may help. In order to effectively use the visitor data, it can be added to the model along with the order data in a multiple regression:
fd̂ᴹ(α, o, v) = α[1] .+ α[2]o .+ α[3]v
αΠᴹᵒ = optimize(α -> -fΠ(fd̂ᴹ(α, o, v), d), [0. 1. 1.]).minimizer
println(αΠᴹᵒ)
fΠ(fd̂ᴹ(αΠᴹᵒ, o, v), d)
[0.015104509015262729 1.6444684204385456 1.0247033027779333]
9902.722869055702
Question: How would the number of loaves to bake for day 14 be determined if 120 loaves have been ordered and there have been 45 unique visitors to the website by the morning of day 14:
o14, v14 = 120, 45
q14 = fd̂ᴹ(αΠᴹᵒ, o14, v14)
243.46296358664773
| Procedure | Total Profit |
|---|---|
| Baseline | 8517 |
| Single parameter mean (demand) | 8542 |
| Single parameter median (demand) | 7592 |
| Single parameter max profit (demand) | 9407 |
| Using just order data (orders) | 5969 |
| Linear regression (demand; orders) | 9854 |
| Multiple regression (demand; orders, website visitors) | 9902 |
| Prefect prediction (UB) | 11,340 |