Panel regression and same store sales
TL;DR
Difficulty: Advanced
- Data with a who (shops, accounts, markets) and a when (quarters, weeks) is a panel. Pooling all the rows lets unit size leak into every marketing coefficient.
- Fixed effects give each unit its own baseline and compare it only with its own history. In the example the newsletter effect halves and the price effect flips sign.
- The Hausman test asks whether random effects can be trusted. Cluster standard errors by unit, and be humble with only a handful of units.
- The fixed effects show a unit's level, the residuals its trend: a shop can be ordinary and still be falling behind itself.
- Like for like sales is the simple version of the same idea. Fix the comparable set before you look.
When a business has several shops, hotels, markets or accounts, adding them up hides the one question that matters: is a unit bad, or did it have a bad year? Using an invented chain of five delis in Leeds and Manchester, this chapter shows how panel regression answers that, why pooled data flatters the newsletter and gets price backwards, and how the same idea produces the like for like sales figure retailers publish. Part 9 of 21 of the Marketing Analytics series.
A good quarter or a good shop?
Every January the five shop managers of Pennine Pantry sit round one table in Leeds with the owner, Hannah, and a stack of printouts. Pennine Pantry is an invented chain of five delicatessens, three in Leeds and two in Manchester. The flagship in Chapel Allerton reports its best autumn yet. Headingley reports a soft year and blames the weather, the roadworks and the delivery apps. Marketing reports 40% more newsletters than the year before and double the paid social. Everyone has a number. Nobody knows which number explains which.
The question underneath is one I hear in some form from every business with more than one site: "Is Headingley a bad shop, or did Headingley have a bad year?" Those are different problems with different fixes, and a spreadsheet that adds up all five shops cannot tell them apart. Panel regression can.
It is the method for data with two dimensions at once: a cross section of units (shops, hotels, regions, accounts) and a run of time periods (weeks, months, quarters). It is also the statistical backbone of the "like for like sales" figure every retailer publishes and few people can derive. This chapter shows how it works, what it says about marketing when the units differ wildly in size, and how it catches a shop falling behind its own history rather than behind its neighbours. Pennine Pantry and every number in this chapter are made up so I can show each step.
Where this sits in the series
This is chapter 9 of 21 in my Marketing Analytics series, in part two, the dependent variable techniques of chapters 4 to 10. The previous chapter, on survival analysis, churn and customer lifetime value, followed individual customers through time until they left. This chapter also follows units through time, but the units are shops and the outcome is sales, so we are back in regression territory with a twist. The next chapter, on forecasting demand with autocorrelation and seasonality, takes the time dimension on its own and asks what next quarter looks like.
If regression itself is new to you, Foundations: regression covers the slope, the intercept and from scratch.
Why pooling everything hides the structure
Pennine Pantry has five shops and twelve quarters of data, 2023 to 2025. That is 60 rows, each with the shop's till transactions, newsletter sends, paid social spend, the shop's average item price and the quarter. The obvious move is to throw the 60 rows into one regression and read off the coefficients. The marketing team had already done that in a spreadsheet and was proud of the result: every extra thousand newsletters brought 710 extra transactions, and higher prices went with more sales, not fewer.
Both numbers are wrong in an instructive way. The Chapel Allerton flagship has 360 square metres, a café, a mailing list four times the size of Roundhay's, and its average item price is about £2 higher. In the pooled data, high newsletter volume, high prices and high sales all travel together. Not because newsletters or prices cause sales, but because they all belong to the big shop. The regression sees a cloud of points and draws a line through it, and the line mostly describes shop size.
The horizontal axis is the quarter, the vertical axis the number of till transactions. The takeaway is in the shape: the same summer bulge every year, and the levels stacked like shelves. The vertical gaps between the lines are the shop effects. The wiggles within each line are what marketing, price and season have to explain.
The fix is to give every shop its own baseline and ask a narrower question: when a given shop sends more newsletters than usual, does it make more sales than usual? That comparison is immune to shop size, location and the café, because those do not change from quarter to quarter. It is called a fixed effects model, and the diagram shows the choice you are making.
Read it top to bottom. Pooled OLS (ordinary least squares on all rows at once) pretends the shop dimension is not there. Random effects treat the shop intercepts as draws from a distribution, which is efficient but honest only under a condition the Hausman test checks. Fixed effects give each shop its own intercept.
The panel model, with the maths
Write for till transactions at shop in quarter . The pooled regression the team ran is
where is the outcome, is newsletter sends in thousands, is paid social spend in thousands of pounds, is the shop's average item price in steps of 25p, is a single intercept shared by every shop, , and are the effects we want, and is everything the model does not explain. The trouble sits inside : it contains "this is the Chapel Allerton flagship", which is correlated with and , so the coefficients are biased. The regression itself is explained in Foundations: regression.
The two way fixed effects model pulls the shop and the quarter out of the error term and gives each its own parameter:
Here is the shop fixed effect, one number per shop that absorbs everything about it that does not change over the three years: floor space, location, reputation, the café. is the period effect, one number per quarter that absorbs everything that hit all five shops at once: the season, a wet July, a change in delivery app commission. The coefficients , and now measure only variation within a shop and within a quarter, which is what we want. Many equations at once like this are bookkeeping with matrices, see Foundations: matrices.
You can estimate this with dummy variables: five shop dummies and eleven quarter dummies, with no separate common intercept (or four shop dummies and an intercept, which is the same model). Fitting the dummy version by ordinary least squares and fitting the demeaned version below give exactly the same .1 The demeaned version, the within transformation, subtracts each shop's own average from every observation:
where is shop 's mean over the twelve quarters, , and are its means of the three drivers, is the average quarter effect and the shop's average error. Notice that has vanished: it is constant within a shop, so subtracting the shop mean removes it exactly. The quarter effects survive as and are still estimated with quarter dummies. With quarter effects in the model that term has to stay. In a balanced panel like this one you can remove it as well, by also subtracting each quarter's mean and adding back the overall mean. The meaning is now obvious. Each shop is compared only with itself. The price coefficient answers "when Headingley raises its prices above its own typical level, what happens to Headingley's sales", not "do expensive shops sell more". The mean is in Foundations: the mean.
Fixed or random effects: the Hausman intuition
There is a rival specification. Random effects keeps a shop level term but treats it as a random draw rather than a parameter to estimate:
where is a common intercept, is shop 's deviation from it with mean zero and variance , and is the covariance. The last condition is the catch: the shop's unobserved level must be unrelated to the drivers. Random effects uses variation both within and between shops, so its standard errors are smaller. It is the more efficient estimator when the condition holds and a biased one when it does not. Variance is in Foundations: spread.
At Pennine Pantry the condition is plainly broken: the flagship sends more newsletters and charges more because it is the flagship. The Hausman intuition is simply this: estimate both, and if the coefficients disagree by more than sampling noise could explain, the random effects assumption has failed and you keep fixed effects.2 Formally,
where and are the fixed and random effects coefficient vectors, the prime turns the first difference into a row, is each estimator's covariance matrix and the power is the matrix inverse. Under the null hypothesis that random effects is fine, follows a chi squared distribution with as many degrees of freedom as there are coefficients tested. A large means the two estimates are further apart than chance allows. The logic of a test statistic and its p value is in Foundations: hypothesis tests.
Worked example: five shops, twelve quarters
A few rows of the panel, so you can see what the data look like. Floor space matters too, and I come back to it under like for like sales.
| Shop | Quarter | Till transactions | Newsletter sends (k) | Paid social (£k) | Average item price |
|---|---|---|---|---|---|
| Chapel Allerton | 2025 Q3 | 35,200 | 48.0 | 9.5 | £5.45 |
| Headingley | 2025 Q3 | 19,900 | 19.5 | 5.5 | £3.73 |
| Roundhay | 2025 Q3 | 15,800 | 11.5 | 3.0 | £3.28 |
| Headingley | 2025 Q1 | 14,800 | 17.0 | 4.5 | £3.48 |
Now the three estimators side by side. Standard errors are in brackets. The last column shows what happens to the fixed effects standard errors once they are clustered by shop, which I explain below.
| Variable | Pooled OLS | Random effects | Fixed effects (shop and quarter) | Fixed effects, clustered |
|---|---|---|---|---|
| Newsletter sends, per 1,000 | 710 (90) | 460 (100) | 380 (110) | 380 (160) |
| Paid social, per £1,000 | 90 (60) | 180 (70) | 210 (70) | 210 (100) |
| Average item price, per 25p | 120 (80) | minus 290 (130) | minus 440 (150) | minus 440 (220) |
| Q2 against Q1 | 2,300 | 2,380 | 2,410 | 2,410 |
| Q3 against Q1 | 5,900 | 6,120 | 6,240 | 6,240 |
| Q4 against Q1 | 1,700 | 1,780 | 1,830 | 1,830 |
| R squared | 0.61 | 0.87 (within) | 0.87 (within) | |
| Hausman H (3 degrees of freedom) | 14.6 |
Reading it row by row.
The newsletter. The coefficient nearly halves from pooled to fixed effects, 710 to 380. The pooled 710 was carrying the flagship's size on its back. The fixed effects 380 says that when any one shop sends a thousand more newsletters than usual, it makes about 380 more sales than usual. The example assumes an average transaction of £16 across the chain, so that is , about £6,000 of sales per thousand sends. Still a good trade, but half of what the team believed, and it changes what growing the list is worth.
Paid social. It moves the other way, from 90 to 210. Pooled, it looked almost useless, because the small shops spent proportionally more on it and had lower sales for reasons that had nothing to do with social. Within a shop, an extra £1,000 brings about 210 transactions, or , roughly £3,400 of sales. A positive return, and now visible.
Price. This matters most for Hannah. Pooled says raising prices raises sales, which is the flagship talking. Fixed effects says that when a shop lifts its average item price 25p above its own norm, it loses about 440 transactions a quarter.
Worked example: the price coefficient as an elasticity
Take the chain's averages as the example's: about 18,000 transactions a quarter per shop and an average item price of £4.00. Losing 440 transactions is fewer sales. A 25p rise on £4.00 is . The elasticity at the mean is therefore , close to minus 0.4. Demand is inelastic: a 1% price rise loses about 0.4% of transactions, so price rises still add revenue, just less than "transactions times the new price" suggests. Elasticity is explained in Foundations: elasticity, and the chapter on demand and price elasticity goes deeper.
Season. The quarter effects barely move across the columns, which is reassuring: everyone has a summer.
Random effects sits between pooled and fixed effects here, as it usually does, because it only partly removes each shop's mean. Taking none of it out gives pooled OLS, taking all of it out gives fixed effects. The Hausman statistic of 14.6 on three degrees of freedom has a p value of about 0.002, so the blend is not trustworthy and we keep fixed effects.
Clustered standard errors. The 60 observations are not 60 independent draws. The twelve quarters of one shop share whatever the model has missed about that shop, so its residuals are correlated over time, and default standard errors then overstate how precise the estimates are.3 Clustering by shop accounts for that. Here the standard errors grow by roughly 45% (110 to 160, 70 to 100, 150 to 220), and the newsletter effect goes from very confident to merely confident. With only five clusters the correction itself is shaky, which is the first pitfall below. I report both and say so.
What the fixed effects say about Headingley
The are not a nuisance. They are the second deliverable. Here they are, expressed as expected transactions per quarter at the chain's average marketing and prices, with the residuals added up over 2025.
| Shop | Floor space | Fixed effect (transactions per quarter) | Per square metre | Sum of 2025 residuals |
|---|---|---|---|---|
| Chapel Allerton | 360 m² | 24,100 | 66.9 | plus 450 |
| Didsbury | 240 m² | 16,600 | 69.2 | minus 200 |
| Headingley | 225 m² | 15,200 | 67.6 | minus 6,250 |
| Chorlton | 190 m² | 13,100 | 68.9 | plus 600 |
| Roundhay | 170 m² | 10,900 | 64.1 | plus 350 |
Divided by floor space, the fixed effects show that the five shops are remarkably similar once marketing, price and season are stripped out. Headingley's 67.6 transactions per square metre per quarter is right in the middle. It is not a bad shop, and it is not structurally behind its neighbours. Had Hannah looked only at totals, she would have filed it as the weakest of the three middle sized shops and moved on.
The residuals say something else entirely. A residual is the gap between what a shop actually sold and what its own fixed effect, the quarter effect and its own marketing predicted. For four shops the 2025 residuals add up to something near zero, as they should. For Headingley they add up to minus 6,250 transactions, and the pattern over time is the point.
The horizontal axis is the quarter. The vertical axis is actual transactions minus the model's prediction, with the zero line drawn in. Eight quarters of noise around zero, small and of mixed sign, then four in a row that are negative and growing.
Worked example: what Headingley's gap is worth
Add up the four 2025 residuals: transactions. The example assumes Headingley's average transaction is £14.50, a little below the chain's £16. So the gap is , roughly £90,000 of sales that the shop's own history, the chain's marketing and the season all said should have arrived in 2025 and did not.
That is what "underperforming its own history" means, and it is a very different diagnosis from "the weakest of the middle sized shops". Roadworks, weather and delivery apps hit everyone, and the quarter effects already absorbed them. Something changed at Headingley in the first quarter of 2025. In this invented case it was a new manager who quietly cut the opening hours on Sunday. In real panels I have seen a broken booking widget and a departed head receptionist, both at hotels. The panel model does not tell you the cause. It tells you where and when to look, with a number attached.
Like for like sales
Retailers publish "like for like sales" (in America "same store sales") because total growth mixes two things: existing shops doing better or worse, and shops opening or closing. Like for like strips out sales from shops opened or acquired over the previous year and adds back any disposed of, so what is left shows how the existing business did.4 Growth of 8% with twelve new shops is a very different business from 8% with the same estate. The simple version keeps only the units that existed in both periods and compares them with themselves:
where is like for like growth in quarter , is the comparable set of units, adds up over the shops in that set, is sales at unit in quarter , and is the same quarter a year earlier, so the season cancels. Percent change is in Foundations: percentages.
Worked example: total growth against like for like
Say Pennine Pantry opened a sixth shop, in Ancoats in Manchester, in spring 2025, and it made 9,800 transactions in the third quarter. The five existing shops made 111,000 transactions in 2024 Q3 (34,200 plus 22,400 plus 21,200 plus 18,600 plus 14,600) and 112,200 in 2025 Q3 (35,200 plus 23,300 plus 19,900 plus 18,000 plus 15,800).
Total growth counts Ancoats: . Like for like leaves it out: . The headline says the chain grew by almost a tenth. The estate it already had grew by about a hundredth, with Headingley pulling it down.
The panel fixed effects model is the same idea done properly. Comparing a shop with itself is exactly what the within transformation does, and the period effects are the "everyone had a bad quarter" adjustment the simple ratio cannot make. The residuals of the panel model are, in effect, a like for like figure per shop per quarter, corrected for the marketing and prices that shop ran.
Two things about capacity. If a shop closes part of its floor for a refit, it is no longer comparable with its own history, so you either drop those periods or model sales per square metre. Chorlton's refit in early 2024 shows up as a dip until you do. And the comparable set must be fixed before you look at results. Definitions genuinely vary between companies: Marston's, for example, decides whether a pub counts as like for like day by day, including it only on the days it was trading.5 Choose a rule, write it down, then look. Deciding afterwards which shops count as comparable is how like for like numbers get flattered.
The same idea for SaaS accounts and online shops
The shop chain is the classic, but the structure appears everywhere I work. Vanlog, the invented Manchester SaaS for trades businesses from the survival chapter, has a few thousand accounts observed monthly: seats, logins, invoices sent, onboarding emails received, plan price. Accounts differ enormously in size, and a pooled regression of seat growth on onboarding emails mostly tells you that big accounts get more of everything. Account fixed effects ask instead whether an account that receives more onboarding touches than usual adds more seats than usual. The data are already in the right shape, one row per tenant per period, if the multi tenancy decisions in a Rails application that are expensive to reverse were made well: a tenant identifier on every table is what makes the panel possible later.
Northgate Hampers, the invented Leeds hamper shop from earlier chapters, runs separate campaigns for England and Wales and for Scotland. Two markets over 104 weeks is a thin cross section, but even two market effects and week effects separate "England is the bigger market" from "the campaign in week 46 worked". Whenever your data have a "who" and a "when" at the same time, and the "who" differs in ways you cannot fully measure, you have a panel and should treat it as one.
How to run it
You need one row per unit per period, with the outcome, the marketing inputs aligned to the same period, the price, and a capacity measure. Twelve periods and five units is close to the minimum I would model. Eight periods and three units is a chart, not a regression. Building the table is a morning in SQL. The within transformation itself is one window function, avg(x) over (partition by shop), subtracted from each column.
In Python the dummy variable version with clustered errors is a few lines. I prefer it to the demeaned version because the fixed effects come out as named coefficients you can hand to the owner:
import pandas as pd
import statsmodels.formula.api as smf
df = pd.read_csv("pennine_panel.csv") # one row per shop and quarter
df["email_k"] = df["email_sends"] / 1000
df["social_k"] = df["paid_social_gbp"] / 1000
df["price25"] = df["avg_item_price_gbp"] / 0.25
shop_codes = pd.factorize(df["shop"])[0] # cluster ids must be integers
fe = smf.ols(
"transactions ~ email_k + social_k + price25 + C(shop) + C(quarter)",
data=df,
).fit(cov_type="cluster", cov_kwds={"groups": shop_codes})
print(fe.summary())
df["resid"] = fe.resid # the Headingley chart is this column
statsmodels expects the cluster groups as integers, which is why the shop names are turned into codes first.6 R users will reach for fixest, where feols(transactions ~ email_k + social_k + price25 | shop + quarter, cluster = ~shop) is the most readable two way fixed effects specification I know.7
Before trusting the result I do four checks. I plot residuals per unit over time, which is how the Headingley pattern appears in the first place. I compare the fixed and random effects tables and compute the Hausman statistic. I refit with the last two periods held out and check that the coefficients barely move. And I add next quarter's marketing spend as a driver of this quarter's sales: if future spend "predicts" current sales, the causation is running backwards, and the marketing team is reacting to sales rather than driving them. From a clean table to a decision memo takes me about two weeks for a business of this size, and most of that is the data.
Pitfalls
Few clusters. Clustered standard errors are only reliable when there are many clusters. There is no clear line: depending on the situation, "few" can mean fewer than 20 or fewer than 50.3 Five is few by any definition. With few clusters I report conventional, clustered and wild cluster bootstrap standard errors, a method designed for exactly this case,3 say which is which, and let the widest one drive any decision that costs money.
Things that never change disappear. Fixed effects absorb everything about a unit that does not change, so you cannot estimate the effect of a café, of being the flagship, or of a tenant being on the enterprise plan since day one. If that is the question, fixed effects is the wrong tool, and you are back to comparing units with all the confounding that brings.
Marketing that reacts to sales. Shops spend on paid social when trade is soft and cut it when they are busy. That produces a negative relationship between spend and sales within a shop that has nothing to do with the effect of the spend. The lead test above catches the worst of it, lagging the spend by one period helps, and the proper fix is an experiment, which comes in part five of this series. Fixed effects remove confounding between units. They do nothing about confounding within a unit over time.
Chain wide campaigns divided by five. When one brand campaign runs and finance splits its cost equally across the shops, the "shop level spend" is fiction and its coefficient is meaningless. Use spend that was genuinely targeted at the unit, or treat the chain campaign as part of the period effect, which is what it is.
Confusing a level with a trend. A shop with a low fixed effect is smaller or worse placed. A shop with negative and growing residuals is changing. Headingley had an ordinary level and a worrying trend. Reporting only the fixed effects would have missed the trend, and reporting only totals would have misread the level. You need both tables, and you need enough periods: with four quarters the quarter effects eat everything and the marketing coefficients are noise. Three years of quarterly data is a workable minimum, and two years of weekly data beats five years of annual data.
How I do this for clients
The deliverable is a table of coefficients you can act on, a table of unit effects you can rank, and a residual chart per unit that tells you where to look this quarter. It comes with a one page decision memo: the number, its error margin stated honestly, and the risk if the number is wrong. For Pennine Pantry the memo would say, in effect: "Each thousand newsletter sends is worth about £6,000 of sales, price rises still pay but at an elasticity near minus 0.4, and Headingley has lost roughly £90,000 of sales against its own history since January. Find out why before you spend another pound on its marketing."
What I need from you is the panel: one row per unit per period, which usually means exports from the till or the shop system, the newsletter tool, the ad accounts and the price list. If your systems cannot produce that table, that is the first finding and often the most valuable one. The data roles article explains who in a small business should own that pipeline.
It starts with the free workshop, where we work out whether you have a panel at all and what the one question is. Then two weeks of real work on me: the table, the models, the checks and the memo, before you commit to anything. After that it is a Data Science engagement, and where the answer feeds straight into spend decisions, Performance Marketing. Your raw data stays yours, the terms for code and models are in the analytics and data science terms, and the fees are on the pricing page. One accountable person, honest error bars, no vanity metrics.
Questions to ask your team or agency
- When you report that a channel works, is that within units over time, or is it mostly the big units having more of everything?
- Does every unit in the comparison have the same capacity in both periods, and who decided which units count as comparable, and when?
- Show me the residuals per unit over time. Which unit is drifting away from its own history, and since when?
- Are the standard errors clustered by unit, and how many clusters are there?
- Did you test whether next period's spend predicts this period's sales?
- Which of our questions are about differences between units, where fixed effects cannot help, and which are about changes within units, where they can?
The series is inspired by Mike Grigsby's Marketing Analytics.8 The explanations, examples and numbers here are my own.
Footnotes
-
Christoph Hanck, Martin Arnold, Alexander Gerber and Martin Schmelzer, Introduction to Econometrics with R, section 10.3 Fixed effects regression, https://www.econometrics-with-r.org/10.3-fixed-effects-regression.html
-
StataCorp, hausman: Hausman specification test, Stata Base Reference Manual (after Hausman, 1978), https://www.stata.com/manuals/rhausman.pdf
-
A. Colin Cameron and Douglas L. Miller, A practitioner's guide to cluster robust inference, Journal of Human Resources 50(2), 2015, http://cameron.econ.ucdavis.edu/research/Cameron_Miller_JHR_2015_February.pdf
-
MoneyWeek, Like for like sales, glossary, 21 May 2018, https://moneyweek.com/glossary/like-for-like-sales
-
Marston's PLC, Annual Report and Accounts 2025, alternative performance measures (LFL sales), https://www.marstonspubs.co.uk/docs/financials/2025/ar/additional-information.pdf
-
statsmodels, OLSResults.get_robustcov_results (cluster covariance, groups), https://www.statsmodels.org/stable/generated/statsmodels.regression.linear_model.OLSResults.get_robustcov_results.html
-
Laurent Bergé, fixest reference, feols (fixed effects and the cluster argument), https://lrberge.github.io/fixest/reference/feols.html
-
Mike Grigsby, Marketing Analytics: A Practical Guide to Improving Consumer Insights Using Data Techniques, Kogan Page, 2022, ISBN 9781398608191, https://www.netgalley.co.uk/catalog/book/267990