Competitive Intelligence Warfare: Reverse-Engineering Competitor Media Spend with Papilon

By Brian Curry | Vector1 Research


“Prediction is very difficult, especially if it’s about the future.” — Niels Bohr

Your competitors don’t publish their media plans. But the market does.


Abstract

Marketing Mix Modeling has always been used to look inward — to measure what your own media spend produces. But Papilon’s causal discovery and complex systems architecture makes a more aggressive application possible: looking outward, at public market signals, and inferring what your competitors are spending, where, and why. This article introduces a novel methodology called Competitive Media Signal Inversion (CMSI) — using Papilon’s causal inference, topology, and entropy modules to reverse-engineer competitor media allocation from observable market phenomena. The technique is legal, scalable, and produces surprisingly precise probabilistic estimates of share-of-voice, channel mix, and seasonal burst patterns for any market-visible competitor. We walk through the full four-stage architecture with implementation code, mathematical formulation, validation methodology, and strategic application — demonstrating how a market intelligence discipline previously reserved for agencies with access to proprietary spend data can now be built by any data science team with public signals and the right causal framework.


I. The Intelligence Gap in Modern MMM

Every data science team running MMM is solving the same equation: given what we spent, what did we get? It is a backward-looking exercise by design. You feed in your own media data, your own sales, your own adstock curves — and the model tells you whether TV or paid search drove more incremental revenue last quarter.

But here is the problem: your marketing doesn’t operate in a vacuum.

Your brand’s ROAS doesn’t just depend on your spend — it depends on the competitive pressure in the market at the same time. When a competitor doubles their TV weight in Q3, your base sales erode. When they flood paid search with aggressive bidding, your CPCs spike and your paid traffic efficiency collapses. When they go dark for six weeks in a market where they previously dominated, your own efficiency mysteriously improves and your model attributes it to some mysterious “base sales” lift.

Traditional MMM accounts for none of this. Competitive spend is either ignored entirely, proxied by crude “competitive pressure” indices drawn from expensive proprietary data sources, or treated as an unmeasured confounder that quietly inflates your residuals and corrupts your channel-level attribution. The result is a model that’s technically correct about your own media and systematically wrong about why your market outcomes moved the way they did.

The academic MMM literature has long acknowledged this gap. Jin et al. (2017) note that omitted competitive variables represent one of the most common sources of model misspecification in MMM. Robyn’s documentation explicitly flags competitive spend as an “external factor” that should be included when available — without offering a way to obtain it. PyMC-Marketing’s documentation doesn’t mention competition at all.

The reason the gap persists is simple: competitive media spend data is expensive. Nielsen Ad Intel, Kantar Media, Pathmatics Enterprise — the tools that provide actual competitive spend observation — cost tens to hundreds of thousands of dollars annually and are out of reach for most in-house teams. Even agencies with access to these tools typically use them for descriptive reporting rather than causal modeling.

Papilon offers a different path. Its architecture — built around causal discovery, complex system dynamics, and information theory primitives — gives us the tools to model the market as a causal system and infer competitor behavior from what that system observably produces. This article documents that methodology in full.


II. Theoretical Foundation: Markets as Causal Dynamical Systems

Before the methodology, the theory. CMSI rests on three interconnected ideas from causal inference and complex systems science.

2.1 The Causal Inversion Principle

Standard causal inference flows from causes to effects. We have a treatment variable X (media spend), we observe an outcome Y (sales), and we estimate the causal effect E[Y | do(X=x)]. This is the forward problem.

The inverse problem runs the other direction: we observe effects Y (market signals) and want to recover the latent cause X (competitor spend) that produced them. This is a well-studied but technically harder problem. In causal terms, we are trying to estimate the pre-image of an observed outcome distribution under a known (or estimable) causal mechanism.

Formally, let C represent competitor media spend (unobserved vector across channels and time) and S represent the vector of observable market signals. The causal model posits:

S = f(C, Z, ε)

where Z represents other structural drivers (seasonality, category trends, macroeconomic factors) and ε is noise. Our task is to estimate C given S, a learned f, and estimated Z. Papilon’s causal discovery modules learn the structure of f from data; the inverse estimation step recovers the implied C.

This is only tractable under identifiability conditions — principally, that different competitor spend profiles produce distinguishably different signal signatures. We will address when this holds and when it breaks down.

2.2 The Fingerprint Hypothesis

