I was designing a bandpass filter in python using some of the scipy.signal modules.
I am plotting the frequency response of my filter to verify that my desired frequency is in the passband. However, when I make the sampling frequency larger, the frequency response of my filter gets completely messed up.
I currently have a signal that I sample >300KSPS and am trying to create a bandpass filter for some fairly low frequencies (1-100hz). Could somebody explain why this happens?
For example, the following code yields this frequency response:
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal
N = 3
fs = 10000.0
low = 100.0
high = 150.0
nyq = fs * 0.5
Wn = [low/nyq, high/nyq]
b, a = scipy.signal.butter(N, Wn, btype='bandpass')
w, h = scipy.signal.freqz(b, a, worN=round(fs/2))
ax = plt.subplot(121)
ax.set(title='filter frequency response',
xlabel='frequency [hz]',
ylabel='gain',
xlim=(low/2, high*2))
ax.plot((nyq / np.pi) * w, abs(h), label='filter freq response')
ax.axvline(x=125.0, linestyle='--', alpha=0.5, c='black', label='f=125.0')
ax.axhline(y=np.sqrt(0.5), linestyle='--', alpha=0.5, c='black', label='sqrt(0.5)')
ax.grid()
ax.legend()
Then when I change my sampling frequency to 100000.0, the response turns out to be this:
fs = 100000.0
EDIT: Outputting the filter as second-order sections and using scipy.signal.sosfreqz
yielded the proper filter. See code below for the modified lines:
sos = scipy.signal.butter(N, Wn, btype='bandpass', output='sos')
w, h = scipy.signal.sosfreqz(sos, worN=round(fs/2))
scipy.signal.freqz()
use double precision? because, when increasing the sample rate to a high value turns a nice frequency response into a crappy frequency response, that usually is because of the cosine problem. this is because $$ \cos\big(\tfrac{\omega}{f_\mathrm{s}}\big) \approx 1$$ is so close to 1 that much of the precision regarding $\tfrac{\omega}{f_\mathrm{s}}$ is lost. $\endgroup$ – robert bristow-johnson Jan 22 '19 at 19:39freqz()
) that deals with this cosine problem. $\endgroup$ – robert bristow-johnson Jan 22 '19 at 19:41output='sos'
andsosfreqz()
andsosfilt()
, which does that automatically. It should be the first thing you reach for when filtering. $\endgroup$ – endolith Jan 22 '19 at 22:30sosfreqz()
andsosfilt()
. I will update the OP $\endgroup$ – khuynh Jan 22 '19 at 22:42