Lindsay Smithのチュートリアルを使用したRでのPCAの段階的な実装


13

私はRでLindsay I Smithによる優れたPCAチュートリアルを行っており、最終段階で動けなくなっています。以下のRスクリプトは、元のデータがPCA1軸に沿った直線プロットを生成する(この場合は単一の)主成分から再構築される段階(p.19)に連れて行きます(データが与えられた場合) 2つのディメンションのみがあり、2番目のディメンションは意図的に削除されています)。

d = data.frame(x=c(2.5,0.5,2.2,1.9,3.1,2.3,2.0,1.0,1.5,1.1),
               y=c(2.4,0.7,2.9,2.2,3.0,2.7,1.6,1.1,1.6,0.9))

# mean-adjusted values 
d$x_adj = d$x - mean(d$x)
d$y_adj = d$y - mean(d$y)

# calculate covariance matrix and eigenvectors/values
(cm = cov(d[,1:2]))

#### outputs #############
#          x         y
# x 0.6165556 0.6154444
# y 0.6154444 0.7165556
##########################

(e = eigen(cm))

##### outputs ##############
# $values
# [1] 1.2840277 0.0490834
#
# $vectors
#          [,1]       [,2]
# [1,] 0.6778734 -0.7351787
# [2,] 0.7351787  0.6778734
###########################


# principal component vector slopes
s1 = e$vectors[1,1] / e$vectors[2,1] # PC1
s2 = e$vectors[1,2] / e$vectors[2,2] # PC2

plot(d$x_adj, d$y_adj, asp=T, pch=16, xlab='x', ylab='y')
abline(a=0, b=s1, col='red')
abline(a=0, b=s2)

ここに画像の説明を入力してください

# PCA data = rowFeatureVector (transposed eigenvectors) * RowDataAdjust (mean adjusted, also transposed)
feat_vec = t(e$vectors)
row_data_adj = t(d[,3:4])
final_data = data.frame(t(feat_vec %*% row_data_adj)) # ?matmult for details
names(final_data) = c('x','y')

#### outputs ###############
# final_data
#              x           y
# 1   0.82797019 -0.17511531
# 2  -1.77758033  0.14285723
# 3   0.99219749  0.38437499
# 4   0.27421042  0.13041721
# 5   1.67580142 -0.20949846
# 6   0.91294910  0.17528244
# 7  -0.09910944 -0.34982470
# 8  -1.14457216  0.04641726
# 9  -0.43804614  0.01776463
# 10 -1.22382056 -0.16267529
############################

# final_data[[1]] = -final_data[[1]] # for some reason the x-axis data is negative the tutorial's result

plot(final_data, asp=T, xlab='PCA 1', ylab='PCA 2', pch=16)

ここに画像の説明を入力してください

これは私が得た限りであり、これまでのところすべて問題ありません。しかし、最終プロット(PCA 1に起因する分散)でデータがどのように取得されるかはわかりません。スミスは次のようにプロットします。

ここに画像の説明を入力してください

これは私が試したものです(元の手段の追加を無視します):

trans_data = final_data
trans_data[,2] = 0
row_orig_data = t(t(feat_vec[1,]) %*% t(trans_data))
plot(row_orig_data, asp=T, pch=16)

..そして間違った:

ここに画像の説明を入力してください

..行列の乗算で何らかの形でデータの次元を失ったためです。ここで何が間違っているのか、私はとても感謝しています。


*編集*

これが正しい式なのかな?

row_orig_data = t(t(feat_vec) %*% t(trans_data))
plot(row_orig_data, asp=T, pch=16, cex=.5)
abline(a=0, b=s1, col='red')

(a)rowVectorFeature必要な次元(PCA1の固有ベクトル)に減らす必要があることを理解し、(b)PCA1のablineと一致しないため、少し混乱しています。

ここに画像の説明を入力してください

どんな意見でも大歓迎です。


s1y/バツバツ/y

:主要主成分から元のデータを復元について、この新しいスレッド参照stats.stackexchange.com/questions/229092を
アメーバは、モニカを復活させる

回答:


