Measuring Forecast Error After a Store Opens
This page solves one exact task: taking the sales forecast a site was given before approval, comparing it against what the store achieved once mature, and decomposing the difference into the parts of the model that produced it.
The decomposition is what makes this worth doing. Knowing a forecast missed by eighteen per cent is mildly interesting; knowing that the catchment population was right, the capture rate was right and the basket assumption was wrong tells the modelling team exactly what to fix and tells the committee that the site was not misjudged.
Prerequisites
- The archived forecast with its components: reachable population, expected capture rate, expected basket or spend per visit, and the assumptions applied.
- Realized performance at maturity, per backtesting site scores.
- Observed catchment evidence — a mobility panel or loyalty origins — so the catchment component can be measured rather than inferred.
- Python packages:
pandas,numpy. Install withpip install pandas numpy.
Configuration and execution parameters
| Parameter | Value for this task | Notes |
|---|---|---|
maturity_months |
18 | Same definition as the backtest |
components |
catchment, capture, basket, market | Multiplicative decomposition |
market_index |
applied | Separates the model’s error from the economy’s |
attribution_order |
fixed, documented | Order changes the split; pick one and keep it |
report_sign |
over or under, kept separate | Systematic bias hides in an absolute mean |
review_threshold |
±20% | Beyond this, a written post-mortem |
feedback |
to the model card, quarterly | Errors that do not reach the model are lost |
Keeping the sign is more important than it sounds. A model that misses by fifteen per cent in both directions is noisy; one that misses by fifteen per cent upward on every store is systematically optimistic, and the two demand completely different responses. Reporting mean absolute error alone makes them indistinguishable.
Annotated implementation
from __future__ import annotations
import numpy as np
import pandas as pd
COMPONENTS = ["catchment", "capture", "basket", "market"]
def decompose(forecast: pd.Series, observed: pd.Series) -> pd.Series:
"""Multiplicative decomposition, applied in a FIXED order.
Sales = catchment × capture × basket × market. Swapping one factor at a
time from forecast to observed attributes the change to that factor; the
order matters when factors interact, so it is fixed and documented rather
than chosen per store."""
values = forecast[COMPONENTS].astype(float).to_numpy()
target = observed[COMPONENTS].astype(float).to_numpy()
contributions = {}
running = values.copy()
baseline = float(np.prod(running))
for i, name in enumerate(COMPONENTS):
running[i] = target[i]
after = float(np.prod(running))
contributions[name] = (after - baseline) / float(np.prod(values))
baseline = after
contributions["total"] = (float(np.prod(target)) - float(np.prod(values))) / float(np.prod(values))
return pd.Series(contributions).round(4)
def portfolio_error(store_errors: pd.DataFrame) -> pd.DataFrame:
"""Bias and dispersion, per format — both, and never only the absolute."""
return (store_errors.groupby("format")
.agg(stores=("total", "size"),
mean_signed=("total", "mean"),
median_signed=("total", "median"),
mean_absolute=("total", lambda s: float(s.abs().mean())),
within_10pct=("total", lambda s: float((s.abs() < 0.10).mean())))
.round(3).reset_index())
The fixed attribution order deserves the comment it gets in the code. When factors interact, the amount attributed to each depends on the order in which they are swapped, so a team that varies the order — or lets each analyst choose — will produce decompositions that cannot be compared across stores.
Failure modes and debugging
Comparing against a forecast that was revised. Sites are often re-forecast during negotiation, and comparing against the final, lower number flatters the model. Archiving the forecast at the point of the investment decision, and comparing against that one, is the only version that answers the question the committee asked.
Attributing market movement to the model. A store that opened into a downturn missed its forecast for reasons no site model could have known, and correcting the model for it teaches it to distrust perfectly good sites. The market index is what separates the two, and it should be a published index rather than the chain’s own performance, which is partly caused by the same decisions.
Only reviewing the misses. Post-mortems triggered by underperformance produce a systematically biased learning set: the model is corrected downward every time it is optimistic and never upward when it is pessimistic. Reviewing the largest errors in both directions keeps the correction balanced.
Feeding every error straight back into the weights. A single store’s miss is one observation, and adjusting weights in response is how a model acquires a coefficient for last quarter’s disappointment. Errors accumulate into the validation record and inform the scheduled refit; they do not trigger one.
Verification
- Confirm the forecast archive predates the decision. A forecast recorded after approval is not a forecast.
- Check the decomposition multiplies back to the total error within rounding. If it does not, a component is missing or double-counted.
- Compare the observed catchment against the modelled one directly, rather than inferring it from the residual. Where a panel exists, this converts an assumption into a measurement.
- Test the bias detection by simulating a cohort with a known systematic error and confirming the report identifies it.
Frequently Asked Questions
How many openings are needed before the error report means anything?
Around twenty for a signed-bias conclusion and considerably more for anything per format. With fewer, the honest report is the individual decompositions rather than a statistic — five stores with detailed attributions are more useful to a modelling team than a mean computed from five numbers. Estates that open a handful of stores a year should expect this to be a rolling three-year view rather than an annual one.
Should the review include sites that were rejected?
Where they were subsequently developed by somebody else, yes, and they are among the most valuable observations available. A site the model scored highly, the committee declined, and a competitor opened successfully is direct evidence about a decision rule rather than about the model — which is a conversation worth having separately from the modelling one.
What if the store underperformed for an operational reason?
Record it and exclude it from the modelling feedback, with the reason. A store that lost its anchor tenant, suffered eighteen months of roadworks or opened with a staffing problem did not test the site model, and folding its miss into the error distribution teaches the model something false. The exclusions themselves should be visible and countable, because a review process that excludes half its misses is not a review process.
How does this connect to the model card?
Directly: the error distribution from this exercise is the accuracy claim the card publishes. Regenerating the card from the validation table rather than editing it by hand means the published accuracy is always the measured one, and it removes the situation where a model card describes a performance the model achieved two years ago.
Should the error review be shared with the property team?
Yes, and it usually improves the input data more than the model. Property teams hold context the pipeline cannot see — a delayed anchor tenant, an access change during construction, a competitor that opened two months before — and the decomposition gives them a specific question rather than a general one. In practice these reviews are also where the most useful additions to the criterion set originate, because the property team can name the factor that explained the miss.
How does this differ from an ordinary variance analysis?
An ordinary variance analysis explains a store’s performance against its plan; this explains it against the model’s forecast, and attributes the difference to model components rather than to trading decisions. The two are complementary and answer to different people: the variance analysis belongs to the operator, and the decomposition belongs to whoever will use the model to approve the next site.
What is the right cadence for the review?
Quarterly for the aggregate report and per store as each one matures. Batching the individual reviews into an annual exercise loses the detail — nobody remembers eighteen months later why a particular store underperformed — while reviewing the portfolio statistics more often than quarterly produces movement that is mostly sampling. Running the two on different clocks is more work to schedule and considerably more informative.
Should the decomposition drive the analogue set as well as the model?
It should. If capture is the component that consistently misses, the analogue matching features probably under-represent competition; if basket misses, they under-represent the demographic mix. The decomposition is therefore a maintenance signal for both the fitted model and the analogue baseline, and updating only the first is how the baseline quietly stops being a fair comparison.
Does the review need a formal format?
A short structured record beats a written narrative: the forecast, the outcome, the four component contributions, an exclusion flag where one applies, and two or three sentences of context from whoever knows the store. Structure makes the reviews aggregatable, which is what turns twenty individual post-mortems into a portfolio finding — and the free-text field is where the reason that eventually becomes a new criterion tends to appear first.
Related
- Backtesting Site Scores Against Realized Store Sales — the ranking equivalent of this exercise.
- Building a Sales Forecast Baseline from Analogue Stores — the forecast this measures.
- Documenting Model Assumptions for Investment Committees — where the error distribution is published.