高速フーリエ変換FFTを使って波形データを周波数分析してみた【Numpy】

 

こんにちは、うさじん(@Rabi_Jin_)です。

気温も一時期より大分暖かくなってきて、完全防寒対策して外出することもなくなってきました。

個人的には寒いより熱いほうが得意なので、早く春になればいいなと思っている今日このごろです。

さて、昨日作成したPythonの高速フーリエ変換(FFT)プログラムを、単一周波数以外の波形について分析してみました。

本記事ではその分析結果などについてご紹介します。

 

前回記事

関連記事

  こんにちは、うさじん(@Rabi_Jin_)です。 今回はデータ分析シリーズとなります。 タイトル通り、PythonのNumpyを使って周波数分析をやってみました。 高速フーリエ変換(FFT)は[…]

 

分析した波形

分析した波形は以下の4つです。

  1. 単一周波数をもつ正弦波の波形(おさらい)
  2. 2種以上の周波数をもつ波形
  3. 2種以上の周波数及び直流成分をもつ波形
  4. 2種以上の周波数及びランダム要素を持つ波形

上記の波形について、設定条件の値とFFTの分析値の比較をします。

Pythonコード

import pandas as pd
from matplotlib import pyplot as plt
import numpy as np
import japanize_matplotlib

n= 1024 #データ数
Fs = 1024 #サンプリング周波数
dt = 1/Fs #サンプリング周期
Frange = Fs/2.56 #分析周波数
F1, F2 = 50, 250 #正弦波の周波数
gain = 1 #正弦波の振幅
offset = 1 #直流成分
t = 1 #データ上限(秒)

#時間軸データ #0~1秒までを表示
time = np.arange(0, t, dt)

# 単一周波数をもつ正弦波の波形(おさらい)
f1 = gain * np.sin(2*np.pi*F1*time)

# 2種以上の周波数をもつ波形
f2 = gain * np.sin(2*np.pi*F1*time) + gain * 0.8 * np.sin(2*np.pi*F2*time)

# 2種以上の周波数及び直流成分をもつ波形
f3 = gain * np.sin(2*np.pi*F1*time) + gain * 0.8 * np.sin(2*np.pi*F2*time) + offset

# 2種以上の周波数及びランダム要素を持つ波形
f4 = gain * np.sin(2*np.pi*F1*time) + gain * 0.8 * np.sin(2*np.pi*F2*time) + gain * 0.3 * np.random.randn(n)

# 正弦波の列名称をリスト化
dlist=["time", "f1", "f2", "f3", "f4"]
# すべてのデータをDataFrameに格納
df = pd.DataFrame(list(zip(time, f1, f2, f3, f4)), columns = dlist)


#Figureの作成
fig = plt.figure(figsize=(15, 4),tight_layout=True) #figの設定、余分な余白削除

#グラフタイトルの作成
fig.suptitle("正弦波データ", weight=5, fontsize=16) #グラフタイトルの設定、太さ、サイズ

#データ及び軸項目の作成
#波形データのプロット
for i in range(len(dlist)-1): #range()はdfの列数-1を指定
  maxd = str(round(df[dlist[i + 1]].max(), 2)) #データの最大値抽出
  mind = str(round(df[dlist[i + 1]].min(), 2)) #データの最小値抽出
  plt.subplot(1, len(dlist)-1, i+1) #サブプロットの作成、位置 列数は繰り返し回数を指定
  plt.title(df.columns[i + 1] + " 最大値:" + maxd + " 最小値:" + mind, loc='left') #グラフタイトルの設定、位置
  plt.plot(time, df.iloc[:,i + 1]) #グラフデータの指定(x, y)
  plt.xlim(0,0.1) #横軸の最小値、最大値
  plt.ylim(-3, 3) #横軸の最小値、最大値
  plt.yticks(np.arange(-3, 3.1, step=0.5)) #軸目盛りを指定値から等間隔に分割
  plt.grid() #目盛線の表示
  plt.xlabel("Time[sec]") #横軸ラベル
  plt.ylabel("振幅[-]") #縦軸ラベル

