こんにちは、うさじん(@Rabi_Jin_)です。
気温も一時期より大分暖かくなってきて、完全防寒対策して外出することもなくなってきました。
個人的には寒いより熱いほうが得意なので、早く春になればいいなと思っている今日このごろです。
さて、昨日作成したPythonの高速フーリエ変換(FFT)プログラムを、単一周波数以外の波形について分析してみました。
本記事ではその分析結果などについてご紹介します。
前回記事
こんにちは、うさじん(@Rabi_Jin_)です。 今回はデータ分析シリーズとなります。 タイトル通り、PythonのNumpyを使って周波数分析をやってみました。 高速フーリエ変換(FFT)は[…]
分析した波形
分析した波形は以下の4つです。
- 単一周波数をもつ正弦波の波形(おさらい)
- 2種以上の周波数をもつ波形
- 2種以上の周波数及び直流成分をもつ波形
- 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構文で描画します。
実行結果
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円 |
Pythonによるあたらしいデータ分析の教科書 第2版【電子書籍】[ 寺田学 ] 価格:2,838円 |
実行結果
無事高速フーリエ変換ができていることを確認しました。
内容を分析する
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) #横軸の最小値、最大値
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円 |
Pythonによるあたらしいデータ分析の教科書 第2版【電子書籍】[ 寺田学 ] 価格:2,838円 |