macroforecast.model_ensemble#
macroforecast.model_ensemble owns fit-time model composition. These
callables receive one aligned X, y training sample, fit multiple member models
inside that training window, and return one ModelFit whose predict(X_new)
returns one forecast series. This is different from
macroforecast.forecasting.combination, which combines already-produced
forecast rows after models have been fitted.
Public Surface#
Symbol |
Kind |
Purpose |
|---|---|---|
|
fit function |
Bootstrap or block-bootstrap member fits. |
|
fit function |
Sampling-without-replacement member fits. |
|
fit function |
Member fits on random feature subsets. |
|
fit function |
Out-of-fold base predictions plus a meta learner. |
|
fit function |
SuperLearner-style OOF convex weighted average. |
|
fit function |
Bagged overfit stochastic boosting with feature perturbation. |
|
estimator class |
Backend for |
|
estimator class |
Backend for |
|
estimator class |
Backend for |
|
estimator class |
Backend for |
|
estimator class |
Backend for |
|
registry |
Supported inner estimators for |
|
registry |
Registered fit-time ensemble specs. |
|
helper |
Return supported inner estimators and backend names. |
|
spec helper |
Resolve a name, callable, or spec. |
|
spec helper |
Return a registry table. |
|
spec helper |
Return parameter/default/search-space documentation. |
|
spec helper |
Return a preset search space. |
|
extension helper |
Build a user-owned |
Boundary#
Question |
Use |
|---|---|
Fit several member models inside one training window and expose one predictor? |
|
Fit independent models and combine their OOS forecast rows? |
|
Use tree boosting as one estimator, such as XGBoost or LightGBM? |
|
Common Contract#
All public fit functions in this namespace use the same callable shape:
Item |
Contract |
|---|---|
Input |
pandas-like predictor matrix. Index is preserved in fitted diagnostics. Missing values are aligned with |
Input |
pandas-like target series. Required unless |
Output |
|
Metadata |
|
Diagnostics |
|
Common diagnostics:
Key |
Produced by |
Meaning |
|---|---|---|
|
all estimator-backed ensembles |
Number of fitted member models stored in the ensemble. |
|
|
Compact row-sampling ledger for each member. |
|
|
Feature subset ledger for each member. |
|
|
Out-of-bag diagnostics from member fits that did not use a row. |
|
|
Fold ledger used to create out-of-fold predictions. |
|
|
Out-of-fold library matrix. |
|
|
Convex library weights and OOF MSE by learner. |
Example:
fit = macroforecast.model_ensemble.subagging(
X_train,
y_train,
base="ridge",
n_estimators=50,
max_samples=0.632,
)
pred = fit.predict(X_test)
Runner usage:
result = macroforecast.forecasting.run(
panel,
model="super_learner",
target="INDPRO",
horizon=1,
features=macroforecast.feature_engineering.feature_spec(
target="INDPRO",
horizon=1,
lags=12,
),
params={
"super_learner": {
"models": ("ridge", "lasso", "random_forest"),
"n_splits": 5,
"weight_method": "nnls",
}
},
)
When a model ensemble is passed through a runner alias, downstream forecast combination selects the alias, not the registry name:
result = macroforecast.forecasting.run(
panel,
{"bagged": "bagging", "linear": "ridge"},
combination={
"linear_plus_bagged": {
"method": "mean",
"models": ["linear", "bagged"],
}
},
)
R And Paper Alignment#
macroforecast callable |
R / paper reference |
Alignment |
Difference |
|---|---|---|---|
|
Multiple resampled training sets, one member model per draw, average predictions. |
R default is tree-focused; macroforecast allows several sklearn-compatible base regressors. |
|
|
Samples fewer than |
Exposed as a separate callable for clarity. |
|
|
|
Repeated random predictor-subspace fits. |
General member-level feature bagging, not tree split-level |
|
Fits a meta model on out-of-fold base predictions. |
Adds |
|
|
Uses OOF library predictions and nonnegative weights that sum to one. |
Regression-only callable; supports |
|
|
Goulet Coulombe, To Bag is to Prune and the |
Samples rows with |
Uses sklearn |
The added functions beyond the old bagging/booging pair are subagging,
random_subspace, stacking, and super_learner. They cover the main
fit-time ensemble families that are useful before producing forecast rows:
row-resampling ensembles, feature-subspace ensembles, OOF meta-learner
ensembles, and convex-weight OOF library ensembles.
Not every R ensemble feature is copied. ipred double-bagging/bundling is a
classification-oriented extension that passes extra learner predictions into
trees; in macroforecast, cross-model forecast combinations belong in
macroforecast.forecasting.combination. regRSM variable-importance final
model selection belongs closer to feature selection and is not bundled into this
fit-time ensemble callable.
Paper Citation And Scope#
The Booging implementation is based on:
Philippe Goulet Coulombe. 2024. To Bag is to Prune. arXiv:2008.07063v5.
The method code is cross-checked against the author’s public bagofprunes R
source, especially Booging(y, X, X.new, ...) in
PGC_Bag_of_Prunes_v200829.R. The package treats the paper as a method port,
not as a full empirical replication. The goal is to make the randomized
greedy-ensemble mechanism callable inside the macroforecast workflow.
The review-mapped mechanism is:
Paper idea |
Meaning for macroforecast |
|---|---|
Random Forest has a large in-sample versus out-of-sample R-squared wedge |
The model can overfit member trees without necessarily damaging the ensemble forecast. |
Bagging plus perturbation performs implicit pruning |
Averaging many randomized greedy paths cancels unstable noise-fitting steps while preserving stable signal-fitting steps. |
Greedy separability |
Early greedy steps are not re-optimized after later overfit steps, so useful structure can survive overfitting. |
Perfectly random forest null argument |
Under pure noise, averaging many random predictions tends toward a sample-mean-like forecast, matching the optimal pruning intuition. |
LASSO contrast |
Global re-optimization along a regularization path weakens the same mechanism; setting the penalty to zero collapses to OLS rather than randomized implicit pruning. |
Booging |
Applies bagging and perturbation to boosted trees, so the stopping point is regularized by averaging randomized overfit boosting paths. |
MARSquake |
Applies the same outer idea to MARS through randomized predictor admission in the forward pass. |
This package currently implements Booging. MARSquake is documented as a related
paper method but is not exposed as a first-class callable yet, because exact
alignment requires either a stable Python analogue of R earth::earth with the
custom allowed callback or a package-native MARS forward-pass implementation.
The plain MARS model remains available under macroforecast.models; that is not
the same as MARSquake.
The paper’s empirical evidence spans simulated DGPs, many non-macro benchmark datasets, and six US macro forecasting tasks covering GDP, unemployment, and inflation horizons. For macroforecast, the relevant takeaway is operational: Booging is useful when small noisy macro samples make the exact boosting stopping point unstable, and the user wants randomized overfitting plus averaging as an alternative regularization route.
Bagging, Random Forest, And Booging#
These functions expose a common decomposition:
Method |
Row sampling |
Feature perturbation |
Base learner |
macroforecast location |
|---|---|---|---|---|
Bagging |
bootstrap or block bootstrap |
optional member-level |
any registered inner base learner |
|
Random-subspace bagging |
bootstrap/subsampling |
member-level |
any registered inner base learner |
|
Random forest |
bootstrap plus split-level feature search |
split-level tree |
CART trees |
|
Booging |
row subsampling |
member-level |
overfit boosted trees |
|
The distinction matters. bagging(base="decision_tree", max_features="sqrt")
is a random-subspace tree ensemble, not an exact random forest, because the
feature subset is drawn once per member rather than at each tree split. Use
macroforecast.models.random_forest when the exact random-forest backend is the
target. Use booging when the target is Goulet Coulombe’s randomized
greedy-boosting ensemble.
Registered Specs#
MODEL_ENSEMBLE_SPECS mirrors the model registry but is intentionally separate
from macroforecast.models.MODEL_SPECS.
macroforecast.model_ensemble.list_model_ensemble_specs()
Name |
Default search |
Presets |
Backend |
|---|---|---|---|
|
|
|
internal member resampling + sklearn-compatible base estimators |
|
|
|
internal subagging + sklearn-compatible base estimators |
|
|
|
internal random subspace + sklearn-compatible base estimators |
|
|
|
internal OOF stacking + sklearn-compatible base/meta estimators |
|
|
|
internal SuperLearner-style OOF NNLS/equal/best weighting |
|
|
|
internal augmentation/bagging + sklearn.ensemble.GradientBoostingRegressor |
The forecasting runner and macroforecast.model_selection.search_spec() both
resolve these names, so model="bagging" now means the fit-time ensemble spec,
not a base model spec.
Inner Base Estimators#
bagging, subagging, and random_subspace use base=....
stacking and super_learner use models=(...), and stacking also uses
meta_model=.... These names are intentionally narrower than
macroforecast.models.MODEL_SPECS: they are inner sklearn-compatible
estimators used inside a fit-time ensemble.
macroforecast.model_ensemble.list_model_ensemble_bases()
Name |
Backend |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bagging#
macroforecast.model_ensemble.bagging(
X,
y,
*,
base="ridge",
n_estimators=50,
max_samples=0.8,
random_state=0,
base_params=None,
strategy="standard",
block_length=4,
replace=True,
max_features=None,
)
Input: X is a pandas-like predictor matrix and y is a target series.
Output: ModelFit wrapping BaggingRegressor. predict(X_new) returns the
mean across member predictions. predict_quantiles(X_new, levels=...) returns
empirical quantiles across members.
The fitted object stores out-of-bag diagnostics when any observation is left out
by at least one member: fit.diagnostics["model_ensemble"]["oob_predictions"],
oob_residuals, oob_metrics, and n_members. It also stores
member_features, even when max_features=None; this makes generic bagging,
random-subspace bagging, and CART-like tree bagging inspectable with the same
metadata contract.
Parameter |
Default |
Meaning |
|---|---|---|
|
|
Base estimator name. |
|
|
Number of member fits. |
|
|
Row sample fraction per member. |
|
|
Resampling seed. |
|
|
Parameters passed to the base estimator. |
|
|
|
|
|
Moving-block length when |
|
|
Whether rows are sampled with replacement. |
|
|
Optional member-level feature-subspace size. Accepts |
subagging#
macroforecast.model_ensemble.subagging(
X,
y,
*,
base="ridge",
n_estimators=50,
max_samples=0.632,
random_state=0,
base_params=None,
max_features=None,
)
subagging is bagging(..., replace=False, strategy="standard"). It follows
the ipredbagg(ns < n) distinction where sampling fewer than n rows without
replacement creates subagging rather than bootstrap bagging.
Input: X, y as above.
Output: ModelFit wrapping BaggingRegressor with fit.model == "subagging". predict(X_new) averages member predictions.
Diagnostics store member_samples, OOB diagnostics when available, and
n_members. fit.metadata["replace"] is always False.
Parameter |
Default |
Meaning |
|---|---|---|
|
|
Base estimator name. |
|
|
Number of subsampled member fits. |
|
|
Row sample fraction per member. |
|
|
Resampling seed. |
|
|
Parameters passed to the base estimator. |
|
|
Optional member-level feature-subspace size, with the same accepted values as |
random_subspace#
macroforecast.model_ensemble.random_subspace(
X,
y,
*,
base="ridge",
n_estimators=100,
max_features=0.5,
max_samples=1.0,
random_state=0,
base_params=None,
)
Each member model sees a random subset of columns. max_features can be a
fraction of columns, an integer count, "sqrt", or "log2". This is useful
when p is large and the package user wants a fit-time model ensemble distinct
from random-forest split-level mtry.
predict_quantiles(X_new, levels=...) returns empirical quantiles across random
subspace members. The fitted diagnostics include member_features, which records
which columns each member saw.
Parameter |
Default |
Meaning |
|---|---|---|
|
|
Base estimator name. |
|
|
Number of random feature-subspace fits. |
|
|
Fraction, integer count, |
|
|
Row subsample fraction per member. |
|
|
Feature and row sampling seed. |
|
|
Parameters passed to the base estimator. |
stacking#
macroforecast.model_ensemble.stacking(
X,
y,
*,
models=("ridge", "lasso", "random_forest"),
meta_model="ridge",
n_splits=5,
splitter="forward",
random_state=0,
model_params=None,
meta_params=None,
passthrough=False,
)
stacking creates out-of-fold base predictions, fits meta_model on those
predictions, then refits every base model on the full training sample. For macro
forecasting, the default splitter="forward" only validates on later blocks
after earlier training data. splitter="blocked" and splitter="kfold" are
available when the user wants R-style generic cross-validation behavior.
models must contain unique names because model_params is keyed by model
name. The fitted diagnostics include the OOF prediction matrix.
Parameter |
Default |
Meaning |
|---|---|---|
|
|
Base model library. Names must be unique. |
|
|
Meta learner fit on OOF predictions. |
|
|
Number of OOF validation folds. |
|
|
OOF splitter: |
|
|
Base/meta seed. |
|
|
Per-base model parameter dictionary keyed by model name. |
|
|
Meta-model parameters. |
|
|
Whether the meta learner also receives original |
Output: ModelFit wrapping StackingRegressor. Diagnostics include
folds and oof_predictions.
super_learner#
macroforecast.model_ensemble.super_learner(
X,
y,
*,
models=("ridge", "lasso", "random_forest"),
n_splits=5,
splitter="forward",
weight_method="nnls",
random_state=0,
model_params=None,
)
super_learner uses the same OOF library matrix as stacking but fits a convex
weighted average instead of a general meta model. weight_method="nnls" uses
nonnegative least squares and normalizes weights to sum to one. best gives all
weight to the lowest OOF-MSE learner, matching the discrete Super Learner idea.
equal uses equal weights.
The fitted estimator exposes weights_ and oof_predictions_.
models must contain unique names because weights and model_params are keyed
by model name. fit.diagnostics["model_ensemble"] stores weights, oof_risk,
and oof_predictions.
Parameter |
Default |
Meaning |
|---|---|---|
|
|
Base learner library. Names must be unique. |
|
|
Number of OOF validation folds. |
|
|
OOF splitter: |
|
|
|
|
|
Base learner seed. |
|
|
Per-base learner parameters keyed by model name. |
Output: ModelFit wrapping SuperLearnerRegressor. Diagnostics include
folds, oof_predictions, oof_risk, and weights.
booging#
macroforecast.model_ensemble.booging(
X,
y,
*,
B=100,
sampling_rate=0.75,
mtry=0.8,
data_aug=False,
noise_level=0.3,
shuffle_rate=0.2,
n_trees=1000,
tree_depth=3,
nu=0.3,
bf=0.5,
n_augmented_copies=2,
scale_continuous=True,
fix_seeds=True,
random_state=0,
)
booging fits many intentionally overfit stochastic gradient-boosting members.
It is the boosted-tree analogue of the randomized greedy ensemble logic behind
random forests: draw rows, draw features, fit an overfit boosted-tree path, and
average over many perturbed paths.
The callable accepts the paper/R-code names directly:
R code name |
macroforecast name |
Meaning |
|---|---|---|
|
|
Number of overfit boosting members. |
|
|
Row fraction sampled without replacement per member. |
|
|
Feature-subspace size per member. Accepts fractions, integer counts, |
|
|
Whether to append fake perturbed feature copies. |
|
|
Gaussian noise scale for continuous fake copies after standardization. |
|
|
Share of rows shuffled inside binary fake copies. |
|
|
Stochastic boosting subsample share inside each boosted member. |
|
|
Boosting stages inside each member. |
|
|
Inner boosting tree depth. |
|
|
Inner boosting learning rate. |
Backward-compatible names are still accepted: sample_frac,
inner_n_estimators, inner_learning_rate, inner_max_depth,
inner_subsample, max_features, da_noise_frac, and da_drop_rate.
Backend parameter mapping:
R |
sklearn / macroforecast target |
|---|---|
|
|
|
|
|
|
|
|
|
outer row subsampling before each boosted member |
|
outer member-level feature subsampling before each boosted member |
When data_aug=True, macroforecast follows the R algorithm’s fake-column
structure. Continuous variables are standardized and then copied with Gaussian
noise. Binary variables are copied after shuffling a shuffle_rate share of
rows. Two fake copies are used by default, matching the R source’s fake1_ and
fake2_ construction. Prediction uses deterministic fake copies of X_new, so
calling predict() does not draw new perturbations.
Algorithm:
Align
Xandyunder the standardModelFitcontract.Detect binary predictors as columns with exactly two finite unique values.
Standardize continuous predictors using the training sample.
If
data_aug=True, appendn_augmented_copiesfake copies: continuous fake columns receive Gaussian perturbations, and binary fake columns receive row-shuffle perturbations.For each member
b = 1, ..., B, draw rows atsampling_rate, draw columns atmtry, fit an overfit stochastic gradient-boosting member, and store the row and feature ledgers.Predict by averaging member predictions. Quantile forecasts use empirical quantiles across member predictions.
Input: X, y as above.
Output: ModelFit wrapping BoogingRegressor. predict(X_new) averages
overfit boosting members, and predict_quantiles(X_new, levels=...) returns
member-forecast empirical quantiles.
Diagnostics include member_samples, member_features, augmentation_summary,
and n_members. member_features names original and fake columns; the
augmentation summary reports the number of binary and continuous features, the
number of fake copies, and the leakage boundary for continuous scaling.
Parameter |
Default |
Meaning |
|---|---|---|
|
|
Number of overfit boosting members. |
|
|
Row sample fraction per member. |
|
|
Feature-subspace size per member. |
|
|
Whether to append perturbed fake feature copies. |
|
|
Gaussian noise scale for continuous fake copies. |
|
|
Binary-feature row-shuffle share for fake copies. |
|
|
Boosting stages inside each member. |
|
|
Inner boosting tree depth. |
|
|
Inner boosting learning rate. |
|
|
Stochastic gradient boosting subsample share. For samples below 100 rows, this is floored at |
|
|
Number of fake feature copies when |
|
|
Standardize continuous variables before fake-copy perturbation. |
|
|
Use deterministic member seeds analogous to the R source’s |
|
|
Member, row, column, and augmentation seed. |
Important implementation note: the R script scales continuous X and X.new
jointly because it receives the training and new matrices in the same function
call. The macroforecast estimator uses train-only scaling, because a standard
fit/predict object must not use future X_new information during fit(). This
is the deliberate leakage-safe difference; the member sampling, fake-copy
perturbation, boosting backend role, and averaging logic follow the R algorithm.
Custom Extensions#
Use custom_model_ensemble() when the user wants a fit-time ensemble that
behaves like a model spec:
spec = macroforecast.model_ensemble.custom_model_ensemble(
"my_ensemble",
my_fit_function,
default_params={"B": 20},
)
Use macroforecast.forecasting.custom_combination() instead when the custom
logic combines forecast rows after runner execution.