
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","beta", and"legendre".
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 <- bridge(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "mean",
h = 1
)
last_model <- bridge(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "last",
h = 1
)
summary(mean_model)
#> Bridge 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
#> -----------------------------------
#> Indicator summary:
#> Frequency Predict Aggregation
#> monthly_indicator month last mean
#> -----------------------------------
summary(last_model)
#> Bridge 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
#> -----------------------------------
#> 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 <- bridge(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "unrestricted",
h = 1
)
summary(unrestricted_model)
#> Bridge 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
#> -----------------------------------
#> 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; combining "unrestricted" with
indic_predict = "direct" gives a direct MIDAS-style
alignment and is covered in the ragged-edge vignette.
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 <- bridge(
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 <- bridge(
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
)
legendre_model <- bridge(
target = quarter_target,
indic = monthly_indicator,
indic_predict = "last",
indic_aggregators = "legendre",
solver_options = list(seed = 123, n_starts = 1, maxiter = 100),
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]]
),
dplyr::tibble(
model = "legendre",
month = seq_along(legendre_model$parametric_weights[[indicator_id]]),
weight = legendre_model$parametric_weights[[indicator_id]]
)
)
#> # A tibble: 9 × 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-16
#> 7 legendre 1 1.99 e- 1
#> 8 legendre 2 5.97 e- 1
#> 9 legendre 3 2.05 e- 1Forecast 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)
),
dplyr::tibble(
model = "legendre",
forecast = as.numeric(forecast(legendre_model)$mean)
)
)
#> # A tibble: 6 × 2
#> model forecast
#> <chr> <dbl>
#> 1 mean 29.0
#> 2 last 28.9
#> 3 unrestricted 29.0
#> 4 expalmon 29.0
#> 5 beta 29.0
#> 6 legendre 29.0Choosing an Aggregation Strategy
As a rough guide:
- Use deterministic bridge aggregation 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","beta", or"legendre"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 MIDAS-style alignment based only on the latest observed 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 bridge and MIDAS-style models
without switching to a different API.