Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ _Avoid_: "order of differencing" for `d_max` — `d_max` is the maximum across t
The number of independent long-run relationships among integrated series, from the Johansen procedure (`johansen_test`). Both sequential tests are reported — `rank_trace` and `rank_max_eigen` — and `rank` is `rank_trace` by documented convention. Decisions rest on **critical values, not p-values** (MacKinnon-Haug-Michelis 1996 tables, as vendored by statsmodels), which is why `alpha` is restricted to 0.10 / 0.05 / 0.01. Rank ≥ 1 means differencing every series discards the long-run relationship. A vector error-correction model (VECM) is **out of scope**; the recommended response is a VAR in levels (the Sims–Stock–Watson stance; the Minnesota prior already shrinks toward random walks).
_Avoid_: "number of cointegrating vectors" in API surface (fine in prose); "cointegration test" without saying which statistic, since trace and max-eigen can disagree.

**Granger causality**:
Conditional predictive precedence: the past of one variable improves the prediction of another beyond that other's own past, *within the fitted system of variables*. Reported by `FittedVAR.granger_causality(cause, effect)` as the posterior of the strength norm `‖b‖` over the tested lags of the cause in the effect's equation, with the per-lag posteriors alongside. Ordered and directional — the two orderings are separate queries with unrelated answers. Reduced-form: no identification scheme is involved, and `B` is time-invariant under every volatility process, so it needs no `at`.
_Avoid_: "X causes Y" for a Granger result, and "causal effect" — the finding is about information sets, not interventions; an omitted common driver manufactures it. Reserve "effect" for identified structural objects (IRFs, counterfactuals).

**ROPE (region of practical equivalence)**:
The magnitude below which the analyst declares a relationship practically negligible, supplied as `rope=` and echoed on the result. `p_rope = P(‖b‖ < rope | data)` is the only probability statement the Granger surface makes. There is deliberately no default: the threshold is the analyst's judgement, and it travels with the number that came from it. Without a `rope` the result reports the distribution and `p_rope` is `None`.
_Avoid_: "probability of no causality" / "probability the coefficient is zero" for `p_rope` — under continuous coefficient priors `P(b = 0) = 0` before and after the data, and an edge-inclusion probability needs a spike-and-slab prior Impulso does not fit (see ADR-0010). Also avoid bare "threshold" — the ROPE is on the magnitude, not on a p-value.

**Toda-Yamamoto augmentation**:
Fitting a VAR in levels with `p + d` lags and testing only the first `p`, which restores standard Granger inference on possibly-integrated series without differencing (Toda & Yamamoto 1995). `n_lags_tested` and `n_lags_fitted` are separate fields on `GrangerCausalityResult` — the test lag order is never silently changed to match the fit, and the augmented lags never appear in `summary()`. `toda_yamamoto` consumes the frozen `IntegrationOrderResult` contract (`order` / `d_max` / `inconclusive`): it **refuses to run** when `inconclusive` is non-empty, because `d_max` is then a floor and an under-augmented test is invalid rather than imprecise. `augmentation_source` records the provenance — `"user"` for an explicit `d=` (which skips the diagnostics entirely), `"integration_order"` when they were consulted, including when they returned `d_max = 0`.
_Avoid_: "extra lags" without saying they are untested; "corrected for non-stationarity" — nothing is corrected, the asymptotics are restored.

## Relationships

- A **VAR** carries one **prior**, one **volatility process**, and one **observation error distribution**.
Expand All @@ -148,6 +160,8 @@ _Avoid_: "number of cointegrating vectors" in API surface (fine in prose); "coin
- A **VAR** is estimated by NUTS; a **ConjugateVAR** is estimated analytically with a Metropolis step on hyperparameters. Both produce a **FittedVAR**.
- A **stationarity pretest** consumes `VARData` (endogenous block only), a DataFrame, or a Series, and produces a result object — never a modified dataset and never a specification. It sits *beside* the pipeline, not in it: nothing downstream of `VAR.fit()` reads its output.
- **Integration order** feeds **cointegration rank**: the Johansen test is only meaningful for series that are individually integrated, and it is conditioned on a lag order (`k_ar_diff = p - 1`) that `select_lag_order` supplies.
- A **FittedVAR** answers **Granger causality** queries on its own — the coefficients are reduced-form and time-invariant, so neither an identification scheme nor an `at` is involved. The query never refits: `test_lags` selects which of the already-fitted lags are tested.
- **Toda-Yamamoto augmentation** consumes an **integration order**: `toda_yamamoto` reads `d_max` (after checking `inconclusive`), fits `p + d` lags with a **ConjugateVAR**, and produces the same `GrangerCausalityResult` the `FittedVAR` query does — which is why the manual route (`VAR(lags=p + d).fit(...)` then `granger_causality(..., test_lags=p)`) is equivalent and is the documented escape hatch for exogenous regressors, NUTS, and stochastic volatility.
- A **ConjugateVAR** carries an **NIW prior** and optionally a **deterministic volatility break**; a **VAR** carries a **MinnesotaPrior** and a **PyMC volatility process** (`PyMCVolatilityProcess`, the `build_pymc_latent` extension of the `VolatilityProcess` query surface). Each estimator's fields accept only its compatible components, enforced by types + validators rather than a builder.

