How-to Guides

Subsections of How-to Guides

Define the Estimand and Windows

Write the estimand before configuring GeoSC:

Average difference, in outcome units per observed period, between treated outcomes and their SparseSC counterfactual over the declared measurement window for the declared treated geographies.

GeoSC labels this att_unscaled. The top-level att is the average across the post-period effect path. If several geographies are treated, the SparseSC procedure forms a joint treated effect; do not describe the result as a unit-specific effect unless a separate analysis supports that claim.

Set:

  • intervention_date_str: first observed treatment period;
  • cooldown_periods: observed periods after launch excluded from measurement;
  • optional measurement_start_date_str: explicit start that must agree with the cooldown calculation;
  • min_pre_treatment_periods and min_post_treatment_periods: admissibility thresholds where supplied.

Cooldown periods are neither pre-period fit data nor measured post-period data. They count observations, not calendar units. A seven-period cooldown is seven days only for a complete daily panel.

Pre-specify the end of measurement by trimming the input panel to the intended window. The shipped inference config has no active end_date key.

Define Treatment and Donor Eligibility

Create an eligibility table before running GeoSC. For every geography record:

  • treatment assignment;
  • inclusion or exclusion;
  • reason;
  • decision date and owner;
  • known campaign, operational, or measurement contamination;
  • geographic adjacency or audience leakage concerns.

Eligible donors should be unaffected by treatment, measured consistently, and plausible counterfactual contributors. Pre-period correlation alone is not an eligibility rule. Exclude geographies for design reasons established without post-treatment outcomes.

Power treats every non-treatment unit in its input as a control. Donor evaluation scores every eligible non-treatment unit in its own input. Inference uses the controls in its prepared panel. GeoSC does not automatically apply the donor recommendations to the other stages. Materialise the approved universe as a versioned panel and reuse it across the stages.

For several treated geographies, confirm that the donor pool can support joint in-space placebo assignments: with \(N_0\) controls and \(N_1\) treated units, the exhaustive assignment count is \(\binom{N_0}{N_1}\), provided \(N_0 \geq N_1\).

Diagnose a Weak Design

Work from failure mechanism to remedy.

Symptom Likely issue Action
Poor pre-period fit Donors cannot reproduce treated dynamics Revisit eligibility, geography level, outcome, and comparable history.
Coarse p-value granularity Too few effective placebo assignments Add eligible controls or reduce treated count only if the design permits it.
Dominant fitted donor weight Counterfactual depends on one geography Audit that donor and run exclusion sensitivity.
High power failure rate SparseSC or DGP instability Inspect exceptions, rank, panel completeness, and placebo feasibility.
High grid-based MDE Noise or weak counterfactual relative to plausible lift Redesign duration, outcome, treated scale, or method before launch.
Interference signal Outcome changes among controls are compatible with contamination Investigate exposure using campaign and geographic evidence; the screen is not causal proof.
Result partial Missing uncertainty or warnings Resolve the stated fields before decision use.

Do not tune exclusions, dates, or donors by searching for significance. If the remedy changes the design, rerun power and record the new specification.

Evaluate Donors

Run donor evaluation on pre-treatment data after applying substantive eligibility exclusions.

geosc donors --config data-config/donor_eval_config.yaml --jobs -1

The evaluator aligns each treatment-donor pair by time, then calculates Pearson correlation and its p-value, root mean squared error (RMSE), percentage RMSE, mean absolute percentage error (MAPE), and normalised dynamic time-warping (DTW) distance. It normalises metrics within each treated geography, combines them using configured or adaptive weights, and ranks complete rows.

Review:

  • overlap_count, treatment_coverage, and donor_coverage before scores;
  • metrics_complete and metric_failure_reasons;
  • correlation, rmse, mape, and dtw, not only composite_score;
  • warning_flags, quality bands, concentration, and effective donor count;
  • maps only as a geographic diagnostic, not as proof of comparability.

selected_weight is normalised from positive composite scores among the recommended donors. Its role is design_stage_recommendation_weight; it is not a fitted SparseSC counterfactual weight.

The evaluator’s output is a screening artefact. If it changes the donor pool, filter the canonical panel and rerun power and inference. To render maps, see Use Shapemaps.

How to Use Shapemaps

