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.

  1. Start from a session with the 1-echelon 2017 dataset.
  2. Paste the script and run it.
  3. 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.

  1. Paste the script and run it.
  2. You should now see the fitted base and slope plus a table of the trend values.
  3. 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.

  1. Paste the script and run it.
  2. You should now see a table with P10, P50, and P90 bands.
  3. 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.

  1. Paste the script and run it.
  2. You should now see median and P90 demand for each day index.
  3. 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.

  1. Paste the script and run it.
  2. You should now see a lead-time demand P50 and P95 per SKU.
  3. 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.

User Contributed Notes
0 notes + add a note