Migrating from statsmodels¶
Part of The tsecon Guide to Time Series Econometrics. This is an adoption guide, not a tutorial: it maps calls you already know in
statsmodels.tsato their tsecon equivalents and is honest about what tsecon does not yet cover. Every Python block runs against the current library.
If you write empirical time series in Python today, you almost certainly write
statsmodels. tsecon is not a drop-in replacement — it deliberately makes
different choices — but most of the statsmodels.tsa surface has a direct
counterpart here, and the parts that do not (LP-IV and state-dependent local
projections, sign-restricted SVARs, BVARs, threshold and smooth-transition
models) are exactly the reasons to reach for tsecon. This page gets you across.
Five differences to internalize first¶
Before the table, five conventions that will trip you up if you carry
statsmodels habits over unexamined:
-
Functions return dicts and arrays, not results objects.
statsmodelsgives you a fittedResultsobject you interrogate with attributes and methods (res.params,res.summary(),res.irf(10)). tsecon gives you a plaindict(or a NumPy array, or a nested list). You readfit["params"], notfit.params. This keeps the boundary between the Rust core and Python thin and the return values trivially serializable. Formatted output is opt-in rather than absent:tsecon.summarize(fit)renders any result as an aligned report, and thetsecon.resultslayer adds richer per-family objects with.summary()and.plot_*(). All of them aredictsubclasses, sofit["params"],json, andpicklekeep working either way. -
You pass arrays, and any index is dropped. tsecon has no pandas dependency and no notion of a date index. A
DataFrame/Seriesgoes straight in — every estimator coerces any.to_numpyarray-like to float64 at the boundary — but only its values survive: a VAR sees aT x kfloat array, a panel raw arrays in a documented shape. Column order is therefore load-bearing — it is your Cholesky ordering, your variable labels, everything. Keep your own column-name list alongside the array. -
No automatic intercept.
tsecon.ols(y, x, ...)regressesyon the columns ofxexactly as given. Likestatsmodels'OLS(which also requiresadd_constant), it will not invent a constant for you — but there is noadd_constanthelper, so prepend a column of ones yourself. The VAR/BVAR and filter routines handle their own trend terms via atrend=argument. -
One
se_type=grammar for robust inference. Wherestatsmodelsspreads robust covariance acrosscov_type=/cov_kwds=and per-model flags, tsecon exposes a uniformse_type=on every regression estimator:"nonrobust","hc0"–"hc3", and"hac"(Newey-West), withmaxlags=for the HAC bandwidth. Panels add"cluster"and"driscoll_kraay". Local projections usese=with"lag_augmented"(the default, per Montiel Olea–Plagborg-Møller 2021) or"hac". The method chosen is stamped back into the result:res["se_type"]wherever the argument isse_type=,res["se_method"]for these=local projections (lp,lp_state). -
Impulse responses are nested lists, indexed
[horizon][response][shock].tsecon.var_irf(...)[h][i][j]is the response of variableiat horizonhto a shock in variablej. Horizon runs0..=horizon, so the outer length ishorizon + 1. The variance decompositionvar_fevduses the same horizon-first axis order —[horizon][variable][shock],horizonouter entries — and its innermost slice sums to one across shocks. Note the statsmodels difference:VARResults.fevd(h).decompstores the same numbers variable-major, so transpose(1, 0, 2)to compare. Convert to a NumPy array and index deliberately.
The mapping table¶
"Roadmap" marks a capability tsecon does not ship today; see the roadmap and the module specs. Everything else is a shipped function you can call now.
Diagnostics and unit roots¶
| statsmodels | tsecon | Notes |
|---|---|---|
adfuller(y) |
adf(y, regression="c", autolag=..., maxlag=None) |
Returns a dict (statistic, p_value, used_lag, nobs, crit), not a tuple. MacKinnon p-values. |
kpss(y) |
kpss(y, regression="c", nlags=None) |
Null is stationarity. Dict return. |
| — | check_stationarity(y, alpha=0.05) |
The ADF+KPSS confirmatory quadrant with a recommendation. No statsmodels analogue. |
| (run each test by hand) | check_series(data, seasonal_period=None, lags=None, alpha=0.05) |
One-call diagnostic battery: descriptives, outliers, the ADF+KPSS quadrant, Ljung-Box/ACF/PACF, ARCH-LM, Jarque-Bera, a sup-F/Bai-Perron mean-shift scan, GPH long memory, and seasonality — ending in an ordered recommendations list routing to concrete tsecon calls. 2D (n, k) input adds per-series integration, Johansen, and VAR lag selection. A tsecon convenience, not a statsmodels port. |
acf(y, nlags=n) |
acf(y, nlags=20, adjusted=False) |
Returns {"acf", "bartlett_se"}. |
pacf(y, nlags=n, method="yw") |
pacf(y, nlags=20, method="yw") |
Returns a bare array. method is "yw" or "ols". |
acorr_ljungbox(y, lags=n) |
ljung_box(y, nlags=10) |
Returns Ljung-Box and Box-Pierce for lags 1..=nlags. |
jarque_bera(x) |
jarque_bera(x) |
Dict with statistic, p_value, skewness, kurtosis, n. |
het_arch(resid) |
arch_lm(resid, nlags=...) |
Engle's ARCH-LM test. |
q_stat(...) |
use ljung_box |
Box-Pierce is the bp_stat/bp_pvalue keys. |
(none; arch.unitroot.PhillipsPerron) |
phillips_perron(y, regression="c", test_type="tau", lags=None) |
Z-tau (default) or Z-alpha with MacKinnon p-values; both come back as ztau/zalpha. Alongside it dfgls (Elliott-Rothenberg-Stock) and ng_perron — neither has a statsmodels analogue. |
zivot_andrews(y, regression=, trim=) |
zivot_andrews(y, regression="c", trim=0.15, autolag=..., lags=None) |
Single-break unit-root test; matches statsmodels. Dict stat, pvalue, crit, break_index. |
range_unit_root_test, leybourne |
— | Roadmap. |
Regression with dependent-data standard errors¶
| statsmodels | tsecon | Notes |
|---|---|---|
OLS(y, add_constant(X)).fit() |
ols(y, Xc, se_type="nonrobust") |
Prepend your own constant column Xc. |
... .fit(cov_type="HAC", cov_kwds={"maxlags": L}) |
ols(y, Xc, se_type="hac", maxlags=L, use_correction=False) |
Newey-West. The use_correction defaults differ: statsmodels cov_type="HAC" defaults the small-sample n/(n−k) factor off, tsecon defaults it on — so pass use_correction=False for a bit-for-bit migration, or add "use_correction": True to cov_kwds on the statsmodels side. At each library's own defaults the HAC bse differ by exactly sqrt(n/(n−k)). |
... .fit(cov_type="HC0".."HC3") |
ols(y, Xc, se_type="hc0".."hc3") |
Same White family. |
cov_hac, cov_nw_panel helpers |
long_run_variance(x, kernel=, bandwidth=) |
The kernel LRV as a standalone number. |
Specification and structural-break tests¶
statsmodels scatters these across stats.diagnostic; tsecon gathers them behind
a uniform (y, x) signature — pass the regression's design matrix (with its
constant column), not pre-computed residuals.
| statsmodels | tsecon | Notes |
|---|---|---|
het_white(resid, exog) |
heteroskedasticity_test(y, x, test="white") |
Pass the regression (y, x-with-constant), not residuals. Dict statistic/pvalue (LM) plus fstat/f_pvalue. |
het_breuschpagan(resid, exog) |
heteroskedasticity_test(y, x, test="breusch_pagan") |
Same signature; Breusch-Pagan variant. |
linear_reset(res, power=[2,3]) |
reset_test(y, x, max_power=3) |
Ramsey RESET functional-form F-test; fitted powers 2..=max_power of ŷ. |
breaks_cusumolsresid(resid) |
cusum_test(y, x) |
Brown-Durbin-Evans recursive-residual CUSUM; returns the path and 5% bound_lower/bound_upper. (statsmodels' test is the OLS-residual variant.) |
| (no direct Chow test) | chow_test(y, x, split=k) |
Structural-break F-test at a known 0-indexed split k. |
(only the breaks_cusumolsresid/breaks_hansen stability tests) |
bai_perron(y, x, max_breaks=m, trim=0.15) |
Bai-Perron multiple breaks (global DP partitions + sequential supF selection). No statsmodels analogue. |
| — | sup_f_test(y, x, trim=0.15) |
Andrews sup-F (Quandt) unknown-break test, Hansen (1997) p-value. No statsmodels analogue. |
Univariate models and volatility¶
| statsmodels / arch | tsecon | Notes |
|---|---|---|
ARIMA(y, order=(p,d,q)).fit() |
arima_fit(y, p, d, q, constant=True, forecast_steps=0) |
Exact-MLE. Forecast bands via conf_alpha=. |
SARIMAX(..., seasonal_order=(P,D,Q,s)) |
arima_fit(y, p, d, q, seasonal=(P, D, Q, s)) |
Multiplicative SARIMA by exact MLE, statsmodels-style parameter names (ma.L1, ma.S.L12). Note the kwarg is seasonal= and takes the 4-tuple, not seasonal_order=. Exogenous regressors — the X of SARIMAX — are roadmap. auto_arima does Hyndman-Khandakar order selection on the same engine. |
ExponentialSmoothing, ETSModel |
theta_forecast(y, steps, period) |
Only the Theta method ships; general ETS is roadmap. |
arch_model(r, vol="Garch", p, q).fit() |
garch_fit(y, vol="garch", p=1, q=1, ...) |
Also vol="gjr"/"egarch". Note: arch_model(..., o=1) silently becomes GJR in arch; in tsecon you must say vol="gjr" — o > 0 with vol="garch" raises (0.6.0). Robust SEs in se_robust. |
arch_model(..., dist="StudentsT") |
garch_fit(..., dist="studentst") |
Distribution string. |
| (score-driven / DCS) | gas_volatility(y, density="gaussian") |
GAS(1,1), Gaussian or "student_t". No statsmodels analogue. |
MarkovAutoregression(y, k_regimes, order) |
markov_switching_ar(y, k_regimes=2, order=1, switching_variance=True) |
Hamilton (1989) EM. Returns regimes, transition, durations. |
MarkovRegression |
markov_switching_ar(..., order=0) |
Set order=0 for a switching-mean model. |
UnobservedComponents, MLEModel |
local_level_smooth(y, sigma2_eps, sigma2_eta) |
Only the local-level (exact-diffuse) filter ships; general custom state space is roadmap. |
Systems: VAR, cointegration, factors¶
| statsmodels | tsecon | Notes |
|---|---|---|
VAR(df).fit(p) |
var_fit(data, lags=p, trend="c") |
data is T x k. Dict: params, sigma_u, ICs, max_root. |
res.irf(h).orth_irfs |
var_irf(data, lags, horizon, orth=True) |
Nested list [h][resp][shock]. orth=False for non-orthogonalized. |
res.irf(h).cum_effects |
var_irf(..., cumulative=True) |
Running sums. |
res.irf(h).stderr(), .errband_mc() |
var_irf_bands(data, lags, horizon, method="asymptotic") |
Lütkepohl delta-method SEs, or method="bootstrap" for a Kilian residual bootstrap with optional bias correction. Returns point/se/lower/upper in the var_irf layout; lower/upper are pointwise unless you ask for band="sup-t"/"sidak"/"bonferroni". |
res.fevd(h).decomp |
var_fevd(data, lags, horizon) |
[horizon][variable][shock], same axis order as the IRFs; statsmodels' .decomp is variable-major — transpose (1, 0, 2) to compare. |
res.forecast_interval(y, h) |
var_forecast(data, lags, steps, alpha=0.05) |
Dict {"point", "lower", "upper"}. |
res.test_causality(caused, causing) |
var_granger(data, caused, causing, lags) |
F-test; matches statsmodels' test_causality. Index lists, not names. |
grangercausalitytests(...) |
var_granger(...) |
Same test, VAR-based. |
coint_johansen(data, det, k_ar_diff) |
johansen(data, k_ar_diff=1) |
Trace and max-eig stats + selected ranks at 5% + evec. Fixed det_order=0 (unrestricted constant) — the vecm(..., deterministic="co") case, not vecm's "n" default. |
VECM(data, k_ar_diff, coint_rank, deterministic, seasons, first_season).fit() |
vecm(data, k_ar_diff=1, coint_rank=1, deterministic="n", seasons=0) |
ML estimation; alpha, beta, det_coef_coint, gamma, det_coef, sigma_u, llf. All nine deterministic cases ("n"/"co"/"ci"/"lo"/"li"/"colo"/"coli"/"cilo"/"cili") plus centered seasonal dummies, statsmodels-exact — restricted terms come back as det_coef_coint (the widened-beta rows), matching VECMResults' split. Use "co" when the rank came from johansen. first_season defaults to None (resolving to 0) and is taken modulo seasons; unlike statsmodels' inert first_season=0, passing it explicitly under seasons=0 raises (0.7.0) — pass it only with seasons > 0. |
coint(y0, y1) (Engle-Granger) |
engle_granger(data, trend="c", autolag=..., maxlag=None) |
Two-step residual test; data is T x k with the dependent column first. Dict stat, pvalue, crit, coint_coefs, resid, used_lag. phillips_ouliaris(y, x) for the Zt/Za variant. |
DynamicFactor, DynamicFactorMQ |
dfm_nowcast(data, n_factors, factor_order, method="two_step") |
Two-step DGR (2011) nowcaster with a ragged edge, or method="mle" for the exact one-step Gaussian MLE; dfm_news for the Bańbura-Modugno news decomposition and factor_model for static PCA factors + Bai-Ng selection. The mixed-frequency state space behind DynamicFactorMQ: roadmap. |
VARMAX |
— | Roadmap. |
Structural identification beyond Cholesky is where tsecon pulls ahead of
statsmodels, whose SVAR stops at short-run A/B matrices:
| statsmodels | tsecon | Notes |
|---|---|---|
| (recursive) | var_irf(..., orth=True) |
Cholesky IRFs = the column order of data. |
| — | sign_restricted_svar(data, restrictions, ...) |
Sign-restricted Bayesian SVAR with identified-set bands. No statsmodels analogue. |
| — | bvar_fit, bvar_irf_draws |
Minnesota-NIW BVAR + posterior IRF draws for credible bands. |
| — | favar(panel, policy, ...) |
Two-step FAVAR (Bernanke-Boivin-Eliasz 2005). |
| — | connectedness(data, ...) |
Diebold-Yilmaz spillover tables from a VAR's GFEVD. |
SVAR(endog, svar_type="A"/"B"/"AB") |
long_run_svar(data, lags, horizon, restrictions=None) |
Blanchard-Quah long-run zeros in closed form — statsmodels' SVAR has no long-run option. Alongside it max_share_svar, proxy_svar, hetero_svar, nongaussian_svar, narrative_svar, zero_sign_svar, plus structural_fevd and historical_decomposition. Explicit short-run A/B matrices, the one thing SVAR does do: roadmap. |
Local projections — tsecon's deepest lead¶
statsmodels 0.15.0 added a single-equation LocalProjections (one shock
index, Newey-West bands, .conf_int()/.cumulative_effects()). tsecon ships
the whole family around it — lag-augmented inference by default, LP-IV,
state-dependent regimes, panels, smooth LP, quantile LP — and that depth is
still one of the main reasons to adopt tsecon.
| statsmodels | tsecon | Notes |
|---|---|---|
LocalProjections(endog, shock_idx, lags, horizons).fit() |
lp(y, shock, horizons=12, se="lag_augmented") |
Jordà (2005) LP IRFs. statsmodels is HAC-only; tsecon defaults to lag-augmented inference and offers se="hac" for the old behaviour. Also smooth_lp (Barnichon-Brownlees) and lp_multiplier. |
| — | lp_iv(y, impulse, instrument, horizons=8) |
LP-IV with a first-stage F diagnostic. |
| — | lp_state(y, shock, state_indicator, ...) |
State-dependent (Ramey-Zubairy 2018) per-regime IRFs. |
| — | panel_lp(outcome, shock, ...) |
Panel local projection with fixed effects. |
Quantile regression and Growth-at-Risk¶
statsmodels ships QuantReg; tsecon matches it and adds the projection and
tail-risk extensions built on the same check-loss estimator.
| statsmodels | tsecon | Notes |
|---|---|---|
QuantReg(y, X).fit(q=tau) |
quantile_regression(y, x, taus=[tau]) |
IRLS check-loss with Powell kernel-sandwich SEs; matches QuantReg defaults. Include the constant column in x; pass several taus in one call. |
| — | quantile_lp(y, shock, taus=..., horizons=8) |
Quantile local projections, irf[tau][h]. No statsmodels analogue. |
| — | growth_at_risk(y, conditions, horizon=4, taus=...) |
Adrian-Boyarchenko-Giannone (2019) conditional-quantile Growth-at-Risk; current is the latest tail read. No statsmodels analogue. |
Filters, spectral, forecast evaluation¶
| statsmodels / scipy | tsecon | Notes |
|---|---|---|
hpfilter(y, lamb) |
hp_filter(y, lamb=1600, one_sided=False) |
Returns {"trend", "cycle", ...}. |
bkfilter(y, low, high, K) |
bk_filter(y, low=6, high=32, k=12) |
Loses k obs each end; first_index tells you where the cycle starts. |
cffilter(y, low, high, drift) |
cf_filter(y, low=6, high=32, drift=True) |
Christiano-Fitzgerald. |
hamilton_filter(x, h, p) (new in 0.15.0) |
hamilton_filter(y, h=8, p=4) |
Hamilton (2018) regression filter — the modern HP alternative; cross-checked against the new statsmodels function at 4.2e-14. tsecon adds method="random_walk" for the short-sample variant and se="hac" for Newey-West coefficient errors, neither of which statsmodels has; it returns first_index where statsmodels pads with NaN. |
| (none) | bn_decomposition(y, p=2, q=2) |
Classic Beveridge-Nelson (1981) from an ARIMA(p,1,q); statsmodels has no BN decomposition. |
| (none) | bn_filter(y, p=12) |
Kamber-Morley-Wong (2018) BN filter — the pinned-signal-to-noise output gap. |
scipy.signal.periodogram |
periodogram(x, fs, window, detrend) |
Matches SciPy. |
scipy.signal.welch |
welch(x, nperseg, ...) |
Matches SciPy. |
scipy.signal.coherence |
coherence(x, y, nperseg, ...) |
Magnitude-squared coherence. |
STL(y, period).fit(), MSTL(y, periods).fit() |
stl(y, period, seasonal=7, robust=False, ...), mstl(y, periods) |
Cleveland et al. (1990) loess semantics, pinned elementwise to statsmodels; seasonal_strength and the nsdiffs advisor sit on top. Classical seasonal_decompose: roadmap. |
For forecast comparison, tsecon ships a fuller battery than statsmodels.tsa:
dm_test (Diebold-Mariano with the Harvey-Leybourne-Newbold correction — the
one with a statsmodels counterpart since 0.15.0, diebold_mariano_test, though
that one takes forecasts where dm_test takes the two error series),
cw_test (Clark-West, nested models), gw_test (Giacomini-White), accuracy
(ME/RMSE/MAE/MAPE/sMAPE/MASE/RMSSE), and a rolling/expanding backtest engine.
Worked translations¶
The tables above are the reference; here are five you can run. Each shows the
statsmodels call as a comment.
Unit-root screen and a portmanteau test¶
import numpy as np, tsecon
rng = np.random.default_rng(0)
y = np.cumsum(rng.standard_normal(300)) # a random walk
r = tsecon.adf(y, regression="c") # adfuller(y)
print(round(r["statistic"], 3), round(r["p_value"], 3), r["used_lag"])
lb = tsecon.ljung_box(y, nlags=10) # acorr_ljungbox(y, 10)
print(round(lb["lb_stat"][-1], 2), round(lb["lb_pvalue"][-1], 4))
OLS with Newey-West standard errors¶
Note the explicit constant column — there is no add_constant.
import numpy as np, tsecon
rng = np.random.default_rng(2)
X = rng.standard_normal((200, 2))
y = 3.0 + X @ np.array([1.0, -0.5]) + rng.standard_normal(200)
Xc = np.column_stack([np.ones(len(y)), X]) # add the intercept yourself
# statsmodels: OLS(y, add_constant(X)).fit(cov_type="HAC",
# cov_kwds={"maxlags": 4})
res = tsecon.ols(y, Xc, se_type="hac", maxlags=4)
print(np.round(res["params"], 3), res["se_type"])
A VAR, its orthogonalized IRFs, and its FEVD¶
import numpy as np, tsecon
rng = np.random.default_rng(1)
data = rng.standard_normal((200, 3)) # T x k; columns are variables
fit = tsecon.var_fit(data, lags=2, trend="c") # VAR(df).fit(2)
print(round(fit["aic"], 3), round(fit["max_root"], 3))
irf = tsecon.var_irf(data, lags=2, horizon=10, orth=True) # .irf(10).orth_irfs
resp = irf[5][0][1] # horizon 5, response of var 0 to a shock in var 1
fevd = tsecon.var_fevd(data, lags=2, horizon=10) # .fevd(10)
share = fevd[9][0][1] # horizon 10, var 0, share explained by shock 1
print(round(resp, 4), round(share, 4))
GARCH(1,1) with robust standard errors¶
The arch package is the usual companion to statsmodels for volatility;
garch_fit covers the common cases.
import numpy as np, tsecon
rng = np.random.default_rng(3)
# a GARCH(1,1) return series so the QMLE has clustering to fit
e = rng.standard_normal(1500); h = np.empty(1500); r = np.empty(1500)
h[0] = 0.5; r[0] = np.sqrt(h[0]) * e[0]
for t in range(1, 1500):
h[t] = 0.05 + 0.08 * r[t-1]**2 + 0.90 * h[t-1]
r[t] = np.sqrt(h[t]) * e[t]
g = tsecon.garch_fit(r, vol="garch", p=1, q=1, dist="normal")
# arch: arch_model(r, vol="Garch", p=1, q=1, mean="Zero").fit()
print(dict(zip(g["param_names"], np.round(g["params"], 4))))
print(np.round(g["se_robust"], 4)) # Bollerslev-Wooldridge SEs
A local projection — the thing you came here for¶
import numpy as np, tsecon
rng = np.random.default_rng(4)
n = 300
shock = rng.standard_normal(n)
y = np.zeros(n)
for t in range(1, n):
y[t] = 0.5 * y[t-1] + 0.8 * shock[t] + rng.standard_normal()
out = tsecon.lp(y, shock, horizons=12, se="lag_augmented")
# statsmodels 0.15.0: LocalProjections(np.column_stack([shock, y]),
# shock_idx=0, horizons=12).fit() — HAC only
print(np.round(out["irf"][:3], 3), np.round(out["se"][:3], 3))
What statsmodels has that tsecon does not (yet)¶
Be direct about the gaps so you can plan around them. As of this writing the
following common statsmodels.tsa capabilities are roadmap, not shipped:
- Exogenous regressors in ARIMA — the X of SARIMAX, and
VARXon the system side.arima_fitships multiplicative SARIMA viaseasonal=(P, D, Q, s)but takes noexog. - ExponentialSmoothing / ETS beyond the Theta method.
- Classical
seasonal_decomposeadditive/multiplicative decomposition;stlandmstlship. - VARMAX / VARMA, and
ARDL/UECM. - Explicit short-run A/B SVAR restrictions — the one identification scheme
SVARoffers that tsecon does not. tsecon ships Cholesky (var_irf(orth=True)), long-run / Blanchard-Quah (long_run_svar), max-share (max_share_svar), proxy/IV (proxy_svar), heteroskedasticity (hetero_svar), non-Gaussian (nongaussian_svar), narrative (narrative_svar) and sign / zero-sign (sign_restricted_svar,zero_sign_svar) instead. - Custom state-space models (
MLEModel,UnobservedComponents); only the local-level filter and the internal DFM state space ship. - Mixed-frequency dynamic factor models (
DynamicFactorMQ).dfm_nowcastdoes two-step or one-step-MLE nowcasting on a single-frequency ragged-edge panel, anddfm_newsthe Bańbura-Modugno news decomposition, but the mixed-frequency state space is roadmap;umidas/weighted_midascover the mixed-frequency regression case today. range_unit_root_testand the Leybourne-McCabe stationarity test.
Where tsecon leads statsmodels.tsa — and why the switch is often worth it —
is the modern macro-structural toolkit: the local-projection family beyond the
plain estimator (lp_iv/lp_state/panel_lp/smooth_lp), Bayesian VARs
(bvar_fit/bvar_irf_draws), sign-restricted SVARs (sign_restricted_svar),
FAVAR (favar), nonlinear dynamics (setar/star/threshold_var/
threshold_vecm), MIDAS mixed frequency (umidas/weighted_midas),
IV-GMM (iv_gmm), heterogeneous panels (panel_mean_group/panel_pmg),
conditional-quantile Growth-at-Risk (growth_at_risk/quantile_lp),
Bai-Perron multiple-break estimation (bai_perron/sup_f_test), and a Rust core
fast enough to make 5,000-draw bootstraps a default rather than a luxury.
See also the R and Stata guides, and the cross-package Rosetta glossary.