Examples/svd - NCL tips

例:高度場偏差とSST偏差のSVD解析

JRA-55の月平均データから1月平均Z500偏差,ERSSTv4の月平均データから1月平均SST偏差を用いてSVD解析を行う。期間は1958年~2015年,領域はZ500が北太平洋,SSTが熱帯インド洋~太平洋。

時間関数(中央)と特異ベクトル(左:Z500,右:SST)

svd_sv


時間関数(中央)と同質相関マップ(左:Z500,右:SST)

svd_hom


時間関数(中央)と異質相関マップ(左:Z500,右:SST)

svd_het


通常は異質相関マップを示すことが多いようだ。時間関数に加えて異質相関マップ(あるいは同質相関マップ)を示すだけであれば,svdcovあるいはsvdstdを用いて計算できる。
解析の内容によっては特異ベクトル自体が必要となる場合もあるだろう。その場合にはsvdcov_svあるいはsvdstd_svを用いる。


スクリプト

load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/gsn_code.ncl"
load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/gsn_csm.ncl"
load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/contributed.ncl"
load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/shea_util.ncl"

begin

  y1      = 1958                    ; 最初の年
  y2      = 2015                    ; 最後の年
  nsvd    =    3                    ; 求めるモード数

  infile1 = "~/hgt.1958-2015.nc"    ; 変数1(Z500)のデータ
  infile2 = "~/sst.mnmean.v4.nc"    ; 変数2(SST)のデータ
  out     = "eps"                   ; 出力形式

  ;;; 変数1(Z500)のデータを読む
  in1     = addfile(infile1,"r")             ; 読み込むファイルを取得   
  time    = in1->time                        ; 時間の配列を取得
  YYYYMM  = cd_calendar(time,-1)             ; timeからYYYYMMの配列を作成
  t1      = ind(YYYYMM.eq.y1*100+1)          ; 1958年1月に対応するインデックスを取得
  t2      = ind(YYYYMM.eq.y2*100+12)         ; 2015年12月に対応するインデックスを取得
  hgt     = in1->hgt(t1:t2:12,{500},:,:)     ; Z500の値を取得。{500}で500hPa面を指定(高さ次元の中身をあらかじめ知っている)。
                                             ; 時間次元はt1からt2まで12おき
                                             ; これは1958年から2015年の1月の値のみ切り取ることに対応
  hgt     = dtrend_msg_n(ispan(y1,y2,1),hgt,True,False,0)
                                             ; 平均とトレンドの除去
  lat1    = in1->lat                         ; Z500に対応する緯度の配列を取得
  lon1    = in1->lon                         ; Z500に対応する経度の配列を取得
  hgt!0   = "time"                           ; hgtに座標をつけておく
  hgt!1   = "lat"
  hgt!2   = "lon"
  delete([/time,YYYYMM/])                    ; timeとYYYYMMの配列を消しておく
                                             ; (SSTデータの読み込みでも同じ変数名を使うから)

  ;;; 変数2(SST)のデータを上と同様にして読む
  in2     = addfile(infile2,"r")
  time    = in2->time
  YYYYMM  = cd_calendar(time,-1)
  t1      = ind(YYYYMM.eq.y1*100+1)
  t2      = ind(YYYYMM.eq.y2*100+12)
  sst     = in2->sst(t1:t2:12,:,:)
  sst     = dtrend_msg_n(ispan(y1,y2,1),sst,True,False,0)
  lat2    = in2->lat
  lon2    = in2->lon
  sst!0   = "time"
  sst!1   = "lat"
  sst!2   = "lon"
  delete([/time,YYYYMM/])

  ;;; SVD解析の下ごしらえをしましょう
  ;;; まずは変数1(Z500)に対して
  LAT1  = ind(lat1.ge.20.and.lat1.lt.89)      ; 変数1のSVDの領域(緯度範囲)を指定
  LON1  = ind(lon1.ge.105.and.lon1.le.255)    ;                 (経度範囲)
  var1  = hgt(lat|LAT1,lon|LON1,time|:)       ; Z500の必要な領域を切り出し
                                              ; ただし,時間と空間の次元の順番を入れ替える
  dim1  = dimsizes(var1)                      ; 各次元の大きさを左からNY1,NX1,NT1とする
  NY1   = dim1(0)
  NX1   = dim1(1)
  NT1   = dim1(2)
 ;;; 各グリッドの面積の重みをかける
 ;;; SVD解析では共分散行列の各成分に対して面積の重みをかけるべきなので,
 ;;; 偏差に対しては面積の平方根の重みをかけることに注意
  wgt1  = sqrt(cos(lat1(LAT1)*atan(1.)/45.))  ; √cos(緯度)の重み(緯度幅は一様)
  NW1   = dimsizes(wgt1)
  do j = 0, NW1-1
    var1(j,:,:) = var1(j,:,:) * wgt1(j)
  end do
  ;;; var1の形を変える。緯度経度の次元をまとめて,空間×時間の2次元配列とする。
  var1 := reshape(var1,(/NY1*NX1,NT1/))
  ;;; SVDの関数は未定義値が含まれているとうまく動かない。そこで,未定義値を取り除く必要がある。
  ;;; いま,(SSTデータの陸地のように)時間によらず同じ空間的位置に未定義値が含まれているとする
  NMS1  = ind(.not.ismissing(var1(:,0)))      ; 空間次元のうち未定義でない場所を取得
  var1 := var1(NMS1,:)                        ; 未定義でない値だけ切り出して配列を小さくする
                                              ; ここで定義したNMS1はあとでもとの大きさに戻すときに必要

  ;;; 同様に変数2(SST)に対しても下ごしらえをしましょう 
  LAT2  = ind(lat2.ge.-20.and.lat2.le.20)
  LON2  = ind(lon2.ge.30.and.lon2.le.290)
  var2  = sst(lat|LAT2,lon|LON2,time|:)
  dim2  = dimsizes(var2)
  NY2   = dim2(0)
  NX2   = dim2(1)
  NT2   = dim2(2)                             ; NT1と同じでなくてはならない 
  wgt2  = sqrt(cos(lat2(LAT2)*atan(1.)/45.))
  NW2   = dimsizes(wgt2)
  do j = 0, NW2-1
    var2(j,:,:) = var2(j,:,:) * wgt2(j)
  end do
  var2 := reshape(var2,(/NY2*NX2,NT2/))
  NMS2  = ind(.not.ismissing(var2(:,0)))
  var2 := var2(NMS2,:)

  ;;; SVDを実行。今回は特異ベクトルも同質・異質相関マップも両方求める
  ;;; まずはsvdcov_svを用いて特異ベクトルを計算
  var1_svc = new((/nsvd,dimsizes(NMS1)/),float)             ; 変数1(Z500)の特異ベクトル
  var2_svc = new((/nsvd,dimsizes(NMS2)/),float)             ; 変数2(SST)の特異ベクトル
  pcvar_a  = svdcov_sv(var1,var2,nsvd,var1_svc,var2_svc)    ; SVD!

  var1_hom = new((/nsvd,dimsizes(NMS1)/),float)             ; 変数1(Z500)の同質相関マップ
  var1_het = new((/nsvd,dimsizes(NMS1)/),float)             ; 変数2(SST)の同質相関マップ
  var2_hom = new((/nsvd,dimsizes(NMS2)/),float)             ; 変数1(Z500)の異質相関マップ
  var2_het = new((/nsvd,dimsizes(NMS2)/),float)             ; 変数2(SST)の異質相関マップ
  pcvar_b  = svdcov(var1,var2,nsvd,var1_hom,var1_het,var2_hom,var2_het)  : SVD!

  ;;; 同じSVDを行っているのでpcvar_aとpcvar_bは同一の値が入るはずである(attributeは異なる)
  ;;; pcvar_bのほうには,@akとして変数1の時間関数が,@bkとして変数2の時間関数が付随している。
  ;;; ただし,時間関数はnsvd×時間の長さの1次元配列であることに注意
  hgt_pc   = reshape(pcvar_b@ak,(/nsvd,NT1/))               ; 変数1(Z500)の時間関数
  sst_pc   = reshape(pcvar_b@bk,(/nsvd,NT2/))               ; 変数2(SST)の時間関数

  ;;; ちなみに,時間関数は元のデータを特異ベクトルへ射影したものであるから,下のように計算することができる。
  ;;;    hgt_pc   = var1_svc#var1
  ;;;    sst_pc   = var2_svc#var2
  ;;; ただし,SVDでは異なるモードの時間関数は直交しないので,
  ;;; 元のデータを時間関数へ射影することで特異ベクトルを求めることはできないので注意。

  hgt_pc@pcvar = pcvar_b                          ; 共分散寄与率
  sst_pc@pcvar = pcvar_b
  hgt_svc  = new((/nsvd,NY1*NX1/),float)          ; Z500の特異ベクトル(空間次元は未定義値も含む大きさで定義)
                                                  ; デフォルトでは全要素に未定義値が入る
  hgt_svc(:,NMS1) = var1_svc                      ; もとも未定義値でなかった場所へSVDで得られた特異ベクトルを代入
  hgt_svc := reshape(hgt_svc,(/nsvd,NY1,NX1/))    ; 空間次元を緯度×経度の2次元に戻す
  sst_svc  = new((/nsvd,NY2*NX2/),float)          ; SSTの特異ベクトル
  sst_svc(:,NMS2) = var2_svc
  sst_svc := reshape(sst_svc,(/nsvd,NY2,NX2/))
  hgt_hom  = new((/nsvd,NY1*NX1/),float)          ; Z500の同質相関マップ
  hgt_hom(:,NMS1) = var1_hom
  hgt_hom := reshape(hgt_hom,(/nsvd,NY1,NX1/))
  sst_hom  = new((/nsvd,NY2*NX2/),float)          ; SSTの同質相関マップ
  sst_hom(:,NMS2) = var2_hom
  sst_hom := reshape(sst_hom,(/nsvd,NY2,NX2/))
  hgt_het  = new((/nsvd,NY1*NX1/),float)          ; Z500の異質相関マップ
  hgt_het(:,NMS1) = var1_het
  hgt_het := reshape(hgt_het,(/nsvd,NY1,NX1/))
  sst_het  = new((/nsvd,NY2*NX2/),float)          ; SSTの異質相関マップ
  sst_het(:,NMS2) = var2_het
  sst_het := reshape(sst_het,(/nsvd,NY2,NX2/))
  delete([/var1,var2,var1_svc,var2_svc,var1_hom,var1_het,var2_hom,var2_het/])

  ;;; 重みの分だけ戻してあげる
  do j = 0, NW1-1
    hgt_svc(:,j,:) = hgt_svc(:,j,:) / wgt1(j)
  end do
  do j = 0, NW2-1
    sst_svc(:,j,:) = sst_svc(:,j,:) / wgt2(j)
  end do

  ;;; 描画に備えて各変数に座標を設定
  hgt_svc!0    = "svd"
  hgt_svc!1    = "lat_hgt"
  hgt_svc!2    = "lon_hgt"
  hgt_svc&svd  = ispan(1,nsvd,1)
  hgt_svc&lat_hgt = lat1(LAT1)
  hgt_svc&lon_hgt = lon1(LON1)
  copy_VarCoords(hgt_svc,hgt_hom)
  copy_VarCoords(hgt_svc,hgt_het)
  hgt_pc!0     = "svd"
  hgt_pc!1     = "year"
  hgt_pc&svd   = ispan(1,nsvd,1)
  hgt_pc&year  = ispan(y1,y2,1)
  sst_svc!0    = "svd"
  sst_svc!1    = "lat_sst"
  sst_svc!2    = "lon_sst"
  sst_svc&svd  = ispan(1,nsvd,1)
  sst_svc&lat_sst = lat2(LAT2)
  sst_svc&lon_sst = lon2(LON2)
  copy_VarCoords(sst_svc,sst_hom)
  copy_VarCoords(sst_svc,sst_het)
  sst_pc!0    = "svd"
  sst_pc!1    = "year"
  sst_pc&svd  = ispan(1,nsvd,1)
  sst_pc&year = ispan(y1,y2,1)

  ;;; 必ずしも必要ではないが,時間関数を規格化し,特異ベクトルを次元を持つ量に  
  do i = 0, nsvd-1
    hgt_svc(i,:,:) = hgt_svc(i,:,:)*stddev(hgt_pc(i,:))
    sst_svc(i,:,:) = sst_svc(i,:,:)*stddev(sst_pc(i,:))
    hgt_pc(i,:)    = hgt_pc(i,:)/stddev(hgt_pc(i,:))
    sst_pc(i,:)    = sst_pc(i,:)/stddev(sst_pc(i,:))
  end do

  ;;; ここからお絵描きの設定
  res1 = True                                  ; Z500の描画に用いるresource
  res1@gsnDraw          = False                ; gsn_csm_contour_map_ce実行時にDrawしない
  res1@gsnFrame         = False                ; gsn_csm_contour_map_ce実行時にFrame更新しない
  res1@cnFillOn         = True                 ; シェードを使う
  res1@cnLinesOn        = False                ; コンターは使わない
  res1@cnLineLabelsOn   = False                ; コンターラベルもない
  res1@cnInfoLabelOn    = False                ; コンター情報もない
  res1@lbLabelBarOn     = False                ; 各パネルごとにはカラーバーを描かない(今のところ)
                                               ; あとで一時的にTrueとなる
  res1@lbTitleOn        = True                 ; カラーバーにタイトルを付ける
  res1@lbTitlePosition  = "Right"              ; カラーバーのタイトルの位置
  res1@lbTitleDirection = "Across"             ; カラーバーのタイトルの向き
  res1@pmLabelBarOrthogonalPosF    = 0.3       ; カラーバーの位置を少し下にずらす
  res1@mpGeophysicalLineThicknessF = 2         ; 海岸線の太さ
  res1@gsnAddCyclic     = False                ; 経度方向に周期的ではない
  res1@tmXTOn           = False                ; 上側のx軸を用いない(目盛を消す)
  res1@cnLevelSelectionMode = "ManualLevels"   ; コンターレベルの設定を"ManualLevels"モードに
 
  ;;; 実はここまではSSTの描画に用いるresourceと共通。
  ;;; なのでSSTの描画に用いるresourceをres3としてコピー
  res3 = res1                         

  ;;; これ以降,個別の設定を行っていく
  ;;; Z500に対して
  res1@mpMinLatF                = 25.          ; 地図の南端
  res1@mpMaxLatF                = 90.          ;       北端
  res1@mpMinLonF                = 105.         ;       東端
  res1@mpMaxLonF                = 255.         ;       西端
  res1@mpCenterLonF             = 180.         ; 0°~360°の経度座標を持つ
  res1@cnMinLevelValF           = -75.         ; コンター最小値
  res1@cnMaxLevelValF           =  75.         ; コンター最大値
  res1@cnLevelSpacingF          =  15.         ; コンター間隔
  res1@lbTitleString            = "m"          ; カラーバーのタイトル
  res1@lbTitleFontHeightF       = 0.021        ; カラーバーのタイトルの文字の大きさ
  res1@lbLabelFontHeightF       = 0.021        ; カラーバーのラベルの文字の大きさ
  res1@gsnLeftString            = "Z500"       ; LeftStringの文字
  res1@gsnLeftStringFontColor   = "red"        ; LeftStringの文字の色
  res1@gsnLeftStringFontHeightF = 0.038        ; LeftStringの文字の大きさ
  res1@tmYLOn                   = True         ; 左側のy軸を使う
  res1@tmYLLabelsOn             = True         ; タテ軸のラベルは左側のy軸を使う
  res1@tmYROn                   = False        ; 右側のy軸を使わない
  res1@tmYRLabelsOn             = False        ; タテ軸のラベルは右側のy軸を使わない
  res1@tmXBLabelFontHeightF     = 0.025        ; ヨコ軸ラベルの文字の大きさ
  res1@tmYLLabelFontHeightF     = 0.025        ; タテ軸ラベルの文字の大きさ

  ;;; SSTに対して(基本的には同様で,異なる値を入れていく)
  res3@mpMinLatF                = -20.
  res3@mpMaxLatF                = 20.
  res3@mpMinLonF                = 30.
  res3@mpMaxLonF                = 290.
  res3@mpCenterLonF             = 180.
  res3@cnMinLevelValF           = -0.9
  res3@cnMaxLevelValF           =  0.9
  res3@cnLevelSpacingF          =  0.1
  res3@lbTitleString            = "~S~o~N~C"
  res3@lbTitleFontHeightF       = 0.008
  res3@lbLabelFontHeightF       = 0.008
  res3@gsnLeftString            = "SST"
  res3@gsnLeftStringFontColor   = "blue"
  res3@gsnLeftStringFontHeightF = 0.014
  res3@tmYLOn                   = False        ; 左側のy軸を使わない
  res3@tmYLLabelsOn             = False
  res3@tmYROn                   = True         ; 右側のy軸を使う
  res3@tmYRLabelsOn             = True
  res3@tmXBTickSpacingF         = 45
  res3@tmXBLabelFontHeightF     = 0.009
  res3@tmYLLabelFontHeightF     = 0.009
  res3@mpShapeMode              = "FreeAspect" ; 地図プロットの縦横比を可変に
  res3@vpWidthF                 = 0.3          ; 図の幅(attachしたとき横の長さが変わる)
                                               ; これに合わせて地図は縦長になる
  res3@tmXBTickSpacingF         = 45           ; 横軸の目盛間隔を45°に

  res2                          = True         ; 時間関数の描画に用いるresource
  res2@gsnDraw                  = False        ; gsn_csm_xy実行時にDrawしない
  res2@gsnFrame                 = False        ; gsn_csm_xy実行時にFrame更新しない
  res2@trXMinF                  = y1           ; ヨコ軸の範囲
  res2@trXMaxF                  = y2           ;
  res2@trYMinF                  = -3           ; タテ軸の範囲
  res2@trYMaxF                  =  3           ;
  res2@tmXTOn                   = False        ; 上側のx軸を用いない(目盛を消す)
  res2@vpHeightF                = 0.3
  res2@tmYRLabelsOn             = False        ; 右側のy軸にラベルを描かない
  res2@xyLineColors   = (/"red","blue"/)       ; 折れ線の色
  res2@xyDashPattern            = 0            ; 折れ線の線種(実線)
  res2@gsnYRefLine              = 0            ; y=0の線を引く
  res2@tmXBLabelFontHeightF     = 0.045        ; ヨコ軸ラベルの文字の大きさ
  res2@gsnLeftStringFontHeightF = 0.045        ; LeftStringの文字の大きさ
 
  pres                            = True       ; パネルプロットのresource
  pres@gsnPanelTop                = 0.9        ; パネルプロットの上10%に余白を作る
  pres@gsnPanelBottom             = 0.1        ; パネルプロットの下10%に余白を作る
  pres@gsnPanelYWhiteSpacePercent = 10         ; 各パネル間に10%の余白を挟む
  pres@gsnPanelLabelBar           = False      ; カラーバーを描かない(今のところ)
                                               ; あとでTrueになる
  pres@pmLabelBarWidthF           = 0.6        ; カラーバーの幅
  pres@pmLabelBarHeightF          = 0.05       ; カラーバーの高さ
  pres@lbLabelFontHeightF         = 0.012      ; カラーバーのラベルの文字の大きさ

  ;;; attachのresources
  attach_res1 = True
  attach_res2 = True
  attach_res3 = True

  ;;; 1枚目:時間関数と特異ベクトル
  wks   = gsn_open_wks(out,"svd_sv")
  plot1 = new(nsvd,graphic)
  plot2 = new(nsvd,graphic)
  plot3 = new(nsvd,graphic)
  dum   = new(nsvd,graphic)
  ;;; モードごとに描画
  do i = 0, nsvd-1
    ;; 最後のモードの時だけ,Z500とSSTそれぞれのカラーバーを描く
    if (i.eq.nsvd-1) then
      res1@lbLabelBarOn  = True
      res3@lbLabelBarOn  = True
    end if
    ;; 各パネルのタイトル,res2につけることで中央付近に位置するようになる
    ;; " ~C~ "は改行
    res2@gsnLeftString = "SVD "+sprinti("%1.1i",hgt_svc&svd(i)) + \
                         " : "+sprintf("%3.1f",hgt_pc@pcvar(i)) + "% ~C~ "
    ;; Z500の特異ベクトルを描く
    plot1(i) = gsn_csm_contour_map_ce(wks,hgt_svc(i,:,:),res1)
    ;; 時間関数を描く
    plot2(i) = gsn_csm_xy(wks,hgt_pc&year,(/hgt_pc(i,:),sst_pc(i,:)/),res2)
    ;; SSTの特異ベクトルを描く
    plot3(i) = gsn_csm_contour_map_ce(wks,sst_svc(i,:,:),res3)
    ;; SSTの特異ベクトルのグラフを時間関数のグラフにattach
    dum(i)   = gsn_attach_plots(plot2(i),plot3(i),attach_res2,attach_res3)
    ;; さらにそれをZ500の特異ベクトルのグラフにattach
    dum(i)   = gsn_attach_plots(plot1(i),plot2(i),attach_res1,attach_res2)
  end do
  gsn_panel(wks,plot1,(/nsvd,1/),pres)      ; パネルプロット
  delete([/wks,plot1,plot2,plot3,dum/])     ; 次の描画のために削除

  ;;; 同質・異質相関マップの描画に備えて一部変更
  res1@lbLabelBarOn     = False             ; 個々のパネルに対してはカラーバーを描かない
  res3@lbLabelBarOn     = False             ;
  pres@gsnPanelLabelBar = True              ; その代わり全体にカラーバーをつける
 
  res1@cnMinLevelValF   = -0.9              ; コンターの最大・最小・間隔も変更
  res1@cnMaxLevelValF   =  0.9              ; 相関係数なので-1から+1までです
  res1@cnLevelSpacingF  =  0.1
  res3@cnMinLevelValF   = -0.9
  res3@cnMaxLevelValF   =  0.9
  res3@cnLevelSpacingF  =  0.1

  ;;; 2枚目:時間関数と同質相関マップ
  wks   = gsn_open_wks(out,"svd_hom")
  plot1 = new(nsvd,graphic)
  plot2 = new(nsvd,graphic)
  plot3 = new(nsvd,graphic)
  dum   = new(nsvd,graphic)
  do i = 0, nsvd-1
    res2@gsnLeftString = "SVD "+sprinti("%1.1i",hgt_hom&svd(i)) + \
                         " : "+sprintf("%3.1f",hgt_pc@pcvar(i)) + "% ~C~ "
    plot1(i) = gsn_csm_contour_map_ce(wks,hgt_hom(i,:,:),res1)
    plot2(i) = gsn_csm_xy(wks,hgt_pc&year,(/hgt_pc(i,:),sst_pc(i,:)/),res2)
    plot3(i) = gsn_csm_contour_map_ce(wks,sst_hom(i,:,:),res3)
    dum(i)   = gsn_attach_plots(plot2(i),plot3(i),attach_res2,attach_res3)
    dum(i)   = gsn_attach_plots(plot1(i),plot2(i),attach_res1,attach_res2)
  end do
  gsn_panel(wks,plot1,(/nsvd,1/),pres)
  delete([/wks,plot1,plot2,plot3,dum/])

  ;;; 3枚目:時間関数と異質相関マップ
  wks   = gsn_open_wks(out,"svd_het")
  plot1 = new(nsvd,graphic)
  plot2 = new(nsvd,graphic)
  plot3 = new(nsvd,graphic)
  dum   = new(nsvd,graphic)
  do i = 0, nsvd-1
    res2@gsnLeftString = "SVD "+sprinti("%1.1i",hgt_het&svd(i)) + \
                         " : "+sprintf("%3.1f",hgt_pc@pcvar(i)) + "% ~C~ "
    plot1(i) = gsn_csm_contour_map_ce(wks,hgt_het(i,:,:),res1)
    plot2(i) = gsn_csm_xy(wks,hgt_pc&year,(/hgt_pc(i,:),sst_pc(i,:)/),res2)
    plot3(i) = gsn_csm_contour_map_ce(wks,sst_het(i,:,:),res3)
    dum(i)   = gsn_attach_plots(plot2(i),plot3(i),attach_res2,attach_res3)
    dum(i)   = gsn_attach_plots(plot1(i),plot2(i),attach_res1,attach_res2)
  end do
  gsn_panel(wks,plot1,(/nsvd,1/),pres)
  delete([/wks,plot1,plot2,plot3,dum/])
 
end

トップ   編集 凍結 添付 名前変更   新規