Pythonで高速フーリエ変換をプロットする


98

NumPyとSciPyにアクセスでき、データセットの単純なFFTを作成したいと思います。2つのリストがあります。1つはy値で、もう1つはそれらの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]です。


あなたが試したこと、失敗した方法、そしてあなたが取り組んでいる例を教えてください。
ポールH

私が試した例と私が考えたことを投稿しました。出力を正しくプロットする方法について混乱しているだけだと思います。
user3123955 2014

それは素晴らしい例ですが、問題は正確には何ですか?そのコードは私にとって素晴らしい働きをします。プロットが表示されないだけですか?
ポールH

つまり、どのような種類の引数を使用していますか(少なくとも一部のデータを確認する必要があります)
Paul H

x軸とy軸のペーストビンを追加しました。xデータは秒単位で、yデータは単なるセンサーの読み取り値です。これらのデータのリストをfftの例に入れると、ゼロで大きなスパイクが発生します
user3123955 2014

回答:


103

そのため、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()

私は非常に合理的な出力であると私が信じているものを手に入れます。

ここに画像の説明を入力してください

工学部で信号処理を考えていたので、認めるよりも長いですが、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)

ここに画像の説明を入力してください


1
例が間違っているわけではありません。それを取得してデータに適用する方法がわかりません。
user3123955 2014

1
@ user3123955、そうです。だからこそ、私たちはあなたのデータを見る必要があり、あなたがあなたを助けるつもりならそれがどのように失敗するかを見る必要があります。
ポールH

私はペーストビンを追加しました
user3123955 2014

2
@ user3123955では、FFTアルゴリズムがそれについて何をすることを期待していますか?データをクリーンアップする必要があります。
ポールH

6
@PaulHは、周波数50 Hzでの振幅をである必要が1あり80 Hzます0.5か?
フルカンハシム

25

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()

これで問題が解決するはずです。


3
データを均等な間隔で補間しました。fftfreqの機能を正確に教えてください。なぜx軸が必要なのですか?なぜYの腹筋と角度をプロットするのですか?角度は位相ですか?相対的な位相は何ですか?私のデータでこれを行うと、0Hzに巨大なピークがあり、非常に速くテールオフしますが、一定のオフセットを持たないデータを供給しています(エッジが0.15 Gz〜12Hzのデータで大きなバンドパスを実行します)一定のオフセットを取り除くために、私のデータはとにかく4 Hzを超えてはならないので、帯域によって情報が失われるはずです)。
user3123955 2014

3
1.fftfreqデータに対応する周波数成分を提供します。プロットfreqすると、x軸が増加し続ける関数ではないことがわかります。x軸に正しい周波数成分があることを確認する必要があります。マニュアルを見ることができます:docs.scipy.org/doc/numpy/reference/generated/…–
ssm

3
2.ほとんどの人は、fftの大きさと位相を見たいと思うでしょう。位相情報が何を伝えているのかを一文で説明するのは難しいですが、私が言えるのは、信号を組み合わせると意味があるということだけです。同相の同じ周波数の信号を組み合わせると増幅しますが、180度位相がずれていると減衰します。これは、フィードバックのあるアンプやその他のものを設計するときに重要になります。
ssm 2014

3
3.一般に、最低周波数の位相は実質的にゼロであり、これを参照しています。信号がシステム内を移動すると、すべての周波数が異なる速度で移動します。これが位相速度です。位相プロットはこの情報を提供します。使用しているシステムがわからないので、明確な答えを出すことはできません。このような質問については、フィードバック制御、アナログ電子工学、デジタル信号処理、電磁場理論など、またはシステムに固有の何かについて読むことをお勧めします。
ssm 2014

4
4.データを使用するのではなく、独自の信号を生成することから始めてはどうでしょうかt = linspace(0, 10, 1000); ys = [ (1.0/i)*sin(i*t) for i in arange(10)]; y = reduce(lambda m, n: m+n, ys)。次に、ysとのそれぞれをプロットしy、各コンポーネントのfftを取得します。あなたはあなたのプログラミングに自信を得るでしょう。次に、結果の信憑性を判断できます。あなたが分析しようとしている信号があなたが今までに最初に取った信号であるなら、あなたはいつもあなたが何か間違ったことをしていると感じるでしょう...
ssm 2014

