I\'m designing a bandpass filter in scipy following the cookbook. However, if I decrecrease the filtering frequencies too much I end up with garbage at high order filters. What
This is a common problem in digital filters. High order filters with cutoff frequencies far below the nyquist frequency tend to have unstable coefficients due to the limited precision of floating point numbers. Last I checked (admittedly a couple of years ago) Matlab did a much better job of conserving precision than scipy, although it will still give problems with sufficiently extreme filters.
There are a couple of options if you can't use matlab. The first is to break your filter up into cascaded second order sections. Basically you compute your desired poles and zeros, break them up into complex conjugate pairs, and compute the transfer function for each pair.
The second option is to resample to a sample rate more similar to the filter frequency. For instance, in your second example your sample rate is 25 and your highest cutoff frequency is .4. You can use a low-pass anti-aliasing filter and then decimate by a factor of 10 to a sample rate of 2.5. With the lower sampling rate, your bandpass filter coefficients will be less sensitive to rounding errors. If you do this, you have to make sure the anti-aliasing filter doesn't have the same problem.
What happens is that the orders of the bandpass (BP) filters created in the script are in fact the double of those shown in the plot. Recall that the order of the filter is the order of the polynomial in the denominator of the transfer function. Canonic bandpass filters are always of even order.
Those numbers shown are the orders of the low-pass (LP) prototype (usually normalized to a cutoff frequency of 1 rad/s) which is used to apply the LP-to-BP transformation which doubles the order of the filter. So, for instance, if we start with an LP of order 1, we end up with a second order bandpass:
1/(S+1) => LP-2-BP transf. => k.s/(s^2+a.s+b)
Where k, a and b are constants. The numerator of a standard bandpass filter is k.s^ (N/2) and so the order N of the filter has to be even.
This order issue for bandpass (which also happens to notches or band-reject filters) is not mentioned in SciPy documentation. In fact, if you print the length of denominator a (by using print(len(a))
) before plt.show()
, you will see it has 19 coefficients, what corresponds to a polynomial of 18th order.
b, a = butter
for high-order filters, whether in Matlab or SciPy or Octave. Transfer function format has numerical stability problems, because some of the coefficients are very large while others are very small. This is why we changed the filter design functions to use zpk format internally. To see the benefits of this, you need to use z, p, k = butter(output='zpk')
and then work with poles and zeros instead of numerator and denominator.sos = butter(output='sos')
and then filter using sosfilt() or sosfiltfilt(). This is the recommended way to filter for most applications.Apparently the issue is a known bug:
Github