補遺2 ぱらぱらめくる『Functional Data Analysis with R and MATLAB』

  • こちらの記事で、関数解析について勉強した
  • 2次元に時刻パラメタで記録された曲線の解析をしてみよう
  • テキストブックのサンプルコードはこちら
  • データはこんな感じ

library(fda)
# handwrit は"fda"の筆記文字の情報
# 全1401点、20個のfda筆記サンプル、2次元平面の文字
dim(handwrit)

plot(handwrit[,1,],type="l",xlim=range(handwrit),ylim=range(handwrit))
for(i in 2:20){
	points(handwrit[,i,],type="l",col=i)
}
  • 単純に20本のカーブの「平均カーブ」を作って描いてみる

simple.mean <- apply(handwrit,c(1,3),mean)
plot(simple.mean,type="l")
  • 20本のカーブの特徴(癖)を取り出す
    • 手順としては、適当に関数の線形和に分解し、それを分散に着目して、成分の大きい方から取り出してやる
    • すべての分散軸を取り出せば元通りの曲線を表すが、寄与度の低い分を捨てれば、次元を落とせる
  • まずは、基底関数を少なくしてやってみる(全部の情報は取り出せない)(赤は単純な20本の平均、黒が、平滑化した上での平均、情報が少なすぎて、超いい加減なサインみたいに見えます)

fdatime <- seq(0,1,len=dim(handwrit)[1])
fdarange <- c(0,1)
fdabasis <- create.bspline.basis(fdarange,4,3)
fdafd <- smooth.basis(fdatime,handwrit,fdabasis)$fd

fdameanfd  = mean(fdafd)
fdameanmat = eval.fd(fdatime, fdameanfd)

plot(fdameanmat[,1,],type="l",xlim=range(handwrit),ylim=range(handwrit))
points(simple.mean,type="l",col=2)
    • じゃあ、関数はいくつにすればよいかを考えてみる。階数(?)は関数の数を上限とするのでひとまず3から200でやってみる

plot(simple.mean,type="l",xlim=range(handwrit),ylim=range(handwrit))

n.f <- 3:200
diff.from.simple.mean <- rep(0,length(n.f))
for(i in 1:length(n.f)){
	fdabasis <- create.bspline.basis(fdarange,n.f[i],3)
	fdatime <- seq(0,1,len=dim(handwrit)[1])
	fdafd <- smooth.basis(fdatime,handwrit,fdabasis)$fd

	fdameanfd  = mean(fdafd)
	fdameanmat = eval.fd(fdatime, fdameanfd)
	points(fdameanmat[,1,],type="l",col=i)
	diff.from.simple.mean[i] <- sum((fdameanmat[,1,]-simple.mean)^2)
}
    • 単純な平均との残差平方和がだんだん0になっていくことを図で示す

plot(n.f,diff.from.simple.mean)
    • どうやって、基底の数を選んだり、階数(?)を決めたりすればよいか…。いろいろやってみて、なるべく少ない基底と階数で、「シンプル平均」とそこそこ近いのを選べばよいか…
    • 今はその決め方はスキップして、テキストブックが選んだ、105,6を使ってみる。一致しすぎて、重ねてプロットしていることがわからないくらいです

fdabasis <- create.bspline.basis(fdarange,105,6)
fdatime <- seq(0,1,len=dim(handwrit)[1])
fdafd <- smooth.basis(fdatime,handwrit,fdabasis)$fd

fdameanfd  = mean(fdafd)
fdameanmat = eval.fd(fdatime, fdameanfd)
plot(simple.mean,type="b",xlim=range(handwrit),ylim=range(handwrit))

