
Aggregation and Mixed-Frequency Modeling in bridgr
Source:vignettes/mixed-frequency-modeling.Rmd
mixed-frequency-modeling.RmdThe Bridge-to-MIDAS Spectrum
bridgr supports several ways to map higher-frequency
observations into a lower-frequency target equation. Those choices span
a continuum:
- Bridge-style deterministic aggregation:
"mean","last","sum", or fixed numeric weights. - Unrestricted mixed-frequency regression:
"unrestricted". - Parametric MIDAS-style weighting:
"expalmon"and"beta".
This vignette uses a simple monthly-to-quarterly simulation so the differences between these approaches are easy to see.
A Small Monthly-to-Quarterly Example
n_quarters <- 40
quarter_index <- rep(seq_len(n_quarters), each = 3)
slot <- rep(1:3, times = n_quarters)
monthly_time <- seq(
as.Date("2010-01-01"),
by = "month",
length.out = n_quarters * 3
)
monthly_indicator <- dplyr::tibble(
time = monthly_time,
value = 15 + quarter_index * 0.35 +
ifelse(slot == 1, 0.8 * sin(quarter_index / 2), 0) +
ifelse(slot == 2, -0.6 * cos(quarter_index / 3), 0) +
ifelse(slot == 3, 0.7 * sin(quarter_index / 4 + 0.3), 0)
)
quarter_time <- monthly_time[seq(1, length(monthly_time), by = 3)]
quarter_target <- dplyr::tibble(
time = quarter_time,
value = 0.5 +
vapply(
seq_along(quarter_time),
function(i) {
block <- monthly_indicator$value[((i - 1) * 3 + 1):(i * 3)]
0.2 * block[[1]] + 0.6 * block[[2]] + 0.2 * block[[3]]
},
numeric(1)
) +
rep(c(0.1, -0.05, 0.08, -0.02), length.out = length(quarter_time))
)The target is intentionally driven more by the middle month of each quarter, so we can see how the different aggregation schemes react. The monthly indicator also has slot-specific within-quarter movements, so the unrestricted specification can estimate three distinct monthly coefficients.
Deterministic Bridge Aggregation
mean_model <- mf_model(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "mean",
h = 1
)
last_model <- mf_model(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "last",
h = 1
)
summary(mean_model)
#> Mixed-frequency model summary
#> -----------------------------------
#> Target series: quarter_target
#> Target frequency: quarter
#> Forecast horizon: 1
#> Estimation rows: 40
#> Regressors: monthly_indicator
#> -----------------------------------
#> Target equation coefficients:
#> Estimate
#> (Intercept) 0.516
#> monthly_indicator 0.999
#> -----------------------------------
#> Model fit:
#> Statistic Value
#> R-squared 0.999
#> Adjusted R-squared 0.999
#> Residual standard error 0.142
#> -----------------------------------
#> Indicator summary:
#> Frequency Predict Aggregation
#> monthly_indicator month last mean
#> -----------------------------------
summary(last_model)
#> Mixed-frequency model summary
#> -----------------------------------
#> Target series: quarter_target
#> Target frequency: quarter
#> Forecast horizon: 1
#> Estimation rows: 40
#> Regressors: monthly_indicator
#> -----------------------------------
#> Target equation coefficients:
#> Estimate
#> (Intercept) 0.590
#> monthly_indicator 0.993
#> -----------------------------------
#> Model fit:
#> Statistic Value
#> R-squared 0.993
#> Adjusted R-squared 0.993
#> Residual standard error 0.337
#> -----------------------------------
#> Indicator summary:
#> Frequency Predict Aggregation
#> monthly_indicator month last last
#> -----------------------------------These are classic bridge-model choices. They are easy to interpret and often work well when you want a transparent rule for within-period aggregation.
Unrestricted Mixed-Frequency Regression
unrestricted_model <- mf_model(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "unrestricted",
h = 1
)
summary(unrestricted_model)
#> Mixed-frequency model summary
#> -----------------------------------
#> Target series: quarter_target
#> Target frequency: quarter
#> Forecast horizon: 1
#> Estimation rows: 40
#> Regressors: monthly_indicator_hf1, monthly_indicator_hf2, monthly_indicator_hf3
#> -----------------------------------
#> Target equation coefficients:
#> Estimate
#> (Intercept) 0.541
#> monthly_indicator_hf1 0.198
#> monthly_indicator_hf2 0.596
#> monthly_indicator_hf3 0.205
#> -----------------------------------
#> Model fit:
#> Statistic Value
#> R-squared 1.000
#> Adjusted R-squared 1.000
#> Residual standard error 0.067
#> -----------------------------------
#> Indicator summary:
#> Frequency Predict Aggregation
#> monthly_indicator month last unrestricted
#> -----------------------------------"unrestricted" estimates one coefficient for each
within-quarter monthly observation. In a monthly-on-quarterly example
this means three separate regressors. This example keeps
indic_predict = "last" so the comparison isolates the
aggregation choice. The ragged-edge vignette covers
indic_predict = "direct", which skips indicator forecasting
and instead works from the latest observed complete high-frequency
blocks.
Parametric MIDAS-Style Weighting
bridgr also supports parametric weighting rules that
estimate the within-period shape from the data while keeping the number
of free parameters small.
expalmon_model <- mf_model(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "expalmon",
solver_options = list(seed = 123, n_starts = 1, maxiter = 100),
h = 1
)
beta_model <- mf_model(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "beta",
solver_options = list(
seed = 123,
n_starts = 1,
maxiter = 100,
start_values = c(2, 2)
),
h = 1
)The fitted object stores both the estimated weight profile and the underlying parametric coefficients.
indicator_id <- expalmon_model$indic_name[[1]]
dplyr::bind_rows(
dplyr::tibble(
model = "expalmon",
month = seq_along(expalmon_model$parametric_weights[[indicator_id]]),
weight = expalmon_model$parametric_weights[[indicator_id]]
),
dplyr::tibble(
model = "beta",
month = seq_along(beta_model$parametric_weights[[indicator_id]]),
weight = beta_model$parametric_weights[[indicator_id]]
)
)
#> # A tibble: 6 × 3
#> model month weight
#> <chr> <int> <dbl>
#> 1 expalmon 1 1.99 e- 1
#> 2 expalmon 2 5.97 e- 1
#> 3 expalmon 3 2.05 e- 1
#> 4 beta 1 8.88 e-16
#> 5 beta 2 1.000e+ 0
#> 6 beta 3 8.88 e-16Forecast Comparison
All of these models share the same downstream interface.
dplyr::bind_rows(
dplyr::tibble(
model = "mean",
forecast = as.numeric(forecast(mean_model)$mean)
),
dplyr::tibble(
model = "last",
forecast = as.numeric(forecast(last_model)$mean)
),
dplyr::tibble(
model = "unrestricted",
forecast = as.numeric(forecast(unrestricted_model)$mean)
),
dplyr::tibble(
model = "expalmon",
forecast = as.numeric(forecast(expalmon_model)$mean)
),
dplyr::tibble(
model = "beta",
forecast = as.numeric(forecast(beta_model)$mean)
)
)
#> # A tibble: 5 × 2
#> model forecast
#> <chr> <dbl>
#> 1 mean 29.0
#> 2 last 28.9
#> 3 unrestricted 29.0
#> 4 expalmon 29.0
#> 5 beta 29.0Choosing an Aggregation Strategy
As a rough guide:
- Use deterministic bridge aggregation like “mean”, “last”, “sum” when you want a transparent and stable nowcasting rule.
- Use
"unrestricted"when the frequency gap is small and you want each within-period observation to have its own coefficient. - Use
"expalmon"or"beta"when you want data-driven within-period weights but would like a more parsimonious parameterization than"unrestricted". - Use
indic_predict = "direct"when you want direct alignment based only on the latest observed complete high-frequency blocks.
The key design choice in bridgr is that all of these
specifications share the same estimation, forecasting, and summary
workflow. You can therefore move between classic bridge models,
unrestricted mixed-frequency regressions, and parametric MIDAS-style
models without switching to a different API.