---
title: "Econometric Filters"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Econometric Filters}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
editor_options:
  markdown:
    wrap: 80
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4.5,
  fig.align = "center",
  message = FALSE,
  warning = FALSE
)
```

```{r setup}
#| include: false
library(trendseries)
library(dplyr)
library(tidyr)
library(ggplot2)
```

This vignette covers six econometric filters available in `trendseries`:
the **Henderson** and **Spencer** moving averages, the **Baxter-King** and
**Christiano-Fitzgerald** bandpass filters, and the **Hodrick-Prescott** and
**Hamilton** filters. All six are widely used in macroeconomics and official
statistics for extracting trends and isolating business cycles.

```{r libs}
#| eval: false
library(trendseries)
library(dplyr)
library(tidyr)
```

The theme below is used throughout the vignette for consistent styling.

```{r theme}
#| code-fold: true
library(ggplot2)

theme_series <- theme_minimal(paper = "#fefefe") +
  theme(
    legend.position = "bottom",
    panel.grid.minor = element_blank(),
    strip.background = element_rect(fill = "#2c3e50"),
    strip.text = element_text(color = "#fefefe"),
    axis.ticks.x = element_line(color = "gray40", linewidth = 0.5),
    axis.line.x = element_line(color = "gray40", linewidth = 0.5),
    axis.title.x = element_blank(),
    palette.colour.discrete = c(
      "#2c3e50",
      "#e74c3c",
      "#f39c12",
      "#1abc9c",
      "#9b59b6"
    )
  )
```

# Henderson and Spencer Moving Averages

## The Henderson Filter

The Henderson filter is the trend estimator used inside every major official
seasonal adjustment program: X-11, X-13ARIMA-SEATS (US Census Bureau), and
Statistics Canada's X-12-ARIMA. Originally proposed by Robert Henderson (1916)
to smooth actuarial mortality tables, it became the standard for national
accounts trend extraction because it satisfies two properties simultaneously:

1. **Cubic polynomial reproduction.** The filter passes polynomials up to
   degree 3 through exactly — a cubic trend is returned unchanged. This means
   the filter does not distort smooth, polynomial-like economic trajectories.
2. **Minimum roughness.** Among all symmetric filters that reproduce cubic
   polynomials, the Henderson weights minimise the sum of squared
   **third differences** of the smoothed series,
   $\sum_t (\Delta^3 \hat{\tau}_t)^2$.
   Minimising third differences is equivalent to finding the "most gradually
   changing" trend consistent with the cubic-reproduction constraint.

### The weights

For a filter of length $n = 2m + 1$, the unnormalized weights are

$$
w_j \propto \bigl[(m+1)^2-j^2\bigr]\bigl[(m+2)^2-j^2\bigr]\bigl[(m+3)^2-j^2\bigr]
  \cdot (\eta - j^2), \quad j = -m, \ldots, m
$$

where $\eta$ is determined by setting $\sum_j j^2 w_j = 0$, which enforces
cubic polynomial reproduction. The weights are then normalized to sum to one.

A distinguishing feature is that the weights take **small negative values at
the outermost positions**. Rather than amplifying peaks and troughs, the filter
slightly downweights the extreme observations relative to the center. This
property gives the Henderson trend its characteristic stability at turning
points.

```{r henderson-weights}
#| code-fold: true
#| fig-height: 3.5
# Compute 13-term Henderson weights from the closed-form formula
n <- 13L
m <- (n - 1L) / 2L
j <- seq_len(n) - (m + 1L)

P <- ((m + 1L)^2L - j^2L) *
     ((m + 2L)^2L - j^2L) *
     ((m + 3L)^2L - j^2L)

eta    <- sum(j^4L * P) / sum(j^2L * P)
w      <- P * (eta - j^2L)
w      <- w / sum(w)

weights_df <- data.frame(lag = j, weight = w)

ggplot(weights_df, aes(lag, weight)) +
  geom_col(aes(fill = weight > 0), width = 0.65, show.legend = FALSE) +
  geom_hline(yintercept = 0, color = "gray40", linewidth = 0.4) +
  scale_fill_manual(values = c("FALSE" = "#e74c3c", "TRUE" = "#2c3e50")) +
  scale_x_continuous(breaks = seq(-6, 6)) +
  labs(
    title    = "13-term Henderson Filter: Weight Profile",
    subtitle = "Weights sum to 1; small negative values at the outer lags stabilise turning points",
    x = "Lag (j)", y = "Weight"
  ) +
  theme_series
```

### Basic usage

`trendseries` defaults to a 9-term filter for quarterly data and 13-term for
monthly data. Pass the data frame directly to `augment_trends()`; the trend is
added as a new column.

```{r henderson-basic}
# Default: 13-term for monthly data
ibcbr_hend <- augment_trends(ibcbr, value_col = "index", methods = "henderson")

head(ibcbr_hend)
```

```{r henderson-plot}
ggplot(ibcbr_hend, aes(date)) +
  geom_line(aes(y = index, color = "Original"), linewidth = 0.6, alpha = 0.7) +
  geom_line(aes(y = trend_henderson, color = "Trend: 13-term Henderson"),
            linewidth = 0.9) +
  scale_x_date(date_breaks = "3 years", date_labels = "%Y") +
  labs(
    title  = "IBC-Br: 13-term Henderson Moving Average",
    x = NULL, y = "Index", color = NULL
  ) +
  theme_series
```

### Choosing the window

The three standard lengths reflect the trade-off between smoothness and
endpoint coverage:

- **9-term** — the standard for quarterly data; suppresses cycles shorter than
  four quarters.
- **13-term** — the default for monthly data in X-11 and in `trendseries`.
  Loses six observations at each end of the sample.
- **23-term** — for monthly series with high irregularity (sharp spikes,
  volatile revisions). Loses eleven observations at each end.

X-13ARIMA-SEATS selects among these three lengths automatically using the
**irregularity-to-cycle (I/C) ratio**, a signal-to-noise measure computed from
the series. In practice, 13-term is the right starting point for most monthly indicators.

Passing a vector to `window` runs all lengths in a single call and returns one
column per window, named `trend_henderson_{n}`.

```{r}
ibcbr_windows <- ibcbr |>
  filter(date >= as.Date("2015-01-01")) |>
  augment_trends(
    value_col = "index",
    methods = "henderson",
    window = c(9, 13, 23),
    .quiet = TRUE
  )
```

```{r henderson-windows}
#| code-fold: true
#| fig-height: 6

ibcbr_windows_long <- ibcbr_windows |>
  pivot_longer(
    cols = starts_with("trend_henderson_"),
    names_to = "window",
    values_to = "trend"
  ) |>
  mutate(
    window = factor(
      window,
      levels = c(
        "trend_henderson_9",
        "trend_henderson_13",
        "trend_henderson_23"
      ),
      labels = c("9-term", "13-term", "23-term")
    )
  )

ggplot(ibcbr_windows_long, aes(date, trend)) +
  geom_line(
    data = ibcbr_windows,
    aes(y = index),
    color = "#2c3e50",
    alpha = 0.5,
    linewidth = 0.5
  ) +
  geom_line(color = "#e74c3c", linewidth = 0.9, na.rm = TRUE) +
  facet_wrap(vars(window), ncol = 1) +
  scale_x_date(date_breaks = "2 years", date_labels = "%Y") +
  labs(
    title = "Henderson Filter: Effect of Window Size",
    subtitle = "Gray = original series; larger windows are smoother but lose more data at the endpoints",
    x = NULL,
    y = "Index"
  ) +
  theme_series
```

> **When to use the Henderson filter:** It is the natural choice when matching
> the methodology of official seasonal adjustment software (X-11, X-13,
> TRAMO-SEATS), or when you need a symmetric filter that exactly preserves
> polynomial trends up to degree 3. For monthly data, 13-term is the right
> default; switch to 23-term only if the series has sharp irregular spikes.
> For quarterly data, use 9-term.
>
> **When to be cautious:** Like all symmetric filters, Henderson loses
> $\lfloor n/2 \rfloor$ observations at each end — 6 months for the 13-term,
> 11 months for the 23-term. If end-of-sample estimates matter (nowcasting,
> near real-time monitoring), the one-sided HP filter or the Hamilton filter
> are more appropriate choices.

## The Spencer Moving Average

The Spencer 15-term moving average is the classical predecessor of the
Henderson filter, originally designed for smoothing mortality tables. It uses
fixed weights — no tuning parameters — and passes polynomials up to degree 3,
just like the Henderson filter. Because `trendseries` applies linear
extrapolation at the endpoints, the Spencer filter returns a trend for every
observation.

```{r spencer-data}
ibcbr_sp <- augment_trends(ibcbr, value_col = "index", methods = "spencer")

