2010年4月30日金曜日

k-平均法



k-平均法は非階層型クラスタリング手法の一つである。

参考 
クラスタリングの定番アルゴリズム「K-means法」をビジュアライズしてみた(てっく煮ブログ)

以前つくった串本浦神の潮位差と黒潮緯度の分布(下図)を2つのグループに分けてみる。

道具はscipyのk-means2関数を用いる。分けられたグループで色分けする。大きな●はそれぞれのグループの重心である(kmeans2で出てくる重心はNormarizeされたデータのものなのでそのままはつかわないこと)。重心が収束しているかの確認もおこなっている。


#read module
from read_kuroshio_lat import read_kuroshio_lat
from read_kushimoto_uragami import read_kushimoto_uragami

import matplotlib.pyplot as plt
import numpy as np

from scipy.cluster.vq import  kmeans2, whiten,vq # functions for k-means

#read data
kuroshio_lat=read_kuroshio_lat()
kushimoto_uragami=read_kushimoto_uragami()   

#kmeans
nomissing=np.logical_and(~kuroshio_lat.mask,~kushimoto_uragami.mask) #missing value should 
be excluded.
lat=kuroshio_lat.data[nomissing]
level=kushimoto_uragami.data[nomissing]
data=np.column_stack((level,lat))

whiteneddata=whiten(data) #whitened beforehand
centroids,index=kmeans2(whiteneddata,2,iter=20) #k-means

#check centroids are converged
label = vq(whiteneddata, centroids)[0]
for i in range(2):
    print 'group=',i
    print 'estimated=', centroids[i,:]
    print 'actual   =',whiteneddata[label==i,:].mean(axis=0)

#### plot
plt.figure()
#1stgroup 
plt.plot(level[index==0],lat[index==0],'r.')
plt.plot(level[index==1].mean(),lat[index==1].mean(),'go',markersize=12)
#2nd group
plt.plot(level[index==1],lat[index==1],'g.')
plt.plot(level[index==0].mean(),lat[index==0].mean(),'ro',markersize=12)

plt.xlabel("Kushimoto-Uragami")
plt.ylabel("Kuroshio Latitude")
plt.savefig("kurosho_kmeans.png")
plt.show()


出力
group= 0
estimated= [ 1.58989573 42.05977249]
     actual = [ 1.58989573 42.05977249]
group= 1
estimated= [ 0.14166519 40.3829155 ]
     actual = [ 0.14166519 40.3829155 ]

2010年4月27日火曜日

NINO3のスペクトル密度を信頼区間つきで



前回つくった信頼区間つきでスペクトル密度をもとめる関数を、NINO3監視指数に適用する。NINO3の読み込みは以前に作ったread_NINOモジュールを用いる。

下の例ではtaperにはHammingフィルタ、周波数空間でのスムージングに1-2-1フィルタ、信頼度は90%を用いた。
黒線がスペクトル密度、赤点線が信頼区間を示す。縦の黒点線は2年と7年に対応する補助線である。


from read_NINO import read_NINO

import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
import numpy as np

from  psd_withError import psd_withError #import psd_withError module  

data=read_NINO()
x=data["NINO3 ANOM"]

# case1
Pxx,f,upper,lower=psd_withError(x, NFFT=len(x),Fs=12,window=np.hamming(len(x)),smooth=[1,2,1],Confidencelevel=0.90)  
plt.figure()
plt.loglog(f,Pxx,'k-o')
plt.loglog(f,upper,'r--')
plt.loglog(f,lower,'r--')
plt.loglog([1./2.,1./2.],[1.E-5,1.E2],'k--') #vertical line for 2 years
plt.loglog([1./7.,1./7.],[1.E-5,1.E2],'k--') #vertical line for 7 years
plt.savefig("NINO_psd_withError.png")
plt.show() 
 
 

2010年4月26日月曜日

psd関数をスペルトル密度誤差も出せるよう拡張



前回の見延さんのスペクトル密度の推定誤差理論を使って、以前用いたmatlabplotlib.mlab.psd関数を拡張したモジュールを作った(psd_withError)。

stand aloneで用いる時(if __name__ == '__main__':以下)は、見延さんのスクリプトと同じことを確認するプログラム。結果が同じにならないのは、
  • 見延さんのスクリプトではfftの後データ数で割ってから二乗をとっている
  • matplotlibのpsd関数でonesidedを取るとき2倍している
  • matplotlibのpsd関数では、データテーパーをかけることによる振幅の減少を補償している
ためである。もし同じ結果を得たければ、プログラム中のコメントアウトしている部分をはずすことで可能である。

 
psd_withError.py
import numpy as np
from numpy.random import randn
import scipy as sp
import scipy.signal as signal
import scipy.stats as stats

import matplotlib.pyplot as plt 
import matplotlib.mlab as mlab
import matplotlib.cbook as cbook