GeoSC donor-stage maps use shapemap_file to read a GeoPandas-compatible vector file and shapemap_id_column to choose the geography ID used for joins. The values in that ID column must match the unit IDs used in your experiment data, such as DMA IDs, postcode districts, postcode areas, or another market key. The inference and power stages do not consume shapemap configuration.

Built wheel and sdist installs do not include the repository-local shapemap/ directory. Packaged users should provide map files from their own runtime environment.

Basic Config

shapemap_file: "/path/to/markets.geojson"
shapemap_id_column: "id"

GeoSC can read formats supported by GeoPandas, including GeoJSON, TopoJSON, Shapefile, and GeoPackage inputs when the local geospatial stack supports them.

Repository-local shapemap examples are available for common markets:

  • shapemap/uk/postcode_areas.geojson
  • shapemap/uk/postcode_districts.geojson
  • shapemap/us/dma_legacy_normalized.geojson
  • shapemap/fr/fr_nuts3.geojson
  • shapemap/de/de_nuts3.geojson
  • shapemap/uk/meta_commuting_zones.geojson
  • shapemap/us/meta_commuting_zones.geojson
  • shapemap/fr/fr_meta_commuting_zones.geojson
  • shapemap/de/de_meta_commuting_zones.geojson

These are source-checkout assets. They are not included in built wheel or sdist artifacts.

Meta Commuting Zones are also available as optional geographies for Meta-aligned study design. They are commuting/economic zones, not Meta ad-targeting boundaries.

UK Postcode District Example

For UK production workflows, postcode district is often the practical market geography:

location_col_name: "postcode_district"
treatment_locations: ["SW1A", "M1"]
shapemap_file: "/secure/maps/uk_postcode_districts.geojson"
shapemap_id_column: "id"

The input data must use the same IDs as the shapemap:

postcode_district,date,Y
SW1A,01/01/2025,125.3
SW1A,02/01/2025,128.7
M1,01/01/2025,98.2

Use scripts/prepare_uk_postcode_shapemap.py from a source checkout to normalize licensed or user-supplied postcode polygon data:

python scripts/prepare_uk_postcode_shapemap.py \
  --input /path/to/licensed-postcode-polygons.gpkg \
  --output shapemap/uk/postcode_districts.geojson \
  --level district \
  --postcode-column postcode \
  --metadata-output shapemap/uk/postcode_districts.metadata.json

The generated file exposes:

  • id: postcode district, such as SW1A, M1, or BT7
  • name: display name, defaulting to the same value as id
  • country: UK
  • geography_level: postcode_district
  • geometry: dissolved polygon geometry

The repository includes an approximate version built from ONSPD live postcode centroids. Its metadata file retains the derivation year in its name:

location_col_name: "postcode_district"
treatment_locations: ["SW1A", "M1"]
shapemap_file: "shapemap/uk/postcode_districts.geojson"
shapemap_id_column: "id"

Validate the generated file before using it in donor evaluation:

python scripts/validate_shapemap.py \
  --shapemap shapemap/uk/postcode_districts.geojson \
  --id-column id \
  --expected-ids SW1A,M1 \
  --json

UK Postcode Area Example

For broader UK markets, use postcode areas:

location_col_name: "postcode_area"
treatment_locations: ["SW", "M"]
shapemap_file: "/secure/maps/uk_postcode_areas.geojson"
shapemap_id_column: "id"

Prepare the map with:

python scripts/prepare_uk_postcode_shapemap.py \
  --input /path/to/licensed-postcode-polygons.gpkg \
  --output shapemap/uk/postcode_areas.geojson \
  --level area \
  --postcode-column postcode

Provenance and Licensing

Do not commit or distribute postcode geometry unless the source license permits that use. The repository records shapemap provenance policy in shapemap/README.md and shapemap/manifest.yaml.

For UK data:

  • OS Code-Point Open is an open postcode unit point dataset, not polygon geometry.
  • OS Code-Point with Polygons is the appropriate official polygon source when you have the necessary access and licensing.
  • ONS Postcode Directory is useful for postcode-to-geography lookup workflows.

Troubleshooting

If donor maps do not highlight treatment or donor units:

  • confirm the shapemap contains the configured shapemap_id_column
  • confirm the shapemap ID values exactly match the experiment unit IDs
  • normalize both sides to the same case and spacing convention
  • confirm the file can be opened by geopandas.read_file

