Phase Transitions in Media Portfolios: Detecting Regime Shifts Before ROAS Collapses

Your media portfolio is a driven dynamical system, and it fails the way lakes flip and markets crash — suddenly, with warning signs hiding in the noise. Here’s the physics of that failure, and an open-source monitor that catches it weeks in advance.
Every seasoned media buyer has lived this story. A channel mix that printed money for two years starts feeling heavier. ROAS drifts down a little, nothing alarming — well within the noise band. The MMM refresh comes back with slightly wider credible intervals but the same story: keep spending. Then, over six to ten weeks, performance falls off a cliff. Not a decline. A collapse. And no amount of budget poured back in restores the old efficiency.
The standard postmortem blames creative fatigue, auction crowding, audience saturation, an algorithm update — and those are all real. But they’re causes of a parameter drifting, not explanations of why the failure was sudden. The suddenness is the interesting part, and it has a precise mathematical character: it’s a phase transition. The portfolio didn’t degrade. It crossed a bifurcation point, its high-performance equilibrium ceased to exist, and the system fell to a lower branch.
This matters for one practical reason: systems approaching this kind of transition emit early warning signals — statistical precursors that appear in the time series before anything is visible in the level of the KPI. Ecologists use them to anticipate lake eutrophication and ecosystem collapse. Climate scientists use them for tipping points. Quants have used them for market crashes. Nobody, as far as I can find, has built them for media portfolios.
So I did. This article walks through the theory — critical slowing down, entropy collapse, energy landscapes, and topological data analysis — and a companion open-source library, mediaphase, that fuses all four into a single Regime Risk Index. On synthetic portfolios with known ground truth, it detects 8 of 8 collapses with a median lead time of about 12 weeks before the visible break, at a low false-alarm rate. All code below is runnable from the repo.
This piece extends the complex-systems primitives in my Papilon framework (entropy, energy, state-space, and topology modules) from descriptive analysis to predictive monitoring — the third leg of a stool that starts with attribution (MMM) and optimization (budget allocation) and ends here: stability.
Part 1: Why Your MMM Can’t See This Coming
Marketing mix models — mine included — share a structural blind spot: they are equilibrium models of a system that is not always at equilibrium.
Consider the canonical MMM response specification:
revenue_t = Σ_c β_c · saturation(adstock(spend_c,t)) + controls + ε_t
Everything load-bearing in that equation assumes a stationary regime. The saturation curve is fixed over the estimation window. The channel coefficients β_c are constants (or slowly varying at best). The error term is well-behaved noise around a stable conditional mean. Under those assumptions, the model answers “what is the marginal return of channel c” beautifully.
But a phase transition violates every one of those assumptions at once — and it does so before the transition, not just after. As the system approaches a bifurcation:
- The effective response surface deforms. The “saturation curve” you fit last quarter is a snapshot of a landscape that is now tilting.
- Residual autocorrelation rises. What your MMM diagnostics read as “mild misspecification, acceptable” is actually the single most important signal in the data.
- Variance inflates and skews. The widened credible intervals your Bayesian refresh reports are not estimation noise — they are the system telling you its restoring force is failing.
In other words: the symptoms your MMM workflow treats as degraded model quality are the disease itself. The information about the coming collapse is in exactly the parts of the data that regression-style models are built to average away.
This is not an argument against MMM. It’s an argument that MMM answers “where should the next dollar go within this regime,” and an entirely different instrument is needed to answer “how close is this regime to ending.”
Part 2: The Portfolio as a Driven Dynamical System
Strip a media portfolio to its dynamical skeleton. Let x_t be the portfolio’s response efficiency state — think of it as the latent multiplier connecting spend to response, the thing ROAS noisily measures. Its evolution is a stochastic differential equation:
dx = f(x, a) dt + σ dW
where f is the drift (the system’s restoring dynamics), σ dW is weekly noise (competitive moves, demand shocks, platform variance), and a is the slow control parameter — the aggregate of creative fatigue, frequency-driven audience burnout, auction pressure, and channel maturity. Crucially, a doesn’t jump. It drifts, week over week, driven by your own spending.
The simplest drift consistent with what practitioners observe — a high-performing regime and a degraded regime, with sudden transitions between them — is the normal form of a fold (saddle-node) bifurcation:
f(x, a) = −x³ + x − a
For small a, this system has two stable equilibria: a high branch (your good regime) and a low branch, separated by an unstable saddle. The portfolio lives on the high branch, jiggled by noise but pulled back by the drift. As a rises, the high branch and the saddle move toward each other. At the critical value
a_c = 2 / (3√3) ≈ 0.385
they annihilate. The high-ROAS equilibrium ceases to exist — not weakens, ceases — and the state falls to the low branch. That fall is your collapse.
Two properties of this structure explain the phenomenology every media veteran recognizes:
The collapse is sudden even though the cause is gradual. The control parameter drifted smoothly for a year; the state fell in weeks. Level-based monitoring is structurally incapable of anticipating this, because the level barely moves until the basin vanishes.
The collapse is hysteretic. Once on the low branch, reversing the control parameter to just below a_c does not restore performance — the system stays on the low branch until a falls far enough for the low branch to lose stability. This is why “we cut spend, things got worse, we restored spend, and it never came back” is such a common war story. It’s not incompetence. It’s hysteresis.
The question becomes: can we detect a approaching a_c without observing a directly? The answer, from three decades of critical-transition research, is yes — through four independent physical signatures.
Part 3: Four Physics of an Approaching Collapse
3.1 Critical Slowing Down
Linearize the dynamics around the occupied equilibrium x*: small perturbations decay as exp(λt), where λ = f′(x*) < 0. The magnitude |λ| is the restoring force — how fast the portfolio shrugs off a shock.
Here is the key theorem-level fact: as a → a_c, λ → 0. The basin flattens before it vanishes. Perturbations that used to decay in a week take a month. The system becomes sluggish — “critically slow.”
You can’t observe λ directly, but its fingerprints on the observable time series are unmistakable (Scheffer et al., Nature, 2009). Discretized at weekly cadence, the state near equilibrium behaves like an AR(1) process with
ρ₁ = exp(λ·Δt) (lag-1 autocorrelation) Var = σ² / (2|λ|) (stationary variance)
As λ → 0⁻:
- ρ₁ → 1 — this week’s deviation increasingly predicts next week’s, because nothing is pulling the state back.
- Variance → ∞ — the state wanders further in the flattening basin.
- |Skewness| rises — the state spends time near the saddle side of the basin, sampling the asymmetry.
These are computed on the detrended series over rolling windows, and their upward trend — summarized with a Kendall τ statistic, the standard in the early-warning literature (Dakos et al., 2012) — is the alarm-relevant quantity, not any single value.
3.2 Entropy Collapse
Information theory gives an independent view of the same approach. Three entropies matter:
Spectral entropy of the detrended response — the Shannon entropy of the normalized power spectrum. A healthy portfolio’s fluctuations are spectrally broad (many independent forces, no dominant timescale). Near a transition, variance concentrates at low frequencies — the spectrum “reddens” — because the slowing dominant mode swallows the dynamics. Spectral entropy falls.
H_spec = −Σ_f p(f) · log p(f), p(f) = PSD(f) / Σ PSD
Permutation entropy (Bandt & Pompe, 2002) — the entropy of ordinal patterns in the series. As the dynamics become enslaved to one slow mode, ordinal diversity collapses. This estimator is nearly assumption-free and robust to monotone distortions of the KPI, which makes it well suited to ratio metrics like ROAS.
Allocation entropy — the Shannon entropy of the spend-share vector, normalized to [0,1]. This one is structural rather than dynamical: portfolios drifting toward transitions often concentrate first (doubling down on the channel that still works), and falling allocation entropy quantifies that concentration as rising fragility. It’s the same diversity index ecologists use for ecosystems, applied to budgets.
3.3 The Energy Landscape and Basin Depth
For a stochastic system with stationary density p(x), the effective potential is
U(x) = −log p(x)
Estimate p(x) with a kernel density over a rolling window of the response state and you get a moving picture of the landscape the portfolio lives in: where the basins are, how many exist, and — the resilience metric — how deep the occupied basin is, measured as the potential difference between the basin minimum and the lowest escape barrier:
depth = U(barrier) − U(minimum)
Kramers’ escape theory says the expected time before noise kicks the system out of its basin scales as exp(depth/σ²) — resilience is exponential in depth. As a → a_c, the depth of the high-ROAS basin shrinks to zero. Watching a rolling estimate of basin depth is watching your portfolio’s survival time contract in real time. A landscape that flips from bimodal to unimodal in the rolling window is the post-transition signature — by then you’re reading history.
3.4 Topology: The Shape of the State Cloud
The fourth channel is the newest, and it’s the one imported from crash detection in finance. Gidea & Katz (Physica A, 2018) showed that the Lᵖ norms of persistence landscapes — a topological summary of sliding-window point clouds of equity returns — rose sharply ahead of the 2000 and 2008 crashes, ahead of conventional indicators.
The pipeline for a portfolio:
- Embed. Takens’ theorem (1981) says a delay-coordinate embedding of a single observable reconstructs the attractor’s topology. Map the detrended response series into ℝ³ with delay coordinates: z_t = (x_t, x_{t−τ}, x_{t−2τ}).
- Filter. Build the Vietoris–Rips filtration on each sliding-window cloud: grow balls of radius ε around every point and record when components merge (H0) and loops form and fill (H1).
- Summarize. Compute persistence lifetimes and take their L¹ norm (“total persistence”), plus persistent entropy (Atienza et al., 2020) for a scale-free variant.
The intuition: a healthy, noise-dominated regime produces a compact, blob-like state cloud with short persistence. As critical slowing sets in, trajectories become elongated and structured — excursions toward the saddle stretch the cloud — and total persistence rises and drifts. Topology sees the geometry of the wandering, which is information the moment statistics of Section 3.1 don’t fully capture. In tests on the simulator, the topology channel and the AC1 channel disagree often enough to make their agreement meaningful — which is exactly what you want from a fusion component.
One implementation note worth knowing: H0 persistence of a Vietoris–Rips filtration is exactly the edge-length spectrum of the Euclidean minimum spanning tree. That means the H0 pipeline needs nothing beyond SciPy — no specialized TDA stack — and runs in milliseconds on weekly windows. mediaphase computes H0 natively this way and treats ripser as an optional extra for H1.
Part 4: The Regime Risk Index
Each channel alone is noisy and each has known failure modes: EWS indicators can trend on smooth nonstationarity; entropy estimators are window-sensitive; KDE landscapes wobble in small samples; persistence norms react to volatility clustering. The design principle that makes the monitor usable is fusion with a quorum rule:
- Compute five component series over rolling windows: AC1, variance, spectral entropy, basin depth, and L¹ persistence norm.
- Orient each so that “more dangerous” is positive (entropy and depth get flipped).
- Z-score each against its own baseline window — the portfolio’s own healthy period is the null model.
- Fuse into the Regime Risk Index: RRI_t = weighted mean of component z-scores.
- Gate the alarm: fire only when RRI > θ and at least q components individually exceed their z threshold, sustained for p consecutive weeks.
The quorum term is the important design choice. A single hot indicator is a curiosity; three independent physics agreeing — the series is slowing and its spectrum is reddening and its state cloud is deforming — is evidence. The patience term suppresses one-week spikes from promos, holidays, and platform reporting glitches.
Part 5: The Code — mediaphase
Everything above is implemented in mediaphase, an open-source Python library (MIT, repo linked at the end). The dependency footprint is deliberately minimal: NumPy, SciPy, pandas. Here is the entire end-to-end workflow:
from mediaphase import PortfolioConfig, RegimeMonitor, simulate_portfolio
# 1. A five-channel portfolio driven through a fold bifurcation.
# Creative fatigue drifts slowly; the collapse week is known ground truth.
df = simulate_portfolio(PortfolioConfig(n_weeks=260, seed=7))
# 2. Portfolio-level order parameter: blended ROAS
spend = df.filter(like="spend_").sum(axis=1)
response = df.filter(like="response_").sum(axis=1)
roas = (response / spend).to_numpy()
# 3. Score regime risk
monitor = RegimeMonitor(window=40, baseline=60,
threshold=1.75, patience=6, quorum=3)
report = monitor.score(roas)
print(report.alarm_week) # first sustained alarm
print(report.index.tail()) # per-week component z-scores + RRIThe simulator is worth a word, because it’s the honesty mechanism of the whole project. It integrates the fold normal form dx = (−x³ + x − a)dt + σdW per channel, with cross-channel coupling and a linearly drifting fatigue parameter, then maps the latent efficiency state through a concave spend-response function to produce realistic spend, response, and ROAS series. Because the bifurcation crossing is known exactly, detection lead time can be measured against ground truth rather than anecdote — a discipline most “we predicted the downturn” claims never face.
Individual components are exposed for à la carte use:
from mediaphase import (rolling_ews, kendall_trend, spectral_entropy,
empirical_potential, basin_depth,
takens_embedding, h0_persistence, persistence_norm)
ews = rolling_ews(roas, window=40) # AC1, variance, skewness
tau = kendall_trend(ews["ac1"].to_numpy()) # EWS alarm statistic
land = empirical_potential(roas[-60:]) # U(x) = −log p̂(x)
depth = basin_depth(land, state=roas[-1]) # resilience, in nats
cloud = takens_embedding(roas[-40:], dim=3, delay=1)
tp = persistence_norm(h0_persistence(cloud), p=1) # total persistenceRunning python examples/demo.py produces the monitoring picture and this console report:
Bifurcation crossed (hidden) : week 174 Observable ROAS collapse : week 194 RRI alarm fired : week 127 Lead time before visible collapse: 67 weeks

