# Why does a higher sampling frequency mess up my bandpass filter?

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))

• this is happening using double precision? doesn't 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. – robert bristow-johnson Jan 22 '19 at 19:39
• i have an answer at another question about plotting frequency response "manually" (instead of using freqz()) that deals with this cosine problem. – robert bristow-johnson Jan 22 '19 at 19:41
• You should try to split your filter into an order-2 filter and an order-1 filter and then cascade them. You should get a better conditioned filter. You simply need to add the frequency response of both filters. – Ben Jan 22 '19 at 21:33
• @Ben An easier way is to use output='sos' and sosfreqz() and sosfilt(), which does that automatically. It should be the first thing you reach for when filtering. – endolith Jan 22 '19 at 22:30
• thank you all, i was able to get it using sosfreqz() and sosfilt(). I will update the OP – khuynh Jan 22 '19 at 22:42

You might benefit from splitting this into second-order sections. The easiest way is to use output='sos' and sosfreqz() and sosfilt(), which handle the splitting automatically.
These second-order section functions should be the first thing you try, actually. They didn't exist in scipy until recently, so they aren't the default, for backwards-compatibility, but should always work better than output='ba', lfilter, etc.