Learning CStree using Gibbs sampler

This notebook shows how to learn a CStree from observational data using McMC (Gibbs) sampling to approximate the posterior order distribution.

[70]:
import sys
import logging
import random

from causallearn.search.ConstraintBased.PC import pc
from causallearn.utils.cit import chisq

import pandas as pd
import matplotlib.pyplot as plt
import cslearn.cstree as ct
import cslearn.scoring as sc
import cslearn.stage as st
import cslearn.learning as ctl
import networkx as nx
import numpy as np
import pp

%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload

Sample a random CStree

We sample a 4-variables random CStree.

[71]:
np.random.seed(22)
random.seed(22)
p = 4
cards = [2] * p  # state space cardinalities

t = ct.sample_cstree(cards, max_cvars=2, prob_cvar=0.5, prop_nonsingleton=1)
t.labels = ["a", "b", "c", "d"]
t.sample_stage_parameters(alpha=2)
[72]:
t.sample(10)
[72]:
a b c d
0 2 2 2 2
1 1 0 1 1
2 1 0 0 1
3 1 0 1 1
4 1 1 1 0
5 1 0 1 1
6 1 0 1 1
7 1 0 1 1
8 1 0 0 1
9 1 0 0 1
10 1 0 1 1

Plot the part of the tree that was used for sampling data

[73]:
t.plot()
Use plot(full=True) to draw the full tree.
[73]:
../_images/source_learn_demo_gibbs_8_1.svg
[74]:
t.sample(5)
[74]:
a b c d
0 2 2 2 2
1 0 0 1 1
2 0 0 1 1
3 1 1 1 0
4 1 1 1 0
5 1 0 0 0
[75]:
t.plot()
Use plot(full=True) to draw the full tree.
[75]:
../_images/source_learn_demo_gibbs_10_1.svg

Get the minimal context DAGs

[76]:
agraphs = t.to_minimal_context_agraphs()
print("Number of minimal contexts:", len(agraphs))
Number of minimal contexts: 2
[77]:
keys = list(agraphs.keys())
print("Context:", keys[0])
agraphs[keys[0]]
Context: None
[77]:
../_images/source_learn_demo_gibbs_13_1.svg
[78]:
keys = list(agraphs.keys())
print("Context:", keys[1])
agraphs[keys[1]]
Context: b=1
[78]:
../_images/source_learn_demo_gibbs_14_1.svg
[79]:
np.random.seed(6)
x = t.sample(2000)

Create the score tables

When creating the score tables, we restrict the possible context variables for each node by the parents estimated by PC algorithm.

[94]:
pcgraph = pc(x[1:].values, 0.05, "chisq", node_names=x.columns)
poss_cvars = ctl.causallearn_graph_to_posscvars(pcgraph, labels=x.columns)
print("Possible context variables per node:", poss_cvars)
Depth=1, working on node 3: 100%|██████████| 4/4 [00:00<00:00, 1812.38it/s]
Possible context variables per node: {'a': [], 'b': ['d'], 'c': ['d'], 'd': ['b', 'c']}

Create the score tables

[81]:
score_table, context_scores, context_counts = sc.order_score_tables(
    x, max_cvars=2, alpha_tot=1.0, method="BDeu", poss_cvars=poss_cvars
)
Context score tables: 100%|██████████| 4/4 [00:00<00:00, 426.29it/s]
Creating #stagings tables: 100%|██████████| 4/4 [00:00<00:00, 16070.13it/s]
Order score tables: 100%|██████████| 4/4 [00:00<00:00, 5970.54it/s]

Run the Gibbs sampler

[82]:
orders, scores = ctl.gibbs_order_sampler(5000, score_table)
Gibbs order sampler: 100%|██████████| 5000/5000 [00:00<00:00, 18999.33it/s]

Trajectory score plots

We plot the first 200 iterations of the order score trajectory.

[83]:
plt.plot(scores[:200]);
../_images/source_learn_demo_gibbs_25_0.png

Variables order positions

To further investigate the McMC trajectory we find the individual variables positions/levels in the orders and plot these. This gives an overview of the mixing properties of the sampler and if the trajectory seems to have converged.

[84]:
var_positions = {var: [x.index(var) for x in orders] for var in orders[0]}
var_positions["iteration"] = list(range(len(orders)))
plotdf = pd.DataFrame(var_positions)

Assuming that the majority of probability mass is centered around the order corresponding to the true CStree (though there could be many or them), the a-line should converge to 0 (i.e. position 0), the b-line should converge to 1, and so on.

[85]:
plotdf[:100].plot(x="iteration", y=range(p), yticks=range(len(orders[0])));
../_images/source_learn_demo_gibbs_30_0.png

Get the maximal scoring order

[86]:
maporder = orders[scores.index(max(scores))]
maporder
[86]:
['b', 'd', 'a', 'c']

Get the maximal scoring CStree for the order

[87]:
opttree = ctl._optimal_cstree_given_order(maporder, context_scores)
[88]:
opttree.to_df()
[88]:
b d a c
0 2 2 2 2
1 0 - - -
2 1 - - -
3 * * - -
4 * * * -
5 - - - -
[89]:
opttree.plot(full=True)
[89]:
../_images/source_learn_demo_gibbs_36_0.svg

Estimate the CStree parameters

[90]:
opttree.estimate_stage_parameters(x, alpha_tot=2.0, method="BDeu")

opttree.to_df(write_probs=True)
[90]:
b d a c PROB_0 PROB_1
0 2 2 2 2 NaN NaN
1 0 - - - 0.352174 0.647826
2 1 - - - 0.847140 0.152860
3 * * - - 0.353646 0.646354
4 * * * - 0.300699 0.699301
5 - - - - 0.746753 0.253247
[91]:
opttree.plot(full=True)
[91]:
../_images/source_learn_demo_gibbs_39_0.svg

Plot the DAG representation

[92]:
agraphs = opttree.to_minimal_context_agraphs()
len(agraphs)
[92]:
1
[93]:
keys = list(agraphs.keys())
print(keys[0])
agraphs[keys[0]]
None
[93]:
../_images/source_learn_demo_gibbs_42_1.svg