tag:crieit.net,2005:https://crieit.net/tags/numpy/feed 「numpy」の記事 - Crieit Crieitでタグ「numpy」に投稿された最近の記事 2021-01-19T15:50:06+09:00 https://crieit.net/tags/numpy/feed tag:crieit.net,2005:PublicArticle/16633 2021-01-19T15:50:06+09:00 2021-01-19T15:50:06+09:00 https://crieit.net/posts/Numpy-FFT Numpyを使用してFFT&トレンド除去 <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>Pythonで,Numpyを使って時系列データをFFT(Fast Fourier Transform: 高速フーリエ変換)する方法と,時系列データのトレンドを除去する方法について紹介しようと思います.FFTとは,DFT(Discrete Fourier Transform:離散フーリエ変換)を高速処理する計算方法です.この記事では理論には触れず,FFTを実行する最低限のコードを示します.参考にした文献は「スペクトル解析 著:日野幹雄 (朝倉書店)」.フーリエ解析の基礎からFFTの理論まで,この本1冊で十分です.</p> <h1 id="目次"><a href="#%E7%9B%AE%E6%AC%A1">目次</a></h1> <p>1.時系列データ<br /> 2.FFT実行<br /> 3.トレンド除去<br /> 4.フーリエ成分を周波数平滑化(スムージング)<br /> 5.Appendix</p> <h1 id="本題"><a href="#%E6%9C%AC%E9%A1%8C">本題</a></h1> <h2 id="1. 時系列データ"><a href="#1.+%E6%99%82%E7%B3%BB%E5%88%97%E3%83%87%E3%83%BC%E3%82%BF">1. 時系列データ</a></h2> <p>サンプリング周波数10Hzの30分間データ.オレンジの線は移動平均です.トレンドがあるのが分かりますね.<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649674/292fc03d-cd12-d9d0-9a92-f21afd798e12.png" alt="元時系列.png" /><br /> 図1.元の時系列データ</p> <p>このデータをFFTしていきます.</p> <h2 id="2. FFT実行"><a href="#2.+FFT%E5%AE%9F%E8%A1%8C">2. FFT実行</a></h2> <pre><code class="python">N =len(X) #データ長 fs=10 #サンプリング周波数 dt =1/fs # サンプリング間隔 t = np.arange(0.0, N*dt, dt) #時間軸 freq = np.linspace(0, fs,N) #周波数軸 fn=1/dt/2 #ナイキスト周波数 </code></pre> <p>FFTはデータ長が2のべき乗である場合に最も計算速度が速くなるような計算手法ですが,そうでない場合でも計算は可能です(処理時間が多少長くなりますが).ただし,データ長が素数の場合には,2のべき乗の時に比べて相対的にかなり処理時間がかかるので,2のべき乗になるよう0パディングした方が良さそうです.Appendixにそれを確かめるコードを載せたので是非確かめてみてください.</p> <pre><code class="python">F=np.fft.fft(X)/(N/2) F[(freq>fn)]=0 #ナイキスト周波数以降をカット plt.plot(freq,np.abs(F))# plt.xlabel("[Hz]") plt.ylabel("Amp") plt.xlim(-0.01,0.5) plt.grid() plt.show() </code></pre> <p>np.fft.fftでは複素フーリエ成分で与えられるので,その絶対値をとったものをプロットしたのが下図です.トレンド成分が0Hz付近にありますね.<br /> 次節ではトレンド除去してみましょう.<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649674/10cfb5d7-225a-0fcb-b587-15cd835a54d6.png" alt="元時系列をFFT.png" /><br /> 図2.元の時系列データに対してFFT実行</p> <h2 id="3. トレンド除去"><a href="#3.+%E3%83%88%E3%83%AC%E3%83%B3%E3%83%89%E9%99%A4%E5%8E%BB">3. トレンド除去</a></h2> <p>図1の移動平均(オレンジ線)を見ると,時系列データの軸がx軸と乖離しており,データにトレンドがあるのが分かります.次の操作で,0.03Hz以下を0にすることでトレンドを除去してみましょう.</p> <pre><code class="python">F=np.fft.fft(X)/(N/2) F[(freq>fn)]=0 F[(freq<=0.03)]=0 #0.03HZ以下を除去 X_1=np.real(np.fft.ifft(F))*N plt.xlabel("Time [s]") plt.ylabel("Signal") plt.xlim(-50,1850) plt.grid() plt.show() </code></pre> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649674/81607c19-d8a0-741c-0607-b738e22389e6.png" alt="周波数カット時系列.png" /><br /> 図3.トレンド除去後の時系列データ</p> <p>x軸を起点に周期関数となっていることが分かります.</p> <h2 id="4. フーリエ成分を周波数平滑化(スムージング)"><a href="#4.+%E3%83%95%E3%83%BC%E3%83%AA%E3%82%A8%E6%88%90%E5%88%86%E3%82%92%E5%91%A8%E6%B3%A2%E6%95%B0%E5%B9%B3%E6%BB%91%E5%8C%96%28%E3%82%B9%E3%83%A0%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0%29">4. フーリエ成分を周波数平滑化(スムージング)</a></h2> <p>図3のデータをFFTすると図4になります.<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649674/4b9efda7-b282-5598-308e-0a3b8206bd93.png" alt="周波数カット時系列をFFT.png" /><br /> 図4.トレンド除去後の時系列データに対してFFT</p> <p>ガタガタしてるので,平滑化ウィンドーを作用させてスムージングをしてみます.</p> <pre><code class="python">window=np.ones(5)/5 #平滑化ウィンドー F3=np.convolve(F2,window,mode='same') #畳み込み F3=np.convolve(F3,window,mode='same') #畳み込み F3=np.convolve(F3,window,mode='same') #畳み込み plt.plot(freq,np.abs(F3)) plt.xlabel("[Hz]") plt.ylabel("Amp") plt.xlim(-0.01,0.5) plt.grid() plt.show() </code></pre> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649674/e5c9259f-0027-f647-a77c-5bff2aa82fda.png" alt="周波数平滑化.png" /><br /> 図5.平滑化した</p> <h2 id="5. Appendix"><a href="#5.+Appendix">5. Appendix</a></h2> <p>データ長さを3種類用意して計算時間を比較してみます.<br /> ①2^19(2のべき乗) ②2^(19)-1 (素数) ③2^(19)-2 (素数でも2のいべき乗でもない)</p> <pre><code class="python">import time if __name__ == '__main__': start = time.time() x2 = np.random.uniform(size=2**19-2)#2**19 , 2**19-1 print(np.fft.fft(x2)) elapsed_time = time.time() - start print ("elapsed_time:{0}".format(elapsed_time) + "[sec]") </code></pre> <p>計算結果<br /> ①0.04197[sec]<br /> ②0.1679[sec]<br /> ③0.05796[sec]</p> <p>データ長が素数の場合には0パディングを行って2のべき乗にした方が良さそうですね.</p> kawai_mizugorou