見延さんのスペクトル誤差に関するmatlab scriptをpythonに移植してみた。
見延 2001:大 気海洋物理学特論4 大気海洋統計データ解析 第3章numpy/scipy/matplotlibを使えば、matlabのscriptを移植するのはそれほど難しくない。
http://ocw.hokudai.ac.jp/Course/GraduateSchool/Science/MeteorologyAndOceanology/2001/page/materials/MeterologyAndOceanology-2001-Note-03.pdf
Page15-16
χ二乗分布の累積分布の逆関数はscipyのstats.chi2.ppfをそのまま用いた。
(注:今の場合関係ないが、この関数は自由度が1以下の場合はうまくいかないバグがあるようだ。すくなくともversion 0.7.0で確認した。)
#coding: utf-8
import numpy as np
from numpy.random import randn
import scipy as sp
import scipy.signal as signal
import matplotlib.pyplot as plt
#---------------------------------------
def chi2inv_nint(p,df):
import scipy.stats as stats
c=stats.chi2.ppf(p,df)
return c
#------------------------------------------
def psd_ex1(tsrs,nfft,ctaper,avfnc):
"""
function [ds_psd,frq]= psd_ex1(tsrs,nfft,ctaper,avfnc)
Power Spectrum Density を直接法によって求めるpython Script
平滑化は周波数領域で行なう.
複数時系列(sz2$gt;1)についても計算する.
入力
tsrs: 入力時系列 (sz1, sz2)各列についてスペクトルを求める
nfft: fft を計算する時間サンプル数
ctaper: taper 関数('hamming','hanning'など)
avfnc: 平滑化ウインドウ
出力
ds_psd: スペクトル密度.1×nfft/2+1.
frq: 周波数.1×nfft/2+1.
todo
PSD の定義に振幅がきちんと合うようにする.
"""
if tsrs.ndim==1:
sz1=tsrs.size #tsrs をsz2*1 のデータに変換.
sz2=1
tsrs=tsrs.reshape((sz1,1))
else:
sz1,sz2=tsrs.shape
print sz1,nfft
assert sz1<=nfft, ' sz1$gt;nfft' #nfft はtlng よりも大きくなくてはならない
#満たさないとエラーメッセージを出して終了
avfnc=avfnc/float(np.sum(avfnc)) #後で説明する平滑化ウインドウの規格化
w = getattr(signal, ctaper)(sz1) #実際のtaper 計算
for n in range(sz2):
tsrs[:,n]=tsrs[:,n]*w #時系列にテーパーをかける
tsrs=np.append(tsrs,np.zeros((nfft-sz1+1,sz2)),axis=0) #ゼロパッティング
famp=sp.fft(tsrs,axis=0) #フーリエ変換
famp=famp[0:(nfft/2+1),:]/sz1 #実数時系列のスペクトルなら,この範囲のみが必要.
d_psd=abs(famp)**2; #直接スペクトル値 (d: direct)
ds_psd=np.zeros_like(d_psd)
for n in range(sz2):
ds_psd[:,n]=np.convolve(d_psd[:,n],avfnc,mode='same'); #畳込みを使って平滑化 (ds: direct-smoothed) #畳み込みで増えた部分を除く
frq=np.arange(nfft/2+1)/float(nfft); #周波数を求めておく
return ds_psd,frq
########################################################
if __name__ == '__main__':
plt.figure()
for npnl in range(4):
tlng=100; nsmpl=1000
if npnl==0 :
nfft=100
taper=signal.boxcar(tlng)
ctaper='boxcar'
avfnc=np.asarray([1, 1, 1, 1, 1]);
cttl='no padding, boxcar, 5-running'
elif npnl==1:
nfft=100
taper=signal.hamming(tlng)
ctaper='hamming'
avfnc=np.asarray([1, 1, 1, 1, 1])
cttl='no padding, hamming, 5-running'
elif npnl==2:
nfft=200
taper=signal.boxcar(tlng)
ctaper='boxcar'
avfnc=np.asarray([1, 1, 1, 1, 1]);
cttl='double padding, boxcar, 5-running'
elif npnl==3:
nfft=200
taper=signal.hamming(tlng)
ctaper='hamming'
avfnc=np.convolve([1, 2, 1],[1, 2, 1])
cttl='double padding, hamming, double 1-2-1'
tsrs=randn(tlng,nsmpl);
[ds_psd,frq]=psd_ex1(tsrs,nfft,ctaper,avfnc)
# Monte-Carlo 法で得る90%信頼限界
srt_psd=np.sort(ds_psd,axis=1)
c=np.zeros([frq.size,2])
c[:,0]=np.log10(srt_psd[:,nsmpl*0.05])
c[:,1]=np.log10(srt_psd[:,nsmpl*0.95])
avfnc=avfnc/float(sum(avfnc)) # frequency domain の平滑化関数の規格化
c0=srt_psd[:,nsmpl*0.5]
edof=(1+(1/sum(avfnc**2)-1)*tlng/nfft*np.mean(taper))# 等価自由度の計算
ce=np.zeros([frq.size,2])
ce[:,0]=np.log10(c0*chi2inv_nint(0.050,2*edof)/chi2inv_nint(0.50,2*edof))
ce[:,1]=np.log10(c0*chi2inv_nint(0.950,2*edof)/chi2inv_nint(0.50,2*edof))
#plot
plt.subplot(2,2,npnl+1)
plt.plot(frq,c,'b',frq,ce,'r')
plt.title(cttl)
plt.xlabel('frq')
plt.ylabel('psd')
if (npnl==0): plt.legend(('Monte-carlo','','Theory'),'lower center',labelspacing=0.05)
plt.subplots_adjust(wspace=0.6,hspace=0.4)
plt.savefig("psd_ex1.png")
plt.show()
結果の図

0 件のコメント:
コメントを投稿