ibcbr_sp_recent <- ibcbr_sp |>
  filter(date >= as.Date("2015-01-01"))
```

```{r spencer-plot}
ggplot(ibcbr_sp_recent, aes(date)) +
  geom_line(aes(y = index, color = "Original"), linewidth = 0.6, alpha = 0.7) +
  geom_line(aes(y = trend_spencer, color = "Trend: Spencer 15-term"),
            linewidth = 0.9) +
  scale_x_date(date_breaks = "2 years", date_labels = "%Y") +
  labs(
    title = "IBC-Br: Spencer 15-term Moving Average",
    x = NULL, y = "Index", color = NULL
  ) +
  theme_series
```

Comparing Henderson and Spencer on the same chart reveals that the two filters
are very similar. The main practical advantage of the Henderson
filter is that its length can be chosen to match the irregularity of the series. The `augment_trends` function accepts multiple methods and returns a column for each method.

```{r henderson-spencer-data}
ibcbr_hs <- ibcbr |>
  augment_trends(
    value_col = "index",
    methods = c("henderson", "spencer"),
    .quiet = TRUE
  )
```

```{r henderson-spencer-compare}
#| code-fold: true
ibcbr_hs_long <- ibcbr_hs |>
  filter(date >= as.Date("2015-01-01")) |>
  pivot_longer(
    cols = c(trend_henderson, trend_spencer),
    names_to = "filter",
    values_to = "trend"
  ) |>
  mutate(
    filter = recode(
      filter,
      trend_henderson = "Henderson (13-term)",
      trend_spencer = "Spencer (15-term)"
    )
  )

ggplot(ibcbr_hs_long, aes(date, trend)) +
  geom_line(
    aes(y = index),
    color = "gray70",
    linewidth = 0.5
  ) +
  geom_line(color = "#2c3e50", linewidth = 0.9, na.rm = TRUE) +
  facet_wrap(vars(filter), ncol = 2) +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
  labs(
    title = "Henderson vs. Spencer",
    subtitle = "Gray = original series; both filters produce very similar trends",
    x = NULL,
    y = "Index"
  ) +
  theme_series
```

---

# Bandpass Filters: Baxter-King and Christiano-Fitzgerald

Bandpass filters are designed to **isolate oscillations within a specific
frequency range**. In macroeconomics the goal is usually to isolate the
*business cycle*: fluctuations with periods between roughly 1.5 and 8 years
(6 to 32 quarters). The trend returned by these filters is the series with
those frequencies removed: a very smooth, long-run path.

Both the **Baxter-King (BK)** and **Christiano-Fitzgerald (CF)** filters share
the same economic interpretation: they pass only components with periods
outside the $[p_l, p_u]$ band and suppress everything within that band.

We use the quarterly GDP construction index (Brazil) to illustrate these
filters, since quarterly data maps naturally onto the standard 6–32 quarter
business cycle definition.

```{r gdp-plot}
#| code-fold: true
ggplot(gdp_construction, aes(date, index)) +
  geom_line(linewidth = 0.7, color = "#2c3e50") +
  scale_x_date(date_breaks = "5 years", date_labels = "%Y") +
  labs(
    title = "GDP – Construction (Brazil)",
    subtitle = "Chained index, quarterly",
    x = NULL,
    y = "Index"
  ) +
  theme_series
```

## Baxter-King Filter

The BK filter approximates the ideal bandpass filter with a **symmetric
moving average**. Its weights are chosen to minimise the distance between the
approximating filter and the ideal (brick-wall) bandpass filter in the
frequency domain. The key parameters are:

- `band = c(pl, pu)`: lower and upper period bounds (in quarters by default).
  The default `c(6, 32)` targets cycles of 1.5 to 8 years — the standard
  macroeconomic business cycle definition.

Because the BK filter is symmetric, it introduces $\lfloor \mathrm{pu}/2
\rfloor$ missing values at each end of the series.

```{r bk-filter}
gdp_bk <- augment_trends(
  gdp_construction,
  date_col = "date",
  value_col = "index",
  methods = "bk"
)

