GeoLift is a Python package for geo-level marketing incrementality measurement using Augmented Synthetic Control Methods (ASCM). It is a Python port of the R GeoLift package, covering the same end-to-end workflow with the same API surface:
- Data ingestion — flexible date parsing, time-index creation
- Pre-test market selection — correlation and DTW-based market ranking, power analysis
- Post-test inference — synthetic control fitting, ATT estimation, conformal p-values and confidence intervals
- Multi-cell experiments — multiple simultaneous treatment arms (experimental)
- Visualization — pre- and post-test plots
Algorithmic note: Deterministic quantities (average ATT, L2 imbalance, synthetic control weights) match the R package to within numerical precision. Stochastic inference uses a different method — see Known differences below.
- You want to run GeoLift analyses in a Python environment without switching to R.
- You are building a data pipeline or ML platform in Python and need programmatic access to geo-lift inference.
- You want to reproduce or audit R GeoLift results in Python and understand where the two implementations agree and where they intentionally differ.
This package is not a drop-in replacement for R GeoLift when exact p-value or CI parity is required — the inference algorithms differ by design.
pip install .pip install -e ".[dev]"Requires Python ≥ 3.9.
All functions expect a pandas.DataFrame with at minimum these three columns:
| Column | Type | Description |
|---|---|---|
location |
str | Geo unit name (lowercased internally) |
time |
int | Integer time index starting at 1 |
Y |
float | Outcome variable |
Use geo_data_read() to convert raw data with date columns into this canonical format.
import pandas as pd
from geolift import geo_lift
df = pd.read_csv("tests/fixtures/small_single_cell.csv")
result = geo_lift(
data=df,
treatment_locations=["chicago", "portland"],
treatment_start_time=36,
treatment_end_time=45,
model="none", # or "ridge" or "best"
random_state=42, # set for reproducible p-values / CI
)
print(result)
# GeoLift Output
# Test results for 10 treatment period(s), from time-stamp 36 to 45
# Test markets: CHICAGO, PORTLAND
# Average ATT: -50.531
# P-value: 0.65
# 90% CI: (-296.4, 195.4)
# L2 Imbalance: 595.6240
# Scaled L2 Imbalance: 0.6424from geolift import geo_lift_market_selection
pretest_df = pd.read_csv("tests/fixtures/small_pretest.csv")
selection = geo_lift_market_selection(
data=pretest_df,
treatment_periods=[5],
N=[2],
effect_size=[0, 0.05, 0.10, 0.15, 0.20],
alpha=0.1,
random_state=42,
)
print(selection)from geolift import geo_lift_power
power_df = geo_lift_power(
data=pretest_df,
treatment_locations=["chicago", "portland"],
treatment_start_time=41,
treatment_end_time=45,
effect_size=[0, 0.05, 0.10, 0.15, 0.20],
random_state=42,
)
print(power_df[["effect_size", "power", "scaled_l2_imbalance"]])| Function | Description |
|---|---|
geo_data_read() |
Ingest raw data, normalize dates, create time index |
trim_controls() |
Reduce control pool via stratified sampling |
geo_lift() |
Post-test inference — ATT, p-value, CI |
geo_lift_power() |
Power analysis for a given market and treatment period |
geo_lift_market_selection() |
Rank candidate markets by power and fit quality |
geo_lift_multi_cell() |
Multi-cell post-test inference (experimental) |
multi_cell_market_selection() |
Multi-cell market selection (experimental) |
multi_cell_power() |
Multi-cell power analysis (experimental) |
Full parameter documentation: docs/api_reference.md.
On fixed small-sample fixtures, deterministic quantities match the R baseline to within numerical precision: average ATT, L2 imbalance, scaled L2 imbalance, synthetic control weights, and incremental lift.
Stochastic inference uses a different algorithm. R GeoLift uses time-shift placebo tests — it randomly selects pre-treatment windows and re-runs the full model on each to build a null distribution. Python GeoLift uses conformal inference — it fits the model once and permutes the fitted residuals. These are structurally different methods; p-values and confidence intervals will differ between the two implementations regardless of random seed. This is expected and documented behaviour, not a bug.
For a detailed comparison including per-fixture numeric tables, see
docs/r_python_differences.md.
| Feature | Status |
|---|---|
model="gsyn" (Generalized Synthetic Control) |
Raises NotImplementedError — planned for a future release |
# Run the full test suite
pytest
# Run with coverage
pytest --cov=geolift
# Run only R parity regression tests (no R installation needed)
pytest tests/test_r_parity.py
# Run only reproducibility tests
pytest tests/test_reproducibility.pyCI is configured via .github/workflows/tests.yml and runs on Python 3.10, 3.11, and 3.12.
For parity checks against an externally installed R GeoLift package:
Rscript tests/r_example_baseline.R
python tests/small_numeric_python_compare.pyBoth scripts use the fixture CSVs in tests/vignette_outputs/ and write
JSON outputs for comparison. Interactive versions are in examples/.
GeoLift is MIT licensed, as found in the LICENSE.md file.
This Python package is a port of the R GeoLift package developed by Meta Platforms, Inc. and its affiliates.