Chebyshev Filter Design: Theory and Python Implementation

Learn Chebyshev filter (Type I & II) design from mathematical foundations — equiripple theory, order determination, and practical lowpass/highpass/bandpass design using Python SciPy.

Introduction

The Chebyshev filter achieves a sharper transition band than a Butterworth filter of the same order by allowing equiripple in the passband (Type I) or stopband (Type II).

Where the Butterworth filter maximizes flatness in the passband, the Chebyshev filter trades that flatness for steeper rolloff. This article covers the mathematical foundations of both types and their practical design with SciPy.

Chebyshev Polynomials

Chebyshev filter frequency responses are based on the Chebyshev polynomial \(T_N(x)\):

\[T_N(x) = \begin{cases} \cos(N \arccos x) & |x| \leq 1 \\ \cosh(N \operatorname{arccosh} x) & |x| > 1 \end{cases} \tag{1}\]

The first few Chebyshev polynomials are:

Order \(N\)\(T_N(x)\)
0\(1\)
1\(x\)
2\(2x^2 - 1\)
3\(4x^3 - 3x\)
4\(8x^4 - 8x^2 + 1\)

Within \(|x| \leq 1\), the Chebyshev polynomial oscillates between \(-1\) and \(+1\) with equal amplitude — this property produces the equiripple characteristic in the filter.

Chebyshev Type I Filter

Magnitude Response

The magnitude squared function of an \(N\)-th order Chebyshev Type I lowpass filter is:

\[|H(j\Omega)|^2 = \frac{1}{1 + \varepsilon^2 T_N^2\!\left(\dfrac{\Omega}{\Omega_p}\right)} \tag{2}\]

where:

  • \(\varepsilon\): ripple factor (\(\varepsilon > 0\))
  • \(\Omega_p\): passband edge angular frequency
  • \(T_N\): Chebyshev polynomial of order \(N\)

In the passband \(\Omega \leq \Omega_p\), we have \(T_N(\Omega/\Omega_p) \in [-1, 1]\), so the magnitude ripples between \(1/\sqrt{1+\varepsilon^2}\) and \(1\) with equal amplitude. The relationship between passband ripple \(R_p\) [dB] and the ripple factor is:

\[\varepsilon = \sqrt{10^{R_p/10} - 1} \tag{3}\]

Pole Locations

The poles of a Chebyshev Type I filter lie on an ellipse. For \(k = 1, 2, \ldots, N\):

\[\sigma_k = -\sinh\!\left(\frac{\operatorname{arcsinh}(1/\varepsilon)}{N}\right)\sin\theta_k \tag{4}\]

\[\omega_k = \cosh\!\left(\frac{\operatorname{arcsinh}(1/\varepsilon)}{N}\right)\cos\theta_k \tag{5}\]

where \(\theta_k = \dfrac{\pi(2k-1)}{2N}\). Only the left half-plane poles \(s_k = \sigma_k + j\omega_k\) are used for stability.

Chebyshev Type II Filter

The Chebyshev Type II (inverse Chebyshev) filter places equiripple in the stopband while maintaining a monotonic passband. Its magnitude squared function is:

\[|H(j\Omega)|^2 = \frac{1}{1 + \left[\varepsilon^2 T_N^2\!\left(\dfrac{\Omega_s}{\Omega}\right)\right]^{-1}} \tag{6}\]

where \(\Omega_s\) is the stopband edge angular frequency. With a monotonic passband, the phase response is smoother than Type I.

Type I vs Type II Comparison

PropertyChebyshev Type IChebyshev Type II
PassbandEquirippleMonotone (flat)
StopbandMonotoneEquiripple
TransitionSharper than ButterworthSharper than Butterworth
PhaseNonlinearRelatively smooth
Use caseSharp cutoff, ripple allowedFlat passband, sharp cutoff

Filter Order Determination

Given specifications (passband ripple \(R_p\), stopband attenuation \(R_s\), passband edge \(\Omega_p\), stopband edge \(\Omega_s\)), the minimum required order is:

\[N \geq \frac{\operatorname{arccosh}\!\left(\sqrt{\dfrac{10^{R_s/10}-1}{10^{R_p/10}-1}}\right)}{\operatorname{arccosh}(\Omega_s/\Omega_p)} \tag{7}\]

For the same specification, a Chebyshev filter requires a lower order than a Butterworth filter.

Python Implementation

Chebyshev Type I Filter with SciPy

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal

# --- Filter specification ---
fs = 1000         # Sampling frequency [Hz]
fp = 100          # Passband edge [Hz]
rp = 1.0          # Passband ripple [dB]
orders = [2, 4, 6]

fig, axes = plt.subplots(2, 1, figsize=(10, 8))

for N in orders:
    sos = signal.cheby1(N, rp, fp, btype='low', fs=fs, output='sos')
    w, h = signal.sosfreqz(sos, worN=4096, fs=fs)

    axes[0].plot(w, 20 * np.log10(np.abs(h) + 1e-12), label=f'N={N}')
    axes[1].plot(w, np.degrees(np.unwrap(np.angle(h))), label=f'N={N}')

axes[0].set_xlabel('Frequency [Hz]')
axes[0].set_ylabel('Magnitude [dB]')
axes[0].set_title('Chebyshev Type I - Magnitude Response (rp=1dB)')
axes[0].set_xlim(0, 500)
axes[0].set_ylim(-80, 5)
axes[0].axvline(fp, color='gray', linestyle='--', alpha=0.5, label=f'fp={fp}Hz')
axes[0].axhline(-rp, color='r', linestyle=':', alpha=0.5, label=f'-{rp}dB ripple')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

