causal-observational-linear-non-gaussian

Causal Inferencecausal-learnrigorous codebase

Description

Causal Discovery: Observational Linear Non-Gaussian Data

Objective

Implement a causal discovery algorithm that recovers the DAG structure from purely observational data generated by a Linear Non-Gaussian Acyclic Model (LiNGAM). Your code goes in bench/custom_algorithm.py.

Background

LiNGAM-based methods exploit non-Gaussian noise to achieve full DAG identifiability from observational data alone, going beyond the Markov Equivalence Class limit of constraint-based (PC) and score-based (GES) methods.

Evaluation Scenarios

LabelGraph typeNodesSamplesNoise
ER10Erdos-Renyi10250Exponential
ER15Erdos-Renyi15500Laplace
SF12Scale-Free (BA)12300Uniform
ER30Erdos-Renyi301000Laplace
ER50Erdos-Renyi502000Exponential
ER50-LowSampleErdos-Renyi50250Exponential
SF100Scale-Free (BA)1001000Uniform
ER20-DenseErdos-Renyi20500Laplace

Metrics

All computed on the directed edge set (skeleton + direction must be correct):

  • F1 (primary ranking metric), SHD, Precision, Recall

Baselines

  • icalingam: ICA-based LiNGAM (Shimizu 2006)
  • directlingam: DirectLiNGAM (Shimizu 2011)

Code

custom_algorithm.py
EditableRead-only
1import numpy as np
2
3# =====================================================================
4# EDITABLE: implement run_causal_discovery below
5# =====================================================================
6def run_causal_discovery(X: np.ndarray) -> np.ndarray:
7 """
8 Input: X of shape (n_samples, n_variables)
9 Output: adjacency matrix B of shape (n_variables, n_variables)
10 B[i, j] != 0 means j -> i (follows causal-learn convention)
11 """
12 n = X.shape[1]
13 return np.zeros((n, n))
14# =====================================================================
15
run_eval.py
EditableRead-only
1"""Evaluation harness for the causal-observational-linear-non-gaussian task."""
2import argparse
3import os
4import sys
5
6sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
7
8from data_gen import simulate_lingam
9from metrics import compute_metrics
10from custom_algorithm import run_causal_discovery
11
12
13def main():
14 parser = argparse.ArgumentParser(
15 description="Evaluate a causal discovery algorithm on synthetic LiNGAM data."
data_gen.py
EditableRead-only
1"""Synthetic linear non-Gaussian DAG data generator for LiNGAM benchmarking."""
2import numpy as np
3import networkx as nx
4
5
6def simulate_dag(n_nodes, graph_type, seed, er_prob=0.5, sf_m=2):
7 """Return a binary adjacency matrix for a random DAG.
8
9 Convention: adj[i, j] = 1 means i -> j (i is a parent of j).
10 The DAG is enforced by keeping only edges i -> j with i < j, imposing
11 a topological ordering by node index.
12 """
13 rng = np.random.default_rng(seed)
14 graph_seed = int(rng.integers(0, 2**31 - 1))
15
metrics.py
EditableRead-only
1"""Evaluation metrics for directed causal graph recovery."""
2import numpy as np
3
4
5def compute_metrics(B_est, B_true, threshold=0.01):
6 """Compute SHD, F1, precision, and recall for directed edge recovery.
7
8 Convention: B[i, j] != 0 means j -> i.
9
10 SHD definition (each type counts as exactly 1 error):
11 - Reversed edge : correct skeleton edge but wrong direction
12 - Extra edge : present in estimate but absent in truth (non-reversal)
13 - Missing edge : present in truth but absent in estimate (non-reversal)
14
15 F1 / precision / recall are computed on the directed edge set

Results

ModelTypeshd SF100 f1 SF100 precision SF100 recall SF100 shd ER10 f1 ER10 precision ER10 recall ER10 shd SF12 f1 SF12 precision SF12 recall SF12 shd ER30 f1 ER30 precision ER30 recall ER30 shd ER50 f1 ER50 precision ER50 recall ER50
directlingambaseline7.3330.9880.9751.0000.6670.9760.9561.0000.6670.9840.9681.0002.3330.9900.9791.0003.0000.9940.9881.000
icalingambaseline120.3330.8030.7010.9401.3330.9540.9141.0001.0000.9760.9541.0002.3330.9890.9791.0003.0000.9940.9881.000
rcdbaseline379.0000.1070.1810.0784.3330.8100.9170.74218.0000.1791.0000.100104.6670.3500.5170.270----
anthropic/claude-opus-4.6vanilla712.0000.4430.2870.9736.0000.8240.7001.00019.0000.6780.5131.00061.0000.7770.6351.000167.0000.7500.6011.000
google/gemini-3.1-pro-previewvanilla1120.0000.2440.1490.6677.0000.8000.6671.0009.0000.8160.6901.000278.0000.2560.1690.528269.0000.6510.4831.000
gpt-5.4-provanilla215.0000.6410.5440.7802.0000.8970.8670.9293.0000.9300.8701.00019.0000.8990.9130.88792.0000.8020.8300.777
anthropic/claude-opus-4.6agent0.0001.0001.0001.0000.0001.0001.0001.0001.0000.9760.9521.0001.0000.9950.9911.0001.0000.9980.9961.000
google/gemini-3.1-pro-previewagent392.0000.4760.3580.7080.0001.0001.0001.0001.0000.9760.9521.0001.0000.9950.9911.0003.0000.9940.9881.000
gpt-5.4-proagent17.0000.9720.9480.9970.0001.0001.0001.0001.0000.9760.9521.0001.0000.9950.9911.00021.0000.9570.9870.928

Agent Conversations