Optimal transport-based causal discovery using Wasserstein non-Gaussianity
Project description
📊 Optimal Transport LiNGAM
otlingam is a Python package for causal discovery in linear non-Gaussian structural equation models. It learns causal orders by maximizing the Wasserstein non-Gaussianity of standardized regression residuals and estimates edge weights with adaptive lasso.
✨ Features
- Exhaustive causal-order learning:
ExhaustiveLiNGAMuses subset dynamic programming to find a globally optimal order. - Scalable greedy learning:
GreedyLiNGAMconstructs an order by sequentially selecting the most non-Gaussian residual. - Optimal transport ICA:
ICALiNGAMusesOTICAwith FastICA initialization in the classical ICA-LiNGAM pipeline. - Exact empirical criterion: Computes one-dimensional Wasserstein scores directly from ordered residuals and Gaussian quantiles.
- LiNGAM integration: Exposes causal orders and weighted adjacency matrices through the established LiNGAM estimator API.
⚡ Method
The estimators assume the linear structural equation model
$$ X_j = \sum_{k \in \mathrm{Pa}(j)} B_{jk} X_k + \varepsilon_j, $$
where the graph is acyclic and the structural noises are mutually independent, centered, and have finite nonzero variances. Causal-order identification additionally requires at most one Gaussian structural noise.
For a candidate order $\sigma$, let $R_j(\sigma)$ be the population residual obtained by regressing $X_j$ on its predecessors under $\sigma$. The oracle Wasserstein order objective is
$$ G(\sigma) = \sum_{j = 1}^{d} \mathcal{W}_2(\mathrm{std}(R_j(\sigma)), \mathcal{N}(0, 1))^2. $$
Given $n$ observations, let $\widehat{R}_j^{(i)}(\sigma)$ be the ordinary least-squares residual for observation $i$. OTLiNGAM maximizes the empirical order objective
$$ \widehat{G}n(\sigma) = \sum{j = 1}^{d} \mathcal{W}2(\mathrm{std}(\frac{1}{n} \sum{i = 1}^{n} \delta_{\widehat{R}_j^{(i)}(\sigma)}), \mathcal{N}(0, 1))^2. $$
At the population level, the maximizers of $G$ are exactly the topological orders under the stated assumptions. A topological order exposes the independent structural noises as regression residuals, whereas an incorrect order may mix several noises and reduce the total objective. Each empirical one-dimensional Wasserstein distance is evaluated exactly by sorting the standardized residuals and comparing them with the Gaussian reference quantiles.
🚀 Installation
pip install otlingam
🔧 Usage
Example
The following example simulates a linear non-Gaussian structural equation model, learns a causal order with GreedyLiNGAM, and compares the true and estimated weighted adjacency matrices.
import matplotlib.pyplot as plt
import numpy as np
from otlingam import GreedyLiNGAM, disorder
rng = np.random.default_rng(42)
n_samples = 5_000
adjacency_matrix = np.array(
[
[0.0, 0.0, 0.0, 0.0, 0.0],
[0.8, 0.0, 0.0, 0.0, 0.0],
[0.0, -0.7, 0.0, 0.0, 0.0],
[0.5, 0.0, 0.9, 0.0, 0.0],
[0.0, -0.6, 0.0, 0.7, 0.0],
]
)
noise = rng.uniform(-1.0, 1.0, size=(n_samples, 5))
X = noise @ np.linalg.inv(np.eye(5) - adjacency_matrix).T
model = GreedyLiNGAM().fit(X)
print("Estimated causal order:", model.causal_order_)
print("Disorder:", disorder(model.causal_order_, adjacency_matrix))
fig, axes = plt.subplots(1, 2, figsize=(10, 4), layout="constrained")
matrices = (adjacency_matrix, model.adjacency_matrix_)
titles = ("True adjacency matrix", "Estimated adjacency matrix")
for ax, matrix, title in zip(axes, matrices, titles, strict=True):
image = ax.imshow(matrix, cmap="RdBu_r", vmin=-1.0, vmax=1.0)
ax.set_title(title)
ax.set_xlabel("Parent")
ax.set_ylabel("Child")
fig.colorbar(image, ax=axes, label="Edge weight")
plt.show()
ExhaustiveLiNGAM provides global order optimization at an exponential cost in the number of variables. GreedyLiNGAM provides a quadratic-time alternative. Set fit_intercept=False when the observations are already centered. The default fit_intercept=True centers the data and exposes the fitted intercepts through intercept_.
📖 Learn More
For configuration details and the API reference, visit otlingam's documentation.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file otlingam-0.1.0.tar.gz.
File metadata
- Download URL: otlingam-0.1.0.tar.gz
- Upload date:
- Size: 29.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f1e8e7eecb23f4edad1d04e013f3898f07dc0c6b9b8801507f46206d61468f6
|
|
| MD5 |
b0d8dcac94fbc1f440df7e7caa50c62b
|
|
| BLAKE2b-256 |
c9835fb45d805e3c52fad41bfa2f7c918b9b24b44cb4b1fe6f088fa2384656c3
|
File details
Details for the file otlingam-0.1.0-py3-none-any.whl.
File metadata
- Download URL: otlingam-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e68fbf8382c66e8e3a18cb7c61bb320e2838f8f1e381251e40fa9574e42a2e20
|
|
| MD5 |
77d2af770ebec758aef8d8d277f78eac
|
|
| BLAKE2b-256 |
16f3d053875f493d016fc4591bd4f9d801d896190f4d71c04c86ca98c343425a
|