axes[1].set_xlabel('Frequency [Hz]')
axes[1].set_ylabel('Phase [degrees]')
axes[1].set_title('Chebyshev Type I - Phase Response')
axes[1].set_xlim(0, 500)
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Chebyshev Type II Filter

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal

fs = 1000
fs_stop = 150     # Stopband edge [Hz]
rs = 40.0         # Stopband attenuation [dB]
orders = [2, 4, 6]

fig, axes = plt.subplots(2, 1, figsize=(10, 8))

for N in orders:
    sos = signal.cheby2(N, rs, fs_stop, btype='low', fs=fs, output='sos')
    w, h = signal.sosfreqz(sos, worN=4096, fs=fs)

    axes[0].plot(w, 20 * np.log10(np.abs(h) + 1e-12), label=f'N={N}')
    axes[1].plot(w, np.degrees(np.unwrap(np.angle(h))), label=f'N={N}')

axes[0].set_xlabel('Frequency [Hz]')
axes[0].set_ylabel('Magnitude [dB]')
axes[0].set_title('Chebyshev Type II - Magnitude Response (rs=40dB)')
axes[0].set_xlim(0, 500)
axes[0].set_ylim(-80, 5)
axes[0].axvline(fs_stop, color='gray', linestyle='--', alpha=0.5, label=f'fs={fs_stop}Hz')
axes[0].axhline(-rs, color='r', linestyle=':', alpha=0.5, label=f'-{rs}dB')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

axes[1].set_xlabel('Frequency [Hz]')
axes[1].set_ylabel('Phase [degrees]')
axes[1].set_title('Chebyshev Type II - Phase Response')
axes[1].set_xlim(0, 500)
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Order Comparison with Butterworth

from scipy.signal import buttord, cheb1ord

fp = 100    # Passband edge [Hz]
fs_stop = 150  # Stopband edge [Hz]
rp = 1.0    # Passband ripple [dB]
rs = 40.0   # Stopband attenuation [dB]
fs = 1000   # Sampling frequency

N_butter, _ = buttord(fp, fs_stop, rp, rs, fs=fs)
N_cheby1, _ = cheb1ord(fp, fs_stop, rp, rs, fs=fs)

print(f"Butterworth    : order {N_butter}")
print(f"Chebyshev I    : order {N_cheby1}")

For the same specification, Chebyshev Type I typically achieves a lower order than Butterworth.

Noise Reduction Application

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal

fs = 1000
t = np.arange(0, 1, 1/fs)
clean = np.sin(2 * np.pi * 10 * t) + 0.5 * np.sin(2 * np.pi * 30 * t)
noisy = clean + 0.8 * np.random.randn(len(t))

# Butterworth (order 4)
sos_butter = signal.butter(4, 100, fs=fs, output='sos')
y_butter = signal.sosfiltfilt(sos_butter, noisy)

# Chebyshev Type I (order 4, 1 dB ripple)
sos_cheby1 = signal.cheby1(4, 1, 100, fs=fs, output='sos')
y_cheby1 = signal.sosfiltfilt(sos_cheby1, noisy)

# Chebyshev Type II (order 4, 40 dB attenuation)
sos_cheby2 = signal.cheby2(4, 40, 150, fs=fs, output='sos')
y_cheby2 = signal.sosfiltfilt(sos_cheby2, noisy)

fig, axes = plt.subplots(4, 1, figsize=(10, 12), sharex=True)
axes[0].plot(t, noisy, alpha=0.5, label='Noisy'); axes[0].plot(t, clean, 'k', label='Original')
axes[0].set_title('Input Signal'); axes[0].legend(); axes[0].grid(True, alpha=0.3)

axes[1].plot(t, y_butter, label='Butterworth N=4'); axes[1].plot(t, clean, 'k', lw=1.5)
axes[1].set_title('Butterworth (N=4)'); axes[1].legend(); axes[1].grid(True, alpha=0.3)

axes[2].plot(t, y_cheby1, label='Chebyshev I N=4, rp=1dB'); axes[2].plot(t, clean, 'k', lw=1.5)
axes[2].set_title('Chebyshev Type I (N=4, rp=1dB)'); axes[2].legend(); axes[2].grid(True, alpha=0.3)

axes[3].plot(t, y_cheby2, label='Chebyshev II N=4, rs=40dB'); axes[3].plot(t, clean, 'k', lw=1.5)
axes[3].set_title('Chebyshev Type II (N=4, rs=40dB)')
axes[3].set_xlabel('Time [s]'); axes[3].legend(); axes[3].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Comparison with Other IIR Filters

FilterPassbandStopbandTransitionPhase
ButterworthMaximally flatMonotoneWideRelatively smooth
Chebyshev Type IEquirippleMonotoneNarrowerNonlinear
Chebyshev Type IIMonotoneEquirippleNarrowerRelatively smooth
Elliptic (Cauer)EquirippleEquirippleNarrowestMost nonlinear

Use Type I when a sharp cutoff is needed and passband ripple is acceptable. Use Type II when passband flatness is required but improved stopband attenuation is desired.

Summary

  • Chebyshev filters exploit the equal-amplitude oscillation property of Chebyshev polynomials to produce equiripple IIR filters
  • Type I has equiripple in the passband; Type II has equiripple in the stopband
  • Both types achieve sharper transition bands than Butterworth at the same order
  • In SciPy, use signal.cheby1() / signal.cheby2() for design, and signal.cheb1ord() / signal.cheb2ord() to compute the required order automatically

References