Data API¶
uplift_bench.data.base
¶
Abstract dataset loader.
A DatasetLoader is responsible for one job: take a path on disk and produce
a validated UpliftDataset. Anything fancier (downloading, caching, schema
inference) belongs in subclasses or utility modules — keep the contract
small so swapping a dataset is a one-class change.
uplift_bench.data.validation
¶
Schema-level validation for uplift datasets.
We validate at the boundary between disk and the rest of the pipeline:
once a UpliftDataset is in memory we trust its invariants. This avoids
re-checking inside every metric and model.
The pydantic schema is intentionally narrow — it catches the dumb mistakes (wrong dtype, missing column, T outside {0, 1}) before they corrupt downstream computations. Distributional checks (overlap, class balance) live under robustness/ where they belong.
DatasetSchema
¶
Bases: BaseModel
Declarative schema for a binary-treatment uplift dataset.
Used by every loader (real and synthetic) to declare which columns are
treatment / outcome / features. Loaders pass the raw DataFrame through
validate_dataframe and get back a normalized one.
UpliftDataset
dataclass
¶
Validated, ready-to-use dataset.
df keeps the original columns; X, t, y are convenience views.
We avoid copying because for Criteo (~10M rows) the memory matters.
validate_dataframe(df, schema)
¶
Check the DataFrame matches the schema; coerce where safe.
Coercions performed:
- treatment column is cast to int8 (memory; downstream code assumes int).
- outcome column is cast to int8 if allowed_outcome_values is set,
otherwise float64.
Failures (raised as ValueError):
- missing required column,
- treatment values outside allowed_treatment_values,
- outcome values outside allowed_outcome_values (when set),
- any null in treatment, outcome, or feature columns.
uplift_bench.data.splits
¶
Train / val / test splitting for uplift datasets.
Two non-obvious choices documented here so future-me doesn't undo them:
-
We stratify on the joint (T, Y) instead of T alone. I tried T-only stratification first. On Criteo it produced a test fold where the conversion rate was 8% off the train fold purely by chance, which made bootstrap CIs confusingly wide. (T, Y) stratification keeps the marginal P(Y=1|T=t) stable across folds.
-
We force a fixed permutation per seed instead of using sklearn's
StratifiedShuffleSplitrandom state. Reason: when a single seed has to reproduce results across pandas / numpy / sklearn versions (which we pin only loosely), explicit indexing is the only thing that's actually stable. Sklearn changed its shuffle algorithm twice in the 1.x series.
make_splits(dataset, train_frac=0.7, val_frac=0.15, *, seed=42)
¶
Stratified train/val/test split for an uplift dataset.
Stratification is on the joint (treatment, outcome) when the outcome is
discrete; on treatment alone otherwise. Indices are positional integers
into dataset.df.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
UpliftDataset
|
Validated UpliftDataset. |
required |
train_frac
|
float
|
Fractions in (0, 1). test_frac = 1 - train - val. |
0.7
|
val_frac
|
float
|
Fractions in (0, 1). test_frac = 1 - train - val. |
0.7
|
seed
|
int
|
Reproducibility. |
42
|
Returns:
| Type | Description |
|---|---|
SplitIndices
|
Three disjoint, sorted arrays of positional integer indices. |
uplift_bench.data.factory
¶
Build a DatasetLoader from a name + kwargs.
Mirrors uplift_bench.models.factory. Used by the Hydra entry point.
uplift_bench.data.hillstrom
¶
Hillstrom (MineThatData) email-marketing dataset.
Kevin Hillstrom's classic 64k-row dataset. Three treatment arms:
"No E-Mail", "Mens E-Mail", "Womens E-Mail". Outcomes: visit, conversion,
spend (we use visit because it has decent base rate; conversion is
extremely rare and the bench would need a much bigger n).
We binarize treatment to "Womens E-Mail" vs "No E-Mail" by default — that
contrast has the strongest measured ATE in published analyses, which makes
it a useful sanity-check setup. The other contrasts are reachable via
treatment_arm.
uplift_bench.data.criteo
¶
Criteo Uplift v2 dataset.
The original dataset is hosted by Criteo Research: https://ailab.criteo.com/criteo-uplift-prediction-dataset/
The 12-feature CSV is ~300 MB compressed (~1.5 GB uncompressed) and has roughly 13.9M rows. We materialise the validated DataFrame as parquet on first load — pandas takes 90+ seconds to re-parse the CSV, parquet does it in 4.
For local-machine sanity we also support subsample: a fixed-seed random
sample of the full set. The bench reports both "full" and "subsample" runs
in results/ and labels them clearly.
uplift_bench.data.retailhero
¶
X5 RetailHero Uplift Modeling Contest dataset.
The data is hosted on Ods.ai behind a free account login: https://ods.ai/competitions/x5-retailhero-uplift-modeling/data
That makes a programmatic download impossible without scraping a session cookie, which we won't do. The loader expects two files placed by the user:
{data_dir}/retailhero/uplift_train.csv
{data_dir}/retailhero/clients.csv
uplift_train.csv has columns: client_id, treatment_flg, target.
clients.csv carries the customer features. We join, drop the id column,
one-hot the small categoricals, and use median-imputation for missing
numeric features (RetailHero ships some genuinely-missing values).
For tests and the smoke-config a tiny synthetic stand-in lives at
data/sample/retailhero/. It has the same schema but only a few thousand
rows so CI runs in seconds.
uplift_bench.data.megafon
¶
MegaFon Uplift Competition dataset.
Source: https://ods.ai/competitions/megafon-uplift-competition/data (also requires Ods.ai login). Same shape rationale as RetailHero — manual placement, sample stand-in for tests.
The dataset has ~600k rows, ~50 anonymised numeric features, binary treatment_group, and a binary conversion target.
uplift_bench.data.download
¶
HTTP downloader with resume + sha verification.
Used by individual loaders that can self-download (Hillstrom, Criteo). Datasets that gate behind login (RetailHero, MegaFon) raise a clear error in their loader instead of going through here.
download_file(url, dest, *, expected_sha256=None, overwrite=False, timeout=60.0)
¶
Download url to dest. Skips if file exists and hash matches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
Direct HTTP(S) URL. We don't follow login redirects on purpose — if a host needs auth, the caller should fail loud, not silently. |
required |
dest
|
Path
|
Target path. Parent created if missing. |
required |
expected_sha256
|
str | None
|
If given, verifies the downloaded file. On mismatch the file is deleted and a ValueError is raised — better than silently using corrupted data. |
None
|
overwrite
|
bool
|
Force re-download even if |
False
|
timeout
|
float
|
Per-request connect/read timeout, in seconds. |
60.0
|
Returns:
| Type | Description |
|---|---|
Path
|
|
fetch(name, data_dir)
¶
CLI dispatch: download one (or all) auto-downloadable dataset(s).