v-TimeLKFlow
Time-varying Liang–Kleeman information flow implementations — a
BIA Lab package. Its current main implementation (mtvlk) is Zhou et al.
(2024)'s Kalman-filter-based method for estimating time-varying,
multivariate causal information flow between time series.
License: MIT
Contents
- Time-varying vs. static causality
- Overview
- Features
- Installation
- Quick start
- Package contents
- API overview
- Tutorials
- On methodological reconstructions
- Testing
- References
- Contributing / Issues
- License
- Citation
Time-varying vs. static causality
Most causality analyses — classical Granger causality, and the original
(static) Liang–Kleeman information flow itself — collapse an entire
observation record into a single number per variable pair: "does X
cause Y?" is answered once, from the whole time series, and that one
verdict is assumed to hold for the entire record. That's a poor fit for
systems where a causal relationship switches on, strengthens, weakens, or
disappears partway through — a coupling that only exists during part of a
climate regime, for example, can average out to "no detectable causality"
under a whole-series estimate even though it was clearly present for a
while. Instead of one static answer, this package tracks a running,
time-indexed estimate: a Kalman filter updates the local covariance
structure as each new observation arrives, so the causal-flow strength
T_{j→i}(t) (and its significance) is itself a function of time t, not a
single scalar — you get a curve showing when a link turned on or off, not
just whether it exists anywhere in the record.
Input format: every function in this package takes a single array X
of shape (T, n) — T time points (rows) by n signals/variables
(columns), i.e. n co-observed signals sampled at the same T time steps.
Passing all n signals into one call makes every estimated flow
conditional on the other n − 2 variables automatically — unlike a purely
bivariate/pairwise method, which can report a spurious link that's actually
mediated entirely through an unmodeled third signal (see the worked
bivariate-vs-conditional example in
examples/fig2_fig3_replication.py).
Overview
The Liang–Kleeman (LK) information flow (Liang 2021) is a rigorous,
first-principles measure of causality between time series, computed directly
from sample covariances — no model fitting, no surrogate data, and (unlike
Granger causality) a provable "principle of nil causality": if X truly
doesn't cause Y, the estimated flow is exactly zero in the population
limit.
The classical LK formalism assumes the underlying covariance structure is
stationary — a single number for the whole time series. Hagan et al.
(2019) extended this to a time-varying bivariate form (TvLK) by
tracking the covariances with a Kalman filter instead of a single sample
estimate. Zhou et al. (2024) generalized that further to the multivariate
case (MtvLK), so you can ask "does X1 cause X2, conditional on
X3, ..., Xn?" and have the answer change over time.
This package implements the full multivariate, time-varying method, plus the paper's Kalman-filter noise-estimation and significance-testing machinery — including places where the paper's own description is underspecified, which are documented explicitly rather than silently guessed at (see On methodological reconstructions).
- Zhou, F., et al. (2024). Estimating time-dependent structures in a multivariate causality for land–atmosphere interactions. Journal of Climate, 37(6). 10.1175/JCLI-D-23-0207.1
- Hagan, D. F. T., et al. (2019). A time-varying causality formalism based on the Liang–Kleeman information flow for analyzing directed interactions in nonstationary climate systems. Journal of Climate, 32(21). 10.1175/JCLI-D-18-0881.1
- Liang, X. S. (2021). Normalized multivariate time series causality analysis and causal graph reconstruction. Entropy, 23(6), 679. 10.3390/e23060679
Features
- Static multivariate LK (
compute_lk) — the stationary baseline, with block-bootstrap standard errors. - Time-varying MtvLK (
compute_mtvlk) — a Square-Root Kalman Filter tracks the covariance structure through time, givingT(t)instead of a singleT. - Adaptive process/measurement noise (
qr_mode="adaptive") — the paper's Kalman filter noise covariancesQ/Raren't held constant; this package estimates them online instead of requiring you to hand-tune them. - Appendix A window-fill (
compute_mtvlk_filled) — the Kalman filter needs a lookback window to "warm up," during which its estimate is unreliable. This runs a second pass backward through time to fill that initial gap, following the paper's own Appendix A prescription. - Three significance-test modes (
se_mode="kf"|"classical"|"regression") — from the filter's own uncertainty tracking, a closed-form classical approximation, or a literal windowed-regression implementation of the paper's stated Fisher-information formula. See each mode's docstring inmtvlk/core/mtvlk.pyfor when to reach for which. - A full synthetic-model replication suite (
examples/) — every synthetic experiment in Zhou et al. (2024) (Figures 2–4 and the Appendix C null-link tests), runnable as scripts or notebooks. See Tutorials.
Installation
The PyPI distribution is named v-TimeLKFlow (the lab's umbrella name for this
family of time-varying LK flow implementations); the importable Python package
remains mtvlk (the specific Zhou et al. 2024 algorithm this release implements).
pip install v-TimeLKFlow
Optional extras:
pip install v-TimeLKFlow[viz] # + matplotlib, for mtvlk.viz plotting helpers
pip install v-TimeLKFlow[xarray] # + xarray, for to_xarray() output
pip install v-TimeLKFlow[all] # viz + xarray + numba
pip install v-TimeLKFlow[dev] # + pytest, for running the test suite
pip install v-TimeLKFlow[notebooks] # + jupyter, for the tutorial notebooks
Core dependencies are just numpy and scipy — everything else is opt-in.
Quick start
import numpy as np
from mtvlk import compute_lk, compute_mtvlk, compute_mtvlk_filled
rng = np.random.default_rng(0)
X = rng.standard_normal((500, 3)) # 500 time steps, 3 variables
# Static multivariate LK (Liang 2021) -- one T matrix for the whole series
result_static = compute_lk(X, dt=1.0)
print(result_static["T"]) # (3, 3) causal matrix, T[j, i] = T_{j->i}
# Time-varying MtvLK (Zhou et al. 2024) -- a T(t) for every time step
result_tv = compute_mtvlk(X, dt=1.0)
print(result_tv["T_t"].shape) # (499, 3, 3)
# With the paper's adaptive noise + Appendix A window-fill + a real
# significance test, all at once:
result = compute_mtvlk_filled(
X, dt=1.0,
qr_mode="adaptive", qr_window=100,
fill_window=100,
se_mode="classical", se_window=100,
return_SE=True,
)
print(result["T_t"].shape, result["SE_t"].shape)
Package contents
| Path | Contents |
|---|---|
mtvlk/core/lk_stationary.py |
Static multivariate LK (compute_lk), the core cofactor-matrix formula, block-bootstrap standard errors. |
mtvlk/core/sqkf.py |
SquareRootKF — the Cholesky square-root Kalman filter that tracks the packed covariance/tendency-covariance state, plus the closed-form analytical_R noise formula. |
mtvlk/core/mtvlk.py |
compute_mtvlk / compute_mtvlk_filled / to_xarray — the time-varying MtvLK driver, adaptive-noise wiring, and the Appendix A backward/forward splice. |
mtvlk/utils/adaptive_noise.py |
AdaptiveQR — online EWMA estimator of the Kalman filter's process noise Q_t. |
mtvlk/utils/regression_se.py |
RegressionSE — windowed VAR(1) regression-coefficient variance, a literal implementation of Hagan (2019) appendix C's significance formula. |
mtvlk/utils/covariance.py |
Low-level helpers: finite differences, sample/tendency covariance, cofactor matrices, upper-triangular packing. |
mtvlk/utils/significance.py |
pointwise_ttest — an alternative block-permutation significance test. |
mtvlk/viz/plots.py |
Optional plotting helpers (causal-matrix heatmaps, time-series plots, static-vs-time-varying comparisons) — requires mtvlk[viz]. |
tests/ |
The pytest suite — one test file per mtvlk/ module, plus end-to-end checks in test_mtvlk.py. |
examples/ |
The full synthetic-model replication suite (see Tutorials below). |
papers/ |
The reference paper PDF, kept for convenience — not shipped in the PyPI package. |
API overview
compute_lk(X, dt, n_boot=200, block_len=None, rng=None)— static multivariate LK over the whole series. ReturnsT,tau, and bootstrap standard errorsT_err/tau_err.compute_mtvlk(X, dt, ...)— time-varying MtvLK. Key parameters:qr_mode:"constant"(fixedQ_scale/R_scale, the paper's naive baseline) or"adaptive"(online noise estimation).init_window: how many initial samples seed the Kalman filter (the paper uses 300 for its 3-variable models, 200 for its 5-variable one).se_mode:"kf","classical", or"regression"— see each mode's docstring for the trade-offs.return_covariances/return_SE: also return the trackedC_t/dC_tand/or the standard errorSE_t.
compute_mtvlk_filled(X, dt, ..., fill_window=None)— same ascompute_mtvlk, plus the Appendix A backward-pass splice that fills in the initialinit_window-length gap. This is what the example scripts use.SquareRootKF— the underlying filter class, if you need lower-level access (e.g. toanalytical_Rdirectly).to_xarray(result, time=None, var_names=None)— wrap acompute_mtvlk/compute_mtvlk_filledresult as anxarray.Dataset(mtvlk[xarray]required).
Every parameter is documented in its function's docstring — read those for the full picture; this section is a map, not a duplicate.
Tutorials
The examples/ directory replicates every synthetic experiment
in Zhou et al. (2024), as both standalone scripts and Jupyter notebooks. See
examples/README.md for a full walkthrough of each
one, what it demonstrates, and its expected output. In brief:
| Script | Notebook | Demonstrates |
|---|---|---|
fig2_fig3_replication.py |
01_fig2_fig3_replication.ipynb |
Synthetic Models 1 & 2 (paper Figs. 2–3): a 3-variable causal network whose links switch on and back off, bivariate vs. conditional causality, and a worked example of a spurious bivariate link that vanishes once conditioned on the confounder. |
fig4_model3_replication.py |
02_fig4_model3_replication.ipynb |
Synthetic Model 3 (paper Fig. 4): a 5-variable network that switches on and stays on, testing multivariate conditioning with several mediating paths at once. |
fig_appendixC_replication.py |
03_fig_appendixC_replication.ipynb |
Appendix C: every non-causal pair in all three models, confirming the method reports no significant flow where none was designed in. |
synthetic_replication.py |
04_synthetic_replication.ipynb |
Supplementary validation scenarios (abrupt regime shift, a trivariate confounder, sinusoidal coupling) not tied to a specific paper figure. |
Each script/notebook takes roughly 1–5 minutes to run (they use a reduced ensemble size compared to the paper's 1000 realizations — see each file's header comment for the exact reduction and why it doesn't change the qualitative conclusions).
On methodological reconstructions
The paper describes two mechanisms without giving an exact, reproducible formula, and its own code repository (linked in the paper) is no longer available. This package is transparent about where it had to reconstruct rather than copy:
- Adaptive
Q/Rnoise estimation (qr_mode="adaptive",AdaptiveQR) — the paper says it uses an "exponentially weighted moving average" but doesn't give the update equation. This package's reconstruction is documented inadaptive_noise.py's module docstring, including the specific alternative formulations that were tried and rejected during development (an earlier design caused a destabilizing feedback loop; see the docstring for why). se_mode="classical"— a closed-form approximation to the paper's significance test, decoupled from the Kalman filter's ownQ/R. Verified to be within the right order of magnitude of the paper's own stated formula, but not an exact algebraic match (seemtvlk/utils/regression_se.py's module docstring for the ~20% gap and why it's expected).se_mode="regression"— the most literal reconstruction, directly implementing the windowed VAR(1)-regression Fisher-information formula that Hagan et al. (2019) Appendix C states explicitly. Atn=2(the bivariate case both papers describe most precisely), this has been checked against a from-scratch OLS calculation intests/test_mtvlk.py.
Where the underlying causality formula itself is concerned (the actual T
computation, _lk_from_moments), there is no ambiguity — it's a direct,
verified implementation of Liang (2021)'s published formula, confirmed this
project to reduce to Hagan et al. (2019)'s bivariate formula to machine
precision at n=2.
Testing
pip install -e ".[dev]"
pytest
References
- Liang, X. S. (2021). Normalized multivariate time series causality analysis and causal graph reconstruction. Entropy, 23(6), 679. 10.3390/e23060679
- Hagan, D. F. T., Wang, G., Liang, X. S., & Dolman, H. A. J. (2019). A time-varying causality formalism based on the Liang–Kleeman information flow for analyzing directed interactions in nonstationary climate systems. Journal of Climate, 32(21). 10.1175/JCLI-D-18-0881.1
- Zhou, F., Hagan, D. F. T., Wang, G., Liang, X. S., Li, S., Shao, Y., Yeboah, E., & Wei, X. (2024). Estimating time-dependent structures in a multivariate causality for land–atmosphere interactions. Journal of Climate, 37(6). 10.1175/JCLI-D-23-0207.1
Contributing / Issues
Bug reports, questions, and pull requests are welcome — please open an issue at the repository's issue tracker.
License
MIT © 2026 BIA Lab
Citation
There is no standalone paper for this software (yet) — please cite the GitHub repository for the software itself, and the paper for the algorithm it implements:
@misc{v_timelkflow,
author = {Zhou, Felix Y.},
title = {v-TimeLKFlow: Time-Varying Liang-Kleeman Information Flow Implementations},
year = {2026},
howpublished = {\url{https://github.com/BIA-Lab-Team/v-TimeLKFlow}},
license = {MIT}
}
@article{zhou2024mtvlk,
author = {Zhou, Feihong and Hagan, Daniel Fiifi Tawia and Wang, Guojie and
Liang, X. San and Li, Shijie and Shao, Yuhao and Yeboah, Emmanuel
and Wei, Xikun},
title = {Estimating Time-Dependent Structures in a Multivariate Causality
for Land--Atmosphere Interactions},
journal = {Journal of Climate},
volume = {37},
number = {6},
year = {2024},
doi = {10.1175/JCLI-D-23-0207.1}
}
Metadata
Release files for v-TimeLKFlow 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| v_timelkflow-0.1.0.tar.gz | 33.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| v_timelkflow-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.5 kB
Release files / v_timelkflow-0.1.0.tar.gz
| Download URL | v_timelkflow-0.1.0.tar.gz |
|---|---|
| Size | 33.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7b7911147fbdff5e2f1a1ca86e61fd128379469f1f27dd4f8b0f85602f74fee4
|
|
BLAKE2b-256 checksum How to use checksums |
5ccdac5036f5adeecba11ad185811da3a1c4e3e81d0729b9087d6d651076b392
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / v_timelkflow-0.1.0-py3-none-any.whl
| Download URL | v_timelkflow-0.1.0-py3-none-any.whl |
|---|---|
| Size | 28.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2b97e8126a7ebbde24ea6cb6e2c2ed09c104c4830f8fe90331b8a523fa3f7e2c
|
|
BLAKE2b-256 checksum How to use checksums |
3bdd1952b542c2b017b21b42d16d7d014e6cbdd3d1d6a2ed0b0acd0fec951243
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|