head(gdp_bk)
```

```{r bk-plot}
#| code-fold: true
ggplot(gdp_bk, aes(date)) +
  geom_line(aes(y = index, color = "Original"), linewidth = 0.6, alpha = 0.7) +
  geom_line(
    aes(y = trend_bk, color = "Trend: Baxter-King"),
    linewidth = 0.9,
    na.rm = TRUE
  ) +
  scale_x_date(date_breaks = "5 years", date_labels = "%Y") +
  labs(
    title = "GDP Construction: Baxter-King Filter",
    subtitle = "Trend after removing business-cycle frequencies (6–32 quarters)",
    x = NULL,
    y = "Index",
    color = NULL
  ) +
  theme_series
```

It is often more informative to look at the **cyclical component**:
the deviations of the series from its long-run trend.

```{r bk-cycle-data}
gdp_cycle_bk <- gdp_bk |>
  mutate(cycle = index - trend_bk) |>
  filter(!is.na(cycle))
```

```{r bk-cycle-plot}
#| code-fold: true
ggplot(gdp_cycle_bk, aes(date, cycle)) +
  geom_hline(yintercept = 0, color = "gray50", linewidth = 0.5) +
  geom_line(linewidth = 0.8, color = "#e74c3c") +
  geom_area(alpha = 0.2, fill = "#e74c3c") +
  scale_x_date(date_breaks = "5 years", date_labels = "%Y") +
  labs(
    title    = "GDP Construction: Business Cycle (BK Filter)",
    subtitle = "Deviations from long-run trend; positive = above trend",
    x = NULL, y = "Cyclical component"
  ) +
  theme_series
```

> **When to use BK:** It is the standard reference filter for business cycle
> analysis in academic research. Prefer it when reproducibility matters and
> the series is long enough.
>
> **When to avoid BK:** The rule of thumb is to have at least $3 \times
> p_{u}$ observations. With the default `pu = 32` quarters, that means
> at least 96 quarterly observations (24 years). Shorter series will have
> unreliable endpoint estimates.

## Christiano-Fitzgerald Filter

The CF filter relaxes the symmetry requirement: it is an **asymmetric filter
that uses all available observations**, including those near the endpoints. As
a result, it produces no missing values due to truncation.

The parameters are the same as for BK (`band = c(pl, pu)`) and the economic
interpretation is identical.

```{r cf-bk-data}
gdp_bk_cf <- augment_trends(
  gdp_construction,
  date_col = "date",
  value_col = "index",
  methods = c("bk", "cf")
)

gdp_cycles_long <- gdp_bk_cf |>
  mutate(
    `Baxter-King` = index - trend_bk,
    `Christiano-Fitzgerald` = index - trend_cf
  ) |>
  pivot_longer(
    cols = c(`Baxter-King`, `Christiano-Fitzgerald`),
    names_to = "filter",
    values_to = "cycle"
  )
```

```{r cf-bk-compare}
#| code-fold: true
ggplot(gdp_cycles_long, aes(date, cycle)) +
  geom_hline(yintercept = 0, color = "gray50", linewidth = 0.4) +
  geom_line(color = "#e74c3c", linewidth = 0.8, na.rm = TRUE) +
  geom_area(alpha = 0.15, fill = "#e74c3c", na.rm = TRUE) +
  facet_wrap(vars(filter), ncol = 2) +
  scale_x_date(date_breaks = "5 years", date_labels = "%Y") +
  labs(
    title    = "Business Cycle: Baxter-King vs. Christiano-Fitzgerald",
    subtitle = "Both isolate cycles of 6–32 quarters; CF has no endpoint NAs",
    x = NULL, y = "Cyclical component"
  ) +
  theme_series