## Example dialogue
Expand Down
19 changes: 19 additions & 0 deletions docs/adr/0010-granger-strength-is-a-magnitude-statement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Granger-causal strength is a magnitude statement, not a test

Impulso reports Granger causality as the posterior of `‖b‖`, the Euclidean norm over the tested lags of the cause's coefficients in the effect's equation, evaluated draw by draw — median, highest-density interval (HDI), and the per-lag posteriors alongside it. The only probability statement attached is `p_rope = P(‖b‖ < rope | data)`, against a region of practical equivalence (ROPE) the analyst supplies (Kruschke's formulation). With no `rope` the result reports the distribution and nothing else. There is no default ROPE, and no probability of *no* causality anywhere in the API.

## Considered options

- **A Wald-style quadratic form as the headline** — `b'V⁻¹b` over the posterior, the Bayesian echo of the classical test — rejected: dividing through by the posterior covariance conflates "the effect is small" with "the effect is precisely estimated", which is exactly the distinction a posterior is for. A large quadratic can mean a large effect or a tight posterior around a small one, and the reader cannot tell which. It remains a reasonable *supplementary* statistic and may arrive later; it is not the headline.
- **A fixed default epsilon for the ROPE** (0.05, say, in standardised units) — rejected: any default is arbitrary, and a default is precisely what makes a threshold invisible. That the analyst had to name a magnitude, and that the magnitude travels with the number in `GrangerCausalityResult.rope`, is the honesty of the statement.
- **Spike-and-slab / edge-inclusion priors, to report `P(b = 0 | data)` properly** — rejected for this slice, not on principle. It is the only construction that makes the quantity users actually ask for well defined, but it is a different prior and a different estimator, not a post-processing step. Reporting an edge-inclusion probability without fitting one would be a fabrication, and the issue explicitly rules it out.
- **Savage-Dickey density ratio at `b = 0`** — rejected: it gives a Bayes factor for the point null under a continuous prior, but its value depends on the prior density at zero, which under the Minnesota prior is a shrinkage choice rather than a considered statement about the null. It would export the tightness parameter into what reads as evidence.
- **Per-lag summaries only, no aggregate** — rejected: a `p`-lag block needs a single number to be comparable across pairs and models, and users would compute one anyway (usually badly, from the per-lag medians rather than draw by draw).

## Consequences

- `p_rope` is `None` unless a `rope` is given. Downstream code must handle that; there is no substitute value.
- The class docstring carries the full statement of what `p_rope` is not, and the how-to page has a section devoted to it. This is documentation load the feature cannot shed: the wrong reading of the number is the natural one.
- The norm is scale-dependent, so `standardize=True` is the default and the applied factor is recorded on the result. Under Toda-Yamamoto augmentation the fit is in levels and the sample standard deviations carry the series' trends, so standardised magnitudes compare within a fit rather than across fits — documented on the result and in the how-to.
- Toda-Yamamoto metadata is kept separable rather than collapsed: `n_lags_tested` and `n_lags_fitted` are distinct fields, and `augmentation_source` records whether the augmentation came from the analyst or from the integration-order diagnostics (including when those returned `d_max = 0`). A procedure that silently tested the lags it happened to fit would be a different, invalid test.
- `toda_yamamoto` refuses to run on inconclusive diagnostics instead of warning. `d_max` is a floor whenever `IntegrationOrderResult.inconclusive` is non-empty, and an under-augmented test is invalid rather than imprecise, so there is nothing useful to return.
5 changes: 4 additions & 1 deletion docs/how-to/climate-pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ them the way you would record the lag order:
without its `alpha` and its `regression` is not reproducible.
- **`d_max`**, the highest integration order in the system. It is the
augmentation term a Toda-Yamamoto style procedure needs, and it is far
easier to carry forward now than to reconstruct later.
easier to carry forward now than to reconstruct later. See [Granger
causality and Toda-Yamamoto](granger-causality.md) for the procedure that
consumes it — including why it refuses to run when `inconclusive` is
non-empty.
- The **cointegration rank** and the lag order it was conditioned on. The
rank is not invariant to `k_ar_diff`.
Loading
Loading