scripts/validate_shapemap.py reports missing expected IDs, duplicate IDs, blank IDs, null or empty geometry, invalid geometry, row counts, unique ID counts, and the detected CRS.

Prepare an Input Panel

Use a long CSV with one row per geography and observed period. The inference loader requires configured date, geography, and outcome columns; treatment is defined in YAML, not read from a treatment flag in the CSV.

Required checks

  1. Use stable geography identifiers and one geographic level.
  2. Use a regular time interval appropriate to the campaign and outcome.
  3. Confirm that every intended unit-period has one finite outcome.
  4. Make the intervention date an observed period.
  5. Preserve enough observations before and after the resolved measurement start.
  6. Use a consistent outcome definition, currency, attribution window, and data production process across geographies and time.

Inference defaults to duplicate_policy: error and missing_outcome_policy: error. Alternative policies are explicit analytical decisions. mean or sum changes duplicate rows before pivoting; drop_unit, drop_period, or impute_with_report changes the estimable panel.

data_path: data/client_panel.csv
date_col_name: date
location_col_name: region_id
outcome_col_name: revenue
date_format: "%Y-%m-%d"
treatment_unit_ids: ["FRJ"]
intervention_date_str: "2026-09-01"
duplicate_policy: error
missing_outcome_policy: error

Do not use locale-dependent date parsing in an audited run. After execution, review data_validation.json; a successful model does not supersede a failed data contract.

Produce a Stakeholder Summary

Lead with the decision and its conditions. A compact summary should include:

  • treated geography, outcome, launch, cooldown, and measurement dates;
  • eligible donor universe and material exclusions;
  • design-stage MDE at the stated target power and plausible lift range;
  • inference status, ATT in outcome units, interval, p-value, and placebo count;
  • pre-period fit and donor concentration;
  • concurrent media, spillover, data, and sensitivity caveats;
  • a clear recommendation: proceed, interpret cautiously, redesign, or reject.

Example:

GeoSC estimated average incremental revenue of £X per week against the pre-specified SparseSC counterfactual over dates A–B. The two-sided in-space placebo p-value was P using N placebo assignments; the interval was [L, U]. Interpretation is conditional on the recorded donor-eligibility and treatment- isolation assumptions. The design-stage grid-based MDE was M% at T% target power, so effects below M% were not reliably detectable under the fitted DGP.

Do not translate statistical significance into ROI without campaign cost and a commercial value definition. Do not describe status: ok as proof of causal validity.

Run Inference

Freeze the panel and inference YAML before viewing post-treatment results, then run:

geosc infer \
  --config data-config/geolift_analysis_config.yaml \
  --create-plots

Use --data to override only the CSV path and --no-create-plots to suppress plotting. The command returns non-zero when the stage raises an error or the result status is failed.

Review outputs in this order:

  1. data_validation.json for panel construction and window metadata;
  2. assumption_validation.json for the parallel-trends gate and advisory interference screen;
  3. geolift_results.json for status, unscaled ATT, p-value, interval, placebo count, granularity, warnings, and errors;
  4. geolift_diagnostics.json for pre-fit and fitted donor-weight diagnostics;
  5. uplift_timeseries.png as a visual diagnostic.

Describe the result as an estimated difference from the configured SparseSC counterfactual. Causal interpretation requires defensible donor exchangeability, treatment isolation, stable measurement, timing, and absence of material unmodelled shocks. The advisory interference screen cannot prove or disprove spillover.

Run Power Analysis

Define a plausible effect grid and duration grid, then run:

geosc power --config data-config/power_analysis_config.yaml --jobs -1

Use a fixed random_seed for an audited run. Seeded runs use CPU simulation paths. Start with a small n_simulations only to debug the configuration, then increase it until Monte Carlo intervals are adequate for the decision.

For each row:

  1. require valid: true;
  2. inspect failure_rate and warnings;
  3. compare power with its Wilson interval;
  4. verify the DGP rank, explained variance, backends, placebo support, and injection metadata;
  5. interpret MDE as the smallest tested effect reaching target_power for a duration.

