Esercizio - Cammini backdoor e insiemi di aggiustamento validi (laboratorio 8)
Testo (laboratorio 8, Causal inference - Part B, esercizi 1 e 2 e esempio con il grafo big_csm). Il laboratorio usa la libreria CausalGraphicalModels, che rappresenta un modello causale grafico con nodi e archi orientati e sa elencare i cammini backdoor e gli insiemi di aggiustamento.
- Creare la rete causale con quattro nodi e archi , , , per verificare che il cammino evidenziato in rosso è un cammino backdoor.
- Creare la rete con nodi e archi , , , , , , e trovare tutti gli insiemi che soddisfano il criterio backdoor. Verificare che sia valido.
- (Esempio del laboratorio) Nel grafo con nodi (figura sotto): quanti cammini backdoor ci sono tra e ? Quali sono gli insiemi di aggiustamento validi? Perché aggiungere , o a un insieme lo rende non valido?
Teoria: criterio backdoor in 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 →, d-separazione in Inferenza causale - modelli causali strutturali e scala della causalitàI dati e la distribuzione congiunta da soli non dicono quali variabili causano quali: lo mostrano il paradosso di Simpson (la medicina funziona in ogni sottogruppo ma sembra peggiorare la popolazione) e il fatto che reti bayesiane diverse (A→B e B→A) descrivono la stessa congiunta. Serve un modello causale: un modello causale strutturale (SCM) ha variabili esogene U, endogene V e funzioni $v_i=f_i(\text{genitori},u_i)$; il suo grafo è la rete causale, e una rete causale è una rete bayesiana in cui le frecce sono cause. Le tre strutture elementari decidono l'indipendenza: catena $A\to B\to C$ e fork $A\leftarrow B\to C$ si bloccano osservando B, il collider $A\to B\leftarrow C$ si blocca se B e i suoi discendenti NON sono osservati (osservarlo crea dipendenza, explaining away). d-separazione: X e Y sono separati da Z se ogni cammino è bloccato; allora $X\perp!!!\perp Y\mid Z$. La scala della causalità (Pearl): associazione $P(y\mid x)$, intervento $P(y\mid do(x))$, controfattuali. Argomento del corso da 9 CFU.Inferenza causale - modelli causali strutturali e scala della causalità →.
Strumenti
CausalGraphicalModels (non installata qui) offre CausalGraphicalModel(nodes=..., edges=...), get_all_backdoor_paths(x, y), get_all_backdoor_adjustment_sets(x, y), is_valid_backdoor_adjustment_set(x, y, S). Per poter verificare i risultati senza la libreria, qui si scrive una piccola funzione equivalente: si enumerano i cammini (frecce in ogni verso), si prendono quelli il cui primo arco entra in (cammini backdoor), e si prova ogni sottoinsieme dei nodi restanti.
from itertools import combinations
def figli(E, v): return {b for a, b in E if a == v}
def genitori(E, v): return {a for a, b in E if b == v}
def discendenti(E, v):
out, pila = set(), [v]
while pila:
for c in figli(E, pila.pop()):
if c not in out: out.add(c); pila.append(c)
return out
def cammini(E, x, y, p=None):
"""tutti i cammini semplici da x a y, con le frecce percorse in ogni verso"""
p = p or [x]
if p[-1] == y: yield p; return
for n in sorted(figli(E, p[-1]) | genitori(E, p[-1])):
if n not in p: yield from cammini(E, x, y, p + [n])
def bloccato(E, p, S):
for a, b, c in zip(p, p[1:], p[2:]):
if (a, b) in E and (c, b) in E: # collider: blocca se né b né i suoi discendenti sono in S
if not ({b} | discendenti(E, b)) & S: return True
elif b in S: return True # catena o fork: blocca se b è in S
return False
def cammini_backdoor(E, x, y):
return [p for p in cammini(E, x, y) if (p[1], x) in E] # il primo arco entra in x
def insiemi_backdoor(E, x, y):
nodi = sorted({n for e in E for n in e} - {x, y})
out = []
for r in range(len(nodi) + 1):
for S in combinations(nodi, r):
S = set(S)
if not S & discendenti(E, x) and all(bloccato(E, p, S) for p in cammini_backdoor(E, x, y)):
out.append(sorted(S))
return outUn insieme è valido se (1) blocca tutti i cammini backdoor e (2) non contiene discendenti di (not S & discendenti(E, x)): sono le due condizioni del criterio.
Soluzione
1. Un cammino backdoor
Grafico interattivo: Esercizio 1: il cammino backdoor X ← Z → W → Y (in arancione)
E1 = {("X", "Y"), ("Z", "X"), ("Z", "W"), ("W", "Y")}
print(cammini_backdoor(E1, "X", "Y"))Output: [['X', 'Z', 'W', 'Y']]. Ragionamento: i cammini tra e sono due, (diretto, il primo arco esce da ) e (il primo arco entra in ): il secondo è l'unico cammino backdoor, coincide con quello rosso del testo e con l'output di get_all_backdoor_paths. È un fork in seguito da una catena: resta aperto finché non si condiziona su o su .
2. Insiemi che soddisfano il criterio backdoor
Il grafo è quello della lezione: , , , , .
E2 = {("X", "A"), ("A", "Y"), ("X", "Y"), ("B", "X"), ("B", "C"), ("D", "C"), ("D", "Y")}
print(cammini_backdoor(E2, "X", "Y"))
print(insiemi_backdoor(E2, "X", "Y"))
print(bloccato(E2, cammini_backdoor(E2, "X", "Y")[0], {"B", "C", "D"}))Output:
[['X', 'B', 'C', 'D', 'Y']]
[[], ['B'], ['D'], ['B', 'C'], ['B', 'D'], ['C', 'D'], ['B', 'C', 'D']]
TrueRagionamento. Un solo cammino backdoor: , con collider.
- è valido: il collider , non condizionato e senza discendenti osservati, blocca il cammino.
- e sono validi: bloccano il cammino sul fork (risp. sul fork ). Se si condiziona su uno solo dei due, resta non condizionato e il collider continua a bloccare.
- non è valido: condizionare sul collider apre il cammino. Per questo compare solo con o (cioè ), che richiudono il cammino altrove.
- Qualsiasi insieme con è escluso: è discendente di (regola 2).
Quindi i sette insiemi in uscita sono tutti e soli i validi; è valido (True: il cammino è bloccato su e su ). Con CausalGraphicalModels si ottengono gli stessi: get_all_backdoor_adjustment_sets('X','Y') restituisce frozenset con e is_valid_backdoor_adjustment_set('X','Y',{'B','C','D'}) dà True.
3. L'esempio big_csm
Grafico interattivo: Esempio del laboratorio: due cammini backdoor tra x e y; x ← a → h ← y è bloccato dal collider h, x ← b → d → e → y no
Archi (letti dalla figura): , , , , , , , , , .
E3 = {("a", "x"), ("b", "x"), ("a", "h"), ("b", "d"), ("x", "c"), ("d", "e"), ("c", "y"), ("e", "y"), ("y", "h"), ("y", "f")}
print(cammini_backdoor(E3, "x", "y"))
print(len(insiemi_backdoor(E3, "x", "y")), insiemi_backdoor(E3, "x", "y"))Output:
[['x', 'a', 'h', 'y'], ['x', 'b', 'd', 'e', 'y']]
14 [['b'], ['d'], ['e'], ['a', 'b'], ['a', 'd'], ['a', 'e'], ['b', 'd'], ['b', 'e'], ['d', 'e'], ['a', 'b', 'd'], ['a', 'b', 'e'], ['a', 'd', 'e'], ['b', 'd', 'e'], ['a', 'b', 'd', 'e']]- Cammini backdoor: due. e . Il primo ha il collider e quindi è bloccato finché non si condiziona su (o su un suo discendente); il secondo va bloccato condizionando su uno qualsiasi di , , (fork in o catena in o ).
- Insiemi validi: ogni insieme che contiene almeno uno tra , eventualmente con (aggiungere non crea cammini: il cammino resta bloccato dal collider non condizionato). In tutto . Il vuoto e non sono validi: lasciano aperto il cammino per .
- Perché , , invalidano un insieme: è sul cammino diretto ed è discendente di ; e sono discendenti di , che a sua volta è discendente di , quindi discendenti di (regola 2). In più condizionare su aprirebbe il collider , creando un cammino non causale tra e . Il testo del corso lo dice così: «aggiungere , o creerebbe un nuovo cammino rendendo l'insieme non valido».
- Quando tutte le variabili sono osservate, l'insieme dei genitori di () è sempre valido, e infatti compare nella lista.
Il corso nota anche che, se non tutte le variabili sono osservate, alcune affermazioni causali non si possono stimare dai dati (nessun insieme valido tra gli osservati).
Versione ripasso
- Strumenti: CausalGraphicalModels:
CausalGraphicalModel(nodes, edges),get_all_backdoor_paths,get_all_backdoor_adjustment_sets,is_valid_backdoor_adjustment_set. Qui funzione equivalente: cammini con frecce in ogni verso; backdoor se il primo arco entra in (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 →). valido se blocca tutti i backdoor e non contiene discendenti di . Blocco: catena/fork con nodo centrale in , collider con né esso né i discendenti in . - Es. 1: , , , : unico backdoor (output
[['X','Z','W','Y']]). - Es. 2: , , , , : unico backdoor . Validi ; no (apre il collider), insiemi con no (discendente di ). è valido.
big_csm: ; ; ; ; . Due backdoor: (bloccato dal collider ) e (serve uno tra ). Validi: almeno uno tra , con o senza : . sono discendenti di e aprirebbe il collider.- Genitori di : i genitori di , , sono sempre validi se osservati.
- Errori tipici: includere un collider da solo; includere discendenti di (, , ); contare diretto come cammino backdoor; dimenticare che un collider non condizionato blocca già.