User Guide

This guide walks through the GeoLift workflow for measuring campaign impact using causal inference.

Overview

GeoLift measures the true sales lift and ROI from your regional marketing campaigns using advanced causal inference methods. It follows a proven 3-step workflow to ensure reliable results.

The 3-Step Workflow

Step 1: Find a fair comparison (Donor evaluation)

Before measuring impact, we need to identify control markets that behave similarly to your test markets.

Data Requirements

Your dataset should include:

  • Time Series Data: At least 12-24 weeks of pre-campaign data

  • Geographic Units: Markets, DMAs, states, or regions

  • Outcome Metrics: Sales, conversions, or other KPIs

  • Treatment Assignment: Which markets received the campaign

Running donor evaluation

Use the bundled recipe and the YAML under data-config/:

python recipes/donor_evaluator.py --config data-config/donor_eval_config.yaml

Outputs (by default): outputs/multicell_donor_eval/

  • donor_eval_results.csv and donor_map_*.png

What to Look For

  • High Correlation: Control markets should track closely with treatment markets pre-campaign

  • Similar Trends: Parallel movement in the pre-period

  • Geographic Diversity: Avoid clustering all controls in one region

Step 2: Check if the test is strong enough (Power analysis)

Power analysis determines if your experiment can detect meaningful effects.

# GPU optional, CPU parallelisation supported
python recipes/power_calculator_sparsesc.py \
  --config data-config/power_analysis_config.yaml \
  --use-gpu --jobs -1

Outputs: outputs/multicell_power_analysis/

  • power_curves.png, power_analysis_results.csv

Power Analysis Outputs

  • Minimum Detectable Effect (MDE): Smallest lift you can reliably detect

  • Recommended Duration: How long to run the campaign for reliable results

  • Sample Size Requirements: Number of markets needed

Step 3: Measure the lift (inference)

Run the main causal inference analysis to measure campaign impact.

python recipes/geolift_multi_cell.py --config data-config/geolift_analysis_config.yaml

Outputs: outputs/multicell_geolift_analysis/

  • geolift_results.json, geolift_diagnostics.json, uplift_timeseries.png

Understanding Your Results

Key Metrics Explained

Causal Impact

  • Absolute Lift: Raw units of incremental impact

  • Relative Lift: Percentage increase over baseline

  • Confidence Intervals: Range of plausible effect sizes

Statistical Significance

  • P-value: Probability results occurred by chance

  • Confidence Level: How certain we are about the effect

  • Statistical Power: Ability to detect true effects

Business Impact

  • Incremental ROI: Return on marketing investment

  • Cost Per Incremental Unit: Efficiency of campaign spend

  • Payback Period: Time to recover campaign investment

Interpreting Results

# Access key metrics from results dictionary
if results:
    print(f"Average Treatment Effect: {results.get('att', 'N/A')}")
    print(f"P-value: {results.get('p_value', 'N/A')}")
    print(f"Confidence Interval: [{results.get('ci_lower', 'N/A')}, {results.get('ci_upper', 'N/A')}]")
    
    # Check statistical significance
    if results.get('p_value', 1.0) < 0.05:
        print("Result is statistically significant at 95% confidence level")
    
    # Results are also saved in output_dir as JSON files:
    # - geolift_results.json: Main results
    # - geolift_diagnostics.json: Detailed diagnostics

Visual Diagnostics

If you run the recipe with create_plots: true, the analyser will save uplift_timeseries.png.

Data Preparation Best Practices

Data Quality Requirements

  • Completeness: No missing values in key periods

  • Consistency: Same measurement methodology throughout

  • Granularity: Weekly or daily data preferred over monthly

  • Baseline Period: At least 12 weeks of pre-campaign data

Common Data Issues

  • Seasonality: Account for holidays and seasonal patterns

  • External Events: Note major market disruptions

  • Data Breaks: Ensure consistent measurement methodology

  • Outliers: Identify and handle extreme values appropriately

Data validation

Ensure input data are consistent and complete (no missing key columns or periods). Use the load_and_prepare_data utility for file‑based workflows.

Configuration Options