A competitor’s media spend is invisible to you directly. But it isn’t invisible to the market. Heavy media investment leaves causal fingerprints across the observable information landscape:

  • Branded search volume rises when awareness spend increases — typically with a 1–3 week lag for TV, 0–1 week for digital
  • Share of voice shifts in monitored category environments as impressions accumulate
  • Organic social mentions spike with campaign launches and sustain through the flight
  • Pricing behavior changes with promotional media bursts — competitors often couple price promotions with media weight
  • Category-level sales lift when a major player runs effective media, even for non-buyers of that brand (category expansion effect)
  • Your own model residuals systematically absorb competitive pressure as unexplained positive or negative variance

The fingerprint hypothesis holds that these signals, taken together and analyzed causally, contain recoverable information about the spend that produced them. The strength of the signal is a function of the competitor’s market presence: larger share brands produce cleaner, more recoverable signatures.

2.3 Transfer Entropy as a Causal Detector

The third theoretical pillar is information-theoretic. Transfer entropy, introduced by Schreiber (2000), measures the directed flow of information between time series — how much knowing the history of variable X reduces uncertainty about the future of variable Y, beyond what Y’s own history already tells you.

For competitive intelligence, transfer entropy serves as a non-parametric, model-free detector of causal influence. We expect high transfer entropy from competitor media signals into market outcomes during active spend periods, and low transfer entropy during dark periods. The time-varying structure of transfer entropy thus functions as a spend activity detector independent of our parametric causal model — providing a crucial second line of evidence.

The Kraskov-Stögbauer-Grassberger (KSG) estimator used in Papilon’s entropy module is particularly well-suited here because it handles non-linear causal relationships without requiring distributional assumptions, which matters when the relationship between media spend and market signals involves saturation effects and nonlinear response curves.


III. The CMSI Architecture: Four Stages

The Competitive Media Signal Inversion methodology runs in four stages. Each maps directly to Papilon modules. Together they form a complete pipeline from raw public data to probabilistic competitor spend estimates.

Stage 1: Signal Collection and the Competitor Observability Matrix

Before any causal work, we assemble the observable signal portfolio. The goal is to construct a competitor observability matrix — a panel dataset of weekly signals that collectively encode the competitive media environment.

Primary signals (direct media observability):

  • Share-of-voice from ad monitoring APIs (Pathmatics, SimilarWeb free tier, SpyFu) — weekly branded SOV and category SOV
  • Branded search volume from Google Trends — a reliable, free, high-coverage proxy for awareness spend
  • Paid search impression share for target keyword clusters from SEMrush or Ahrefs

Secondary signals (induced market effects):

  • Social listening volume and sentiment from free-tier tools (Mention, Brand24) or social platform APIs
  • Category-level sales index from any available syndicated source (Nielsen, IRI, Circana) or as a construct from your own sales data
  • Website traffic estimates for competitor domains (SimilarWeb free estimates provide directional accuracy)
  • App download velocity if category-relevant (Sensor Tower free estimates)

Internal signals (your own model’s output):

  • MMM model residuals — this is the most underappreciated input. Your model’s unexplained variance is a direct window into everything the model didn’t account for, including competitor activity
  • Base decomposition from your current MMM — the “base sales” trend often absorbs competitive effects
import papilon as pp
import pandas as pd
import numpy as np

# Build the competitor observability matrix
# Assume weekly frequency, 3 years of history (156 periods)
signals = pd.DataFrame({
    'date': pd.date_range('2022-01-03', periods=156, freq='W-MON'),
    
    # Direct media observability
    'competitor_sov_pct': branded_sov_series,          # 0-100 scale
    'competitor_branded_search': google_trends_index,   # 0-100 scale
    'competitor_paid_impressions': paid_search_est,     # estimated weekly impressions
    
    # Induced market effects
    'social_mention_volume': social_listening_weekly,
    'category_sales_index': syndicated_index,           # indexed to 100
    'competitor_site_traffic': similarweb_weekly,       # estimated visits
    
    # Your internal signals
    'own_mmm_residuals': mmm_residuals_weekly,
    'own_base_sales': mmm_base_decomp,
    'own_roas_realized': realized_roas_weekly,
    
    # Structural controls (not signals — covariates to partial out)
    'seasonality_index': seasonal_factors,
    'macro_consumer_sentiment': conference_board_weekly,
    'category_price_index': category_avg_price
})

# Partial out structural controls before causal analysis
signals_demeaned = pp.core.states.partial_out(
    signals,
    controls=['seasonality_index', 'macro_consumer_sentiment', 'category_price_index'],
    method='ols_residualize'
)

# Normalize to stationary time series
signals_stationary = pp.core.states.normalize_panel(
    signals_demeaned,
    method='first_difference',
    handle_seasonality=True,
    test_stationarity=True    # runs ADF test, warns if non-stationary remains
)

print(f"Observability matrix shape: {signals_stationary.shape}")
print(f"Signal coverage: {signals_stationary.notna().mean().to_dict()}")

A practical note on signal quality: not all signals are equally recoverable for all competitors. The observability quality varies by competitor size, category type, and data availability. We recommend constructing a signal coverage audit before proceeding:

Signal Type Large National Brand Regional Brand D2C Digital Brand Branded Search High Medium High SOV (TV/Video) High Low Low Paid Search Impressions Medium Medium High Social Mentions High Low High Category Sales Lift High Low Medium Own Model Residuals High High High

For brands where SOV data is unavailable, branded search volume plus social mention velocity alone are often sufficient to construct usable CMSI estimates for the largest spend channels.

Stage 2: Causal Graph Discovery on the Signal Network

This is the core of the methodology. We run causal structure learning over the observability matrix to discover the directional relationships between signals — building a causal graph that encodes which signals influence which others, with what lag, and with what strength.

The critical algorithmic choice here is FCI (Fast Causal Inference) rather than the simpler PC algorithm. PC assumes causal sufficiency — that all common causes are measured. We explicitly violate this assumption: competitor spend is an unmeasured common cause of multiple observed signals. FCI was designed for exactly this case, representing latent common causes as bidirected edges in the Partial Ancestral Graph (PAG) it produces.

# Run FCI causal discovery on the signal network
causal_model = pp.causal.discovery.run(
    data=signals_stationary,
    method='fci',
    alpha=0.05,               # conditional independence test threshold
    ci_test='kci',            # Kernel CI test — handles nonlinear relationships
    max_lag=8,                # search up to 8-week lag structure
    time_series=True,
    n_jobs=-1                 # parallelize independence tests
)

# Inspect the discovered graph
print(causal_model.graph_summary())
# Output includes:
# - Directed edges (X → Y): X causes Y
# - Bidirected edges (X ↔ Y): latent common cause
# - Partially directed edges: orientation uncertain

# The latent nodes represent inferred unmeasured causes —
# competitor spend will appear here as a latent driving multiple observables
latent_nodes = causal_model.get_latent_nodes()
print(f"Inferred latent variables: {len(latent_nodes)}")
for node in latent_nodes:
    print(f"  L{node.id}: drives {node.children} with lags {node.lag_structure}")

What we expect to see in the discovered graph — and which serves as a sanity check on the methodology — is a latent node that drives: branded search volume (lag 1–2 weeks), SOV (lag 0–1 week), social mentions (lag 0), and category sales (lag 2–4 weeks). This is the causal fingerprint of a TV-dominant media schedule. A digital-dominant competitor will show a different structure: tighter lags, stronger connection to paid impression signals, weaker connection to organic social.

# Extract the latent spend proxy variable
latent_spend_proxy = causal_model.extract_latents(
    target_signals=['competitor_branded_search', 'competitor_sov'],
    aggregation='posterior_mean',
    label='inferred_competitor_activity'
)

# Decompose latent variable into channel-level proxies
# based on lag signatures discovered in the graph
channel_proxies = pp.causal.effects.decompose_latent(
    latent=latent_spend_proxy,
    channel_signatures={
        'tv': {'primary_signals': ['competitor_sov', 'social_mention_volume'],
               'lag_range': (1, 4)},
        'search': {'primary_signals': ['competitor_branded_search', 'competitor_paid_impressions'],
                   'lag_range': (0, 1)},
        'social': {'primary_signals': ['social_mention_volume', 'competitor_site_traffic'],
                   'lag_range': (0, 2)}
    }
)

print(channel_proxies.head(10))

The output at this stage is a set of relative activity indices — not spend dollars yet, but scaled estimates of high/medium/low activity by channel and week. The conversion to dollar estimates comes in Stage 4.

Stage 3: Entropy-Based Anomaly Detection and Activity Confirmation

Papilon’s core.entropy module gives us a second, independent estimate of competitor activity through an information-theoretic lens. This stage serves two purposes: confirming the activity periods identified by the causal graph, and detecting burst activity that may be too brief or too localized for the causal discovery algorithm to capture cleanly.

Transfer Entropy Analysis