波形のデータはnumpyのsinでそれぞれ1秒の長さで作成します。

複数の周波数で作成するときはそれぞの周波数のsin構文を足し合わせします。

データの作成後はDataFrameに格納し、サブプロットをFor構文で描画します。

 

実行結果

f1:単一周波数をもつ正弦波の波形、f2:2種以上の周波数をもつ波形、f3:2種以上の周波数及び直流成分をもつ波形、f4:2種以上の周波数及びランダム要素を持つ波形

 

f4についてはランダム要素を含むため、実行ごとに値が変るので注意します。

  • f1は50Hzの信号のみ 振幅は1
  • f2は50Hzと250Hzの周波数信号を含む 振幅は50Hz:1、250Hz:0.8
  • f3は50Hzと250Hzおよび1の直流成分を含む 振幅は50Hz:1、250Hz:0.8 直流成分は1
  • f4は50Hzと250Hzおよびランダム成分を含む 振幅は50Hz:1、250Hz:0.8 ランダム成分は0.3

各グラフのタイトルに波形の最大値と最小値を挿入し、分析値を確認しやすくしました。

波形データを一度データフレームに変換し、サブプロットでグラフ化しています。

 

~広告~

 

高速フーリエ変換FFTの実施

Pythonコード

#周波数軸
Fdf = pd.DataFrame(np.fft.fftfreq(n, d = dt), columns = ["frequency"]) #データ数、サンプリング周期を指定する。

#高速フーリエ変換
for f in range(len(df.columns)-1): #range()はdfの列数-1を指定
  temp = np.fft.fft(df.iloc[:, f+1]) #tempをFFT
  temp = abs(temp * 2 / n) #交流成分の正規化(2倍してサンプル数で割り算して絶対値を取る)
  temp[0] = abs(temp[0] / 2) #直流成分の正規化(サンプル数(2)で割り算して絶対値を取る)
  Fdf[df.columns[f + 1]] = temp #FdfのDataFrameにFFTを追加

#print(Fdf)

#Figureの作成
fig = plt.figure(figsize=(15, 4),tight_layout=True) #figの設定、余分な余白削除

#グラフタイトルの作成
fig.suptitle("FFT パワースペクトルデータ", weight=5, fontsize=16) #グラフタイトルの設定、太さ、サイズ

#波形データのプロット
for i in range(len(Fdf.columns)-1): #range()はdfの列数-1を指定
  plt.subplot(1, len(Fdf.columns)-1, i + 1) #サブプロットの作成、位置 列数は繰り返し回数を指定
  plt.title(Fdf.columns[i + 1], loc='left') #グラフタイトルの設定、位置
  plt.plot(Fdf.iloc[:, 0][0:int(n/2.56)], Fdf.iloc[:, i + 1][0:int(n/2.56)]) #グラフデータの指定(x, y)
  plt.xlim(0, Frange) #横軸の最小値、最大値
  plt.grid() #目盛線の表示
  plt.xlabel("Frequency[Hz]") #横軸ラベル
  plt.ylabel("振幅[-]") #縦軸ラベル

周波数軸の配列を作った際、DataFrameに変換します。

その後、For構文で作成した波形のFFTを分析処理し、DataFrameに列追加します。

波形データのプロット時の注意点として、
グラフデータの指定の際、

[0:int(n/2.56)]

で400ライン(n/2.56=400)分にスライスしなければFFT処理後の鏡像の周波数範囲まで表示されます。

横軸の最小値最大値も分析周波数範囲に設定します。

 

以下の書籍がPythonのデータ分析にとても約立つと思います。参考までにどうぞ。

 

[エンジニアのための]データ分析基盤入門 データ活用を促進する! プラットフォーム&データ品質の考え方 [ 斎藤 友樹 ]

価格:2,992円
(2023/1/17 23:41時点)

Pythonによるあたらしいデータ分析の教科書 第2版【電子書籍】[ 寺田学 ]

価格:2,838円
(2023/1/17 23:43時点)

 

 

実行結果

高速フーリエ変換 実行結果

 

無事高速フーリエ変換ができていることを確認しました。

 

内容を分析する

