Skip to content

Step 1 — Lagged adjacencies (PCMCI)

Implements PCMCI (Runge et al., Sci. Adv. 2019) — not PCMCI+. PCMCI+ extends PCMCI to discover contemporaneous edges in the same pass; CDANs deliberately handles contemporaneous structure separately in Step 3 with a different conditioning strategy that exploits the lagged parents discovered here.

The implementation is fully self-contained — no tigramite dependency, no causal-learn dependency. PC-stable selection, MCI conditioning with proper time-shifting, and the CI test loop are all built directly on top of NumPy and the pluggable CITest protocol.

Two-phase structure

PC step (PC-stable, Colombo & Maathuis 2014)

For each target X_j[t], prune candidate lagged parents by an iterative process keyed on conditioning-set size:

candidates = all (variable, lag) columns
min_pval[c] = 1.0 for all c       # tracks strongest evidence of dependence

for cond_size = 0, 1, 2, ...:
    snapshot ranking = candidates sorted by min_pval[c] ascending
    to_remove = []
    for each c in ranked:
        cond_subset = top cond_size strongest *other* candidates from ranked
        p = ci_test(X_j[t], c, cond_subset)
        min_pval[c] = min(min_pval[c], p)
        if p > pc_alpha: to_remove.append(c)
    candidates -= to_remove
    if to_remove was empty: stop

Two specific properties make this stable:

  • The candidate ranking is snapshotted before each iteration starts. Removals inside the iteration don't change which conditioning sets are tested for the remaining candidates.
  • The min-p-value tracker preserves the strongest evidence of dependence observed across all subsets, so the ranking is consistent across iterations.

MCI step

For each surviving candidate X_i[t-lag] → X_j[t], run one final CI test at the strict threshold alpha (default 0.05). The conditioning set is what makes this step "Momentary":

  • Other lagged parents of X_j (no time shift).
  • Lagged parents of X_i, time-shifted by lag: a parent of X_i at offset lag' is at absolute time (t-lag) - lag', which corresponds to column (parent_var, lag + lag') in the design matrix. Parents whose shifted lag exceeds tau_max are dropped.

The shift is the critical detail. Without it the conditioning would be incoherent (asking about X_i's parents at time t while we're testing an edge about X_i at time t-lag).

Why not PCMCI+?

PCMCI+ would subsume Steps 1 and 3 of CDANs into a single pass, but it does not use the lagged-parent-only conditioning that gives CDANs its dimensionality advantage on contemporaneous edges. The two methods make different statistical trade-offs; CDANs deliberately keeps them separate so each step's conditioning strategy is optimal for its own job.

API

discover_lagged_adjacencies

discover_lagged_adjacencies(
    data: ndarray,
    tau_max: int,
    *,
    ci_test: str | CITest = "fisherz",
    alpha: float = 0.05,
    pc_alpha: float = 0.2,
    max_conds_dim: int | None = None,
    var_names: list[str] | None = None,
    verbose: bool = False,
) -> TimeSeriesGraph

Identify lagged parents for every variable using PCMCI.

Parameters:

Name Type Description Default
data ndarray

Time series, shape (n_samples, n_vars).

required
tau_max int

Maximum lag to consider.

required
ci_test str | CITest

CI test name ("fisherz" or "kci") or an instance.

'fisherz'
alpha float

Significance level for the final MCI step.

0.05
pc_alpha float

Looser significance level for the PC step. Following PCMCI conventions this is usually larger than alpha.

0.2
max_conds_dim int | None

Cap on the size of conditioning sets in the PC step. None (default) means no cap.

None
var_names list[str] | None

Optional variable names for the returned graph.

None
verbose bool

Print per-variable progress.

False

Returns:

Type Description
TimeSeriesGraph

Graph with lagged_edges populated and an empty contemporaneous skeleton (filled in by Step 2).