在python中将FFT绘制为一组正弦波?
问题内容:
我在演示中看到有人这样做,但是我很难复制他的能力。这是他的演讲的幻灯片:
很酷 他使用FFT分解了一个数据集,然后绘制了FFT指定的适当正弦波。
因此,为了重现他所做的工作,我创建了一系列与两个正弦波组合相对应的点:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
x = np.arange(0, 10, 0.01)
x2 = np.arange(0, 20, 0.02)
sin1 = np.sin(x)
sin2 = np.sin(x2)
x2 /= 2
sin3 = sin1 + sin2
plt.plot(x, sin3)
plt.show()
现在,我想将该波(或更确切地说,这些点所暗示的波)分解回原来的两个正弦波:
# goal: sin3 -> sin1, sin2
# sin3
array([ 0.00000000e+00, 2.99985000e-02, ... 3.68998236e-01])
# sin1
array([ 0. , 0.00999983, 0.01999867, ... -0.53560333])
# sin2
array([ 0. , 0.01999867, 0.03998933, ... 0.90460157])
我开始通过导入numpy
和得到fft
的sin3
:
import numpy as np
fft3 = np.fft.fft(sin3)
好,就我所知。现在,我得到了一个包含复数的数组:
array([ 2.13316069e+02+0.00000000e+00j, 3.36520138e+02+4.05677438e+01j,...])
如果我天真地绘制它,我会看到:
plt.plot(fft3)
plt.show()
好的,不确定该怎么做。
我想从这里转到看起来像sin1和sin2的数据集:
plt.plot(sin1)
plt.show()
plt.plot(sin2)
plt.show()
我明白在复数的实部和虚部fft3
的数据集,我只是不知道该怎么跟他们做得出sin1
,并sin2
从它的数据集。
我知道这与编程的关系较小,而与数学的关系较大,但是有人可以在这里给我一个提示吗?
编辑:更新马克·斯奈德的答案:
使用Mark的代码,我能够得到我期望的结果,并最终得到了这种方法:
def decompose_fft(data: list, threshold: float = 0.0):
fft3 = np.fft.fft(data)
x = np.arange(0, 10, 10 / len(data))
freqs = np.fft.fftfreq(len(x), .01)
recomb = np.zeros((len(x),))
for i in range(len(fft3)):
if abs(fft3[i]) / len(x) > threshold:
sinewave = (
1
/ len(x)
* (
fft3[i].real
* np.cos(freqs[i] * 2 * np.pi * x)
- fft3[i].imag
* np.sin(freqs[i] * 2 * np.pi * x)))
recomb += sinewave
plt.plot(x, sinewave)
plt.show()
plt.plot(x, recomb, x, data)
plt.show()
稍后,我将使它返回重新组合的wave列表,但现在我有点不明白了。首先,我这样称呼它,只是传入一个数据集。
decompose_fft(sin3, threshold=0.0)
但是看起来不错,但是我在这行中得到了一条怪异的话:y=0.2
有人知道这可能是什么或是什么造成的吗?
编辑:
马克回答了以上问题,谢谢!
问题答案:
离散傅立叶变换可为您提供复指数的系数,这些系数相加在一起可产生原始离散信号。特别是,第k个傅里叶系数可为您提供有关在给定数量的样本上具有k个周期的正弦波振幅的信息。
请注意,由于您的正弦波在1000个样本中没有整数个周期,因此实际上您将无法使用FFT检索原始正弦波。相反,您将得到许多不同正弦曲线的混合体,包括〜.4的恒定分量。
您可以使用以下代码绘制各种分量正弦曲线,并观察它们的和为原始信号:
freqs = np.fft.fftfreq(len(x),.01)
threshold = 0.0
recomb = np.zeros((len(x),))
for i in range(len(fft3)):
if abs(fft3[i])/(len(x)) > threshold:
recomb += 1/(len(x))*(fft3[i].real*np.cos(freqs[i]*2*np.pi*x)-fft3[i].imag*np.sin(freqs[i]*2*np.pi*x))
plt.plot(x,1/(len(x))*(fft3[i].real*np.cos(freqs[i]*2*np.pi*x)-fft3[i].imag*np.sin(freqs[i]*2*np.pi*x)))
plt.show()
plt.plot(x,recomb,x,sin3)
plt.show()
通过更改threshold
,您还可以选择排除低功率的正弦波,并查看其如何影响最终的重建。
编辑:上面的代码中有一个陷阱,尽管它没有错。它隐藏了DFT对于真实信号的固有对称性,并以其真实幅度的一半绘制了每个正弦曲线两次。此代码性能更高,并以正确的幅度绘制正弦曲线:
freqs = np.fft.fftfreq(len(x),.01)
threshold = 0.0
recomb = np.zeros((len(x),))
middle = len(x)//2 + 1
for i in range(middle):
if abs(fft3[i])/(len(x)) > threshold:
if i == 0:
coeff = 2
else:
coeff = 1
sinusoid = 1/(len(x)*coeff/2)*(abs(fft3[i])*np.cos(freqs[i]*2*np.pi*x+cmath.phase(fft3[i])))
recomb += sinusoid
plt.plot(x,sinusoid)
plt.show()
plt.plot(x,recomb,x,sin3)
plt.show()
在一般情况下,如果您知道信号是由一些正弦波子集组成的,这些子集的频率可能与信号的长度未正确对齐,则可以通过零填充或扩展信号来识别频率。您可以在此处了解更多信息。如果信号是完全任意的,而您只是对查看分量正弦波感兴趣,则无需这样做。