This document describes every public function and class in the geolift package.
- Data utilities
- Core inference
- Pre-test (power & market selection)
- Multi-cell (experimental)
- Auxiliary utilities
All GeoLift functions expect a pandas.DataFrame with these columns:
| Column | Type | Description |
|---|---|---|
location |
str |
Geo-unit name (lowercased internally) |
time |
int |
Integer time index starting at 1 |
Y |
float |
Outcome variable (e.g. conversions, revenue) |
geo_data_read converts raw date-indexed data into this canonical shape.
from geolift import geo_data_read
geo_data_read(
data: pd.DataFrame,
date_id: str = "date",
location_id: str = "location",
Y_id: str = "units",
format: str = "mm/dd/yyyy",
X: Optional[List[str]] = None,
summary: bool = False,
keep_unix_time: bool = False,
) -> Optional[pd.DataFrame]Reads and pre-processes a raw geo panel into the GeoLift standard format.
Normalises locations to lowercase, parses dates into a 1-indexed consecutive
integer time column, drops rows with null Y, and removes locations whose
time series is incomplete (i.e. missing any time period present in other
locations). Duplicate (location, time) pairs are aggregated by summing Y.
Parameters
| Parameter | Default | Description |
|---|---|---|
data |
— | Raw panel DataFrame |
date_id |
"date" |
Name of the date column |
location_id |
"location" |
Name of the location column |
Y_id |
"units" |
Name of the outcome column |
format |
"mm/dd/yyyy" |
Date format string. Supported: mm/dd/yyyy, yyyy-mm-dd, mm-yyyy, ww/yyyy, and 20+ others — see _VALID_FORMATS in data.py |
X |
None |
Optional list of covariate column names to carry through (each summed within (location, time)) |
summary |
False |
Print processing summary if True |
keep_unix_time |
False |
Retain the intermediate date_unix column |
Returns pd.DataFrame in GeoLift standard format, or None if validation fails.
Example
import pandas as pd
from geolift import geo_data_read
raw = pd.read_csv("my_data.csv") # has columns: date, location, sales
data = geo_data_read(raw, date_id="date", Y_id="sales", format="yyyy-mm-dd")from geolift import trim_controls
trim_controls(
data: pd.DataFrame,
Y_id: str = "Y",
time_id: str = "time",
location_id: str = "location",
max_controls: int = 20,
test_locations: Optional[List[str]] = None,
forced_control_locations: Optional[List[str]] = None,
random_state: Optional[int] = None,
) -> Optional[pd.DataFrame]Reduces the control pool via stratified sampling by mean-deviation quintile.
Computes each location's mean deviation from the cross-sectional average Y,
assigns to 5 equal quintile buckets, then samples round(max_controls / 5)
locations per bucket. Test and forced-control locations are always retained.
Parameters
| Parameter | Default | Description |
|---|---|---|
data |
— | Panel in GeoLift standard format |
max_controls |
20 |
Target number of control locations. Recommended: 4–5× the number of test locations |
test_locations |
None |
Treatment locations — always kept |
forced_control_locations |
None |
Additional locations to always keep in the control pool |
random_state |
None |
Integer seed for reproducible sampling |
Returns Filtered pd.DataFrame, or None when max_controls exceeds distinct locations.
from geolift import geo_lift
geo_lift(
data: pd.DataFrame,
treatment_locations: List[str],
treatment_start_time: int,
treatment_end_time: int,
Y_id: str = "Y",
time_id: str = "time",
location_id: str = "location",
X: Optional[List[str]] = None,
model: str = "none",
fixed_effects: bool = True,
alpha: float = 0.1,
stat_func: Union[str, Callable] = "two_sided",
side_of_test: str = "two_sided",
type: str = "iid",
n_samples: int = 200,
compute_ci: bool = True,
grid_size: int = 250,
random_state: Optional[int] = None,
) -> GeoLiftResultMain inference function. Fits an Augmented Synthetic Control Model (ASCM) and
returns a GeoLiftResult containing point estimates, inference statistics, and
diagnostics.
Inference method: conformal inference via residual permutation (IID or
block). This differs from R's time-shift placebo tests — see
docs/r_python_differences.md.
Parameters
| Parameter | Default | Description |
|---|---|---|
data |
— | Panel in GeoLift standard format |
treatment_locations |
— | List of treated location names |
treatment_start_time |
— | First treatment period (inclusive) |
treatment_end_time |
— | Last treatment period (inclusive) |
model |
"none" |
ASCM model type. Options: "none" (OLS-only synth), "ridge" (Ridge-augmented synth), "best" (selects the lower scaled-L2 option). "gsyn" raises NotImplementedError |
fixed_effects |
True |
Demean by location and time fixed effects before fitting |
alpha |
0.1 |
Significance level for CI and p-value |
stat_func |
"two_sided" |
Test statistic: "two_sided" or "positive" / "negative" (one-sided) |
side_of_test |
"two_sided" |
Legacy alias; use stat_func directly |
type |
"iid" |
Permutation type: "iid" or "block" |
n_samples |
200 |
Number of permutation samples |
compute_ci |
True |
Whether to compute the conformal CI via grid search |
grid_size |
250 |
Number of null-hypothesis grid points for CI search |
random_state |
None |
Integer seed for reproducible inference. None uses seed 42 internally |
Returns GeoLiftResult
Example
from geolift import geo_lift
result = geo_lift(
data=data,
treatment_locations=["chicago", "portland"],
treatment_start_time=36,
treatment_end_time=45,
model="none",
alpha=0.1,
n_samples=200,
random_state=42,
)
print(result)
print(f"ATT: {result.average_att:.3f}")
print(f"P-value: {result.p_val:.4f}")from geolift import GeoLiftResultReturned by geo_lift. Attributes:
| Attribute | Type | Description |
|---|---|---|
treatment_start |
int |
First treatment period |
treatment_end |
int |
Last treatment period |
treatment_locations |
List[str] |
Treated location names |
average_att |
float |
Average treatment effect on the treated (per location-period) |
se |
float |
Standard error of the ATT (from conformal inference) |
p_val |
float |
Conformal p-value in [0, 1] |
lower_bound |
float |
Lower bound of the (1 - alpha) confidence interval |
upper_bound |
float |
Upper bound of the (1 - alpha) confidence interval |
att |
pd.DataFrame |
Period-by-period ATT with SE and CI columns; indexed by time |
l2_imbalance |
float |
Pre-period L2 imbalance (absolute units) |
scaled_l2_imbalance |
float |
L2 imbalance scaled by naïve mean outcome (dimensionless) |
bias_est |
float |
Bias estimate from the augmented component |
weights |
pd.DataFrame |
Control location weights; columns ["location", "weight"] |
data |
pd.DataFrame |
Panel data used for fitting (with D treatment indicator) |
model |
AugSynthModel |
Fitted model object |
alpha |
float |
Significance level used |
from geolift import cumulative_lift
cumulative_lift(
geo_lift_obj: GeoLiftResult,
treatment_start_time: Optional[int] = None,
) -> pd.DataFrameComputes cumulative ATT across the treatment period.
Returns pd.DataFrame with columns ["time", "cumulative_att"].
from geolift import ascm_execution
ascm_execution(
data: pd.DataFrame,
treatment_locations: List[str],
treatment_start_time: int,
treatment_end_time: int,
Y_id: str = "Y",
time_id: str = "time",
location_id: str = "location",
X: Optional[List[str]] = None,
model: str = "none",
fixed_effects: bool = True,
) -> dictLow-level ASCM fitting step used internally by geo_lift. Returns a dict with
keys "ascm_model" (AugSynthModel), "data" (panel with D column), and
"treatment_locations" (lowercased list).
from geolift import geo_lift_power
geo_lift_power(
data: pd.DataFrame,
treatment_locations: List[str],
treatment_start_time: int,
treatment_end_time: int,
effect_size: List[float] = None,
Y_id: str = "Y",
time_id: str = "time",
location_id: str = "location",
X: Optional[List[str]] = None,
model: str = "none",
fixed_effects: bool = True,
alpha: float = 0.1,
stat_func: str = "two_sided",
n_samples: int = 200,
random_state: Optional[int] = None,
) -> pd.DataFrameEstimates detection power for a given treatment configuration across multiple effect sizes. For each effect size, simulates the experiment by injecting the scaled effect into the pre-treatment data, fits ASCM, and records whether the null is rejected.
Parameters
| Parameter | Default | Description |
|---|---|---|
effect_size |
[0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3] |
List of multiplicative effect sizes (0 = null, 0.1 = 10% lift) |
n_samples |
200 |
Permutation samples per simulation |
random_state |
None |
Integer seed for reproducibility |
Returns pd.DataFrame with columns including effect_size, power,
average_att, scaled_l2_imbalance.
from geolift import geo_lift_market_selection
geo_lift_market_selection(
data: pd.DataFrame,
treatment_periods: List[int],
N: List[int],
effect_size: List[float] = None,
Y_id: str = "Y",
time_id: str = "time",
location_id: str = "location",
X: Optional[List[str]] = None,
model: str = "none",
fixed_effects: bool = True,
alpha: float = 0.1,
stat_func: str = "two_sided",
include_markets: Optional[List[str]] = None,
exclude_markets: Optional[List[str]] = None,
dtw: float = 0,
n_samples: int = 200,
random_state: Optional[int] = None,
) -> GeoLiftMarketSelectionRuns power analysis across all candidate treatment market combinations and
ranks them. For each combination of treatment period length (from
treatment_periods) and number of treatment markets (from N), fits ASCM
and computes power at each effect size.
Parameters
| Parameter | Default | Description |
|---|---|---|
treatment_periods |
— | List of candidate treatment period lengths |
N |
— | List of candidate numbers of treatment markets |
include_markets |
None |
Markets that must always be included in treatment |
exclude_markets |
None |
Markets to never consider as treatment |
dtw |
0 |
Weight on DTW distance vs correlation for market ranking. 0 = correlation only |
random_state |
None |
Integer seed for reproducibility |
Returns GeoLiftMarketSelection
Returned by geo_lift_market_selection. Attributes:
| Attribute | Type | Description |
|---|---|---|
best_markets |
pd.DataFrame |
Top-ranked market combinations sorted by power and L2 imbalance |
all_results |
pd.DataFrame |
Full results across all combinations |
data |
pd.DataFrame |
Input panel |
alpha |
float |
Significance level |
Note: All multi-cell functions are experimental. The API may change in future releases.
from geolift import geo_lift_multi_cell
geo_lift_multi_cell(
data: pd.DataFrame,
treatment_locations: List[List[str]],
treatment_start_time: int,
treatment_end_time: int,
Y_id: str = "Y",
time_id: str = "time",
location_id: str = "location",
X: Optional[List[str]] = None,
model: str = "none",
fixed_effects: bool = True,
alpha: float = 0.1,
n_samples: int = 200,
random_state: Optional[int] = None,
) -> GeoLiftMultiCellFits an ASCM independently for each cell (arm) in a multi-cell experiment.
treatment_locations is a list of lists where each inner list defines the
treatment markets for one cell.
Returned by geo_lift_multi_cell. Attributes:
| Attribute | Type | Description |
|---|---|---|
cell_results |
List[GeoLiftResult] |
One GeoLiftResult per cell |
k |
int |
Number of cells |
from geolift import multi_cell_market_selection
multi_cell_market_selection(
data: pd.DataFrame,
k: int,
treatment_periods: List[int],
N: List[int],
effect_size: List[float] = None,
...,
random_state: Optional[int] = None,
) -> MultiCellMarketSelectionRuns market selection independently for each of k cells.
from geolift import multi_cell_power
multi_cell_power(
data: pd.DataFrame,
treatment_locations: List[List[str]],
treatment_start_time: int,
treatment_end_time: int,
effect_size: List[float] = None,
...,
random_state: Optional[int] = None,
) -> MultiCellPowerRuns power analysis for each cell independently.
from geolift import multi_cell_winner
multi_cell_winner(
geo_lift_multi_cell_obj: GeoLiftMultiCell,
alpha: float = 0.1,
) -> pd.DataFrameDetermines the winning cell (arm) based on ATT magnitude and statistical significance.
Attributes: models (list of GeoLiftMarketSelection), data, test_details, k, best_markets.
Attributes: cell_power (list of power DataFrames), k.
from geolift import fn_treatment
fn_treatment(
data: pd.DataFrame,
locations: List[str],
treatment_start_time: int,
treatment_end_time: int,
location_id: str = "location",
time_id: str = "time",
) -> pd.DataFrameAdds a binary D column (1 = treated location in treatment period, 0
otherwise) to the panel. Used internally before fitting ASCM.
from geolift import append_agg
append_agg(
data: pd.DataFrame,
treatment_locations: List[str],
Y_id: str = "Y",
time_id: str = "time",
location_id: str = "location",
) -> pd.DataFrameAppends an aggregated row for all treatment locations combined (sum of Y). Useful for multi-location post-test diagnostics.
from geolift import correlation_coefficient
correlation_coefficient(x: np.ndarray, y: np.ndarray) -> floatReturns the Pearson correlation coefficient between two arrays.
from geolift import market_correlations
market_correlations(
data: pd.DataFrame,
location_id: str = "location",
time_id: str = "time",
Y_id: str = "Y",
) -> pd.DataFrameComputes pairwise Pearson correlations between all locations' Y time series.
Returns a long-form DataFrame with columns ["location_1", "location_2", "correlation"] sorted by descending correlation.
| Value | Description |
|---|---|
"none" |
OLS-only synthetic control (Abadie-style) |
"ridge" |
Ridge-augmented synthetic control |
"best" |
Selects "none" or "ridge" based on scaled L2 imbalance |
"gsyn" |
Generalized synthetic control — raises NotImplementedError |
All stochastic functions accept random_state: Optional[int] = None.
Pass the same integer to get identical p-values, confidence bounds, and power
estimates across runs:
result = geo_lift(data, ["city_a"], 30, 40, n_samples=200, random_state=42)- Inference: R uses time-shift placebo tests; Python uses conformal inference.
P-values are not comparable across languages even with the same seed.
See
docs/r_python_differences.md. - Deterministic quantities (ATT, L2 imbalance, weights) agree within numerical tolerance (≤ 0.01 absolute) between R and Python.