statspai.timeseries¶
timeseries ¶
Time series methods: models for causal inference contexts and forecasting.
Provides VAR (vector autoregression), structural break tests, Granger causality, unit-root tests and cointegration analysis; and a forecasting toolkit -- ARIMA, exponential smoothing (ETS), benchmark methods, STL decomposition, forecast accuracy, rolling-origin cross-validation and hierarchical reconciliation.
StateDependentLPResult
dataclass
¶
Bases: ResultProtocolMixin
Regime-specific impulse responses from a state-dependent projection.
*_state1 is the regime F = 1 and *_state0 the regime
F = 0; diff = irf_state1 - irf_state0 with its standard error
and two-sided p-value (missing for the Cholesky projection, whose
bands are not standard errors).
ARDLResult ¶
Bases: ResultProtocolMixin
A fitted AR(p) or ADL(p, q) forecasting regression.
Attributes:
| Name | Type | Description |
|---|---|---|
model |
EconometricResults
|
The OLS fit. Pass it to |
params, std_errors |
Series
|
Coefficients and standard errors of |
lags |
int
|
Autoregressive order |
x_lags |
dict
|
Lag order of each additional regressor. |
omitted, omitted_x |
list of str
|
Terms left out of the fit because of collinearity, and the
regressors that lost every term that way (they are not in |
nobs |
int
|
Observations in the estimation sample. |
ser |
float
|
Standard error of the regression, |
bic, aic |
float
|
|
ic_table |
DataFrame or None
|
The criteria for every candidate order, when |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y = np.zeros(200)
>>> for t in range(1, 200):
... y[t] = 1.0 + 0.5 * y[t - 1] + rng.normal()
>>> res = sp.ardl(pd.DataFrame({"y": y}), "y", lags=1)
>>> type(res).__name__
'ARDLResult'
>>> list(res.params.index)
['Intercept', 'y_L1']
>>> res.forecast().shape
(1, 4)
forecast ¶
forecast(alpha: Optional[float] = None, *, steps: int = 1, exog: Optional[DataFrame] = None) -> DataFrame
Forecasts of the periods after the last estimation observation.
Beyond one step the forecast is dynamic: each forecast of y
stands in for the lag the next one needs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha
|
float
|
Level of the interval. Default: the model's. |
None
|
steps
|
int
|
Number of periods ahead. |
1
|
exog
|
DataFrame
|
Future values of the |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per period with |
Notes
The interval is forecast +/- z * RMSFE. At one step the RMSFE
is the standard error of the regression. At step h it is that
times sqrt(1 + psi_1**2 + ... + psi_{h-1}**2), where psi
are the moving-average weights of the autoregressive part. It
ignores the estimation error in the coefficients, treats the
future x as known and assumes normal errors; a pseudo
out-of-sample RMSFE from :meth:poos is the more honest width at
one step.
long_run ¶
Long-run effect of each x on y.
A permanent unit rise in x moves y in the end by the sum of
the coefficients on x and its lags divided by one minus the sum
of the autoregressive coefficients. In the geometric-lag model
y[t] = a + b x[t] + lam y[t-1] this is b / (1 - lam).
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per |
Notes
The ratio has no finite-sample moments and its normal approximation is poor when the sum of the autoregressive coefficients is near one. With that sum at or above one there is no long-run level to converge to and the method refuses.
granger ¶
F test that every lag of x has a zero coefficient.
The Granger-causality statistic: whether lags of x help predict
y given the lags of y. It uses the model's own covariance
estimator, so it is heteroskedasticity- or autocorrelation-robust
when the fit is. "Causality" here means predictive content.
poos ¶
Pseudo out-of-sample one-step forecasts.
For each date from start to the end of the data the model is
re-estimated on observations before that date and used to forecast
it, as a forecaster standing at the previous date would have done.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
label or int
|
First date to forecast: a label of the data's index (or of the
|
required |
window
|
('expanding', 'rolling')
|
|
"expanding"
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
ARIMAResult
dataclass
¶
Bases: ResultProtocolMixin
Fitted ARIMA(p,d,q) / SARIMAX model returned by :func:statspai.arima.
Carries the estimated params / se (indexed by parameter name),
information criteria (aic / bic / aicc), the log-likelihood,
residuals and fitted values, plus inference accessors
(:attr:tvalues, :attr:pvalues, :meth:conf_int) and a
.summary() / .forecast() interface.
Examples:
>>> import statspai as sp
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> n = 120
>>> y = np.zeros(n)
>>> for t in range(1, n):
... y[t] = 0.6 * y[t - 1] + rng.normal(0, 1)
>>> res = sp.arima(y, order=(1, 0, 0))
>>> type(res).__name__
'ARIMAResult'
>>> bool(np.isfinite(res.aic))
True
>>> list(res.params.index)
['const', 'ar.L1', 'sigma2']
n_arma_params
property
¶
Number of estimated ARMA coefficients, p + q + P + Q: the
degrees of freedom a portmanteau test of the residuals gives up.
conf_int ¶
Confidence intervals for each parameter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha
|
float
|
|
0.05
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Indexed by parameter name with |
forecast ¶
forecast(horizon: int = 10, alpha: float = 0.05, *, level: Optional[Any] = None, exog: Optional[Any] = None, dof_adjust: bool = False) -> DataFrame
Forecasts with prediction intervals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
horizon
|
int
|
Number of periods ahead. |
10
|
alpha
|
float
|
One minus the coverage of the interval, used when |
0.05
|
level
|
float or sequence of float
|
Coverage in percent, e.g. |
None
|
exog
|
array - like or DataFrame
|
Values of the regressors over the forecast periods, one row per period, columns in the order of the fit (a DataFrame is matched by column name). Required when the model has regressors: the forecast is conditional on them. |
None
|
dof_adjust
|
bool
|
Scale the innovation variance by |
False
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Indexed by the forecast periods when the series had a date or period index. |
Notes
The intervals treat the coefficients, and the future regressors, as known.
BaggedForecastResult
dataclass
¶
Bases: ResultProtocolMixin
Bagged forecasts returned by :func:statspai.bagged_forecast.
Attributes:
| Name | Type | Description |
|---|---|---|
forecast |
DataFrame
|
|
members |
DataFrame
|
The forecast from each bootstrapped series, one column each. |
forecaster |
str
|
|
n_boot |
int
|
|
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(1)
>>> y = 100 + np.cumsum(rng.normal(0.5, 1.0, 60))
>>> bag = sp.bagged_forecast(y, "drift", horizon=4, n_boot=20)
>>> bag.forecast.shape, bag.members.shape
((4, 3), (4, 20))
BeveridgeNelsonResult
dataclass
¶
Bases: ResultProtocolMixin
Decomposition returned by :func:statspai.beveridge_nelson.
Attributes:
| Name | Type | Description |
|---|---|---|
trend, cycle |
Series
|
Permanent and transitory components, aligned with the input;
|
drift |
float
|
Mean of the first difference, |
order |
int
|
Autoregressive order of the model for the first difference. |
ma_order |
int
|
Moving-average order; zero for an autoregression. |
intercept |
float
|
Constant of the autoregressive form, |
ar_coefs, ma_coefs |
ndarray
|
|
sigma2 |
float
|
Innovation variance: |
long_run_multiplier |
float
|
|
variance_ratio |
float
|
Variance of the change in the trend over the variance of the
change in the series implied by the fitted model,
|
residuals |
Series
|
Innovations of the model, aligned with the input: least-squares
residuals under |
selection |
DataFrame or None
|
Information criteria by order, when the order was chosen. |
n_obs |
int
|
Number of first differences the model was fitted to. |
method |
str
|
|
loglik |
float or None
|
Log-likelihood under |
Examples:
BVARResult
dataclass
¶
Bases: ResultProtocolMixin
Posterior summary of a Bayesian VAR with Minnesota prior.
Produced by :func:bvar. Holds the posterior-mean coefficient matrix
and residual covariance, and exposes .forecast(), .irf() and
.summary().
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> T = 120
>>> y1 = np.zeros(T); y2 = np.zeros(T)
>>> for t in range(1, T):
... y1[t] = 0.5 * y1[t - 1] + 0.2 * y2[t - 1] + rng.normal()
... y2[t] = 0.3 * y2[t - 1] + 0.1 * y1[t - 1] + rng.normal()
>>> df = pd.DataFrame({"gdp": y1, "inflation": y2})
>>> res = sp.bvar(df, lags=2)
>>> type(res).__name__
'BVARResult'
>>> res.coef.shape
(5, 2)
>>> res.lags
2
>>> isinstance(res.summary(), str)
True
irf ¶
Orthogonalised impulse responses (Cholesky decomposition).
credible_interval ¶
Posterior credible interval for every coefficient.
Returns (lower, upper) matrices the same shape as coef. With
the error covariance fixed at Sigma0 the coefficient posterior is
exactly normal; coef_sd holds its marginal standard deviations.
CointegrationResult ¶
Bases: ResultProtocolMixin
Results from cointegration test.
Produced by :func:engle_granger (and the Johansen routine). Exposes
the test statistic(s), critical values, estimated cointegration rank
and a formatted .summary().
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> T = 200
>>> x = np.cumsum(rng.normal(size=T)) # random walk
>>> y = 2.0 * x + rng.normal(size=T) # cointegrated with x
>>> df = pd.DataFrame({"y": y, "x": x})
>>> res = sp.engle_granger(df, variables=["y", "x"])
>>> type(res).__name__
'CointegrationResult'
>>> res.test_type
'Engle-Granger'
>>> res.n_vars
2
>>> isinstance(res.summary(), str)
True
References
[@engle1987integration]
DLMResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:dlm.
Attributes:
| Name | Type | Description |
|---|---|---|
variances |
DataFrame
|
The observation variance ( |
smoothed |
DataFrame
|
The coefficient paths given the whole sample, one column per
term, with |
filtered |
DataFrame
|
The coefficient paths given the data up to each date. |
fitted |
Series
|
One-step-ahead forecasts of |
loglik |
float
|
Log likelihood at the variances (maximum likelihood), or at their posterior means (Gibbs). |
params |
Series
|
The smoothed coefficients at the last date. |
draws |
DataFrame or None
|
Gibbs draws of the variances. |
method, n_obs, level |
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> T = 150
>>> x = rng.normal(size=T)
>>> slope = 1 + np.cumsum(0.1 * rng.normal(size=T))
>>> df = pd.DataFrame({"x": x, "y": 0.5 + slope * x + 0.3 * rng.normal(size=T)})
>>> fit = sp.dlm("y ~ x", df)
>>> isinstance(fit, sp.DLMResult)
True
>>> list(fit.smoothed.columns)[:2]
['Intercept', 'x']
>>> fit.forecast(2, pd.DataFrame({"x": [0.0, 1.0]})).shape
(2, 3)
forecast ¶
Forecast y beyond the sample.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
steps
|
int
|
Horizon. |
1
|
data
|
DataFrame
|
Regressor values for the forecast dates ( |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
plot ¶
Smoothed coefficient paths with their intervals.
TSCVResult
dataclass
¶
Bases: ResultProtocolMixin
Rolling-origin forecast errors returned by :func:statspai.tscv.
Attributes:
| Name | Type | Description |
|---|---|---|
errors |
DataFrame
|
|
forecasts, actuals |
DataFrame
|
Laid out like |
failures |
list of (origin, message)
|
Origins where the forecaster raised. |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y = 100 + np.cumsum(rng.normal(0.2, 1.0, 60))
>>> cv = sp.tscv(y, "drift", horizon=2, initial=20)
>>> cv.errors.shape
(40, 2)
>>> list(cv.accuracy().columns)
['RMSE', 'MAE', 'MAPE', 'n']
GARCHResult
dataclass
¶
Bases: ResultProtocolMixin
Fitted GARCH(p,q) model returned by :func:garch.
Holds the conditional-variance parameters, the volatility path, and
standardised residuals, plus :meth:forecast for multi-step variance.
Examples:
>>> import statspai as sp
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> T = 400
>>> eps = np.zeros(T)
>>> s2 = np.ones(T)
>>> omega, a1, b1 = 0.05, 0.1, 0.85
>>> for t in range(1, T):
... s2[t] = omega + a1 * eps[t - 1] ** 2 + b1 * s2[t - 1]
... eps[t] = np.sqrt(s2[t]) * rng.standard_normal()
>>> res = sp.garch(eps, p=1, q=1)
>>> bool(res.persistence < 1.0) # alpha + beta < 1 => stationary
True
>>> res.forecast(horizon=3).shape
(3,)
persistence
property
¶
Sum of the coefficients that carry the variance forward:
sum(alpha) + sum(beta), plus half the threshold terms for
'gjr' (a symmetric innovation is negative half the time);
sum(beta) for 'egarch'.
forecast ¶
Multi-step ahead variance forecast (analytic recursion).
E[eps^2_{T+h}] = sigma^2_{T+h|T} for h >= 1, so future squared shocks are replaced by their forecasts; every ARCH and GARCH lag is used.
forecast_mean ¶
Multi-step forecast of the series: the constant mean plus the
autoregressive forecast of the disturbance (ar=).
value_at_risk ¶
One-step-ahead value at risk: the alpha quantile of the
forecast distribution of the next observation.
mu_{T+1} + sigma_{T+1} * q_alpha with q_alpha the quantile
of the standardised innovation (standard normal, or Student t
scaled to unit variance). A return below it has probability
alpha under the fitted model.
ITSResult
dataclass
¶
Bases: ResultProtocolMixin
Result container for :func:its.
Examples:
>>> import statspai as sp
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> t = np.arange(60)
>>> y = 1 + 0.1 * t + (t >= 30) * 3.0 + rng.normal(scale=0.5, size=60)
>>> df = pd.DataFrame({"y": y, "t": t})
>>> res = sp.its(df, y="y", time="t", intervention=30)
>>> type(res).__name__
'ITSResult'
>>> float(res.level_change) > 0
True
JohansenLRTest
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:johansen_lrtest.
Attributes:
| Name | Type | Description |
|---|---|---|
statistic, df, pvalue |
(float, int, float)
|
Likelihood-ratio statistic and its chi-squared reference. |
hypothesis |
str
|
|
rank |
int
|
Cointegration rank held fixed under both hypotheses. |
eigenvalues, eigenvalues_restricted |
ndarray
|
Squared canonical correlations without and with the restriction. |
beta, loading |
DataFrame
|
Restricted estimates (columns |
n_used |
int
|
Observations entering the statistic. |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> trend = np.cumsum(rng.normal(size=300))
>>> df = pd.DataFrame({
... "c": trend + rng.normal(size=300),
... "y": trend + rng.normal(size=300),
... })
>>> test = sp.johansen_lrtest(df, rank=1, beta_known=[1, -1])
>>> type(test).__name__
'JohansenLRTest'
>>> list(test.beta.index)
['c', 'y']
LocalProjectionsResult
dataclass
¶
Bases: ResultProtocolMixin
Impulse-response container returned by :func:local_projections.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(3)
>>> T = 200
>>> shock = rng.normal(size=T)
>>> y = np.zeros(T)
>>> for t in range(1, T):
... y[t] = 0.5 * y[t - 1] + 0.8 * shock[t] + rng.normal(0, 0.5)
>>> df = pd.DataFrame({"y": y, "shock": shock})
>>> res = sp.local_projections(df, outcome="y", shock="shock", horizons=8)
>>> type(res).__name__
'LocalProjectionsResult'
>>> len(res.horizons) == 9
True
>>> bool(res.irf[0] > 0)
True
LongRunVariance
dataclass
¶
Bases: ResultProtocolMixin
Long-run variance returned by :func:statspai.lrvar.
Attributes:
| Name | Type | Description |
|---|---|---|
lrvar |
float or ndarray
|
The long-run variance; a |
var_mean |
float or ndarray
|
Variance of the sample mean, |
se_mean |
float or ndarray
|
Its square root (of the diagonal, for several series). |
variance |
float or ndarray
|
The ordinary variance |
kernel |
str
|
|
bandwidth |
float
|
Scale |
bandwidth_rule |
str
|
|
prewhite |
int
|
|
adjust |
bool
|
|
n_obs |
int
|
|
names |
list of str
|
|
Examples:
MarkovSwitchingResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:mswitch.
Attributes:
| Name | Type | Description |
|---|---|---|
params |
DataFrame
|
One row per coefficient: |
transition |
DataFrame
|
|
transition_table |
DataFrame
|
The same probabilities with delta-method standard errors and intervals built on the logit scale. |
durations |
DataFrame
|
Expected duration |
filtered, smoothed, predicted |
DataFrame
|
Probability of each state given the data up to the date, the whole sample (Kim smoother), and the previous date. |
fitted, resid |
Series
|
One-step predictions |
theta, vcov |
(Series, DataFrame)
|
Estimates and covariance on the estimation scale (log standard
deviations, transition logits |
loglik, aic, bic, hqic, n_obs, n_params, states, model, ar, vce |
|
|
converged |
bool
|
|
starts |
DataFrame
|
Log likelihood reached from each starting value; |
model_info |
dict
|
|
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(1)
>>> s = np.zeros(300, dtype=int)
>>> for t in range(1, 300):
... stay = 0.95 if s[t - 1] == 0 else 0.9
... s[t] = s[t - 1] if rng.uniform() < stay else 1 - s[t - 1]
>>> y = np.where(s == 0, -1.0, 2.0) + rng.normal(size=300)
>>> fit = sp.mswitch(y, states=2, seed=0)
>>> type(fit).__name__
'MarkovSwitchingResult'
>>> list(fit.smoothed.columns)
['state1', 'state2']
plot ¶
Probability of each state over time ('smoothed'/'filtered').
forecast ¶
Forecasts of y and of the state probabilities.
Exact conditional expectations given the sample and the estimates;
available without exogenous regressors, for model='dr' with
ar=0 and for model='ar' with common AR coefficients.
MarkovSwitchingLRTest
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:mswitch_lrtest.
Attributes:
| Name | Type | Description |
|---|---|---|
statistic |
float
|
|
pvalue |
float
|
|
critical_values |
Series
|
Bootstrap critical values at 10%, 5% and 1%: the
|
reject |
bool
|
|
loglik_null, loglik_alt |
float
|
|
states, null_states, n_obs, reps, n_valid |
int
|
|
naive_df, naive_pvalue |
(int, float)
|
Difference in the number of parameters and the chi-squared tail
probability with it. Not a valid reference; for comparison only
(see the Notes of :func: |
replicates |
DataFrame
|
One row per simulated series: |
diagnostics |
dict
|
|
null_params |
Series
|
Estimates of the null model on the estimation scale; the series are simulated from them. |
fit_null |
MarkovSwitchingResult or None
|
|
fit_alt |
MarkovSwitchingResult
|
|
interpretation |
str
|
What the test does and does not establish. |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(1)
>>> s = np.zeros(120, dtype=int)
>>> for t in range(1, 120):
... s[t] = s[t - 1] if rng.uniform() < 0.95 else 1 - s[t - 1]
>>> y = np.where(s == 0, -1.5, 1.5) + rng.normal(size=120)
>>> test = sp.mswitch_lrtest(y, states=2, reps=19, starts=3, seed=0)
>>> type(test).__name__
'MarkovSwitchingLRTest'
>>> bool(test.reject)
True
Hierarchy
dataclass
¶
An aggregation structure returned by :func:statspai.hierarchy.
Attributes:
| Name | Type | Description |
|---|---|---|
Y |
DataFrame
|
Every series of the structure, aggregates first and the bottom
level last; rows are time periods, columns series identifiers
(the key values joined by |
S |
DataFrame
|
Summing matrix: one row per series, one column per bottom-level
series; |
tags |
dict
|
Level name (the key columns joined by |
Examples:
>>> import pandas as pd
>>> import statspai as sp
>>> df = pd.DataFrame({
... "country": "AU", "state": ["A", "A", "B", "B"],
... "t": [1, 2, 1, 2], "y": [1.0, 2.0, 3.0, 4.0]})
>>> h = sp.hierarchy(df, [["country"], ["country", "state"]], time="t", value="y")
>>> h.Y.columns.tolist()
['AU', 'AU/A', 'AU/B']
>>> h.S.values.tolist()
[[1.0, 1.0], [1.0, 0.0], [0.0, 1.0]]
ReconcileResult
dataclass
¶
Bases: ResultProtocolMixin
Reconciled forecasts returned by :func:statspai.reconcile.
Attributes:
| Name | Type | Description |
|---|---|---|
forecasts |
DataFrame
|
Coherent forecasts, laid out like the base forecasts. |
method |
str
|
|
G |
DataFrame
|
The matrix that maps base forecasts of all series to bottom-level
forecasts; |
weights |
ndarray or None
|
The matrix |
shrinkage |
float or None
|
Shrinkage intensity of |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> S = pd.DataFrame([[1, 1], [1, 0], [0, 1]], index=["T", "A", "B"],
... columns=["A", "B"], dtype=float)
>>> base = pd.DataFrame([[10.0, 4.0, 5.0]], columns=["T", "A", "B"])
>>> rec = sp.reconcile(base, S, method="ols")
>>> rec.forecasts.round(4).values.tolist()
[[9.6667, 4.3333, 5.3333]]
intervals ¶
Lower and upper bounds of the reconciled normal prediction
intervals, laid out like forecasts.
Available when sd= was given to :func:statspai.reconcile.
level is the coverage in percent.
SimpleForecastResult
dataclass
¶
Bases: ResultProtocolMixin
A fitted benchmark method returned by :func:statspai.simple_forecast.
Attributes:
| Name | Type | Description |
|---|---|---|
method |
{'mean', 'naive', 'snaive', 'drift'}
|
|
period |
int
|
Seasonal period of the seasonal naive method (1 otherwise). |
fitted_values, residuals |
ndarray
|
One-step fitted values and residuals; |
sigma |
float
|
Residual standard deviation, |
drift |
float or None
|
Average change per period (drift method). |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> y = np.array([10.0, 12.0, 11.0, 13.0, 14.0, 13.0, 15.0, 16.0])
>>> fit = sp.simple_forecast(y, "drift")
>>> round(fit.drift, 4)
0.8571
>>> fit.forecast(2, level=95).round(2)["forecast"].tolist()
[16.86, 17.71]
forecast ¶
forecast(horizon: int = 10, level: Levels = (80, 95), *, bootstrap: bool = False, n_paths: int = 5000, seed: Optional[int] = 0) -> DataFrame
Point forecasts and prediction intervals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
horizon
|
int
|
|
10
|
level
|
float or sequence of float
|
Coverage in percent. |
(80, 95)
|
bootstrap
|
bool
|
Take the intervals from future paths built by resampling the residuals instead of assuming them normal. |
False
|
n_paths
|
int
|
|
5000
|
seed
|
int or None
|
|
0
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
simulate ¶
simulate(horizon: int = 10, n_paths: int = 5, *, bootstrap: bool = True, seed: Optional[int] = 0) -> DataFrame
Future sample paths, one per column.
CumulativePeriodogramResult
dataclass
¶
Bases: ResultProtocolMixin
Bartlett's test returned by
:func:statspai.cumulative_periodogram_test.
Attributes:
| Name | Type | Description |
|---|---|---|
statistic |
float
|
|
pvalue |
float
|
|
n |
int
|
|
table |
DataFrame
|
|
alpha |
float
|
|
band |
float
|
Half-width of the |
Examples:
SpectrumResult
dataclass
¶
Bases: ResultProtocolMixin
Estimated spectral density returned by :func:statspai.periodogram.
Attributes:
| Name | Type | Description |
|---|---|---|
table |
DataFrame
|
One row per frequency: |
method |
{'raw', 'smoothed', 'ar'}
|
|
scale |
{'cycles', 'radians'}
|
|
df |
float or None
|
Equivalent degrees of freedom of each ordinate ( |
bandwidth |
float or None
|
Equivalent bandwidth of the smoothing kernel, in cycles per
observation ( |
n |
int
|
Observations used. |
order |
int or None
|
Autoregressive order ( |
ar_coef |
ndarray or None
|
Yule-Walker coefficients ( |
var_pred |
float or None
|
Innovation variance ( |
settings |
dict
|
|
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> res = sp.periodogram(rng.normal(size=120), method="smoothed", spans=5)
>>> list(res.table.columns)
['freq', 'omega', 'cycle_length', 'spectrum', 'lower', 'upper']
plot ¶
Plot the estimate against frequency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ax
|
matplotlib Axes
|
|
None
|
log
|
bool
|
Logarithmic vertical axis, on which the confidence band has constant width. |
True
|
omega
|
bool
|
Put radians rather than cycles per observation on the horizontal axis. |
False
|
KalmanResult
dataclass
¶
Bases: ResultProtocolMixin
Output of :func:kalman_filter.
Attributes:
| Name | Type | Description |
|---|---|---|
predicted_state, predicted_cov |
ndarray
|
|
filtered_state, filtered_cov |
ndarray
|
The same given |
smoothed_state, smoothed_cov |
ndarray or None
|
The same given the whole sample. |
innovations |
ndarray
|
One-step prediction errors |
innovations_cov |
ndarray
|
Their covariance, |
std_innovations |
ndarray
|
|
loglik |
float
|
Gaussian log-likelihood, the sum of |
loglik_obs |
ndarray
|
Contribution of each date; zero when the whole row is missing. |
n_obs |
int
|
Scalar observations that enter |
n_dates |
int
|
Dates with at least one of them. |
x0, P0 |
ndarray
|
Moments of the initial state that were used. |
init |
str
|
|
predicted_cov_inf, filtered_cov_inf |
ndarray or None
|
Under the exact diffuse initial state, the part of the covariance
that is multiplied by infinity, |
n_diffuse |
int
|
Scalar observations absorbed by the diffuse initial state. |
P0_inf |
ndarray or None
|
Diffuse part of the initial covariance. |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y = np.cumsum(rng.normal(size=60)) + rng.normal(size=60)
>>> out = sp.kalman_filter(y, F=1.0, G=1.0, Q=1.0, R=1.0)
>>> isinstance(out, sp.KalmanResult)
True
>>> out.states("filtered").shape
(60, 2)
states ¶
State estimates with their standard errors as a DataFrame.
which is 'predicted', 'filtered' or 'smoothed'; the
columns are the state names and <name>_se. Under the exact
diffuse initial state a standard error is infinite as long as the
data have not pinned that state down.
forecast ¶
Forecast states and observations beyond the sample.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
steps
|
int
|
Horizon. |
1
|
**future
|
Any
|
Values of |
{}
|
Returns:
| Type | Description |
|---|---|
dict
|
|
plot ¶
State paths with pointwise 1 - alpha bands.
StateSpaceResult
dataclass
¶
Bases: ResultProtocolMixin
Maximum-likelihood fit returned by :func:statespace.
Attributes:
| Name | Type | Description |
|---|---|---|
params |
Series
|
Estimates of the parameter vector passed to |
se |
Series
|
Standard errors. |
table |
DataFrame
|
|
cov |
DataFrame
|
Covariance of the estimates. |
transformed |
DataFrame or None
|
The same table for |
loglik, aic, bic |
float
|
|
n_obs, n_dates |
int
|
Scalar observations in the likelihood and dates carrying them;
|
converged |
bool
|
Scaled gradient near zero and a positive definite Hessian. |
filter |
KalmanResult
|
Filter and smoother output at the estimates. |
model_info |
dict
|
|
Examples:
A local level model with both variances estimated:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(1)
>>> level = np.cumsum(0.3 * rng.normal(size=150))
>>> y = level + rng.normal(size=150)
>>> def build(th):
... return {"F": 1.0, "G": 1.0, "Q": np.exp(th[0]), "R": np.exp(th[1])}
>>> fit = sp.statespace(y, build, [0.0, 0.0], init="diffuse", burn=1,
... param_names=["log_q", "log_r"])
>>> type(fit).__name__
'StateSpaceResult'
>>> list(fit.params.index)
['log_q', 'log_r']
DecompositionResult
dataclass
¶
Bases: ResultProtocolMixin
Decomposition of a series returned by :func:statspai.stl and
:func:statspai.classical_decompose.
Attributes:
| Name | Type | Description |
|---|---|---|
observed, trend, remainder |
ndarray
|
|
seasonal |
ndarray
|
The seasonal component (the sum of them with several periods;
the separate ones are in |
periods |
tuple of int
|
|
multiplicative |
bool
|
Whether |
method |
str
|
|
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> t = np.arange(120)
>>> y = 0.1 * t + 3 * np.sin(2 * np.pi * t / 12) + np.cos(t)
>>> dec = sp.stl(y, period=12)
>>> dec.strength["seasonal"] > 0.9
True
>>> list(dec.to_frame().columns)
['observed', 'trend', 'seasonal', 'remainder', 'seasonally_adjusted']
seasonally_adjusted
property
¶
The series with the seasonal component removed.
strength
property
¶
Strength of trend and of seasonality, each between 0 and 1:
max(0, 1 - Var(R) / Var(T + R)) and
max(0, 1 - Var(R) / Var(S + R)) (Wang, Smith and Hyndman,
2006). With several seasonal periods there is one
seasonal_<period> entry per period as well.
forecast ¶
forecast(horizon: int = 10, level: Levels = (80, 95), *, method: str = 'ets', **kwargs: Any) -> DataFrame
Forecast by modelling the seasonally adjusted series and adding back a seasonal naive forecast of the seasonal component.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
horizon
|
int
|
|
10
|
level
|
float or sequence of float
|
|
(80, 95)
|
method
|
('ets', 'naive', 'drift', 'arima')
|
Model of the seasonally adjusted series: a non-seasonal ETS model chosen by AICc, the naive or drift method, or a non-seasonal ARIMA model chosen automatically. Further keyword arguments go to that model. |
"ets"
|
Notes
The intervals are those of the seasonally adjusted series shifted
by the seasonal forecast: the uncertainty of the seasonal
component is ignored, as in R's forecast::stlf.
plot ¶
Stacked panels: the series and each component.
StructuralBreakResult ¶
Bases: ResultProtocolMixin
Results from structural break tests.
Returned by :func:structural_break. Holds the detected break
point(s), the sup-F statistic(s) with their Andrews (1993)
asymptotic p-value(s), and segment goodness-of-fit (RSS, BIC).
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> # mean shift halfway through the sample
>>> y = np.concatenate([rng.normal(0.0, 1.0, 100),
... rng.normal(3.0, 1.0, 100)])
>>> df = pd.DataFrame({"y": y})
>>> res = sp.structural_break(df, y="y", method="sup-f")
>>> isinstance(res, sp.StructuralBreakResult)
True
>>> res.test_type
'Sup-F'
>>> res.n_obs
200
>>> res.n_breaks >= 1
True
SVARResult ¶
Bases: ResultProtocolMixin
Outcome of :func:svar.
Attributes:
| Name | Type | Description |
|---|---|---|
identification |
str
|
|
impact |
DataFrame
|
The structural impact matrix |
A, B |
DataFrame or None
|
Short-run identification: the estimated matrices of |
C |
DataFrame or None
|
Long-run identification: the long-run impact matrix. |
table |
DataFrame or None
|
The free parameters with standard error, z and p-value. |
log_likelihood |
float or None
|
|
overid |
dict or None
|
Over-identified models: the likelihood-ratio test of the
over-identifying restrictions ( |
n_accepted, n_tried |
int or None
|
Sign restrictions: draws kept, and rotations tried to get them. |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> T = 300
>>> u = rng.normal(size=(T, 2))
>>> e = u @ np.array([[1.0, 0.0], [0.5, 0.8]]).T
>>> y = np.zeros((T, 2))
>>> for t in range(1, T):
... y[t] = 0.5 * y[t - 1] + e[t]
>>> fit = sp.var(pd.DataFrame(y, columns=["x", "y"]), lags=1)
>>> res = sp.svar(fit, B=[[np.nan, 0], [np.nan, np.nan]])
>>> res.identification
'short-run'
>>> res.impact.shape
(2, 2)
>>> res.irf(4).shape # (periods + 1) x variables x shocks, long format
(20, 4)
irf ¶
irf(periods: int = 20, cumulative: bool = False, *, ci: Optional[str] = None, reps: int = 500, seed: Optional[int] = None, boot: str = 'kilian', alpha: Optional[float] = None) -> DataFrame
Structural impulse responses, one row per shock, response and period.
Column irf is the response to a one-standard-deviation shock.
With sign restrictions it is the pointwise median over the
admissible set and lower / upper are its alpha / 2 and
1 - alpha / 2 quantiles: the spread of the identified set at the
estimated reduced form, not a confidence interval.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
periods
|
int
|
|
20
|
cumulative
|
bool
|
Accumulated responses (the response of the level when the variable enters the VAR in differences). |
False
|
ci
|
(None, 'bootstrap')
|
Short- and long-run identification: add |
None
|
reps
|
int
|
|
500
|
seed
|
int
|
|
None
|
boot
|
('kilian', 'efron', 'hall')
|
The bias-corrected bootstrap, the plain percentile band, or
the band reflected about the estimate (see
:func: |
'kilian'
|
alpha
|
float
|
|
None
|
fevd ¶
fevd(periods: int = 20, *, ci: Optional[str] = None, reps: int = 500, seed: Optional[int] = None, boot: str = 'kilian', alpha: Optional[float] = None) -> DataFrame
Forecast-error variance decomposition: the share of the
period-step forecast-error variance of each response due to each
shock (zero at period = 0, as in Stata's irf table fevd).
With sign restrictions, the pointwise median over the admissible
set, with its quantile band.
ci='bootstrap' adds se, lower and upper for a
short- or long-run identification, by the bootstrap of
:meth:irf (same reps, seed, boot and alpha). The
percentile bands stay inside the unit interval; boot='hall'
reflects them about the estimate and need not.
historical_decomposition ¶
Historical decomposition of every variable into shock contributions.
Returns one row per period and variable: actual, baseline
(the path from the initial values and deterministic terms with all
shocks set to zero), one column per identified shock with its
accumulated contribution sum_s Theta_s[:, j] u_{j, t-s} from the
first residual on, and with a proxy identification other for
the shocks it leaves unidentified. actual = baseline + sum of the
shock columns (+ other) holds exactly in every row. Not defined
for sign restrictions, whose median impact matrix is not itself an
admissible model.
FilterResult
dataclass
¶
Bases: ResultProtocolMixin
Trend and cycle returned by :func:statspai.tsfilter.
Attributes:
| Name | Type | Description |
|---|---|---|
observed, trend, cycle |
Series
|
On the index of the input. |
method |
str
|
|
params |
dict
|
The settings that define the filter. For |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> y = np.cumsum(np.random.default_rng(0).normal(0.5, 1, 120))
>>> res = sp.tsfilter(y, method="hp")
>>> bool(np.allclose(res.trend + res.cycle, res.observed))
True
gain ¶
Gain of the cycle filter: by how much it scales a wave of
frequency omega.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
omega
|
array - like
|
Frequencies in radians per observation, between 0 and pi. Default: 201 equally spaced points on that interval. |
None
|
Returns:
| Type | Description |
|---|---|
Series
|
Gain, indexed by |
Raises:
| Type | Description |
|---|---|
MethodIncompatibility
|
For |
Notes
'hp': 4 s (1 - cos w)**2 / (1 + 4 s (1 - cos w)**2) with
s = smooth. 'bw': s (1 - cos w)**m / ((1 + cos w)**m +
s (1 - cos w)**m) with m = order and
s = tan(pi / high)**(-2 m). Both describe the filter away
from the ends of the sample. 'bk' and 'cf' with
sma_order: |b_0 + 2 sum_j b_j cos(j w)| from the weights
used, exact at every date where the cycle is defined.
'hamilton':
|1 - sum_j beta_j exp(-i w (h + j))| from the estimated
coefficients.
TVPVARResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:tvp_var.
Attributes:
| Name | Type | Description |
|---|---|---|
coef_filtered, se_filtered |
ndarray, shape (T, K, K p + 1)
|
Coefficients given the data up to each date, and their standard
errors. Axis 1 is the equation, axis 2 the regressor in the order
of |
coef_smoothed, se_smoothed |
ndarray or None
|
The same given the whole sample ( |
sigma |
DataFrame
|
Error covariance. |
sigma_t |
ndarray(T, K, K) or None
|
The path of the error covariance ( |
variances |
DataFrame or None
|
|
loglik |
float
|
|
index |
Index
|
Dates of the |
var_names, terms |
list of str
|
|
lags, n_obs, method, alpha |
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y = np.zeros((160, 2))
>>> for t in range(1, 160):
... a = 0.2 + 0.6 * t / 160
... y[t, 0] = a * y[t - 1, 0] + rng.normal()
... y[t, 1] = 0.3 * y[t - 1, 1] + rng.normal()
>>> df = pd.DataFrame(y, columns=["x", "z"])
>>> fit = sp.tvp_var(df, lags=1, method="forgetting", lam=0.98)
>>> fit.coef_filtered.shape
(159, 2, 3)
>>> fit.terms
['L1.x', 'L1.z', '_cons']
>>> list(fit.irf(at=-1, periods=4).columns)
['date', 'shock', 'response', 'period', 'irf']
coefficients ¶
Coefficient paths, one row per date, equation and regressor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
('smoothed', 'filtered')
|
Default: smoothed when the method has them, else filtered. |
'smoothed'
|
alpha
|
float
|
|
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
irf ¶
irf(at: Any = -1, periods: int = 10, orthogonal: bool = True, cumulative: bool = False, kind: Optional[str] = None) -> DataFrame
Impulse responses of the VAR frozen at one or several dates.
The coefficients and the error covariance of date at are held
fixed and the responses are those of that constant VAR. It answers
"how would a shock propagate if the economy stayed as it was at
that date", and ignores that coefficients keep moving afterwards.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
at
|
label, int or list of them
|
Dates: labels of |
-1
|
periods
|
int
|
|
10
|
orthogonal
|
bool
|
Responses to one-standard-deviation shocks from the Cholesky
factor of the error covariance in the order of the variables,
as in :func: |
True
|
cumulative
|
bool
|
|
False
|
kind
|
('smoothed', 'filtered')
|
|
'smoothed'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
stability ¶
Largest companion root of the frozen VAR at every date.
Returns:
| Type | Description |
|---|---|
DataFrame
|
Indexed by date: |
forecast ¶
Forecasts from the last filtered coefficients.
The coefficients stay at their last filtered value (the forecast
of a random walk) and are treated as known: the standard errors
come from the error covariance of the last date only, as in
VARResult.forecast, and understate the uncertainty.
Returns:
| Type | Description |
|---|---|
DataFrame
|
Indexed by horizon |
plot ¶
plot(equation: Optional[str] = None, terms: Optional[Sequence[str]] = None, kind: Optional[str] = None) -> Any
Coefficient paths with pointwise 1 - alpha bands.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
equation
|
str
|
One equation; default all of them (one column each). |
None
|
terms
|
list of str
|
Regressors to show; default all. |
None
|
kind
|
('smoothed', 'filtered')
|
|
'smoothed'
|
UnitRootResult ¶
Bases: ResultProtocolMixin
Outcome of :func:unitroot.
Attributes:
| Name | Type | Description |
|---|---|---|
test |
str
|
|
statistic |
float
|
The t statistic on the lagged level (ADF, DF-GLS), the adjusted
|
pvalue |
float or None
|
MacKinnon (1994) approximate p-value for ADF and Phillips-Perron.
|
null |
str
|
|
z_rho |
float or None
|
Phillips-Perron only: the adjusted |
critical_values |
dict
|
|
reject |
bool
|
Whether the null is rejected at |
lags |
int
|
Lagged differences in the test regression; for Phillips-Perron and KPSS, the Newey-West truncation lag. |
lag_selection |
str
|
|
n_obs |
int
|
Observations in the test regression. |
rho, se |
float
|
Coefficient on the lagged level and its standard error. The
regression differs by test: for ADF and DF-GLS the dependent
variable is the difference, |
rho_level, rho_diff |
float
|
The coefficient on |
trend |
str
|
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> walk = pd.DataFrame({"y": np.cumsum(rng.normal(size=200))})
>>> res = sp.unitroot(walk, "y", lags=1)
>>> type(res).__name__
'UnitRootResult'
>>> sorted(res.critical_values)
['1%', '10%', '5%']
>>> res.lags
1
VARResult ¶
Bases: ResultProtocolMixin
Results from VAR estimation.
Returned by :func:var; carries coefficient tables, the residual
covariance, information criteria, and helper methods .irf(),
.granger_test() and .plot_irf().
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(3)
>>> T = 200
>>> x = np.zeros(T); y = np.zeros(T)
>>> for t in range(1, T):
... x[t] = 0.5 * x[t - 1] + rng.normal()
... y[t] = 0.3 * y[t - 1] + 0.4 * x[t - 1] + rng.normal()
>>> df = pd.DataFrame({"y": y, "x": x})
>>> vr = sp.var(df, variables=["y", "x"], lags=2)
>>> type(vr).__name__
'VARResult'
>>> vr.var_names
['y', 'x']
>>> vr.lags
2
>>> print(vr.summary().splitlines()[0])
Vector Autoregression (VAR)
References
lutkepohl2005new
irf ¶
irf(periods: int = 20, impulse: Optional[str] = None, response: Optional[str] = None, orthogonal: bool = True, **kwargs: Any) -> Dict[str, Any]
Compute impulse response functions (see :func:irf).
fevd ¶
fevd(periods: int = 20, *, ci: Optional[str] = None, alpha: float = 0.05, reps: int = 1000, seed: Optional[int] = None, boot: str = 'kilian') -> DataFrame
Forecast-error variance decomposition under the Cholesky ordering of the variables.
One row per shock, response and period: the share of the
period-step forecast-error variance of response due to the
orthogonalised innovation of shock (zero at period = 0, as
in Stata's irf table fevd). The orthogonalisation uses the same
residual covariance as :meth:irf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
periods
|
int
|
|
20
|
ci
|
(None, asymptotic, bootstrap)
|
Add |
None
|
alpha
|
float
|
|
0.05
|
reps
|
int
|
|
1000
|
seed
|
int
|
|
None
|
boot
|
(kilian, efron)
|
The bias-corrected bootstrap, or the plain percentile one (see
:func: |
'kilian'
|
Notes
A share of exactly zero or one (the first-ordered variable one step ahead) is on the boundary: its asymptotic standard error is zero and no normal band applies to it.
granger_table ¶
Granger-causality Wald tests for every equation (Stata
vargranger).
lag_exclusion ¶
Wald tests that a whole lag can be dropped (Stata varwle).
lm_test ¶
LM test for residual autocorrelation at each lag order up to
lags (Stata varlmar).
stability ¶
Eigenvalues of the companion matrix (Stata varstable);
attrs['stable'] is True when all lie inside the unit circle.
equation_table ¶
Root MSE, R-squared and Wald chi-squared of each equation.
forecast ¶
Dynamic forecasts steps periods past the sample, with
standard errors that take the coefficients as known.
plot_irf ¶
Plot impulse response functions.
VECResult ¶
Bases: ResultProtocolMixin
Fitted vector error-correction model.
Attributes:
| Name | Type | Description |
|---|---|---|
alpha |
DataFrame
|
Adjustment coefficients, one row per equation and one column per cointegrating relation. |
beta |
DataFrame
|
Cointegrating vectors (columns |
beta_se |
DataFrame
|
Standard errors of |
coefs |
dict of DataFrame
|
For each equation |
sigma_ml |
DataFrame
|
Maximum-likelihood residual covariance. |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> common = np.cumsum(rng.normal(size=300))
>>> df = pd.DataFrame({
... "y": common + rng.normal(scale=0.5, size=300),
... "x": 0.5 * common + rng.normal(scale=0.5, size=300),
... })
>>> fit = sp.vec(df, ["y", "x"], lags=1, rank=1)
>>> type(fit).__name__
'VECResult'
>>> list(fit.beta.index)
['y', 'x', '_cons']
>>> list(fit.coefs)
['D_y', 'D_x']
>>> fit.lm_test(2).shape
(2, 4)
equation_table ¶
Per equation: number of coefficients, root MSE (divisor T -
d), R-squared about zero, and the Wald chi-squared that every
coefficient of the equation is zero.
irf ¶
irf(periods: int = 20, impulse: Optional[str] = None, response: Optional[str] = None, orthogonal: bool = True) -> Dict[str, Any]
Impulse responses of the levels.
Because the system has K - r unit roots, a shock can move the
levels for good: the responses settle at a constant that need not
be zero, where those of a stationary VAR die out.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
periods
|
int
|
Horizon; responses are returned for periods |
20
|
impulse
|
str
|
Restrict to one shock or one responding variable. |
None
|
response
|
str
|
Restrict to one shock or one responding variable. |
None
|
orthogonal
|
bool
|
Responses to one-standard-deviation orthogonalised shocks, by
the Cholesky factor of the maximum-likelihood residual
covariance in the order of the variables (Stata |
True
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Notes
No confidence bands are computed. The ordering matters for the orthogonalised responses exactly as in a VAR.
fevd ¶
Forecast-error variance decomposition of the levels under the Cholesky ordering of the variables.
One row per shock, response and period: the share of the
period-step forecast-error variance of response due to the
orthogonalised innovation of shock (zero at period = 0, as
in Stata's irf table fevd).
forecast ¶
Dynamic forecasts of the levels steps periods past the sample.
Each forecast feeds the next through the error-correction
equations, deterministic terms included. The standard error is the
root of sum_i Phi_i Sigma Phi_i' with the maximum-likelihood
residual covariance; it treats the coefficients as known, so it
understates the uncertainty in short samples, and it grows without
bound with the horizon because the levels are integrated.
Returns:
| Type | Description |
|---|---|
DataFrame
|
Indexed by horizon |
stability ¶
Eigenvalues of the companion matrix (Stata vecstable).
A VECM with K series and rank r has K - r eigenvalues
equal to one by construction (attrs['unit_moduli']); the model
is stable when the others lie inside the unit circle
(attrs['stable']).
lm_test ¶
LM test for residual autocorrelation at each lag order (Stata
veclmar): (T - d - 0.5) log(|Sigma| / |Sigma_s|) from the
equations re-estimated, at the fitted cointegrating relation, with
the residuals lagged s periods added (zeros where the lag does
not exist); d counts the coefficients of one augmented
equation. Chi-squared with K^2 degrees of freedom.
CrossCorrelogram
dataclass
¶
Bases: ResultProtocolMixin
Cross-correlogram returned by :func:statspai.xcorr.
Attributes:
| Name | Type | Description |
|---|---|---|
table |
DataFrame
|
Indexed by |
haugh |
dict
|
Portmanteau test of no cross-correlation at any lag in the table
( |
n_obs |
int
|
Observations the correlations are computed from. |
prewhiten |
str or None
|
|
ar_orders |
dict
|
Order of the AR filter of each series. |
ar_coefs |
dict
|
Its coefficients. |
residuals |
DataFrame
|
The two series that were cross-correlated, about their means. |
alpha |
float
|
|
names |
tuple of str
|
|
Examples:
ZivotAndrewsResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:zivot_andrews.
Attributes:
| Name | Type | Description |
|---|---|---|
statistic |
float
|
Minimum over the candidate break dates of the t statistic of the lagged level. |
break_index |
int
|
Number of observations in the first regime: the break dummies
switch on at observation |
break_label |
object
|
Index label (or |
critical_values |
dict
|
Asymptotic 1%, 5% and 10% values. |
lags |
int
|
Lagged differences in the regression at the chosen break. |
model |
str
|
|
path |
Series
|
The t statistic at every candidate break, indexed by
|
coefficients |
DataFrame
|
The regression at the chosen break. |
n_obs |
int
|
Observations in the regression at the chosen break. |
lag_method |
str
|
|
lags_path |
Series or None
|
With |
Examples:
bagged_forecast ¶
bagged_forecast(y: Any, forecaster: Union[str, Callable[..., Any]] = 'ets', *, horizon: int = 10, n_boot: int = 100, period: int = 1, block_size: Optional[int] = None, boxcox: Any = 'auto', seed: Optional[int] = 0, data: Optional[DataFrame] = None, **kwargs: Any) -> BaggedForecastResult
Forecast every bootstrapped version of a series and average.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array - like, Series or str)
|
|
required |
forecaster
|
str or callable
|
|
"ets"
|
horizon
|
int
|
|
10
|
n_boot
|
int
|
Number of series forecast, the original included. |
100
|
period
|
int
|
As in :func: |
1
|
block_size
|
int
|
As in :func: |
1
|
boxcox
|
int
|
As in :func: |
1
|
seed
|
int
|
As in :func: |
1
|
data
|
DataFrame
|
|
None
|
Returns:
| Type | Description |
|---|---|
BaggedForecastResult
|
|
Notes
With forecaster="ets" and the model chosen separately for each
series this is the bagged ETS of Bergmeir, Hyndman and Benitez (2016),
R's forecast::baggedETS. Bagging averages over model choice and
parameter estimates, the two sources of error a single fit ignores.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> t = np.arange(72)
>>> y = (40 + 0.5 * t) * (1 + 0.15 * np.sin(2 * np.pi * t / 4))
>>> y = y + rng.normal(0, 1.5, 72)
>>> bag = sp.bagged_forecast(y, "snaive", horizon=4, n_boot=10, period=4)
>>> list(bag.forecast.columns)
['forecast', 'ensemble_min', 'ensemble_max']
References
bergmeir2016bagging, hyndman2026fpppy
bootstrap_series ¶
bootstrap_series(y: Any, n_boot: int = 100, *, period: int = 1, block_size: Optional[int] = None, boxcox: Any = 'auto', seed: Optional[int] = 0, data: Optional[DataFrame] = None) -> DataFrame
Bootstrapped versions of a series that keep its trend, seasonal pattern and the autocorrelation of what is left.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array - like, Series or str)
|
The series, in time order; a column name when |
required |
n_boot
|
int
|
Number of series returned. The first is the original. |
100
|
period
|
int
|
Seasonal period. With |
1
|
block_size
|
int
|
Length of the resampled blocks of the remainder. Default twice
the period, or |
None
|
boxcox
|
(float, 'auto' or None)
|
Box-Cox parameter applied before decomposing, so that the
components add up. |
"auto"
|
seed
|
int or None
|
|
0
|
data
|
DataFrame
|
|
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One column per series, |
Notes
The procedure is that of Bergmeir, Hyndman and Benitez (2016) and of
R's forecast::bld.mbb.bootstrap. The draws are random, so the
series are not those R returns for the same seed.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> t = np.arange(80)
>>> y = (50 + t) * (1 + 0.2 * np.sin(2 * np.pi * t / 4)) + rng.normal(0, 2, 80)
>>> boot = sp.bootstrap_series(y, 10, period=4)
>>> boot.shape
(80, 10)
>>> bool(np.allclose(boot["boot_0"], y))
True
References
bergmeir2016bagging
chow_test ¶
chow_test(data: DataFrame, y: str, x: Optional[Sequence[str]] = None, *, break_point: Any, time: Optional[str] = None, break_vars: Optional[Sequence[str]] = None, vce: str = 'nonrobust', alpha: float = 0.05) -> Dict[str, Any]
Chow test for a structural break at a known date.
Tests that the regression coefficients are the same before and after
break_point. The model is fitted once with every coefficient in
break_vars allowed to differ between the regimes, and the
restrictions that the differences are zero are tested. With every
coefficient free and classical errors this is Chow's statistic
[(RSS - RSS_1 - RSS_2) / k] / [(RSS_1 + RSS_2) / (n - 2k)].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Observations in time order. |
required |
y
|
str
|
Dependent variable. |
required |
x
|
sequence of str
|
Regressors. A constant is always included; with |
None
|
break_point
|
int, label or list of these
|
Where the second regime starts. An |
required |
time
|
str
|
Column that dates the observations. The rows are sorted by it before the regimes are formed. |
None
|
break_vars
|
sequence of str
|
Coefficients allowed to change, as names from |
None
|
vce
|
('nonrobust', 'hc0', 'hc1')
|
Covariance estimator of the Wald statistic. With |
'nonrobust'
|
alpha
|
float
|
Level used for |
0.05
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Notes
A regime needs at least as many observations as it has free coefficients. Chow's second test, for a regime too short to fit, is not computed.
The F reference holds for a date fixed in advance. Picking the date
with the largest statistic and then reading its p-value here rejects a
stable relation far more often than alpha.
References
chow1960tests
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 120
>>> x = rng.normal(size=n)
>>> shift = np.where(np.arange(n) < 60, 0.0, 1.5)
>>> df = pd.DataFrame({"y": 1 + 0.5 * x + shift + rng.normal(size=n), "x": x})
>>> res = sp.chow_test(df, "y", ["x"], break_point=60)
>>> res["df1"], res["df2"]
(2, 116)
>>> bool(res["reject"])
True
engle_granger ¶
engle_granger(data: DataFrame, variables: Optional[List[str]] = None, lags: Optional[int] = None, trend: str = 'c', alpha: float = 0.05) -> CointegrationResult
Engle-Granger (1987) two-step cointegration test.
Step 1: OLS regression of y (first variable) on the remaining
variables and the deterministic terms selected by trend.
Step 2: augmented Dickey-Fuller regression on the step-1 residuals
without deterministic terms,
.. math:: \Delta e_t = \rho e_{t-1} + \sum_{j=1}^{L} \gamma_j \Delta e_{t-j} + u_t ,
whose t-ratio on rho is the test statistic. This is the
specification of Stata's egranger (Schaffer, SSC) and the one the
MacKinnon critical values are tabulated for; the deterministic terms
live in step 1 only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
variables
|
list of str
|
Variables to test (first is dependent). |
None
|
lags
|
int
|
Number of lagged differences |
None
|
trend
|
(c, ct, ctt)
|
Deterministic terms in step 1: constant; constant + linear trend
( |
'c'
|
alpha
|
(0.01, 0.05, 0.1)
|
Level at which |
0.01
|
Returns:
| Type | Description |
|---|---|
CointegrationResult
|
|
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> T = 200
>>> x = np.cumsum(rng.normal(size=T)) # random walk
>>> y = 2.0 * x + rng.normal(size=T) # cointegrated with x
>>> df = pd.DataFrame({"y": y, "x": x})
>>> res = sp.engle_granger(df, variables=["y", "x"])
>>> res.test_type
'Engle-Granger'
>>> res.n_vars
2
>>> bool(res.rank in (0, 1))
True
>>> isinstance(res.summary(), str)
True
References
[@engle1987integration], [@mackinnon1994approximate]
johansen ¶
johansen(data: DataFrame, variables: Optional[List[str]] = None, lags: int = 1, trend: str = 'c', test: str = 'trace', alpha: float = 0.05) -> CointegrationResult
Johansen (1991) cointegration test.
Tests for the cointegration rank using the trace or maximum eigenvalue test statistic.
Equivalent to Stata's vecrank and R's urca::ca.jo().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
variables
|
list of str
|
Variables to test. |
None
|
lags
|
int
|
Number of lagged differences in the VECM. This is Stata's
|
1
|
trend
|
str
|
Deterministic specification (Stata |
'c'
|
test
|
(trace, maxeig)
|
|
'trace'
|
alpha
|
(0.05, 0.01)
|
Level of the Osterwald-Lenum critical values (the table Stata's
|
0.05
|
Returns:
| Type | Description |
|---|---|
CointegrationResult
|
|
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> trend = np.cumsum(rng.normal(size=200)) # common stochastic trend
>>> df = pd.DataFrame({
... 'gdp': trend + rng.normal(scale=0.5, size=200),
... 'consumption': 0.8 * trend + rng.normal(scale=0.5, size=200),
... 'investment': 0.3 * trend + rng.normal(scale=0.5, size=200),
... })
>>> result = sp.johansen(
... df, variables=['gdp', 'consumption', 'investment'], lags=2)
>>> type(result).__name__
'CointegrationResult'
>>> isinstance(result.summary(), str)
True
tscv ¶
tscv(y: Any, forecaster: Forecaster = 'naive', *, horizon: int = 1, initial: Optional[int] = None, step: int = 1, window: Optional[int] = None, period: int = 1, data: Optional[DataFrame] = None, **kwargs: Any) -> TSCVResult
Time series cross-validation on a rolling forecast origin.
For each origin the forecaster sees only the observations up to it
and forecasts the next horizon periods; the errors against what
then happened estimate the out-of-sample accuracy at each horizon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array - like, Series or str)
|
The series, in time order; a column name when |
required |
forecaster
|
str or callable
|
A built-in method -- |
"naive"
|
horizon
|
int
|
Longest forecast horizon. |
1
|
initial
|
int
|
Number of observations in the first training window. The default is the larger of 10 and two seasonal periods plus one, the least the built-in methods can be fitted on; choose it so that the first window is long enough for the model you evaluate. |
None
|
step
|
int
|
Number of periods between successive origins. |
1
|
window
|
int
|
Length of a rolling training window. |
None
|
period
|
int
|
Seasonal period handed to the built-in methods. |
1
|
data
|
DataFrame
|
|
None
|
Returns:
| Type | Description |
|---|---|
TSCVResult
|
|
Notes
The error matrix has the layout of R's forecast::tsCV: the row of
an origin holds y[origin + h] - forecast. Origins where the
forecaster raises are reported in failures and warned about; they
are not dropped silently.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(1)
>>> y = 50 + np.cumsum(rng.normal(0.3, 1.0, 80))
>>> cv = sp.tscv(y, "drift", horizon=4, initial=30)
>>> cv.accuracy()["RMSE"].is_monotonic_increasing
True
>>> ses = sp.tscv(y, lambda train, h: sp.ets(train, "ANN"), horizon=1, initial=30)
>>> ses.errors.shape
(50, 1)
References
hyndman2026fpppy
hierarchy ¶
hierarchy(data: DataFrame, spec: Sequence[Sequence[str]], *, time: str, value: str, sep: str = '/') -> Hierarchy
Aggregate bottom-level series into a hierarchical or grouped structure and build its summing matrix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Long table: one row per bottom-level series and period, with the key columns that define the aggregation. |
required |
spec
|
list of list of str
|
One list of key columns per level, e.g. |
required |
time
|
str
|
Period and value columns. |
required |
value
|
str
|
Period and value columns. |
required |
sep
|
str
|
Separator of the key values in the series identifiers. |
"/"
|
Returns:
| Type | Description |
|---|---|
Hierarchy
|
|
Notes
The bottom level must be balanced: every bottom series observed in every period. Missing cells would silently bias every aggregate, so they are refused.
Examples:
>>> import pandas as pd
>>> import statspai as sp
>>> df = pd.DataFrame({
... "all": "Total",
... "state": ["A", "A", "A", "A", "B", "B", "B", "B"],
... "kind": ["x", "x", "y", "y", "x", "x", "y", "y"],
... "t": [1, 2] * 4,
... "y": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]})
>>> spec = [["all"], ["all", "state"], ["all", "kind"], ["all", "state", "kind"]]
>>> h = sp.hierarchy(df, spec, time="t", value="y")
>>> h.S.shape
(9, 4)
>>> h.Y["Total"].tolist()
[16.0, 20.0]
References
hyndman2011optimal, hyndman2026fpppy
cumulative_periodogram_test ¶
cumulative_periodogram_test(data: ArrayLike, y: Optional[str] = None, *, alpha: float = 0.05) -> CumulativePeriodogramResult
Bartlett's cumulative-periodogram test that a series is white noise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
(DataFrame, Series or array)
|
The series, in time order. With a DataFrame, |
required |
y
|
str
|
Column of |
None
|
alpha
|
float
|
Level of the band reported in |
0.05
|
Returns:
| Type | Description |
|---|---|
CumulativePeriodogramResult
|
|
Raises:
| Type | Description |
|---|---|
DataInsufficient
|
Fewer than four observations or a constant series. |
MethodIncompatibility
|
A gap in the series, or |
Notes
The periodogram of the demeaned series is taken at the frequencies
k / n, k = 0, ..., q - 1 with q = floor(n / 2) + 1, and
accumulated into F_k, the share of the total up to k / n. Under
white noise F_k is close to 2 k / n. The statistic is
B = sqrt(q) * max_k |F_k - 2 k / n| and is referred to the
Kolmogorov distribution, P(B > b) = 2 sum_j (-1)**(j-1)
exp(-2 j**2 b**2). This is the statistic of Stata wntestb; R
cpgram draws the same curve. The p-value sums the series to
machine precision; Stata stops once a term falls below about 1e-8, so
its r(p) agrees to that accuracy only.
A series that was estimated (residuals of a fitted model) is closer to the line than white noise would be, and the test is then conservative.
Examples:
periodogram ¶
periodogram(data: ArrayLike, y: Optional[str] = None, *, method: str = 'raw', spans: Union[int, Sequence[int], None] = None, taper: float = 0.1, detrend: str = 'linear', pad: float = 0.0, fast: bool = False, order: Optional[int] = None, max_order: Optional[int] = None, n_freq: int = 500, scale: str = 'cycles', alpha: float = 0.05) -> SpectrumResult
Estimate the spectral density of a series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
(DataFrame, Series or array)
|
The series, in time order. With a DataFrame, |
required |
y
|
str
|
Column of |
None
|
method
|
('raw', 'smoothed', 'ar')
|
|
'raw'
|
spans
|
int or sequence of int
|
Widths of the modified Daniell smoothers, applied one after the
other ( |
None
|
taper
|
float
|
Share of the sample at each end that is tapered by a split cosine
bell before the transform, between 0 and 0.5. Ignored by |
0.1
|
detrend
|
('linear', 'mean', 'none')
|
What is removed before the transform: a least-squares line, the
mean, or nothing. |
'linear'
|
pad
|
float
|
Zeros appended before the transform, as a share of the sample
length; |
0
|
fast
|
bool
|
Pad with zeros to the next length whose prime factors are 2, 3
and 5 (the default of R |
False
|
order
|
int
|
Autoregressive order ( |
None
|
max_order
|
int
|
Largest order considered. Default
|
None
|
n_freq
|
int
|
Number of frequencies for |
500
|
scale
|
('cycles', 'radians')
|
|
'cycles'
|
alpha
|
float
|
|
0.05
|
Returns:
| Type | Description |
|---|---|
SpectrumResult
|
|
Raises:
| Type | Description |
|---|---|
MethodIncompatibility
|
Unknown option values, |
DataInsufficient
|
Fewer than four observations, a constant series, or an autoregressive order the sample cannot support. |
Notes
With taper=0 and scale='radians' the raw estimate at
w_j = 2 pi j / n is the textbook periodogram
I(w_j) = |sum_t x_t exp(-i w_j t)|**2 / (2 pi n); neither the mean
nor a constant changes it at these frequencies, so detrend='mean'
and detrend='none' agree. scale='cycles' returns
|sum_t x_t exp(-2 pi i f t)|**2 / n.
The defaults taper=0.1 and detrend='linear' are those of R
spec.pgram; fast=True completes the match when n has a
prime factor above 5. The estimate is divided by the mean square of
the taper, 1 - 5 taper / 4.
Each ordinate is treated as spectrum * chi2(df) / df with
df = 2 n / (n_padded * c * sum_j K_j**2), where K is the kernel
and c = (1 - 93 taper / 64) / (1 - 5 taper / 4)**2 allows for the
taper; the interval is spectrum * df / chi2 at the two quantiles.
The raw periodogram has df = 2 and is not consistent: the interval
does not shrink as the sample grows. bandwidth is the standard
deviation of the kernel, with a continuity correction of 1/12, in
cycles per observation.
For 'ar' the density is s2 / |1 - sum_k a_k exp(-2 pi i f k)|**2
with s2 the Yule-Walker innovation variance times
n / (n - order - 1), and the order minimises
n log(s2_k) + 2 k (R ar and spec.ar). No interval is
reported: the chi-squared approximation does not apply.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> t = np.arange(200)
>>> rng = np.random.default_rng(1)
>>> x = np.cos(2 * np.pi * t / 20) + 0.5 * rng.normal(size=200)
>>> res = sp.periodogram(x)
>>> float(res.peak["cycle_length"])
20.0
>>> smooth = sp.periodogram(x, method="smoothed", spans=(3, 5))
>>> round(smooth.df, 2)
9.36
>>> sp.periodogram(x, method="ar", order=2).order
2
References
[@brockwell1991time], [@neusser2016time]
kalman_filter ¶
kalman_filter(y: Any, *, F: Any, G: Any, Q: Any, R: Any, A: Optional[Any] = None, x0: Optional[Any] = None, P0: Optional[Any] = None, init: str = 'auto', kappa: float = 10000000.0, diffuse: Optional[Any] = None, smooth: bool = True, burn: int = 0, data: Optional[DataFrame] = None, state_names: Optional[Sequence[str]] = None) -> KalmanResult
Kalman filter and smoother for a linear state space model.
The model is X_t = F_t X_{t-1} + V_t with Var(V_t) = Q_t and
Y_t = A_t + G_t X_t + W_t with Var(W_t) = R_t. All system
matrices are taken as known; :func:statespace estimates them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
array-like, Series, DataFrame, or column name(s) with ``data``
|
Observations, |
required |
F
|
array-like, ``(m, m)`` or ``(T, m, m)``
|
Transition matrix. With a time axis, entry |
required |
G
|
array-like, ``(n, m)`` or ``(T, n, m)``
|
Loadings of the observables on the state. With one observable,
|
required |
Q
|
array-like, ``(m, m)`` or ``(T, m, m)``
|
Covariance of the state disturbance. May be singular. |
required |
R
|
array-like, ``(n, n)`` or ``(T, n, n)``
|
Covariance of the measurement error. May be singular or zero. |
required |
A
|
array-like, ``(n,)`` or ``(T, n)``
|
Intercept of the observation equation. Default zero. |
None
|
x0
|
array - like
|
Mean and covariance of the state one step before the first
observation. |
None
|
P0
|
array - like
|
Mean and covariance of the state one step before the first
observation. |
None
|
init
|
('auto', 'stationary', 'diffuse', 'exact')
|
Rule for |
'auto'
|
kappa
|
float
|
Variance of the initial state under |
1e7
|
diffuse
|
sequence of bool, or symmetric matrix
|
One flag per state: |
None
|
smooth
|
bool
|
Also run the fixed-interval smoother. |
True
|
burn
|
int
|
Leading dates left out of |
0
|
data
|
DataFrame
|
Source of the columns named by |
None
|
state_names
|
sequence of str
|
Names of the states. Default |
None
|
Returns:
| Type | Description |
|---|---|
KalmanResult
|
Predicted, filtered and smoothed states with their covariances,
prediction errors, the log-likelihood, |
Raises:
| Type | Description |
|---|---|
MethodIncompatibility
|
System matrices of the wrong shape, an unstable |
DataInsufficient
|
|
Notes
The log-likelihood is the Gaussian prediction-error decomposition,
summed over every observed element of y from date burn + 1:
date t contributes -0.5 [n_t log(2 pi) + log det S_t + v_t'
S_t^{-1} v_t] with n_t the number of elements observed and
S_t the covariance of their prediction errors. Missing elements
contribute nothing.
init='diffuse' is the large-variance approximation: the first
prediction errors have variance of order kappa and enter the
likelihood with it, and state moments differ from their limit by a
term of order 1 / kappa at every date (about 1e-6 for the default
kappa on series of unit scale). Use burn to leave the first
dates out of the likelihood.
init='exact' is that limit itself. The initial covariance is
P0_* + kappa P0_inf with P0_inf = diag(diffuse) and kappa
sent to infinity; the recursions carry the pair (P_*, P_inf),
reported as predicted_cov and predicted_cov_inf (likewise
filtered), until the data have used P_inf up, and are the ordinary
ones afterwards. During those dates the elements of Y_t enter one
at a time (after a rotation by the eigenvectors of R_t when it is
not diagonal), so several observables, missing values and
time-varying matrices need no special case. n_diffuse scalar
observations are absorbed by the initial state. Each contributes
-0.5 [log(2 pi) + log F_inf] to loglik, F_inf being the
coefficient of kappa in its prediction-error variance, so that
loglik = lim [loglik(kappa) + 0.5 n_diffuse log(kappa)].
The normal constant is kept for every observation, as in Durbin and
Koopman and in statsmodels; R KFAS leaves it out for the absorbed
ones, which makes its logLik larger by
0.5 n_diffuse log(2 pi). The value also depends on which vector is
called diffuse: here it is X_0, so the first predicted state has
P_inf = F_1 P0_inf F_1'; software that puts P_inf = I on the
first predicted state differs by the constant
0.5 log pdet(F_1 P0_inf F_1'), zero for random-walk states.
Smoothed moments, and predicted and filtered moments from the end of
the diffuse period, do not depend on that choice; predicted and
filtered states inside it do, in the directions that are still
unidentified, where .states() reports an infinite standard error.
On the diffuse dates innovations and innovations_cov hold the
joint prediction error and the finite part of its covariance, and
std_innovations is NaN.
The covariance update uses the Joseph form, and the smoother the
backward recursion that inverts only prediction-error covariances, so
singular Q and R need no special treatment. It gives the same
moments as the Rauch-Tung-Striebel recursion J_t = P_{t|t} F'
P_{t+1|t}^{-1}, P_{t|T} = P_{t|t} + J_t (P_{t+1|T} - P_{t+1|t})
J_t'.
Examples:
A local level model:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(1)
>>> level = np.cumsum(0.5 * rng.normal(size=100))
>>> y = level + rng.normal(size=100)
>>> out = sp.kalman_filter(y, F=1.0, G=1.0, Q=0.25, R=1.0)
>>> out.smoothed_state.shape
(100, 1)
An AR(2) in companion form; the likelihood is the exact Gaussian one:
>>> F = np.array([[0.5, 0.3], [1.0, 0.0]])
>>> Q = np.diag([1.0, 0.0])
>>> out = sp.kalman_filter(y, F=F, G=[1.0, 0.0], Q=Q, R=0.0)
>>> out.init
'stationary'
>>> out.forecast(2)["obs"].shape
(2, 1)
A local linear trend with an exact diffuse initial state:
>>> out = sp.kalman_filter(y, F=[[1.0, 1.0], [0.0, 1.0]], G=[1.0, 0.0],
... Q=np.diag([0.25, 0.01]), R=1.0, init="exact")
>>> out.n_diffuse
2
References
[@kalman1960new], [@koopman1997exact], [@neusser2016time]
classical_decompose ¶
classical_decompose(y: Any, period: int, *, model: str = 'additive', data: Optional[DataFrame] = None) -> DecompositionResult
Classical decomposition by moving averages.
The trend is a centred moving average of one full cycle (a
2 x m average when the period is even), the seasonal figure the
average detrended value at each position of the cycle. Kept as the
textbook baseline; :func:statspai.stl is the better tool: it has
no missing ends, lets the seasonal pattern change and can be made
robust to outliers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array - like, Series or str)
|
|
required |
period
|
int
|
|
required |
model
|
('additive', 'multiplicative')
|
|
"additive"
|
data
|
DataFrame
|
|
None
|
Returns:
| Type | Description |
|---|---|
DecompositionResult
|
|
Notes
Equal to R's stats::decompose and to
statsmodels.tsa.seasonal.seasonal_decompose with its default
two-sided filter.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> y = np.tile([8.0, 12.0, 10.0, 14.0], 6) * (1 + 0.02 * np.arange(24))
>>> dec = sp.classical_decompose(y, period=4, model="multiplicative")
>>> round(float(dec.seasonal[:4].mean()), 6)
1.0
References
hyndman2026fpppy
cusum_test ¶
cusum_test(data: DataFrame, y: str, x: Optional[List[str]] = None, alpha: float = 0.05, method: str = 'recursive') -> Dict[str, Any]
CUSUM test for parameter stability.
Tests H0: parameters are stable vs H1: parameter shift.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
y
|
str
|
Dependent variable. |
required |
x
|
list of str
|
Regressors. |
None
|
alpha
|
float
|
|
0.05
|
method
|
(recursive, ols)
|
Which residuals are cumulated. |
'recursive'
|
Returns:
| Type | Description |
|---|---|
dict
|
For Keys: |
Notes
This is the recursive-residual CUSUM of Brown, Durbin & Evans (1975), as
in R's strucchange::efp(type="Rec-CUSUM"). Its boundary is linear in
the recursion index with coefficient a (0.948 at the 5% level), not
the constant sup|Brownian-bridge| value (1.358) that belongs to the
OLS-CUSUM of Ploberger & Kramer (1992).
References
brown1975techniques, ploberger1992cusum
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 120
>>> x = rng.normal(size=n)
>>> y = 1.0 + 0.5 * x + rng.normal(scale=0.5, size=n) # stable relation
>>> df = pd.DataFrame({"y": y, "x": x})
>>> res = sp.cusum_test(df, y="y", x=["x"])
>>> sorted(res.keys())
['boundary_coef', 'critical_value', 'cusum', 'max_cusum', 'method',
'n_obs', 'p_value', 'reject', 'statistic']
>>> ols = sp.cusum_test(df, y="y", x=["x"], method="ols")
>>> round(ols["critical_value"], 3)
1.358
>>> res["n_obs"]
120
>>> bool(res["reject"])
False
boxcox_lambda ¶
boxcox_lambda(y: Any, period: int = 1, *, lower: float = -1.0, upper: float = 2.0, data: Optional[DataFrame] = None) -> float
Box-Cox parameter that makes the variation of a series the same across its level, by Guerrero's method.
The series is cut into consecutive cycles; lambda minimises the
coefficient of variation, across cycles, of
sd / mean^(1 - lambda).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array - like, Series or str)
|
A strictly positive series. |
required |
period
|
int
|
Seasonal period; cycles of length 2 are used for non-seasonal data. |
1
|
lower
|
float
|
Search interval. |
-1 and 2
|
upper
|
float
|
Search interval. |
-1 and 2
|
data
|
DataFrame
|
|
None
|
Returns:
| Type | Description |
|---|---|
float
|
Apply it with |
Notes
The criterion is that of R's forecast::BoxCox.lambda and
fabletools::guerrero. It is minimised to full precision, where
R's optimize stops at a tolerance of about 1e-4; expect
agreement to four decimals.
A back-transformed point forecast is the median of the forecast
distribution, not its mean. For a logarithm the mean is
exp(mu) * (1 + sigma_h^2 / 2) to second order, with sigma_h
the forecast standard deviation on the log scale.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> t = np.arange(96)
>>> y = np.exp(0.03 * t) * (1 + 0.2 * np.sin(2 * np.pi * t / 4))
>>> abs(sp.boxcox_lambda(y, period=4)) < 0.05 # the logarithm
True
References
guerrero1993time
fourier_terms ¶
Sine and cosine regressors for a seasonal pattern.
K pairs sin(2 pi k t / period), cos(2 pi k t / period),
k = 1..K, describe a smooth seasonal pattern with 2K
coefficients instead of period - 1 dummies. They handle long
and non-integer periods (52.18 weeks in a year) and several
seasonal patterns at once, as regressors of :func:statspai.arima
(dynamic harmonic regression) or of :func:statspai.regress.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
(int, Series, DataFrame or Index)
|
Number of rows; or an object whose length and index are used. |
required |
period
|
float
|
Length of the seasonal cycle, in observations. |
required |
K
|
int
|
Number of harmonics, at most |
required |
start
|
int
|
Time of the first row. The regressors of the |
1
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns |
Notes
The values equal R's forecast::fourier(x, K) and, for the future
rows, fourier(x, K, h). Choose K by AICc: every extra pair
costs two parameters.
Examples:
>>> import statspai as sp
>>> X = sp.fourier_terms(8, period=4, K=2)
>>> list(X.columns)
['sin1_4', 'cos1_4', 'cos2_4']
>>> sp.fourier_terms(2, period=4, K=1, start=9).round(6).values.tolist()
[[1.0, 0.0], [0.0, -1.0]]
References
hyndman2026fpppy
ljungbox ¶
ljungbox(x: Any, lags: Union[int, Sequence[int], None] = None, *, model_df: Optional[int] = None, method: str = 'ljung-box', period: int = 1, data: Optional[DataFrame] = None) -> DataFrame
Ljung-Box (or Box-Pierce) portmanteau test of no autocorrelation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array-like, pd.Series, str or fitted model
|
The series, usually residuals; a column name when |
required |
lags
|
int or sequence of int
|
Number of autocorrelations in the statistic. An integer gives one test at that lag, a sequence one test per entry. Default: 10 for non-seasonal data and twice the period for seasonal data, capped at a fifth of the sample. |
None
|
model_df
|
int
|
Number of ARMA parameters estimated in the model the residuals
come from; the reference distribution is chi-squared with
|
None
|
method
|
('ljung-box', 'box-pierce')
|
|
"ljung-box"
|
period
|
int
|
Seasonal period, used only for the default number of lags. |
1
|
data
|
DataFrame
|
|
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Indexed by |
Notes
Q = n (n + 2) sum_{k=1}^{L} r_k^2 / (n - k) (Ljung and Box, 1978)
or Q = n sum r_k^2 (Box and Pierce, 1970). Equal to R's
Box.test(x, lag, type, fitdf), Stata's wntestq and
statsmodels' acorr_ljungbox(x, lags, model_df=).
A small p-value says the residuals are autocorrelated: the model leaves forecastable structure behind, and its prediction intervals are not to be trusted.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> e = rng.normal(size=200)
>>> out = sp.ljungbox(e, lags=10)
>>> out.index.tolist(), list(out.columns)
([10], ['statistic', 'df', 'p_value'])
>>> walk = np.cumsum(e)
>>> bool(sp.ljungbox(walk, lags=10)["p_value"].iloc[0] < 0.001)
True
References
ljung1978measure, box1970distribution
ndiffs ¶
Number of first differences that make a series stationary.
The series is differenced until a KPSS test no longer rejects level
stationarity at alpha, at most max_d times.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array - like, Series or str)
|
|
required |
alpha
|
(0.1, 0.05, 0.025, 0.01)
|
Level of each KPSS test (the levels its critical values are tabulated for). |
0.10
|
max_d
|
int
|
|
2
|
data
|
DataFrame
|
|
None
|
Returns:
| Type | Description |
|---|---|
int
|
|
Notes
The rule of Hyndman and Khandakar (2008) and of R's
forecast::ndiffs(test="kpss"), including its lag truncation
trunc(3 sqrt(n) / 13). :func:statspai.arima with auto=True
uses it to fix d before comparing models by AICc, because
likelihoods of differently differenced series are not comparable.
For a seasonal series take the seasonal differences first, see
:func:statspai.nsdiffs.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> e = rng.normal(size=300)
>>> sp.ndiffs(e), sp.ndiffs(np.cumsum(e))
(0, 1)
References
hyndman2008automatic, kwiatkowski1992testing
nsdiffs ¶
nsdiffs(y: Any, period: int, *, threshold: float = 0.64, max_D: int = 1, data: Optional[DataFrame] = None) -> int
Number of seasonal differences a series needs.
A seasonal difference is taken while the strength of seasonality of
an STL decomposition exceeds threshold.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array - like, Series or str)
|
|
required |
period
|
int
|
Seasonal period. |
required |
threshold
|
float
|
Strength of seasonality above which a seasonal difference is taken; 0.64 minimised forecast errors on the M3 competition data. |
0.64
|
max_D
|
int
|
|
1
|
data
|
DataFrame
|
|
None
|
Returns:
| Type | Description |
|---|---|
int
|
|
Notes
The strength is max(0, 1 - Var(R) / Var(S + R)) from
:func:statspai.stl with its defaults, the measure of Wang, Smith
and Hyndman (2006); the rule is that of R's
forecast::nsdiffs(test="seas").
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> t = np.arange(120)
>>> rng = np.random.default_rng(0)
>>> y = 10 * np.sin(2 * np.pi * t / 12) + rng.normal(size=120)
>>> sp.nsdiffs(y, 12), sp.nsdiffs(rng.normal(size=120), 12)
(1, 0)
References
wang2006characteristic, hyndman2008automatic
seasonal_dummies ¶
Seasonal indicator regressors for a regression with a seasonal pattern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
(int, Series, DataFrame or Index)
|
Number of rows; or an object whose length and index are used. |
required |
period
|
int
|
Seasonal period. |
required |
start
|
int
|
Time of the first row; row |
1
|
drop_first
|
bool
|
Leave out the indicator of season 1, the reference season of a regression with an intercept (the "dummy variable trap"). |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns |
Notes
R's forecast::seasonaldummy and the season term of tslm
build the same columns (with the last season as the reference in
seasonaldummy). With a long period prefer
:func:statspai.fourier_terms.
Examples:
>>> import statspai as sp
>>> sp.seasonal_dummies(5, 4).values.tolist()
[[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 0, 0]]
References
hyndman2026fpppy
unitroot ¶
unitroot(data: Union[DataFrame, Series, ndarray], y: Optional[str] = None, *, test: str = 'adf', trend: str = 'c', lags: Union[int, str] = 'aic', max_lags: Optional[int] = None, time: Optional[str] = None, alpha: float = 0.05) -> UnitRootResult
Unit-root test for one series: ADF, DF-GLS, Phillips-Perron or KPSS.
Equivalent to Stata's dfuller / dfgls / pperron / kpss,
R's urca::ur.df / urca::ur.ers / urca::ur.pp /
urca::ur.kpss and statsmodels.tsa.stattools.adfuller / kpss.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
(DataFrame, Series or array)
|
The series, in time order (or pass |
required |
y
|
str
|
Column to test when |
None
|
test
|
('adf', 'dfgls', 'pp', 'kpss')
|
|
"adf"
|
trend
|
('c', 'ct', 'n')
|
Deterministic terms under the alternative: a constant, a constant
and a linear trend, or nothing ( |
"c"
|
lags
|
int or {'aic', 'bic'}
|
Lagged differences in the test regression. An information criterion
searches |
"aic"
|
max_lags
|
int
|
Upper end of the search. Default: Schwert's (1989) rule
|
None
|
time
|
str
|
Column to sort a DataFrame by first. |
None
|
alpha
|
float
|
Level for |
0.05
|
Returns:
| Type | Description |
|---|---|
UnitRootResult
|
|
Raises:
| Type | Description |
|---|---|
MethodIncompatibility
|
Unknown |
DataInsufficient
|
Too few observations for the requested lag order, a constant series, or a series the test regression fits exactly. |
Notes
Rejecting means the data are unlikely under a unit root. Not rejecting is not evidence of a unit root: these tests have little power against a root of, say, 0.95 in a few decades of quarterly data.
The ADF critical values are MacKinnon's response surface, not the interpolated Fuller table Stata prints, so they differ in the second decimal; the statistic is identical.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> e = rng.normal(size=300)
>>> df = pd.DataFrame({"walk": np.cumsum(e)})
>>> df["ar"] = 0.0
>>> for t in range(1, 300):
... df.loc[t, "ar"] = 0.5 * df.loc[t - 1, "ar"] + e[t]
>>> bool(sp.unitroot(df, "walk").reject)
False
>>> bool(sp.unitroot(df, "ar").reject)
True
>>> gls = sp.unitroot(df, "ar", test="dfgls", lags=1)
>>> gls.test
'DF-GLS'
References
[@dickey1979distribution], [@said1984testing], [@elliott1996efficient], [@mackinnon1994approximate], [@schwert1989tests], [@phillips1988testing], [@kwiatkowski1992testing]
granger_causality ¶
granger_causality(var_result: Optional[VARResult] = None, data: Optional[DataFrame] = None, caused: Optional[str] = None, causing: Optional[str] = None, lags: Optional[int] = None, id: Optional[str] = None, time: Optional[str] = None) -> Dict[str, Any]
Granger causality test.
Tests whether causing variable Granger-causes caused variable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var_result
|
VARResult
|
Pre-estimated VAR model. |
None
|
data
|
DataFrame
|
Data (if var_result not provided). |
None
|
caused
|
str
|
Variable being tested for causation. |
None
|
causing
|
str or list of str
|
Variable(s) hypothesised to cause; a list tests all their lags
jointly (Stata |
None
|
lags
|
int
|
Number of lags (if fitting new VAR). |
None
|
id
|
str
|
Unit and period columns of a panel in |
None
|
time
|
str
|
Unit and period columns of a panel in |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Keys: 'F_stat', 'p_value', 'df1', 'df2', 'chi2', 'chi2_p_value',
'caused', 'causing', 'reject'. |
Examples:
x Granger-causes y by construction, but not vice versa:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(5)
>>> T = 200
>>> x = np.zeros(T)
>>> y = np.zeros(T)
>>> for t in range(1, T):
... x[t] = 0.5 * x[t - 1] + rng.normal()
... y[t] = 0.3 * y[t - 1] + 0.4 * x[t - 1] + rng.normal()
>>> df = pd.DataFrame({"y": y, "x": x})
>>> vr = sp.var(df, variables=["y", "x"], lags=2)
>>> gc = sp.granger_causality(vr, caused="y", causing="x")
>>> print(gc["p_value"] < 0.05) # x helps predict y
True
Or fit the VAR implicitly from data:
>>> gc2 = sp.granger_causality(data=df, caused="x", causing="y", lags=2)
>>> print(gc2["reject"]) # y does not Granger-cause x
False
References
[@granger1969investigating], [@dumitrescu2012testing]
irf ¶
irf(var_result: VARResult, periods: int = 20, impulse: Optional[str] = None, response: Optional[str] = None, orthogonal: bool = True, cumulative: bool = False, sigma_df: Optional[str] = None, *, ci: Optional[str] = None, alpha: float = 0.05, reps: int = 1000, seed: Optional[int] = None, boot: str = 'kilian') -> Dict[str, Any]
Compute impulse response functions from VAR.
Simple responses are the MA coefficients Phi_s of the VAR;
orthogonalised responses are Phi_s P with P the lower Cholesky
factor of the residual covariance (variable order = Cholesky order).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var_result
|
VARResult
|
Estimated VAR model. |
required |
periods
|
int
|
Number of periods for IRF. |
20
|
impulse
|
str
|
Impulse variable (if None, all). |
None
|
response
|
str
|
Response variable (if None, all). |
None
|
orthogonal
|
bool
|
Orthogonalized (Cholesky) IRF. |
True
|
cumulative
|
bool
|
Return cumulative responses |
False
|
sigma_df
|
(ml, unbiased)
|
Divisor of the residual covariance that the orthogonalisation uses:
|
'ml'
|
ci
|
(None, asymptotic, bootstrap)
|
Add standard errors and a band. |
None
|
alpha
|
float
|
The band has nominal pointwise coverage |
0.05
|
reps
|
int
|
Bootstrap replications. |
1000
|
seed
|
int
|
Seed of the bootstrap draws. |
None
|
boot
|
(kilian, efron, hall)
|
Bootstrap band. |
'kilian'
|
Returns:
| Type | Description |
|---|---|
dict
|
Keys: 'irf' (dict of arrays), 'periods'; with |
Notes
Both bands are pointwise and condition on the lag order. The
asymptotic standard error of an orthogonalised response at horizon 0
comes from the residual covariance alone and is zero for the
responses the Cholesky ordering sets to zero. Near a unit root the
normal approximation is poor and the bootstrap replicates are biased
towards zero persistence. boot='kilian' is built for that case; it
removes the first-order bias and keeps every replicate stationary, and
it is not a unit-root method either.
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(3)
>>> T = 200
>>> x = np.zeros(T); y = np.zeros(T)
>>> for t in range(1, T):
... x[t] = 0.5 * x[t - 1] + rng.normal()
... y[t] = 0.3 * y[t - 1] + 0.4 * x[t - 1] + rng.normal()
>>> df = pd.DataFrame({"y": y, "x": x})
>>> vr = sp.var(df, variables=["y", "x"], lags=2)
>>> out = sp.irf(vr, periods=10, impulse="x", response="y")
>>> sorted(out.keys())
['irf', 'periods']
>>> len(out["irf"]["x -> y"]) # s = 0, 1, ..., 10
11
>>> out["periods"][0]
0
>>> bool(np.isfinite(out["irf"]["x -> y"]).all())
True
References
[@lutkepohl2005new], [@runkle1987vector], [@kilian1998small], [@kilian2017structural]
varsoc ¶
varsoc(data: DataFrame, variables: Optional[Sequence[str]] = None, *, maxlag: int = 4, trend: str = 'c', alpha: float = 0.05, exog: Optional[Sequence[str]] = None) -> DataFrame
Lag-order selection statistics for a VAR.
Fits VAR(0) .. VAR(maxlag) on the same observations (the first
maxlag are set aside for every model, so the likelihoods are
comparable) and reports, for each order, the log likelihood, the
likelihood-ratio test against the order below, the final prediction
error and the Akaike, Hannan-Quinn and Schwarz criteria.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The series, in time order. |
required |
variables
|
sequence of str
|
Endogenous variables. Default: every numeric column. |
None
|
maxlag
|
int
|
Highest order considered. |
4
|
trend
|
('c', 'ct', 'n')
|
Deterministic terms: constant, constant and trend, or none. |
'c'
|
alpha
|
float
|
Level of the sequential likelihood-ratio test. |
0.05
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Indexed by lag, with columns |
Notes
The criteria are scaled by the number of observations, AIC = (-2 LL +
2 m) / T with m the total number of coefficients, as in Stata's
varsoc and Lütkepohl's text; R's vars::VARselect reports
log|Sigma| + 2 m / T, which ranks the orders the same way.
SBIC and HQIC are consistent for the true order; AIC and FPE
overestimate it with positive probability in large samples but can
forecast better in small ones. When they disagree, check the residuals
of the smaller model (sp.estat(fit, 'varlmar')) before settling.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(1)
>>> T = 300
>>> y = np.zeros((T, 2))
>>> for t in range(1, T):
... y[t] = 0.5 * y[t - 1] + rng.normal(size=2)
>>> table = sp.varsoc(pd.DataFrame(y, columns=["a", "b"]), maxlag=3)
>>> int(table.attrs["selected"]["SBIC"])
1
References
lutkepohl2005new
vec ¶
vec(data: DataFrame, variables: Optional[Sequence[str]] = None, *, lags: int = 1, rank: int = 1, trend: str = 'c', alpha: float = 0.05) -> VECResult
Vector error-correction model (Johansen maximum likelihood).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The series in levels, in time order. |
required |
variables
|
sequence of str
|
The series of the system. Default: every numeric column. Their order
matters for the normalisation: the first |
None
|
lags
|
int
|
Number of lagged differences, as in :func: |
1
|
rank
|
int
|
Number of cointegrating relations, |
1
|
trend
|
('c', 'rc', 'n', 'rt', 'ct')
|
|
'c'
|
alpha
|
float
|
One minus the confidence level (kept on the result for reporting). |
0.05
|
Returns:
| Type | Description |
|---|---|
VECResult
|
|
Notes
With an unrestricted constant v the model writes v = alpha * mu +
gamma with gamma orthogonal to alpha: mu is reported as
the constant of the cointegrating relation (no standard error, it is
not separately estimated) and gamma as the constant of each
equation.
The cointegrating vector is super-consistent, but its normalisation is a
choice: a relation that is insignificant in the first variable cannot be
solved for it. If alpha of an equation is not different from zero,
that series is weakly exogenous for the long-run parameters.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> common = np.cumsum(rng.normal(size=400))
>>> df = pd.DataFrame({
... "y": common + rng.normal(scale=0.5, size=400),
... "x": 0.5 * common + rng.normal(scale=0.5, size=400),
... })
>>> fit = sp.vec(df, ["y", "x"], lags=1, rank=1)
>>> bool(abs(fit.beta.loc["x", "_ce1"] + 2.0) < 0.3)
True
>>> int(fit.stability().attrs["unit_moduli"])
1
References
johansen1991estimation; lutkepohl2005new