#------------------------------------------
def psd_withError(x, NFFT=256, Fs=2, detrend=mlab.detrend_none, window=mlab.window_hanning,noverlap=0, pad_to=None, sides='def
ault', scale_by_freq=None,smooth=None,Confidencelevel=0.9):
    """
Extention of matplotlib.mlab.psd
The power spectral density with upper and lower limits within confidence level
You should not use Weltch's method here, instead you can use smoother in frequency domain with *smooth* 

Same input as matplotlib.mlab.psd except the following new inputs
*smooth*:
    smoothing window in frequency domain  
    for example, 1-2-1 filter is [1,2,1]
    default is None
*Confidencelevel*
    Confidence level to estimate upper and lower estimates.
    Default is 0.9

Returns the tuple (*Pxx*, *freqs*,*upper*,*lower*).
    upper and lower are limits of psd within confidence level.   
"""

    Pxxtemp,freqs = mlab.psd( x, NFFT, Fs, detrend, window, noverlap, pad_to, sides,scale_by_freq)

# commented out, if you want to get Minobe-san's result  Pxxtemp=Pxxtemp/float(NFFT)/2.*((np.abs(window)**2).mean())

#smoothing
    smooth=np.asarray(smooth)
    if smooth is not None:
        avfnc=smooth/float(sum(smooth))
        Pxx=np.convolve(Pxxtemp[:,0],avfnc,mode="same")
    else:
        Pxx=Pxxtemp[:,0]
        avfnc=np.asarray([1.])

#estimate uppper and lower estimate woth equivalent degree of freedom
    if pad_to is None:
        pad_to = NFFT

    if cbook.iterable(window):
        assert(len(window) == NFFT)
        windowVals = window
    else:
        windowVals = window(np.ones((NFFT,), x.dtype))

    edof=(1.+(1./sum(avfnc**2)-1.)*float(NFFT)/float(pad_to)*np.mean(windowVals))# equivalent degree of freedom
    a1=(1.-Confidencelevel)/2.
    a2=1.-a1
    lower=Pxx*stats.chi2.ppf(a1,2*edof)/stats.chi2.ppf(0.50,2*edof)
    upper=Pxx*stats.chi2.ppf(a2,2*edof)/stats.chi2.ppf(0.50,2*edof)
    return Pxx,freqs,upper,lower

########################################################

if __name__ == '__main__':
    plt.figure()
    for npnl in range(4):
        tlng=100; nsmpl=1000
        if npnl==0 :
            nfft=100
            taper=signal.boxcar(tlng)
            avfnc=[1, 1, 1, 1, 1];
            cttl='no padding, boxcar, 5-running'
        elif npnl==1:
            nfft=100
            taper=signal.hamming(tlng)
            avfnc=[1, 1, 1, 1, 1]
            cttl='no padding, hamming, 5-running'
        elif npnl==2:
            nfft=200
            taper=signal.boxcar(tlng)
            avfnc=[1, 1, 1, 1, 1];
            cttl='double padding, boxcar, 5-running'
        elif npnl==3:
            nfft=200
            taper=signal.hamming(tlng)
            avfnc=np.convolve([1, 2, 1],[1, 2, 1])
            cttl='double padding, hamming, double 1-2-1'
    
        tsrs=randn(tlng,nsmpl)
        ds_psd=np.zeros([nfft/2+1,nsmpl])
        upper_psd=np.zeros([nfft/2+1,nsmpl])
        lower_psd=np.zeros([nfft/2+1,nsmpl])
        for n in range(nsmpl):
            a,b,u,l=psd_withError(tsrs[:,n],NFFT=tlng,pad_to=nfft,Fs=1,window=taper,smooth=avfnc,Confidencelevel=0.9)
            ds_psd[:,n]=a[:]
            upper_psd[:,n]=u[:]
            lower_psd[:,n]=l[:]
        frq=b[:]

# 90% confidence level by Monte-Carlo
        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])

# estimate from extended degree of freedom 
        ce=np.zeros([frq.size,2]) 
        ce[:,0]=np.log10(np.sort(upper_psd,axis=1)[:,nsmpl*0.5])
        ce[:,1]=np.log10(np.sort(lower_psd,axis=1)[:,nsmpl*0.5])
#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_withError.png")
    plt.show()

2010年4月20日火曜日

スペクトルの推定誤差



見延さんのスペクトル誤差に関するmatlab scriptをpythonに移植してみた。
見延 2001:大 気海洋物理学特論4 大気海洋統計データ解析 第3章
http://ocw.hokudai.ac.jp/Course/GraduateSchool/Science/MeteorologyAndOceanology/2001/page/materials/MeterologyAndOceanology-2001-Note-03.pdf
Page15-16
numpy/scipy/matplotlibを使えば、matlabのscriptを移植するのはそれほど難しくない。
χ二乗分布の累積分布の逆関数は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()  

結果の図

2010年4月10日土曜日

ENSO の周期 psd関数のオプション5 scale_by_freq



前回の続き。
これは単なるテクニカルなオプションだが、メモのために記しておく。
*scale_by_freq*: boolean
          Specifies whether the resulting density values should be scaled by the scaling frequency, which gives density in units of Hz^-1. This allows for integration over the returned frequency values. The default is True for MatLab compatibility.