Analysis parameters

# config.yaml
analysis:
  treatment_start_date: "2023-06-01"
  treatment_end_date: "2023-08-31"
  pre_period_weeks: 24
  outcome_column: "sales"
  
inference:
  method: "bootstrap"  # bootstrap, placebo, jackknife
  n_bootstrap: 1000
  confidence_level: 0.95
  # Bootstrap uses stationary (circular) block bootstrap with automatic
  # block-length selection. Typical block lengths: 3-5 periods for marketing
  # time series with moderate autocorrelation.
  
validation:
  min_pre_period_weeks: 12
  max_missing_data_pct: 0.05
  outlier_threshold: 3.0

Advanced Options

# Using direct data initialisation for more control (advanced)
import pandas as pd

# Load and prepare your data
data = pd.read_csv('campaign_data.csv')
# Pivot to get units as rows, time periods as columns
outcomes_df = data.pivot(index='geo', columns='date', values='sales')

# Define treatment periods
unit_treatment_periods = pd.Series({
    502: '2023-06-01',  # Treatment unit
    503: '2023-06-01',  # Treatment unit
    501: None,          # Control unit
    504: None,          # Control unit
    505: None           # Control unit
})

# Initialize with direct data
# Note: SparseSC uses time-blocked cross-validation on the pre-treatment period
# (not unit-folding CV) to select regularisation penalties. This prevents data
# leakage and aligns model selection with the counterfactual prediction objective.
analyzer = GeoLiftAnalyzer(
    outcomes_df=outcomes_df,
    unit_treatment_periods=unit_treatment_periods,
    intervention_date='2023-06-01',
    config={
        'sparse_sc_model_type': 'retrospective',
        'sparse_sc_max_n_pl': 5000,
        'sparse_sc_level': 0.95
    }
)

results = analyzer.run_analysis()

Reporting and Export

Generate Business Reports

import json
import pandas as pd

# Load saved results from JSON files
with open('outputs/geolift_analysis/geolift_results.json', 'r') as f:
    saved_results = json.load(f)

with open('outputs/geolift_analysis/geolift_diagnostics.json', 'r') as f:
    saved_diagnostics = json.load(f)

# Create custom summary
summary_data = {
    'campaign_name': 'Q3 Brand Campaign',
    'lift_estimate': saved_results.get('att'),
    'lift_ci_lower': saved_results.get('ci_lower'),
    'lift_ci_upper': saved_results.get('ci_upper'),
    'p_value': saved_results.get('p_value'),
    'campaign_cost': 50000,
    'incremental_revenue': saved_results.get('att', 0) * 100  # Assuming $100 per unit
}

# Calculate ROI
if summary_data['incremental_revenue'] and summary_data['campaign_cost']:
    summary_data['roi'] = summary_data['incremental_revenue'] / summary_data['campaign_cost']

# Export to CSV
pd.DataFrame([summary_data]).to_csv('campaign_summary.csv', index=False)

# Create detailed report
detailed_report = pd.DataFrame({
    'Metric': ['ATT', 'P-value', 'CI Lower', 'CI Upper'],
    'Value': [
        saved_results.get('att'),
        saved_results.get('p_value'),
        saved_results.get('ci_lower'),
        saved_results.get('ci_upper')
    ]
})
detailed_report.to_csv('detailed_results.csv', index=False)

Troubleshooting Common Issues

Poor Pre-Period Fit

Problem: Synthetic control doesn’t match treatment markets well before campaign Solutions:

  • Extend pre-period data

  • Remove outlier periods

  • Try different donor selection criteria

Low Statistical Power

Problem: Cannot detect meaningful effects Solutions:

  • Extend campaign duration

  • Include more treatment markets

  • Use more sensitive outcome metrics

Implausible Results

Problem: Effect sizes seem too large or small Solutions:

  • Check data quality and definitions

  • Validate treatment assignment

  • Review external factors during campaign period

Next Steps

  • Need technical details? → See API Reference

  • Want to understand the math? → Check Advanced Topics

  • Having specific issues? → Review FAQ

  • Ready for production? → See deployment guides in Advanced Topics