Skip to main content

Optimal transport-based causal discovery using Wasserstein non-Gaussianity

Project description

📊 Optimal Transport LiNGAM

codecov

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

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 = 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 = 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.

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.2.5.tar.gz (263.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.2.5-py3-none-any.whl (201.5 kB view details)

Uploaded Python 3

File details

Details for the file otlingam-0.2.5.tar.gz.

File metadata

  • Download URL: otlingam-0.2.5.tar.gz
  • Upload date:
  • Size: 263.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.2.5.tar.gz
Algorithm Hash digest
SHA256 599163726a627ba5002d653d7495eaebe02cc6c3c28a169ddf4a3233b15b476a
MD5 e4646b10bae78b5f95fc9dd825238240
BLAKE2b-256 0e955154f7da5e77522ca5a800f2113874adaa17b24426b193e58278c6f426e7

See more details on using hashes here.

File details

Details for the file otlingam-0.2.5-py3-none-any.whl.

File metadata

  • Download URL: otlingam-0.2.5-py3-none-any.whl
  • Upload date:
  • Size: 201.5 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.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 f47eabb5dcd69740b565fe6e69eff43277d0e20664cf4ead9e9836604b4914fb
MD5 822f65282d0e569e092bf82e15bfb409
BLAKE2b-256 38a5b6dde9db02800a66ca8e5e25263ee95dd2b5d3b38058d15d3545a8882244

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