10

あなたは非常に近くにいて、Rで行列を操作する際に微妙な問題に巻き込まれました。私はあなたfinal_dataからやり直し、独立して正しい結果を得ました。それからあなたのコードを詳しく見てみました。あなたが書いた長い話を短くカットする

row_orig_data = t(t(feat_vec[1,]) %*% t(trans_data))

書いていたら大丈夫だっただろう

row_orig_data = t(t(feat_vec) %*% t(trans_data))

代わりに(trans_data2番目の固有ベクトルに投影された部分をゼロにしたため)。あなたが掛けようとしていたので2×12×10t(feat_vec[1,])1×2row_orig_data = t(as.matrix(feat_vec[1,],ncol=1,nrow=2) %*% t(trans_data))non-conformable arguments

row_orig_data = t(as.matrix(feat_vec[1,],ncol=1,nrow=2) %*% t(trans_data)[1,])

2×11×10final_data20=2×10row_orig_data12=2×1+1×10

バツYT=YTバツTt(t(p) %*% t(q)) = q %*% t

バツ/yy/バツ


書く

d_in_new_basis = as.matrix(final_data)

その後、データを元の状態に戻すために必要です

d_in_original_basis = d_in_new_basis %*% feat_vec

を使用して、2番目のコンポーネントに沿って投影されるデータの部分をゼロにすることができます。

d_in_new_basis_approx = d_in_new_basis
d_in_new_basis_approx[,2] = 0

その後、以前のように変換できます

d_in_original_basis_approx = d_in_new_basis_approx %*% feat_vec

これらを同じプロットにプロットし、主成分ラインを緑色で表示すると、近似がどのように機能したかがわかります。

plot(x=d_in_original_basis[,1]+mean(d$x),
     y=d_in_original_basis[,2]+mean(d$y),
     pch=16, xlab="x", ylab="y", xlim=c(0,3.5),ylim=c(0,3.5),
     main="black=original data\nred=original data restored using only a single eigenvector")
points(x=d_in_original_basis_approx[,1]+mean(d$x),
       y=d_in_original_basis_approx[,2]+mean(d$y),
       pch=16,col="red")
points(x=c(mean(d$x)-e$vectors[1,1]*10,mean(d$x)+e$vectors[1,1]*10), c(y=mean(d$y)-e$vectors[2,1]*10,mean(d$y)+e$vectors[2,1]*10), type="l",col="green")

ここに画像の説明を入力してください

あなたが持っていたものに巻き戻しましょう。この行は大丈夫でした

final_data = data.frame(t(feat_vec %*% row_data_adj))

feat_vec %*% row_data_adjY=STバツSバツYYバツYバツ 2番目の固有ベクトルで重み付けされた。

その後、あなたは持っていました

trans_data = final_data
trans_data[,2] = 0

これは問題ありません。2番目のコンポーネントに沿って投影されるデータの部分をゼロにするだけです。それがうまくいかないところは

row_orig_data = t(t(feat_vec[1,]) %*% t(trans_data))

Y^Ye1t(feat_vec[1,]) %*% t(trans_data)e1Y^

2×12×10Y^Yy1e1y1e1y1e1


TooToneのおかげで、これは非常に包括的であり、最終段階でのマトリックス計算とfeatureVectorの役割に関する私の理解のあいまいさを解決します。
ジオセオ14年

すごい :)。この質問に答えたのは、現在SVD / PCAの理論を研究しており、それが例でどのように機能するかを把握したかったからです。すべてのマトリックス計算を行った後、Rの問題であることがわかったので少し驚いたので、マトリックスの側面も評価してくれて嬉しいです。
TooTone 14年

4

あなたは正しい考えを持っていると思いますが、Rの厄介な機能につまずいたのです。

trans_data = final_data
trans_data[,2] = 0
row_orig_data = t(t(feat_vec[1,]) %*% t(trans_data))
plot(row_orig_data, asp=T, pch=16)

本質的final_dataには、共分散行列の固有ベクトルによって定義された座標系に関する元の点の座標が含まれます。したがって、元の点を再構成するには、各固有ベクトルに関連する変換済み座標を乗算する必要があります。たとえば、