12

あなたが持っている高いスパイクはあなたの信号のDC(不変、すなわち周波数= 0)部分によるものです。それは規模の問題です。DC以外の周波数コンテンツを表示する場合は、視覚化のために、信号のFFTのオフセット0からではなく、オフセット1からプロットする必要があります。

@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:])

出力プロット: DCを使用してFFT信号をプロットし、それを削除する場合(freq = 0をスキップ)

もう1つの方法は、データを対数スケールで視覚化することです。

使用:

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

表示されます: ここに画像の説明を入力してください


はい、Hz単位です。コードでは、の定義によりxf、fftビンが周波数にマップされます。
hesham_EE 2018

1
いいね!そして、y軸はどうですか?振幅?どうもありがとうhesham_EE–
ビクター

はい、y軸は複素fftの絶対値です。np.abs()
hesham_EE 2018年

8

すでに与えられた答えを補足するのと同じように、FFTのビンのサイズで遊ぶことがしばしば重要であることを指摘したいと思います。一連の値をテストして、アプリケーションにとってより意味のある値を選択することは理にかなっています。多くの場合、それはサンプル数と同じ大きさです。これは、与えられたほとんどの回答で想定されていたとおりであり、優れた合理的な結果をもたらします。それを探求したい場合のために、ここに私のコードバージョンがあります:

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

fig = plt.figure(figsize=[14,4])
N = 600           # Number of samplepoints
Fs = 800.0
T = 1.0 / Fs      # N_samps*T (#samples x sample period) is the sample spacing.
N_fft = 80        # Number of bins (chooses granularity)
x = np.linspace(0, N*T, N)     # the interval
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)   # the signal

# removing the mean of the signal
mean_removed = np.ones_like(y)*np.mean(y)
y = y - mean_removed

# Compute the fft.
yf = scipy.fftpack.fft(y,n=N_fft)
xf = np.arange(0,Fs,Fs/N_fft)

##### Plot the fft #####
ax = plt.subplot(121)
pt, = ax.plot(xf,np.abs(yf), lw=2.0, c='b')
p = plt.Rectangle((Fs/2, 0), Fs/2, ax.get_ylim()[1], facecolor="grey", fill=True, alpha=0.75, hatch="/", zorder=3)
ax.add_patch(p)
ax.set_xlim((ax.get_xlim()[0],Fs))
ax.set_title('FFT', fontsize= 16, fontweight="bold")
ax.set_ylabel('FFT magnitude (power)')
ax.set_xlabel('Frequency (Hz)')
plt.legend((p,), ('mirrowed',))
ax.grid()

##### Close up on the graph of fft#######
# This is the same histogram above, but truncated at the max frequence + an offset. 
offset = 1    # just to help the visualization. Nothing important.
ax2 = fig.add_subplot(122)
ax2.plot(xf,np.abs(yf), lw=2.0, c='b')
ax2.set_xticks(xf)
ax2.set_xlim(-1,int(Fs/6)+offset)
ax2.set_title('FFT close-up', fontsize= 16, fontweight="bold")
ax2.set_ylabel('FFT magnitude (power) - log')
ax2.set_xlabel('Frequency (Hz)')
ax2.hold(True)
ax2.grid()

plt.yscale('log')

出力プロット: ここに画像の説明を入力してください


6

実信号のFFTをプロットする関数を作成しました。以前の回答と比較した私の関数の追加のボーナスは、信号の実際の振幅を取得することです。

また、実際の信号を想定しているため、FFTは対称であるため、x軸の正の側のみをプロットできます。

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


