| Title: | Robust Trend-Cycle Decomposition for Macroeconomic Time Series |
| Version: | 0.1.0 |
| Description: | Provides high-performance tools for macroeconomic trend extraction and filtering, specifically designed to solve the end-point problem in real-time. Implements the MacroBoost Hybrid (MBH) filter using penalized P-splines and gradient boosting. Unlike the standard Hodrick-Prescott filter, 'MacroFilters' utilizes component-wise L2-boosting with robust loss functions (Huber) to handle extreme transient shocks (e.g., COVID-19) without inducing spurious trend shifts. The algorithm includes an automated two-layer diagnostic stage for unit roots and structural breaks, optimized via corrected AICc for computational efficiency. Methodology detailed in Kinel (2026) <doi:10.2139/ssrn.6371138>. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Language: | en-US |
| LazyData: | true |
| RoxygenNote: | 7.3.3 |
| URL: | https://github.com/michal0091/MacroFilters, https://michal0091.github.io/MacroFilters/ |
| BugReports: | https://github.com/michal0091/MacroFilters/issues |
| Imports: | Matrix, mboost, tseries |
| Suggests: | data.table, ggplot2, knitr, rmarkdown, scales, strucchange, testthat (≥ 3.0.0), usethis, xts, zoo |
| VignetteBuilder: | knitr |
| NeedsCompilation: | no |
| Packaged: | 2026-05-20 16:46:23 UTC; miki |
| Author: | Michal Kinel |
| Maintainer: | Michal Kinel <michal.kinel@gmail.com> |
| Depends: | R (≥ 3.5.0) |
| Repository: | CRAN |
| Date/Publication: | 2026-05-27 20:00:07 UTC |
Boosted HP Filter
Description
Iteratively applies the Hodrick-Prescott filter on residuals to better capture stochastic trends. At each iteration the HP smoother is applied to the current residual and the resulting trend increment is added to the cumulative trend estimate. Iteration stops according to one of three rules: BIC minimisation (default), ADF stationarity test on residuals, or a fixed number of iterations.
Usage
bhp_filter(
x,
lambda = NULL,
iter_max = 100L,
stopping = c("bic", "adf", "fixed"),
sig_level = 0.05,
freq = NULL
)
Arguments
x |
Numeric vector, |
lambda |
Smoothing parameter.
If |
iter_max |
Integer. Maximum number of boosting iterations (default 100). |
stopping |
Character.
Stopping rule: |
sig_level |
Numeric.
Significance level for the ADF test when |
freq |
Numeric frequency override (1 = annual, 4 = quarterly,
12 = monthly). Used only when |
Details
The boosted HP filter starts from the standard HP solution and then re-applies the same HP smoother to the residual (cycle) component. The trend increment from each pass is accumulated, and the procedure stops when one of the following criteria is met:
"bic"Schwarz information criterion computed as
n \log(\hat\sigma^2) + \log(n)\,\mathrm{tr}(S^m), whereS^mis the iterated smoother. Iteration stops when the BIC increases relative to the previous best."adf"Augmented Dickey-Fuller test on the residual. Iteration stops when the residual is stationary at level
sig_level."fixed"Runs exactly
iter_maxiterations.
Value
A macrofilter object with trend, cycle, data, and meta
components. The meta list contains method = "bHP", lambda,
iterations, stopping_rule, and compute_time.
References
Phillips, P.C.B. and Shi, Z. (2021). Boosting: Why You Can Use the HP Filter. International Economic Review, 62(2), 521–570.
Examples
# Quarterly GDP-like series
y <- ts(cumsum(rnorm(200)), start = c(2000, 1), frequency = 4)
result <- bhp_filter(y)
print(result)
Hamilton Filter
Description
Decomposes a time series into trend and cycle components using the
regression-based filter proposed by Hamilton (2018). The trend is the
fitted value from an OLS regression of y_{t+h} on
(1, y_t, y_{t-1}, \ldots, y_{t-p+1}), and the cycle is the residual.
Usage
hamilton_filter(x, h = NULL, p = 4L)
Arguments
x |
Numeric vector, |
h |
Integer horizon (number of periods ahead). If |
p |
Integer number of lags in the regression (default 4). |
Details
Hamilton (2018) proposes replacing the HP filter with a simple regression:
y_{t+h} = \beta_0 + \beta_1 y_t + \beta_2 y_{t-1} + \cdots +
\beta_p y_{t-p+1} + v_{t+h}
The fitted values \hat{y}_{t+h} define the trend and the residuals
\hat{v}_{t+h} define the cycle.
The first h + p - 1 observations have no computable trend or cycle
and are filled with NA.
The lag matrix is constructed vectorized via embed() and the
regression is solved with stats::lm.fit() for speed.
Value
A macrofilter object with trend, cycle, data, and meta
components. meta includes h, p, coefficients, and compute_time.
References
Hamilton, J.D. (2018). Why You Should Never Use the Hodrick-Prescott Filter. Review of Economics and Statistics, 100(5), 831–843.
Examples
# Quarterly GDP-like series
y <- ts(cumsum(rnorm(200)), start = c(2000, 1), frequency = 4)
result <- hamilton_filter(y)
print(result)
Hodrick-Prescott Filter (Sparse Matrix Implementation)
Description
Decomposes a time series into trend and cycle components by solving the HP penalized least-squares problem using a sparse Cholesky factorization. This avoids the dense O(n^3) inversion used by other implementations and scales linearly in the number of observations.
Usage
hp_filter(x, lambda = NULL, freq = NULL)
Arguments
x |
Numeric vector, |
lambda |
Smoothing parameter.
If |
freq |
Numeric frequency override (1 = annual, 4 = quarterly,
12 = monthly). Used only when |
Details
The HP filter minimises
\sum (y_t - \tau_t)^2 + \lambda \sum (\Delta^2 \tau_t)^2
which admits the closed-form solution
(I + \lambda D'D)\,\tau = y
where D is the second-difference operator.
The implementation builds D as a banded sparse matrix
(Matrix::bandSparse()) and solves the symmetric positive-definite system
with a sparse Cholesky decomposition (Matrix::solve()).
When lambda is not supplied the Ravn-Uhlig (2002) rule is applied:
lambda = 6.25 * freq^4, yielding 6.25 (annual), 1600 (quarterly), and
129 600 (monthly).
Value
A macrofilter object with trend, cycle, data, and meta
components.
References
Hodrick, R.J. and Prescott, E.C. (1997). Postwar U.S. Business Cycles: An Empirical Investigation. Journal of Money, Credit and Banking, 29(1), 1–16.
Ravn, M.O. and Uhlig, H. (2002). On Adjusting the Hodrick-Prescott Filter for the Frequency of Observations. Review of Economics and Statistics, 84(2), 371–376.
Examples
# Quarterly GDP-like series
y <- ts(cumsum(rnorm(200)), start = c(2000, 1), frequency = 4)
result <- hp_filter(y)
print(result)
MacroBoost Hybrid (MBH) Filter
Description
Decomposes a time series into trend and cycle using a robust boosting algorithm. Unlike the HP filter, MBH uses the Huber loss function to automatically downweight outliers (like the COVID-19 shock), preventing them from distorting the trend.
Usage
mbh_filter(
x,
knots = NULL,
mstop = 500L,
d = NULL,
nu = 0.1,
df = 4L,
select_mstop = FALSE,
boundary.knots = NULL
)
Arguments
x |
Numeric vector, |
knots |
Integer.
Number of interior knots for the P-Spline.
If |
mstop |
Integer.
Maximum number of boosting iterations (default 500).
If |
d |
Numeric or Scale-mismatch warning: When Recommendation: For a quick preliminary calibration use
|
nu |
Numeric. The learning rate (shrinkage) for boosting (default 0.1). |
df |
Integer. Effective degrees of freedom per boosting step for the P-Spline base learner (default 4). This enforces the weak-learner constraint of Bühlmann & Hothorn (2007): each boosting step contributes only a small, smooth update so that the trend is built up gradually over many iterations rather than fitted in one pass. End-point instability warning: Higher |
select_mstop |
Logical.
If AICc underfitting warning: In the combination of Huber
quasi-likelihood + P-splines, AICc penalises model complexity
hyper-aggressively. In practice the algorithm stops at iteration ~5–15
instead of the intended ~500. The resulting trend is nearly a straight
line; all long-run variance is pushed into the cycle component, defeating
the purpose of the filter. Treat |
boundary.knots |
A numeric vector of length 2 specifying the global
domain for the B-spline basis (e.g., |
Details
The model estimated is an additive model:
y_t = \text{Linear}(t) + \text{Smooth}(t) + \epsilon_t
It is fitted using mboost::mboost() with:
-
Base Learners: A linear time trend (
mboost::bols()) to capture the global path, plus a B-spline (mboost::bbs()) to capture local curvature. -
Loss Function: Huber loss (
mboost::Huber()) with parameterd. This is the key to robustness.
The default parameters (knots = n/2, mstop = 500) are calibrated to
mimic the flexibility of a standard HP filter while retaining the robustness
of the Huber loss.
Value
A macrofilter object with trend, cycle, data, and meta
components. The meta list contains method = "MBH", knots, d,
mstop, nu, df, select_mstop, and compute_time.
Calibration Guidance
Three failure modes were discovered through empirical stress-testing. The defaults guard against all three:
- 1. Huber delta scale mismatch (
d) -
The automatic fallback
mad(diff(y))operates on the scale of growth rates, not the output gap. For log-level input this setsdone to two orders of magnitude too small, causing ordinary business-cycle swings to be treated as outliers. If the estimated cycle looks implausibly large or the trend is nearly linear, override withd = mad(hp_filter(x)$cycle)as a starting point. - 2. AICc underfitting (
select_mstop) -
AICc + Huber quasi-likelihood + P-splines stops boosting at iteration ~5–15. The trend degenerates to a near-straight line and the cycle absorbs all long-run variance. Leave
select_mstop = FALSE(the default) and setmstopexplicitly instead. - 3. End-point instability (
df) -
Values above 4 shift the B-spline basis matrix non-smoothly as the sample grows, producing a "rubber-band" distortion in the final observations. Keep
df = 4(the default) for real-time applications.
Examples
# Fast example with reduced series and iterations
set.seed(42)
y <- ts(cumsum(rnorm(80)), start = c(2000, 1), frequency = 4)
result <- mbh_filter(y, mstop = 100L)
print(result)
# Full example with default parameters
y2 <- ts(cumsum(rnorm(200)), start = c(2000, 1), frequency = 4)
result2 <- mbh_filter(y2)
print(result2)
US Real GDP — FRED Vintage
Description
Quarterly US Real Gross Domestic Product from the Federal Reserve Bank of St. Louis (FRED) public data API (series GDPC1), expressed in billions of chained 2017 US dollars, seasonally adjusted annual rate.
Usage
us_gdp_vintage
Format
A data.table with one row per quarter and three columns:
dateDate. Quarter start date (e.g.1947-01-01= 1947 Q1).gdp_realnumeric. Real GDP level, billions of chained 2017 USD.gdp_lognumeric. Natural logarithm ofgdp_real, pre-computed for convenience.
Details
The dataset covers 1947 Q1 through the latest vintage available at download
time (approximately 316 rows as of 2025). Rows with NA in gdp_real are
excluded.
The log-level column (gdp_log) is particularly useful for trend-cycle
decomposition because log-differences approximate quarter-on-quarter
percentage growth rates:
\Delta \log(\text{GDP}_t) \approx g_t
Source
Federal Reserve Bank of St. Louis — FRED Economic Data,
series GDPC1. Downloaded via the public CSV endpoint
https://fred.stlouisfed.org/graph/fredgraph.csv?id=GDPC1.
See data-raw/us_gdp_vintage.R for the reproducible download script.
Examples
data("us_gdp_vintage", package = "MacroFilters")
head(us_gdp_vintage)
plot(us_gdp_vintage$date, us_gdp_vintage$gdp_log,
type = "l", xlab = "Date", ylab = "Log Real GDP",
main = "US Real GDP (log level)")