Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Stochastic Demand Forecasting and Inventory Optimization

Product modelled: 85123A — WHITE HANGING HEART T-LIGHT HOLDER
Dataset: UCI Online Retail Dataset
Language: Python 3 | Libraries: NumPy, pandas, SciPy, Matplotlib


Project Overview

This project builds two competing stochastic demand models for a retail product and uses them to derive optimal inventory stock levels. The core question is: does the assumption you make about how demand arrives change the inventory policy you should adopt?

The answer is yes — and significantly so.


Methodology

Data and Train/Test Split

The dataset covers 374 calendar days (December 2010 – December 2011). After filtering out cancellations and anonymous transactions, product 85123A has demand observations on 305 of those days, with 69 zero-demand days.

The last 60 days are held out as a test set. All model fitting is done on the training period (314 days) only, and the mean forecast is evaluated against the test set.

Split Days Period Mean Demand Std Dev
Train 314 2010-12-01 → 2011-10-10 94.8 units/day 230.2
Test 60 2011-10-11 → 2011-12-09 116.8 units/day 179.5

Step 1 — Stationarity Diagnostics

Before choosing a forecasting model, the time series properties of training demand are assessed using autocorrelation (ACF) and a variance test on differencing.

Diagnostic Result
Level variance 52,820
First-difference variance 106,697
ACF lags 1–7 All < 0.08

Finding: Differencing increases variance, and all autocorrelations are negligible. The series is already stationary white noise. This rules out ARIMA models with differencing (d ≥ 1).

Step 2 — Forecast Benchmark

Because the demand series has no meaningful autocorrelation structure, a mean forecast (ARIMA(0,0,0)) is the appropriate and honest benchmark.

  • Forecast: 94.8 units/day
  • Test MAE: 90.2 units | Test RMSE: 179.4 units
  • 30-day forecast total: 2,845 units

The high MAE and RMSE relative to the mean are expected — the series has extreme spikes (up to 3,114 units on a single day) that no univariate model can predict. The mean forecast is not a good predictor of when spikes occur, but it is an unbiased estimate of expected demand over a longer horizon.

Step 3 — Demand Size Distribution

Non-zero daily demand sizes are fitted to two candidate distributions using Maximum Likelihood Estimation (MLE):

Distribution Log-Likelihood
Lognormal −1,424.8 ✓
Gamma −1,458.9

Lognormal wins. Parameters: shape s = 1.070, scale = 63.1.

The demand size cap is set at the 99th percentile of training data (787 units), replacing the original arbitrary cap of 500.

Step 4 — Trend Test

A linear regression on daily training demand tests whether a time-varying intensity factor is needed.

  • Slope: −0.23 units/day
  • p-value: 0.108

No statistically significant trend. The intensity factor from the original code is dropped.

Step 5 — Model 1: Baseline (Independent Daily Demand)

Each day independently has probability p = 0.806 of a demand event. Event sizes are lognormal. 5,000 Monte Carlo paths are simulated over a 30-day horizon.

Result: Mean 30-day demand = 2,614 units (std = 676)

The simulation is within 8.1% of the mean forecast (2,845), which is a reasonable consistency check.

Step 6 — Model 2: Weibull Duration-Dependent Demand

Inter-arrival times between demand events are fitted to a Weibull distribution via MLE, with bootstrap confidence intervals.

Parameter MLE Estimate 95% Bootstrap CI
Shape k 1.739 [1.385, 3.078]
Scale λ 1.396 [1.287, 1.515]

k > 1 means an increasing hazard rate: the longer since the last demand event, the more likely demand occurs on the next day. Demand events are not independent — they cluster and then correct.

In the Weibull simulation, the daily probability of a demand event is not a fixed constant but a function of how many days have passed since the last event.

Result: Mean 30-day demand = 1,822 units (std = 564)

Why does Weibull produce lower demand than baseline?

Both models imply the same average event rate:

  • Baseline: p_event = 0.806 events/day
  • Weibull: 1/mean_interarrival = 1/1.242 = 0.805 events/day

The divergence is not from different event frequencies — it comes from the simulation dynamics of the duration-dependent process. When k > 1, the Weibull hazard rises steeply after the first day without demand, pulling events closer together in time. This clustering causes periods of dense demand followed by genuine quiet periods, which the 30-day simulation captures differently than the i.i.d. baseline. This is a genuine modelling tension that warrants further investigation.

Step 7 — Inventory Optimisation

Stock levels are optimised by minimising a total cost function:

Total Cost = Holding Cost × S + Shortage Penalty × E[max(D − S, 0)]

where S is the stock level and D is the simulated 30-day demand. Holding cost is normalised to 1. The shortage-to-holding cost ratio is varied across four values to test sensitivity.