def fftPlot(sig, dt=None, 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 preferred 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 frequence 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()

    return sigFFTPos, freqAxisPos


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

    # Build a signal within Nyquist - the result will be the positive FFT with actual magnitude
    f0 = 200  # [Hz]
    t = np.arange(0, 1 + dt, dt)
    sig = 1 * np.sin(2 * np.pi * f0 * t) + \
        10 * np.sin(2 * np.pi * f0 / 2 * t) + \
        3 * np.sin(2 * np.pi * f0 / 4 * t) +\
        7.5 * np.sin(2 * np.pi * f0 / 5 * t)

    # Result in frequencies
    fftPlot(sig, dt=dt)
    # Result in samples (if the frequencies axis is unknown)
    fftPlot(sig)

分析FFTプロット結果


5

このページにはすでに優れたソリューションがありますが、すべての人がデータセットが均一に/均等にサンプリング/分散されていることを前提としています。ランダムにサンプリングされたデータのより一般的な例を提供しようと思います。このMATLABチュートリアルも例として使用します。

必要なモジュールの追加:

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")

ここに画像の説明を入力してください

次に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)

ここに画像の説明を入力してください

PSついに、不均一に分布したデータのフーリエ変換を取得するために、より標準的なアルゴリズムを実装する時間ができました。ここに、コード、説明、およびJupyterノートブックの例が表示されます


resample不均一なサンプル時間の処理を示唆するものはドキュメントに表示されません。時間パラメーター(例では使用されていません)を受け入れますが、サンプル時間も均一であると想定しているようです。
user2699 2018

@ user2699この例が役立つかもしれません
Foad 2018

2
'scipy.signal.resample`は、FFTメソッドを使用してデータをリサンプリングします。これを使用して不均一なデータをリサンプリングし、均一なFFTを取得することは意味がありません。
user2699 2018

1
@ user2699ここは素朴すぎたようです。利用でき、すでにいくつかのライブラリ:1. NFFTのラッパーであるように思わライブラリNFFT 2. pyNFFTと3 PyNUFFT
FOAD

2
あなたが与えたすべての方法には長所と短所があります(ただしsklearn.utils.resample、補間を実行しないことに注意してください)。不規則にサンプリングされた信号の周波数を見つけるために利用できるオプション、またはさまざまなタイプの補間のメリットについて話し合いたい場合は、別の質問を開始してください。どちらも興味深いテーマですが、FFTのプロット方法に関する回答の範囲をはるかに超えています。
user2699 2018

4

この追加の回答を書いて、FFTを使用するときのスパイクの拡散の原因を説明し、特に、ある時点で同意できないscipy.fftpackチュートリアルについて説明します。

この例では、録音時間tmax=N*T=0.75。信号はsin(50*2*pi*x) + 0.5*sin(80*2*pi*x)です。周波数信号には、周波数5080振幅1およびの2つのスパイクが含まれている必要があり0.5ます。ただし、分析された信号に整数の周期がない場合、信号の切り捨てにより拡散が発生する可能性があります。

  • パイク1:50*tmax=37.5=>周波数501/tmax=>この周波数での信号の切り捨てによる拡散の存在の倍数ではありません。
  • パイク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. 整数個の信号周期を持ち、日付と周波数がFFT理論から取得された元のscipy.fftpackの例。

コード:

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 positioning 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 positioning 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バージョンを使用すると、スパイクが周波数50との右側に少しある最初の図に見られるように、本来あるべき周波数よりもわずかに高い周波数にあるスパイクのオフセットも発生し80ます。

使用例は次のコードに置き換える必要があると結論付けます(私の意見では誤解を招きにくいです)。

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()

出力(2番目のスパイクはもう拡散されません):

この答えは、正しく離散フーリエ変換を適用する方法について、まだいくつかの追加の説明をもたらすと思います。明らかに、私の答えは長すぎると(言うために追加のものが常にあるewerlopesを簡単に話したエイリアシング例えば、ロットはについて語ったことができ窓掛け私が停止されますので、は、)。

離散フーリエ変換を適用する際には、その原理を深く理解することが非常に重要だと思います。なぜなら、必要なものを得るために適用するときに、あちこちで因子を追加する人がたくさんいるからです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.