📊 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:
ExhaustiveOTLiNGAMuses subset dynamic programming to find a globally optimal order. - Scalable greedy learning:
GreedyOTLiNGAMconstructs an order by sequentially selecting the most non-Gaussian residual. - Optimal transport ICA:
OTICALiNGAMusesOTICAwith 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.
- scikit-learn integration: Native
BaseEstimatorintegration with familiarfit,get_params,set_params, andclonesupport.
⚡ 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\left( \mathrm{std}(R_j(\sigma)), \mathcal{N}(0, 1) \right)^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\left( \mathrm{std}\left( \frac{1}{n} \sum{i = 1}^{n} \delta_{\widehat{R}_j^{(i)}(\sigma)} \right), \mathcal{N}(0, 1) \right)^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
python -m pip install otlingam
🔧 Usage
Example
The following example simulates a linear non-Gaussian structural equation model, learns a causal order with GreedyOTLiNGAM, and compares the true and estimated weighted adjacency matrices.
import matplotlib.pyplot as plt
import numpy as np
from otlingam import GreedyOTLiNGAM, disorder
rng = np.random.default_rng(42)
n_samples = 5000
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 = GreedyOTLiNGAM().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()
ExhaustiveOTLiNGAM provides global order optimization at an exponential cost in the number of variables. GreedyOTLiNGAM 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.
b54f299ce4a7d10bd3c7ac1e6c487aa9eff255e1
Metadata
Release files for otlingam 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| otlingam-0.3.0.tar.gz | 89.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| otlingam-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 114.0 kB
Release files / otlingam-0.3.0.tar.gz
| Download URL | otlingam-0.3.0.tar.gz |
|---|---|
| Size | 89.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fd8c21571452b31458e9bc114b68a6b2d657a741b84b3fc74ed477deec5918e3
|
|
BLAKE2b-256 checksum How to use checksums |
f8690e0fb88df5433af53d25b4bf81b881db6c2eed110af1983a149558faa3ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.14
|
Release files / otlingam-0.3.0-py3-none-any.whl
| Download URL | otlingam-0.3.0-py3-none-any.whl |
|---|---|
| Size | 24.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
325e11cb331dea1f2ff27199bd9909c3c21c4e37747be2bd9a3ba91f156fa224
|
|
BLAKE2b-256 checksum How to use checksums |
b3fb77c52352c5aae6904f072a11c64373afe2484b484830d78d7692c7b9e94b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.14
|