knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4.5, out.width = "100%" )
library(MacroFilters) library(ggplot2) data("fr_gdp", package = "MacroFilters") # France real GDP (log level) as a quarterly ts; includes the 2020 Q2 COVID shock d0 <- fr_gdp$date[1] fr <- ts( fr_gdp$gdp_log, start = c(as.integer(format(d0, "%Y")), (as.integer(format(d0, "%m")) - 1L) %/% 3L + 1L), frequency = 4 )
A point estimate of the trend is only half the story. In real time the trend is
most uncertain exactly where it matters most — at the end of the sample,
where the smoother has no future observations to lean on. Every filter in
MacroFilters can attach a 95% confidence band to its trend through a single
argument, boot_iter.
fit <- mbh_filter(fr, boot_iter = 50L) # mstop defaults to 500 str(fit[c("trend_lower", "trend_upper")], max.level = 1)
When boot_iter > 0 the returned object gains $trend_lower and
$trend_upper; otherwise they are absent. Plotting is automatic — autoplot()
draws the ribbon whenever the bands are present:
autoplot(fit)
The trend cuts almost straight through the 2020 COVID collapse: the Huber loss treats the shock as an outlier rather than bending the trend toward it.
The engine is a Circular Block Bootstrap (Politis & Romano, 1992) of the filter's pseudo-residuals (the cycle):
trend + resampled cycle and refit the same
filter to it. Each refit uses the same estimator as the base fit (same
mstop for MBH, same iteration count for bHP), so the band width is not
biased.trend ± 1.96 * sd(bootstrap trends),
centred on the point estimate. The standard deviation is used instead of raw
2.5%/97.5% percentiles because it is smooth and stable at a practical
boot_iter (percentiles need hundreds of replicates to avoid jitter).Two knobs control it:
boot_iter — number of bootstrap replicates (cost grows linearly).block_size — block length; "auto" (default) uses 2 * frequency
(two cycles), capped at length(x) / 3 to keep at least three blocks.# Quarterly data -> auto block size = 2 * 4 = 8 fit_b <- mbh_filter(fr, boot_iter = 50L, block_size = 8L)
The same machinery is available on all four filters. Note that MBH keeps the
default mstop = 500: for long log-level series, reducing mstop collapses the
trend (the Huber gradient is capped from the first iteration, so the trend never
climbs its full range).
autoplot(hp_filter(fr, boot_iter = 50L)) autoplot(bhp_filter(fr, boot_iter = 50L)) autoplot(hamilton_filter(fr, boot_iter = 50L)) autoplot(mbh_filter(fr, boot_iter = 50L))
For all filters the band widens toward the edges — typically two to three
times the mid-sample width. This is honest, not a bug: estimating the trend at
t = n with no future data is genuinely far more uncertain than in the
interior. It is precisely the end-point problem these filters are designed to
expose.
The Hamilton filter is a regression on lags of the series, so its first
h + p - 1 observations have no fitted trend (they appear as NA, leaving a
gap at the start of the plot). Its bootstrap is conditional on those initial
observations: the lead-in is held fixed across replicates. As a result the
band is narrow at the start of the valid window — where the predictors are the
frozen lead-in and only the regression coefficients vary — and widens forward as
the predictors themselves become resampled quantities.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.