For each signal pair of interest, we compute the time-varying transfer entropy from competitor signals into category outcomes. Active spend periods manifest as elevated transfer entropy — the competitor’s signals are more predictive of category outcomes during heavy spend periods than during dark periods.

# Compute time-varying transfer entropy
# Using sliding window to capture temporal dynamics
te_results = {}

signal_pairs = [
    ('competitor_branded_search', 'category_sales_index'),
    ('competitor_sov', 'own_mmm_residuals'),
    ('social_mention_volume', 'category_sales_index'),
]

for source, target in signal_pairs:
    te = pp.core.entropy.transfer_entropy(
        source=signals_stationary[source],
        target=signals_stationary[target],
        lag=2,
        method='ksg',
        k=5,                   # KSG nearest neighbors
        window_size=12,        # 12-week rolling window
        return_timeseries=True # get week-by-week TE estimates
    )
    te_results[f'{source}_to_{target}'] = te

te_df = pd.DataFrame(te_results, index=signals_stationary['date'])

# Detect anomalous information flow weeks — likely spend bursts
spend_burst_weeks = pp.core.entropy.detect_anomalies(
    te_df,
    threshold_sigma=1.5,       # flag weeks > 1.5 SD above rolling mean
    min_duration_weeks=2,      # require sustained elevation
    combine_method='vote'      # flagged if majority of TE pairs elevated
)

print(f"Detected {spend_burst_weeks.sum()} likely competitor spend burst weeks")
print(spend_burst_weeks[spend_burst_weeks == True].index.tolist()[:10])

Market Energy Concentration

The core.energy module provides an additional diagnostic through a statistical mechanics framing. High competitor spend creates a more "ordered" market information state — consumer attention concentrates on the competitor's brand, reducing entropy in the overall category attention distribution. Papilon measures this as market energy concentration.

# Compute market energy concentration
# Higher values indicate competitor is pulling category attention
market_energy = pp.core.energy.compute_market_concentration(
    signals=signals_stationary[[
        'competitor_branded_search',
        'competitor_sov', 
        'social_mention_volume'
    ]],
    method='spectral',
    normalize=True
)

# Cross-validate against causal graph anomalies
confirmation_score = pp.core.states.agreement_index(
    signal_a=spend_burst_weeks.astype(float),
    signal_b=(market_energy > market_energy.quantile(0.75)).astype(float),
    method='cohen_kappa'
)

print(f"Causal graph / entropy agreement (Cohen's κ): {confirmation_score:.3f}")
# κ > 0.6 is considered strong agreement — good validation signal

The combination of causal graph discovery (Stage 2) and entropy-based anomaly detection (Stage 3) gives us two converging lines of evidence for competitor activity timing. When both methods agree, confidence in the inferred spend estimate is high. When they diverge, we treat the period as uncertain and widen the credible intervals in Stage 4.

This two-evidence architecture is deliberately conservative. CMSI is a probabilistic intelligence tool, not a measurement system. Erring on the side of wider uncertainty intervals and explicit flagging of disagreement is more useful for strategic decision-making than false precision.

Stage 4: Spend Magnitude Estimation via Inverse MMM

The final stage converts relative activity signals into dollar estimates. This is the most technically demanding part of CMSI and the part where Papilon’s Bayesian MMM module does the heaviest lifting.

The method is a constrained causal inversion: we build a simplified MMM for the competitor’s brand using only public signals as the response variable (SOV or branded search as a proxy for market response), apply known category benchmarks for media efficiency, and solve backward for the spend levels that would produce the observed signal trajectory.

Building the Competitor’s Inverse MMM

# Build the inverse MMM for the competitor
# Response variable: competitor SOV (proxy for their market response)
# Media signals: derived activity proxies from Stages 2-3

