Multi-regressor MIDAS¶
The MultiMIDAS class
extends the single-indicator MIDAS regression to several
high-frequency (monthly) regressors plus, optionally, quarterly
regressors that enter the model linearly. Every regressor can keep
its own weighting scheme, lag length and start lag.
Model¶
For each forecast horizon \(h\) the model is
where
- \(X_{k,t}\) is the \(k\)-th monthly indicator with its own MIDAS weight function \(w_k(\cdot;\theta_k)\) and slope \(\beta_k\),
- \(Z_{\ell,t}\) is the \(\ell\)-th quarterly regressor entering linearly with one \(\delta_{\ell,m}\) per quarterly lag,
- \(\mathbf{d}_{t+h}\) are optional outlier dummies,
- \(\phi_p\) are optional AR coefficients of the target.
Estimator routing¶
Weighting methods fall into two groups:
- Linear-in-parameters (
almon,unrestricted): the lag weights are a linear map of their parameters, so the corresponding term can be written as fixed design columns. Foralmon, \(w(j)=\sum_i c_i\,j^i\) gives \(X_k\,w_k = (X_k V_k)\,c_k\) with \(V_k\) the polynomial (Vandermonde) basis;unrestricteduses \(X_k\) directly. These are estimated by OLS with \(\beta_k\) fixed to \(1.0\). - Nonlinear (
exp_almon,beta): the weights depend nonlinearly on \(\theta_k\) (exponential/Beta density with normalisation), so the model estimates the weight function and slope \(\beta_k\) together.
| Monthly methods | Estimator |
|---|---|
All almon or unrestricted |
joint OLS (closed-form) |
At least one exp_almon or beta |
joint NLS (Levenberg-Marquardt with JAX Jacobian) |
The routing rule selects the solver, but it does not change each variable's
role in the model. almon/unrestricted terms and quarterly regressors
always enter as fixed design columns with \(\beta_k = 1.0\). The model
estimates only the exp_almon/beta weight functions and their slopes on
the NLS path.
Why linear, not normalised, inside NLS
Keeping a normalised weight scheme such as almon's \(w/\sum w\)
alongside a separately estimated slope \(\beta_k\) would leave \(\theta_k\) identified
only up to scale (the scale cancels in the normalisation), so the
optimiser can drift to degenerate parameters. Estimating these terms
linearly removes the redundant degree of freedom and makes an
almon variable yield identical estimates whether the model is
solved by OLS or by NLS. The trade-off is interpretive: the recovered
weights are unnormalised (\(V_k c_k\), with \(\beta_k = 1.0\)) rather
than summing to one.
Quarterly regressors are always linear and are folded into both paths without changing the routing rule.
Input data¶
fit takes a target frame with date/value and a long-format
regressor frame with date/variable/value:
target = pd.DataFrame({"date": q_dates, "value": y})
regressors = pd.DataFrame({"date": ..., "variable": ..., "value": ...})
The examples below use the built-in simulator so every block runs as-is; later blocks continue from these names:
from nowcast_midas import MultiMIDAS
from nowcast_midas.utils import sample_combo_data
# long-format frames with monthly_1..3 (ME) + quarterly_1 (QE) regressors
target, regressors, info = sample_combo_data(n_quarters=60, seed=42)
Specifying the model¶
The simplest call shares the same weight scheme across all monthly indicators:
from nowcast_midas import MultiMIDAS
model = MultiMIDAS(
variables=["monthly_1", "monthly_2"],
method="almon",
n_lags=6,
horizons=[0, 1],
)
model.fit(target, regressors)
For per-variable overrides — including mixing monthly and quarterly
regressors — pass VariableSpec
objects:
from nowcast_midas import VariableSpec
model = MultiMIDAS(
variables=[
VariableSpec("monthly_1", method="exp_almon", n_lags=6),
VariableSpec("monthly_2", method="almon", n_lags=3),
VariableSpec("quarterly_1", frequency="QE", n_lags=2),
],
horizons=[0, 1],
)
model.fit(target, regressors)
VariableSpec(frequency="QE", n_lags=M) requests a quarterly regressor
with \(M\) quarterly lags; method, n_pars_weights and estimator are
ignored for quarterly variables.
Outputs¶
After fit, results for each horizon are stored in model.fits_[h]
(FittedMultiMidas):
alpha,gamma(dummy coefficients),phi(AR coefficients).variable_fits[name]→VariableFitwithbeta— slope; estimated separately only for nonlinear methods (exp_almon,beta). It is fixed to \(1.0\) for the linearly-estimated methods (almon,unrestricted) and for quarterly regressors, regardless of whether the model was solved by OLS or NLS,theta— weight-shape parameters (or quarterly deltas),weights— evaluated lag weights (lengthn_lags).
For the nonlinear methods, weights are the normalised MIDAS weights
\(w_k(\theta_k)\). For almon/unrestricted, weights are the
unnormalised lag coefficients (\(V_k c_k\) or \(c_k\)). For quarterly
regressors, weights is the vector of linear coefficients
\((\delta_{\ell,0}, \dots, \delta_{\ell,M-1})\).
Forecasting¶
forecast returns a long-format DataFrame with one row per fitted
horizon and the columns date, horizon, spec, value. spec is
the "+"-joined regressor variable names (a MultiMIDAS forecast is a
single joint prediction). Monthly and quarterly lag rows are built
independently, and a missing lag in any regressor anchors that horizon's
forecast to NaN.
fit() additionally stores the long-format in-sample fitted values as
model.fits_df_ (date, horizon, value), and forecast() stores its
result as model.forecasts_df_.
forecast_decomp splits each horizon's forecast into additive components
— one row per regressor block plus intercept, dummy_i and ar_lagk
— that sum back to the forecast. See
Interpreting decompositions.
Pipeline integration¶
Use MultiMidasSpec to embed
a MultiMIDAS block as a single named source inside
MidasCombo:
from nowcast_midas import ComboSpec, MidasCombo, MultiMidasSpec
multi_spec = MultiMidasSpec(
name="multi_block",
variables=[
VariableSpec("monthly_1", method="almon", n_lags=6),
VariableSpec("monthly_2", method="exp_almon", n_lags=6),
VariableSpec("quarterly_1", frequency="QE", n_lags=1),
],
)
combo = ComboSpec(name="my_combo", sources=[multi_spec], method="average")
pipe = MidasCombo(combo_specs=combo, horizons=4)
pipe.fit(target, regressors)
forecasts = pipe.forecast()
The fitted MultiMIDAS model is exposed via
pipe.multi_midas_instances_[name] and its per-horizon FittedMultiMidas
under pipe.multi_midas_models_[name][h].