AIPW-style semisupervised mean inference with calibration methods.
Project description
Prediction-Powered Inference via Calibration
Many modern applications involve a small labeled dataset, a much larger unlabeled dataset, and model predictions for far more units than those with observed outcomes. A common example is a small randomized trial paired with a larger observational study or registry: predictions may improve power and precision, but outcome labels are scarce and the prediction model may be imperfect.
ppi_aipw is designed for this setting. It uses AIPW (Robins et al., 1994) as a safe and statistically efficient baseline for semisupervised mean inference when outcomes are missing completely at random. AIPW remains valid even when the prediction model is misspecified because it is doubly robust. The package then goes beyond mean-bias correction by adding calibration methods that improve the reliability of the prediction score itself, which can yield more efficient semisupervised inference when the raw predictions are miscalibrated.
What calibration means here
From an ML perspective, calibration means the prediction scale is trustworthy, not just the ranking. If a model outputs values near 0.8, we want outcomes near 0.8 on average for cases scored around 0.8.
From an inference perspective, calibration matters because this package does not just rank units; it averages predictions and uses them inside AIPW-style estimators. A miscalibrated score can therefore affect bias correction and efficiency, while recalibration can improve semisupervised mean inference without retraining the original model.
This repository contains the code and manuscript assets for our paper on calibration-based semisupervised mean inference. The repository is organized around two experiment pipelines: a synthetic simulation study and a real-data reproduction of the original PPI mean benchmarks, together with a user-facing Python package for semisupervised mean estimation with AIPW, calibration, and practical uncertainty quantification.
Repo layout
experiments/: core experiment drivers and shared estimator codesrc/ppi_aipw/: user-facing Python package for AIPW with calibrationdocs/ppi_aipw_vignette.md: package vignettedocs/index.html: interactive package website with docs, examples, and paper linkspaper/: manuscript source files and tracked manuscript-ready assetsscripts/: public entrypoints for reproductiontests/: smoke and unit testsoutputs/: local-only regenerated runs, caches, and intermediate artifacts
ppi_aipw package
This repo now includes ppi_aipw, a Python package for semisupervised mean
estimation with AIPW, calibration, and uncertainty quantification.
Install directly from GitHub:
python -m pip install "git+https://github.com/Larsvanderlaan/ppi-aipw.git"
For local development from this checkout:
python -m pip install -e .
Most users should start with:
from ppi_aipw import mean_inference
result = mean_inference(
Y,
Yhat,
Yhat_unlabeled,
method="monotone_spline",
alpha=0.1,
)
estimate = result.pointestimate
standard_error = result.se
lower, upper = result.ci
print(result.summary())
If you want a linear prognostic adjustment with optional extra covariates:
result = mean_inference(
Y,
Yhat,
Yhat_unlabeled,
X=X,
X_unlabeled=X_unlabeled,
method="prognostic_linear",
)
If you only want one output at a time, the narrower convenience functions are still available:
from ppi_aipw import mean_pointestimate, mean_ci
estimate = mean_pointestimate(Y, Yhat, Yhat_unlabeled)
lower, upper = mean_ci(Y, Yhat, Yhat_unlabeled, alpha=0.1)
If you want raw-score AIPW with empirical efficiency maximization, turn on the new flag:
result = mean_inference(
Y,
Yhat,
Yhat_unlabeled,
method="aipw",
efficiency_maximization=True,
)
estimate = result.pointestimate
lambda_hat = result.efficiency_lambda
If you want the package to choose among a small set of methods by cross-validated empirical efficiency maximization:
result = mean_inference(
Y,
Yhat,
Yhat_unlabeled,
method="auto",
candidate_methods=("aipw", "linear", "monotone_spline", "isotonic"),
num_folds=100,
)
estimate = result.pointestimate
selected_method = result.method
selected_candidate = result.selected_candidate
Plain aliases are available:
mean_inferencemean_pointestimatemean_cimean_se
The original explicit names like aipw_mean_inference,
aipw_mean_pointestimate, and aipw_mean_ci are still available too.
Available methods:
method="aipw": no calibration, just AIPW on the raw predictionsmethod="linear": linear recalibration before AIPWmethod="prognostic_linear": semisupervised linear adjustment using the prediction score plus optional extra covariatesX; the intercept and score coefficient are unpenalized, while the extra covariates are ridge-tuned on the labeled samplemethod="sigmoid": sigmoid-style recalibration with outcome-range rescalingmethod="monotone_spline": smooth monotone spline recalibration before AIPWmethod="isotonic": monotone isotonic recalibration before AIPW; by default this uses a one-round monotone XGBoost calibrator withisocal_min_child_weight=10, and you can switch toisocal_backend="sklearn"for scikit-learn isotonic regressionmethod="auto": choose among candidate methods by cross-validated influence-function variance minimization; ifaipwis among the candidates,autoalso compares against an efficiency-maximized AIPW candidatenum_folds=100: default number of folds used bymethod="auto"; it is capped at the labeled sample size- for
method="auto", the foldwise objective uses an unlabeled subset of sizemin(n_unlabeled, 10 * n_labeled)by default - for
method="auto", the selected calibration map is refit on the full labeled sample, the final point estimate uses the full unlabeled sample, and any final lambda scaling is learned from pooled out-of-fold labeled predictions plus unlabeled-subset predictions for the selected method and then reused for the point estimate and Wald SE - for
method="auto"with jackknife inference, the package selects the method once on the original sample and then refits only that chosen method in each leave-fold-out refit - for
method="auto"with bootstrap inference, the package selects the method once on the original sample and then bootstraps only that chosen method
Optional score rescaling:
efficiency_maximization=True: replace the predictor used by the estimator bylambda m(X), wherem(X)is the raw score formethod="aipw"and the calibrated score for the other methods- the package estimates the coordinatewise
lambdaby minimizing the empirical influence-function variance and then uses the empirically chosen efficiency-maximizing rescaled predictor in the final estimator - with
method="aipw", this is the unrestricted efficiency-maximized raw-score correction without clipping lambda - with
method="auto", this flag does not affect the method-choice stage; it only says whether the finally selected method also gets a cross-fitted lambda after selection
Optional weights:
wandw_unlabeledare available throughout the mean API- weights are used in the weighted means, calibration/regression fits, method selection, and standard errors
- these can be inverse probability of missingness weights to adjust for informative missingness, or balancing weights if you want to reweight toward a covariate-adjusted target population
- uniform weights reproduce the unweighted behavior
compute_two_sample_balancing_weights(X_labeled, X_unlabeled, ...)computes nonnegative labeled-sample balancing weights for the two-sample mean problem- this utility is intended for the semisupervised mean setup and does not construct causal-arm-specific weights
Available interval types:
inference="wald": fast analytic intervalinference="jackknife": recommended resampling-style interval using a V-fold delete-a-group jackknife withjackknife_folds=20by defaultinference="bootstrap": percentile bootstrap interval that refits calibration in each resample
Recommended defaults:
- start with
method="monotone_spline" - start with
inference="wald" - use
method="linear"when you want the simplest affine recalibration and maximum interpretability - use
method="prognostic_linear"when you want a linear prognostic adjustment with optional extra covariatesX - use
method="sigmoid"when predictions are probability-like or naturally bounded - use
method="monotone_spline"when you want the default smooth monotone nonlinear recalibration that is less stepwise than isotonic - use
method="isotonic"when you expect monotone nonlinear miscalibration and have enough labeled data; the default backend is monotone XGBoost, withisocal_backend="sklearn"available as a simpler fallback - use
efficiency_maximization=Truewhen you want the package to empirically rescale the score for lower variance, especially for raw-scoremethod="aipw" - use
method="auto"when you want a data-adaptive choice amongaipw,linear,monotone_spline, andisotonic, while also letting the selector compare against an efficiency-maximized AIPW candidate - use
num_folds=100as the default auto-selection setting unless you have a reason to make it smaller - use
inference="jackknife", jackknife_folds=20as the default resampling-style uncertainty check - use
inference="bootstrap"when you specifically want percentile bootstrap intervals and can afford extra compute
Result object:
result.pointestimate: point estimateresult.se: standard errorresult.ci: confidence interval for the requestedalphaandalternativeresult.method: selected base methodresult.selected_candidate: selected auto candidate labelresult.efficiency_lambda: estimated lambda when efficiency maximization is activeresult.diagnostics: method-selection and inference metadataresult.summary(): human-readable summary including the Wald t-statistic and p-value for the default null0
Calibration diagnostics helpers:
calibration_diagnostics(result_or_model, Y, Yhat, ...): structured labeled-sample calibration diagnosticsplot_calibration(diagnostics, ...): optional plotting helper built on those diagnostics; installmatplotlibor the optionalplotextra if you want the plotting convenience
Causal API:
causal_inference(Y, A, Yhat_potential, ...)computes arm-specific potential outcome means plus all control-vs-treatment ATEs in one Wald-only callYhat_potentialshould have one column per treatment arm and one row per unit- optional
Xis passed through arm-by-arm, somethod="prognostic_linear"gives the same score-plus-covariate linear adjustment inside each arm - optional full-sample weights
ware also supported and can be inverse propensity weights or balancing weights; in randomized studies they can adjust for chance covariate imbalance for efficiency, and in observational studies they can adjust for confounding for bias reduction - the returned causal result object exposes
arm_means,ate, interval fields, andresult.summary()for a compact Wald summary of both arm means and control-vs-treatment effects control_arm=Nonedefaults to the minimum resolved treatment level- this API is a convenience layer over the semisupervised mean engine, not a full observational causal pipeline
The full user guide is in docs/ppi_aipw_vignette.md. There is also a compact runnable notebook covering both the mean and causal APIs in examples/ppi_aipw_quickstart.ipynb, and it can be opened directly in Colab at colab.research.google.com/github/Larsvanderlaan/ppi-aipw/blob/main/examples/ppi_aipw_quickstart.ipynb. The package website lives at docs/index.html, with a dedicated causal page at docs/causal.html.
Environment setup
python -m venv .venv
.venv/bin/pip install -r requirements.txt
Smoke test
.venv/bin/pytest -q
./scripts/reproduce_paper.sh --smoke
The smoke run validates the full pipeline with tiny replication counts. It is intended to check that the code paths, downloads, plotting, and asset export all work.
Full paper reproduction
./scripts/reproduce_paper.sh
This runs the finalized paper settings end-to-end:
- the synthetic simulation pipeline
- synthetic plotting/export
- the real-data PPI mean reproduction pipeline
- export of manuscript-facing assets into
paper/assets/
What gets generated
- Heavy local outputs are written under
outputs/ - Tracked manuscript-ready assets are written to
paper/assets/
The repository intentionally tracks only lightweight paper assets, not the full raw outputs. Heavy outputs are excluded from git and are meant to be regenerated locally.
Rough runtime
The full reproduction is a multi-hour run. The finalized paper settings use large replication counts for both the synthetic study and the real-data benchmark rerandomization, so expect the full command to take several hours on a typical laptop or workstation.
Dataset downloads
The real-data reproduction uses the official ppi_py dataset bundle. If the cached files are not already present, they are downloaded automatically into the local cache directory under outputs/cache/ppi_datasets/.
References
- Robins, J. M., Rotnitzky, A., and Zhao, L. P. (1994). "Estimation of regression coefficients when some regressors are not always observed." Journal of the American Statistical Association 89(427): 846-866.
- Rubin, D. B. and van der Laan, M. J. (2008). "Empirical efficiency maximization: improved locally efficient covariate adjustment in randomized experiments and survival analysis." The International Journal of Biostatistics 4(1).
- van der Laan, L. and van der Laan, M. (2024). "Prediction-Powered Inference via Calibration." arXiv:2411.02771.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ppi_aipw-0.2.1.tar.gz.
File metadata
- Download URL: ppi_aipw-0.2.1.tar.gz
- Upload date:
- Size: 53.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3fe230f14f3a161338cd013c687fe30ba2c6a935cfe70c08a22078b28600085c
|
|
| MD5 |
92d11f717fb1a036bc5f9476143a473d
|
|
| BLAKE2b-256 |
dd07bf0760b8245abfd06f57013e5396143305c96ae8a6473887bc8245903c6f
|
Provenance
The following attestation bundles were made for ppi_aipw-0.2.1.tar.gz:
Publisher:
publish.yml on Larsvanderlaan/ppi-aipw
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ppi_aipw-0.2.1.tar.gz -
Subject digest:
3fe230f14f3a161338cd013c687fe30ba2c6a935cfe70c08a22078b28600085c - Sigstore transparency entry: 1282888373
- Sigstore integration time:
-
Permalink:
Larsvanderlaan/ppi-aipw@b6e08fa6befc4454a009e22b511a19c70fecbe82 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Larsvanderlaan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b6e08fa6befc4454a009e22b511a19c70fecbe82 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ppi_aipw-0.2.1-py3-none-any.whl.
File metadata
- Download URL: ppi_aipw-0.2.1-py3-none-any.whl
- Upload date:
- Size: 39.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c67c34d61597830691210a31dd686cbb9d539ab955525af322ff7ee1bb352ddc
|
|
| MD5 |
e188789b7f55b83615394948c69770be
|
|
| BLAKE2b-256 |
427e43b7b1976a89cdad8ac482e0d26ff7e17e87a52e2046017f88e34042db8f
|
Provenance
The following attestation bundles were made for ppi_aipw-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on Larsvanderlaan/ppi-aipw
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ppi_aipw-0.2.1-py3-none-any.whl -
Subject digest:
c67c34d61597830691210a31dd686cbb9d539ab955525af322ff7ee1bb352ddc - Sigstore transparency entry: 1282888377
- Sigstore integration time:
-
Permalink:
Larsvanderlaan/ppi-aipw@b6e08fa6befc4454a009e22b511a19c70fecbe82 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Larsvanderlaan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b6e08fa6befc4454a009e22b511a19c70fecbe82 -
Trigger Event:
push
-
Statement type: