SumOfCoefficientsPrior#
- class cultivars.bayes.priors.SumOfCoefficientsPrior(*, tightness=1.0)[source]
Bases:
_PriorThe no-cointegration dummies of Doan, Litterman & Sims.
One artificial observation per variable, each saying that a series sitting at its pre-sample average forever should stay there. For variable \(j\) with pre-sample mean \(\bar y_j\) and tightness \(\mu\), the row has target \(\mu \bar y_j\) in column \(j\), the same value in variable \(j\)’s column at every lag of the design, and zeros in the deterministic and exogenous columns:
\[\underbrace{\mu\, \bar y_j\, e_j^\top}_{\text{target}} \;=\; \underbrace{\bigl[\,0_{n_{\text{det}}},\; \mu\, \bar y_j\, e_j^\top,\; \ldots,\; \mu\, \bar y_j\, e_j^\top,\; 0_{k_{\text{exog}}}\bigr]}_{\text{design row}}\; B + \text{error}.\]Regressing target on design then says \(\sum_l A_{l,jj} = 1\) and \(\sum_l A_{l,ij} = 0\) for \(i \ne j\), with weight \(\mu\) relative to a data row. That is a restriction on the sum of a variable’s lag coefficients, which no diagonal variance can state, and which therefore enters as rows rather than as moments. At the limit \(\mu \to \infty\) it pushes every equation toward a unit root with no cross-variable error correction, which is why the literature reads it as a no-cointegration prior, and why it should be loosened when the system is believed to cointegrate.
This is the standalone, composable form of what
MinnesotaPrior.sum_of_coefficientsembeds: written separately it can be stacked onto the conjugate prior with+, given its own tightness, and carried into the hierarchical layer as its own hyperparameter, which is how Giannone, Lenza, and Primiceri (2015) treat it. Alone it is improper – every coefficient variance is infinite – so it is a component, not a prior: the conjugate model refuses it by itself and acceptsNormalInverseWishartPrior() + SumOfCoefficientsPrior().- Variables:
tightness – How hard the restriction binds; larger is tighter, matching the package’s \(\lambda_5\) convention. The Giannone-Lenza-Primiceri \(\mu\) is its reciprocal.
- Raises:
SpecificationError – If
tightnessis not positive, raised whendummy_observations()is first called, which is the model’s fit.
Note
The rows are scaled by the pre-sample means, so on data with a mean near zero – demeaned series, or growth rates – the rows carry almost no weight whatever
tightnessis, and the restriction is effectively absent. That is correct behaviour (there is no level to hold), but it means the hyperparameter’s effect depends on the data’s units. The deterministic columns of the row are zero, so the restriction is on the lag sum alone and says nothing about the intercept; theDummyInitialObservationPrioris the component that ties the intercept to the pre-sample mean.See also
DummyInitialObservationPrior– the companion row, one for the whole system, allowing cointegration.NormalInverseWishartPrior– the proper base prior this component is stacked onto.MinnesotaPrior.sum_of_coefficients– the same rows carried inside the Minnesota prior for the non-conjugate paths.
References
Doan, T., Litterman, R., & Sims, C. (1984). Forecasting and conditional projection using realistic prior distributions. Econometric Reviews, 3(1), 1-100.
Sims, C. A., & Zha, T. (1998). Bayesian methods for dynamic multivariate models. International Economic Review, 39(4), 949-968.
Giannone, D., Lenza, M., & Primiceri, G. E. (2015). Prior selection for vector autoregressions. Review of Economics and Statistics, 97(2), 436-451.
Example
Two variables at two lags, pre-sample means ten and twenty, at half weight; the design row repeats the target in the variable’s own column at each lag and is zero in the leading intercept column:
>>> import numpy as np >>> from cultivars.bayes.priors import PriorContext >>> ctx = PriorContext( ... k_endog=2, order=2, scales=np.ones(2), ... presample_mean=np.array([10.0, 20.0]), ... ) >>> target, design = SumOfCoefficientsPrior(tightness=0.5).dummy_observations(ctx) >>> target array([[ 5., 0.], [ 0., 10.]]) >>> design array([[ 0., 5., 0., 5., 0.], [ 0., 0., 10., 0., 10.]])
Stacked onto the conjugate prior, the rows enter the exact update and are counted as dummy observations on the result:
>>> from cultivars.multivariate.large_dim.bayesian import BVAR >>> rng = np.random.default_rng(0) >>> y = np.zeros((120, 2)) >>> for t in range(1, 120): ... y[t] = 0.7 * y[t - 1] + rng.standard_normal(2) >>> prior = NormalInverseWishartPrior() + SumOfCoefficientsPrior(tightness=2.0) >>> res = BVAR(y, order=1, prior=prior).fit(n_draws=100, seed=0) >>> res.prior_label, res.n_dummy ('niw(l1=0.2, l3=1, l4=100) + soc(2)', 2)
Alone it is improper and the conjugate model refuses it:
>>> BVAR(y, order=1, prior=SumOfCoefficientsPrior()).fit(n_draws=10) ... Traceback (most recent call last): ... cultivars.exceptions.SpecificationError: the prior leaves some coefficient variances ...