Nonlinear and regime-switching univariate models#
This page covers autoregressions whose conditional mean changes with a regime: a hard switch on an observed variable (SETAR, TAR), a smooth blend (LSTAR, ESTAR), a latent Markov chain (Markov-switching AR), or a mean function learned by a neural network (AR-NN, TAR-NN). Use it when a linear AR leaves structure in the residuals that depends on the level or sign of the series. Linear ARMA models are in Univariate linear models, volatility models in Volatility models, and the vector versions of everything here in Nonlinear, time-varying and regime-switching VARs.
The family at a glance#
Class |
Module |
Model |
Use when |
|---|---|---|---|
Two AR regimes split by \(y_{t-d} \le r\) |
The regime is a lag of the series and switches abruptly |
||
Two AR regimes split by an external \(z_{t-d} \le r\) |
An observed driver (a spread, an index) decides the regime |
||
Two AR regimes blended by a logistic weight in \(y_{t-d}\) |
Asymmetry with a direction (contractions versus expansions) |
||
Two AR regimes blended by an exponential weight in \(y_{t-d}\) |
Mean reversion that strengthens with distance from equilibrium |
||
Intercept, AR block and variance switch with a latent chain |
Regimes are not observable (recession dating, volatility states) |
||
\(y_t = f(y_{t-1}, \ldots, y_{t-p}) + \varepsilon_t\), \(f\) learned |
The shape of the nonlinearity is unknown and prediction is the goal |
||
One learned function per side of a fixed threshold |
A known split point with unknown dynamics on each side |
All seven take a one-dimensional array and are fitted with fit(). Only
MarkovSwitchingAR takes
arguments in fit, because its likelihood is multimodal and the answer
depends on the starting values and the seed.
Test for linearity before fitting#
A threshold or smooth-transition model claims that the linear AR is wrong
in a specific way. Test that claim first, on the linear model’s own terms,
with cultivars.diagnostics.nonlinearity (details in
Diagnostics and specification testing). Every test takes the raw series and fits the
AR(\(p\)) null internally, so the degrees of freedom are always right.
The threshold and transition parameters are not identified under linearity
(Davies, 1987), so a plain likelihood-ratio test against the AR is invalid;
these tests are the valid route.
Function |
What a rejection points to |
|---|---|
A threshold (Tsay, 1989, arranged autoregression) |
|
A SETAR, by a bootstrap sup-F over the threshold grid (Hansen, 1996); the record carries the threshold it found |
|
A smooth transition (Teräsvirta, 1994); |
|
Some smooth function of the fit left in the residual (no regime implied) |
The running example is a two-regime SETAR: the AR coefficient is 0.7 when the previous value is at or below zero and -0.3 above it.
import numpy as np
from cultivars.diagnostics.nonlinearity import (
hansen_threshold,
ramsey_reset,
terasvirta,
tsay,
)
rng = np.random.default_rng(42)
y = np.zeros(400)
for t in range(1, 400):
phi = 0.7 if y[t - 1] <= 0.0 else -0.3
y[t] = phi * y[t - 1] + rng.standard_normal()
for test in (tsay(y, order=1), terasvirta(y, order=1), ramsey_reset(y, order=1)):
print(f"{test.name:11s} p={test.pvalue:.4f} -> {test.alternative}")
sup = hansen_threshold(y, order=1, replications=200, seed=0)
print(round(sup.statistic, 2), round(sup.pvalue, 3), round(sup.threshold, 3))
print({k: round(v, 2) for k, v in sup.critical_values.items()})
Tsay p=0.0000 -> threshold
Teräsvirta p=0.0000 -> smooth transition
RESET p=0.0000 -> smooth misspecification
62.4 0.0 0.346
{'1%': 19.42, '5%': 13.44, '10%': 10.96}
All four reject. Two further points before choosing a model. The tests
target the conditional mean, and neglected conditional heteroskedasticity
inflates every one of them, so run an ARCH test on the linear residuals
first (see Volatility models). And the Teräsvirta record always carries a
suggestion, even when it does not reject; read it only after
reject() is True.
Threshold autoregressions: SETAR and TAR#
The model#
Two linear autoregressions, selected by an indicator on an observed transition variable (Tong, 1990):
with \(z_t = y_t\) for SETAR
and an external series for TAR.
The sum of squares is a step function of \(r\), so no gradient method
applies. The estimator is an exhaustive grid search: n_grid quantiles
of the transition variable with trim removed from each tail, two
least-squares regressions at every candidate, and (for SETAR with
delay=None) every delay in 1..order on one common sample. The
threshold estimate is super-consistent (Chan, 1993), so the coefficients
are read as OLS conditional on it.
A minimal fit#
from cultivars.univariate.autoregression import AR
from cultivars.univariate.threshold import SETAR, TAR
hard = SETAR(y, order=2).fit()
print(hard.delay, hard.searched_delay, round(hard.threshold, 3))
print(hard.n_lower, hard.n_upper, hard.nobs, hard.n_params)
print(hard.lower_params.round(3), hard.upper_params.round(3))
print(hard.is_regimewise_stationary)
1 True 0.347
325 73 398 7
[ 0.056 0.858 -0.132] [-0.324 -0.082 0.188]
True
lower_params and upper_params are [const, ar.L1, ..., ar.Lp].
params is the same information as a dict keyed lower.const,
lower.ar.L1, …, threshold, sigma2. The summary prints the
coefficient table and the regime split:
print(hard.summary())
SETAR(2, d=1) Results
================================================================================
Model: SETAR(2, d=1) Log-likelihood: -535.834
Delay: 1 AIC: 1085.669
Threshold: 0.3474 BIC: 1113.574
Observations: 398 HQIC: 1096.722
--------------------------------------------------------------------------------
coef
lower.const 0.0563
lower.ar.L1 0.8583
lower.ar.L2 -0.1322
upper.const -0.3243
upper.ar.L1 -0.0822
upper.ar.L2 0.1877
threshold 0.3474
sigma2 0.8648
================================================================================
Regime-wise stationary: True max |root| lower = 0.6572, upper = 0.4763
Regime split: 325 below, 73 above (18.3% upper).
The delay was selected by the same grid search as the threshold; d = 1 minimized
the total sum of squares.
The threshold is not identified under linearity, so information criteria rank
specifications but do not test for a threshold.
Standard errors are not yet available for this estimator.
Against the linear AR, rank by information criteria. compare requires
both fits to share the effective sample, which holds here because both
drop order = 2 observations:
linear = AR(y, order=2).fit()
table = hard.compare(linear, criterion="bic")
print([row[0] for row in table.rows])
['SETAR(2, d=1)', 'AR(2)']
The likelihood-ratio test is refused rather than computed, because its chi-squared reference is wrong under the Davies problem:
SpecificationError
Specification choices#
Argument |
Default |
Meaning |
|---|---|---|
|
required |
AR order \(p\) within each regime, at least 1 |
|
|
Lag \(d\) of the transition variable. |
|
|
Fraction removed from each tail of the threshold grid, in
|
|
|
Candidate thresholds per delay; splits leaving a regime fewer than
|
|
TAR only, required |
The external driver, same length as |
For TAR the threshold is in the
units of the driver and threshold_values holds the lagged driver
aligned with the residuals:
rng = np.random.default_rng(1)
driver = rng.standard_normal(400)
x = np.zeros(400)
for t in range(1, 400):
phi = 0.5 if driver[t - 1] <= 0.0 else -0.5
x[t] = phi * x[t - 1] + rng.standard_normal()
ext = TAR(x, order=1, threshold_variable=driver).fit()
print(ext.self_exciting, ext.delay, round(ext.threshold, 3))
print(bool(np.allclose(ext.threshold_values, driver[:-1])))
print(ext.lower_params.round(2), ext.upper_params.round(2))
False 1 0.089
True
[-0.1 0.53] [ 0.04 -0.47]
What the result gives you#
Both classes return a SETARResult.
Estimates:
threshold,delay,lower_params,upper_params,sigma2(one pooled variance),ssr,params.Regimes:
threshold_values(the lagged transition variable over the effective sample),regime_weight(1.0 above the threshold, 0.0 at or below),n_lower,n_upper,upper_fraction.Stability per regime:
lower_stability,upper_stability,is_regimewise_stationary. Regime-wise stationarity is sufficient, not necessary, for the process to be stationary; an explosive regime that the other regime pulls back from is admissible.Comparison:
information_criteria,compare.simulate(n, seed=...)andnonlinear_state_space(measurement_variance)(SETAR only; see Simulation and state-space emission).fittedvalues,residandto_pandas/to_polars/to_dictfor the aligned series.
Smooth-transition autoregressions: LSTAR and ESTAR#
The model#
The indicator is replaced by a weight \(G \in [0, 1]\) that moves continuously with \(z_t = y_{t-d}\) (Teräsvirta, 1994):
where \(s\) is the sample standard deviation of the transition
variable, so gamma is a speed per standard deviation and comparable
across series. The logistic weight is monotone (regimes are low and high).
The exponential weight is symmetric about \(c\): the “lower” regime is
the middle (weight 0 at \(z = c\)) and the “upper” regime is both
tails. Conditional on \((\gamma, c)\) the model is linear, so the two
coefficient blocks are concentrated out by least squares and only
\((\gamma, c)\) are searched, by multi-start Nelder-Mead seeded from
the best hard split. The fit is deterministic; there is no seed.
LSTAR#
from cultivars.univariate.smooth_transition import ESTAR, LSTAR
rng = np.random.default_rng(1)
s = np.zeros(500)
for t in range(1, 500):
w = 1.0 / (1.0 + np.exp(-6.0 * s[t - 1]))
s[t] = (1 - w) * (0.4 + 0.6 * s[t - 1]) + w * (-0.4 - 0.5 * s[t - 1])
s[t] += 0.4 * rng.standard_normal()
print(terasvirta(s, order=1).suggestion)
smooth = LSTAR(s, order=1).fit()
print(smooth.transition, round(smooth.gamma, 3), round(smooth.transition_scale, 3))
print(round(smooth.gamma / smooth.transition_scale, 2), round(smooth.threshold, 3))
print(smooth.lower_params.round(2), smooth.upper_params.round(2))
print(list(smooth.params))
print(smooth.is_effectively_abrupt, round(smooth.upper_fraction, 3))
LSTAR
logistic 2.661 0.462
5.75 -0.114
[0.48 0.69] [-0.32 -0.67]
['lower.const', 'lower.ar.L1', 'upper.const', 'upper.ar.L1', 'threshold', 'gamma', 'sigma2']
False 0.502
gamma / transition_scale is the speed in the units of the series (the
truth here is 6). For the logistic form upper_fraction is the mean
weight, not a count.
When the fitted speed is so large that the weight is an indicator,
is_effectively_abrupt turns True (logistic, gamma > 100). The
likelihood is then flat in gamma, the reported value is a lower bound,
and SETAR says the same thing
with two fewer parameters. On this sample the hard switch wins on BIC even
though the truth is smooth:
rivals = [ESTAR(s, order=1).fit(), SETAR(s, order=1).fit(), AR(s, order=1).fit()]
table = smooth.compare(*rivals, criterion="bic")
for row in table.rows:
print(row[0], row[2], row[4])
SETAR(1, d=1) 5 0.000
LSTAR(1, d=1) 6 5.870
ESTAR(1, d=1) 6 5.958
AR(1) 3 149.984
ESTAR#
The canonical ESTAR is a real exchange rate under transaction costs: persistent near equilibrium, mean-reverting far from it (Kapetanios, Shin and Snell, 2003). Exponential fits need more data than logistic ones to pin down \(c\); 800 observations are used here.
rng = np.random.default_rng(0)
q = np.zeros(800)
for t in range(1, 800):
w = 1.0 - np.exp(-3.0 * q[t - 1] ** 2)
q[t] = (1 - w) * 0.95 * q[t - 1] + w * 0.2 * q[t - 1]
q[t] += 0.3 * rng.standard_normal()
print(terasvirta(q, order=1).suggestion)
band = ESTAR(q, order=1).fit()
print(round(band.threshold, 3), round(band.gamma, 3))
print(band.lower_params.round(2), band.upper_params.round(2))
ESTAR
-0.098 0.41
[0.01 0.9 ] [0.04 0.23]
The “lower” block (near the threshold) carries the near-unit-root persistence 0.9, the “upper” block (the tails) the fast reversion 0.23.
Specification choices and outputs#
Both constructors take endog, order (at least 1) and delay
(default 1). The delay is not searched; fit several and rank them on a
common sample. Both return a
STARResult, which carries
everything listed for SETARResult above (threshold,
lower_params, upper_params, regime_weight, the per-regime
stability verdicts, simulate, nonlinear_state_space) plus
transition, gamma, transition_scale and
is_effectively_abrupt. regime_weight reproduces the estimator’s
weight exactly, so (1 - w) * X @ lower_params + w * X @ upper_params
equals fittedvalues.
Markov-switching autoregression#
The model#
A \(K\)-regime autoregression whose intercept, AR coefficients and variance may each switch with a latent first-order Markov chain \(S_t\) (Hamilton, 1989):
This is the intercept-switching form. Conditional on the observed lags
each regime is linear, so the Hamilton filter runs on \(K\) states
rather than the \(K^{p+1}\) that Hamilton’s mean-switching form
needs; the two coincide only at \(p = 0\). Estimation is EM (Hamilton,
1990): the E-step is the Hamilton filter and Kim (1994) smoother, the
M-step updates \(P\) from expected transition counts and the
coefficients by probability-weighted least squares. Which blocks switch is
reported in Krolzig’s (1997) notation by specification: I
intercept, A AR block, H variance. Hamilton’s recession model,
MSIH(2)-AR(4), is MarkovSwitchingAR(y, order=4, n_regimes=2).
A minimal fit#
The example has a low-mean quiet regime and a high-mean volatile one, with a common AR coefficient of 0.5:
from cultivars.univariate.regime_switching import MarkovSwitchingAR
rng = np.random.default_rng(3)
P = np.array([[0.95, 0.05], [0.10, 0.90]])
state = np.zeros(400, dtype=int)
for t in range(1, 400):
state[t] = rng.choice(2, p=P[state[t - 1]])
m = np.zeros(400)
for t in range(1, 400):
level, scale = [(-0.5, 0.5), (1.0, 1.0)][state[t]]
m[t] = level + 0.5 * m[t - 1] + scale * rng.standard_normal()
ms = MarkovSwitchingAR(m, order=1, n_regimes=2).fit(seed=0, n_init=4)
print(ms.specification, ms.label_ordering, ms.converged, ms.nobs, ms.n_params)
print(ms.intercepts.round(2), ms.ar_params.ravel().round(2), ms.variances.round(2))
print(ms.transition.round(3))
print(ms.expected_durations.round(1), ms.ergodic_prob.round(3))
MSIH(2)-AR(1) intercept True 399 7
[-0.5 1.17] [0.48 0.48] [0.24 1.02]
[[0.959 0.041]
[0.142 0.858]]
[24.4 7. ] [0.776 0.224]
ar_params has shape (K, p); a non-switching block is stored with
identical rows. n_params counts the \(K(K-1)\) free transition
probabilities plus each block once per regime that switches it: here
2 + 2 intercepts + 1 AR + 2 variances = 7.
Reading regime probabilities and regimes#
The regime is never observed. Three posteriors are returned, each of shape
(nobs, K) and aligned with the last nobs observations:
probabilities("smoothed")(the default, alsosmoothed_prob): \(\Pr(S_t \mid y_{1:T})\), for dating regimes after the fact;probabilities("filtered")(filtered_prob): \(\Pr(S_t \mid y_{1:t})\), the real-time view;probabilities("predicted")(predicted_prob): \(\Pr(S_t \mid y_{1:t-1})\), the one-step-ahead state forecast.
smoothed = ms.probabilities()
filtered = ms.probabilities("filtered")
print(smoothed.shape, smoothed[:3].round(3).tolist())
print(round(float(np.mean(ms.most_likely_regime == state[1:])), 3))
print(round(float(np.mean(filtered.argmax(axis=1) == state[1:])), 3))
print(ms.regime_shares.round(3), round(ms.regime_uncertainty, 3))
print(round(ms.unconditional_mean, 3))
(399, 2) [[0.991, 0.009], [0.996, 0.004], [0.998, 0.002]]
0.98
0.94
[0.776 0.224] 0.115
-0.245
most_likely_regime is the pointwise argmax of the smoothed posterior,
not the Viterbi path; it can contain a one-period switch that, as a
sequence, has almost no posterior mass. regime_shares (average
smoothed probability) is the honest frequency count; compare it with
ergodic_prob. regime_uncertainty is the mean posterior entropy
normalized to [0, 1]: near 0 the regimes are cleanly separated, near
1 the data barely distinguish them.
Two tables read across regimes rather than down parameters:
print(ms.regime_table())
MSIH(2)-AR(1) regimes
================================================================================
Ordering: ascending intercept Observations: 399
--------------------------------------------------------------------------------
regime const sigma2 ergodic duration share
0 -0.5038 0.2381 0.7760 24.36 0.7764
1 1.1700 1.0212 0.2240 7.03 0.2236
================================================================================
'ergodic' is the stationary probability implied by the transition matrix;
'share' is the average smoothed probability actually observed. 'duration' is 1 /
(1 - P_jj), in periods.
transition_table() prints \(P\) with rows indexed by the origin
regime.
Specification and estimation choices#
Argument |
Default |
Meaning |
|---|---|---|
|
required |
AR order \(p\); |
|
|
\(K\), at least 2 |
|
|
Intercept switches |
|
|
Variance switches |
|
|
AR block switches; multiplies the AR parameter count by \(K\) |
|
|
Iteration cap for the refining EM run |
|
|
Tolerance on the log-likelihood increment |
|
|
Starting values screened; the first uses quantile intercepts and a 0.9 diagonal, the rest draw both at random |
|
|
EM iterations used to score each start; only the best is refined |
|
|
Seed or |
At least one block must switch. Regime labels are sorted to make two runs
comparable: by ascending intercept when it switches, else by variance,
else by the first AR coefficient; label_ordering names the rule. Under
a variance-only model, regime 0 is the quiet regime, not a low-mean one.
Testing against nested specifications#
A likelihood-ratio test that holds \(K\) fixed is valid and allowed; here, whether the variance switches:
MSI(2)-AR(1) 40.19 1 True
A test that changes \(K\) (including two regimes against a linear AR)
is refused with a SpecificationError: under the smaller model the
extra regime’s parameters are unidentified and its transition
probabilities sit on the boundary (Hansen, 1992). Rank such models by
compare instead, or bootstrap.
What the result gives you#
MarkovSwitchingARResult
carries transition, intercepts, ar_params, variances,
params (keys const[j], ar.L1[j], sigma2[j],
p[i->j]), the three posteriors, ergodic_prob,
expected_durations, n_iter, converged,
regime_stability(j), is_regimewise_stationary,
unconditional_mean (None if any regime is non-stationary),
simulate, simulate_regimes and state_space. fittedvalues
are posterior-weighted means \(\sum_j \Pr(S_t = j)\, m_{j,t}\), not
the fit of any one regime; to_pandas adds one smoothed-probability
column per regime and the most likely regime.
state_space re-applies the fitted chain to data the model was not
estimated on, the natural out-of-sample check for a regime model:
True
(99, 2)
Learned conditional means: ARNN and TARNN#
The model#
The mean is a function learned from lagged levels by a pluggable training engine (Kuan and White, 1994):
for the default engine, a one-hidden-layer perceptron with 8 tanh units
trained by L-BFGS with an L2 penalty, 3 random restarts and a fixed seed
of 0. TARNN splits the
sample at a fixed threshold on \(z_{t-d}\) (the median by default)
and trains one network per side with a pooled variance. The variance is
concentrated out as \(\mathrm{SSR}/n\).
There is no coefficient table: the weights are unidentified up to
permuting and sign-flipping hidden units. The results expose the fitted
function through predict and report r_squared, the capacity, and
the engine.
from cultivars.univariate.mean_function import ARNN, TARNN
rng = np.random.default_rng(0)
v = np.zeros(200)
for t in range(1, 200):
v[t] = 0.8 * np.tanh(2.0 * v[t - 1]) + 0.3 * rng.standard_normal()
net = ARNN(v, order=1).fit()
print(net.nobs, net.n_learner_parameters, net.n_params, net.engine)
print(round(net.r_squared, 2), round(net.parameters_per_observation, 3))
grid = np.array([[-1.0], [0.0], [1.0]])
print(net.predict(grid).round(1), (0.8 * np.tanh(2.0 * grid[:, 0])).round(1))
199 25 26 NumpyMLPEngine
0.78 0.126
[-0.8 0.1 0.7] [-0.8 0. 0.8]
predict takes a matrix of shape (n, order) with columns
[y_{t-1}, ..., y_{t-p}]. Columns in the wrong order do not raise; they
return wrong numbers. TARNNResult
predict also needs the already-lagged threshold variable, because the
regime cannot always be read off the feature columns:
0.0 74 125 50
[-0.8 0.7]
Plugging in an engine#
Any object with fit(features, target) returning something with
predict(features) and an n_parameters attribute satisfies
MeanFunctionEngine. A
least-squares engine reproduces the linear AR and shows the contract:
from cultivars.univariate.mean_function import MeanFunctionEngine
class LinearEngine:
def fit(self, features, target):
design = np.column_stack([np.ones(len(target)), features])
beta = np.linalg.lstsq(design, target, rcond=None)[0]
class Predictor:
n_parameters = design.shape[1]
def predict(self, new):
return np.column_stack([np.ones(len(new)), new]) @ beta
return Predictor()
print(isinstance(LinearEngine(), MeanFunctionEngine))
ols = ARNN(v, order=1, engine=LinearEngine()).fit()
print(ols.engine, round(ols.llf, 3), round(AR(v, order=1).fit().llf, 3))
True
LinearEngine -40.18 -40.18
Specification choices#
ARNN(endog, *, order, engine=None):engine=Noneuses the reference perceptron.TARNN(endog, *, order, engine=None, threshold_variable=None, delay=1, threshold=None, trim=0.15):threshold=Noneuses the median of the lagged threshold variable;fitraisesNumericalErrorif a stated threshold leaves either side with less thantrimof the sample. The threshold is never searched: a grid would retrain both networks at every candidate.
Information criteria penalize the nominal weight count, which overstates
the effective degrees of freedom of a weight-decayed network. Use them to
choose capacity within one engine configuration, never against a
linear model. llf is a concentrated Gaussian likelihood at a
penalized fit, a goodness-of-fit summary. likelihood_ratio_test
against a linear null is refused.
Simulation and state-space emission#
The parametric results simulate at the fitted parameters with Gaussian
innovations. seed takes an integer or a Generator; burn
initial periods are discarded.
(300,) True
[ 0.357 -0.12 0.026 -0.469 0.456]
(1000,) [0.739 0.261]
A TAR fit cannot simulate (SpecificationError): its driver’s future is
not part of the model. The ARNN and TARNN results do not simulate.
nonlinear_state_space(measurement_variance) on SETAR, STAR, ARNN and
self-exciting TARNN results returns a
NonlinearSSM whose state is the
lag stack of the true series and whose observation adds Gaussian noise of
the stated (strictly positive) variance. Use the particle filter for the
hard-switch emitters and any of the three filters for the smooth ones
(see State-space models and filtering).
1 True
How the pieces connect#
Diagnostics first.
cultivars.diagnostics.nonlinearitysupplies the valid tests of linearity (Tsay, Hansen sup-F, Teräsvirta, RESET) andbds()for residual independence; see Diagnostics and specification testing.Comparison. Every parametric result here and the linear
ARsharecompareandinformation_criteriathrough the comparison mixin.compareraisesDimensionErrorunless all fits have the samenobs.State space.
nonlinear_state_spacehands the fitted mean tocultivars.state_space.nonlinear;MarkovSwitchingARResult’sstate_spaceis the same Hamilton-filter engine thatMarkovSwitchingSSMexposes for general measurements.Vector versions.
TVAR,STVARandMarkovSwitchingVARin Nonlinear, time-varying and regime-switching VARs.Forecast evaluation. None of these results has a
forecastmethod. For a forecast comparison (Forecasting and forecast evaluation), build point forecasts fromsimulatepaths or frompredicton a lag matrix you construct.
Nuances and gotchas#
No forecast method.
SETARResult,STARResultandMarkovSwitchingARResulthave noforecast;ARNNResultandTARNNResulthavepredict(one step, given lags you build) but nosimulate. The multi-step conditional mean of a nonlinear model is a simulation problem.No standard errors. None of the estimators on this page reports standard errors; every summary says so.
Effective samples differ. SETAR and STAR drop
max(order, delay)observations, MS-AR dropsorder.LSTAR(y, order=1, delay=2)has one fewer observation thanAR(y, order=1), andcomparebetween them raisesDimensionError. Trim the series yourself to align samples.Parameter counts are not uniform.
SETARResult.n_paramsandSTARResult.n_paramscount the coefficient blocks, the threshold and (STAR)gamma, but not the variance, whileARcounts its variance. In the BIC table above SETAR(1) hask = 5and AR(1) hask = 3: the threshold model is penalized one parameter less than a consistent count would.The threshold sits on a data point. Candidates are sample quantiles of the lagged transition variable, and
z <= ris assigned to the lower regime everywhere (estimation,regime_weight,simulate,nonlinear_state_space).Large ``gamma`` is a lower bound. When
is_effectively_abruptisTrue, refit as SETAR.gammais per standard deviation of the transition variable; divide bytransition_scalefor the raw-unit speed.ESTAR labels. For ESTAR the “lower” block is the region near the threshold, the “upper” block is both tails.
TAR refusals. A TAR result refuses
simulateandnonlinear_state_space; so does an externally driven TARNN for the latter.TARrequires a fixeddelay(default 1).Markov-switching seeds. Different seeds can land on different local optima. Every start, including the first, draws a perturbed transition matrix from the generator, so even
n_init=1depends onseed. Always passseedand checkconverged.``converged=False`` with ``n_iter == screen_iter``. If the refining EM run ends below the best screened log-likelihood, the result falls back to the screened start and reports that run’s iteration count and a
Falseconvergence flag, and the summary leads with a non-convergence warning. Refit with a differentseedor largern_initbefore trusting the numbers.Testing the number of regimes.
likelihood_ratio_testrefuses any comparison that changes \(K\), including against a non-switching result.Fitted values are mixtures. MS-AR
fittedvaluesandresidare expectations under the smoothed posterior, which uses the full sample; they are not real-time quantities. Useprobabilities("filtered")for anything that must be real time.Configuring the reference network. The default engine class,
NumpyMLPEngine, lives in the private modulecultivars.engine._internals._engines; there is no public import path for it. To changehidden_units,alpha,n_restartsor the trainingseedyou either import that private name or write your own engine.engine_configon the result records the full configuration.Network fits are slow. The reference engine takes a few seconds per fit on a few hundred observations; TARNN trains two networks.
ARNN comparisons.
comparedoes not stop you ranking an ARNN against an AR by BIC; the result is not meaningful. Two ARNN fits with different engines share the labelAR-NN(p)in the table; read thekcolumn to tell them apart.
See also#
Diagnostics and specification testing for the linearity tests and BDS.
Univariate linear models for the linear AR/ARMA baselines.
Volatility models for conditional-variance models and ARCH tests.
Nonlinear, time-varying and regime-switching VARs for TVAR, STVAR and MS-VAR.
State-space models and filtering for filtering the emitted nonlinear systems.
Nuances and known issues for package-wide conventions on refusals and comparisons.
API:
cultivars.univariate.threshold,cultivars.univariate.smooth_transition,cultivars.univariate.regime_switching,cultivars.univariate.mean_function,cultivars.diagnostics.nonlinearity.