points(fdameanmat[,1,],type="l",col=i)
    • 次に、どのくらいのトップ関数を採用するべきかを判断するために、"PCA"のeigenvaluesを計算する。
      • 採用数を仮に5とする(この値に何を与えても、eigenvaluesは結局全部出るので、ここではnharmの値は適当でよい
      • 念のため異なるnharmの値でeigenvaluesを出して、等しいことを確認しておく
nharm <- 5
fdapcaList <- pca.fd(fdafd,nharm)
fdaeig <- fdapcaList$values
nharm2 <- 50
fdapcaList2 <- pca.fd(fdafd,nharm2)
fdaeig2 <- fdapcaList2$values

plot(fdaeig,fdaeig2) # 同じ個数の値が、等しい値で返ってくる
    • 意味のある成分数を選ぶ
      • あからさまに意味のある・ないで分けることができる。さらに、意味のある19成分を、対数スケールで直線に載るか、それよりもさらに上に上がるかで見ると、上位3個は、残りの16個が作る直線の上に来るので、特に異議が強い(と本では述べて)、上位3つを選んでいた。19ではだめなのか(増やしたところで、増やしたほどの改善がない、ということなのだろう)とは思うが、ここもテキストブックの通り3つをとっておこう
      • この19と言う数字は、サンプル数(筆記カーブ数)から1引いたもの。通常のPCAだと、標本数に比べて、値の列・次元が小さく、その場合には、その次元の数だけのeigenvaluesが出るが、関数解析の場合は、「標本数」「標本ごとの観測数」、その積が「観測値」になってくる。でも「観測しているのは、標本数」。そんなとき、観測値すべて分のeigenvaluesが出るわけはなく、たいては、「標本ごとの観測数」が多いので、0でないeigenvaluesは標本数に縛られる。今、分散を考えるときに「平均カーブ」を差し引いているから(たぶん、それが理由)、「標本数ー1」個のeigenvaluesが出る。今回の場合は20-1=19。となると、19個全部を使うのでは、情報を圧縮していないので、絞りましょう。それが「対数をとってその直線から外れているか」で判断しました、と言うのが、上の3の決め方のこと

par(mfcol=c(1,2))
plot(log(fdaeig.))
# 意味のあるのは、
abline(v=19)
# 意味のあるところを取り出すと
plot(log(fdaeig.[1:19]))
par(mfcol=c(1,1))
nharm <- 3
fdapcaList <- pca.fd(fdafd,nharm)
plot.pca.fd(fdapcaList)
fdarotpcaList <- varmx.pca.fd(fdapcaList)
plot.pca.fd(fdarotpcaList)
    • PCAなので、「曲線のサンプルセット」に対して「中心」と、「中心からのずれ」がある
      • 「中心」というのは、平滑化した後の曲線セットの平均

# 平均した関数オブジェクト
fdameanfd  = mean(fdafd)
# 平均曲線の関数オブジェクトから、座標を取り出す
fdameanmat = eval.fd(fdatime, fdameanfd)
plot(fdameanmat[,1,])
    • ここで「平滑化した曲線の平均としての曲線」というのは、平滑化した曲線が基底関数の係数ベクトルで表されているときに、その係数の平均を出すこと。やってみる
tmp.mean <- apply(fdafd$coefs,c(1,3),mean)
tmp.mean-fdameanfd$coefs[,1,] # すべて0になる
    • 「中心からのずれ」は「曲線にそって」全体にある。それぞれの箇所でのずれをつなぐと、曲線になるから、それを図示することもできる

# PCA解析から、分散曲線情報を関数オブジェクトとして取り出し
harmfd  = fdarotpcaList$harm
# それを座標化する
# nharm個の要素だけに集約することにしたから、以下のように1,2,3の曲線が得られ、4以上はない
harmmat = eval.fd(fdatime, harmfd)
par(mfcol=c(2,2))
plot(harmmat[,1,])
plot(harmmat[,2,])
plot(harmmat[,3,])
par(mfcol=c(1,1))
    • 結局、pca.fd()の出力は
      • harmonicsとよばれる、eigenfunctionsが、指定して個数。これは関数なので関数オブジェクトのリストとして返る
      • valuesはeigenvaluesただし、harmonicsの数ではなくて、全eigenvaluesが返る
      • scoresが、観測標本ごとの、harmonicsの重み係数を納めた行列
      • meanfdは「平均カーブ」の関数オブジェクト