Forecasting and Uncertainty
This page collects the forecasting and uncertainty tutorials into a single, repeatable sequence.
Table of contents
Explore Demand Data and Define the Forecast Grain
Start by looking at the demand pattern at two grains.
- Start from a session with the 1-echelon 2017 dataset.
- Paste the script and run it.
- You should now see daily and weekly demand line charts.
read "/sample/Lokad_Orders.tsv.gz" as Orders expect [date] with
"Date" as date : date
Quantity : number
Day.Demand = sum(Orders.Quantity)
Week.Demand = sum(Day.Demand)
show linechart "Daily demand" with
Day.Demand
show linechart "Weekly demand" with
Week.Demand
You should now have a quick sense of which grain is smoother and easier to forecast.
Fit First Point Forecast Model with Autodiff
Fit a small trend model to a short demand series.
- Paste the script and run it.
- You should now see the fitted base and slope plus a table of the trend values.
- Adjust one demand value and re-run to see the fit change.
table History[date] = with
[| as date, as Demand |]
[| date(2024, 1, 1), 120 |]
[| date(2024, 1, 2), 118 |]
[| date(2024, 1, 3), 125 |]
[| date(2024, 1, 4), 130 |]
[| date(2024, 1, 5), 128 |]
[| date(2024, 1, 6), 132 |]
History.Index = rank() scan date
autodiff History epochs:40 learningRate:0.05 with
params base auto
params slope auto
Pred = base + slope * History.Index
err = Pred - History.Demand
return (err^2, mae: abs(err))
History.Trend = base + slope * History.Index
show summary "Fitted trend" with
base
slope
show table "Demand vs trend" with
History.date
History.Demand
History.Trend
You should now have a first point-forecast model in place.
Turn a Point Forecast into a Quantile Forecast
Convert a point forecast into a set of quantiles you can act on.
- Paste the script and run it.
- You should now see a table with P10, P50, and P90 bands.
- Increase the dispersion to widen the bands.
start = date(2024, 3, 1)
keep span date = [start .. start + 13]
Day.t = date - start
Day.Point = 120 + 2 * Day.t
Day.Dist = negativeBinomial(Day.Point, 2)
Day.P10 = quantile(Day.Dist, 0.10)
Day.P50 = quantile(Day.Dist, 0.50)
Day.P90 = quantile(Day.Dist, 0.90)
show table "Quantile forecast" with
date
Day.Point
Day.P10
Day.P50
Day.P90
You should now see how uncertainty bands wrap the point forecast.
Simulate Demand Paths with Monte Carlo
Use Monte Carlo sampling to capture variability in demand.
- Paste the script and run it.
- You should now see median and P90 demand for each day index.
- Increase the number of simulations and compare the resulting P90 values.
table Horizon = extend.range(14)
Horizon.Day = Horizon.N
montecarlo 250 with
Horizon.Demand = random.poisson(20 into Horizon)
sample Horizon.Dist = ranvar(Horizon.Demand)
Horizon.P50 = quantile(Horizon.Dist, 0.50)
Horizon.P90 = quantile(Horizon.Dist, 0.90)
show table "Monte Carlo demand" with
Horizon.Day
Horizon.P50
Horizon.P90
You should now have a simple distribution view of demand paths.
Compute Demand Over Lead Time
Combine variable demand and variable lead time into a single lead-time demand distribution.
- Paste the script and run it.
- You should now see a lead-time demand P50 and P95 per SKU.
- Change the daily demand or lead time means to see the distribution shift.
table Items[id] = with
[| as id, as DailyMean, as LeadTimeMean |]
[| "A-100", 5, 7 |]
[| "B-200", 8, 12 |]
[| "C-300", 3, 5 |]
table Days max 1000 = extend.range(Items.LeadTimeMean * 4)
montecarlo 500 with
Items.LeadTimeSample = 1 + random.poisson(Items.LeadTimeMean into Items)
Days.DailySample =
if Days.N <= Items.LeadTimeSample then
random.poisson(Items.DailyMean into Days)
else
0
Items.LeadTimeDemand = sum(Days.DailySample)
sample Items.LeadTimeDist = ranvar(Items.LeadTimeDemand)
Items.LeadTimeP50 = quantile(Items.LeadTimeDist, 0.50)
Items.LeadTimeP95 = quantile(Items.LeadTimeDist, 0.95)
show table "Lead-time demand" with
Items.id
Items.LeadTimeMean
Items.LeadTimeP50
Items.LeadTimeP95
You should now have a lead-time demand distribution that accounts for both sources of variability.
The 250-path P90 and 500-path P95 examples provide about 25 expected paths in their respective upper tails. For production use, follow the Monte Carlo sample-count guide and validate convergence on the downstream decision.
Visualize Forecast Uncertainty for Decision-Making
Build a forecast view with a median line and two nested demand intervals. For this visualization exercise, we will choose an illustrative daily demand pattern and a spread that increases over 21 days. These are inputs to the example, not estimates fitted from the earlier data.
Replace the script with the block below and run it. The region holds a heading, the chart, and a footer explaining how to read the intervals.
start = date(2024, 4, 1)
keep span date = [start .. start + 20]
Day.t = date - start
Day.Point = 90 + 1.5 * Day.t + 12 * sin(Day.t / 3)
Day.Sigma = 6 + 0.5 * Day.t
Day.Dist = normal(Day.Point, Day.Sigma)
Day.P10 = quantile(Day.Dist, 0.10)
Day.P25 = quantile(Day.Dist, 0.25)
Day.P50 = quantile(Day.Dist, 0.50)
Day.P75 = quantile(Day.Dist, 0.75)
Day.P90 = quantile(Day.Dist, 0.90)
forecast = show region { 1..8, 1..29;
tileBackground: "#F8FAFC!";
header { blockColor: "#17324D!"; tileBackground: "#EAF1F6!" }
} with
header "Demand outlook | 21 days"
footer "Intervals describe each day separately, not an entire demand path."
show label "A range of possible demand" { .., 1..3 in forecast;
size: 1; textAlign: left; textColor: "#17324D!"
}
show linechart "Forecast bands | daily demand" { .., 5..23 in forecast;
legend { legendPosition: bottom };
haxis { value { dateFormat: "MMM d" } };
vaxis { gridlineColor: "#CBD5E1!"; left { axisMin: 50; axisMax: 160 } }
} with
Day.P10 as "80% band" {
seriesType: area; areaTo: #[Day.P90]; color: "#4F86C6!"; seriesOpacity: 0.18;
seriesSmooth: linear; unit: " units"; seriesLegendRank: 3
}
Day.P25 as "50% band" {
seriesType: area; areaTo: #[Day.P75]; color: "#4F86C6!"; seriesOpacity: 0.35;
seriesSmooth: linear; unit: " units"; seriesLegendRank: 2
}
Day.P50 as "Median" { color: "#17324D!"; seriesSmooth: linear; unit: " units"; seriesLegendRank: 1 }
Follow the dark median line from April 1 to April 21. It rises, dips, and rises again with the chosen demand pattern. Hover over its first and last dates: the median is 90 units on April 1 and 124 units on April 21.
The darker band spans P25 to P75, an interval containing approximately 50% of the demand distribution for each day. The full lighter band spans P10 to P90, containing approximately 80%. Both percentages describe the distribution chosen for this example; they do not establish the accuracy of a fitted model. The intervals describe each day separately: they do not say that an entire 21-day demand path will stay inside a band with that probability.
Look at the two area series in the script. Each starts at its lower quantile,
and areaTo supplies its upper quantile. For example, Day.P10 together with
areaTo: #[Day.P90] fills the space between P10 and P90. Drawing the wider,
paler interval first keeps the narrower interval and median visible. In the
hover readout, each area series reports its lower boundary; its shaded extent
shows the interval.
Now change Day.Sigma = 6 + 0.5 * Day.t to Day.Sigma = 6 + 1 * Day.t and run
again. The first day’s spread stays the same, while the later bands widen.
The demand pattern in Day.Point is unchanged. This widening comes from our
explicit spread assumption; a forecast need not become more uncertain at
every future date. Restore 0.5 to finish.
For practice with line patterns, delivery bars, axes, and legends, see Styling Dashboards. The linechart reference covers its styling scopes, and quantile defines the quantile operation used to construct the intervals.