简体   繁体   中英

Calculate non-integer frequency with NumPy FFT

I would like to calculate the frequency of a periodic time series using NumPy FFT. As an example, let's say my time series y is defined as follows:

import numpy as np
freq = 12.3
x = np.arange(10000)
y = np.cos(x * 2 * np.pi * freq / 10000)

If the frequency is an integer, I can calculate it using np.argmax(np.abs(np.fft.fft(y))) . However, in case the frequency is not an integer, how do I calculate the frequency with more precision?

EDIT: To clarify, we are not supposed to know how the time series y is generated. The above code snippet is just an artificial example of how a non-integer frequency could come up. Obviously if we already know the function that generates the time series, we don't need FFT to determine the frequency.

You need to give your signal more resolution

import numpy as np
freq = 12.3
x = np.arange(100000)  # 10 times more resolution
y = np.cos(x * 2 * np.pi * freq / 10000)  # don't change this

print(np.argmax(np.abs(np.fft.fft(y))) / 10)  # divide by 10
# 12.3

The number of data points in x need to be 10 times more than the number you divide y with. You could get the same effect like this:

x = np.arange(10000)
y = np.cos(x * 2 * np.pi * freq / 1000)

print(np.argmax(np.abs(np.fft.fft(y))) / 10) 
# 12.3

If you want to find the frequency with two decimals the resolution needs to be 100 times more.

freq = 12.34
x = np.arange(10000)  
y = np.cos(x * 2 * np.pi * freq / 100)  # 100 times more resolution

print(np.argmax(np.abs(np.fft.fft(y))) / 100)  # divide by 100
# 12.34

You can pad the data with zeros before computing the FFT.

For example, here's your original calculation. It finds the Fourier coefficient with the maximum magnitude at frequency 12.0:

In [84]: freq = 12.3

In [85]: x = np.arange(10000)

In [86]: y = np.cos(x * 2 * np.pi * freq / 10000)

In [87]: f = np.fft.fft(y)

In [88]: k = np.argmax(np.abs(f))

In [89]: np.fft.fftfreq(len(f), d=1/10000)[k]
Out[89]: 12.0

Now recompute the Fourier transform, but pad the input to have a length of six times the original length (you can adjust that factor as needed). With the padded signal the Fourier coefficient with maximum magnitude is associated with frequency 12.333:

In [90]: f = np.fft.fft(y, 6*len(y))

In [91]: k = np.argmax(np.abs(f))

In [92]: np.fft.fftfreq(len(f), d=1/10000)[k]
Out[92]: 12.333333333333332

Here's a plot that illustrates the effect of padding the signal. The signal is not the same as above; I used different values with a much shorter signal to make it easier to see the effect. The shapes of the lobes are not changed, but the number of points at which the frequency is sampled is increased.

情节

The plot is generated by the following script:

import numpy as np
import matplotlib.pyplot as plt

fs = 10
T = 1.4
t = np.arange(T*fs)/fs

freq = 2.6
y = np.cos(2*np.pi*freq*t)

fy = np.fft.fft(y)
magfy = np.abs(fy)
freqs = np.fft.fftfreq(len(fy), d=1/fs)
plt.plot(freqs, magfy, 'd', label='no padding')

for (factor, markersize) in [(2, 9), (16, 4)]:
    fy_padded = np.fft.fft(y, factor*len(y))
    magfy_padded = np.abs(fy_padded)
    freqs_padded = np.fft.fftfreq(len(fy_padded), d=1/fs)
    plt.plot(freqs_padded, magfy_padded, '.', label='padding factor %d' % factor,
             alpha=0.5, markersize=markersize)

plt.xlabel('Frequency')
plt.ylabel('Magnitude of Fourier Coefficient')
plt.grid()
plt.legend(framealpha=1, shadow=True)

plt.show()

You can try using either interpolation or zero-padding (which is equivalent to entire vector interpolation) to potentially improve your frequency estimation, if the S/N allows. Sinc kernel interpolation is more accurate than parabolic interpolation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM