Add causal inference: bn.inference.fit(..., do={...}) (closes #79) - #137
Open
dbolser wants to merge 2 commits into
Open
Add causal inference: bn.inference.fit(..., do={...}) (closes #79)#137dbolser wants to merge 2 commits into
dbolser wants to merge 2 commits into
Conversation
…t#79) Expose Pearl's do-operator via pgmpy's CausalInference, which became available with the pgmpy 1.x migration (erdogant#135). do={} simulates setting a variable by intervention (incoming edges cut, comparable to mutilated() in the R version of bnlearn), and combines with ordinary evidence. Interventions are labeled do(X) in the readable summary. Locked test values on the sprinkler network, derived by hand from the CPDs: P(W=1|S=1)=0.927 (observational) vs P(W=1|do(S=1))=0.945. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dh1KPAuSYmLpERF2T3Drm
…ard compatible Codex confirmed two real correctness bugs in the adjustment-based CausalInference.query path (verified locally): evidence outside the adjustment set was mishandled (P(W=1|do(S=1),R=1) gave 0.9612, exact is 0.99) and interventions on causally related nodes collapsed (P(C=1|do(A=1,B=1)) gave 0.7, exact is 1.0). Do-queries now run plain variable elimination on model.do()'s mutilated network with the interventions fixed as evidence, which is exact in both cases and lets elimination_order/joint apply to causal queries too. Regression tests lock both values. Also: move do to the end of the signature so existing positional calls keep their meaning; return the factor dict directly for joint=False (attaching .df to it raised AttributeError before, on master too); fix the do(X=x) docstring notation; replace an assigned lambda (E731). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dh1KPAuSYmLpERF2T3Drm
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.
Adds Pearl's do-operator to
bn.inference.fit, so interventions are available from Python the waymutilated()is in the R version of bnlearn. Closes #79.Observing
Sprinkler=1is evidence that it was probably not cloudy; setting it by intervention says nothing about the weather.docombines freely withevidence, and interventions are labeleddo(Sprinkler)=1in the readable summary.How it works, and why not
CausalInferenceThe obvious implementation is pgmpy's
CausalInference.query, but its adjustment-based estimator gives wrong answers on two cases this feature needs, both verified against hand-derived CPT values on pgmpy 1.1.2 and on current pgmpy dev (reported as pgmpy/pgmpy#3543):CausalInference.queryP(W=1 | do(S=1), R=1)— evidence outside the adjustment setP(C=1 | do(A=1), do(B=1))— interventions on causally related nodesSo
doinstead runsVariableEliminationovermodel.do(...)'s mutilated network with the intervened variables fixed as evidence — in a network whose incoming edges are cut, conditioning is intervening. That is exact in both cases above, and it keepselimination_orderandjointworking withdo. If pgmpy fixes #3543, nothing here needs to change.Notes
dois the last parameter offit, so existing positional calls are unaffected.do, and a variable given in bothdoandevidence, raise instead of silently misbehaving.evidence=NoneraisedAttributeErroron theevidence.keys()validation line.🤖 Generated with Claude Code