Salta al contenuto
Note per Studenti Esercizio - Aggiustamento front-door ed effetto del fumo sul cancro (laboratorio 8)

Esercizio - Aggiustamento front-door ed effetto del fumo sul cancro (laboratorio 8)

Testo (laboratorio 8, Causal inference - Part B, esercizi 3, 4 e 5). Il confondente tra XX e YY non è osservato (nella libreria è un arco tratteggiato «latente» tra XX e YY), ma esistono variabili intermedie osservate.

  1. Creare la rete con nodi X,A,B,YX,A,B,Y, archi X→AX\to A, A→BA\to B, B→YB\to Y e un arco latente tra XX e YY; trovare tutti gli insiemi validi per il criterio front-door.
  2. Creare la rete con nodi X,Y,A,BX,Y,A,B, archi X→AX\to A, A→YA\to Y, X→BX\to B, B→YB\to Y e un arco latente tra XX e YY; trovare tutti gli insiemi validi per il criterio front-door.
  3. Ripetendo l'esempio del fumo, si hanno i dati di un esperimento ipotetico con 400 fumatori e 400 non fumatori. Scrivere un programma Python che implementi l'aggiustamento front-door e calcoli l'effetto causale del fumo sul cancro, P(y∣do(x))P(y\mid do(x)).

Teoria: 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 → (criterio e formula front-door). Per le funzioni dei grafi si usano quelle di Esercizio - Cammini backdoor e insiemi di aggiustamento validi (laboratorio 8).

Criterio front-door

Un insieme SS soddisfa il criterio front-door per (X,Y)(X,Y) se

  1. blocca ogni cammino diretto da XX a YY (tutti passano per SS);
  2. non ci sono cammini backdoor aperti da XX a SS;
  3. tutti i cammini backdoor da SS a YY sono bloccati da XX.

In tal caso P(y∣do(x))=∑sP(s∣x)∑x′P(y∣s,x′)P(x′)P(y\mid do(x))=\sum_sP(s\mid x)\sum_{x'}P(y\mid s,x')P(x'). La libreria rappresenta il confondente come arco latente; qui lo si rappresenta con un nodo UU presente nel grafo ma non tra i nodi osservati (non si può condizionare su UU):

python
from e4lib import *                      # figli, genitori, discendenti, cammini, bloccato (funzioni dell'esercizio precedente)

def cammini_diretti(E, x, y):
    return [p for p in cammini(E, x, y) if all((a, b) in E for a, b in zip(p, p[1:]))]

def backdoor_verso(E, x, z):             # cammini da x a z il cui primo arco entra in x
    return [p for p in cammini(E, x, z) if (p[1], x) in E]

def insiemi_frontdoor(E, x, y, osservati):
    out = []
    for r in range(1, len(osservati) + 1):
        for S in combinations(sorted(osservati), r):
            S = set(S)
            ok1 = all(set(p[1:-1]) & S for p in cammini_diretti(E, x, y))                 # intercetta ogni cammino diretto
            ok2 = all(bloccato(E, p, set()) for s in S for p in backdoor_verso(E, x, s))  # nessun backdoor aperto da x a S
            ok3 = all(bloccato(E, p, {x}) for s in S for p in cammini(E, s, y) if (p[1], s) in E)  # backdoor da S a y chiusi da x
            if ok1 and ok2 and ok3: out.append(sorted(S))
    return out

Soluzione

3. Catena X→A→B→YX\to A\to B\to Y

Grafico interattivo: Esercizio 3: X → A → B → Y con confondente non osservato U (tratteggiato) tra X e Y

python
E3 = {("X", "A"), ("A", "B"), ("B", "Y"), ("U", "X"), ("U", "Y")}
print(insiemi_frontdoor(E3, "X", "Y", {"A", "B"}))

Output: [['A'], ['B'], ['A', 'B']], lo stesso di get_all_frontdoor_adjustment_sets('X','Y') (frozenset({{A},{B},{A,B}})). Ragionamento: l'unico cammino diretto è X→A→B→YX\to A\to B\to Y, quindi {A}\{A\}, {B}\{B\} e {A,B}\{A,B\} lo intercettano. Il solo cammino backdoor da XX a AA o a BB passa per YY (X←U→Y←B←AX\leftarrow U\to Y\leftarrow B\leftarrow A) dove YY è un collider non condizionato: già bloccato. I cammini backdoor da AA (o BB) a YY passano tutti per XX (A←X←U→YA\leftarrow X\leftarrow U\to Y) e sono bloccati dal condizionare su XX.

4. Due mediatori in parallelo

Grafico interattivo: Esercizio 4: X → A → Y e X → B → Y con confondente non osservato U (tratteggiato)

python
E4 = {("X", "A"), ("A", "Y"), ("X", "B"), ("B", "Y"), ("U", "X"), ("U", "Y")}
print(insiemi_frontdoor(E4, "X", "Y", {"A", "B"}))

Output: [['A', 'B']]. Ci sono due cammini diretti, X→A→YX\to A\to Y e X→B→YX\to B\to Y: {A}\{A\} da solo lascia aperto quello per BB (condizione 1 violata), e viceversa. Serve {A,B}\{A,B\}.

Caso impossibile (slide). Se UU è causa anche del mediatore ZZ (U→ZU\to Z), insiemi_frontdoor restituisce []: il cammino backdoor da ZZ a YY, Z←U→YZ\leftarrow U\to Y, non passa per XX e non è bloccato (condizione 3 violata), né si può condizionare su UU.

5. Fumo, catrame e cancro

Il grafo è X→Z→YX\to Z\to Y con UU (il «gene del fumo», o un fattore come l'età) causa non osservata di XX e YY: XX fumo, ZZ deposito di catrame, YY cancro.

Grafico interattivo: Front-door: il gene U (non osservato, tratteggiato) è causa comune di X (fumo) e Y (cancro); Z (catrame) è osservato

Dal codice del corso si leggono le tabelle (indici t=vero, f=falso):

  • P(X)P(X): 400800\frac{400}{800} per fumatori e per non fumatori;
  • P(z∣x)=380400=0.95P(z\mid x)=\frac{380}{400}=0.95, P(z∣¬x)=20400=0.05P(z\mid\neg x)=\frac{20}{400}=0.05;
  • P(y∣z,x)=57380=0.15P(y\mid z,x)=\frac{57}{380}=0.15; P(y∣z,¬x)=1920=0.95P(y\mid z,\neg x)=\frac{19}{20}=0.95; P(y∣¬z,x)=220=0.10P(y\mid\neg z,x)=\frac{2}{20}=0.10; P(y∣¬z,¬x)=342380=0.90P(y\mid\neg z,\neg x)=\frac{342}{380}=0.90.

Formula front-door: P(y∣do(x))=∑zP(z∣x)∑x′P(y∣z,x′)P(x′)=P(z∣x)[P(y∣z,x)P(x)+P(y∣z,¬x)P(¬x)]+P(¬z∣x)[P(y∣¬z,x)P(x)+P(y∣¬z,¬x)P(¬x)].P(y\mid do(x))=\sum_zP(z\mid x)\sum_{x'}P(y\mid z,x')P(x')=P(z\mid x)\big[P(y\mid z,x)P(x)+P(y\mid z,\neg x)P(\neg x)\big]+P(\neg z\mid x)\big[P(y\mid\neg z,x)P(x)+P(y\mid\neg z,\neg x)P(\neg x)\big].

Con i numeri: P(y∣do(x))=0.95 [0.15⋅0.5+0.95⋅0.5]+0.05 [0.10⋅0.5+0.90⋅0.5]=0.95⋅0.55+0.05⋅0.5=0.5225+0.025=0.5475.P(y\mid do(x))=0.95\,[0.15\cdot0.5+0.95\cdot0.5]+0.05\,[0.10\cdot0.5+0.90\cdot0.5]=0.95\cdot0.55+0.05\cdot0.5=0.5225+0.025=0.5475. (la parentesi quadra interna è la media su x′x' pesata con P(x′)P(x'): è P(y∣do(z))P(y\mid do(z)), l'effetto del catrame sul cancro, che si ricava dal backdoor XX.) In frazioni esatte è 219400\frac{219}{400}.

python
import numpy as np

t, f = 0, 1                                                                   # indici: 0 = vero, 1 = falso
P_X = np.array([400 / 800, 400 / 800])
P_Z_X = np.array([[380 / 400, 20 / 400], [20 / 400, 380 / 400]])             # P(z | x): [z][x]
P_Y_ZX = np.array([[[57 / 380, 19 / 20], [2 / 20, 342 / 380]],
                   [[323 / 380, 1 / 20], [18 / 20, 38 / 380]]])               # P(y | z, x): [y][z][x]

def front_door(x):
    return sum(P_Z_X[z, x] * sum(P_Y_ZX[t, z, xp] * P_X[xp] for xp in (t, f)) for z in (t, f))

print(front_door(t), front_door(f), front_door(t) - front_door(f))

Output: 0.5475 0.5025 0.04500000000000004. Quindi P(y∣do(fumo))=0.5475P(y\mid do(\text{fumo}))=0.5475. Per il caso senza fumo P(z∣¬x)=0.05P(z\mid\neg x)=0.05 e P(y∣do(¬x))=0.05⋅0.55+0.95⋅0.5=0.0275+0.475=0.5025P(y\mid do(\neg x))=0.05\cdot0.55+0.95\cdot0.5=0.0275+0.475=0.5025. L'ACE del fumo è 0.5475−0.5025=0.045>00.5475-0.5025=0.045>0: fumare aumenta di 4.5 punti percentuali la probabilità di cancro.

Perché è un risultato controintuitivo di proposito. I dati osservazionali dicono l'opposto: tra i fumatori il cancro è P(y∣x)=57+2400=0.1475P(y\mid x)=\frac{57+2}{400}=0.1475, tra i non fumatori P(y∣¬x)=19+342400=0.9025P(y\mid\neg x)=\frac{19+342}{400}=0.9025, come se fumare proteggesse. Una spiegazione possibile: in alcuni paesi i fumatori sono molti più giovani dei non fumatori, e i vecchi si ammalano di più. L'età è un confondente di fumo e cancro, ma non è osservata, quindi non si può aggiustare su di essa. L'unica alternativa sarebbe un esperimento randomizzato (forzare a fumare metà persone), impossibile; con il catrame, osservato, il front-door recupera comunque l'effetto causale.

Verifica del metodo (modello inventato da me, non del corso). SCM booleano: P(U)=0.5P(U)=0.5, P(x∣u)=0.1; 0.9P(x\mid u)=0.1;\ 0.9 per u=0;1u=0;1, P(z∣x)=0.1; 0.9P(z\mid x)=0.1;\ 0.9, P(y∣z,u)=0.05; 0.45P(y\mid z,u)=0.05;\ 0.45 per z=0z=0 e 0.30; 0.700.30;\ 0.70 per z=1z=1. Il calcolo esatto dal modello dà P(y∣do(x))=0.475P(y\mid do(x))=0.475 e P(y∣do(¬x))=0.275P(y\mid do(\neg x))=0.275; il front-door applicato alla sola congiunta osservata di (X,Z,Y)(X,Z,Y) (senza UU) dà gli stessi 0.4750.475 e 0.2750.275; la simulazione con due milioni di campioni dà 0.47450.4745 e 0.27480.2748. Il confronto osservazionale (P(y∣x)=0.635P(y\mid x)=0.635 contro P(y∣¬x)=0.115P(y\mid\neg x)=0.115) è distorto dalla causa comune.

Versione ripasso

Teoria collegata