Building a Sales Forecast Baseline from Analogue Stores
This page solves one exact task: producing a sales forecast for a candidate site by finding the most similar stores already trading, adjusting for the ways the candidate differs from them, and reporting a range rather than a point.
An analogue forecast is the baseline every more sophisticated approach should be measured against. It is transparent — a committee can look at the five stores it was based on — it needs no fitted parameters, and it frequently performs within a few points of a regression while being far easier to defend. Any model that cannot beat it is not earning its complexity.
Prerequisites
- A trading estate with matured sales, at least eighteen months per store, from the same measure used in backtesting.
- The same criterion set for candidates and trading stores — catchment population, demographic fit, competition, site attributes — computed identically for both.
- Python packages:
pandas,numpy,scikit-learnfor the nearest-neighbour search. Install withpip install pandas numpy scikit-learn.
Configuration and execution parameters
| Parameter | Value for this task | Notes |
|---|---|---|
k_analogues |
5–8 | Fewer is unstable; more dilutes similarity |
match_features |
reach, demo fit, competition, format, size | Normalised before distance |
feature_weights |
from backtest importance | Not equal, and not the score’s weights |
same_format_only |
true | A format mismatch invalidates the analogue |
distance_metric |
weighted Euclidean on normalised features | Simple and explainable |
output |
median and interquartile range | Never a single point |
min_analogue_similarity |
stated threshold | Below it, report “no good analogue” |
The last row is the one that keeps this honest. A candidate genuinely unlike anything in the estate — a new format, a market type never entered — has no analogues, and producing a forecast anyway by taking the five least dissimilar stores manufactures confidence out of nothing. Saying “no comparable store exists” is a legitimate and useful answer.
Annotated implementation
from __future__ import annotations
import numpy as np
import pandas as pd
from sklearn.neighbors import NearestNeighbors
FEATURES = ["reach_pop", "demo_fit", "competition", "floor_area_m2"]
FEATURE_WEIGHTS = np.array([0.40, 0.30, 0.20, 0.10]) # from backtest importance
K = 5
MIN_SIMILARITY = 0.60
def _normalise(frame: pd.DataFrame, reference: pd.DataFrame) -> np.ndarray:
"""Scale using the TRADING estate's statistics so candidates and stores
share one frame of reference across runs."""
mu = reference[FEATURES].mean().to_numpy()
sd = reference[FEATURES].std().replace(0, 1).to_numpy()
return ((frame[FEATURES].to_numpy() - mu) / sd) * np.sqrt(FEATURE_WEIGHTS)
def analogue_forecast(candidates: pd.DataFrame, trading: pd.DataFrame,
k: int = K) -> pd.DataFrame:
"""Median and spread of the k most similar trading stores, per candidate."""
rows = []
for fmt, group in candidates.groupby("format"):
pool = trading.loc[trading["format"] == fmt]
if len(pool) < k:
rows.extend({"site_id": s, "forecast": np.nan,
"reason": "too few same-format stores"}
for s in group["site_id"])
continue
nn = NearestNeighbors(n_neighbors=k).fit(_normalise(pool, trading))
dist, idx = nn.kneighbors(_normalise(group, trading))
# Similarity falls off with distance in the weighted feature space;
# below the threshold the "analogues" are not analogous.
similarity = 1.0 / (1.0 + dist.mean(axis=1))
for i, site in enumerate(group["site_id"].to_numpy()):
picks = pool.iloc[idx[i]]["sales_per_m2"]
rows.append({
"site_id": site,
"forecast": float(picks.median()) if similarity[i] >= MIN_SIMILARITY else np.nan,
"p25": float(picks.quantile(0.25)),
"p75": float(picks.quantile(0.75)),
"similarity": round(float(similarity[i]), 3),
"analogues": list(pool.iloc[idx[i]]["store_id"]),
"reason": "" if similarity[i] >= MIN_SIMILARITY else "no close analogue",
})
return pd.DataFrame(rows)
Returning the analogue store identifiers with every forecast is what makes the method defensible. A committee that can see the five stores a number came from will interrogate the comparison rather than the arithmetic, and that is a far more productive conversation than one about a coefficient.
Failure modes and debugging
Analogues drawn from a different era. Stores that opened ten years ago traded in a different competitive and economic environment, and using them without adjustment imports that environment into the forecast. Restricting the pool to stores opened or refitted within a recent window, or adjusting by a market index, keeps the comparison contemporary.
Feature weights borrowed from the score. The weights that make a good ranking are not necessarily the ones that identify a good analogue — a criterion may matter to the decision and be useless for matching. Deriving matching weights from what actually predicts sales in the backtest gives better analogues than reusing the committee’s priorities.
Ignoring cannibalization. An analogue store trading in isolation is a poor comparison for a candidate that will open next to a sister store. Either restrict analogues to similarly-cannibalized stores or apply a transfer adjustment to the forecast, and state which.
Reporting a point estimate. The spread across analogues is information — it says how much variation exists among stores that look identical on the model’s features — and collapsing it to a median throws away the honest part of the answer. A candidate whose analogues span thirty per cent is a genuinely uncertain forecast, and the committee should see that.
Verification
- Leave-one-out the trading estate. Forecast each existing store from its own analogues, excluding itself, and compare against its actual sales. That gives an error distribution for the method on real stores.
- Check the analogues by eye for a sample of candidates. If the five stores do not look comparable to someone who knows the estate, the feature set is missing something.
- Compare against the fitted model on the same candidates. Where the two disagree substantially, at least one is wrong, and the disagreement is worth resolving before a decision.
- Confirm the no-analogue path fires by scoring a deliberately unusual candidate.
Frequently Asked Questions
How many analogues is the right number?
Five to eight for most estates. Below five, one unusual store moves the median substantially; above eight, the pool starts including stores that are not really comparable and the median drifts toward the estate average — which is a forecast that says nothing about the site. Where the estate is large and homogeneous, a larger k is safe; where it is small or varied, a smaller k with an explicit similarity threshold is better.
Should the forecast be adjusted for the candidate’s differences?
Only for differences the analogues cannot absorb, and explicitly. Adjusting for floor area is standard — the forecast is per square metre for that reason. Adjusting for a slightly better demographic fit invites a chain of small justifications that ends with a number nobody can reconstruct. If a difference matters enough to adjust for, it probably belongs in the matching features instead.
Can analogues come from a competitor’s estate?
Where observable performance exists — through a mobility panel, for instance — a competitor store in a similar setting is a legitimate analogue for a market you have never entered, and often the only one available. The estimate is weaker because the performance measure is indirect and the format may differ, so it belongs labelled as an external analogue with a wider range rather than mixed silently into the pool.
How does this fit alongside the suitability score?
The score ranks and the analogue forecast sizes. A committee needs both: which sites are best, and what each is likely to do. Keeping them separate also provides a useful cross-check, since a site that ranks highly and forecasts poorly is usually revealing something about the estate — most often that the score’s weights reward something that does not translate into sales.
What happens when the estate changes shape?
The analogue pool changes with it, which is a feature rather than a problem — a forecast built from stores of the current format mix describes the business as it is now. What needs watching is the transition: a chain moving into a new format will, for a year or two, have too few same-format analogues to forecast from, and the honest handling is to say so and lean on the fitted model or on external analogues rather than to relax the format constraint quietly.
Should analogues be weighted by similarity rather than treated equally?
Distance weighting is a reasonable refinement and rarely changes the median much. What it does improve is the behaviour at the edge, where the fifth analogue is noticeably less similar than the first: weighting keeps that store from having equal say. The cost is that the forecast is no longer a plain median of five identifiable stores, which is a real loss of explainability — so the choice depends on whether the audience wants a defensible number or an accurate one, and for a committee the two are not always the same.
Should the analogue set be shown in the committee pack?
Yes, and it changes the discussion. A forecast presented as a number invites either acceptance or scepticism; the same forecast presented as five named stores invites recognition — people who know the estate immediately have views about whether those five are fair comparisons, and those views are usually worth incorporating. It also makes the forecast falsifiable in a way a coefficient is not.
Related
- Backtesting Site Scores Against Realized Store Sales — where the matching weights come from.
- Measuring Forecast Error After a Store Opens — closing the loop on this forecast.
- Building Weighted Site Suitability Scores — the ranking this sizing accompanies.