NumPy arrays don’t have butter; call scipy.signal.butter and then filter your data with lfilter or filtfilt.
If you’re seeing attributeerror: ‘numpy ndarray’ object has no attribute ‘butter’, the call is pointed at the array instead of the signal-processing function in SciPy. The fix is to import butter from scipy.signal, generate filter coefficients, and then apply them to your NumPy data. The sections below show clean, copy-ready patterns and quick checks to confirm the filter behaves as expected.
AttributeError: ‘NumPy NdArray’ Object Has No Attribute ‘Butter’ — What It Means
NumPy’s ndarray type exposes array math, indexing, and linear algebra. It doesn’t ship DSP filter-design helpers. Those live in SciPy’s signal toolbox. So, when code uses my_array.butter(...) or np.butter(...), Python looks for a butter attribute on an array or inside NumPy, can’t find it, and raises this AttributeError.
Quick check: if your import block lacks from scipy.signal import butter, filtfilt, lfilter, you’re calling the wrong object. The correct function is scipy.signal.butter, documented as “Butterworth digital and analog filter design.”
Correct Way To Design A Butterworth Filter In Python
The standard flow: design a filter with butter, then apply it with lfilter (causal) or filtfilt (zero-phase). Wn is the normalized cutoff (0–1 if you pass fs) or a pair for band filters. The API below matches the SciPy reference.
<!-- Low-pass with zero-phase response -->
from numpy import linspace, sin, pi
from scipy.signal import butter, filtfilt
fs = 500.0 # Hz
t = linspace(0, 2, int(2*fs), endpoint=False)
x = sin(2*pi*3*t) + 0.5*sin(2*pi*50*t) # 3 Hz signal + 50 Hz noise
# Design: 4th-order low-pass at 10 Hz
b, a = butter(N=4, Wn=10, btype="low", fs=fs)
y = filtfilt(b, a, x) # zero-phase filtering
Why this fixes the error: you never ask the array to “have” butter. You import the function from scipy.signal, get coefficients, and feed them plus data into a filter routine. That aligns with the SciPy manual.
Fix “No Attribute butter” On ndarray — Causes And Solutions
This error pops up from a few repeat patterns. Match your case to the rows below and apply the single-line remedy.
| Symptom | Root Cause | Fix |
|---|---|---|
np.butter(...) or array.butter(...) |
Calling butter from NumPy or an ndarray |
Import from SciPy: from scipy.signal import butter |
TypeError about cutoff scaling |
Wn not normalized / wrong units |
Pass fs= and use Hz for Wn, or pass normalized [0..1] |
| Filter “works” but wave looks shifted | Phase lag from lfilter |
Use filtfilt for zero-phase response |
The call signature and parameter meaning come straight from the SciPy docs.
Practical Recipes: Low-Pass, High-Pass, And Bandpass
These snippets follow the SciPy reference and the community cookbook. They show the correct butter call plus a matching application step.
Low-Pass: Keep Slow Trends
from scipy.signal import butter, filtfilt
def lowpass(data, cutoff_hz, fs, order=4):
b, a = butter(order, cutoff_hz, btype="low", fs=fs)
return filtfilt(b, a, data)
High-Pass: Remove Drift
from scipy.signal import butter, filtfilt
def highpass(data, cutoff_hz, fs, order=4):
b, a = butter(order, cutoff_hz, btype="high", fs=fs)
return filtfilt(b, a, data)
Bandpass: Keep A Window
from scipy.signal import butter, lfilter
def bandpass(data, low_hz, high_hz, fs, order=4, zero_phase=True):
b, a = butter(order, [low_hz, high_hz], btype="band", fs=fs)
return filtfilt(b, a, data) if zero_phase else lfilter(b, a, data)
The cookbook shows a similar bandpass setup and recommends lfilter (causal) or filtfilt (zero-phase).
Can I Fix AttributeError: ‘NumPy NdArray’ Object Has No Attribute ‘Butter’ Without SciPy?
You can smooth or filter with plain NumPy, but that’s not a Butterworth design. A moving average via convolution is one option; it shapes frequencies differently and won’t match a Butterworth response. Use SciPy when you need an actual Butterworth filter. The separation of roles—NumPy for arrays, SciPy for higher-level algorithms—is a core Python-scientific split.
Smoothing With A Moving Window (Non-Butterworth)
import numpy as np
def moving_average(x, k):
k = int(k)
if k < 1:
raise ValueError("window must be >= 1")
w = np.ones(k) / k
return np.convolve(x, w, mode="same")
When you need a real Butterworth: reach for scipy.signal.butter. The API supports low, high, band, and bandstop, with output in transfer-function (b,a) or SOS form.
Parameter Cheatsheet That Prevents New Errors
- Pick Order — Start with 2–4. Higher order sharpens the roll-off but can ring on edges. The SciPy docs accept
Nas an int for order. - Set Cutoffs — With
fs=provided, pass cutoffs in Hz. For bands, pass a two-item list[low_hz, high_hz]. - Choose Response — Use
filtfiltfor shape-faithful output (no phase lag). Uselfilterif you need causal streaming. The cookbook pattern shows both. - Validate With A Plot —
scipy.signal.freqzgives the Bode-style response so you can eyeball passband/stopband.
Frequency Response Check
import numpy as np
from scipy.signal import butter, freqz
fs = 250
b, a = butter(4, 30, btype="low", fs=fs)
w, h = freqz(b, a, worN=1024, fs=fs)
# w: frequency axis in Hz, h: complex response
# Plot with matplotlib if desired:
# import matplotlib.pyplot as plt
# plt.plot(w, 20*np.log10(np.maximum(np.abs(h), 1e-8)))
# plt.xlabel("Hz"); plt.ylabel("dB")
Debugging Checklist When The Error Still Appears
- Verify Imports — Use
from scipy.signal import butter, lfilter, filtfilt; don’t callnp.butter. - Check Shadows — Files named
signal.pyor variables namedbuttercan shadow the real function; rename them. - Confirm Types — Print
type(x); it should benumpy.ndarray. If you’re mixing with other libs (TensorFlow, PyTorch), avoid calling their tensor methods on NumPy arrays, which also yields attribute errors. - Pin SciPy Version — Keep SciPy and NumPy in sync inside your environment to avoid install glitches. The
butterAPI has been stable for years, and docs from older and newer versions show the same core signature.
End-To-End Pattern: From Raw Samples To Clean Signal
This block demonstrates the full pipeline with guards that dodge common mis-steps. It uses a bandpass as a realistic baseline, following the same interface described in the SciPy manual and cookbook.
import numpy as np
from scipy.signal import butter, filtfilt
def design_bandpass(low_hz, high_hz, fs, order=4):
if not (0 < low_hz < high_hz < fs/2):
raise ValueError("cutoffs must be within (0, fs/2) and low < high")
b, a = butter(order, [low_hz, high_hz], btype="band", fs=fs)
return b, a
def apply_zero_phase(b, a, x):
x = np.asarray(x, dtype=float)
return filtfilt(b, a, x)
# demo signal: 5 Hz useful band + 60 Hz interference
fs = 360.0
t = np.arange(0, 5, 1/fs)
x = np.sin(2*np.pi*5*t) + 0.4*np.sin(2*np.pi*60*t)
b, a = design_bandpass(3, 30, fs, order=4) # keep 3–30 Hz
y = apply_zero_phase(b, a, x)
Why This Error Mixes Up NumPy And SciPy In The First Place
In the Python stack, NumPy handles array basics; SciPy adds domain algorithms on top. Calling np.butter or x.butter blurs that line. Later, similar mistakes show up with other missing attributes too, like trying to call list-style methods on arrays. The cure is the same: call the right library for the job.
Mini Playbook For Production Code
- Isolate DSP Imports — Collect
from scipy.signal import ...in one module so calls stay explicit. - Validate Cutoffs — Add guards that compare cutoffs to the Nyquist rate (
fs/2). - Pick SOS For High Orders — For orders > 8, use
output="sos"andsosfiltfiltfor numerical stability, as suggested by SciPy’s API options. - Keep A Response Test — Ship a small unit test that checks passband gain near 0 dB and stopband attenuation at a few probe points using
freqz.
Use these patterns, and attributeerror: ‘numpy ndarray’ object has no attribute ‘butter’ disappears for good while your filters stay reproducible and easy to read. The fixes match the SciPy reference for scipy.signal.butter and the community cookbook’s filter usage.
