Add instrumental-variable LATE example + regression test (#346)#354
Draft
daimon-pymclabs wants to merge 9 commits into
Draft
Add instrumental-variable LATE example + regression test (#346)#354daimon-pymclabs wants to merge 9 commits into
daimon-pymclabs wants to merge 9 commits into
Conversation
Reproduces the non-compliance / LATE example from Facure's causality
handbook and its PyMC counterpart, showing that pathmc estimates a
continuous-outcome IV model with zero API change via the ~~
correlated-residual operator:
push_delivered ~ push_assigned # first stage (relevance)
in_app_purchase ~ push_delivered # structural eq (instrument excluded)
push_delivered ~~ in_app_purchase # unobserved-confounder endogeneity
The worked example explains what an instrumental variable is, why it is
needed under non-compliance, and how the spec maps to the IV assumptions.
On simulated data with a known true effect, naive OLS is biased ~2x upward
while the IV posterior recovers the truth. A fixed-seed regression test
asserts recovery and that the IV estimate beats naive OLS.
Part of #343.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
daimon-pymclabs
requested review from
cetagostini,
drbenvincent and
juanitorduz
as code owners
June 26, 2026 09:43
juanitorduz
marked this pull request as draft
June 26, 2026 09:59
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MvNormal residual block coupled variables on very different scales (purchases ~25 vs delivery ~0.3, a ~23x ratio), which made NUTS crawl with deep trees. Rescale the outcome (smaller intercept, confounder, and noise) so the residual-scale ratio drops to ~3x while preserving the ~2x naive-OLS bias story and clean recovery of the known LATE. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cut the simulated sample to 2,000 customers and match dynamic_pricing's 500/500 sampler settings so the page fits/renders quickly (the full 10k-row do() posterior-predictive pass dominated freeze time). The ~2x naive-OLS bias and clean LATE recovery are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The do() posterior-predictive pass dominated render time and is redundant for a linear structural model — the do(1)-do(0) contrast equals the push_delivered coefficient already shown. Keep the teaching point as prose so the page renders quickly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nutpie on the pinned pymc/pytensor stack produces a non-converged posterior for this binary-treatment IV block (LATE ~11 with an absurd HDI). The default PyMC NUTS sampler recovers the known LATE cleanly (mean 0.55, 94% HDI [0.43, 0.69] vs true 0.6; naive OLS 1.23 sits outside the interval). Drop the nutpie request and commit the executed _freeze/ cache so the docs build restores cached outputs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the now-short in_app_purchase assignment to one line (ruff) and regenerate the freeze so its source hash matches the formatted qmd. Recovered numbers are unchanged (data and seed are identical). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #346 (part of the IV epic #343). Adds a worked instrumental-variable example and a regression test, demonstrating that pathmc already estimates a continuous-outcome IV model with zero API change through the
~~correlated-residual operator.The example reproduces the non-compliance / LATE example from Matheus Facure's Causal Inference for the Brave and True and its PyMC counterpart by Juan Orduz: a randomized push-notification campaign where assignment is random but delivery is not, so engaged customers both receive more pushes and buy more.
The IV spec
The
~~block compiles the two equations into a jointMvNormalwith anLKJCholeskyCovresidual covariance — the Bayesian limited-information IV structure. The residual correlation soaks up the unobserved engagement confounder; the structural coefficient onpush_deliveredrecovers the LATE.What the example covers
model.graph()/equations(), the PyMC plate, recovery vs. the Wald estimator, the residual-correlation diagnostic, and ado()counterfactual.is_identifiableis backdoor-only today (false negative for IV), pointing at IV-aware identification: instrument detection & validation #344.On simulated data with a known true effect, the IV posterior brackets the truth and the frequentist Wald estimate while naive OLS sits far outside the interval.
Regression test
tests/test_instrumental_variable.py— fixed-seed confounded system with a valid instrument; asserts naive OLS is biased, the IV posterior recovers the true effect, beats naive OLS, and yields a positive endogeneity correlation. Robust at the suite's fast-sampling cap.🤖 Generated with Claude Code