-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
[Bug]: Possible problem with parameter NFFT
and pad_to
of mlab._spectral_helper
#24822
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This implementation is pretty clunky, and we strongly recommend just using scipy. Indeed, I was hoping to deprecate the whole thing, but there wasn't sufficient support (#22920) You are not quite using the parameters correctly, but I agree the results are still incorrect: import numpy as np
from matplotlib import mlab
import matplotlib.pyplot as plt
fs = 1000
t = np.arange(0, 1, 1/fs)
rng = np.random.default_rng(42)
s = np.sin(2 * np.pi * 100 * t) + 0.5 * rng.normal(size=t.shape)
nfft = 2048
nsignal = len(s) #1000
psd, freqs = mlab.psd(s, pad_to=nfft, NFFT=nfft, Fs=fs, window=mlab.window_none,
scale_by_freq=True)
psd2, freqs2 = mlab.psd(s, pad_to=nsignal, NFFT=nsignal, Fs=fs, window=mlab.window_none,
scale_by_freq=True)
fig, ax = plt.subplots()
df = np.median(np.diff(freqs))
ax.loglog(freqs, psd, label=f'NFFT = 2048: {np.sum(psd[1:] * df)}')
df2 = np.median(np.diff(freqs2))
ax.loglog(freqs2, psd2, label=f'NFFT=len(data): {np.sum(psd2[1:] * df2)}')
ax.legend()
ax.set_title(f'Signal variance: {np.var(s)}')
plt.show() yields: So, the padded normalization is incorrect by a factor of 1000/2048. |
If matplotlib/lib/matplotlib/mlab.py Lines 342 to 351 in 3418bad
And the problem should be fixed with: # Check if x and y are the same length, zero-pad if necessary
if not same_data:
if len(x) != len(y):
if len(x) < len(y):
n = len(x)
x = np.resize(x, len(y))
x[n:] = 0
else:
n = len(y)
y = np.resize(y, len(x))
y[n:] = 0
if len(x) < NFFT:
NFFT = len(x) Also the following code need be fixed for correct one-sided psd: matplotlib/lib/matplotlib/mlab.py Lines 411 to 416 in 3418bad
# if we have a even number of frequencies, don't scale pad_to/2
if not pad_to % 2:
slc = slice(1, -1, None)
# if we have an odd number, just don't scale DC
else:
slc = slice(1, None, None) |
@gapplef Maybe? |
cc @oscargus |
Also the zero padding if NFFT is less then the signal length should be performed after the detrend. Otherwise the zero padding would screw up the "mean" and "linear" calculations. |
Bug summary
If my understanding is correct:
NFFT
ofmlab._spectral_helper
corresponds tonperseg
of spectral funtions ofscipy.signal
.It's the actual length of signal (segment) for DFT.
pad_to
ofmlab._spectral_helper
corresponds tonfft
of spectral funtions ofscipy.signal
.It's the length of DFT, i.e. parameter
n
offft
funtion.matplotlib/lib/matplotlib/mlab.py
Line 385 in 3418bad
But the implementation of Matplotlib is really confusion:
If length of signal is less than NFFT, signal will pad to length of NFFT.
matplotlib/lib/matplotlib/mlab.py
Lines 342 to 351 in 3418bad
And the window is determined by NFFT instead of actual length of signal. This would cause wrong window correction.
matplotlib/lib/matplotlib/mlab.py
Lines 376 to 380 in 3418bad
The Nyquist frequency is
NFFT/2
instead ofpad_to/2
.matplotlib/lib/matplotlib/mlab.py
Lines 411 to 416 in 3418bad
Code for reproduction
Actual outcome
0.6876640419947717
Expected outcome
0
The text was updated successfully, but these errors were encountered: