在Python中绘制快速傅里叶变换

时间:2014-09-09 00:45:06

标签: python numpy scipy fft

我可以访问numpy和scipy,并且想要创建数据集的简单FFT。我有两个列表,一个是y值,另一个是y值的时间戳。

将这些列表提供给scipy或numpy方法并绘制结果FFT的最简单方法是什么?

我查了一些示例,但它们都依赖于创建一组具有一定数量的数据点和频率等的虚假数据,而且并没有真正展示如何使用一组数据和相应的时间戳。

我尝试过以下示例:

from scipy.fftpack import fft
# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N/2]))
plt.grid()
plt.show()

但是当我将fft的参数更改为我的数据集并绘制它时会得到非常奇怪的结果,看起来频率的缩放可能会关闭。我不确定。

这是我试图FFT的数据的粘贴框

http://pastebin.com/0WhjjMkb http://pastebin.com/ksM4FvZS

当我对整个事情做一个fft时,它只有一个巨大的峰值零,没有别的

这是我的代码:

## Perform FFT WITH SCIPY
signalFFT = fft(yInterp)

## Get Power Spectral Density
signalPSD = np.abs(signalFFT) ** 2

## Get frequencies corresponding to signal PSD
fftFreq = fftfreq(len(signalPSD), spacing)

## Get positive half of frequencies
i = fftfreq>0

##
plt.figurefigsize=(8,4));
plt.plot(fftFreq[i], 10*np.log10(signalPSD[i]));
#plt.xlim(0, 100);
plt.xlabel('Frequency Hz');
plt.ylabel('PSD (dB)')

间距等于xInterp[1]-xInterp[0]

7 个答案:

答案 0 :(得分:68)

所以我在IPython笔记本中运行一个功能相同的代码形式:

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack

# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = scipy.fftpack.fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)

fig, ax = plt.subplots()
ax.plot(xf, 2.0/N * np.abs(yf[:N//2]))
plt.show()

我得到了我认为非常合理的输出。

enter image description here

由于我在工程学院考虑过信号处理,所以我承认的时间比我要承认的要长,但50和80的峰值正是我所期待的。那么问题是什么?

响应发布的原始数据和评论

这里的问题是您没有定期数据。您应该始终检查您提供给任何算法的数据,以确保它是合适的。

import pandas
import matplotlib.pyplot as plt
#import seaborn
%matplotlib inline

# the OP's data
x = pandas.read_csv('http://pastebin.com/raw.php?i=ksM4FvZS', skiprows=2, header=None).values
y = pandas.read_csv('http://pastebin.com/raw.php?i=0WhjjMkb', skiprows=2, header=None).values
fig, ax = plt.subplots()
ax.plot(x, y)

enter image description here

答案 1 :(得分:17)

关于fft的重要之处在于它只能应用于时间戳统一的数据(及时统一采样,如上所示)。

如果采样不均匀,请使用函数来拟合数据。有几个教程和功能可供选择:

https://github.com/tiagopereira/python_tips/wiki/Scipy%3A-curve-fitting    http://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html

如果无法选择拟合,您可以直接使用某种形式的插值来将数据插值为均匀采样:

https://docs.scipy.org/doc/scipy-0.14.0/reference/tutorial/interpolate.html

当你有统一的样本时,你只需要了解样本的时间差值(t[1] - t[0])。在这种情况下,您可以直接使用fft函数

Y    = numpy.fft.fft(y)
freq = numpy.fft.fftfreq(len(y), t[1] - t[0])

pylab.figure()
pylab.plot( freq, numpy.abs(Y) )
pylab.figure()
pylab.plot(freq, numpy.angle(Y) )
pylab.show()

这应该可以解决您的问题。

答案 2 :(得分:7)

您的高峰值是由于信号的DC(非变化,即freq = 0)部分造成的。这是一个规模问题。如果要查看非DC频率内容,可视化,您可能需要从偏移1绘制而不是从信号FFT的偏移0绘制。

修改@PaulH上面给出的例子

import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack

# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = 10 + np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = scipy.fftpack.fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)

plt.subplot(2, 1, 1)
plt.plot(xf, 2.0/N * np.abs(yf[0:N/2]))
plt.subplot(2, 1, 2)
plt.plot(xf[1:], 2.0/N * np.abs(yf[0:N/2])[1:])

输出图: Ploting FFT signal with DC and then when removing it (skipping freq = 0)

另一种方法是以对数比例显示数据:

使用:

plt.semilogy(xf, 2.0/N * np.abs(yf[0:N/2]))

将显示: enter image description here

答案 3 :(得分:5)

作为已经给出的答案的补充,我想指出,通常使用FFT的二进制位大小非常重要。测试一堆值并选择一个对您的应用程序更有意义的值是有意义的。通常,它与样本数量的大小相同。这是大多数给出的答案所假设的,并产生了很好的合理结果。如果有人想探索它,这是我的代码版本:

CultureInfo

输出图: enter image description here

答案 4 :(得分:4)

我写了这个附加答案,以解释使用fft时尖峰扩散的起源,并特别讨论了我在某些时候不同意的scipy.fftpack教程。

在此示例中,记录时间tmax=N*T=0.75。信号为sin(50*2*pi*x)+0.5*sin(80*2*pi*x)。频率信号应在频率5080处包含2个尖峰,振幅为10.5。但是,如果所分析的信号的周期数不为整数,则由于信号的截断会出现扩散:

  • 派克1:50*tmax=37.5 =>频率50不是1/tmax =>的倍数存在扩散,因为在该频率上信号被截断了。< / li>
  • 派克2:80*tmax=60 =>频率801/tmax =>的倍数。没有扩散,因为该频率的信号被截断了。

以下代码分析的信号与教程(sin(50*2*pi*x)+0.5*sin(80*2*pi*x))中的信号相同,但略有不同:

  1. 原始的scipy.fftpack示例。
  2. 原始scipy.fftpack示例具有整数个信号周期(tmax=1.0而不是0.75,以避免截断扩散)。
  3. 原始scipy.fftpack示例,其中包含整数个信号周期,并且日期和频率均取自FFT理论。

代码:

import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack

# 1. Linspace
N = 600
# sample spacing
tmax = 3/4
T = tmax / N # =1.0 / 800.0
x1 = np.linspace(0.0, N*T, N)
y1 = np.sin(50.0 * 2.0*np.pi*x1) + 0.5*np.sin(80.0 * 2.0*np.pi*x1)
yf1 = scipy.fftpack.fft(y1)
xf1 = np.linspace(0.0, 1.0/(2.0*T), N//2)

# 2. Integer number of periods
tmax = 1
T = tmax / N # sample spacing
x2 = np.linspace(0.0, N*T, N)
y2 = np.sin(50.0 * 2.0*np.pi*x2) + 0.5*np.sin(80.0 * 2.0*np.pi*x2)
yf2 = scipy.fftpack.fft(y2)
xf2 = np.linspace(0.0, 1.0/(2.0*T), N//2)

# 3. Correct positionning of dates relatively to FFT theory (arange instead of linspace)
tmax = 1
T = tmax / N # sample spacing
x3 = T * np.arange(N)
y3 = np.sin(50.0 * 2.0*np.pi*x3) + 0.5*np.sin(80.0 * 2.0*np.pi*x3)
yf3 = scipy.fftpack.fft(y3)
xf3 = 1/(N*T) * np.arange(N)[:N//2]

fig, ax = plt.subplots()
# Plotting only the left part of the spectrum to not show aliasing
ax.plot(xf1, 2.0/N * np.abs(yf1[:N//2]), label='fftpack tutorial')
ax.plot(xf2, 2.0/N * np.abs(yf2[:N//2]), label='Integer number of periods')
ax.plot(xf3, 2.0/N * np.abs(yf3[:N//2]), label='Correct positionning of dates')
plt.legend()
plt.grid()
plt.show()

输出:

正如这里可能的那样,即使使用整数周期,仍然会有一些扩散。此行为是由于scipy.fftpack教程中的日期和频率定位错误所致。因此,在离散傅立叶变换理论中:

  • 应该在日期t=0,T,...,(N-1)*T评估信号,其中T是采样周期,信号的总持续时间为tmax=N*T。请注意,我们在tmax-T停了下来。
  • 关联的频率为f=0,df,...,(N-1)*df,其中df=1/tmax=1/(N*T)是采样频率。信号的所有谐波均应为采样频率的倍数,以避免扩散。

在上面的示例中,您可以看到使用arange而非linspace可以避免频谱中的其他扩散。此外,使用linspace版本还会导致尖峰的偏移,该尖峰的频率比应有的频率稍高,如在第一张图片中可以看到的那样,尖峰稍微偏右。频率5080

我将得出结论,用法示例应替换为以下代码(在我看来,这不太容易引起误解):

import numpy as np
from scipy.fftpack import fft
# Number of sample points
N = 600
T = 1.0 / 800.0
x = T*np.arange(N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = 1/(N*T)*np.arange(N//2)
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N//2]))
plt.grid()
plt.show()

输出(第二个峰值不再扩散):

我认为这个答案仍然带来了有关如何正确应用离散傅立叶变换的其他解释。显然,我的答案太长了,总是有更多的话要说(例如,@ ewerlopes简短地谈论了aliasing,而关于windowing可以说很多),所以我将停下来。我认为,在应用离散傅里叶变换时,深刻理解离散傅里叶变换的原理非常重要,因为我们都知道很多人在应用离散傅里叶变换时都会在其中添加因数以获得自己想要的东西。

答案 5 :(得分:1)

此页面上已经有不错的解决方案,但是所有人都假定数据集是均匀/均匀采样/分布的。我将尝试提供一个更一般的随机采样数据示例。我还将以this MATLAB tutorial为例:

添加所需的模块:

import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
import scipy.signal

生成示例数据:

N = 600 # number of samples
t = np.random.uniform(0.0, 1.0, N) # assuming the time start is 0.0 and time end is 1.0
S = 1.0 * np.sin(50.0 * 2 * np.pi * t) + 0.5 * np.sin(80.0 * 2 * np.pi * t) 
X = S + 0.01 * np.random.randn(N) # adding noise

排序数据集:

order = np.argsort(t)
ts = np.array(t)[order]
Xs = np.array(X)[order]

重新采样:

T = (t.max() - t.min()) / N # average period 
Fs = 1 / T # average sample rate frequency
f = Fs * np.arange(0, N // 2 + 1) / N; # resampled frequency vector
X_new, t_new = scipy.signal.resample(Xs, N, ts)

绘制数据和重新采样的数据:

plt.xlim(0, 0.1)
plt.plot(t_new, X_new, label="resampled")
plt.plot(ts, Xs, label="org")
plt.legend()
plt.ylabel("X")
plt.xlabel("t")
  

enter image description here

现在计算fft:

Y = scipy.fftpack.fft(X_new)
P2 = np.abs(Y / N)
P1 = P2[0 : N // 2 + 1]
P1[1 : -2] = 2 * P1[1 : -2]

plt.ylabel("Y")
plt.xlabel("f")
plt.plot(f, P1)
  

enter image description here

答案 6 :(得分:1)

我必须构建一个函数,用于绘制实信号的FFT。在我的函数中,存在信号的实际振幅(同样,由于假设是真实信号,这意味着对称...):

import matplotlib.pyplot as plt
import numpy as np
import warnings

def fftPlot(sig, dt=None, block=False, plot=True):
    # here it's assumes analytic signal (real signal...)- so only half of the axis is required

    if dt is None:
        dt = 1
        t = np.arange(0, sig.shape[-1])
        xLabel = 'samples'
    else:
        t = np.arange(0, sig.shape[-1]) * dt
        xLabel = 'freq [Hz]'

    if sig.shape[0] % 2 != 0:
        warnings.warn("signal prefered to be even in size, autoFixing it...")
        t = t[0:-1]
        sig = sig[0:-1]

    sigFFT = np.fft.fft(sig) / t.shape[0]  # divided by size t for coherent magnitude

    freq = np.fft.fftfreq(t.shape[0], d=dt)

    # plot analytic signal - right half of freq axis needed only...
    firstNegInd = np.argmax(freq < 0)
    freqAxisPos = freq[0:firstNegInd]
    sigFFTPos = 2 * sigFFT[0:firstNegInd]  # *2 because of magnitude of analytic signal

    if plot:
        plt.figure()
        plt.plot(freqAxisPos, np.abs(sigFFTPos))
        plt.xlabel(xLabel)
        plt.ylabel('mag')
        plt.title('Analytic FFT plot')
        plt.show(block=block)

    return sigFFTPos, freqAxisPos


if __name__ == "__main__":
    dt = 1 / 1000
    f0 = 1 / dt / 4

    t = np.arange(0, 1 + dt, dt)
    sig = np.sin(2 * np.pi * f0 * t)
    fftPlot(sig, dt=dt)
    fftPlot(sig)

    t = np.arange(0, 1 + dt, dt)
    sig = np.sin(2 * np.pi * f0 * t) + 10 * np.sin(2 * np.pi * f0 / 2 * t)
    fftPlot(sig, dt=dt, block=True)