The Comparison Everyone Wants To Make
The tempting move is always the same: treated users did better, so the treatment worked. That is exactly how confounding wins.
orthoshift creates a population where treatment depends on covariates, outcomes depend on covariates, and the true average treatment effect is known. That lets the benchmark grade bias instead of vibes.
Orthogonal Residuals Do The Boring Work
The estimator fits nuisance models for outcome and treatment, residualizes both, then estimates the treatment effect from what is left. That orthogonal step is the whole point.
I kept the linear algebra in the repo. No scikit-learn import, no hidden solver, no black box. You can open the ridge routine and see exactly where the estimate came from.
The Number
The true ATE is 1.986. Naive difference reports 3.798 and misses by 1.812. Orthogonal DML reports 1.864 and misses by 0.122.
3 tests pass locally and on GitHub Actions across Python 3.9, 3.11, and 3.13.