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:
- Factual world: condition on observed evidence for a specific unit and infer latent exogenous characteristics (
U) - abduction.
- 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
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
Reframe
counterfactuals_do_operator.ipynb: clarify thatpm.dodemonstrates interventional (L2) what-if analysis, not unit-level counterfactuals (L3)Summary
The notebook
examples/causal_inference/counterfactuals_do_operator.ipynbis a good demonstration ofpm.dofor 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.dois 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:
P(Y | X))P(Y | do(X=x)))Y_x | X=x', Y=y')pm.dodirectly 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:
pm.observepm.doThis computes interventional predictions such as:
P(y | do(b=0), a, c)or mean analogs viay_mubut 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:
U) - abduction.do(...)and predict with those same inferredUvalues - action + prediction.The current notebook does not infer and reuse unit-level
Uacross worlds.Consequence
Readers can leave with the wrong mental model: "counterfactuals are just
pm.doafter fitting."This is especially risky because the notebook title and narrative explicitly use counterfactual language.
Evidence from notebook behavior
The current workflow intervenes on
bafter observinga,b,c,yand samplesy_muunder alternatebsettings. That yields intervention-conditioned predictions, not counterfactual outcomes for a fixed unit.In particular:
y_mu), further distancing from full unit-level counterfactual distributionsProposed fix
Reframe scope and terminology
pm.dooutputs.pm.observe+pm.doalone does not produce unit-level counterfactuals.Acceptance criteria
pm.dooutputs.pm.dois L2.Risks / caveats
pm.dois "wrong" — it correctly answers interventional questions.References
examples/causal_inference/counterfactuals_do_operator.ipynb