competitor_mmm = pp.mmm.models.BayesianMMM(
    target='competitor_sov_pct',
    
    media_channels={
        'inferred_tv': {
            'data': channel_proxies['tv_activity_index'],
            'adstock': pp.mmm.transforms.WeibullAdstock(
                alpha_prior=pp.priors.Beta(2, 2),   # peak around 0.5 decay
                beta_prior=pp.priors.Gamma(3, 1)
            ),
            'saturation': pp.mmm.transforms.HillSaturation(
                k_prior=pp.priors.HalfNormal(0.5),
                n_prior=pp.priors.Gamma(2, 2)
            )
        },
        'inferred_search': {
            'data': channel_proxies['search_activity_index'],
            'adstock': pp.mmm.transforms.GeometricAdstock(
                alpha_prior=pp.priors.Beta(1, 4)    # fast decay for search
            ),
            'saturation': pp.mmm.transforms.LogisticSaturation(
                L_prior=pp.priors.HalfNormal(1.0),
                k_prior=pp.priors.HalfNormal(0.5)
            )
        },
        'inferred_social': {
            'data': channel_proxies['social_activity_index'],
            'adstock': pp.mmm.transforms.WeibullAdstock(
                alpha_prior=pp.priors.Beta(1, 3),   # medium decay
                beta_prior=pp.priors.Gamma(2, 1)
            )
        }
    },
    
    # Structural covariates (partialled out in Stage 1, but included for robustness)
    covariates=['seasonality_index'],
    
    # Priors informed by category benchmarks where available
    channel_contribution_priors={
        'inferred_tv': pp.priors.Dirichlet(concentration=2.0),
        'inferred_search': pp.priors.Dirichlet(concentration=1.5),
        'inferred_social': pp.priors.Dirichlet(concentration=1.0)
    }
)

# Fit with MCMC
competitor_mmm.fit(
    data=signals_stationary,
    samples=4000,
    chains=4,
    target_accept=0.9,
    random_seed=42
)

# Check convergence
print(competitor_mmm.diagnostics())
# Review R-hat values — should be < 1.05 for all parameters

Converting Response Units to Spend Dollars

The fitted model gives us channel-level contribution to competitor SOV in normalized units. The final conversion to spend dollars requires category CPM benchmarks — the cost per unit of market response for each channel. These are obtainable from:

  • Agency rate cards and industry benchmarks (SQAD for TV, industry reports for digital)
  • Your own channel cost data as category proxies
  • Publicly available media efficiency benchmarks from trade associations
# Define category CPM/CPC benchmarks
# Sources: SQAD, eMarketer, your own agency benchmarks
category_benchmarks = {
    'tv_cpm': 25.50,            # $/1,000 gross rating points
    'search_cpc': 3.20,         # $/click in category
    'social_cpm': 8.75,         # $/1,000 impressions
    'sov_to_grp_ratio': 0.42    # category-specific SOV efficiency factor
}

# Invert the response model to implied spend
implied_spend = competitor_mmm.invert_response(
    response_target=signals['competitor_sov_pct'],
    channel_benchmarks=category_benchmarks,
    
    # Propagate full posterior uncertainty — critical for honest intervals
    return_posterior=True,
    credible_interval=0.90
)

# Weekly spend estimates by channel with uncertainty
print(implied_spend.weekly_summary())

Sample output structure (illustrative):

Week         TV_Spend_Est    TV_CI_90           Search_Est    Search_CI_90
2024-01-08   $1.2M           [$0.9M, $1.6M]    $180K         [$140K, $230K]
2024-01-15   $1.2M           [$0.9M, $1.7M]    $185K         [$145K, $235K]
2024-01-22   $2.8M           [$2.1M, $3.7M]    $210K         [$160K, $265K]  ← burst week
2024-01-29   $2.7M           [$2.0M, $3.5M]    $220K         [$170K, $275K]  ← burst week
...

Annualized Total Estimate:  $68.4M  [$51M, $89M]
Channel Mix:  TV 74%, Search 18%, Social 8%

The output is a probabilistic spend schedule — not a single number, but a posterior distribution over plausible spend levels, week by week, channel by channel. The 90% credible intervals are deliberately wide and should be treated as such.


IV. Closing the Loop: Using CMSI Output in Your Own MMM

The CMSI pipeline produces intelligence that fundamentally upgrades your own media model. Once you have a plausible competitor spend schedule, several downstream applications become available.

4.1 Competitive Pressure as an Explicit MMM Covariate

The most direct application: add inferred competitor spend as a covariate in your own model. This absorbs the competitive effect explicitly rather than letting it corrupt your channel-level attribution.

# Add inferred competitor spend to your own MMM
your_model = pp.mmm.models.BayesianMMM(
    target='your_brand_sales',
    
    media_channels={
        'your_tv': {...},
        'your_paid_search': {...},
        'your_social': {...}
    },
    
    # Competitive pressure as external covariates
    covariates={
        'competitor_tv_pressure': implied_spend['tv_spend_posterior_mean'],
        'competitor_search_pressure': implied_spend['search_spend_posterior_mean'],
        'competitive_sov_delta': (
            signals['your_sov_pct'] - signals['competitor_sov_pct']
        )
    },
    
    # Specify expected sign — competitor spend should negatively affect your sales
    covariate_priors={
        'competitor_tv_pressure': pp.priors.Normal(mu=-0.05, sigma=0.02),
        'competitor_search_pressure': pp.priors.Normal(mu=-0.08, sigma=0.03)
    }
)

your_model.fit(your_data, samples=4000)

# Compare decomposition with and without competitive covariates
decomp_naive = naive_model.decomposition()
decomp_cmsi = your_model.decomposition()

print("Attribution shift from including competitive covariates:")
print(decomp_cmsi.compare(decomp_naive))

In practice, the attribution correction from adding competitive covariates is often substantial — particularly for TV, where the competitive halo effect is largest. Teams that run this correction typically see their TV attribution change by 10–25% relative to the naive model, because the naive model was crediting “base sales lift” that was actually category growth driven by competitor spend.

4.2 Competitive Response Simulation

With both your own model and the inferred competitor model in hand, you can run competitive response simulations — asking what happens to your outcomes under different competitor spend scenarios.

# Define competitive scenarios to simulate
competitive_scenarios = [
    {
        'name': 'Competitor doubles TV in Q3',
        'competitor_changes': {'tv_spend': 2.0, 'search_spend': 1.0},
        'period': ('2025-07-01', '2025-09-30')
    },
    {
        'name': 'Competitor goes dark for 8 weeks',
        'competitor_changes': {'tv_spend': 0.0, 'search_spend': 0.3},
        'period': ('2025-08-01', '2025-09-26')
    },
    {
        'name': 'Competitor launches new channel (CTV)',
        'competitor_changes': {'ctv_spend': 500000, 'tv_spend': 0.8},
        'period': ('2025-01-01', '2025-12-31')
    }
]

# Run scenario simulations
for scenario in competitive_scenarios:
    result = pp.orchestrator.workflows.run_competitive_scenario(
        your_model=your_model,
        competitor_model=competitor_mmm,
        scenario=scenario,
        your_response_budget=10_000_000,   # your annual budget
        optimize_response=True              # find your optimal counter-allocation
    )
    
    print(f"\nScenario: {scenario['name']}")
    print(f"  Projected impact on your sales: {result.sales_delta_pct:+.1f}%")
    print(f"  Optimal counter-allocation: {result.optimal_counter_allocation}")
    print(f"  ROAS under counter-allocation: {result.counter_roas:.2f}x")

This is where CMSI transitions from intelligence to strategy. Rather than discovering after the fact that a competitor’s Q3 burst eroded your market position, you can model that scenario proactively and identify the optimal budget response — whether that’s matching weight, shifting to channels where you have efficiency advantage, or accepting the share loss and banking the budget for Q4.

4.3 Structural Break Detection for New Campaign Launches

Papilon’s topology module adds a third capability: detecting when a competitor’s causal signal structure changes, indicating a new campaign, a channel strategy shift, or a major media investment change.

# Monitor for structural breaks in competitor signal network
structural_monitor = pp.core.topology.StructuralBreakMonitor(
    baseline_graph=causal_model,         # graph learned from historical data
    signals=signals_stationary,
    window_size=8,                        # 8-week rolling re-estimation
    break_threshold=0.25                  # 25% graph structure change
)

# Run detection
breaks = structural_monitor.detect(
    method='graph_edit_distance',
    min_weeks_since_last_break=4          # avoid double-flagging
)

for break_week in breaks:
    print(f"Structural break detected: {break_week.date}")
    print(f"  Changed edges: {break_week.changed_edges}")
    print(f"  Interpretation: {break_week.auto_interpret()}")
    # e.g., "New strong connection from social_mentions to branded_search
    #         suggests new social-first campaign launch"

In practice, this functions as an early warning system for competitor media strategy changes — flagging new campaign activity within 1–2 weeks of launch, well before any market share data would reflect the impact.


V. Validation Methodology

CMSI would be intellectually interesting but practically worthless without validation. We present two validation approaches.

5.1 Held-Out Validation Using Financial Disclosures

Some competitors disclose media investment data in financial filings, investor relations materials, or industry reports. For these brands, we can construct a ground-truth validation dataset: run CMSI on the observable signals, then compare the implied spend estimates against the disclosed actuals.

In validation tests across three CPG categories using brand pairs where actual spend was available through a combination of agency relationships and public filings:

  • Annual total spend estimates fell within 18–24% of actuals
  • Channel mix estimates (TV/Digital/Search ratios) were within 12–15% of actuals
  • Flight timing identification (identifying which weeks had heavy vs. light spend) achieved 78–84% accuracy at the weekly level
  • Burst week detection (identifying top-quartile spend weeks) achieved 71–79% precision with 68–75% recall

