ButterworthFilter#

class cultivars.spectral.filters.ButterworthFilter(*, period=32.0, order=2)[source]#

Gomez’s (2001) Butterworth sine low-pass filter, stated by cutoff period and order.

The trend is the minimizer of

\[\sum_t (y_t - \tau_t)^2 + \lambda \sum_t (\Delta^n \tau_t)^2, \qquad \lambda = \bigl(2 \sin(\pi / P)\bigr)^{-2n},\]

whose two-sided gain is the Butterworth form

\[G(\omega) = \frac{1}{1 + \bigl(\sin(\omega/2) / \sin(\omega_c/2)\bigr)^{2n}}, \qquad \omega_c = 2\pi / P,\]

equal to one half at the cutoff period \(P\) for every order and falling from one to zero more sharply as \(n\) grows. Unlike HodrickPrescottFilter the user states the frequency, not the weight: period=32, order=2 is HP at \(\lambda = 677\), and period=39.7, order=2 is HP at 1600. The penalty grows as \(P^{2n}\), so the same cutoff at order 3 is \(\lambda \approx 17620\) and at order 4 \(\lambda \approx 4.6 \times 10^5\).

Variables:
  • _period – The cutoff period \(P\) in observations per cycle.

  • _order – The differences penalized, \(n\).

Parameters:
  • period (float) – Cutoff period in observations per cycle, above 2 (the Nyquist period); 32 quarterly for the Burns-Mitchell convention.

  • order (int) – Differences penalized, at least 1; 2 reproduces HP at the matching cutoff, higher is sharper.

Raises:

SpecificationError – If the period is 2 or below, or the order is not an integer of at least 1.

Note

The order buys sharpness at two costs. The trend’s null space is polynomials of degree \(n - 1\), so order 3 lets a quadratic through as trend where HP would bend it into the cycle. And the smoother is solved as \((I + \lambda D'D)\tau = y\) in banded form, whose conditioning degrades with \(\lambda\): the gain matches the closed form to three decimals through order 7 at period 32 and is visibly wrong at order 8 (0.86 where it should be 1.00), so treat orders above 6 as outside the filter’s reliable range. The end-point problem worsens with order as well, and the result flags the same ceil(period) rows at each edge as provisional regardless.

See also

References

Gomez, V. (2001). The use of Butterworth filters for trend and cycle estimation in economic time series. Journal of Business & Economic Statistics, 19(3), 365-373.

Example

>>> rng = np.random.default_rng(0)
>>> y = np.cumsum(rng.standard_normal(200))
>>> res = ButterworthFilter(period=32, order=3).filter(y)
>>> res.trend.shape, round(res.cutoff, 1), round(res.penalty)
((200, 1), 32.0, 17620)

Order 2 at the HP cutoff period is the HP filter, and the gain at a period half the cutoff falls from 0.20 at order 1 to 0.004 at order 4:

>>> hp = HodrickPrescottFilter().filter(y)
>>> same = ButterworthFilter(period=hp.cutoff, order=2).filter(y)
>>> bool(np.allclose(same.trend, hp.trend))
True
>>> t = np.arange(4000.0)
>>> wave = np.cos(2 * np.pi * t / 16)
>>> gain = lambda n: ButterworthFilter(period=32, order=n).filter(wave).trend[1000:3000, 0]
>>> [round(float(gain(n).std() / wave[1000:3000].std()), 3) for n in (1, 2, 4)]
[0.202, 0.06, 0.004]

Methods#

filter(data)

Filter a panel.