```

The two filters track each other closely in the interior of the sample. The CF
filter's main advantage is its ability to provide estimates at the endpoints,
making it the safer default when the sample is short or when recent values are
important.

---

# The Hodrick-Prescott Filter

The Hodrick-Prescott (HP) filter is one of the most widely used trend-extraction
methods in macroeconomics. It finds the trend $\{\tau_t\}$ that solves the
penalised least-squares problem

$$
\min_{\{\tau_t\}} \sum_{t=1}^{T}(y_t - \tau_t)^2
  + \lambda \sum_{t=2}^{T-1}(\Delta^2 \tau_t)^2
$$

where $\Delta^2 \tau_t = \tau_t - 2\tau_{t-1} + \tau_{t-2}$ is the second
difference. The smoothing parameter $\lambda$ trades off fit against
smoothness: larger values force $\tau_t$ closer to a linear trend. The
standard values are **$\lambda = 1600$** for quarterly data and **$\lambda =
14400$** for monthly data. These defaults typically produce very smooth trends.

```{r hp-filter}
ibcbr_hp <- augment_trends(ibcbr, value_col = "index", methods = "hp")

head(ibcbr_hp)
```

```{r hp-plot}
#| code-fold: true
ggplot(ibcbr_hp, aes(date)) +
  geom_line(aes(y = index,    color = "Original"), linewidth = 0.6, alpha = 0.7) +
  geom_line(aes(y = trend_hp, color = "Trend: HP (λ = 14,400)"),
            linewidth = 0.9) +
  scale_x_date(date_breaks = "3 years", date_labels = "%Y") +
  labs(
    title = "IBC-Br: Hodrick-Prescott Filter",
    x = NULL, y = "Index", color = NULL
  ) +
  theme_series
```

### The smoothing parameter $\lambda$

The `smoothing` argument controls $\lambda$. Values above 1 are used directly;
values in $(0, 1]$ are interpreted as a fraction of the standard lambda.

```{r hp-lambda-data}
#| code-fold: true
hp_lambdas <- ibcbr |>
  filter(date >= as.Date("2010-01-01")) |>
  augment_trends(
    value_col = "index",
    methods = "hp",
    smoothing = 1600,
    .quiet = TRUE
  ) |>
  augment_trends(
    value_col = "index",
    methods = "hp",
    smoothing = 14400,
    .quiet = TRUE
  ) |>
  augment_trends(
    value_col = "index",
    methods = "hp",
    smoothing = 129600,
    .quiet = TRUE
  )
```

```{r hp-lambda-plot}
#| code-fold: true
hp_lambdas_long <- hp_lambdas |>
  pivot_longer(
    cols = starts_with("trend_hp"),
    names_to = "lambda",
    values_to = "trend"
  ) |>
  mutate(
    lambda = factor(
      lambda,
      levels = c("trend_hp", "trend_hp_1", "trend_hp_2"),
      labels = c("λ = 1,600", "λ = 14,400 (default)", "λ = 129,600")
    )
  )

ggplot(hp_lambdas_long, aes(date, trend)) +
  geom_line(
    data = hp_lambdas,
    aes(y = index),
    color = "gray60",
    linewidth = 0.5
  ) +
  geom_line(color = "#2c3e50", linewidth = 0.9) +
  facet_wrap(vars(lambda), ncol = 3) +
  scale_x_date(date_breaks = "3 years", date_labels = "%Y") +
  labs(
    title = "HP Filter: Effect of the Smoothing Parameter λ",
    subtitle = "Gray = original series; larger λ → smoother trend, closer to a linear fit",
    x = NULL,
    y = "Index"
  ) +
  theme_series
```

> **When to use HP:** It is the standard benchmark in academic macro and is
> directly comparable across studies that use the same $\lambda$. The two-sided
> HP filter (the default) provides a balanced trend with no asymmetric lag.
>
> **Known limitations:** The HP filter suffers from an **endpoint problem** —
> the trend at recent observations is strongly influenced by the last data
> point and can exhibit spurious movements. Hamilton (2018) shows that the HP
> filter can introduce spurious cyclicality even in random walk processes.
> For real-time or end-of-sample analysis, consider the one-sided HP filter
> (`params = list(hp_onesided = TRUE)`) or the Hamilton filter.

---

# The Hamilton Filter

Hamilton (2018) proposed a regression-based alternative specifically designed
to avoid the HP filter's shortcomings. The idea is to regress the value
$h$ periods ahead on $p$ lags of the current level:

$$
y_{t+h} = \alpha + \beta_1 y_t + \beta_2 y_{t-1} + \cdots + \beta_p y_{t-p+1} + \varepsilon_{t+h}
$$

The **fitted values** $\hat{y}_{t+h}$ serve as the trend estimate. The
residuals $\hat{\varepsilon}_{t+h}$ form the cyclical component.

The recommended parameters from Hamilton (2018) are:

- **Monthly data:** $h = 24$ (two years ahead), $p = 12$ (one year of lags)
- **Quarterly data:** $h = 8$ (two years ahead), $p = 4$ (one year of lags)

For quarterly data the regression written out in full is:

$$
y_{t+8} = \alpha + \beta_1 y_t + \beta_2 y_{t-1} + \beta_3 y_{t-2} + \beta_4 y_{t-3} + \varepsilon_{t+8}
$$

These are the defaults in `trendseries` for monthly and quarterly series
respectively. Because the regressors require $p$ consecutive lags and the
dependent variable requires $h$ forward observations, the first $h + p - 1$
observations have no trend estimate.

```{r hamilton-filter}
ibcbr_hamilton <- augment_trends(
  ibcbr,
  value_col = "index",
  methods = "hamilton"
)

