Econometrics for Modern Marketing: From OLS to Bayesian MMM, Causal Lift, and Geo Experiments (in Python)

Marketers today are drowning in platform dashboards, each claiming credit for sales. Google Ads reports inflated ROAS, Meta’s conversions API credits every view-through, and your analytics pipeline is riddled with tracking gaps. The real question is: what truly drove incremental revenue?
This is where econometrics still shines. Unlike heuristic attribution, econometric models are built to handle confounding, seasonality, lagged effects, and uncertainty. They force us to:
- Write down hypotheses explicitly.
- Control for external factors (promotions, holidays, pricing).
- Quantify uncertainty, instead of pretending we know the truth exactly.
The reward is credibility with finance and strategy teams. When you can show response curves, elasticities, and confidence intervals instead of single-point ROAS claims, you shift the conversation from “marketing as a cost center” to “marketing as an investment portfolio.”
This article walks through a practical econometrics stack for marketing:
- Elasticities & baselines with OLS
- Time-series effects (adstock/carryover & saturation)
- Bayesian MMM for multi-channel attribution and budget optimization
- Causal inference & lift (A/B, geo-experiments, synthetic control
- SEO modeling for rank→CTR→traffic→conversion
- Visualization & optimization (response curves, efficient budget frontiers)
1) Baselines and elasticities: the entry point
Most econometric journeys start with regression models. In marketing, the first thing teams want to know is: “If I spend $1 more on channel X, how much revenue will I get back?” This is essentially an elasticity question.
When to use OLS
- Early-stage diagnostic analysis.
- Small datasets with clear separation between variables.
- As a sanity check before moving to Bayesian or nonlinear models.
What it tells you
- Direction of relationships (positive, negative, insignificant).
- Magnitude of impact in log-log models (elasticities).
- Which variables may be collinear (warning signs for later).
Limitations
OLS breaks down when:
- Media spend is correlated across channels (common in omnichannel plans).
- Effects spill over time (lagged conversions, brand halo).
- You have diminishing returns that flatten at higher spend.
Still, it’s the best starting point for grounding your intuition. A regression that says “SEO score improves revenue by 1.5% per point, holding spend constant” is already a powerful message for a CMO who thinks SEO is “free.”
Example: paid search + SEO score on revenue
import pandas as pd import statsmodels.api as sm from patsy import dmatrices # df has weekly rows; all currency in nominal USD # cols: revenue, search_spend, brand_terms_share, seo_score, promo, season_week df = df.copy() # log-log to interpret beta as elasticity; add seasonal fixed effects formula = "np.log(revenue) ~ np.log(search_spend + 1) + seo_score + promo + C(season_week)" y, X = dmatrices(formula, df, return_type="dataframe") model = sm.OLS(y, sm.add_constant(X)).fit(cov_type="HC3") # robust SEs print(model.summary())
Interpretation:
- The coefficient on
log(search_spend+1)is the elasticity of revenue wrt search spend at the observed range. - Control for seasonality and promos early to avoid confounding.
- Check VIF, residual ACF/PACF; if you see autocorrelation, move to time-series.
2) Time-series structure: lags and diminishing returns
OLS assumes effects are immediate and linear. But marketing doesn’t work that way:
- TV spots may trigger search demand days later.
- Display impressions create awareness that surfaces in brand queries weeks later.
- Paid search saturates — the 100th $1,000 in spend never drives the same ROI as the first $1,000.
Adstock / carryover

