Code for the bachelor thesis "When the Propensity Model Is Wrong: Informal Benchmarking and a False Sense of Robustness in Causal Sensitivity Analysis".
- Author: Roland Vízner
- Supervisors: Jesse Krijthe, Matej Havelka
Causal conclusions from observational data rely on the assumption that every confounder (a variable that affects both treatment and outcome) is observed. Sensitivity analysis with the Marginal Sensitivity Model (MSM) relaxes this assumption through a parameter
Because IB is read entirely off a fitted propensity model, this thesis asks what happens to it when that model is misspecified. Using controlled simulations, it shows that omitting a non-linear term from the propensity model deflates
What is the interaction between the parameter from informal benchmarking and the bound from sensitivity analysis if the propensity model is incorrect?
- Omitting a quadratic term from a parametric propensity model biases
$\hat{\Gamma}_{IB}$ downward, producing falsely robust sensitivity bounds. - The mechanism is coefficient attenuation: in logistic regression an omitted term shrinks every fitted coefficient toward zero (non-collapsibility), so the error leaks into the benchmark even when it is measured only on covariates that are individually well specified.
- A practical safeguard: refit the propensity model with a richer, still cross-fitted specification and rerun IB; a rise in
$\hat{\Gamma}_{IB}$ signals that the original estimate was deflated.
├── src/ # Core library
│ ├── data_generation/ # Synthetic DGPs: LinearDGP (base), QuadraticDGP, UniformProxyDGP
│ ├── propensity_models/ # LogisticPropensityEstimator, QuadraticLogisticPropensityEstimator
│ └── analysis/ # InformalBenchmarking (K-fold cross-fitted, leave-one-out)
├── experiments/ # Reproducible experiment drivers and shared plot style
└── results/ # Saved data (CSV) and generated plots
The thesis write-up, poster, presentation, and background literature are kept out of this public code repository.
python -m venv venv
.\venv\Scripts\Activate.ps1
pip install -r requirements.txtRun from the repository root:
# Experiment 1 - functional-form misspecification (main paper results)
python experiments/quadratic_misspecification_gamma_scaling.py
# Experiment 2 - structural omission of a correlated covariate (appendix)
python experiments/correlated_omission_gamma_scaling.py
# Sanity check - including X1 in the benchmark set
python experiments/sanity_x1_benchmark.pyEvery sweep is seeded for reproducibility and writes its results to results/data/. Pass --replot to an experiment script to regenerate its figures from the saved CSVs without rerunning the sweep.
from src.data_generation.quadratic import QuadraticDGP, QuadraticDGPConfig
from src.analysis.informal_benchmarking import InformalBenchmarking
from src.propensity_models.logistic import LogisticPropensityEstimator
# True propensity has a quadratic term in X1; alpha sets its strength
config = QuadraticDGPConfig(p_x=5, p_u=2, lambda_=0.6, alpha=2.0)
data = QuadraticDGP(config).sample(n=5000, seed=42)
# Informal Benchmarking with a (misspecified) linear logistic model
ib = InformalBenchmarking(estimator_factory=LogisticPropensityEstimator, n_folds=5)
result = ib.leave_one_out(data.X, data.T)
print(f"Gamma_IB = {result.gamma_high:.3f}")