Causal Cognition: Tutorial 2¶
import sys
import os
# Get the absolute path to the parent directory
module_path = os.path.abspath(os.path.join('..')) # .. refers to one directory up
if module_path not in sys.path:
sys.path.append(module_path)
import utils
Recap: Pearl's Casual Hierarchy¶
- Associative inference (seeing)
- Establishing dependence and independence relations
- Causal inference (doing)
- Establishing causal relationships
- Counterfactual inference (imagining)
- Establishing causal representations and explanations
Structural Inference¶
Suppose we have data that comes from one of the following structures:
Chain: $ A \rightarrow B \rightarrow C $
Fork: $ B \leftarrow A \rightarrow C $
Collider: $A \rightarrow C \leftarrow B$
Can we try to tell them apart?
From Observations¶
test = utils.make_collider(U_a=0.5,U_b = 0.5, theta_ac = .750, theta_bc = 0.75)
test_data = test.simulate(10) # get 10 observations
test_data
0%| | 0/3 [00:00<?, ?it/s]
| A | C | B | |
|---|---|---|---|
| 0 | 1 | 0 | 0 |
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 1 |
| 3 | 0 | 1 | 1 |
| 4 | 0 | 0 | 0 |
| 5 | 0 | 0 | 0 |
| 6 | 0 | 0 | 0 |
| 7 | 0 | 0 | 1 |
| 8 | 1 | 1 | 0 |
| 9 | 0 | 0 | 0 |
Lets check if we see some screening off or explaining away:
test_data[test_data.C==1]#.A.mean()
| A | C | B | |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 1 |
| 3 | 0 | 1 | 1 |
| 8 | 1 | 1 | 0 |
test_data[(test_data.C==1) & (test_data.B==1)]#.A.mean()
| A | C | B | |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 1 |
| 3 | 0 | 1 | 1 |
def check_screening(data,evidence1 = 'A', evidence2 = 'B',set_to=1):
query = [ev for ev in ['A','B','C'] if ev not in [evidence1,evidence2]][0]
p_c_b = data[data[evidence1]==set_to] # P(C|B)
p_c_ab = data[(data[evidence1]==set_to) & (data[evidence2]==set_to)] # P(C|A,B)
print(f'P({query}|{evidence1}) = {p_c_b[query].mean()}')
print(f'P({query}|{evidence1},{evidence2}) = {p_c_ab[query].mean()}')
return p_c_b,p_c_ab
_,_ = check_screening(test_data,'C','B',1)
P(A|C) = 0.75 P(A|C,B) = 0.6666666666666666
Clear here that we do not see screening off, so we can conclude the structure is likely the collider!
test_2 = utils.make_fork(U_a=0.75,p_B_cond_A=0.75,p_C_cond_A=0.75)
test_data2 = test_2.simulate(1000)
test_data2
0%| | 0/3 [00:00<?, ?it/s]
| A | C | B | |
|---|---|---|---|
| 0 | 1 | 1 | 1 |
| 1 | 0 | 0 | 0 |
| 2 | 0 | 0 | 0 |
| 3 | 0 | 0 | 0 |
| 4 | 0 | 0 | 0 |
| ... | ... | ... | ... |
| 995 | 1 | 0 | 1 |
| 996 | 1 | 1 | 0 |
| 997 | 1 | 0 | 1 |
| 998 | 0 | 0 | 0 |
| 999 | 1 | 1 | 1 |
1000 rows × 3 columns
_,_ = check_screening(test_data2,'A', 'B',1)
P(C|A) = 0.7860026917900403 P(C|A,B) = 0.7757685352622061
test_3 = utils.make_chain(U_a=0.75,p_B_cond_A=0.75,p_C_cond_B=0.75)
test_data3 = test_3.simulate(10)
test_data3
0%| | 0/3 [00:00<?, ?it/s]
| A | C | B | |
|---|---|---|---|
| 0 | 1 | 0 | 0 |
| 1 | 0 | 0 | 0 |
| 2 | 1 | 0 | 1 |
| 3 | 0 | 0 | 0 |
| 4 | 1 | 0 | 0 |
| 5 | 1 | 1 | 1 |
| 6 | 1 | 1 | 1 |
| 7 | 1 | 1 | 1 |
| 8 | 1 | 0 | 0 |
| 9 | 1 | 1 | 1 |
_,_ = check_screening(test_data3,'B', 'A',1)
P(C|B) = 0.8 P(C|B,A) = 0.8
From here it looks like we have some screening off:
Ether we have
Chain: $ A \rightarrow B \rightarrow C $
or
Fork: $ B \leftarrow A \rightarrow C $
Can we make an intervention to tell them appart?
With interventions¶
Interventions let us fix variables to anythink we like before observing.
Positive test Strategy:¶
Intervene on the root-nodes and observe the system:
# fork intervention
test_2.simulate(10,do={'A':1})
0%| | 0/10 [00:00<?, ?it/s]
| A | C | B | |
|---|---|---|---|
| 0 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 |
| 2 | 1 | 0 | 1 |
| 3 | 1 | 1 | 1 |
| 4 | 1 | 1 | 1 |
| 5 | 1 | 1 | 1 |
| 6 | 1 | 1 | 0 |
| 7 | 1 | 1 | 1 |
| 8 | 1 | 0 | 1 |
| 9 | 1 | 0 | 1 |
# chain intervention
test_3.simulate(10,do={'A':1})
0%| | 0/10 [00:00<?, ?it/s]
| A | C | B | |
|---|---|---|---|
| 0 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 1 |
| 3 | 1 | 0 | 0 |
| 4 | 1 | 0 | 0 |
| 5 | 1 | 1 | 1 |
| 6 | 1 | 0 | 1 |
| 7 | 1 | 1 | 1 |
| 8 | 1 | 0 | 1 |
| 9 | 1 | 1 | 1 |
Maximum information gain:¶
Intervene on the variable that gives maximum information:
In this case it would be the one that could be at the centre of the chain:
# fork intervention
test_2.simulate(10,do={'B':0})
0%| | 0/10 [00:00<?, ?it/s]
| A | C | B | |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 0 |
| 2 | 1 | 0 | 0 |
| 3 | 1 | 1 | 0 |
| 4 | 1 | 1 | 0 |
| 5 | 0 | 0 | 0 |
| 6 | 1 | 1 | 0 |
| 7 | 1 | 0 | 0 |
| 8 | 0 | 0 | 0 |
| 9 | 1 | 1 | 0 |
# chain intervention
test_3.simulate(10,do={'B':0})
0%| | 0/10 [00:00<?, ?it/s]
| A | C | B | |
|---|---|---|---|
| 0 | 1 | 0 | 0 |
| 1 | 1 | 0 | 0 |
| 2 | 1 | 0 | 0 |
| 3 | 0 | 0 | 0 |
| 4 | 1 | 0 | 0 |
| 5 | 1 | 0 | 0 |
| 6 | 1 | 0 | 0 |
| 7 | 1 | 0 | 0 |
| 8 | 1 | 0 | 0 |
| 9 | 1 | 0 | 0 |
Activity: Create and infer causal structures¶
You will get in groups of 3. In these groups you will each be doing 10 trials of coin flips. You will record the outcome as successful (1) or unsuccessful (0). Flips are successfull if they land on heads and unsuccessful if Tails or if the coin is not thrown.
You get to decide how you want to structure your coin flips:
Chain: $ A \rightarrow B \rightarrow C $
Fork: $ B \leftarrow A \rightarrow C $
Collider: $A \rightarrow C \leftarrow B$

Here your task will be to try to identify the structure of coin flips in other groups.