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.
SciPy Beginner’s Learning Path
- What is SciPy?
- Python SciPy tutorial
- How to install SciPy (Windows, MacOS, Linux)
- SciPy subpackages and library structure
- SciPy constants
- SciPy special functions
- SciPy linear algebra module
- SciPy integrate
- SciPy minimize
- SciPy interpolate
- SciPy integrate quad
- SciPy integrate solve_ivp
- SciPy fft
- SciPy signal
- Applying Filters with scipy.signal
- SciPy signal find_peaks
- SciPy ndimage