Skip to content

Reframe counterfactuals_do_operator.ipynb as interventional what-if tutorial #849

@drbenvincent

Description

@drbenvincent

Reframe counterfactuals_do_operator.ipynb: clarify that pm.do demonstrates interventional (L2) what-if analysis, not unit-level counterfactuals (L3)

Summary

The notebook examples/causal_inference/counterfactuals_do_operator.ipynb is a good demonstration of pm.do for interventional "what-if" analysis, but it currently labels these outputs as "counterfactuals" without implementing the required abduction step.

This can mislead readers into thinking that pm.observe + pm.do is sufficient for unit-level counterfactual inference. It is not.

The proposed fix is to reframe and relabel the existing notebook so it accurately describes its content as interventional (rung 2) analysis, and to add a brief note explaining why unit-level counterfactuals (rung 3) require additional steps beyond what the notebook demonstrates.

Causal ladder framing (important)

The confusion in the current notebook is best explained using Pearl's causal ladder:

  • L1: Association (seeing) - "What do we observe?" (P(Y | X))
  • L2: Intervention (doing) - "What happens if we force a variable?" (P(Y | do(X=x)))
  • L3: Counterfactuals (imagining/retrospection for a unit) - "What would have happened to this specific unit under a different action, given what we already observed?" (Y_x | X=x', Y=y')

pm.do directly supports L2 queries. True unit-level counterfactuals are L3 queries and require an additional abduction step to infer unit-specific exogenous terms before prediction in the intervened world.

So the key edit is not that the notebook is "wrong everywhere" - it is mostly a strong L2 tutorial currently described with some L3 language.

Problem details

Current notebook:

  • builds a generative model
  • conditions using pm.observe
  • applies interventions with pm.do
  • samples predictions and calls them "counterfactuals"

This computes interventional predictions such as:

  • P(y | do(b=0), a, c) or mean analogs via y_mu

but does not compute a unit-level counterfactual Y_{b=0} for a specific observed individual with known factual outcome.

Why calling these "counterfactuals" is inaccurate

Counterfactuals are a two-world query:

  1. Factual world: condition on observed evidence for a specific unit and infer latent exogenous characteristics (U) - abduction.
  2. Counterfactual world: modify equations with do(...) and predict with those same inferred U values - action + prediction.

The current notebook does not infer and reuse unit-level U across worlds.

Consequence

Readers can leave with the wrong mental model: "counterfactuals are just pm.do after fitting."

This is especially risky because the notebook title and narrative explicitly use counterfactual language.

Evidence from notebook behavior

The current workflow intervenes on b after observing a,b,c,y and samples y_mu under alternate b settings. That yields intervention-conditioned predictions, not counterfactual outcomes for a fixed unit.

In particular:

  • no explicit abduction step for latent exogenous terms
  • no preservation of the same unit-specific disturbance across factual and counterfactual worlds
  • outputs are often posterior means (y_mu), further distancing from full unit-level counterfactual distributions

Proposed fix

Reframe scope and terminology

  • Change the notebook title from "Counterfactual generation using pymc do-operator" to something like "Interventional what-if analysis using the PyMC do-operator."
  • Replace uses of "counterfactual" with "interventional what-if" or "what-if scenario" where they refer to pm.do outputs.
  • Keep the existing code and pedagogical flow — it is a solid L2 tutorial.
  • Add a brief section (or callout) that explains:
    • What the notebook computes is on rung 2 of Pearl's causal ladder (intervention).
    • Rung 3 (unit-level counterfactuals) requires an additional abduction step to infer unit-specific exogenous terms before prediction in the intervened world.
    • pm.observe + pm.do alone does not produce unit-level counterfactuals.
  • Add a reference to Pearl's Causal Inference in Statistics: A Primer Section 4.2.3-4.2.4 for readers who want to learn about true counterfactuals.

Acceptance criteria

  • Title and narrative use "interventional what-if" (or similar) instead of "counterfactual" for pm.do outputs.
  • A section or callout explicitly explains the L2/L3 distinction and why pm.do is L2.
  • Text notes that unit-level counterfactuals (L3) require abduction of exogenous terms, which is not demonstrated here.
  • Existing code and scenarios are preserved — only labels and framing text change.
  • References include Pearl's Primer Section 4.2.3-4.2.4 for readers interested in L3 counterfactuals.

Risks / caveats

  • The fix is deliberately scoped to relabeling and adding a brief explanatory note — no new code or examples are added in this issue.
  • The notebook remains a useful L2 tutorial; we are not deprecating it.
  • Care should be taken not to imply that pm.do is "wrong" — it correctly answers interventional questions.

References

  • Pearl, Glymour, Jewell (2016), Causal Inference in Statistics: A Primer, Section 4.2.3-4.2.4
  • PyMC example notebook: examples/causal_inference/counterfactuals_do_operator.ipynb

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions