Examples

Pairwise direction (ANM / PNL)#

Open in Colab Download this notebook

ANM and PNL take two variables and test which one causes the other. Each fits a model in both directions and tests whether the estimated noise is independent of the proposed cause, which holds only in the causal direction. ANM fits an additive noise model, y = f(x) + e; PNL fits a post-nonlinear one, y = f2(f1(x) + e). Both return a two-node DAG and both directions’ p-values.

Start#

Choose a CPU runtime and run this cell first. In Colab it installs andrey-core==0.9.0 from PyPI; local kernels use the installed environment.

import os
import sys

IN_COLAB = "google.colab" in sys.modules

if IN_COLAB:
    os.environ["ANDREY_DEVICE"] = "cpu"
    !pip install --quiet "andrey-core[viz]==0.9.0" pandas
import pandas as pd
from IPython.display import display

import andrey
import andrey.viz
from andrey.data import sample_scm

Data#

sample_scm draws a cause-effect pair: two variables and one edge, in a direction the seed draws. Here the effect is a post-nonlinear function of the cause with Laplace noise, 500 samples.

seed = 3
pair = sample_scm(
    graph="erdos_renyi",
    functional="post_nonlinear",
    noise="laplace",
    d=2,
    n=500,
    seed=seed,
    scale="standardize",
    density=1.0,
)
X, truth = pair.data, pair.graph
print(f"{X.shape[0]} observations of 2 variables, seed {seed}")
display(andrey.viz.draw(truth))
500 observations of 2 variables, seed 3
01

Fit ANM and PNL#

Column 0 is x and column 1 is y. pval_forward tests x -> y and pval_backward tests y -> x; by default the edge points in the direction with the larger p-value. Pass a significance level such as alpha=0.05 to draw an edge only when exactly one direction’s p-value is above it.

print(andrey.anm(X), end="\n\n")
print(andrey.pnl(X))
ANM  dag  |  2 nodes  |  1 edges (1 directed)

  1 -> 0

  pval_forward: 9.834e-13
  pval_backward: 0.0021
  stat_forward: 5.487
  stat_backward: 2.195

PNL  dag  |  2 nodes  |  1 edges (1 directed)

  1 -> 0

  pval_forward: 0.0001507
  pval_backward: 0.01781
  stat_forward: 2.71
  stat_backward: 1.811

Compare over ten pairs#

Each method runs on the pairs its model describes: ANM on additive-noise pairs with Gaussian noise, and PNL on post-nonlinear pairs with Laplace noise. One pair can favor either answer, so the table covers ten seeds.

models = {
    "ANM": ("additive_noise", "gaussian", lambda x: andrey.anm(x)),
    "PNL": ("post_nonlinear", "laplace", lambda x: andrey.pnl(x)),
}
rows = {}
for s in range(10):
    rows[s] = {}
    for name, (functional, noise, fit) in models.items():
        draw = sample_scm(
            graph="erdos_renyi",
            functional=functional,
            noise=noise,
            d=2,
            n=500,
            seed=s,
            scale="standardize",
            density=1.0,
        )
        found = fit(draw.data).structure.oriented_edges()
        if found == draw.graph.oriented_edges():
            rows[s][name] = "true direction"
        else:
            rows[s][name] = "reversed" if found else "no edge"
table = pd.DataFrame(rows).T
table.index.name = "seed"
table
ANM PNL
seed
0 true direction true direction
1 true direction true direction
2 true direction true direction
3 true direction true direction
4 true direction true direction
5 reversed true direction
6 true direction true direction
7 true direction true direction
8 reversed true direction
9 true direction true direction

The 0.4.0 release card compares both methods with causal-learn on pairs of several sizes.

Use your own data#

Pass a two-column table, such as a DataFrame whose column names become the graph’s labels, or change seed and n above and rerun.