FFT分析の結果、出力したサブプロットグラフからは以下のことがわかりました。。

  • f1は50Hzに1の振幅
  • f2は50Hzに1、250Hzに0.8の振幅を確認
  • f3は50Hzに1、250Hzに0.8の振幅を確認 直流成分は不明
  • f4は50Hzに約1、250Hzに約0.8の振幅を確認 ランダムの信号も確認

現状の横軸スケールではf3の直流成分が確認できないので拡大して確認します。

plt.xlim(-1, 1) #横軸の最小値、最大値
横軸スケール拡大

 

f3の0Hz=直流成分は1であることを確認しました。

次にf4の50Hzおよび250Hzの振幅を確認します。

plt.xlim(49, 50) #横軸の最小値、最大値
振幅の確認 50Hz
振幅の確認 250Hz

 

f4について、50Hzと250Hzのピーク箇所が少し大きいような気がします、誤差ですかね。

おそらくランダム周波数が影響していることが原因かと思います。

念のため、数値を確認します。

#FFT最大値と周波数の取得
for m in range(len(Fdf.columns)-1):
  Fmax = max(Fdf.iloc[:, m+1]) #FFTの最大値取得
  maxHz = np.argmax(Fdf.iloc[:, m+1]) #FFTの最大値周波数位置の取得
  fftmax = Fdf.columns[m+1] + " peak max :" + str(round(Fmax, 2)) + " " + "@" + str(maxHz) + "Hz"
  print(fftmax)

実行結果

最大値の抽出

 

f4は若干ずれが生じているようですがプラスマイナスでも5%以内なので誤差の範囲と言えるでしょう。

ランダム成分を含むためリーケージ(漏れ誤差)の問題もあります。

RMS値の確認くらいですかね。正弦波と切り分けて評価する必要がありそうです。

 

まとめ

まとめます。

波形 設定条件 FFT分析結果
f1 単一周波数の正弦波波形 50Hz 振幅1 50Hz 振幅1
f2 2種以上の周波数を持つ波形 50Hz 振幅1、 250Hz 振幅0.8 50Hz 振幅1、 250Hz 振幅0.8
f3 2種以上の周波数及び直流成分を持つ波形 50Hz 振幅1、 250Hz 振幅0.8 直流成分1.0 50Hz 振幅1、 250Hz 振幅0.8 直流成分1.0
f4 2種以上の周波数及びランダム成分を持つ波形 50Hz 振幅1、 250Hz 振幅0.8 ランダム成分振幅0.3 50Hz 振幅1.02、 250Hz 振幅0.82 ランダム成分確認※

※ランダム成分は現状数値評価できないため、今後の課題とする。

以上が今回の波形データを周波数分析してみたとなります。

個人的に一番悩んだのは分析ではなく、PythonのDataFrameの扱いです。

データの取り扱いを手足のように扱えるよう精進したいです。

 

参考サイト

Pandas でデータフレームから特定の行・列を取得する | Python でデータサイエンス
https://pythondatascience.plavox.info/pandas/%E8%A1%8C%E3%83%BB%E5%88%97%E3%81%AE%E6%8A%BD%E5%87%BA

Pandas のデータフレームに行や列 (カラム) を追加する | Python でデータサイエンス
https://pythondatascience.plavox.info/pandas/pandas%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%81%AB%E8%A1%8C%E3%82%84%E5%88%97%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B

Pythonで高速フーリエ変換(FFT)の練習-4 フィルタリングでノイズを除去する | もものきとデータ解析をはじめよう
https://momonoki2017.blogspot.com/2018/03/pythonfft-4.html

 

 

以下の書籍がPythonのデータ分析にとても約立つと思います。参考までにどうぞ。

 

[エンジニアのための]データ分析基盤入門 データ活用を促進する! プラットフォーム&データ品質の考え方 [ 斎藤 友樹 ]

価格:2,992円
(2023/1/17 23:41時点)

Pythonによるあたらしいデータ分析の教科書 第2版【電子書籍】[ 寺田学 ]

価格:2,838円
(2023/1/17 23:43時点)

 

 

 

 

~広告~