今まで使ってきた下のような例では、もとは月単位のデータを年単位のデータにしたときに(Fs=12)、自動的に12で割っている(scale_by_freq=Trueがデフォルト)。
すなわち、周波数が(1/2,1/3,1/4, ......)(月単位)の時のスペクトル密度(Pxx2,scale_by_freq=False)の場合と、周波数が(6,4,3, ....)(年単位)の時のスペクトル密度Pxx(scale_by_freq=True)の積分が一致するように
Pxx=Pxx2/12
の関係がある。
下のグラフのようにPxx(黒線)とPxx2/12(赤線)とは一致する。

from read_NINO import read_NINO

import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
import numpy as np

data=read_NINO()
x=data["NINO3 ANOM"]
Pxx,f=mlab.psd(x, NFFT=len(x),Fs=12)  
Pxx2,f2=mlab.psd(x, NFFT=len(x),Fs=12,scale_by_freq=False) 

plt.figure()
plt.loglog(f,Pxx,'k')
plt.loglog(f2,Pxx2/12.,'r--')
plt.loglog([1./7.,1./7.],[1.E-9,1.E2],'k--')
plt.loglog([1./2.,1./2.],[1.E-9,1.E2],'k--')
plt.savefig("NINO_psd_scale.png")
plt.show()

2010年4月8日木曜日

ENSO の周期 psd関数のオプション4 平滑化



前回の続き。
これはpsd関数のオプションというわけではないが、Welch法ではなく、得られたスペクトル密度を畳込みによって平滑化してみる。
平滑化にはscipyのcookbookの例にあるsmooth関数を用いる。
平滑化にはスペクトルの周波数の解像度を下げるかわりに統計的自由度を上げる働きがある(見延、2001; Hartmann 2008)。
平滑化には1-2-1フィルタを用いる。numpyでは5点bartlett関数を使うと良い(両端が0)。
import numpy as np
print np.bartlett(5)
[ 0.   0.5  1.   0.5  0. ]

以下がプログラム例。
黒線がもとのスペクトル密度。
赤線が平滑化したスペクトル密度。
(れいによって2年目と7年目の周期に当たるところに縦の点線)

from read_NINO import read_NINO

import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
import numpy as np


data=read_NINO()
x=data["NINO3 ANOM"]

#original
Pxx,f=mlab.psd(x, NFFT=len(x),Fs=12)  

# Smooth by 1-2-1 filter 
from cookb_signalsmooth import smooth # from Scipy cookbook
Pxx2=smooth(Pxx[:,0],5,'bartlett') 


#plot
plt.figure()
plt.loglog(f,Pxx,'k-o')
plt.loglog(f,Pxx2,'r-+')
plt.loglog([1./2.,1./2.],[1.E-6,1.E1],'k--') #vertical line for 2 years
plt.loglog([1./7.,1./7.],[1.E-6,1.E1],'k--') #vertical line for 7 years
plt.savefig("NINO_psd_smooth.png")
plt.show()


参考文献
見延 2001:大 気海洋物理学特論4 大気海洋統計データ解析 第3章
Hartmann 2008:ATM552 講義ノート第6a章



ENSOの周期 psd関数のオプション3 Welch法



前回の続き。
ここで用いているpsd関数は、データ長をブロックにわけ、その平均をおこなうことでスペクトル密度をスムーズにするオプションがある(Welch法)。
The power spectral density by Welch’s average periodogram method. The vector x is divided into NFFT length blocks.
noverlap: integer The number of points of overlap between blocks. The default value is 0 (no overlap).
以下がその例。
黒線がオリジナル。
赤線が、データ長を二分割したケース(オーバラップ0)。
青線が、データ超を二分割した上で、50%のオーバラップを許した例。
スペルトル密度の線がなめらかになるのがわかる。
(補助線として2年と7年に対応する周期に縦点線をひいている。)

from read_NINO import read_NINO

import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
import numpy as np

data=read_NINO()
x=data["NINO3 ANOM"]

#original
Pxx,f=mlab.psd(x, NFFT=len(x),Fs=12)  

# Date are devided into 2 blocks
#Zero pad to the length of the orignal data sequence.
Pxx2,f2=mlab.psd(x, NFFT=len(x)//2,pad_to=len(x),Fs=12)

#Date are devided into 2 blocks
#Allow 50 % overlap 
#Zero pad to the length of the orignal data sequence.
Pxx3,f3=mlab.psd(x, NFFT=len(x)//2,noverlap=int(0.5*len(x)/2.),pad_to=len(x),Fs=
12)

#plot
plt.figure()
plt.loglog(f,Pxx,'k-o')
plt.loglog(f2,Pxx2,'r-+')
plt.loglog(f3,Pxx3,'b-x')
plt.loglog([1./2.,1./2.],[1.E-6,1.E1],'k--') #vertical line for 2 years
plt.loglog([1./7.,1./7.],[1.E-6,1.E1],'k--') #vertical line for 7 years
plt.savefig("NINO_psd_welch.png")
plt.show()

参考
pylab.psd関数のデモ
pylab_examples example code: psd_demo2.py
MATLABのWelch法の関数pwelch
http://www.mathworks.com/access/helpdesk_ja_JP/help/toolbox/signal/pwelch.html