Esercizio - Aggiustamento backdoor con un confondente non osservato e con un collider (slide del corso)
Testo (esempi 1 e 2 delle slide Causal Inference parte 2, slide 13 e 14; le slide danno i grafi e le formule, i numeri sono scelti qui). Variabili booleane . Si vuole l'effetto causale di su .
- Esempio 1. Grafo , , , , con non osservato. Probabilità: ; , ; , ; per , per , per , per .
- a. Trovare i cammini backdoor e gli insiemi validi.
- b. Calcolare e con il solo osservato (la formula ) usando solo le probabilità osservabili di .
- c. Confrontare con e con il valore vero ottenuto dal modello strutturale (chirurgia), e controllare con una simulazione.
- d. Cosa succede se si aggiusta invece su o su (verifica del modello)?
- Esempio 2. Grafo , , , (collider ). Probabilità: ; , ; , ; , , , . Quali insiemi si possono usare? Cosa succede aggiustando su ?
Soluzione
Esempio 1
Grafico interattivo: Esempio 1: Z → X, Z → W, W → Y, X → Y; Z non osservato (frecce tratteggiate); cammino backdoor X ← Z → W → Y
a. Un cammino backdoor: (fork in , poi catena). Per il criterio sono validi , e (nessuno è discendente di ). Con non osservato resta .
b. Si usano solo i dati osservabili di . La distribuzione si ricava marginalizzando : per esempio :
Da cui , e . Poiché dipende da e soltanto, nei dati coincide con la tabella: . Dunque
c. Il valore osservazionale è diverso: e , quindi con un «effetto» osservato di contro quello causale : chi ha ha più spesso (effetto di ) e aiuta , un'influenza spuria. Il valore vero si ottiene dalla chirurgia (si toglie la freccia e si fissa ): e , uguali all'aggiustamento.
d. Anche con e con la formula dà e : modello coerente (più insiemi validi, stesso risultato). Se con dati reali i risultati fossero diversi, il modello causale sarebbe sbagliato.
Esempio 2
Grafico interattivo: Esempio 2: Z → X, Z → W, Y → W, X → Y; W è un collider sul cammino backdoor X ← Z → W ← Y
Cammino backdoor: , con collider . Insiemi validi: (il collider blocca) e (blocca il fork ). Non sono validi e : è un discendente di (), quindi viola la seconda condizione del criterio, e in più condizionare sul collider apre il cammino. Con : togliere la freccia non influenza , quindi : e (si legge dalle tabelle: dipende solo da ).
Se invece si aggiusta su , errore: dà per e per (invece di e ): condizionando su si apre il cammino e si introduce una dipendenza spuria tra e . Aggiustando su si ottengono e .
Codice
import itertools
import numpy as np
# ---------- Esempio 1: Z -> X, Z -> W, W -> Y, X -> Y (Z non osservato)
pz = {1: 0.4, 0: 0.6} # P(Z = z)
pw = {0: 0.3, 1: 0.8} # P(W = 1 | z)
px = {0: 0.2, 1: 0.8} # P(X = 1 | z)
py = {(0, 0): 0.1, (0, 1): 0.5, (1, 0): 0.3, (1, 1): 0.7} # P(Y = 1 | x, w)
b = lambda v, p: p if v else 1 - p # P(V = v) quando P(V = 1) = p
# congiunta completa P(z, w, x, y)
J = {(z, w, x, y): pz[z] * b(w, pw[z]) * b(x, px[z]) * b(y, py[x, w])
for z, w, x, y in itertools.product((0, 1), repeat=4)}
P = lambda **k: sum(p for (z, w, x, y), p in J.items() if all(dict(z=z, w=w, x=x, y=y)[n] == v for n, v in k.items()))
# dati osservabili: solo (X, W, Y); Z non si usa nelle formule che seguono
print("P(x,w):", {(x, w): round(P(x=x, w=w), 3) for x in (0, 1) for w in (0, 1)})
oss = {x: P(x=x, y=1) / P(x=x) for x in (0, 1)}
adj_w = {x: sum(P(x=x, w=w, y=1) / P(x=x, w=w) * P(w=w) for w in (0, 1)) for x in (0, 1)}
adj_z = {x: sum(P(x=x, z=z, y=1) / P(x=x, z=z) * P(z=z) for z in (0, 1)) for x in (0, 1)}
vero = {x: sum(pz[z] * b(w, pw[z]) * py[x, w] for z in (0, 1) for w in (0, 1)) for x in (0, 1)} # chirurgia: manca P(x | z)
for nome, d in [("osservazionale P(y|x)", oss), ("aggiustamento su W", adj_w), ("aggiustamento su Z", adj_z), ("verita (chirurgia)", vero)]:
print(f"{nome:24s} x=1: {d[1]:.4f} x=0: {d[0]:.4f} ACE = {d[1] - d[0]:.4f}")
# simulazione dell'SCM e stima dell'aggiustamento su W da un campione (n = 2 milioni)
rng = np.random.default_rng(5); N = 2_000_000
Z = rng.random(N) < 0.4
W = rng.random(N) < np.where(Z, 0.8, 0.3)
Ux, Uy = rng.random(N), rng.random(N)
def simula(x=None):
X = (Ux < np.where(Z, 0.8, 0.2)) if x is None else np.full(N, bool(x))
return X, Uy < np.select([~X & ~W, ~X & W, X & ~W, X & W], [0.1, 0.5, 0.3, 0.7])
print("simulazione do(x=1), do(x=0):", simula(1)[1].mean().round(4), simula(0)[1].mean().round(4))
X, Y = simula()
stima = lambda x: sum(Y[(X == x) & (W == w)].mean() * (W == w).mean() for w in (0, 1))
print("stima da campione (solo X, W, Y):", stima(True).round(4), stima(False).round(4))
# ---------- Esempio 2: Z -> X, Z -> W, Y -> W, X -> Y (W e' un collider)
pz2 = {0: 0.5, 1: 0.5}; px2 = {0: 0.2, 1: 0.8} # P(X = 1 | z)
py2 = {0: 0.7, 1: 0.4} # P(Y = 1 | x)
pw2 = {(0, 0): 0.1, (0, 1): 0.5, (1, 0): 0.4, (1, 1): 0.9} # P(W = 1 | z, y)
J2 = {(z, x, y, w): pz2[z] * b(x, px2[z]) * b(y, py2[x]) * b(w, pw2[z, y])
for z, x, y, w in itertools.product((0, 1), repeat=4)}
P2 = lambda **k: sum(p for (z, x, y, w), p in J2.items() if all(dict(z=z, x=x, y=y, w=w)[n] == v for n, v in k.items()))
print("--- esempio 2")
print("P(y|x) con insieme vuoto:", [round(P2(x=x, y=1) / P2(x=x), 4) for x in (0, 1)], " verita: ", [py2[0], py2[1]])
print("aggiustando su W (errato):", [round(sum(P2(x=x, w=w, y=1) / P2(x=x, w=w) * P2(w=w) for w in (0, 1)), 4) for x in (0, 1)])
print("aggiustando su Z: ", [round(sum(P2(x=x, z=z, y=1) / P2(x=x, z=z) * P2(z=z) for z in (0, 1)), 4) for x in (0, 1)])Output:
P(x,w): {(0, 0): 0.352, (0, 1): 0.208, (1, 0): 0.148, (1, 1): 0.292}
osservazionale P(y|x) x=1: 0.5655 x=0: 0.2486 ACE = 0.3169
aggiustamento su W x=1: 0.5000 x=0: 0.3000 ACE = 0.2000
aggiustamento su Z x=1: 0.5000 x=0: 0.3000 ACE = 0.2000
verita (chirurgia) x=1: 0.5000 x=0: 0.3000 ACE = 0.2000
simulazione do(x=1), do(x=0): 0.5001 0.3
stima da campione (solo X, W, Y): 0.5005 0.3001
--- esempio 2
P(y|x) con insieme vuoto: [0.7, 0.4] verita: [0.7, 0.4]
aggiustando su W (errato): [0.7139, 0.382]
aggiustando su Z: [0.7, 0.4]L'ultima riga di ciascun gruppo mostra che la stima da un campione osservazionale, che usa soltanto senza mai guardare , ritrova l'effetto causale: e contro e . Nell'esempio 2 le liste sono nell'ordine .
Versione ripasso
- Esempio 1 (, , ; non osservato). Backdoor ; validi , con non osservato . Tabelle: ; , ; , ; per (Interventi e operatore do - criterio backdoor e aggiustamentoOsservare $X=x$ e forzare $X=x$ sono cose diverse: $P(y\mid x)\ne P(y\mid do(x))$ quando c'è un confondente. L'intervento $do(X=x)$ si calcola con la «chirurgia» del grafo (si tolgono le frecce entranti in X) e si traduce in probabilità osservazionali con la formula di aggiustamento $P(y\mid do(x))=\sum_z P(y\mid x,z)P(z)$, dove $Z$ sono i genitori di X, o più in generale un insieme S che soddisfa il criterio backdoor: blocca ogni cammino backdoor (cammino da X a Y che inizia con una freccia entrante in X) e non contiene discendenti di X. Effetto causale medio $ACE=P(y\mid do(x))-P(y\mid do(\neg x))$. Se il confondente non è osservabile ma c'è un mediatore Z con X→Z→Y, vale il front-door $P(y\mid do(x))=\sum_z P(z\mid x)\sum_{x'}P(y\mid x',z)P(x')$. Mediazione: effetto diretto controllato CDE con $do(x),do(z)$. Se due insiemi backdoor danno risultati diversi sui dati, il modello è sbagliato. Argomento del corso da 9 CFU.Interventi e operatore do - criterio backdoor e aggiustamento →).
- Dati osservabili: : ; ; ; ; ; uguale alla tabella.
- Aggiustamento su : ; ; . Osservazionale: , , , (differenza ).
- Verifica del modello: aggiustando su o su si ottiene lo stesso e ; la simulazione dà e ; la stima da campione con soli dà e .
- Esempio 2 (, , ; collider ). Validi: , . Con : per e per . Aggiustando su si apre il collider: e , errati.
- Codice: congiunta come dizionario
J[(z, w, x, y)], funzioneP(**k)che somma le righe compatibili; aggiustamento = ; chirurgia = congiunta senza il fattore . - Errori tipici: pesare con al posto di ; aggiustare sul collider; considerare valido nel secondo grafo; fidarsi di nel primo.