Adstock models the idea that media effects decay over time. Imagine a TV campaign: half of its effect happens in week 1, a quarter in week 2, an eighth in week 3, and so on. This is modeled with a decay parameter α.
The beauty of adstock is that it formalizes intuition: brand awareness lingers, but it fades.
Saturation / diminishing returns
Saturation curves (Hill, Michaelis–Menten, logistic) capture the fact that marginal returns decline. Spend enough, and you hit a ceiling.
- The Hill function is flexible: it defines a half-saturation point (spend level where you achieve 50% of max effect) and steepness.
- Michaelis–Menten is popular in biology, but the Hill is more interpretable in marketing.
Hand-rolled functions in Python:
import numpy as np
def geometric_adstock(x, alpha):
w = np.array([alpha**k for k in range(len(x))])
return np.convolve(x, w, mode='full')[:len(x)]
def hill_saturation(x, half_saturation, steepness):
x = np.clip(x, 0, None)
return 1.0 / (1.0 + (x / half_saturation) ** (-steepness))Practical tip: Even if you don’t go full Bayesian MMM, adding adstock + Hill transforms to OLS makes your models far more realistic.
3) Bayesian MMM: the workhorse of modern marketing econometrics
Marketing Mix Modeling (MMM) is the holistic framework to measure multi-channel contributions. But classic MMM (circa 1980s–2000s) was fragile — overfit, opaque, and point-estimate driven. The modern renaissance comes from Bayesian MMM.
Why Bayesian is better
- You can encode priors: e.g., “TV decay between 0.4–0.8 is plausible,” “Elasticities must be positive but less than 2.”
- You get posterior distributions instead of a single coefficient. This gives you credible intervals, not fake precision.
- You can simulate budget scenarios with posterior predictive draws — perfect for risk-aware planning.
Industry adoption
- Google released LightweightMMM (LMMM), then evolved into Meridian.
- Meta released Robyn with a ridge regression + evolutionary search engine.
- PyMC’s marketing components let you build fully customized MMMs in Python.
Minimal Bayesian MMM in PyMC:
import pymc as pm
import aesara.tensor as at
y = df["revenue"].values
m_search = df["m_search"].values
m_social = df["m_social"].values
m_tv = df["m_tv"].values
with pm.Model() as mmm:
alpha = pm.Normal("alpha", 0.0, 5.0)
betas = pm.HalfNormal("betas", 1.0, shape=3)
mu = alpha + betas[0]*m_search + betas[1]*m_social + betas[2]*m_tv
sigma = pm.HalfNormal("sigma", 1.0)
y_obs = pm.Normal("y_obs", mu=mu, sigma=sigma, observed=y)
idata = pm.sample(2000, tune=2000, target_accept=0.9)Outputs you actually use:
- Channel contributions (posterior means + 80/95% intervals)
- Response curves (spend→incremental conversions)
- Optimal budget allocations
This is where serious organizations land: a Bayesian MMM running weekly, calibrated by experiments, and used to optimize budgets.
4) Incrementality by design: experiments, GeoLift, synthetic control
When feasible, randomized experiments trump observational models.
Conversion lift tests
Platforms like Meta and Google can run people-based randomized holdouts. They tell you true incremental conversions, independent of attribution windows.
Geo experiments
When identity isn’t available, randomize geographies. This works especially well in retail, streaming, and QSR contexts. Tools like Meta’s GeoLift automate power calculations and synthetic control adjustments.
Synthetic control
If randomization isn’t feasible, use control groups that look like your treatment group. CausalImpact in Python/R is a powerful way to estimate lift using time-series counterfactuals.
from causalimpact import CausalImpact pre = ['2024-01-01', '2024-06-30'] post = ['2024-07-01', '2024-08-31'] data = df[['treated','control1','control2']] ci = CausalImpact(data, pre, post) print(ci.summary())
Pro tip: Use experiments to inform priors in your MMM. If a geo test shows search elasticity < 0.5, your MMM shouldn’t be allowed to estimate 2.5.
5) Causal ML for heterogeneous effects
Marketers rarely want just the average effect. The CFO might ask: “Which segments are most responsive to TV?” or “Which creative resonates with Gen Z?”
This is where causal machine learning shines. Libraries like EconML and DoWhy let you estimate Conditional Average Treatment Effects (CATEs): uplift by audience, creative, or geo.
Instead of one elasticity, you get a distribution of effects. For example, TV might be ROI-positive for 45+ households but negative for younger cohorts. That’s actionable.
6) SEO econometrics: often ignored, always undervalued
SEO is notoriously hard to model — results are lagged, ranking algorithms change, and “impressions” data from GSC is noisy. But econometrics can still bring rigor.

Key steps
- Rank → CTR curves: Use industry benchmarks (e.g., Backlinko, Advanced Web Ranking) as priors. Update quarterly — SERP features shift CTR.
- CTR → sessions: Multiply by impressions. This gives organic traffic potential.
- Sessions → conversions: Apply historical conversion rates, ideally segmented by intent.
Why it matters
Without modeling, SEO gets dismissed as a cost center. With an econometric framework, you can say: “Improving our average rank from 6 to 3 is worth $3M incremental revenue, with a 90% credible interval of $2.2M–$3.8M.” That’s the language the CFO understands.
Simple projection:
def rank_to_ctr(rank):
return np.where(rank<=1, 0.28,
np.where(rank<=3, 0.15,
np.where(rank<=10, 0.05, 0.01)))
def project_seo(recs):
ctr = rank_to_ctr(recs["projected_rank"].values)
sessions = recs["impressions"].values * ctr
conv = sessions * recs["base_cvr"].values
return pd.DataFrame({"ctr": ctr, "sessions": sessions, "conversions": conv})7) Visualization: response curves

