HamiltonFilter#

class cultivars.spectral.filters.HamiltonFilter(*, horizon=8, lags=4)[source]#

Hamilton’s (2018) regression filter.

Regress the level \(h\) steps ahead on a constant and its own \(p\) most recent values,

\[y_{t+h} = \beta_0 + \beta_1 y_t + \beta_2 y_{t-1} + \cdots + \beta_p y_{t-p+1} + v_{t+h},\]

by ordinary least squares over the whole sample; the fitted value is the trend at \(t + h\) – what the level’s own recent history could predict that far ahead – and the residual \(\hat v_{t+h}\) is the cycle. Both begin at observation \(h + p - 1\), so the result is that many rows shorter than the input and reports the shortfall as offset. The defaults are the paper’s quarterly \(h = 8, p = 4\); monthly data want \(h = 24, p = 12\), and in either case \(h\) should span the two years over which a business-cycle downturn resolves.

The filter exists as the alternative to HodrickPrescottFilter that Hamilton’s critique calls for. The regression is valid for I(1) and I(2) series alike – for a random walk the population coefficients are \(\beta_1 = 1\) and the rest zero, so the cycle is the \(h\)-step forecast error \(y_{t+h} - y_t\) – and the cycle is stationary by construction for any process whose \(d\)-th difference is stationary with \(d \le p\). No future observations enter the regressors, so the filter does not manufacture dynamics from the two-sided smoothing that HP does.

Variables:
  • _horizon – The forecast horizon \(h\).

  • _lags – The number of own-lag regressors \(p\).

Parameters:
  • horizon (int) – Forecast horizon \(h\), at least 1.

  • lags (int) – Regressors \(p\), at least 1.

Raises:

SpecificationError – If either is not an integer of at least 1.

Note

“One-sided” describes the regressors, not the estimate. The coefficients are estimated on the full sample, so extending the data re-estimates \(\beta\) and changes every fitted value a little; the result’s provisional flags are all False because no row depends on observations after \(t + h\) through the design, which is the revision HP suffers from. For a strictly real-time cycle fix the coefficients at their random walk values and take \(y_{t+h} - y_t\) directly.

For a random walk with innovation variance \(\sigma^2\) the cycle’s standard deviation is about \(\sigma\sqrt{h}\), which is what an \(h\)-step forecast error is; this is larger than the HP cycle’s, and correctly so.

See also

References

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

Example

>>> rng = np.random.default_rng(0)
>>> y = np.cumsum(rng.standard_normal(200))
>>> res = HamiltonFilter().filter(y)
>>> res.trend.shape, res.offset
((189, 1), 11)

The result is the OLS regression it claims to be, and on a random walk the cycle has the scale of an eight-step forecast error:

>>> design = np.column_stack([np.ones(189), y[3:192], y[2:191], y[1:190], y[0:189]])
>>> beta = np.linalg.lstsq(design, y[11:], rcond=None)[0]
>>> bool(np.allclose(design @ beta, res.trend[:, 0]))
True
>>> bool(abs(res.cycle.std() / np.sqrt(8) - 1.0) < 0.1)
True

Methods#

filter(data)

Filter a panel, one regression per column.