Pont inter-series (constellation causale) : le do-calculus de Pearl se decline en plusieurs paradigmes dans ce depot, et ce notebook en est le versant probabiliste distributionnel (.NET/Infer.NET) – les effets causaux y sont calcules par mutilation de graphe. Le jumeau Python/PyMC porte les mêmes exemples (barometre, Sprinkler) via l’opérateur natif pm.do : voir PyMC-05-Causal-Inference, ainsi que la demo historique P(Cloudy|do(Rain)) dans PyMC-04-Bayesian-Networks. Enfin, le traitement symbolique (Java/Tweety, raisonneur contrefactuel propositionnel) est dans Tweety-11-Causal. La section Constellation causale en fin de notebook cartographie ces quatre implementations (y compris l’emergence causale a l’echelle, ICT-5).
1. Pourquoi la causalite ? — L’echelle de Pearl
La probabilite seule repond aux questions d’association. Mais de nombreuses questions pratiques sont causales :
Question
Type
Reponse probabiliste
Si je vois le barometre baisser, quelle est la proba d’une tempete ?
Observation P(Storm\|Baro)
Oui
Si je provoque la chute du barometre, la tempete vient-elle ?
Intervention P(Storm\|do(Baro))
Non (sans causalite)
Si j’avais pris le medicament (alors que je ne l’ai pas pris), aurais-je gueri ?
Contrefactuel
Non
Jude Pearl (2000, Causality, Cambridge University Press) structure le raisonnement causal en trois niveaux (l’echelle de Pearl) :
Niveau 1 — Association (voir) : P(Y|X). “Que disent les données ?”
Niveau 2 — Intervention (faire) : P(Y|do(X)). “Que se passe-t-il si j’agis ?”
Niveau 3 — Contrefactuel (imaginer) : P(Y_x | X', Y'). “Que se serait-il passe si ?”
Inegalite fondamentale : P(Y|X) != P(Y|do(X)) des qu’un confondeur (cause commune de X et Y) biaise l’observation. Ce notebook montre comment un reseau bayesien observationnel ne sait pas calculer do(X) seul — il faut mutiler le graphe (couper les arcs entrants de X), une opération que nous implementons explicitement en Infer.NET.
Références : Pearl, J. (1995), Causal diagrams for empirical research, JRSS B 57(3) ; Pearl, J. (2000), Causality : Models, Reasoning, and Inference, Cambridge University Press ; Pearl, J., Glymour, M. & Jewell, N. P. (2016), Causal Inference in Statistics : A Primer, Wiley.
2. Configuration d’Infer.NET
Chargement du moteur d’inference probabiliste et du helper de visualisation des graphes de facteurs (même #load que toute la serie, cf Infer-3, Infer-4).
The below script needs to be able to find the current output cell; this is an easy method to get it.
Installing Packages
Microsoft.ML.Probabilistic
Microsoft.ML.Probabilistic.Compiler
Infer.NET charge.
Chargement du helper pour visualiser les graphes de facteurs (Graphviz).
// Chargement du helper de visualisation des graphes de facteurs#load "FactorGraphHelper.cs"Console.WriteLine("FactorGraphHelper charge.");Console.WriteLine($"Graphviz disponible : {FactorGraphHelper.IsGraphvizAvailable()}");
3. Exemple 1 — Le barometre (confondeur a 3 noeuds)
Pearl illustre la confusion causale par le barometre : un barometre qui baisse annonce une tempete, mais provoquer la chute du barometre ne declenche pas de tempete. Pourquoi ? Parce qu’une cause commune — la basse pression atmospherique — provoque a la fois la chute du barometre ET la tempete.
BassePression
/ \
v v
Barometre Tempete
BassePression -> Barometre : quand la pression baisse, le barometre baisse.
BassePression -> Tempete : quand la pression baisse, la tempete vient.
Pas d’arcBarometre -> Tempete : le barometre n’est qu’un indicateur, pas une cause.
Le barometre et la tempete sont donc correles (observation) mais il n’y a aucun effet causal de l’un sur l’autre (intervention). Construisons ce SCM (Structural Causal Model) et affichons son graphe de facteurs.
Compiling model...done.
Structure du SCM Barometre : BassePression -> {Barometre, Tempete}.
(Graphe de facteurs ci-dessous : aucun arc direct Barometre -> Tempete.)
Model_09_18_26_05_25_38_96.svg
3.1 Niveau 1 — Observation P(Storm | Barometre baisse)
Si on observe le barometre baisser, la probabilite d’une tempete grimpe : la baisse est un signal que la pression est probablement basse, et donc qu’une tempete approche. Mais c’est une simple association statistique.
3.2 Niveau 2 — Intervention P(Storm | do(Barometre baisse))
Maintenant provoquons la chute du barometre (par exemple en tapant dessus). Pour modeliser cette intervention, on mutile le graphe : on coupe l’arc BassePression -> Barometre et on force Barometre = baisseindependamment de la pression. En Infer.NET cela se traduit par Variable.Bernoulli(1.0) (valeur forcee, sans parent) au lieu d’un SetTo conditionne a la pression — c’est l’idiome d’introduction du do() (déjà utilise en Infer-4 cell39).
// NIVEAU 2 — Intervention : P(Storm | do(Barometre baisse))// Mutilation : on COUPE l'arc BassePression -> Barometre en forcant Barometre sans parent.Variable<bool> prInt = Variable.Bernoulli(0.30).Named("prInt");Variable<bool> baroInt = Variable.Bernoulli(1.0).Named("baroInt");// force a True, SANS dependre de prInt = do()Variable<bool> stormInt = Variable.New<bool>().Named("stormInt");using(Variable.If(prInt)){ stormInt.SetTo(Variable.Bernoulli(0.80));}using(Variable.IfNot(prInt)){ stormInt.SetTo(Variable.Bernoulli(0.10));}var eInt =newInferenceEngine();eInt.Compiler.CompilerChoice= CompilerChoice.Roslyn;double pStormDo = eInt.Infer<Bernoulli>(stormInt).GetProbTrue();Console.WriteLine($"P(Storm | do(Barometre baisse)) = {pStormDo:F3} (interventionnel)");Console.WriteLine($"P(Storm) marginale = 0.310");Console.WriteLine();Console.WriteLine($"=> Observer le barometre informe sur la tempete : {pStormObs:F3}");Console.WriteLine($" PROVOQUER sa chute ne change rien a la tempete : {pStormDo:F3}");Console.WriteLine(" Voir != Faire (Pearl, 2000).");
Compiling model...done.
P(Storm | do(Barometre baisse)) = 0,310 (interventionnel)
P(Storm) marginale = 0.310
=> Observer le barometre informe sur la tempete : 0,656
PROVOQUER sa chute ne change rien a la tempete : 0,310
Voir != Faire (Pearl, 2000).
Résultat : P(Storm|Barometre baisse) (observation) !=P(Storm|do(Barometre baisse)) (intervention). L’effet causal de l’intervention est nul — la chute du barometre ne provoque pas de tempete. L’observation était biaisee par le confondeur BassePression.
Prong-B (non-trivialite) : ce résultat n’est pas une lecture triviale de CPT. Un reseau bayesien observationnel calcule P(Storm|Barometre baisse) mais ne sait pas calculer do(Barometre baisse) seul — il a fallu mutiler le graphe (couper l’arc entrant). Les deux quantites différent visiblement, ce qui prouve que le do() fait un travail reel.
4. Reseau Sprinkler — cross-check avec PyMC-4
Verifions sur le reseau canonique de Pearl (Cloudy -> {Sprinkler, Rain} -> WetGrass, déjà construit en Infer-4) que nos valeurs Infer.NET recoupent le jumeau Python/PyMC. La requête P(Cloudy | Rain=True) vs P(Cloudy | do(Rain=True)) vaut 0.800 vs 0.500 dans PyMC-4 (section 8).
Quand le confondeur U est observe, l’effet causal se calcule sans mutiler le graphe, par la formule d’adjustment backdoor (Pearl, 1995) :
P(Y | do(X)) = Somme_u P(Y | X, u) P(u)
On somme l’effet de X sur Y sur toutes les valeurs du confondeur, ponds par la distribution marginale de U. Si Ubloque tous les chemins “backdoor” (arcs entrants de X passant par U), cette formule restitue l’effet causal exact. Verifions sur le barometre, ou U = BassePression bloque le chemin Barometre <- BassePression -> Tempete : l’ajustement doit coincider avec le do() direct (0.310).
// BACKDOOR ADJUSTMENT : P(Storm | do(Baro)) = Somme_pression P(Storm | Baro, pression) P(pression)// Ici Storm independant de Baro | Pression => P(Storm | Baro, pression) = P(Storm | pression)// (le moteur d'inference calcule chaque conditionnelle)double pBassePression =0.30;// P(Storm | pression=basse) et P(Storm | pression=haute), inferees par le moteurdoubleInferStormGiven(bool pressionBasse){var p = Variable.New<bool>(); p.ObservedValue= pressionBasse;var s = Variable.New<bool>();using(Variable.If(p)){ s.SetTo(Variable.Bernoulli(0.80));}using(Variable.IfNot(p)){ s.SetTo(Variable.Bernoulli(0.10));}var e =newInferenceEngine(); e.Compiler.CompilerChoice= CompilerChoice.Roslyn;return e.Infer<Bernoulli>(s).GetProbTrue();}double pStormBasse =InferStormGiven(true);// = 0.80double pStormHaute =InferStormGiven(false);// = 0.10double backdoor = pStormBasse * pBassePression + pStormHaute *(1.0- pBassePression);Console.WriteLine($"Backdoor = P(Storm|basse)*{pBassePression} + P(Storm|haute)*{1.0-pBassePression}");Console.WriteLine($" = {pStormBasse:F2}*{pBassePression} + {pStormHaute:F2}*{1.0-pBassePression} = {backdoor:F3}");Console.WriteLine($"do(Barometre) direct = 0.310");Console.WriteLine($"=> Les deux methodes coincident ({backdoor:F3} ~ 0.310) : verification de coherence.");
Compiling model...done.
Compiling model...done.
Backdoor = P(Storm|basse)*0,3 + P(Storm|haute)*0,7
= 0,80*0,3 + 0,10*0,7 = 0,310
do(Barometre) direct = 0.310
=> Les deux methodes coincident (0,310 ~ 0.310) : verification de coherence.
6. Front-door adjustment
Quand le confondeur U est non observe mais qu’un medicateurM entre X et Y est observe et capture tout l’effet de X sur Y, Pearl (1995) montre que l’effet causal est identifiable par la formule front-door :
Exemple canonique (Pearl) : X = fumer, M = goudron dans les poumons, Y = cancer, U = genotype (non observe, cause a la fois l’envie de fumer et le risque de cancer). On n’a pas acces au genotype, mais le goudron transmet tout l’effet causal de la fumee sur le cancer.
// FRONT-DOOR : X=smoke, M=tar, Y=cancer, U=genotype (non observe)// P(Y|do(X)) = Somme_m P(M=m|X) * Somme_x' P(Y|M=m,x') P(x')// P(X=1) marginal (genotype marginalise)doublePmarginalX1(){var u = Variable.Bernoulli(0.20);var x = Variable.New<bool>();using(Variable.If(u)){ x.SetTo(Variable.Bernoulli(0.80));}using(Variable.IfNot(u)){ x.SetTo(Variable.Bernoulli(0.30));}var e =newInferenceEngine(); e.Compiler.CompilerChoice= CompilerChoice.Roslyn;return e.Infer<Bernoulli>(x).GetProbTrue();}// P(M=1 | X=1) — on INTERROGE m, on NE l'observe PASdoublePMgivenX1(){var x = Variable.New<bool>(); x.ObservedValue=true;var m = Variable.New<bool>();using(Variable.If(x)){ m.SetTo(Variable.Bernoulli(0.90));}using(Variable.IfNot(x)){ m.SetTo(Variable.Bernoulli(0.10));}var e =newInferenceEngine(); e.Compiler.CompilerChoice= CompilerChoice.Roslyn;return e.Infer<Bernoulli>(m).GetProbTrue();}// P(Y=1 | M=m, X=x') — on observe M ET X, on interroge YdoublePYgivenMX(bool smokeVal,bool mVal){var u = Variable.Bernoulli(0.20);var x = Variable.New<bool>(); x.ObservedValue= smokeVal;var m = Variable.New<bool>();using(Variable.If(x)){ m.SetTo(Variable.Bernoulli(0.90));}using(Variable.IfNot(x)){ m.SetTo(Variable.Bernoulli(0.10));} m.ObservedValue= mVal;var y = Variable.New<bool>();using(Variable.If(m)){using(Variable.If(u)){ y.SetTo(Variable.Bernoulli(0.95));}using(Variable.IfNot(u)){ y.SetTo(Variable.Bernoulli(0.70));}}using(Variable.IfNot(m)){using(Variable.If(u)){ y.SetTo(Variable.Bernoulli(0.50));}using(Variable.IfNot(u)){ y.SetTo(Variable.Bernoulli(0.05));}}var e =newInferenceEngine(); e.Compiler.CompilerChoice= CompilerChoice.Roslyn;return e.Infer<Bernoulli>(y).GetProbTrue();}// Verification : effet causal DIRECT par mutilation (coupe U -> X)doublePdoSmoke(){var u = Variable.Bernoulli(0.20);var x = Variable.Bernoulli(1.0);// do(X=1) : force, independant de uvar m = Variable.New<bool>();using(Variable.If(x)){ m.SetTo(Variable.Bernoulli(0.90));}using(Variable.IfNot(x)){ m.SetTo(Variable.Bernoulli(0.10));}var y = Variable.New<bool>();using(Variable.If(m)){using(Variable.If(u)){ y.SetTo(Variable.Bernoulli(0.95));}using(Variable.IfNot(u)){ y.SetTo(Variable.Bernoulli(0.70));}}using(Variable.IfNot(m)){using(Variable.If(u)){ y.SetTo(Variable.Bernoulli(0.50));}using(Variable.IfNot(u)){ y.SetTo(Variable.Bernoulli(0.05));}}var e =newInferenceEngine(); e.Compiler.CompilerChoice= CompilerChoice.Roslyn;return e.Infer<Bernoulli>(y).GetProbTrue();}double pX1 =PmarginalX1();// P(X=1)double pm1 =PMgivenX1();// P(M=1|X=1)double pm0 =1.0- pm1;// P(M=0|X=1)// Somme_x' P(Y|M=m,x') P(x')double innerM1 =PYgivenMX(true,true)*pX1 +PYgivenMX(false,true)*(1-pX1);double innerM0 =PYgivenMX(true,false)*pX1 +PYgivenMX(false,false)*(1-pX1);double frontDoor = pm1*innerM1 + pm0*innerM0;double doDirect =PdoSmoke();Console.WriteLine($"P(X=smoke) marginal = {pX1:F3}");Console.WriteLine($"P(M=1|X=1) = {pm1:F3} ; P(M=0|X=1) = {pm0:F3}");Console.WriteLine($"Front-door P(Cancer | do(Smoke)) = {frontDoor:F3} (ajustement sans acceder a U)");Console.WriteLine($"do(X=1) direct (mutilation) = {doDirect:F3} (verification)");Console.WriteLine($"=> Les deux coincident : la formule front-door restitue l'effet causal.");
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
P(X=smoke) marginal = 0,400
P(M=1|X=1) = 0,900 ; P(M=0|X=1) = 0,100
Front-door P(Cancer | do(Smoke)) = 0,689 (ajustement sans acceder a U)
do(X=1) direct (mutilation) = 0,689 (verification)
=> Les deux coincident : la formule front-door restitue l'effet causal.
7. Paradoxe de Simpson
Un renversement de Simpson survient quand une conclusion s’inverse entre l’analyse agregee et l’analyse conditionnelle. Cas clinique : un medicament semble nuire a la guerison dans la population totale, alors qu’il aide dans chaque sous-groupe (patients graves / patients legers). La cause : la severite est un confondeur — les patients graves, qui guerrissent moins bien, recoivent aussi plus souvent le medicament.
// SIMPSON REVERSAL : drug + severity (confondeur)doubleRecQuery(bool drugObs,bool? sevObs){var sev = Variable.Bernoulli(0.5);// severe = truevar drug = Variable.New<bool>();using(Variable.If(sev)){ drug.SetTo(Variable.Bernoulli(0.8));}// graves -> plus de drugusing(Variable.IfNot(sev)){ drug.SetTo(Variable.Bernoulli(0.2));}var rec = Variable.New<bool>();using(Variable.If(sev)){using(Variable.If(drug)){ rec.SetTo(Variable.Bernoulli(0.5));}// grave+drug -> 0.5using(Variable.IfNot(drug)){ rec.SetTo(Variable.Bernoulli(0.3));}// grave+nodrug -> 0.3}using(Variable.IfNot(sev)){using(Variable.If(drug)){ rec.SetTo(Variable.Bernoulli(0.9));}// leger+drug -> 0.9using(Variable.IfNot(drug)){ rec.SetTo(Variable.Bernoulli(0.7));}// leger+nodrug -> 0.7} drug.ObservedValue= drugObs;if(sevObs.HasValue) sev.ObservedValue= sevObs.Value;var e =newInferenceEngine(); e.Compiler.CompilerChoice= CompilerChoice.Roslyn;return e.Infer<Bernoulli>(rec).GetProbTrue();}double recDrugAgg =RecQuery(true,null);// P(Rec|Drug) agregedouble recNoDrugAgg =RecQuery(false,null);// P(Rec|NoDrug) agregedouble recDrugSev =RecQuery(true,true);// P(Rec|Drug, severe)double recNoDrugSev =RecQuery(false,true);// P(Rec|NoDrug, severe)double recDrugMild =RecQuery(true,false);// P(Rec|Drug, leger)double recNoDrugMild =RecQuery(false,false);// P(Rec|NoDrug, leger)// Backdoor do(drug) = Somme_sev P(Rec|drug,sev) P(sev)double doDrug =0.5*recDrugSev +0.5*recDrugMild;double doNoDrug =0.5*recNoDrugSev +0.5*recNoDrugMild;Console.WriteLine("=== ANALYSE AGREGEE (biaisee par la severite) ===");Console.WriteLine($"P(Rec | Drug) = {recDrugAgg:F3}");Console.WriteLine($"P(Rec | NoDrug) = {recNoDrugAgg:F3} => le medicament semble NUIRE");Console.WriteLine();Console.WriteLine("=== ANALYSE CONDITIONNELLE (verite causale) ===");Console.WriteLine($"Graves : Drug {recDrugSev:F2} > NoDrug {recNoDrugSev:F2}");Console.WriteLine($"Legers : Drug {recDrugMild:F2} > NoDrug {recNoDrugMild:F2}");Console.WriteLine("=> Le medicament AIDE dans chaque sous-groupe. REVERSAL de l'agrege.");Console.WriteLine();Console.WriteLine("=== EFFET CAUSAL (backdoor sur la severite) ===");Console.WriteLine($"P(Rec | do(Drug)) = {doDrug:F3}");Console.WriteLine($"P(Rec | do(NoDrug)) = {doNoDrug:F3} => le medicament AIDE causalement.");
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
=== ANALYSE AGREGEE (biaisee par la severite) ===
P(Rec | Drug) = 0,580
P(Rec | NoDrug) = 0,620 => le medicament semble NUIRE
=== ANALYSE CONDITIONNELLE (verite causale) ===
Graves : Drug 0,50 > NoDrug 0,30
Legers : Drug 0,90 > NoDrug 0,70
=> Le medicament AIDE dans chaque sous-groupe. REVERSAL de l'agrege.
=== EFFET CAUSAL (backdoor sur la severite) ===
P(Rec | do(Drug)) = 0,700
P(Rec | do(NoDrug)) = 0,500 => le medicament AIDE causalement.
8. Mediation – effet direct et effet indirect
La front-door (section 6) identifie l’effet total de X sur Y en passant par le mediateur M. Mais elle ne dit pas combien de cet effet transite par M (chemin indirect X -> M -> Y) versus le chemin direct (X -> Y). C’est la question de la mediation : decomposer l’effet total en une part mediee et une part directe.
On définit le Controlled Direct Effect (CDE) : l’effet de X sur Y quand on fige le mediateur a une valeur m par une seconde intervention do(M=m) :
Si l’effet total TE = P(Y|do(X=1)) - P(Y|do(X=0)) est superieur au CDE, la différence capte la part qui passe par le mediateur (effet indirect). C’est une double mutilation de graphe – le même opérateur do() qu’en section 3.2, applique deux fois.
Exemple. X = suivre un programme de formation, M = competences acquises, Y = obtenir une promotion. La formation peut mener a la promotion de deux facons : directement (effet de signal / diplome) ou indirectement (les competences declenchent la promotion).
// MEDIATION : X (formation) -> M (competences) -> Y (promotion), plus chemin direct X -> Y.// CPTs connus. Effet total (TE) + Controlled Direct Effect (CDE) en figeant M par do(M).// P(Y=1 | do(X=x)) -- effet TOTAL (M libre : capte chemin direct + indirect via M)doublePY_doX(bool xVal){var x = Variable.New<bool>().Named("x"); x.ObservedValue= xVal;var m = Variable.New<bool>().Named("m");using(Variable.If(x)){ m.SetTo(Variable.Bernoulli(0.80));}// formation -> competencesusing(Variable.IfNot(x)){ m.SetTo(Variable.Bernoulli(0.20));}var y = Variable.New<bool>().Named("y");using(Variable.If(x)){using(Variable.If(m)){ y.SetTo(Variable.Bernoulli(0.90));}// formation + competencesusing(Variable.IfNot(m)){ y.SetTo(Variable.Bernoulli(0.60));}// formation seule (signal)}using(Variable.IfNot(x)){using(Variable.If(m)){ y.SetTo(Variable.Bernoulli(0.70));}// competences sans formationusing(Variable.IfNot(m)){ y.SetTo(Variable.Bernoulli(0.30));}// baseline}var e =newInferenceEngine(); e.Compiler.CompilerChoice= CompilerChoice.Roslyn;return e.Infer<Bernoulli>(y).GetProbTrue();}// P(Y=1 | do(X=x), do(M=m)) -- CDE : on FIGE le mediateur (double mutilation)doublePY_doX_doM(bool xVal,bool mVal){var x = Variable.New<bool>().Named("x"); x.ObservedValue= xVal;var m = Variable.New<bool>().Named("m"); m.ObservedValue= mVal;// do(M) fige le mediateurvar y = Variable.New<bool>().Named("y");using(Variable.If(x)){using(Variable.If(m)){ y.SetTo(Variable.Bernoulli(0.90));}using(Variable.IfNot(m)){ y.SetTo(Variable.Bernoulli(0.60));}}using(Variable.IfNot(x)){using(Variable.If(m)){ y.SetTo(Variable.Bernoulli(0.70));}using(Variable.IfNot(m)){ y.SetTo(Variable.Bernoulli(0.30));}}var e =newInferenceEngine(); e.Compiler.CompilerChoice= CompilerChoice.Roslyn;return e.Infer<Bernoulli>(y).GetProbTrue();}double TE =PY_doX(true)-PY_doX(false);double CDE0 =PY_doX_doM(true,false)-PY_doX_doM(false,false);double CDE1 =PY_doX_doM(true,true)-PY_doX_doM(false,true);Console.WriteLine("Mediation : X=formation, M=competences, Y=promotion");Console.WriteLine($" Effet total TE = P(Y|do(X=1)) - P(Y|do(X=0)) = {TE:F3}");Console.WriteLine($" Effet direct CDE a M=0 = P(Y|do(X=1),do(M=0)) - P(Y|do(X=0),do(M=0)) = {CDE0:F3}");Console.WriteLine($" Effet direct CDE a M=1 = P(Y|do(X=1),do(M=1)) - P(Y|do(X=0),do(M=1)) = {CDE1:F3}");Console.WriteLine($" -> part mediee (indirect) ~= TE - CDE(M=0) = {TE - CDE0:F3} (chemin via les competences)");
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Mediation : X=formation, M=competences, Y=promotion
Effet total TE = P(Y|do(X=1)) - P(Y|do(X=0)) = 0,460
Effet direct CDE a M=0 = P(Y|do(X=1),do(M=0)) - P(Y|do(X=0),do(M=0)) = 0,300
Effet direct CDE a M=1 = P(Y|do(X=1),do(M=1)) - P(Y|do(X=0),do(M=1)) = 0,200
-> part mediee (indirect) ~= TE - CDE(M=0) = 0,160 (chemin via les competences)
// EFFETS NATURELS (Pearl 2001) : NDE et NIE calcules sur le SCM de mediation ci-dessus.// Pas de confondeur M <- U -> Y : la sequential ignorability tient, les quantites// "cross-world" se calculent par enumeration sur le meme SCM (monde imbrique).// P(M=m | do(X=x)) : le mediateur sous intervention (CPT direct du SCM)doublePM_doX(bool xVal,bool mVal){double pM1 = xVal ?0.80:0.20;return mVal ? pM1 :1.0- pM1;}// NDE_{0,1} = Somme_m P(m|do(0)) * [P(Y|do(1),do(m)) - P(Y|do(0),do(m))]// (mediateur a sa valeur NATURELLE sous X=0, pas figee -- c'est le contrefactuel imbrique)double NDE =PM_doX(false,true)*(PY_doX_doM(true,true)-PY_doX_doM(false,true))+PM_doX(false,false)*(PY_doX_doM(true,false)-PY_doX_doM(false,false));// NIE_{1,0} = Somme_m [P(m|do(1)) - P(m|do(0))] * P(Y=1|do(1),do(m))// (Y sous X=1, seul le CHANGEMENT du mediateur porte l'effet)double NIE =(PM_doX(true,true)-PM_doX(false,true))*PY_doX_doM(true,true)+(PM_doX(true,false)-PM_doX(false,false))*PY_doX_doM(true,false);Console.WriteLine("Effets naturels (Pearl 2001) sur le SCM formation -> competences -> promotion :");Console.WriteLine($" NDE = {NDE:F3} (effet direct, mediateur naturel sous X=0)");Console.WriteLine($" NIE = {NIE:F3} (effet indirect, Y evalue sous X=1)");Console.WriteLine($" NDE + NIE = {NDE + NIE:F3} vs TE = {TE:F3} -> ecart |TE - NDE - NIE| = {Math.Abs(TE - NDE - NIE):F3}");Console.WriteLine($" Raccourci CDE : NDE =? CDE(0) -> {NDE:F3} vs {CDE0:F3} ; part mediee =? TE-CDE(0) -> {NIE:F3} vs {TE - CDE0:F3}");Console.WriteLine(" (NDE + NIE = TE : identite algebrique ; ce qui derive, ce sont les raccourcis via CDE)");
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Compiling model...done.
Effets naturels (Pearl 2001) sur le SCM formation -> competences -> promotion :
NDE = 0,280 (effet direct, mediateur naturel sous X=0)
NIE = 0,180 (effet indirect, Y evalue sous X=1)
NDE + NIE = 0,460 vs TE = 0,460 -> ecart |TE - NDE - NIE| = 0,000
Raccourci CDE : NDE =? CDE(0) -> 0,280 vs 0,300 ; part mediee =? TE-CDE(0) -> 0,180 vs 0,160
(NDE + NIE = TE : identite algebrique ; ce qui derive, ce sont les raccourcis via CDE)
Lecture. L’effet total de la formation sur la promotion vaut 0,460 (46 points de probabilite). Mais quand on fige les competences acquises (do(M=0), “formation sans competences”), l’effet direct retombe a 0,300. La différence 0,160 est la part mediee : ce que la formation apporte a la promotion via les competences. Le CDE a M=1 (0,200) est encore plus bas, car do(M=1) place tout le monde en position “competent”, ou la formation ajoute moins.
Ce que montre la cellule. La front-door (section 6) donnait l’effet total via le mediateur ; la mediation decompose cet effet. C’est le même opérateur do(), applique deux fois (double mutilation) – l’inference par message passing Infer.NET (exacte sur ce SCM discret) rend TE et CDE calculables separement et comparables. Non-trivialite : l’effet direct (0,300) differe de l’effet total (0,460) de facon visible, preuve que le mediateur porte une part reelle de l’effet – dans un SCM degenere sans chemin indirect, TE egalerais exactement CDE.
Au-dela du CDE : effets naturels (NDE, NIE) et caveats
Le Controlled Direct Effect (CDE) qu’on vient de calculer suppose une seconde intervention do(M=m) qui fige le mediateur a une valeur arbitraire. Pearl (2001, Direct and indirect effects in mediation analysis) distingue cela des effets naturels, qui demandent ce que seraitY si on observait la valeur “naturelle” de M sous le contrefactuel do(X=x') au lieu de la fixer. Formellement :
NDE (Natural Direct Effect) = Y_{1,M_{X=0}} - Y_{0,M_{X=0}} : effet direct avec le mediateur a sa valeur naturelle sous X=0.
NIE (Natural Indirect Effect) = Y_{1,M_{X=1}} - Y_{1,M_{X=0}} : effet indirect transmis par le changement “naturel” de M quand X bascule de 0 a 1.
TE = NDE + NIE (decomposition additive, Pearl 2001, Robins & Greenland 1992).
Dans Infer.NET, le NDE/NIE necessite une hypothese supplemental : la sequential ignorability (ou cross-world independence) sur les contrefactuels M_{X=x'} – c’est-a-dire que la distribution de M sous do(X=x') reste compatible avec le meme SCM. Sur notre SCM de mediation (X -> M -> Y, pas de confondeur M <- U -> Y), cette hypothese tient : les effets naturels sont calculables par enumeration sur le meme SCM (cellule suivante). Les valeurs mesurees : NDE = 0,280, NIE = 0,180, et NDE + NIE = 0,460 = TE – une identite algebrique (le telescopage des deux sommes rend la decomposition exacte pour tout SCM discret), pas une coincidence.
Ce que l’interaction X*M casse en revanche, ce sont les raccourcis via le CDE : notre monde a CDE(1) = 0,200 != CDE(0) = 0,300, donc NDE != CDE(0) (0,280 vs 0,300) et la “part mediee” TE - CDE(M=0) = 0,160 sous-estime le vrai NIE = 0,180 :
NDE + NIE = TE (exacte, toujours -- identite de telescopage)
NDE = CDE(m) (faux des que CDE varie en m : interaction)
part mediee = TE - CDE(M=0) (approximation : 0,160 au lieu de 0,180 ici)
Quand CDE != NDE : si un confondeur U influence a la foisM et Y (par exemple, le “motivation” qui determine a la fois les competences acquises ET la reconnaissance patronale), CDE a do(M=0) ne capture pas l’effet direct reel – il faut le NDE, qui requiert des methodes d’inference contrefactuelle specifiques (voir section 10 et la discussion dans Pearl 2001, sec. 4). Infer.NET, comme moteur de message passing determinant sur graphes discrets, rend ce cas plus difficile (les contrefactuels “cross-world” sortent du formalisme d’un seul DAG).
Pourquoi cette clarif importe pedagogiquement. Un·e etudiant·e qui voit “TE = 0,460, CDE = 0,300, donc part mediee = 0,160” pourrait conclure hativement que la decomposition est toujours TE - CDE. Ce n’est vrai qu’en l’absence de confondeur de mediation. Le scaffold NDE/NIE prepare la transition vers les methodes contrefactuelles (section 10) et les gemives du monde reel ou le confondeur U est la regle, pas l’exception (Pearl 2014, Interpretation and Identification of Causal Mediation).
9. Le do-calculus (règles de Pearl)
Pearl (1995) a demontre que toute quantite causale identifiable peut être calculee a partir de trois règles de reecriture, qui transforment des expressions avec do() en expressions observationnelles (sans do()). Soit G_X le graphe mutile (arcs entrants de X supprimes) et G_{underline{X}} le graphe ou les arcs sortants de X sont supprimes :
Règle
Enonce
Effet
Règle 1 (insertion/deletion)
P(y \| do(x), z, w) = P(y \| do(x), w) si Y _\|\|_ Z \| X, W dans G_X
Observations irrelevantes peuvent être retirees
Règle 2 (action/observation)
P(y \| do(x), do(z), w) = P(y \| do(x), z, w) si Y _\|\|_ Z \| X, W dans G_{underline{X}}
Une action peut être remplacée par une observation
Règle 3 (insertion/deletion d’actions)
P(y \| do(x), do(z), w) = P(y \| do(x), w) si Y _\|\|_ Z \| X, W dans G_{X, tilde{Z(W)}}
Une action irrelevant peut être retiree
Critere backdoor (Shpitser, Pearl & Verma) : un ensemble Z satisfait le critere backdoor relatif a (X,Y) si aucun noeud de Z n’est un descendant de X, et Z bloque tous les chemins backdoor (arcs entrants de X). Alors P(Y|do(X)) = Somme_z P(Y|X,Z)P(Z) — c’est exactement la formule appliquee en section 5.
Théorème de completude (Shpitser & Pearl, 2006) : les trois règles sont complètes — si un effet causal n’est pas calculable par ces règles, il n’est pas identifiable a partir du graphe seul. C’est un plafond fondamental, pas une limite d’implementation.
10. Niveau 3 — Contrefactuel (capstone)
Le niveau 3 de l’echelle de Pearl repond aux questions contrefactuelles : “Sachant qu’un patient a pris le medicament ET a gueri, aurait-il gueri SANS medicament ?”
On resout cela en deux étapes (twin-network / abduction-prediction) :
Abduction : inferer le posterieur sur les variables latentes (ici la severite) sachant ce qu’on observe (pris le medicament, a gueri).
Prediction/action : reutiliser ce posterieur comme prior dans le graphe mutile (do(NoDrug)) pour predire le résultat contrefactuel.
Cette étape est plus couteuse qu’une simple intervention (elle requiert une inference posterieure puis une seconde inference). C’est honnetement le plafond pratique de l’inference causale déterministe (message passing) sur ce type de SCM.
// CONTREFACTUEL (Niveau 3) : abduction + prediction// "Sachant drug=true ET rec=true, aurait-il gueri si do(drug=false) ?"// ETAPE 1 — Abduction : posterieur sur 'severe' sachant (drug=true, rec=true)var sevAbd = Variable.Bernoulli(0.5).Named("sevAbd");var drugAbd = Variable.New<bool>().Named("drugAbd");using(Variable.If(sevAbd)){ drugAbd.SetTo(Variable.Bernoulli(0.8));}using(Variable.IfNot(sevAbd)){ drugAbd.SetTo(Variable.Bernoulli(0.2));}var recAbd = Variable.New<bool>().Named("recAbd");using(Variable.If(sevAbd)){using(Variable.If(drugAbd)){ recAbd.SetTo(Variable.Bernoulli(0.5));}using(Variable.IfNot(drugAbd)){ recAbd.SetTo(Variable.Bernoulli(0.3));}}using(Variable.IfNot(sevAbd)){using(Variable.If(drugAbd)){ recAbd.SetTo(Variable.Bernoulli(0.9));}using(Variable.IfNot(drugAbd)){ recAbd.SetTo(Variable.Bernoulli(0.7));}}drugAbd.ObservedValue=true;// il a pris le medicamentrecAbd.ObservedValue=true;// il a guerivar eAbd =newInferenceEngine(); eAbd.Compiler.CompilerChoice= CompilerChoice.Roslyn;double pSevereGiven = eAbd.Infer<Bernoulli>(sevAbd).GetProbTrue();// P(severe | drug, rec)Console.WriteLine($"Abduction : P(severe | drug=true, rec=true) = {pSevereGiven:F3}");// ETAPE 2 — Prediction : do(drug=false) avec severe tire du posterieurvar sevPred = Variable.Bernoulli(pSevereGiven).Named("sevPred");var drugPred = Variable.Bernoulli(0.0).Named("drugPred");// do(NoDrug) : force a falsevar recPred = Variable.New<bool>().Named("recPred");using(Variable.If(sevPred)){using(Variable.If(drugPred)){ recPred.SetTo(Variable.Bernoulli(0.5));}using(Variable.IfNot(drugPred)){ recPred.SetTo(Variable.Bernoulli(0.3));}}using(Variable.IfNot(sevPred)){using(Variable.If(drugPred)){ recPred.SetTo(Variable.Bernoulli(0.9));}using(Variable.IfNot(drugPred)){ recPred.SetTo(Variable.Bernoulli(0.7));}}var ePred =newInferenceEngine(); ePred.Compiler.CompilerChoice= CompilerChoice.Roslyn;double pRecCounterfactual = ePred.Infer<Bernoulli>(recPred).GetProbTrue();Console.WriteLine($"Contrefactuel : P(rec | do(NoDrug), 'pris drug & gueri') = {pRecCounterfactual:F3}");Console.WriteLine();Console.WriteLine($"=> Meme si le patient a gueri AVEC le medicament, sans medicament il n'aurait eu");Console.WriteLine($" qu'environ {pRecCounterfactual*100:F0}% de chances de guerison (contrefactuel).");
Compiling model...done.
Abduction : P(severe | drug=true, rec=true) = 0,690
Compiling model...done.
Contrefactuel : P(rec | do(NoDrug), 'pris drug & gueri') = 0,424
=> Meme si le patient a gueri AVEC le medicament, sans medicament il n'aurait eu
qu'environ 42% de chances de guerison (contrefactuel).
11. Exercices
Quatre exercices pour ancrer le do-calculus. Chaque stub s’execute sans erreur (Console.WriteLine) ; a vous de compléter le code entre les // TODO.
#
Exercice
Concept
1
Construire un SCM confondu (education -> revenu)
Niveau 1 observation vs margeur
2
Backdoor adjustment sur un reseau personnalise
Identification de l’ensemble Z
3
Front-door : smoke -> tar -> cancer (Pearl)
Adjustement par medicateur
4
Paradoxe de Simpson personnalise
Renversement agrege vs conditionnel
Exercice 1 - SCM confondu : education -> revenu
Concept (cf. section 3, barometre) : un même parent latent U (niveau d’études des parents) cause a la fois X (education de l’enfant) et Y (revenu). L’observationP(Revenu | Education=haute) et l’interventionP(Revenu | do(Education=haute)) différent : seule la seconde isole l’effet causal de l’education, en coupant l’influence de U sur X.
Objectif : coder le SCM (CPT via Variable.If comme en section 3), calculer les deux quantites, puis mutiler l’arc U -> X (Bernoulli(1.0) force) pour le do().
Les étapes (// Étape N) et l’indice sont dans le code ci-dessous.
// Exercice 1 : Construire un SCM confondu "education -> revenu"// Modele suggere : U=niveau_etudes_parents cause a la fois Education (X) et Revenu (Y),// X -> Y (l'education augmente le revenu). Calculez P(Revenu|Education) vs P(Revenu|do(Education)).// Indice : codez les CPT avec Variable.If comme dans la section 3, puis mutilez l'arc U->X pour le do().// Etape 1 : definir les variables et CPT// Etape 2 : P(Revenu | Education=haute) observationnel// Etape 3 : P(Revenu | do(Education=haute)) interventionnelConsole.WriteLine("Exercice 1 a completer : SCM education -> revenu (observation vs intervention).");
Exercice 1 a completer : SCM education -> revenu (observation vs intervention).
Exercice 2 - Backdoor adjustment : identifier l’ensemble Z
Concept (cf. section 5) : quand un confondeur Z est observe et bloque tous les chemins backdoor vers X, l’effet causal s’obtient sans mutiler le graphe, par la formule d’ajustement backdoor (Pearl, 1995) :
Objectif : choisir un reseau a 3-4 noeuds avec un confondeur observe, identifier l’ensemble d’ajustement Z, appliquer la formule, et comparer au do() direct (les deux doivent coïncider si Z est valide).
// Exercice 2 : Backdoor adjustment — identifier l'ensemble Z et calculer P(Y|do(X))// Reprenez un reseau a 3-4 noeuds avec un confondeur observe Z.// Indice : verifiez que Z bloque tous les chemins backdoor vers X, puis appliquez Somme_z P(Y|X,z)P(z).// Etape 1 : choisir Z (ensemble d'ajustement)// Etape 2 : calculer chaque P(Y|X,z) via le moteur// Etape 3 : sommer poids par P(z) et comparer au do() directConsole.WriteLine("Exercice 2 a completer : backdoor adjustment, identifier Z et calculer P(Y|do(X)).");
Exercice 2 a completer : backdoor adjustment, identifier Z et calculer P(Y|do(X)).
Exercice 3 - Front-door : smoke -> tar -> cancer (Pearl)
Concept (cf. section 6) : quand le confondeur U (predisposition génétique) est non observe mais qu’un mediateurM (goudron dans les poumons) est observe et capture tout l’effet de X (fumer) sur Y (cancer), Pearl (1995) montre l’identifiabilite par la formule front-door :
Objectif : adapter la section 6 avec de nouvelles CPT (fumer passif, exposition professionnelle) en preservant la structureX -> M -> Y + U -> X, U -> Y (U reste non observe).
// Exercice 3 : Front-door sur smoke -> tar -> cancer (classique Pearl)// Adaptez la section 6 en changeant les CPT (par ex. fumer passif, exposition professionnelle).// Indice : la structure X -> M -> Y + U -> X, U -> Y doit etre preservee (U non observe).// Etape 1 : fixer de nouvelles CPT coherentes// Etape 2 : recalculer P(M|X), P(x'), P(Y|M,x')// Etape 3 : appliquer la formule front-door et interpreterConsole.WriteLine("Exercice 3 a completer : front-door smoke -> tar -> cancer (Pearl).");
Exercice 3 a completer : front-door smoke -> tar -> cancer (Pearl).
Exercice 4 - Paradoxe de Simpson personnalise
Concept (cf. section 7) : un renversement de Simpson survient quand une conclusion s’inverse entre l’analyse agregee et l’analyse conditionnelle - typiquement parce qu’un confondeur Z favorise X tout en defavorisant Y (les patients graves, moins soignes, faussent la moyenne).
Objectif : concevoir des CPT produisant un renversement (ex. traitement + age, ou genre + admission Berkeley 1973), puis montrer que P(Y|X) agrege differere de P(Y|do(X)) et expliquer le mécanisme. C’est le cas ou la conclusion naive (agregee) est causalement fausse - le coeur de pourquoi l’ajustement causal importe.
// Exercice 4 : Paradoxe de Simpson personnalise// Concevez des CPT ou la conclusion s'INVERSE entre l'agrege et le conditionnel.// Indice : il faut un confondeur Z tel que Z favorise a la fois X et defavorise Y (ou inverse).// Etape 1 : choisir un scenario (ex. traitement + age, ou genre + admission Berkeley 1973)// Etape 2 : coder des CPT produisant un renversement// Etape 3 : montrer P(Y|X) agrege != P(Y|do(X)) et expliquer le mecanismeConsole.WriteLine("Exercice 4 a completer : concevoir un paradoxe de Simpson (renversement agrege vs conditionnel).");
Exercice 4 a completer : concevoir un paradoxe de Simpson (renversement agrege vs conditionnel).
Constellation causale – le do-calculus en quatre paradigmes
Le do-calculus de Pearl (1995, 2000) est un calcul formel indépendant du langage : les trois règles d’identification d’un effet causal ne dependent pas de l’implementation. Ce depot le met en oeuvre dans quatre paradigmes distincts, qui choisissent chacun ce qu’ils calculent versus ce sur quoi ils raisonnent.
Paradigme
Moteur / langage
Idiome du do(X)
Notebook
Ce qu’il apporte
Probabiliste distributionnel
Infer.NET (C#)
Mutilation de graphe : Variable.Bernoulli(1.0) force la valeur et coupe l’arc parent
ce notebook
Effet causal calcule par message passing déterministe (EP/VMP) sur le graphe mutile
Probabiliste (transformation)
PyMC (Python)
Opérateur natif pm.do : l’intervention est une transformation de modèle de première classe
Le do s’applique a l’echelle : quelle description (micro vs macro) est causalement la plus forte (Hoel 2013, Jansma & Hoel 2025)
Valeurs croisees (verification inter-paradigme). Sur le même exemple du barometre (confondeur BassePression -> {Barometre, Storm}), les deux versants probabilistes recoupent exactement :
Quantite
Infer.NET (ce notebook, sec 3)
PyMC-5
P(Storm \| Barometre baisse) (observation)
0,656
0,656
P(Storm \| do(Barometre baisse)) (intervention)
0,310
0,310
L’effondrement de la correlation (0,656 -> 0,310) lorsqu’on passe de l’observation a l’intervention est le signal que le moteur fait un vrai travail causal (mutilation, pas une simple lecture de CPT) – et les deux moteurs, malgre leurs idiomes différents (mutilation Infer.NET vs pm.do PyMC), convergent vers les mêmes valeurs. Sur le reseau Sprinkler, P(Cloudy|Rain)=0,800 vs P(Cloudy|do(Rain))=0,500 (sec 4 ici, et PyMC-4 sec 8).
L’angle ICT (emergence causale). Le quatrieme paradigme, ICT-05-CausalEmergence-Python, deplace la question : au lieu d’intervenir sur une variable (do(X=x)), il intervient sur l’echelle de description (do_coarse_grain) et mesure quelle partition (micro vs macro) maximise l’information effective. C’est le même opérateur d’intervention de Pearl, applique a la granularite du modèle plutot qu’a la valeur d’un noeud – d’ou l’expression emergence causale (la macro peut être causalement plus forte que la micro).
Ce qu’il faut retenir. Quatre implementations, un seul do-calculus. Le paradigme Infer.NET met l’accent sur la structure (mutiler le graphe = une opération que le moteur d’inference execute) ; PyMC met l’accent sur la composabilite (do comme transformation de modèle) ; Tweety met l’accent sur le raisonnement (causalite sans nombres) ; ICT (PyPhi) met l’accent sur l’echelle (intervenir sur la partition, pas sur la variable). Choisir l’un ou l’autre, c’est choisir ce que l’on veut : des probabilites déterministes (message passing), du sampling flexible, des preuves qualitatives, ou identifier la bonne echelle de description.
12. Synthese
Niveau
Question
Opérateur
Méthode Infer.NET
1 Association
Que voit-on ?
P(Y\|X)
ObservedValue
2 Intervention
Que se passe-t-il si on agit ?
P(Y\|do(X))
Mutilation : Bernoulli(1.0) force, coupe l’arc parent
3 Contrefactuel
Que se serait-il passe ?
P(Y_x\|X',Y')
Abduction (posterieur) + prediction dans graphe mutile
P(Y|X) != P(Y|do(X)) : l’observation est biaisee par les confondeurs. Un reseau bayesien observationnel ne sait pas calculer do(X) seul — il faut mutiler le graphe.
La mutilation n’est pas une lecture de CPT : couper les arcs entrants de X est une opération structurelle que le moteur d’inference Infer.NET execute après qu’on a redefini X sans parent. La différence visible entre observation et intervention prouve que le do() fait un travail reel (Prong-B).
Backdoor et front-door rendent l’effet causal identifiable a partir de données observationnelles, sous conditions graphiques — formalisees par le do-calculus (Pearl 1995), complet (Shpitser & Pearl 2006).
Pour aller plus loin
Notebook-pont — du graphe causal au do-calculus : l’armature formelle unifiée de Pearl (échelle + 3 règles) exécutée sur l’outil de référence dowhy (identification backdoor/front-door + estimation + réfutation), qui relie cette série à Tweety-11, PyMC-5 et ICT-5.
Pont symbolique : Tweety-11-Causal traite le do() et les contrefactuels en Java/Tweety (raisonnement propositionnel) — même echelle de Pearl, paradigme différent.
Pont Python : PyMC-04-Bayesian-Networks demontre la même distinction P(Cloudy|Rain) vs P(Cloudy|do(Rain)) en MCMC.
Références : Pearl (2000) Causality ; Pearl, Glymour & Jewell (2016) Causal Inference in Statistics : A Primer ; Shpitser & Pearl (2006) Identification of Conditional Interventional Effects.