Rを利用して 高速フーリエ変換 を試みます。
始めに2つの正弦波(5Hzと20Hz)を重ね合わせたサンプル時系列データを作成します。
<- 20250531
seed set.seed(seed)
# サンプルデータのパラメータ
<- 128 # データ点数 (2のべき乗とする)
N <- 100 # サンプリング周波数 (Hz)
Fs <- (0:(N - 1)) / Fs # 時間ベクトル (0から (N-1)/Fs まで)
t
# 信号成分
<- 5 # 第1の周波数 (Hz)
f1 <- 2 # 第1の振幅
A1 <- 20 # 第2の周波数 (Hz)
f2 <- 1 # 第2の振幅
A2
<- A1 * sin(2 * pi * f1 * t) + A2 * sin(2 * pi * f2 * t)
signal
# ノイズ成分
<- 0.5
noise_amplitude <- noise_amplitude * rnorm(N) # 正規分布に従うノイズ
noise
# ノイズを加えた信号
<- signal + noise
data_time_series
# 時系列データのプロット
plot(t, data_time_series,
type = "l",
xlab = "時間 (s)", ylab = "振幅",
main = "サンプル時系列データ"
)abline(h = 0, col = "grey")
続いて 高速フーリエ変換 のための関数を Cooley-Tukeyアルゴリズム により作成します。
<- function(x) {
fft_recursive <- length(x)
N
# 基底ケース: N=1ならそのまま返す
if (N <= 1) {
return(as.complex(x))
}
# 偶数番目の要素と奇数番目の要素に分割
<- seq(1, N, by = 2)
even_indices <- seq(2, N, by = 2)
odd_indices
<- x[even_indices]
x_even <- x[odd_indices]
x_odd
# 再帰的にFFTを計算
<- fft_recursive(x_even)
X_even <- fft_recursive(x_odd)
X_odd
# 結果を結合(バタフライ演算)
<- complex(length.out = N) # 結果を格納する複素数ベクトル
X for (k in 0:(N / 2 - 1)) {
# 回転因子(複素数)
<- exp(-2 * pi * 1i * k / N)
twiddle_factor
# Rのインデックスは1から始まる
+ 1] <- X_even[k + 1] + twiddle_factor * X_odd[k + 1]
X[k + N / 2 + 1] <- X_even[k + 1] - twiddle_factor * X_odd[k + 1]
X[k
}return(X)
}
サンプル時系列データを 高速フーリエ変換 にかけます。
<- fft_recursive(x = data_time_series)
fft_result head(fft_result)
[1] 16.72316+0.0000000i 12.69468+2.9159884i 8.94039-0.2680589i
[4] 12.50588-3.5751752i 20.75956-4.2024217i 21.97059-7.1845007i
FFTの結果の最初の要素 (fft_result[1]) はDC成分(周波数0Hz)です。
振幅スペクトルをプロットします。
# DC成分 + 正の周波数成分の数
<- ceiling((N + 1) / 2)
num_unique_pts
# 振幅スペクトル(各周波数成分の強さ)
# Mod() で複素数の絶対値(振幅)を取得
<- Mod(fft_result)
amplitudes # 片側スペクトルの振幅を取得
<- amplitudes[1:num_unique_pts]
amplitudes_onesided
# 振幅の正規化
# DC成分 (0 Hz) は N で割る
<- amplitudes_onesided / N
amplitudes_scaled # 他の周波数成分は N で割ってから2倍する
# 負の周波数成分を正の周波数成分に加算するため
2:(num_unique_pts - 1)] <- 2 * amplitudes_scaled[2:(num_unique_pts - 1)]
amplitudes_scaled[
# 周波数の作成
# FFTの結果のk番目の要素は (k-1)*Fs/N Hz の周波数に対応
# ナイキスト周波数 Fs/2 までの片側スペクトルを見る
<- (0:(num_unique_pts - 1)) * Fs / N
frequencies
# 振幅スペクトルのプロット
plot(frequencies, amplitudes_scaled,
type = "h",
xlab = "周波数 (Hz)", ylab = "振幅",
main = "FFTによる振幅スペクトル",
xlim = c(0, Fs / 2)
)abline(v = c(f1, f2), col = "red", lty = 2)
legend("topright", legend = c("FFTで求めた振幅", "元の信号の周波数"), col = c("black", "red"), lty = c(1, 2), lwd = c(1, 1), seg.len = 1)
サンプル時系列データの信号成分(第1の周波数 5(Hz)、第2の周波数 20(Hz)) が抽出できており、それぞれ設定した振幅に近い結果が確認できます(ノイズを加算していますので一致はしません)。
Rの関数 fft {stats} を利用して 高速フーリエ変換 にかけ、結果が一致しているか確認します。
<- fft(data_time_series)
fft_result_by_fft_stats <- Mod(fft_result_by_fft_stats)
amplitudes_by_fft_stats <- amplitudes_by_fft_stats[1:num_unique_pts]
amplitudes_onesided_by_fft_stats <- amplitudes_onesided_by_fft_stats / N
amplitudes_scaled_by_fft_stats 2:(num_unique_pts - 1)] <- 2 * amplitudes_scaled_by_fft_stats[2:(num_unique_pts - 1)]
amplitudes_scaled_by_fft_stats[all.equal(amplitudes_scaled, amplitudes_scaled_by_fft_stats)
[1] TRUE
一致しています。
最後に参考として位相スペクトルをプロットします。
<- Arg(fft_result_by_fft_stats[1:num_unique_pts]) # Arg()で複素数の偏角(位相)を取得
phases_by_fft_stats plot(frequencies, phases_by_fft_stats,
type = "h",
xlab = "周波数 (Hz)", ylab = "位相 (ラジアン)",
main = "FFTによる位相スペクトル",
xlim = c(0, Fs / 2)
)
以上です。