Python SciPy Butterworth Filter

Recently, I was working on a signal processing project where I needed to remove noise from sensor data collected from a traffic monitoring system in downtown Chicago. The challenge was filtering out random fluctuations while preserving the important traffic pattern information. This is where the Butterworth filter in SciPy came to my rescue.

In this article, I’ll walk you through everything you need to know about implementing Butterworth filters using Python’s SciPy library. Whether you’re cleaning noisy signals, processing images, or analyzing time-series data, this powerful filter can be a game-changer.

So let’s start..!

What is a Butterworth Filter?

The Butterworth filter is a type of signal processing filter designed to have a frequency response as flat as possible in the passband. It’s often called a maximally flat magnitude filter.

Unlike other filters that might create ripples in the passband, the Butterworth filter maintains a smooth response, making it ideal for many real-world applications.

When I first started using it for cleaning economic time series data, I was impressed by how well it preserved the underlying trends while removing high-frequency noise.

Read Python SciPy IIR Filter

Method 1: Basic Butterworth Filter Implementation

Let’s start with a simple example of applying a low-pass Butterworth filter to a noisy signal:

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

# Create a noisy signal
t = np.linspace(0, 1, 1000, False)  # 1 second
sig = np.sin(2*np.pi*10*t) + np.sin(2*np.pi*20*t) + 0.5*np.random.randn(len(t))

# Design the Butterworth filter
order = 4  # Filter order
cutoff = 15  # Cutoff frequency in Hz
b, a = signal.butter(order, cutoff, 'low', fs=1000)

# Apply the filter
filtered_sig = signal.filtfilt(b, a, sig)

# Plot the results
plt.figure(figsize=(10, 6))
plt.plot(t, sig, 'b-', label='Noisy signal')
plt.plot(t, filtered_sig, 'r-', label='Filtered signal')
plt.legend()
plt.xlabel('Time [s]')
plt.ylabel('Amplitude')
plt.title('Butterworth Low-Pass Filter')
plt.grid()
plt.show()

You can see the output in the screenshot below.

butterworth filter python

In this example, I’ve created a signal with two sine waves (10 Hz and 20 Hz) and added random noise. The Butterworth filter effectively removes the 20 Hz component and the noise, leaving a clean 10 Hz sine wave.

The signal.butter() function creates the filter coefficients, and signal.filtfilt() applies the filter in forward and backward directions, resulting in zero-phase filtering.

Method 2: High-Pass Butterworth Filter

Sometimes you need to remove low-frequency components while keeping high-frequency ones. Here’s how to implement a high-pass filter:

# Design a high-pass Butterworth filter
order = 4
cutoff = 15  # Cutoff frequency in Hz
b, a = signal.butter(order, cutoff, 'high', fs=1000)

# Apply the filter
filtered_sig = signal.filtfilt(b, a, sig)

# Plot the results
plt.figure(figsize=(10, 6))
plt.plot(t, sig, 'b-', label='Noisy signal')
plt.plot(t, filtered_sig, 'r-', label='Filtered signal')
plt.legend()
plt.title('Butterworth High-Pass Filter')
plt.grid()
plt.show()

You can see the output in the screenshot below.

scipy butter

This will remove frequency components below 15 Hz and keep those above it. When I used this on vibration data from a Chicago-based manufacturing facility, it helped isolate high-frequency machinery faults from the regular operating vibrations.

Check out Python SciPy Sparse

Method 3: Band-Pass Butterworth Filter

A band-pass filter allows frequencies within a certain range to pass through and attenuates frequencies outside that range:

# Design a band-pass Butterworth filter
order = 4
low_cutoff = 8  # Lower cutoff frequency
high_cutoff = 12  # Upper cutoff frequency
b, a = signal.butter(order, [low_cutoff, high_cutoff], 'bandpass', fs=1000)

# Apply the filter
filtered_sig = signal.filtfilt(b, a, sig)

# Plot the results
plt.figure(figsize=(10, 6))
plt.plot(t, sig, 'b-', label='Noisy signal')
plt.plot(t, filtered_sig, 'r-', label='Filtered signal')
plt.legend()
plt.title('Butterworth Band-Pass Filter')
plt.grid()
plt.show()

You can see the output in the screenshot below.

scipy signal butter

In this example, I’m isolating frequencies between 8 Hz and 12 Hz. This is particularly useful when targeting specific frequency ranges, such as when I analyzed Chicago River water level data to identify specific tidal patterns.

Read How to use Python SciPy

Method 4: Band-Stop (Notch) Butterworth Filter

A band-stop filter (also called a notch filter) rejects frequencies within a specific range:

# Design a band-stop Butterworth filter
order = 4
low_cutoff = 18  # Lower cutoff frequency
high_cutoff = 22  # Upper cutoff frequency
b, a = signal.butter(order, [low_cutoff, high_cutoff], 'bandstop', fs=1000)

# Apply the filter
filtered_sig = signal.filtfilt(b, a, sig)

