Granger Causality Validator

Synthetic validation for CAMS × Seshat Granger causality — three-scenario stress test

Before applying Granger causality to real CAMS + Seshat data, we validate the implementation against synthetic series with known ground truth. Three scenarios: A — true signal detection, B — null rejection (no false positives), C — noise stress test. Matches granger_synthetic_validator.py in the repo.

80
2
5
0.5

X "Granger-causes" Y if past values of X improve predictions of Y beyond what Y's own history provides. For CAMS research, we test whether Seshat social complexity indices predict CAMS cross-layer coupling Λ(t), and whether the causal direction is Seshat→Λ, Λ→Seshat, or bidirectional. The synthetic validator confirms the implementation is correct before applying it to real data.

Restricted (H₀): Y[t] = α + Σᵢ₌₁ᴸ aᵢ·Y[t−i] + ε Unrestricted (H₁): Y[t] = α + Σᵢ₌₁ᴸ aᵢ·Y[t−i] + Σᵢ₌₁ᴸ bᵢ·X[t−i] + ε F = ( (RSS_r − RSS_u) / L ) / ( RSS_u / (T − 2L − 1) ) p-value from F(L, T−2L−1) where T = n − L
A — True Signal
Validates sensitivity. Cause→effect injected at known lag — the test must recover it. If this fails, the implementation is underpowered or the series are too short.
B — Null
Validates specificity. Independent series — the test must not fire. With L lags × α=0.05, ≈0.25 false positives expected by chance across 5 lags.
C — High Noise
Stress test at noise_ratio=2.0. Signal may not survive — WARN is acceptable and documents the power limit at this sample size.

The F-test assumes both series are stationary. CAMS Λ(t) and Seshat complexity indices are likely non-stationary (I(1) — unit roots are common in social time series). Use granger_stationary_safe() on real data: it ADF-gates and auto-differences until both series pass, then passes them to the Granger test. Results on ∆-series interpret causality at rates of change, not levels — document which in any publication.

granger_stationary_safe(df, var='seshat_var', max_lag=5) → applies diff() up to 2× until ADF passes → records _diffs_applied in returned dict → if diffs=1: interpret as "change in X predicts change in Y"

Python source: granger_synthetic_validator.py in KaliBond/wintermute. Depends on statsmodels, pandas, numpy. This in-browser implementation uses vanilla JS OLS + jStat F-distribution.