(1) final_data[1,1]*t(feat_vec[1,] + final_data[1,2]*t(feat_vec[2,])

これにより、最初の点の元の座標が得られます。あなたの質問では、2番目のコンポーネントをゼロに正しく設定しました。trans_data[,2] = 0。その後、(すでに編集したように)計算する場合

(2) row_orig_data = t(t(feat_vec) %*% t(trans_data))

すべてのポイントに対して式(1)を同時に計算します。最初のアプローチ

row_orig_data = t(t(feat_vec[1,]) %*% t(trans_data))

Rは次のディメンション属性を自動的に削除するため、異なるものを計算し、機能するだけです。 feat_vec[1,]。したがって、これは行ベクトルではなく、列ベクトルとして扱われます。後続の転置はそれを再び行ベクトルにし、それが少なくとも計算でエラーが発生しない理由ですが、数学を調べてみると、(1)とは異なることがわかります。一般に、行列の乗算では、dropパラメータによって達成できる次元属性のドロップを抑制することをお勧めします。たとえば、feat_vec[1,,drop=FALSE]

y/バツ

s1 = e$vectors[2,1] / e$vectors[1,1] # PC1
s2 = e$vectors[2,2] / e$vectors[1,2] # PC2

ゲオルグ、どうもありがとう。PCA1の勾配については正しいです。drop=F引数についても非常に役立つヒント。
ジオセオ14年

4

この演習を探索した後、Rでより簡単な方法を試すことができます。PCAを実行するための2つの一般的な機能がprincompありprcompます。このprincomp関数は、演習で行ったように固有値分解を行います。このprcomp関数は、特異値分解を使用します。どちらの方法でもほぼ同じ結果が得られます。この回答では R の違いについて説明しますが、この回答では数学について説明します。(この投稿に統合されたコメントについては、TooToneに感謝します。)

ここでは、Rの演習を再現するために両方を使用しますprincomp

d = data.frame(x=c(2.5,0.5,2.2,1.9,3.1,2.3,2.0,1.0,1.5,1.1), 
               y=c(2.4,0.7,2.9,2.2,3.0,2.7,1.6,1.1,1.6,0.9))

# compute PCs
p = princomp(d,center=TRUE,retx=TRUE)

# use loadings and scores to reproduce with only first PC
loadings = t(p$loadings[,1]) 
scores = p$scores[,1] 

reproduce = scores %*% loadings  + colMeans(d)

# plots
plot(reproduce,pch=3,ylim=c(-1,4),xlim=c(-1,4))
abline(h=0,v=0,lty=3)
mtext("Original data restored using only a single eigenvector",side=3,cex=0.7)

biplot(p)

ここに画像の説明を入力してください ここに画像の説明を入力してください

2番目の使用prcomp

d = data.frame(x=c(2.5,0.5,2.2,1.9,3.1,2.3,2.0,1.0,1.5,1.1), 
               y=c(2.4,0.7,2.9,2.2,3.0,2.7,1.6,1.1,1.6,0.9))

# compute PCs
p = prcomp(d,center=TRUE,retx=TRUE)

# use loadings and scores to reproduce with only first PC
loadings = t(p$rotation[,1])
scores = p$x[,1]

reproduce = scores %*% loadings  + colMeans(d)

# plots
plot(reproduce,pch=3,ylim=c(-1,4),xlim=c(-1,4))
abline(h=0,v=0,lty=3)
mtext("Original data restored using only a single eigenvector",side=3,cex=0.7)

biplot(p)

ここに画像の説明を入力してください ここに画像の説明を入力してください

明らかに記号は反転していますが、バリエーションの説明は同等です。


ありがとうmrbcuda。あなたのバイプロットはリンジー・スミスのものと同じように見えるので、彼/彼女は12年前に同じ方法を使用したと思います!いくつかのより高いレベルのメソッドも知っていますが、あなたが正しく指摘しているように、これは基礎となるPCA数学を明示的にするための演習です。
ジオセオ14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.