Examples

Time series (PCMCI, PCMCI+, LPCMCI, Granger, VARMA-LiNGAM, longitudinal LiNGAM)#

Open in Colab Download this notebook

Six methods find causes in data measured over time. PCMCI and Granger find the links from earlier steps; PCMCI+ also finds links within one step, and LPCMCI allows series that are not measured. VARMA-LiNGAM estimates the weights of the links within one step and from the step before. Longitudinal LiNGAM takes a panel instead: the same units measured at a few time points. Each returns a TemporalStructure, one graph per lag.

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 numpy as np
import pandas as pd
from IPython.display import display

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

Data#

sample_scm draws one series of 1,000 time steps over 4 variables: a vector autoregression with one lag, links within each step from a random DAG, and uniform noise. The drawing merges the lags into one graph; a loop is a variable’s dependence on its own past.

series = sample_scm(
    graph="erdos_renyi",
    functional="var",
    noise="uniform",
    d=4,
    n=1000,
    seed=1,
    density=1.0,
)
X, truth = series.data, series.graph
print(f"{X.shape[0]} time steps of {X.shape[1]} variables")
print(truth)
display(andrey.viz.draw(truth.summary_graph()))
1000 time steps of 4 variables
temporal  |  4 nodes  |  2 lags

  lag 0  |  2 edges (2 directed)
    0 -> 1
    0 -> 3

  lag 1  |  10 edges (10 directed)
    0 -> 0
    0 -> 1
    1 -> 0
    0 -> 3
    1 -> 1
    2 -> 1
    1 -> 3
    2 -> 2
    2 -> 3
    3 -> 3
0123

Weights: VARMA-LiNGAM#

VARMA-LiNGAM uses the non-Gaussian noise to order the variables within a step, and estimates the weight of every link. prune=True sets small weights to zero.

out = andrey.varma_lingam(X, order=(1, 1), prune=True)
print(out, end="\n\n")
print("lag 0 weights, row = cause:")
print(np.round(out.structure.lag_weights[0], 2))
varmalingam  temporal  |  4 nodes  |  2 lags

  lag 0  |  2 edges (2 directed)
    0 -> 1
    0 -> 3

  lag 1  |  7 edges (7 directed)
    0 -> 0
    1 -> 0
    1 -> 1
    2 -> 1
    1 -> 3
    2 -> 3
    3 -> 3

  order: 0 -> 3 -> 2 -> 1

lag 0 weights, row = cause:
[[ 0.    1.19  0.   -1.38]
 [ 0.    0.    0.    0.  ]
 [ 0.    0.    0.    0.  ]
 [ 0.    0.    0.    0.  ]]

Panels: longitudinal LiNGAM#

Longitudinal LiNGAM takes one table per time point, the same units in the same rows. Here 500 units are measured at 3 time points; within each, x0 -> x1 -> x2, and each variable depends on its own value at the time point before. The columns come in one order drawn from the seed.

CHAIN = [("x0", "x1"), ("x1", "x2")]  # the true edges within a time point, cause first


def sample_panel(seed: int, n: int = 500, shuffle: bool = True) -> list[pd.DataFrame]:
    """Draw n units at 3 time points, the columns in one order drawn from the seed."""
    rng = np.random.default_rng(seed)
    b0 = np.array([[0.0, 0.0, 0.0], [0.8, 0.0, 0.0], [0.0, -0.7, 0.0]])  # row = effect
    mix = np.linalg.inv(np.eye(3) - b0)
    panel = [rng.uniform(-1, 1, (n, 3)) @ mix.T]
    for _ in range(2):
        panel.append((0.5 * panel[-1] + rng.uniform(-1, 1, (n, 3))) @ mix.T)
    names = ["x0", "x1", "x2"]
    columns = list(rng.permutation(names)) if shuffle else names
    return [pd.DataFrame(x, columns=names)[columns] for x in panel]


out = andrey.longitudinal_lingam(sample_panel(seed=0), n_lags=1)
print(out, end="\n\n")
print("lag 0 at the last time point:", out.structure.lag(0).oriented_edges())
longitudinallingam  temporal  |  3 nodes  |  2 lags

  lag 0  |  2 edges (2 directed)
    x0 -> x1
    x1 -> x2

  lag 1  |  9 edges (9 directed)
    x0 -> x0
    x0 -> x1
    x1 -> x0
    x0 -> x2
    x2 -> x0
    x1 -> x1
    x1 -> x2
    x2 -> x1
    x2 -> x2

  causal_orders: [[], [0, 1, 2], [0, 1, 2]]

lag 0 at the last time point: [('x0', 'x1', 'directed'), ('x1', 'x2', 'directed')]

The release cards compare each method with tigramite, lingam, or causal-learn on the simulations of the methods’ papers.

Use your own data#

Pass a table with one row per time step, such as a DataFrame whose column names become the graph’s labels, or change seed and n above and rerun.