GeoSC does not interpolate between effect sizes. If no grid value reaches the target, report MDE as greater than the maximum tested effect. The CLI’s default planning threshold is 80%; it is configurable through target_power and is not a statistical property of SparseSC.

Power uses the SparseSC estimator and p-value detection rule inside each simulation, but it does not execute the full inference workflow. It does not run inference data policies, cooldown resolution, assumption checks, result processing, or plotting. Align the power input panel and intervention split with the intended inference design yourself.

Run Sensitivity Analysis

Pre-specify a small set of defensible perturbations:

  • alternative comparable pre-period windows;
  • exclusion of a substantively suspect or dominant donor;
  • plausible cooldown or measurement-start choices;
  • DGP rank and effect-pattern choices for power;
  • outcome definitions fixed before post-period inspection;
  • eligible geography aggregations justified by media delivery and spillover.

For each run, save its panel hash, config, artefacts, and rationale. Compare ATT, intervals, p-values, pre-fit, donor weights, placebo support, MDE, and failure rates. Distinguish robustness to a design choice from fishing over arbitrary specifications.

If the decision changes under small, equally defensible perturbations, report the result as specification-sensitive. Do not average incompatible estimands.

Run the Pipeline

Place the three canonical stage configs in one directory, then point the pipeline at any one of them:

geosc pipeline \
  --config config/geolift_analysis_config.yaml \
  --output-dir outputs/run_001

The required filenames are power_analysis_config.yaml, donor_eval_config.yaml, and geolift_analysis_config.yaml. The command runs power, donors, then inference and writes Markdown and HTML summaries by default.

Useful selectors:

geosc pipeline --config config/geolift_analysis_config.yaml --skip-power
geosc pipeline --config config/geolift_analysis_config.yaml --skip-donor
geosc pipeline --config config/geolift_analysis_config.yaml --only-inference
geosc pipeline --config config/geolift_analysis_config.yaml --no-report

--output-dir creates the legacy stage subdirectories below the supplied root. --jobs and --use-gpu are passed as runtime overrides, subject to each stage’s support.

The pipeline is an orchestrator, not a design-state engine. Donor recommendations do not filter power or inference inputs. The compact report is not a decision gate; inspect each underlying artefact and record any manual panel hand-off.

Use the Python API

For file-based inference, initialise the public analyser with a config and data path:

from geolift import GeoLiftAnalyzer

analyser = GeoLiftAnalyzer(
    config_path="config/inference.yaml",
    data_path="data/panel.csv",
)
result = analyser.run_analysis()

if result["status"] == "failed":
    raise RuntimeError(result["errors"])
if result["status"] == "partial":
    print("Review warnings:", result["warnings"])

Direct-data construction is also supported by GeoLiftAnalyzer; the panel must be wide with units as rows and datetime-like periods as columns, with a unit- indexed treatment-period series. Use the Python API reference for signatures.

Power is available through geolift.power.SparseSCPowerCalculator. Donor screening is available through geolift.donor_evaluator.DonorEvaluator, but neither is re-exported from the package root. Prefer the CLI for versioned file-based runs because it writes the maintained artefact family.

Validate a Power Simulation

Treat a power run as a model requiring diagnostics.

Reproducibility

Repeat the same CPU run with the same seed and compare the CSVs. Record the package version, input hash, YAML, seed, and execution backends.

Monte Carlo precision

Use power_ci_lower and power_ci_upper. If the interval straddles the target power, increase simulations before making a boundary decision. The interval quantifies simulation-count uncertainty only; it does not include uncertainty about the DGP specification.

DGP plausibility

Review dgp_rank, dgp_explained_var, and dgp_factor_dynamics. Compare observed and simulated distributions outside GeoSC where the decision is material: unit means and variances, cross-unit correlations, autocorrelation, seasonal patterns, extremes, and zeros. The current DGP does not model calendar effects or heteroskedastic idiosyncratic noise explicitly.

Grid stability

Power should usually rise with larger injected effects in a well-resolved simulation, but finite Monte Carlo estimates can move up or down. Investigate large reversals, high failure rates, coarse placebo p-values, or sensitivity to rank, history window, seed, donor eligibility, and effect pattern.

Do not call the run validated merely because all rows are valid. That flag only checks that at least one simulation succeeded and that the failure rate did not exceed the configured threshold.