Skip to content

Budget Optimization & Constrained Portfolio Reallocation ​

How SpotLift replaces naive "lowest CPIV" sorting with econometric diminishing-returns saturation curves, marginal CPIV (mCPIV) equalization, and real-world media buying boundary constraints.


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:

  1. 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.
  2. 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.
  3. 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/Aggressive

See 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 i∈{1,…,N}, SpotLift models incremental visitor lift Ri(si) as a function of allocated spend si:

Ri(si)=αi⋅siβi

where:

  • si≥0 is the proposed budget allocated to channel i.
  • αi is the channel scale parameter calibrated against observed attribution:αi=L0,iS0,iβi(where S0,i is baseline flight spend and L0,i is attributed incremental lift).
  • βi∈(0,1) is the elasticity parameter (default: 0.70). When βi<1, the response curve is strictly concave, representing diminishing marginal returns.

2. Marginal Response & Marginal CPIV (mCPIV) ​

The marginal incremental lift of an additional euro spent is:

mRi(si)=dRidsi=αiβisiβi−1=βiRi(si)si

The Marginal Cost Per Incremental Visitor (mCPIV) is the reciprocal:

mCPIVi(si)=1mRi(si)=si1−βiαiβi=CPIVi(si)βi

Marginal vs. Average Cost

If elasticity β=0.70, a channel's marginal CPIV is approximately 1.43× higher than its average CPIV! As spend expands, mCPIV continues to rise until it reaches equilibrium with competing stations.

3. The Constrained Portfolio Optimization Problem ​

SpotLift solves the constrained non-linear optimization problem:

maxs1,…,sN∑i=1Nαisiβi

subject to:

  1. Total Budget Constraint:∑i=1Nsi=Btarget
  2. Channel Shift Boundaries:(1−max_shift)⋅S0,i≤si≤(1+max_shift)⋅S0,i
  3. Broadcaster Group Share Commitments (Optional):∑j∈Groupsj≥min_share⋅Btarget

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:

mCPIV1(s1∗)=mCPIV2(s2∗)=⋯=λ

Python API Usage ​

python
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:

bash
# 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.csv

Inspectable Terminal Output ​

text
====================================================================================
 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:

  1. 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.
  2. 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.
  3. 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:

  1. 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 11−λ. 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 (over collision_cluster_id or 2-hour windows) with Empirical-Bayes variance pooling (ν0=5) and explicit discrepancy_rel, avoiding naive i.i.d. Nt overconfidence.
    • Asymmetric Floor or Latent Immediate-Share (attach_pymc_lower_bound): Connects into PyMC via an asymmetric HalfNormal lower-bound potential (CTV,t≥direct_lift_floort) or a latent immediate-share model (direct_liftt∼N(ρ⋅CTV,t,σt2)).
python
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)
  1. 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 surfaces projected_lift_p10, projected_lift_p90, direct_lift_floor, extrapolation_warning, and low_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 (eliminating max(0,D) winner's-curse clipping) and exact draw conservation (∑gCg,t(m)=CTV,t(m)).
python
# 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"]
    ]
)

Released under the MIT License.