head(ibcbr_hamilton)
```

```{r hamilton-plot}
#| code-fold: true
ggplot(ibcbr_hamilton, aes(date)) +
  geom_line(aes(y = index, color = "Original"), linewidth = 0.6, alpha = 0.7) +
  geom_line(
    aes(y = trend_hamilton, color = "Trend: Hamilton"),
    linewidth = 0.9,
    na.rm = TRUE
  ) +
  scale_x_date(date_breaks = "3 years", date_labels = "%Y") +
  labs(
    title = "IBC-Br: Hamilton Filter",
    subtitle = paste0(
      "Fitted values from y[t+24] ~ y[t] + ... + y[t-11]; ",
      "first 35 rows are NA (h + p - 1 = 24 + 12 - 1)"
    ),
    x = NULL,
    y = "Index",
    color = NULL
  ) +
  theme_series
```

The cyclical component — the deviations of the series from the Hamilton trend —
reveals the business cycle as estimated by the regression residuals.

```{r hamilton-cycle}
#| code-fold: true
ibcbr_hamilton_cycle <- ibcbr_hamilton |>
  mutate(cycle = index - trend_hamilton) |>
  filter(!is.na(cycle))

ggplot(ibcbr_hamilton_cycle, aes(date, cycle)) +
  geom_hline(yintercept = 0, color = "gray50", linewidth = 0.5) +
  geom_line(linewidth = 0.8, color = "#e74c3c") +
  geom_area(alpha = 0.2, fill = "#e74c3c") +
  scale_x_date(date_breaks = "3 years", date_labels = "%Y") +
  labs(
    title    = "IBC-Br: Cyclical Component (Hamilton Filter)",
    subtitle = "Residuals from the Hamilton regression; positive = above trend",
    x = NULL, y = "Cyclical component"
  ) +
  theme_series
```

### Hamilton vs. HP

The key differences between the two filters become visible when comparing
them side by side. In contrast to the HP filter, the Hamilton trend shows
more variability — it reacts faster to structural changes and does not exhibit
the smooth "gliding" behaviour that HP produces near the endpoints of the sample.

```{r hamilton-hp-data}
hp_vs_ham <- ibcbr |>
  filter(date >= as.Date("2010-01-01")) |>
  augment_trends(
    value_col = "index",
    methods = c("hp", "hamilton"),
    .quiet = TRUE
  )
```

```{r hamilton-hp-compare}
#| code-fold: true
hp_ham_long <- hp_vs_ham |>
  pivot_longer(
    cols = c(trend_hp, trend_hamilton),
    names_to = "filter",
    values_to = "trend"
  ) |>
  mutate(
    filter = recode(
      filter,
      trend_hp = "HP (λ = 14,400)",
      trend_hamilton = "Hamilton (h = 24, p = 12)"
    )
  )

ggplot(hp_ham_long, aes(date, trend)) +
  geom_line(
    data = hp_vs_ham,
    aes(y = index),
    color = "gray70",
    linewidth = 0.5
  ) +
  geom_line(color = "#2c3e50", linewidth = 0.9, na.rm = TRUE) +
  facet_wrap(vars(filter), ncol = 2) +
  scale_x_date(date_breaks = "2 years", date_labels = "%Y") +
  labs(
    title = "HP vs. Hamilton Filter on IBC-Br",
    subtitle = "Gray = original series; HP is smoother, Hamilton reacts faster and avoids endpoint distortion",
    x = NULL,
    y = "Index"
  ) +
  theme_series