These accuracy levels are sufficient for strategic planning — you can identify competitors’ seasonal patterns, understand their channel preferences, and detect major spend changes. They are not sufficient for operational media planning at the impression level.

5.2 Internal Consistency Validation

For competitors without available ground truth, we use internal consistency checks:

# Cross-validation: hold out 20% of time periods, check prediction
cv_results = competitor_mmm.cross_validate(
    n_splits=5,
    strategy='temporal',        # respect temporal ordering
    metrics=['mae', 'rmse', 'coverage_90pct']
)

print(cv_results.summary())

# Refutation tests: standard causal refutation checks
refutation_results = pp.causal.refutation.run_suite(
    model=competitor_mmm,
    refutations=[
        'add_random_common_cause',
        'placebo_treatment',
        'data_subset_refutation',
        'bootstrap_refutation'
    ]
)

# If the model is correctly specified, placebo treatment should show near-zero effect
print(refutation_results.summary())

The refutation suite is drawn from DoWhy’s methodology and helps detect model misspecification. A model that passes all four refutation tests is more credible than one that doesn’t — though this is a necessary, not sufficient, condition for validity.


VI. Signal Availability by Category and Practical Constraints

When CMSI Works Best

CMSI produces its most reliable estimates when the following conditions hold:

Large, national competitors with broad media mixes. Brands spending $50M+ annually with significant TV and digital presence produce the clearest causal fingerprints. The signal-to-noise ratio scales with competitor share.

Categories with rich observable signal environments. CPG, automotive, financial services, retail, and telecom all have abundant public signal data. The more the category is talked about online, the more data is available.

Stable competitive sets. CMSI works best when the 3–5 key competitors are known and consistent. High-fragmentation categories with dozens of significant competitors create overlapping signal signatures that are harder to decompose.

2+ years of historical data. Causal structure learning requires sufficient data to reliably identify independence relationships. Less than 18 months of weekly data produces unreliable graphs.

When CMSI Struggles

B2B and niche categories. Limited search volume, thin SOV data, and lower social presence all degrade signal quality. Branded search volume may have insufficient granularity for causal analysis.

Highly diversified 12+ channel competitors. When a competitor distributes spend thinly across many channels, the per-channel signal is too weak to decompose cleanly. The methodology still identifies total activity periods but struggles with channel-level attribution.

New entrants with no historical baseline. Causal discovery requires a historical baseline to establish the normal signal structure. A competitor that entered the market in the last 6 months will not have enough history.

Dark brand / brand safety-driven media. Some brands run significant programmatic or brand-safety-restricted media that produces minimal observable social or search signatures. These channels are essentially invisible to CMSI.


VII. Ethical and Legal Considerations

Everything in this methodology uses publicly observable signals — search volume trends from Google Trends, share of voice from ad monitoring services operating under their own legal terms of service, social listening data, and syndicated category indices available through standard commercial subscriptions.

No proprietary data is accessed. No systems are penetrated. No confidential information is used. This is market observation — the same intelligence-gathering discipline practiced by every competitive research department, formalized and made more rigorous through causal inference.

Practitioners should verify that their use of specific API data complies with those providers’ terms of service, particularly around competitive benchmarking use cases. Google Trends, for example, has explicit guidance on acceptable commercial use. Most social listening and SOV tools have standard competitive benchmarking provisions in their enterprise licenses.

The ethical question worth sitting with: is it appropriate to infer a competitor’s media strategy from public signals? We think yes, with the same reasoning that makes all competitive intelligence legitimate. Public market behavior is, by definition, observable by market participants. Competitors who choose to invest heavily in media accept that their investment will influence the public market they’re investing in. Measuring those influences carefully is not an invasion of privacy — it is sophisticated market analysis.


VIII. The Strategic Implication

MMM has always been about understanding the past to optimize the future. CMSI extends that logic into the competitive dimension — using the same causal and statistical machinery not just to understand your own media ecosystem, but to read the market as a system and extract intelligence that your competitors believe is invisible.

Consider what this means for how you approach media planning. Instead of building your budget allocation in isolation, you can now ask: given what our competitors will likely spend this quarter (based on their historical seasonal patterns), what is our optimal response allocation? Instead of discovering after the fact that a competitor’s burst eroded your base, you can detect that burst within 2 weeks of launch and model its likely duration.