Cost Ratio Baseline Stock Baseline SL Weibull Stock Weibull SL
50 4,182 98.0% 3,118 98.0%
100 4,478 99.0% 3,313 99.0%
200 4,637 99.5% 3,502 99.5%
500 4,941 99.8% 3,843 99.8%

Primary result (ratio = 100):

  • Baseline optimal stock: 4,478 units over 30 days (149 units/day)
  • Weibull optimal stock: 3,313 units over 30 days (110 units/day)
  • Both achieve a 99% service level at their respective optimal points

The model assumption alone — independent vs duration-dependent demand — leads to a ~1,165 unit (26%) difference in recommended stock, at the same service level. This is the central finding of the project.


Results and Graphs

Full output

Graph 1 (top): Daily Demand and Mean Forecast

The time series reveals highly intermittent, spike-driven demand with no visible trend or seasonal pattern. Two large spikes (January and May 2011) dominate the training period. The test set (green, October–December 2011) is comparatively stable. The mean forecast (red dashed) sits well below the spike magnitudes but is a reasonable estimate of typical daily demand. The ±1 RMSE band illustrates the high uncertainty inherent in this product's demand.

Graph 2 (middle left): 30-Day Baseline Demand Distribution

The simulated 30-day demand under the baseline model is approximately bell-shaped and centred around 2,500–2,800 units, consistent with the mean forecast. The optimal stock level (4,478, red dashed) sits in the far right tail — above roughly 99% of simulated outcomes — reflecting the high cost penalty assigned to stockouts relative to holding. The mean forecast line (orange dotted, 2,845) falls near the centre of the distribution, confirming the simulation and forecast are consistent.

Graph 3 (middle right): Cost vs Stock Level

The cost curve declines steeply from zero stock (where shortage costs are maximised) and flattens as stock increases past the optimal point. The minimum (red dashed line at 4,478) is well-defined. Beyond this point, additional stock adds holding cost faster than it reduces expected shortage cost. The flat right tail confirms that over-stocking beyond ~5,500 units adds cost with negligible service level improvement.

Graph 4 (bottom left): Demand Distribution Comparison

The Weibull distribution (orange) is shifted left relative to the baseline (blue), centred around 1,800–2,000 units vs 2,500–2,800. Both distributions overlap substantially in the 2,000–3,000 range. Critically, both optimal stock lines (dashed) fall in the right tails of their respective distributions — but the Weibull stock recommendation (3,313) is 26% lower than the baseline (4,478), despite both achieving 99% service level. A supply chain manager using the baseline model would systematically over-stock relative to the Weibull recommendation.

Graph 5 (bottom right): Cost Sensitivity Analysis

The sensitivity plot shows how optimal stock and service level respond to changes in the assumed cost ratio. Both models show stock rising with the ratio, but the gap between them is persistent across all ratios tested (roughly 1,100–1,200 units throughout). Service levels converge to ~100% at high ratios under both models (dashed lines, right axis). The stability of the gap across ratios suggests the finding is robust to the cost assumption — the model choice matters more than the exact cost ratio.


Key Findings

  1. ARIMA(1,1,1) was the wrong model. The demand series is stationary white noise. Differencing destroys signal. A mean forecast is the appropriate benchmark.

  2. Model assumptions drive inventory policy more than cost parameters. The baseline vs Weibull gap (~1,165 units, 26%) is larger and more stable than the variation induced by changing the cost ratio from 50 to 500.

  3. Duration dependence is statistically supported. The Weibull shape parameter k = 1.74 (95% CI: [1.39, 3.08]) is significantly above 1, confirming that demand events are not independent — they exhibit increasing hazard rates after quiet periods.

  4. The source of the Weibull–baseline divergence is not fully resolved. Both models imply the same average event rate (0.805–0.806 events/day), yet simulate materially different 30-day totals. This is a genuine open question in the model that warrants further investigation — potentially through analytical derivation of the expected demand under each process.


Limitations

  • Cost parameters are assumed, not data-derived. The shortage-to-holding ratio is a modelling choice. Sensitivity analysis is provided but real-world calibration would require firm-specific cost data.
  • Single product. Results may not generalise across SKUs with different demand patterns (e.g., seasonal, trend-driven, or highly intermittent products).
  • No external covariates. Promotions, seasonality, and holidays are not modelled, despite visible spikes in the demand series that are likely promotion-driven.
  • Point forecast only. The mean forecast does not provide prediction intervals. A proper probabilistic forecast (e.g., negative binomial regression) would be more appropriate for inventory planning.

Files

File Description
demand_model.py Full model code
sales_data.csv UCI Online Retail Dataset (not included — download from UCI)
demand_model.png Output plots
README.md This file

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages