feat(granger): Bayesian Granger causality with Toda-Yamamoto robustness - #226
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #226 +/- ##
=======================================
+ Coverage 95.2% 95.4% +0.1%
=======================================
Files 45 46 +1
Lines 3317 3431 +114
Branches 420 437 +17
=======================================
+ Hits 3161 3275 +114
Misses 111 111
Partials 45 45 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
thomaspinder
force-pushed
the
feat/140-stationarity-diagnostics
branch
from
July 29, 2026 13:58
56c9fdc to
3f9ac15
Compare
thomaspinder
changed the base branch from
feat/140-stationarity-diagnostics
to
main
July 29, 2026 20:55
thomaspinder
force-pushed
the
feat/154-granger-toda-yamamoto
branch
from
July 29, 2026 21:38
5ecae59 to
fb09b5e
Compare
`FittedVAR.granger_causality(cause, effect)` reports the posterior of the Euclidean norm of the tested lag coefficients of `cause` in the `effect` equation, plus the per-lag posteriors behind it. A magnitude, not a test statistic: nothing divides through by the posterior covariance, so a small effect stays distinguishable from an imprecise one. An optional `rope` adds `p_rope = P(||b|| < rope | data)` — practical negligibility at a threshold the analyst names, deliberately with no default. It is not the probability of no causality: `b = 0` has probability zero under continuous coefficient priors, so that quantity needs a spike-and-slab prior Impulso does not fit. The `GrangerCausalityResult` docstring carries the full statement. The engine lives in a new private `_granger.py` (the `conditional_forecast` delegation precedent), and its extraction is pinned by tests against a hand-built posterior with every entry distinct, cross-checked against `lag_matrices` so the lag-major layout cannot drift between the two consumers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
`toda_yamamoto(data, cause, effect)` runs the Toda-Yamamoto (1995) procedure for possibly-integrated systems: fit the VAR in levels with `p + d` lags, test only the first `p`. The augmented lags are never tested and the reported test lag order is never silently changed to match the fit — the result carries `n_lags_tested` and `n_lags_fitted` separately, with `augmentation` and `augmentation_source` recording where the extra lags came from. `d` comes from `integration_order` unless the caller pins it. Honouring the consumer contract frozen by #140/#197: when the diagnostics leave anything in `inconclusive`, `d_max` is a floor rather than a finding, so this refuses to run — naming the variables, pointing at `.summary()` and at the `d=` override — rather than under-augmenting silently. An explicit `d` skips the diagnostics entirely, so the route also works without statsmodels installed. The fit uses the closed-form conjugate estimator, since augmentation inflates the lag order; exogenous regressors it cannot consume are refused with a message naming the manual `VAR(lags=p + d)` route. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
New how-to covering the fitted-model query, how to read `summary()`, why there is no probability of no causality, the Toda-Yamamoto happy path plus its refusal and the `d=` override, the manual NUTS route, and a worked carbon-dioxide/temperature example with an explicit statement of what it does and does not license (predictive precedence not intervention; omitted forcings; bidirectional physical coupling; annual aggregation). Cross-links with the climate-pitfalls page both ways. New reference page for `toda_yamamoto`; `GrangerCausalityResult` added to the results page. CONTEXT gains three terms — Granger causality, ROPE, Toda-Yamamoto augmentation — plus the two relationships that place the query on `FittedVAR` and wire the augmentation to the integration-order contract. ADR-0010 records the decision: the norm of the tested coefficients as the headline with an analyst-supplied ROPE, against the rejected alternatives (a Wald quadratic headline, a fixed default epsilon, spike-and-slab, and Savage-Dickey). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
A transposed posterior must be realigned by its canonical dim names, and an unlabelled one must fall back to the positional (chain, draw, var, coeff) convention — the same contract `dynamic_multiplier` relies on. Neither path was exercised. Takes `_granger.py` to full branch coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
…event The honesty paragraph's one imprecise sentence read the probability as an assertion of practical negligibility; a p_rope of 0.02 says no such thing. Refs #154 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
… columns) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
thomaspinder
force-pushed
the
feat/154-granger-toda-yamamoto
branch
from
July 29, 2026 21:57
fb09b5e to
f69edaf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on
feat/140-stationarity-diagnostics(#197) — it consumes the frozenIntegrationOrderResult.order / .d_max / .inconclusivecontract pinned on the issue.FittedVAR.granger_causality(cause, effect, *, rope=None, standardize=True, test_lags=None): labelled posterior causal-strength summaries — per-lag coefficient posteriors and the headline posterior of the joint coefficient norm with median/HDI, standardised bysd(cause)/sd(effect)by default. The probability statement isp_rope = P(‖b‖ < rope | data)with a user-supplied region of practical equivalence and no default — the docstring states plainly whyP(no causality)is unavailable under continuous priors and what would be needed to get one (spike-and-slab), per the issue's explicit honesty requirement. ADR-0010 records the stance (including why a Savage-Dickey Bayes factor was rejected: it exports the prior tightness into what reads as evidence).toda_yamamoto(data, cause, effect, ...): lag-augmented mode — fitsConjugateVAR(lags=p + d)(closed-form, milliseconds) but tests only the first p lags;n_lags_testedvsn_lags_fittedmetadata makes the split explicit and the augmented block structurally cannot enter the statistic (single result-construction site). The integration-order decision is consumed per the contract: non-emptyinconclusive→ hard refusal naming the variables and explaining floor-vs-finding, with an explicitd=integer as the only override (recordedaugmentation_source="user"); injectableintegration_order_result=; works without statsmodels whend=is given.p_ropelicenses; reference page; CONTEXT.md terms; ADR-0010.Closes #154
Review
Planned by a Fable-tier planner; independently reviewed (verdict: approve). The reviewer re-derived the lag-major indexing and cross-checked it with an out-of-suite simulation, audited the honesty paragraph claim-by-claim against the implementation, and verified the refusal wording against
_stationarity's actualinconclusivesemantics. Its one P2 (a sentence conflating the event with its probability) is fixed in the final commit.Note: the beartype forward-ref failure on
FittedVARmethods that surfaced during testing is pre-existing and already fixed on main by #209 — it resolves when this stack rebases.Tests
50 tests, 100% branch coverage on the engine: exact-index pins (including transposed-dims realignment and a
lag_matricescross-check), directionality (true edgep_rope0.000 vs null edge 0.968), a 40-query null-calibration sweep, the full TY contract (refusal/override/injection/end-to-end on an I(1) system with statsmodels), and a slow NUTS smoke. Branch fast suite: 641 passed.ruff/ty/docs build clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV