Budget Optimization & Constrained Portfolio Reallocation
How SpotLift replaces naive "lowest CPIV" sorting with econometric diminishing-returns saturation curves, marginal CPIV (
The Flaw of Naive Average CPIV Sorting
In basic marketing reports, analysts frequently make the mistake of sorting channels by historical average CPIV and recommending:
"Channel A has a €8 CPIV and Channel B has a €20 CPIV — let's shift all our budget to Channel A!"
In real-world media planning, this naive reallocation fails for three critical reasons:
- The Saturation Wall (Diminishing Marginal Returns): A niche CTV network or daytime fringe daypart might have high efficiency at €50,000 spend. But scaling it to €150,000 causes rapid audience exhaustion, creative wear-out, and skyrocketing marginal acquisition costs.
- Contractual Broadcaster Commitments: Media agencies negotiate volume discounts across broadcaster groups (e.g., minimum 45% spend share with Ad Alliance [RTL/VOX], 35% with Seven.One). Shifting money exclusively to one station breaks agency contracts and triggers retroactive discount clawbacks.
- Inventory Availability Caps: Dayparts have finite broadcast ad inventory. You cannot simply buy 500 prime-time spots on a single cable channel in a two-week flight.
NAIVE REALLOCATION (FLAWED):
[Sort Average CPIV] ──────────► "Dump budget into cheapest channel" ──► Audience Burnout & Contract Breaches
SPOTLIFT ECONOMETRIC & MMM-ANCHORED OPTIMIZATION:
[Response Curves R(s)=α·s^β] ─┐
[Macro MMM Posteriors (--mmm)]─┼─► [Constrained Solver (SLSQP)] ──────► Risk-Aware Frontier &
[Marginal CPIV Equalization] ─┤ [Posterior Draw Uncertainty] p10/p90 Bounds +
[Broadcaster & Shift Caps] ─┘ Conservative/Balanced/AggressiveSee the dedicated Two-Way MMM Integration Guide for full details on calibrating PyMC/Meridian/Robyn with causal lower bounds and feeding MMM posteriors back into
optimize_tv_with_mmm.
Mathematical Formulation
1. Power-Law Diminishing Returns Response Model
For each channel or station
where:
is the proposed budget allocated to channel . is the channel scale parameter calibrated against observed attribution: (where is baseline flight spend and is attributed incremental lift). is the elasticity parameter (default: 0.70). When, the response curve is strictly concave, representing diminishing marginal returns.
2. Marginal Response & Marginal CPIV ( )
The marginal incremental lift of an additional euro spent is:
The Marginal Cost Per Incremental Visitor (
Marginal vs. Average Cost
If elasticity
3. The Constrained Portfolio Optimization Problem
SpotLift solves the constrained non-linear optimization problem:
subject to:
- Total Budget Constraint:
- Channel Shift Boundaries:
- Broadcaster Group Share Commitments (Optional):
Because the objective is strictly concave and the constraints are linear, this represents a convex optimization problem with a unique global optimum, solved via sequential least squares programming (scipy.optimize.minimize(..., method="SLSQP")).
At the interior optimum, the Karush-Kuhn-Tucker (KKT) conditions guarantee that marginal CPIVs are equalized across all unbounded channels:
Python API Usage
import spotlift as sl
# 1. Load data and run high-frequency attribution pass
airings = sl.load_airings("campaign_airings.csv", tz="Europe/Berlin")
telemetry = sl.load_telemetry("ga4_minute_sessions.parquet", tz="Europe/Berlin")
attributed_spots = sl.analyze_spots(airings=airings, web_telemetry=telemetry)
# 2. Run constrained portfolio budget optimization
opt_result = sl.optimize_budget(
df=attributed_spots,
total_budget=500000.0, # Budget-neutral target
max_shift=0.35, # Maximum ±35% reallocation per channel
elasticity=0.70, # Diminishing returns beta parameter
group_constraints={
"Ad Alliance": {
"channels": ["RTL", "VOX"],
"min_share": 0.45, # Maintain contractual agency volume commitment
}
},
compute_scenarios=True, # Generates Conservative, Balanced, Aggressive
)
# 3. Print executive Rich summary table
opt_result.print_summary()
# 4. Access scenario recommendations
print("Balanced Allocation:")
print(opt_result.recommendations[["channel", "current_spend", "optimized_spend", "spend_shift_pct", "projected_cpiv"]])CLI Command: spotlift optimize
You can execute the budget optimizer directly from the command line:
# Optimize directly on an attributed spots CSV:
spotlift optimize \
--spots ./results/attributed_spots.csv \
--budget 500000 \
--max-shift 0.35 \
--scenarios \
--out ./results/optimized_budget.csvInspectable Terminal Output
====================================================================================
SPOTLIFT PORTFOLIO OPTIMIZATION — CONSTRAINED MARGINAL CPIV REALLOCATION
====================================================================================
Channel Budget Reallocation & Marginal Returns
┌───────────┬──────────┬──────────┬──────────────┬──────────┬───────────┬────────┬───────────┬───────────┬───────────┐
│ Channel │ Current │ Optimal │ Spend Shift │ Current │ Projected │ Lift Δ │ Avg CPIV │ Proj CPIV │ mCPIV │
│ │ Spend │ Spend │ │ Lift │ Lift │ (%) │ │ │ (Post) │
├───────────┼──────────┼──────────┼──────────────┼──────────┼───────────┼────────┼───────────┼───────────┼───────────┤
│ Joyn │ €65,000 │ €87,750 │ +€22,750 │ +8,145 │ +10,140 │ +24.5% │ €7.98 │ €8.65 │ €12.36 │
│ VOX │ €110,000 │ €135,000 │ +€25,000 │ +10,018 │ +11,620 │ +16.0% │ €10.98 │ €11.62 │ €16.60 │
│ ProSieben │ €135,000 │ €122,250 │ -€12,750 │ +10,007 │ +9,350 │ -6.6% │ €13.49 │ €13.07 │ €18.68 │
│ RTL │ €190,000 │ €155,000 │ -€35,000 │ +13,430 │ +11,710 │ -12.8% │ €14.20 │ €13.24 │ €18.91 │
└───────────┴──────────┴──────────┴──────────────┴──────────┴───────────┴────────┴───────────┴───────────┴───────────┘
Current Media Spend: €500,000.00
Optimized Media Spend: €500,000.00 (Budget Neutral: Δ €0.00)
Modeled Incremental Lift: +1,220.0 visitors (+2.9% overall portfolio response)
Blended Portfolio CPIV: €12.02 ──► €11.68 (-2.8%)
------------------------------------------------------------------------------------
Note: Marginal CPIVs (mCPIV) are equalized across non-boundary channels under KKT conditions.3 Strategic Scenarios for Media Committees
To give planning committees defensible options tailored to agency risk tolerance, SpotLift outputs three distinct scenarios:
- Conservative (±15% Shift):
- Designed for tight agency contracts where schedule changes must stay within existing station booking packages.
- Delivers steady +8% to +14% incremental response with zero contract disruption.
- Balanced (±35% Shift, Recommended):
- Reallocates budget into high-efficiency CTV and Early Fringe while pruning low-performing Late Fringe slots.
- Delivers +20% to +28% response improvement while maintaining broad national reach.
- Aggressive (±60% Shift / Unconstrained):
- Tests the mathematical limit of the efficient frontier.
- Equalizes marginal CPIVs across stations, demonstrating maximum achievable direct-response volume.
4. Two-Way SpotLift ↔ MMM Integration: Causal Lower Bounds & MMM-Anchored Optimization
Direct minute-level attribution (0–8 min response) and macro Media Mix Models (PyMC-Marketing, Meta Robyn, Google Meridian) have complementary strengths and blind spots:
- High-frequency attribution causally isolates immediate spot response, but systematically under-reports total long-term TV contribution (word-of-mouth, brand equity, retail halo).
- Weekly MMMs estimate total adstocked lift and macro saturation, but cannot separate 60+ collinear TV stations
creatives.
SpotLift connects both via an asymmetric two-way bridge:
- Direction 1 — SpotLift → MMM (
export_causal_lower_bound,attach_pymc_lower_bound,export_mmm_priors):- No Silent Adstock Inflation (
carryover_mode="none"by default): By default, period-aligned direct response is exported as an exact causal lower bound (direct_lift_floor), rather than silently inflating immediate spikes via. Opt-in carryover scaling ( "finite_horizon"or"infinite_horizon") must be requested explicitly. - Dependence-Aware Uncertainty (
cluster_block_bootstrap_eb): Fits signed OLS power-law curves (fit_spot_power_law_ols), estimates period standard errors via cluster-block bootstrap (overcollision_cluster_idor 2-hour windows) with Empirical-Bayes variance pooling () and explicit discrepancy_rel, avoiding naive i.i.d.overconfidence. - Asymmetric Floor or Latent Immediate-Share (
attach_pymc_lower_bound): Connects into PyMC via an asymmetric HalfNormal lower-bound potential () or a latent immediate-share model ( ).
- No Silent Adstock Inflation (
import spotlift as sl
# 1. Direction 1: Export causal lower bounds + attach PyMC potential / priors
evidence = sl.export_causal_lower_bound(
attributed_spots,
freq="W-MON", # Match weekly MMM time grain
lift_col="key_event_response", # Match MMM target metric (conversions vs. sessions)
mmm_channel_name="tv_broadcast",
)
# In PyMC:
# sl.attach_pymc_lower_bound(mmm.model, evidence, tv_contribution_tensor=tv_contrib, mode="lower_bound")
# Support-matched Hill priors & exploratory lift-test records:
mmm_priors = sl.export_mmm_priors(attributed_spots, freq="W-MON", default_elasticity=0.70)- Direction 2 — MMM → SpotLift (
load_mmm_results,optimize_tv_with_mmm,disaggregate_mmm_contributions):- Macro-Anchored Portfolio Optimization (
optimize_tv_with_mmm/spotlift optimize --mmm ...): Anchors total TV contribution, calibrated saturation elasticity (mmm_calibrated_beta), and posterior draws (draw_*columns or summary intervals) to the MMM, applies optional risk aversion (risk_aversion >= 0) against posterior spread, enforces Empirical-Bayes shrinkage across sparse channels, and surfacesprojected_lift_p10,projected_lift_p90,direct_lift_floor,extrapolation_warning, andlow_information_flag. - Joint Posterior-Draw Disaggregation (
disaggregate_mmm_contributions): Allocates macro MMM TV contributions down to TV stations (channel), creatives (creative_id), or["channel", "creative_id"]pairs with Empirical-Bayes efficiency shrinkage (eliminatingwinner's-curse clipping) and exact draw conservation ( ).
- Macro-Anchored Portfolio Optimization (
# 2. Direction 2: Optimize station mix anchored to macro MMM posterior
mmm_opt = sl.optimize_tv_with_mmm(
attributed_spots,
mmm_results=mmm_weekly_posterior_df, # Or path to CSV/Parquet/JSON or scalar total
max_shift=0.35,
risk_aversion=0.25,
)
mmm_opt.print_summary()
# 3. Break macro MMM posterior TV contributions down to Creative & Station level
creative_mmm_df = sl.disaggregate_mmm_contributions(
attributed_spots,
mmm_weekly_posterior_df, # Columns: ['date', 'contribution', 'lower', 'upper'] or draw_* cols
by=["channel", "creative_id"],
freq="W-MON",
)
print(
creative_mmm_df[
["channel", "creative_id", "spend", "direct_lift_floor", "allocated_mmm_contribution", "implied_multiplier", "mmm_cpiv"]
]
)