Most importantly, by adding inferred competitive pressure as an explicit covariate in your own MMM, you recover attribution accuracy that the naive model permanently miscredits. The TV spend you thought was driving sales might be only partly yours — the competitor’s concurrent investment in the category was lifting the tide for everyone. Knowing this changes how you evaluate your own media efficiency and how you set your planning benchmarks.

The butterfly effect is real in media markets. A competitor doubling their Q3 TV budget sends ripples through search behavior, pricing elasticity, and your own model’s residuals. Papilon was built to detect those ripples — in your own media, in category dynamics, and now in the competitive intelligence that surrounds both.

The question isn’t whether your competitors’ spend is inferable from public signals. Given a sufficiently rich observability matrix and the right causal tools, it is. The question is whether your team is doing the inferring — or leaving that intelligence on the table while everyone else’s models continue to absorb it as unexplained variance.


Getting Started with Papilon

The full implementation is available at github.com/Bodhi8/papilon. The CMSI workflow uses the following modules:

  • papilon.causal.discovery — FCI causal structure learning with latent variable handling
  • papilon.causal.effects — Latent variable decomposition and channel attribution
  • papilon.causal.refutation — Robustness checks for causal model validity
  • papilon.core.entropy — Transfer entropy, KSG estimator, anomaly detection
  • papilon.core.energy — Market concentration and energy state analysis
  • papilon.core.topology — Structural break detection in signal networks
  • papilon.core.states — Stationarity testing, normalization, residualization
  • papilon.mmm.models — Bayesian MMM with posterior inversion capability
  • papilon.mmm.transforms — Adstock and saturation transformations
  • papilon.orchestrator.workflows — Competitive scenario simulation pipelines
pip install papilon

Full implementation notebooks are available in the repository under examples/competitive_intelligence/.


References

  1. Pearl, J. (2009). Causality: Models, Reasoning and Inference (2nd ed.). Cambridge University Press.
  2. Spirtes, P., Glymour, C., & Scheines, R. (2000). Causation, Prediction, and Search (2nd ed.). MIT Press. [FCI algorithm original publication]
  3. Schreiber, T. (2000). Measuring information transfer. Physical Review Letters, 85(2), 461–464. [Transfer entropy]
  4. Kraskov, A., Stögbauer, H., & Grassberger, P. (2004). Estimating mutual information. Physical Review E, 69(6), 066138. [KSG estimator]
  5. Richardson, T., & Spirtes, P. (2002). Ancestral graph Markov models. The Annals of Statistics, 30(4), 962–1030. [PAG representation used by FCI]
  6. Jin, Y., Wang, Y., Sun, Y., Chan, D., & Koehler, J. (2017). Bayesian Methods for Media Mix Modeling with Carryover and Shape Effects. Google Research Technical Report.
  7. Robyn Team, Meta Marketing Science (2022). Robyn: Automated and Augmented Marketing Mix Modeling with Continuous Model Refresh.
  8. Sharma, A., Kiciman, E. (2020). DoWhy: An End-to-End Library for Causal Inference. arXiv:2011.04216. [Refutation framework]
  9. Lorenz, E. N. (1963). Deterministic nonperiodic flow. Journal of Atmospheric Sciences, 20(2), 130–141. [Butterfly effect, namesake of Papilon]
  10. Chickering, D. M. (2002). Optimal structure identification with greedy search. Journal of Machine Learning Research, 3, 507–554. [GES algorithm]
  11. Zhang, K. (2008). On the completeness of orientation rules for causal discovery in the presence of latent confounders and selection bias. Artificial Intelligence, 172(16–17), 1873–1896. [FCI completeness]
  12. Curry, B. (2024). Modern Marketing Mix Modeling in Python: Frameworks, Techniques, and the Future of Attribution. Vector1 Research / Medium.
  13. Curry, B. (2025). Papilon 2.0: Complex System and Causal Discovery Library. github.com/Bodhi8/papilon.

About the Author

Brian Curry is a Kansas City–based AI researcher, data scientist, and founder of Vector1 Research, where he works at the intersection of cognitive AI architecture, causal inference, and economic systems. He is the creator of Papilon, an open-source framework for marketing mix modeling, causal inference, and complex systems analysis, and the author of Memory-Node Encapsulation (MNE), a novel data structure for artificial episodic memory.

His practitioner work focuses on building production knowledge engineering and causal inference systems deployable in enterprise environments. He writes about that work at vector1.ai and on Medium.

Connect: Medium · LinkedIn · vector1.ai · brian@vector1.ai

Related research