A geo lift test measures the incremental effect of advertising by changing spend in some geographic regions and comparing them with regions where nothing changed, so the untouched regions tell you what would have happened without the campaign. GeoLift is Meta’s open-source R package for designing and analyzing these tests, and it’s become the default starting point for many marketers, although Google has published its own methods for the same problem going back to 2011. You’ll also hear the same idea called geo testing, geo experiments, a geo holdout or a matched market test, depending on how the regions were picked and who is selling it to you.
The reason geo tests matter so much now is that they don’t need to follow individual users. Having run a growth agency that spent over $10 million a year on Facebook ads, I’ve come to see platform-reported attribution as the platform marking its own homework, and user-level tracking has only got patchier since iOS privacy changes. A geo test sidesteps all of that: you only need to be able to target ads by region and to count the outcome (sales, sign-ups, store visits) by region. That makes it one of the few ways to test TV, radio, outdoor and other channels with no click to track, and one of the best ways to get ground truth for calibrating a marketing mix model. Most of what follows is about the design choices, because they decide whether anyone should believe the result.
How a geo lift test works
Every geo test has the same three ingredients: a set of regions (countries, states, Nielsen DMAs, postcodes, cities), a pre-period in which every region runs as normal, and a test period in which the treatment regions get a change (more spend, a new channel, or ads switched off entirely) while the comparison regions carry on as before. The effect of the campaign is the gap between what the treatment regions did and what they would have done without the campaign. That second number is never observed, so every method in this space is really a different way of estimating it, which is the counterfactual at the heart of all incrementality work.
The first big choice is how the regions get assigned. In a randomized geo experiment you have enough regions that you can let chance decide which are treated, which protects you against hidden differences you didn’t think to check. Jon Vaver and Jim Koehler at Google described this design in their 2011 paper, using the 210 US DMAs as an example set of geos, and they found that ranking regions by size and randomizing within size groups narrowed the confidence interval on return on ad spend by 10 percent or more. In a matched market test you have too few regions to randomize well (a UK brand testing in two TV regions, say), so you pick treatment and comparison markets that looked alike in the pre-period and rely on that similarity continuing. Matched markets are far more common in practice, and they’re also far easier to fool yourself with.
The second choice is how you build the counterfactual from the comparison regions, and there are three families you’ll meet:
| Method | How it builds “what would have happened” | Where you’ll see it |
|---|---|---|
| Difference-in-differences | Treatment’s pre-period level plus the comparison group’s change | Spreadsheets, simple matched pairs, two-way fixed effects regression |
| Synthetic control | A weighted blend of untreated regions that tracks the treated region in the pre-period | GeoLift (augmented synthetic control), CausalImpact-style analyzes |
| Regression on the pre-period | Test-period outcome regressed on the pre-period outcome and the spend change, across geos (GBR) or over time (TBR) | Google’s geo experiment papers and matched_markets library |
Whichever estimator you use, the test period should run a little beyond the end of the spend change. Vaver and Koehler point out that offline sales in particular lag the advertising (people research, decide and then visit a store), so a test that stops counting the day the ads stop will miss some of the effect. That’s the same adstock carryover an MMM tries to estimate, showing up inside your experiment.
Three answers from one geo test
The course’s invented geo dataset makes the core problem concrete. Twelve regions are split into six matched pairs; one region in each pair gets a campaign worth four (thousand) a week in extra spend for four weeks, and we know the true media effect is eight a week because the data was built that way. There’s a catch built in, which is exactly the kind of thing that happens in real tests: one treated region also ran a local promotion during the test window that nobody in marketing planned around.
Your file needs columns region, arm (treatment/comparison), period (pre/post) and revenue_k (or revenue). Optional: week, pair, media_spend_k, known_media_lift_k, promotion_lift_k.
The treatment-only before/after comparison says 17, because it credits the campaign with the common upward trend that lifted every region by eight between the periods. Difference-in-differences says 9, because subtracting the comparison group’s change removes that shared trend. However, the known media effect is 8, and the extra one comes from the promotion: a six-unit shock in one of six treated regions adds one to the treatment average. Removing a shared trend doesn’t remove a local shock, which is the whole lesson in one line. If you untick the promotion region, DiD lands on 8 and the implied iROAS drops from 2.25 to 2.00, which is the difference between a channel that looks comfortably profitable and one that might only just clear your breakeven ROAS.
In a real test you won’t have the answer-key columns, so the pair-level chart is the part I’d pay most attention to. Five pairs agree and one doesn’t, and an outlying pair is a prompt to go and ask the regional team what happened there, not something to quietly average away. Dropping a region after you’ve seen the results is also a judgment call you should write down and defend, because it’s exactly the move a motivated analyst would make to get the answer they wanted.
Meta’s GeoLift R package
GeoLift is an R package from Meta’s Marketing Science team, created by Arturo Esquerra with Nicolas Cruces, and it covers the whole workflow: reading and checking geo-level data, choosing test markets with a power analysis, and estimating the lift after the test. As of October 2026 the repository is MIT licensed, still marked active, and its DESCRIPTION file lists version 2.7.5; it isn’t on CRAN, so you install it from GitHub, and the code itself has changed little recently (the last substantive change I could find was in January 2026, with most activity since then being dependency updates to the documentation site). That’s fine for a statistical tool that does one job, but it’s worth knowing before you build a production pipeline on it.
Under the hood GeoLift uses the augmented synthetic control method of Eli Ben-Michael, Avi Feller and Jesse Rothstein (2021), via their augsynth package. In plain terms, it builds a weighted blend of untreated markets that tracks the test markets in the pre-period, then corrects for whatever part of the pre-period it couldn’t fit using a ridge regression outcome model. I explain the idea step by step, with a widget that fits the weights live, on the synthetic control method page.
install.packages("remotes")
remotes::install_github("ebenmichael/augsynth")
remotes::install_github("facebookincubator/GeoLift")
library(GeoLift)
geo <- GeoDataRead(data = sales_by_city, date_id = "date",
location_id = "location", Y_id = "Y", format = "yyyy-mm-dd")
# Power analysis: which markets, how many, how long, at what budget?
markets <- GeoLiftMarketSelection(data = geo, treatment_periods = c(14, 21),
N = c(2, 3, 4, 5), effect_size = seq(0, 0.25, 0.05),
cpic = 7.5, budget = 100000)
# After the test: estimate the lift for the chosen test markets
result <- GeoLift(Y_id = "Y", data = geo, locations = c("city_a", "city_b"),
treatment_start_time = 91, treatment_end_time = 105)
summary(result)
The function names and arguments above follow GeoLift’s own walkthrough; the data frame and city names are placeholders. The market selection step is the most useful part of the package in my view, because it forces the conversation about power before anyone spends money. It simulates lifts across your history for different combinations of markets and test lengths, and it uses cpic (cost per incremental conversion) to estimate the budget each design needs to detect a given effect. If the answer is “you need to spend more than you have for longer than you’re willing to wait”, that’s valuable to know in week zero rather than week six.
GeoLift’s best practice notes are also more specific than most vendor guidance. They recommend daily data rather than weekly, the finest geography you can target, a pre-period four to five times the length of the test with no structural breaks, at least 25 pre-treatment periods and 20 or more geographic units, no missing values, and a test of at least 15 days on daily data (four to six weeks on weekly data) that covers a full purchase cycle. Meta’s own documentation also says plainly that people-based experiments like Conversion Lift have more statistical power where they’re feasible, which is a refreshingly honest thing for a geo tool to say about itself. I cover those user-level tests on the conversion lift studies page.
Google’s approaches to geo experiments
Google has published a steady line of research on geo experiments, and it’s worth knowing because most commercial tools borrow from it. Vaver and Koehler (2011) set out the randomized design and a geo-based regression (GBR): each geo’s test-period outcome is regressed on its pre-period outcome and on the change in ad spend, weighted to account for geos of very different sizes, and the coefficient on the spend change is the return on ad spend. Their design section also shows how to estimate the confidence interval in advance by replaying the analysis on historical “pseudo” test periods, which is the same idea GeoLift’s market selection uses.
GBR needs a reasonably large number of geos, so Jouni Kerman, Peng Wang and Jon Vaver (2017) proposed time-based regression (TBR) for tests with only a few regions, such as matched market tests. TBR works on the aggregated treatment and control time series instead, learning the relationship between them in the pre-period and projecting it forward. Google’s open-source matched_markets Python library implements TBR for analysis and a “TBR matched markets” search for design that picks treatment and control groups subject to budget and other constraints. For randomized paired designs, Aiyou Chen and Tim Au developed trimmed match (published in the Annals of Applied Statistics in 2022), which trims the worst-matched pairs so a few outliers can’t dominate the estimate, and Chen, Longfils and Remy (2021) extended it to the design stage; that’s implemented in the trimmed_match library. Both repositories describe themselves as research code rather than officially supported Google products, so treat them like you would any careful academic implementation.
How to design a geo test that people will believe
The arithmetic of a geo test is easier than the design, which is the line I lean on most in the course’s lesson on designing a geo test. The calculation is only useful if the comparison regions give a credible picture of how the treatment regions would have moved without the campaign, and regions looking similar before a test is helpful context, not a guarantee about what happens afterwards. These are the decisions that matter, roughly in the order you’ll make them.
Start from the decision and the effect size you care about
Write down the budget decision the test will inform and the smallest lift that would change it, before you look at any markets. If a lift below 3 percent wouldn’t change your plan, you don’t need a test that can detect 1 percent, and if your design can only detect 20 percent you should know that up front. The statistical power for marketing tests page has a geo calculator that turns region-level noise, the number of regions and the test length into a minimum detectable effect.
Choose markets on the outcome, not on demographics
Match or select markets on the metric you’re going to measure (sales, sign-ups) over a long pre-period, plus anything category-specific that drives it, because two cities with the same age profile can still have very different sales seasonality. Avoid your biggest market as a treatment region if you can (it’s hard to build a counterfactual for something with no peers), and avoid tiny regions where a single store opening swamps the signal. If you can randomize, randomize within size strata, as Vaver and Koehler recommend.
Set the test length, spend level and holdout share together
Test length, spend intensity and the share of the country in treatment trade off against each other. A bigger spend change in fewer regions produces a larger, easier-to-detect lift but tells you about a saturation point you might never run nationally; a smaller change spread more widely is more representative but harder to see. As a rule I’d rather run a heavier test for long enough to cover a purchase cycle plus a cool-down than a light one that ends inconclusively, because a null result needs a precision story and a vague one doesn’t have one. For a “go dark” holdout, keep the holdout share as small as power allows, since every holdout region is revenue you’re choosing to forgo.
Protect against spillover and local shocks
Spillover happens when the treatment leaks into comparison regions: people commuting across a DMA border, national TV or social campaigns that don’t respect your regions, delivery zones that cut across them, or a sales team that hears about the test and pushes harder in the “losing” regions. GeoLift has a vignette on using commuting zones for exactly this reason. Local shocks (a store refit, a competitor closing, a regional promotion like the one in the dataset above) are the other half of the problem, and the only real defense is a log of everything else happening in each region during the test, agreed with the people who run those regions.
Make sure the outcome data exists at the right grain
The outcome has to be countable by the same geography you target ads by, which sounds obvious until you discover that ecommerce orders are recorded by billing address, store sales by store, and the ad platform’s regions by IP address. Decide the mapping before the test, check there are no gaps for any region-day, and if store or retail sales arrive with a lag, schedule the analysis for when the data is complete rather than when the campaign ends. The MMM data requirements page covers the same plumbing from the modeling side.
Reading the result and feeding it into your MMM
When the test is done, report the estimated lift, its interval, the iROAS that implies, and the design caveats, in that order. A point estimate above your hurdle isn’t enough if the interval still includes values below it, and a “not significant” result from an underpowered test isn’t evidence that the channel doesn’t work. It’s also worth being precise about what you measured: a test of extra spend on top of a non-zero baseline tells you about the marginal return on that increment, not the average return on all spend, and a short test can miss later effects.
The most valuable thing to do with a good geo result is to use it as a prior or calibration target for your marketing mix model, so the model’s always-on estimates are anchored to something experimental. Record the treatment, comparison regions, dates, geography, spend change and outcome in a test log, because a library of past tests is worth much more than any single one. The full process from question to design is in the incrementality testing guide, and if you want to compare commercial options, the incrementality testing tools page covers vendors that run geo tests for you. The Vexpower MMM course has a lesson on exactly this design problem, using the invented Northstar brand, where the interactive starts with a competitor closure in the campaign region so you can see what a comparison can and can’t remove.