Across eight seeded runs of the simulator with default settings, the benchmark is:
- Detection: 8/8 collapses flagged before the observable ROAS break
- Lead time: 7–67 weeks ahead of the visible collapse (median ≈ 12)
- False alarms: 1/8 on stationary control portfolios with no transition
Those numbers are on synthetic data and should be read as a demonstration of the mechanism, not a production guarantee — the point of shipping the simulator alongside the monitor is that you can stress-test the thresholds against your own noise levels before trusting anything.
Part 6: The Practitioner’s Playbook
What to feed it. The monitor wants a weekly (or daily, for large accounts) portfolio-level efficiency series with at least ~100 observations of healthy baseline. Blended ROAS works; marginal ROAS from your MMM’s response curves is better, because it strips the spend-mix confound; the deconfounded efficiency state from a state-space MMM is best. Run it per-channel as well — channel-level alarms localize the fatigue.
What an alarm is. Not a prediction of a collapse date. It is a statement that the restoring forces of your current regime are measurably weakening — the portfolio has lost resilience, and a shock that last year would have been absorbed may now tip it. Treat it like a cardiac stress test result, not a countdown clock.
What to do about it. The hysteresis property dictates the strategy: act on the control parameter, not the state. Pouring budget into a shallow basin buys weeks. Reversing fatigue rebuilds the basin. Concretely: rotate creative at the portfolio level rather than the channel level; redistribute frequency toward unsaturated audiences; raise allocation entropy deliberately (the monitor’s own diversity metric doubles as a lever); and stage new-channel incubation before the alarm, because post-collapse diversification happens from the low branch, at low-branch economics.
What it costs to ignore. The asymmetry is brutal. Pre-transition, restoring resilience costs a creative refresh and some reallocation. Post-transition, hysteresis means recovery requires driving fatigue far below where the collapse occurred — months of degraded performance while competitors occupy the auction space you vacated.
Honest limitations. Early warning signals have known failure modes and this system inherits them. Transitions driven by sudden external shocks (a platform policy change, a competitor’s launch) arrive without precursors — this instrument sees slowly driven bifurcations, not meteors. Strong seasonality must be removed before detrending or it masquerades as variance structure; the Gaussian detrender handles drift, not annual cycles. Short series are hostile territory: below ~two years of weekly data, the baseline z-scoring is fragile. And any monitored threshold invites Goodhart dynamics if it becomes a target rather than an instrument.
Where This Sits in the Stack
The measurement stack for media has three layers, and most organizations have built only two. Attribution answers what drove results (MMM, incrementality testing). Optimization answers where the next dollar goes (response curves, constrained allocation). Stability — this layer — answers whether the regime those first two layers assume will still exist next quarter.
mediaphase is the stability layer, designed to slot into the pipeline: fit attribution and response curves in Papilon’s MMM modules, hand the blended efficiency series to RegimeMonitor, and wire the alarm into an orchestration layer as a trigger — which turns regime risk from a slide in the quarterly review into an event your autonomous reallocation actually responds to.
The repo is at github.com/Bodhi8/mediaphase — MIT licensed, with the simulator, the full test suite, and the benchmark reproduction. Papilon, the parent framework, is at github.com/Bodhi8/papilon. If you run the monitor against a real portfolio history that contains a collapse you lived through, I’d genuinely like to see the trace — that’s the dataset this line of research needs next.
Your portfolio is a dynamical system whether you model it as one or not. The only choice is whether you find out where the cliff is from the map, or from the fall.
Brian Curry is the founder of Vector1 Research, an independent applied AI lab publishing open-source frameworks in causal inference, marketing economics, and cognitive AI architecture.
The assumption the whole series has been making
Every optimization so far — the constrained allocation of part one, the causal screening of part two, the optimal-control schedule of part three — takes the response curves as given. Estimate them, then optimize against them. That is the offline paradigm, and it has a blind spot it cannot see out of.
You never actually know the curves. You have noisy, finite estimates, and they are non-stationary — creative fatigues, auctions shift, competitors move, audiences saturate. Worse, offline optimization only ever exploits: it pours budget into whatever currently looks best and deliberately learns nothing. That creates a self-reinforcing trap. A channel that looks mediocre on thin or unlucky data gets underfunded, which means it generates even less data, which means its estimate never improves, which means it stays underfunded forever. The optimizer is confidently starving a channel it has never actually measured.
The fix is to treat allocation as sequential decision-making under uncertainty — to deliberately spend some budget on learning, not just earning. That is the multi-armed bandit problem, and its best-known solution, Thompson sampling, happens to be the exact Bayesian sibling of the MMM philosophy this series already runs on.
The multi-armed bandit
The setup, in its classic form: you face K arms (slot machines). Each has an unknown reward distribution. Each round you pull one arm and observe a stochastic reward. Your goal is to maximize cumulative reward over T rounds — equivalently, to minimize regret, the reward you forfeited by not always pulling the single best arm:
Regret(T) = T · μ* − Σ_{t=1}^T E[ reward at t ]where μ* is the mean of the best arm. Regret is the price of not knowing which arm is best from the start.
The mapping to media is direct: arms are channels, campaigns, creatives, audiences, or dayparts; a pull is an allocated impression or a unit of budget; the reward is a conversion or revenue. Every round you decide where to send the next unit of spend, observe what came back, and update.
The whole problem is the tension between two impulses. Exploit: send the budget to the arm that looks best so far. Explore: send it somewhere uncertain, to learn. Pure exploitation locks in early mistakes; pure exploration wastes money on known losers. The art is the schedule that trades them off, and the algorithms below are increasingly clever about it.
The baseline that works but wastes: ε-greedy
The simplest strategy: exploit the best-looking arm most of the time, but with probability ε pick a random arm to explore.
import numpy as np
def eps_greedy(true_rates, T, eps=0.1, seed=0):
r = np.random.default_rng(seed); K = len(true_rates)
n = np.zeros(K); s = np.zeros(K) # pulls, successes per arm
for t in range(T):
if r.random() < eps or (n == 0).any():
a = r.integers(K) # explore: uniform random
else:
a = np.argmax(s / np.maximum(n, 1)) # exploit: best empirical mean
reward = r.random() < true_rates[a]
n[a] += 1; s[a] += reward
return n, sIt works, and it is a fair baseline, but its exploration is dumb in two ways. It explores uniformly — spending as much probing an obviously terrible arm as a genuinely promising one — and it explores forever at a constant rate ε, even after the best arm is beyond doubt. Both are money left on the table. The better algorithms fix exactly these.
Optimism under uncertainty: UCB
The Upper Confidence Bound family replaces random exploration with a principle: be optimistic in the face of uncertainty. Rank each arm not by its estimated mean but by the top of a confidence interval around it, so arms that are either genuinely good or merely under-sampled both look attractive. UCB1 uses:
index(i) = μ̂_i + sqrt( 2 · ln t / n_i )
The second term is a bonus that shrinks as an arm is pulled more. Pull the arm with the highest index; a rarely-pulled arm carries a large bonus and gets probed, but only until its uncertainty collapses.
def ucb1(true_rates, T, seed=0):
r = np.random.default_rng(seed); K = len(true_rates)
n = np.zeros(K); s = np.zeros(K)
for t in range(T):
if t < K:
a = t # pull each arm once first
else:
mean = s / n
a = np.argmax(mean + np.sqrt(2 * np.log(t + 1) / n))
reward = r.random() < true_rates[a]
n[a] += 1; s[a] += reward
return n, sUCB1 comes with a guarantee: its regret grows only logarithmically in T (Auer, Cesa-Bianchi & Fischer, 2002), matching the asymptotic lower bound of Lai & Robbins (1985) up to constants. That is theoretically about as good as it gets. In practice, though, the constant matters — UCB1’s confidence bonus is conservative, and it often over-explores relative to the Bayesian alternative, as the horse-race below shows.
The centerpiece: Thompson sampling
Thompson sampling is the oldest idea here (Thompson, 1933) and, empirically, usually the best. It is also the most natural fit for a series built on Bayesian modeling, because it is Bayesian inference put in the loop.
Maintain a posterior distribution over each arm’s mean. Each round, draw one sample from every arm’s posterior, and pull the arm whose sample is highest. Observe the reward, update that arm’s posterior, repeat. For binary rewards (converted / didn’t), the Beta distribution is conjugate to the Bernoulli, so the update is just incrementing counts:
def thompson(true_rates, T, seed=0):
r = np.random.default_rng(seed); K = len(true_rates)
alpha = np.ones(K); beta = np.ones(K) # Beta(1,1) uniform prior per arm
for t in range(T):
theta = r.beta(alpha, beta) # one posterior sample per arm
a = np.argmax(theta) # pull the best sampled arm
reward = r.random() < true_rates[a]
alpha[a] += reward; beta[a] += (1 - reward) # conjugate update
return alpha, betaThe elegance is in what this does automatically. An arm is pulled in proportion to the posterior probability that it is the best arm — a strategy called probability matching. Early on, wide posteriors mean lots of sampling variety and heavy exploration. As evidence accumulates, posteriors concentrate, optimistic draws for bad arms become rare, and the algorithm smoothly shifts to exploitation. Nobody tunes an exploration rate; the exploration anneals itself out of the uncertainty. And it is not just pretty — Thompson sampling provably achieves the same optimal logarithmic regret as UCB (Agrawal & Goyal, 2012; Kaufmann, Korda & Munos, 2012), while typically beating it in finite samples.
The horse race (real numbers)
Five channels with true conversion rates of 3.0%, 5.0%, 4.0%, 5.5%, and 2.0% — arm four is best. Running each algorithm for 20,000 rounds, averaged over 40 seeds, cumulative regret (lower is better):
eps-greedy (ε=0.1) regret ≈ 88.4 UCB1 regret ≈ 236.1 Thompson sampling regret ≈ 53.9
Thompson wins decisively — roughly a third of ε-greedy’s regret and a quarter of UCB1’s. (UCB1’s poor showing here is not a bug: its conservative confidence bonus makes it over-explore when reward gaps are small and the horizon is moderate — a well-documented finite-sample effect, and a useful reminder that asymptotic bounds are not finite-sample performance.)
Look at how Thompson actually spent those 20,000 pulls across the five arms:
pull share: [0.032, 0.295, 0.015, 0.642, 0.016]
arm0 arm1 arm2 arm3 arm4It put 64% of budget on the true-best arm and 30% on the close runner-up (5.0% vs 5.5% — a gap small enough to be worth continued probing), while starving the three clearly-worse arms down to a couple of percent each. That is exactly the behavior you want from a media allocator: concentrate on the winners, keep a live hedge on the plausible contender, and stop wasting money on the losers — without anyone hand-setting a single exploration parameter.
From arms to budgets: the bridge to MMM
Classic bandits pick one arm per round. Media allocation splits a budget across many channels every period. Two moves connect them, and the second ties this article back into the rest of the series.
Probability-matching allocation. Instead of pulling the single arm with the highest posterior draw, split the budget across channels in proportion to the posterior probability that each is the most efficient. Estimate those probabilities by drawing many joint samples and counting wins. You get a soft, uncertainty-aware split that automatically over-weights likely winners while keeping exploratory budget on contenders.
Posterior sampling for optimization — the elegant one. This is Thompson sampling lifted from single arms to the entire allocation problem, and it reuses the machinery already built in parts one and three:
# Each planning period: curves_draw = mmm.sample_response_curves() # ONE draw from the MMM posterior allocation = optimize_budget(curves_draw, B, guardrails) # part-one optimizer on the draw execute(allocation) observe(revenue); mmm.update(new_data) # posterior sharpens
Each period you draw one plausible set of response curves from the Bayesian MMM’s posterior and optimize the budget as if those curves were true, then execute and update. Because the draw is random, a channel with an uncertain curve will occasionally get an optimistic draw, receive more budget, and generate the very data that resolves its uncertainty. Exploration falls out of posterior uncertainty exactly as in single-arm Thompson — but now over channels, adstock, and saturation simultaneously. The offline optimizer and the online learner turn out to be the same algorithm viewed at two time scales.
Contextual bandits: when the best arm depends on the situation
Reward rarely depends only on the arm. It depends on context — season, audience segment, device, daypart, geo. A contextual bandit learns a reward model over features and picks the arm that maximizes predicted reward for the current context. The linear version keeps a Bayesian regression over feature weights, samples the weights (Thompson) or takes an optimistic bound (LinUCB), and chooses accordingly.
This is the real bridge to MMM. A contextual bandit whose context includes the spend level is nothing other than an online, exploring response-curve learner — it is estimating the same saturation relationship the MMM fits offline, but updating continuously and directing its own data collection toward the regions of the curve it is least sure about. Offline MMM and online contextual bandits are two implementations of one underlying object.
The two things that break naive bandits in media
Textbook bandits assume a stationary world with instant feedback. Media violates both, and ignoring that quietly wrecks performance.
Non-stationarity. Response curves drift, and a vanilla bandit that has accumulated thousands of observations builds a posterior so concentrated it cannot react when the world changes — it locks onto a winner that has quietly become a loser. The fix is to forget: discount old evidence or keep a sliding window, so the posterior stays responsive. But forgetting is not free. In a simulation where the best arm abruptly switches at the halfway mark:
pre-switch regret post-switch regret vanilla Thompson 41.0 135.2 discounted Thompson (γ=0.9995) 77.0 98.6
Vanilla Thompson exploits beautifully while the world holds still (regret 41) but adapts painfully after the change (135). Discounting pays a standing tax during the stable stretch (77) to buy much faster recovery when the change hits (99). There is no universally right answer — the discount rate encodes how non-stationary you believe your channels are, and in media, where creative fatigue and competitive shifts are constant, some forgetting is almost always worth its price.
Delayed feedback. A conversion may land days or weeks after the impression that caused it. The bandit, updating on what has resolved so far, is always learning from a censored, stale view of reward — and will happily under-credit long-lead channels. Handling it means modeling the attribution/conversion delay explicitly, updating with partial rewards, and matching the decision cadence to the feedback horizon rather than pretending rewards are instant.
Where it fits: two loops, two clocks
Bandits do not replace the marketing mix model; they run at a different clock speed. MMM is the slow, strategic loop — quarterly, causal, calibrated against experiments, deciding the channel mix and the shape of the response curves. Bandits are the fast, tactical loop — daily or hourly, allocating within a channel across campaigns, creatives, and audiences, reacting to drift long before the next MMM refresh. And they compose cleanly: the MMM posterior is the ideal prior that warm-starts the bandit, so the fast loop begins from hard-won causal knowledge instead of a uniform guess, and the bandit’s accumulated data flows back to sharpen the next MMM. In Papilon this is the coupling between the Bayesian modeling layer and the sequential-decision layer — the offline model hands the online learner its priors; the online learner hands the offline model its data.
The takeaway
The first three parts of this series answered where, which way, and when, all under the fiction that the response curves are known. This part drops the fiction. When the curves are uncertain and moving — which is always — the right move is not to optimize harder against a stale estimate but to spend a little budget, deliberately, on learning. Thompson sampling is the disciplined way to do that: hold a posterior, sample it, act on the sample, update, and let exploration anneal itself out of your shrinking uncertainty. It beat every alternative in the horse race not by exploring more, but by exploring smarter — probing exactly where it was unsure and nowhere else.
Optimization assumes you know the world. Bandits admit you don’t, and turn that admission into a strategy. In a market that never holds still, that is the more honest — and the more profitable — posture.
All algorithms are runnable as written with numpy; the regret figures and pull shares are actual output over 20,000 rounds averaged across seeds. Regret bounds referenced: Lai–Robbins (1985) lower bound; UCB1 (Auer et al., 2002); Thompson sampling optimality (Agrawal–Goyal, 2012; Kaufmann et al., 2012). Bandits optimize the fast tactical loop; pair them with a causally-calibrated MMM (parts one through three) for the strategic one.
References
Critical transitions and early warning signals
- Scheffer, M., Bascompte, J., Brock, W. A., et al. (2009). Early-warning signals for critical transitions. Nature, 461, 53–59.
- Dakos, V., Carpenter, S. R., Brock, W. A., et al. (2012). Methods for detecting early warnings of critical transitions in time series illustrated using simulated ecological data. PLoS ONE, 7(7), e41010.
- Kuehn, C. (2011). A mathematical framework for critical transitions: Bifurcations, fast–slow systems and stochastic dynamics. Physica D, 240(12), 1020–1035.
Topological data analysis
- Gidea, M., & Katz, Y. (2018). Topological data analysis of financial time series: Landscapes of crashes. Physica A, 491, 820–834.
- Takens, F. (1981). Detecting strange attractors in turbulence. Dynamical Systems and Turbulence, Lecture Notes in Mathematics, 898, 366–381.
- Atienza, N., González-Díaz, R., & Soriano-Trigueros, M. (2020). On the stability of persistent entropy and new summary functions for topological data analysis. Pattern Recognition, 107, 107509.
Information theory and complexity
- Bandt, C., & Pompe, B. (2002). Permutation entropy: A natural complexity measure for time series. Physical Review Letters, 88(17), 174102.
Stochastic dynamics
- Kramers, H. A. (1940). Brownian motion in a field of force and the diffusion model of chemical reactions. Physica, 7(4), 284–304.
Software
- Curry, B. (2026). mediaphase: Phase-transition detection for media portfolios. github.com/Bodhi8/mediaphase
- Curry, B. (2025). Papilon: Complex systems and causal discovery for marketing. github.com/Bodhi8/papilon
Brian Curry is a Kansas City–based cognitive systems architect and applied AI researcher working at the intersection of AI architecture, knowledge engineering, and intelligent-system design. He is the founder of Vector1 Research, an applied AI lab publishing on agent systems, AI evaluation, cognitive architecture, and the macroeconomics of AI-driven transformation. He has led applied AI and data initiatives across major enterprises including Koch Industries, Tractor Supply, Vail Resorts, Hallmark, Garmin, AT&T, and McClatchy, and founded KC AI Lab, a 1,500-member AI/ML community. His open-source work includes Papilon (causal inference and counterfactual simulation), PyCausalSim (causal discovery via agent-based simulation), MeaningFlow (semantic modeling and ontology), and Memory-Node Encapsulation (episodic memory for agentic systems). Contact: brian at vector1.ai · LinkedIn


