Getting started
Use GeoStep when geographic units can be randomly allocated. Read the methodology before interpreting results. Install the package using the installation guide.
Run a complete parallel example
This artificial example generates a complete panel with an additive assignment effect. It illustrates the API, not expected performance on marketing data.
import numpy as np
import pandas as pd
from geostep import DiDAnalyzer, SimpleRandomizationDesigner
from geostep.reports import generate_display_results_report
rng = np.random.default_rng(42)
units = pd.DataFrame({'geo': np.arange(40)})
allocation = SimpleRandomizationDesigner(seed=42).design(units, geo_col='geo')
dates = pd.date_range('2024-01-01', periods=56, freq='D')
rows = []
for geo, assignment in allocation[['geo', 'assignment']].itertuples(index=False):
for date in dates:
outcome = 100 + geo + rng.normal(0, 10)
if assignment == 'Treatment' and date >= pd.Timestamp('2024-01-29'):
outcome += 5
rows.append({'geo': geo, 'date': date, 'assignment': assignment, 'sales': outcome})
data = pd.DataFrame(rows)
analyser = DiDAnalyzer()
result = analyser.analyze(
data, geo_col='geo', date_col='date', assignment_col='assignment', kpi_col='sales',
pre_period_start='2024-01-01', pre_period_end='2024-01-28',
test_period_start='2024-01-29', test_period_end='2024-02-25',
random_seed=42,
)
generate_display_results_report(result)
The effect and confidence interval are in sales units per geo-period. Retain
result.to_dict() alongside the allocation, input data hash and analysis plan.
Never randomise retrospectively using collected test outcomes.
Choose a normalised contrast
LiftAnalyzer accepts the same inputs as DiDAnalyzer. It divides each geo’s
change by that geo’s baseline mean. Its estimate is a difference in normalised
changes; multiplying by 100 gives percentage points. It is not automatically
relative sales lift. Baseline means must be strictly positive.
analyser.prepare_data(data, **analysis_options) returns the exact geo summaries
used for the parallel analysis, including pre_avg, test_avg and change.
Lift summaries also include lift_index. Pass these to
geostep.visualizer.plot_lift_distribution when creating a Lift figure.
Configure uncertainty
from geostep.base import AnalyzerConfig
from geostep import DiDAnalyzer
analyser = DiDAnalyzer(AnalyzerConfig(
confidence_level=0.95, bootstrap_reps=2000, bootstrap_seed=0,
))
On analyze, set use_bca_bootstrap=True for a BCa interval, or
use_permutation_test=True, n_permutations=9999 for a conditional sharp-null test.
Each result records the interval and p-value methods separately. Analysis
results are never cached.
Preserve strata
Use StratifiedRandomizationDesigner with one prespecified pre-treatment summary
per geo. Every stratum needs at least four geos. Join its assignment, probability
and stratum columns to the collected outcome panel. A column named stratum is
used automatically; otherwise pass stratum_col to the analyser. Do not remove
strata after allocation.
For strata defined externally, use
geostep.designer.randomize_within_strata(units, 'geo', seed=42, stratum_col='block').
The supplied table must contain one row per eligible geo and its frozen block.
Plan a historical power scenario
from geostep import run_power_analysis
# historical_data must be a complete regular daily or weekly panel.
# Use an actual pre-intervention dataset; do not reuse synthetic treated outcomes.
def assess_power(historical_data):
return run_power_analysis(
historical_data, geo_col='geo', date_col='date', kpi_col='sales',
effect_sizes=[0.03, 0.05], test_weeks_list=[4, 6],
pre_period_weeks=8, n_sims=500, random_seed=42,
analyzer='did', n_jobs=1,
)
Inspect status, n_valid, n_failed and failure_reasons before using power.
Durations are calendar weeks. The function does not calculate staircase or CRT
power. It reports conditional historical scenarios and Monte Carlo uncertainty.
Randomise a staircase schedule
from geostep import StaircaseDesigner
designer = StaircaseDesigner(
num_sequences=4, clusters_per_sequence=5,
control_periods=2, intervention_periods=2,
)
allocation = designer.randomize([f'geo_{i}' for i in range(20)], seed=42)
Collect outcomes according to this retained schedule. CRTAnalyzer.analyze
requires geo_col, period_col, assignment_col and kpi_col. Retain sequence
in the outcome data. Read the CRT approximation limits before analysis.
Run from the checkout
python run_pipeline.py --data your_panel.csv --analyzer did \
--geo-col geo --date-col date --kpi-col sales \
--pre-end 2024-01-28 --test-start 2024-01-29 --test-end 2024-02-25 \
--confidence-level 0.95 --random-seed 42 --output-dir results/my_trial
For CRT use --analyzer crt --period-col period and provide the recorded
geo-period outcome table. Parallel date-window flags do not define CRT periods.
Use --stratum-col block for named parallel strata and --permutation-test for
the optional parallel sharp-null test.
Continue with the business interpretation guide, API reference or advanced boundaries.