HOWTO · Python
How to Implement a Low-Pass Filter in Python
Learn how to filter sampled data in Python with a SciPy Butterworth filter, a moving average, or Gaussian smoothing, and understand cutoff frequency and sampling limits.
On this page
A low-pass filter passes lower-frequency components and attenuates higher-frequency components; it does not necessarily remove every component above its cutoff. For sampled data, choose the cutoff in the same frequency units as the sampling rate. This guide shows three ways to filter or smooth a one-dimensional signal with NumPy and SciPy.
The examples below are self-contained, but they are not executed here, and no results are shown for them. Install NumPy, SciPy, and Matplotlib in your Python environment before running them.
Use a SciPy Butterworth filter for a defined cutoff
A Butterworth design has a maximally flat passband. Set the cutoff below the Nyquist frequency, which is half the sampling rate. In the example, fs is 500 samples per second and the cutoff is 100 Hz. Passing fs to scipy.signal.butter lets you express the cutoff in those same units. The order controls the steepness of the transition: a higher order gives a steeper roll-off, with more sensitivity to transients and numerical behavior.
This example uses second-order sections and sosfiltfilt to filter forward and backward. That avoids phase delay for offline analysis, but requires data from both directions and can affect the ends of a finite signal. For streaming or real-time data, use a causal filter such as sosfilt and account for its phase delay and startup transient.
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import butter, sosfiltfilt
fs = 500.0
cutoff = 100.0
order = 4
time = np.arange(500) / fs
signal = np.sin(2 * np.pi * 10 * time) + 0.5 * np.sin(2 * np.pi * 180 * time)
sections = butter(order, cutoff, btype="lowpass", fs=fs, output="sos")
filtered = sosfiltfilt(sections, signal)
plt.plot(time, signal, label="input")
plt.plot(time, filtered, label="filtered")
plt.xlabel("Time (seconds)")
plt.ylabel("Amplitude")
plt.legend()
plt.tight_layout()
plt.show()
Adjust cutoff and order for the signal and the frequencies you want to retain. sosfiltfilt is intended for offline data; its result is not interchangeable with a causal real-time filter.
Apply a moving-average low-pass filter
A moving average replaces each point with the mean of a fixed window. It is a simple smoothing method, not a Butterworth design with a specified cutoff response. With NumPy’s valid convolution mode, the result contains only complete windows, so it is shorter than the input by window_size - 1 samples. Align its time values accordingly when plotting.
import matplotlib.pyplot as plt
import numpy as np
fs = 100.0
window_size = 5
time = np.arange(100) / fs
signal = np.sin(2 * np.pi * 3 * time) + 0.2 * np.sin(2 * np.pi * 25 * time)
weights = np.ones(window_size) / window_size
smoothed = np.convolve(signal, weights, mode="valid")
smoothed_time = time[window_size - 1:]
plt.plot(time, signal, label="input")
plt.plot(smoothed_time, smoothed, label="moving average")
plt.xlabel("Time (seconds)")
plt.ylabel("Amplitude")
plt.legend()
plt.tight_layout()
plt.show()
Choose a positive window size no larger than the signal length. Increasing the window averages more samples and smooths more strongly, but also broadens changes and shifts the alignment of the windowed result. For centered alignment, account for the window’s delay when interpreting time values.
Use a Gaussian filter to smooth sampled data
A one-dimensional Gaussian filter weights nearby samples with a Gaussian-shaped kernel. In scipy.ndimage.gaussian_filter1d, sigma is measured in samples, not seconds or hertz. Convert a desired time scale to samples using the sampling rate when needed. Gaussian smoothing is useful for general smoothness, but it does not create a sharp, specified cutoff frequency.
import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import gaussian_filter1d
fs = 100.0
sigma_seconds = 0.04
time = np.arange(100) / fs
signal = np.sin(2 * np.pi * 3 * time) + 0.2 * np.sin(2 * np.pi * 25 * time)
smoothed = gaussian_filter1d(signal, sigma=sigma_seconds * fs)
plt.plot(time, signal, label="input")
plt.plot(time, smoothed, label="Gaussian smoothed")
plt.xlabel("Time (seconds)")
plt.ylabel("Amplitude")
plt.legend()
plt.tight_layout()
plt.show()
The example converts a smoothing scale of 0.04 seconds to four samples. The default behavior handles the ends by reflecting the signal; choose a different mode if your data has a different endpoint model, and interpret the ends with care.
Choose a filter and interpret its limits
Use a Butterworth design when you need a defined frequency response and know the sampling rate. Use a moving average for a straightforward window-based smoother, and Gaussian smoothing when a smooth local weighting is appropriate. These methods have different responses, delays, and endpoint behavior, so compare the result with the original data and verify that features important to your task remain visible.
For sampled data, the Nyquist frequency is half the sampling rate. A low-pass cutoff must be below Nyquist, and the sampling rate must be high enough to represent the frequencies of interest. No filter can recover information that was not captured during sampling. Offline forward-backward filtering can reduce phase shift, while causal filtering is suitable for streaming but introduces delay and startup behavior. Finite records also make filter results near their ends more sensitive to padding and initial conditions.
FAQ
What does a low-pass filter do?
It passes lower-frequency components and attenuates higher-frequency components according to its response. A cutoff marks a transition in that response; it does not mean all higher frequencies disappear completely.
When should I use a low-pass filter?
Use one when higher-frequency variation is noise for your task and the sampling rate and cutoff are known. Check that smoothing does not remove meaningful rapid changes in the signal.
How does a Butterworth filter differ from a moving average?
A Butterworth filter is designed from a cutoff frequency, sampling rate, and order. A moving average uses a fixed number of samples per window and has a different frequency response; its window length is not a direct substitute for a Butterworth cutoff.
Can I use these filters for real-time data?
A causal filter can process samples as they arrive, but its phase delay and startup transient matter. The forward-backward method in the Butterworth example needs the full record and is intended for offline processing. A moving average can be updated as samples arrive if you manage its window and alignment.
Which Python libraries can I use?
NumPy provides arrays and convolution, while SciPy provides Butterworth design and Gaussian smoothing functions. Matplotlib can plot input and filtered signals for inspection.