# SOS-matrices' order does not correspond to given parameter when designing bandpass with scipy.signal.butter

I do not understand why this scipy operation:

from scipy import signal as sg
import numpy as np

fs = 48000
order = 4

# create bandpass from 100Hz to 200Hz:
sos_test = sg.butter(N=order,
Wn=np.array([100, 200]) / (fs / 2),
btype='bandpass',
analog=False,
output='sos')

print(f'sanity check sos-matrix of order {order}:\n{sos_test}')


produces:

sanity check sos-matrix of order 4:
[[ 1.80397952e-09  3.60795904e-09  1.80397952e-09  1.00000000e+00
-1.98583686e+00  9.86289427e-01]
[ 1.00000000e+00  2.00000000e+00  1.00000000e+00  1.00000000e+00
-1.98941516e+00  9.89671541e-01]
[ 1.00000000e+00 -2.00000000e+00  1.00000000e+00  1.00000000e+00
-1.99279291e+00  9.93445454e-01]
[ 1.00000000e+00 -2.00000000e+00  1.00000000e+00  1.00000000e+00
-1.99638455e+00  9.96563613e-01]]


SOS - "second order sections", meaning each array of 6 coefficients has the order 2. Looking at the print tells me that the first array is of order 2 while the rest is of order 1, which means I have a total of 5 orders. Shouldn't a sos-matrix contain half as many sections as the size of its order? I would expect:

[[b10 b11 b12 a10 a11 a12]    # 2nd order
[b20 b21 b22 a20 a21 a22]]   # 2nd order


for a sos-matrix of order 4.

Your expectations are reasonable. However, the definition of order in the design routine is confusing. It's the same in Matlab/Octave. For lowpass or highpass filters, order is indeed the filter order. BUT, for bandpass or bandstop filters, the resulting filter order is twice the value of order.

The reason for this is that order specifies the order of the (lowpass) prototype filter used for designing bandpass or bandstop filters. The transformation from the prototype lowpass filter to the desired bandpass or bandstop filter doubles the filter order.

In sum, order equals the filter order only in the case of lowpass and highpass filters. For bandpass and bandstop filters, the resulting filter order is 2*order.

• Might be worth a shot to add this to the docs. As of now only your answer clears this up and it doesn't make sense to have to view a stack exchange answer before using a library Jan 31, 2022 at 11:51
• @jake_asks_short_questions: That's right, send them a change request. Jan 31, 2022 at 11:55
• Actually that only seems to be the case for IIR filters. It's true for butter() but not for fir1() for example (at least in Matlab). Jan 31, 2022 at 12:39
• @Hilmar: Of course, it's only the case for methods based on the transformation of a protoype (lowpass) filter, originating from standard analog filter designs (Butterworth, Chebyshev, Elliptic). So yes, only IIR (obtained via bilinear transformation of analog filters). Jan 31, 2022 at 12:53
• Fresh issue on scipy Github Jan 31, 2022 at 13:26

Doc-fix got pulled, see new doc here