```

> **When to use Hamilton:** Prefer it when the endpoint problem of HP is a
> concern (e.g., near real-time analysis), or when you want a trend that is
> robust to the critiques in Hamilton (2018). It is also straightforward to
> interpret: the trend is simply a projection of the future level onto past
> values.
>
> **Limitations:** The first $h + p - 1$ observations have no trend estimate
> (35 months for the default monthly settings, 11 quarters for quarterly).
> Unlike the HP filter, the trend is available all the way to the last
> observation in the sample.

---

# All Filters Together

Applying several filters simultaneously is straightforward with `augment_trends`.
The comparison below uses the IBC-Br after 2010, where all filters have
sufficient data.

```{r all-filters-data}
ibcbr_all <- ibcbr |>
  filter(date >= as.Date("2010-01-01")) |>
  augment_trends(
    value_col = "index",
    methods = c("henderson", "spencer", "hp", "hamilton"),
    .quiet = TRUE
  )
```

```{r all-filters-plot}
#| code-fold: true
#| fig-height: 6
ibcbr_all_long <- ibcbr_all |>
  pivot_longer(
    cols = c(trend_henderson, trend_spencer, trend_hp, trend_hamilton),
    names_to = "filter",
    values_to = "trend"
  ) |>
  mutate(
    filter = factor(
      filter,
      levels = c(
        "trend_henderson",
        "trend_spencer",
        "trend_hp",
        "trend_hamilton"
      ),
      labels = c(
        "Henderson (13-term)",
        "Spencer (15-term)",
        "HP (λ = 14,400)",
        "Hamilton (h=24, p=12)"
      )
    )
  )

ggplot(ibcbr_all_long, aes(date, trend)) +
  geom_line(
    data = ibcbr_all,
    aes(y = index),
    color = "gray70",
    linewidth = 0.5
  ) +
  geom_line(color = "#2c3e50", linewidth = 0.8, na.rm = TRUE) +
  facet_wrap(vars(filter), ncol = 2) +
  scale_x_date(date_breaks = "3 years", date_labels = "%Y") +
  labs(
    title = "IBC-Br: All Filters Compared",
    subtitle = "Gray = original series",
    x = NULL,
    y = "Index"
  ) +
  theme_series
```

The Henderson and Spencer filters are the smoothest and closest to each other.
HP produces a similar result but is derived from a different optimisation
criterion. Hamilton tracks the series more closely and shows more residual
variation in the trend.

# Quick Reference

| Filter | Key parameter | Default (monthly) | Endpoint NAs | Main use |
|---|---|---|---|---|
| `henderson` | `window` (odd integer) | 13 | `floor(window/2)` each end | Official statistics, X-11/X-13 |
| `spencer` | — | 15 (fixed) | 0 (extrapolated) | Classical smoothing |
| `bk` | `band = c(pl, pu)` | `c(6, 32)` | ~`pu/2` each end | Business cycle isolation, long series |
| `cf` | `band = c(pl, pu)` | `c(6, 32)` | 0 | Business cycle isolation, any length |
| `hp` | `smoothing` (λ) | 14 400 | 0 | Macro benchmark, cycle extraction |
| `hamilton` | `params` (h, p) | h=24, p=12 | First h+p−1 | Real-time trend, HP alternative |

# References

Baxter, M. & King, R. G. (1999). Measuring business cycles: Approximate
band-pass filters for economic time series. *Review of Economics and
Statistics*, 81(4), 575–593.

Christiano, L. J. & Fitzgerald, T. J. (2003). The band pass filter.
*International Economic Review*, 44(2), 435–465.

Hamilton, J. D. (2018). Why you should never use the Hodrick-Prescott filter.
*Review of Economics and Statistics*, 100(5), 831–843.

Henderson, R. (1916). Note on graduation by adjusted average.
*Transactions of the Actuarial Society of America*, 17, 43–48.

Hodrick, R. J. & 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. & Uhlig, H. (2002). On adjusting the Hodrick-Prescott filter for
the frequency of observations. *Review of Economics and Statistics*, 84(2),
371–376.