Response curves are the killer feature of MMM. They let you show stakeholders that spend has diminishing returns, and there’s an optimal “sweet spot.”
import matplotlib.pyplot as plt
search_spend = np.linspace(0, 50000, 50)
posterior_samples = idata.posterior["beta_media"].sel(beta_media_dim=0).values.flatten()
mean_effect = posterior_samples.mean() * np.log1p(search_spend)
low, high = np.percentile(posterior_samples * np.log1p(search_spend), [10,90], axis=0)
plt.plot(search_spend, mean_effect, label="Expected Revenue Impact")
plt.fill_between(search_spend, low, high, alpha=0.2, label="80% CI")
plt.xlabel("Search Spend ($)")
plt.ylabel("Incremental Revenue ($)")
plt.legend()
plt.show()Visualizing posterior intervals makes uncertainty explicit. Instead of arguing whether search elasticity is 0.4 or 0.6, you can show that it’s “most likely between 0.3 and 0.7.” That builds trust.
8) Budget frontier optimization

Once you have response curves, you can sweep across spend allocations and build an efficient frontier: the set of allocations that maximize expected revenue for each budget level.
import itertools
channels = ["search", "social", "tv"]
budget = 100000
allocations = []
for w in itertools.product(range(0,11), repeat=len(channels)):
if sum(w) == 10:
allocs = {ch: (wi/10)*budget for ch,wi in zip(channels,w)}
allocations.append(allocs)
def predict_rev(alloc, betas):
return betas[0]*np.log1p(alloc["search"]) + \
betas[1]*np.log1p(alloc["social"]) + \
betas[2]*np.log1p(alloc["tv"])
posterior_means = idata.posterior["beta_media"].mean(("chain","draw")).values
frontier = [(a, predict_rev(a, posterior_means)) for a in allocations]The frontier shows best allocations for each budget level. Present not just expected values but intervals to communicate risk.
This reframes the CMO’s question from “How much should I spend on search?” to “Where on the efficient frontier do we want to operate, given our risk tolerance?”
9) Practical pitfalls
- Attribution bias: MMM without experiments can over-credit correlated channels.
- Shape mis-specification: get adstock and saturation wrong, and your elasticity estimates collapse.
- CTR drift in SEO: curves change with SERP features, so update regularly.
- Overconfidence: always communicate credible intervals, not just means.
10) What “good” looks like in production
The gold standard stack looks like this:
- A Bayesian MMM refreshed weekly.
- Anchored by conversion lift tests and geo experiments.
- Supplemented with causal ML to find heterogeneity.
- Extended to SEO econometrics with current CTR priors.
- Delivered with response curves and budget frontier visualizations.
- Governed with model cards and refutation tests.
Organizations that build this stack have a defensible, finance-ready measurement system. Everyone else is stuck arguing with platform dashboards.
About the Author
Brian Curry is a Kansas City–based Data Scientist, Artificial Intelligence expert and founder of Vector1 Research, a part time passion project, where he explores the frontier of AI, SEO, Machine Learning, Economic Systems, Marketing Modeling, and Econometrics. His work focuses on building intelligent systems that reveal the causal structures driving performance across marketing, content, and economics and the creator of Papilon, an open-source framework for complex system optimization, and Daedalus, a platform for causal and economic analysis.
References
[1] Google. LightweightMMM Documentation. https://github.com/google/lightweight_mmm
[2] Meta. Robyn: An Open-Source MMM. https://facebookexperimental.github.io/Robyn/
[3] PyMC Developers. PyMC-Marketing Components. https://www.pymc.io/projects/marketing
[4] Brodersen et al. (2015). CausalImpact. https://google.github.io/CausalImpact/
[5] Meta. GeoLift Documentation. https://facebookexperimental.github.io/GeoLift/
[6] Backlinko. Organic CTR Research. https://backlinko.com/google-ctr-stats
[7] Advanced Web Ranking. CTR Benchmarks Q4 2024. https://www.advancedwebranking.com/ctrstudy/
[8] SISTRIX. SEO Click Potential Guides. https://www.sistrix.com/
[9] Microsoft Research. EconML Documentation. https://econml.azurewebsites.net/
[10] DoWhy Project. DoWhy Documentation. https://microsoft.github.io/dowhy/


