Description
Describe the issue:
Problem
fft.fftfreq(n, ts)
where
n
is the number of samplests
is the sampling time (step)
generates a vector of n
frequencies in the range [-fs/2, fs/2)
, where fs=1/ts
is the sampling frequency. The generated frequencies should be in the range (-fs/2, fs/2]
, i.e., if one of the frequencies is fs/2
(which happens when n
is even), it should have a positive sign.
Fix
The following code gives the original source code (with cosmetic changes), the fixed code, and an example where the two functions give different result. The fix is in the calculation of m
(first line in each function), which differs when n
is even.
def fftfreq(n, ts):
m = (n - 1) // 2 + 1
f = np.empty(n, dtype=int)
# note: m - n == -(n//2)
f[:m] = np.arange(0, m, dtype=int)
f[m:] = np.arange(m - n, 0, dtype=int)
return f / (n * ts)
def fftfreq_fix(n, ts):
m = n // 2 + 1
f = np.empty(n, dtype=int)
f[:m] = np.arange(0, m, dtype=int)
f[m:] = np.arange(m - n, 0, dtype=int)
return f / (n * ts)
def test(n):
# normalize such that frequencies are integer numbers
ts = 1/n # sampling time (step)
fs = 1/ts # sampling frequency
print('frequencies in [-fs/2, fs/2) :', fftfreq(n, ts))
print('frequencies in (-fs/2, fs/2] :', fftfreq_fix(n, ts))
test(6)
frequencies in [-fs/2, fs/2) : [ 0. 1. 2. -3. -2. -1.]
frequencies in (-fs/2, fs/2] : [ 0. 1. 2. 3. -2. -1.]
Notice that the frequency fs/2 = 3
is wrapped to -3
, but it shouldn't.
Theory
For an even n
, a sequence of n
real numbers is transformed by FFT to a sequence of n
complex numbers, of which the last n/2 - 1
numbers are redundant because they are complex conjugates of corresponding number in the first part. The remaining n/2 + 1
numbers are all necessary to reconstruct the original real signal. They represent n + 2
real numbers, but the imaginary part of the first (index 0) and the last (index n/2
) are always 0, so they actually represent n
independent real numbers.
For an odd n
, the last (n-1)/2
transformed numbers are redundant, and of the remaining (n+1)/2
only the first always has zero imaginary part, so in total they represent n
independent real numbers.
Impact
For an even n
(which is the most common use case), when the FFT of a signal is plotted using fft.fftfreq()
, the maximum frequency fs/2
is hidden. For a realistic sampling process, the sampled signal (e.g., audio) doesn't have frequency content at fs/2
, since it has been filtered before sampling. However fft.fftfreq()
should not make any assumption about the conditioning of the original signal.
Example: plot the FFT as in the documentation examples, with an even number of samples, and a signal which contains the following tone
cos(2*np.pi*(fs/2)*t) = cos(np.pi*t/ts) = [+1, -1, +1, ..., -1]
which is an alternating signal (after sampling). The plot should show a tone at fs/2
, but this tone is removed by fft.fftfreq()
. Notice that replacing cos
by sin
in the above example does not show the problem, because the signal is 0.
Reproduce the code example:
See description.
Error message:
Python and NumPy Versions:
numpy 2.3.0
python 3.13.3
Runtime Environment:
No response
Context for the issue:
No response