Skip to main content

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: ExhaustiveLiNGAM uses subset dynamic programming to find a globally optimal order.
  • Scalable greedy learning: GreedyLiNGAM constructs an order by sequentially selecting the most non-Gaussian residual.
  • Optimal transport ICA: ICALiNGAM uses OTICA with 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

otlingam-0.1.0.tar.gz (29.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

otlingam-0.1.0-py3-none-any.whl (23.6 kB view details)

Uploaded Python 3

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

Hashes for otlingam-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6f1e8e7eecb23f4edad1d04e013f3898f07dc0c6b9b8801507f46206d61468f6
MD5 b0d8dcac94fbc1f440df7e7caa50c62b
BLAKE2b-256 c9835fb45d805e3c52fad41bfa2f7c918b9b24b44cb4b1fe6f088fa2384656c3

See more details on using hashes here.

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

Hashes for otlingam-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e68fbf8382c66e8e3a18cb7c61bb320e2838f8f1e381251e40fa9574e42a2e20
MD5 77d2af770ebec758aef8d8d277f78eac
BLAKE2b-256 16f3d053875f493d016fc4591bd4f9d801d896190f4d71c04c86ca98c343425a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page