Signal processing in Python often starts with the scipy.signal module. If you need to filter, analyze, or extract features from signals – like cleaning up sensor data, audio, or biomedical measurements – scipy.signal delivers powerful, efficient tools you can use right away. With this guide, you’ll see practical steps to get started, process real data, and avoid common pitfalls, all with tight, copy-paste-ready code.

What Does scipy.signal Offer?

scipy.signal handles everything from simple smoothing to advanced digital filter design, spectral analysis, convolution, peak detection, and feature extraction. You don’t have to be a DSP expert to use it. Typical workflows include:

  • Filtering noisy signals (low-pass, high-pass, band-pass)
  • Detecting events or peaks in time series
  • Analyzing the frequency spectrum (beyond FFT)
  • Designing your own custom digital filters
  • Smoothing and denoising data

It works with NumPy arrays and integrates smoothly with matplotlib for plotting.

Installing and Importing

Make sure you have scipy and matplotlib installed.

pip install scipy matplotlib

Import the main modules:

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

Generating a Noisy Example Signal

For hands-on learning, create a noisy sine wave:

fs = 500  # Sampling frequency (Hz)
t = np.arange(0, 1.0, 1.0/fs)  # 1 second of data
freq = 5  # Frequency of the signal (Hz)
x = np.sin(2 * np.pi * freq * t)  # Pure tone

# Add Gaussian noise
np.random.seed(0)
x_noisy = x + 0.5 * np.random.randn(len(t))

plt.plot(t, x_noisy)
plt.title("Noisy Signal")
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.show()

Filtering Signals: Butterworth Low-Pass Example

You can easily filter out high-frequency noise using a Butterworth low-pass filter. Here’s a practical workflow:

Design the filter

cutoff = 10  # Desired cutoff frequency (Hz)
order = 4    # Filter order (steepness)
b, a = signal.butter(order, cutoff, fs=fs, btype="low")

Apply the filter (zero-phase for no time shift)

x_filtered = signal.filtfilt(b, a, x_noisy)

Plot to compare before and after

plt.plot(t, x_noisy, label="Noisy")
plt.plot(t, x_filtered, label="Filtered", linewidth=2)
plt.legend()
plt.title("Signal Before and After Low-Pass Filtering")
plt.xlabel("Time [s]")
plt.show()

Tip: Use filtfilt for zero-phase filtering. If you use lfilter instead, the output will be shifted in time.

Visualizing Frequency Content (Power Spectrum)

You don’t have to stick with FFT. For better frequency analysis, use Welch’s method:

f, Pxx = signal.welch(x_noisy, fs, nperseg=256)
plt.semilogy(f, Pxx)
plt.title("Power Spectral Density (Welch's Method)")
plt.xlabel("Frequency [Hz]")
plt.ylabel("PSD")
plt.show()

Welch’s method averages over segments, giving you a smoother, more robust estimate than a plain FFT, especially when your data is noisy or short.


Peak Detection in Signals

To find peaks (local maxima), such as heartbeats in ECG or clicks in audio:

peaks, _ = signal.find_peaks(x_filtered, height=0.5)
plt.plot(t, x_filtered)
plt.plot(t[peaks], x_filtered[peaks], "x")
plt.title("Detected Peaks")
plt.show()

You can fine-tune detection with parameters like height, distance, and prominence to match your data’s characteristics.

Creating and Applying Custom Filters

Suppose you want a band-pass filter. Design it and apply just like before:

lowcut = 2
highcut = 15
b, a = signal.butter(order, [lowcut, highcut], fs=fs, btype="band")
x_band = signal.filtfilt(b, a, x_noisy)

For more control, use different filter types (cheby1, ellip, etc.), or design with firwin for FIR filters:

numtaps = 101  # Filter length
fir_coeff = signal.firwin(numtaps, [lowcut, highcut], fs=fs, pass_zero='bandpass')
x_fir = signal.filtfilt(fir_coeff, 1.0, x_noisy)

Denoising and Smoothing

Quick moving average:

window = np.ones(10)/10
x_smooth = np.convolve(x_noisy, window, mode="same")

Or use built-in Savitzky-Golay filter for preserving shape:

x_savgol = signal.savgol_filter(x_noisy, window_length=51, polyorder=3)

Common Pitfalls and Best Practices

  • Always check filter stability and output. High-order filters or poor cutoff frequencies can cause ringing or instability.
  • When using filtfilt, your signal needs to be longer than three times the filter’s length. Otherwise, you’ll get edge effects or errors.
  • Choose the correct filter type for your data and application. FIR filters are always stable but may require longer filter lengths.

Integrating with Real Data

Reading and filtering a WAV audio file:

from scipy.io import wavfile

fs, data = wavfile.read('input.wav')
b, a = signal.butter(6, 1000, fs=fs, btype="low")
filtered = signal.filtfilt(b, a, data)
wavfile.write('output.wav', fs, filtered.astype(data.dtype))

Going Further

scipy.signal is packed with more: convolution and correlation, resampling, window functions, continuous and discrete system simulation, and more. Explore the official scipy.signal documentation for additional functions and deep dives.

Key takeaway: You can handle 90% of signal processing needs for data science, audio, and science projects directly in Python with scipy.signal. Start by filtering, peak detection, and spectrum analysis. Experiment and plot everything—you’ll spot issues and insights fast.

Share.
Leave A Reply