# Plot the results
plt.figure(figsize=(10, 6))
plt.plot(t, sig, 'b-', label='Noisy signal')
plt.plot(t, filtered_sig, 'r-', label='Filtered signal')
plt.legend()
plt.title('Butterworth Band-Stop Filter')
plt.grid()
plt.show()

This is excellent for removing specific interference, like the 60 Hz power line noise I often encounter when working with sensor data from Chicago’s smart city infrastructure.

Check out Working with Python, Lil_Matrix SciPy

Method 5: Butterworth Filter for Image Processing

The Butterworth filter isn’t limited to 1D signals. It can be extended to 2D for image processing:

import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm

# Create a sample image with noise
size = 128
image = np.zeros((size, size))
image[32:96, 32:96] = 1  # Square in the middle
noise = np.random.normal(0, 0.1, (size, size))
noisy_image = image + noise

# Compute FFT of the image
fft_image = np.fft.fft2(noisy_image)
fft_shift = np.fft.fftshift(fft_image)

# Create Butterworth low-pass filter mask
rows, cols = noisy_image.shape
crow, ccol = rows//2, cols//2
D0 = 20  # Cutoff frequency
n = 2    # Order of the filter
u, v = np.meshgrid(np.arange(rows) - crow, np.arange(cols) - ccol, sparse=True)
D = np.sqrt(u**2 + v**2)
butterworth_mask = 1 / (1 + (D / D0)**(2*n))

# Apply filter to the FFT
fft_filtered = fft_shift * butterworth_mask

# Inverse FFT to get the filtered image
filtered_image = np.abs(np.fft.ifft2(np.fft.ifftshift(fft_filtered)))

# Display results
plt.figure(figsize=(12, 4))
plt.subplot(131)
plt.imshow(noisy_image, cmap='gray')
plt.title('Noisy Image')

plt.subplot(132)
plt.imshow(np.log1p(np.abs(fft_shift)), cmap='viridis')
plt.title('FFT (log scale)')

plt.subplot(133)
plt.imshow(filtered_image, cmap='gray')
plt.title('Filtered Image')

plt.tight_layout()
plt.show()

I used this technique when processing satellite imagery of Chicago’s urban heat islands, which helped remove high-frequency noise while preserving important structural details.

Understand Filter Order and Cutoff Frequency

Two key parameters that affect Butterworth filter performance are:

  1. Order: Higher orders create sharper transitions between the passband and stopband but can introduce more ringing artifacts.
  2. Cutoff Frequency: This determines where the filter starts attenuating the signal.

Let’s visualize how these parameters affect the filter’s frequency response:

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

# Create frequency response plots for different orders
fs = 1000  # Sampling frequency
cutoff = 100  # Cutoff frequency

plt.figure(figsize=(10, 6))
for order in [1, 2, 4, 8]:
    b, a = signal.butter(order, cutoff, 'low', fs=fs)
    w, h = signal.freqz(b, a, worN=2000)
    plt.plot((fs * 0.5 / np.pi) * w, abs(h), label=f"Order = {order}")

plt.xlabel('Frequency [Hz]')
plt.ylabel('Gain')
plt.title('Butterworth Filter Frequency Response for Different Orders')
plt.grid()
plt.legend()
plt.axvline(cutoff, color='k', linestyle='--', alpha=0.3, label='Cutoff Frequency')
plt.show()

When working with Chicago traffic sensor data, I found that a 4th-order filter provided the best balance between effective noise removal and minimal signal distortion.

Real-World Applications

Butterworth filters have numerous practical applications:

  1. Biomedical Signal Processing: When I worked with ECG data from a Chicago hospital, Butterworth filters helped clean the signals for more accurate heart rate analysis.
  2. Audio Processing: They’re excellent for creating crossover filters in audio systems.
  3. Financial Time Series: I’ve used them to smooth stock price data while preserving important trends.
  4. Vibration Analysis: In predictive maintenance applications, they help isolate specific frequency bands of interest.
  5. Image Enhancement: As demonstrated earlier, they can remove noise from images while preserving important features.

Read Python SciPy Smoothing

Important Considerations When Using Butterworth Filters

Throughout my decade of experience with signal processing, I’ve learned a few important lessons about Butterworth filters:

  1. Edge Effects: The filtfilt() function helps minimize edge effects by applying the filter in both forward and backward directions.
  2. Filter Order: Start with lower orders (2-4) and increase if needed. Very high orders can cause numerical instability.
  3. Normalization: Always normalize your cutoff frequency to the Nyquist frequency or specify your sampling rate explicitly.
  4. Phase Distortion: Use filtfilt() when phase preservation is important (it performs zero-phase filtering).

Python’s SciPy library makes implementing Butterworth filters straightforward, but understanding these nuances is key to effective application.

I hope you found this guide helpful for your signal processing tasks. Whether you’re cleaning environmental sensor data from Chicago’s lakefront or processing financial time series, the Butterworth filter is a versatile tool in your Python toolkit.

You may like to read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.