t_kahi’s blog

KNIMEやCellProfiler、創薬に関する記事と,日々のメモです

【KNIME】主成分分析(PCA)ノードを使って,主成分スコア・固有ベクトル・累積寄与率を出す

こんばんは,@PKです.

今日はKNIMEの主成分分析(Principal Component Analysis : PCA)のノードを使って,主成分スコアや固有ベクトル,累積寄与率を算出してみます.

以前からKNIMEのPCAノードを使用していたのですが,累積寄与率などが直接出てこないので,今回まとめてみました.
また,おまけですが,PCAノードを使わずに「R snippet」でコードを書いて同様の結果を出力してみます.

PCAとは

ご存知の方も多いと思います…

主成分分析(しゅせいぶんぶんせき、英: principal component analysis; PCA)は、相関のある多数の変数から相関のない少数で全体のばらつきを最もよく表す主成分と呼ばれる変数を合成する多変量解析の一手法。データの次元を削減するために用いられる。 https://ja.wikipedia.org/wiki/%E4%B8%BB%E6%88%90%E5%88%86%E5%88%86%E6%9E%90

PCAについては,ここではあまり詳しくは触れませんが,自分が勉強した際にとてもわかりやすくまとめられていた記事を以下に示します.

データ解析・マイニングとR言語

主成分分析の考え方 | Logics of Blue

意味がわかる主成分分析 - Qiita

主成分分析(PCA)の累積寄与率で見る特徴量エンジニアリング【python】 - ギークなエンジニアを目指す男

Rのprcomp関数で主成分分析

まずKNIMEのノードの説明をする前には,Rのprcomp関数でPCAを行い,取得できるデータを確認します.

題材としては一般的なirisのデータを使用しました.

prcomp関数でPCAを行った結果に対して,summary関数を使うと,標準偏差(Standard deviation),寄与率(Proportion of Variance ),累積寄与率(Cumulative Proportion)がそれぞれ表示されます.

> column_list <- c("Sepal.Length","Sepal.Width","Petal.Length","Petal.Width")
> iris.pc<-prcomp(iris[, column_list]) 
> summary(iris.pc)
Importance of components:
                          PC1     PC2    PC3     PC4
Standard deviation     2.0563 0.49262 0.2797 0.15439
Proportion of Variance 0.9246 0.05307 0.0171 0.00521
Cumulative Proportion  0.9246 0.97769 0.9948 1.00000

また,主成分分析の結果(主成分スコア)はiris.pc$xで表示することができます.

> head(iris.pc$x)
           PC1        PC2         PC3          PC4
[1,] -2.684126 -0.3193972  0.02791483  0.002262437
[2,] -2.714142  0.1770012  0.21046427  0.099026550
[3,] -2.888991  0.1449494 -0.01790026  0.019968390
[4,] -2.745343  0.3182990 -0.03155937 -0.075575817
[5,] -2.728717 -0.3267545 -0.09007924 -0.061258593
[6,] -2.280860 -0.7413304 -0.16867766 -0.024200858

主成分(固有ベクトル)を知りたい場合は,iris.pc$rotationで表示することができます.

> iris.pc$rotation
                     PC1         PC2         PC3        PC4
Sepal.Length  0.36138659 -0.65658877  0.58202985  0.3154872
Sepal.Width  -0.08452251 -0.73016143 -0.59791083 -0.3197231
Petal.Length  0.85667061  0.17337266 -0.07623608 -0.4798390
Petal.Width   0.35828920  0.07548102 -0.54583143  0.7536574

KNIMEでもこの辺の値を取得できれば,取り合えずRのPCAと同じことができていることがわかります.

KNIME Workflowの概要

それでは,今回のKNIME Workflowの概要を以下に示します.
f:id:t_kahi:20190612193408p:plain

上青点線の部分で,PCAノードを使って,主成分分析を行っています.
また,下赤点線の部分はおまけでRノードを使った主成分分析です. Rノードで同じ操作を行うことで,KNIMEのPCAノードの理解が深まったので,同じ操作を行っています.

KNIMEのPCAノードを使って主成分分析

それでは,本題です. KNIMEでPCAをする際は,以下のノードを使用します.

PCA — NodePit
PCA Compute — NodePit
PCA Apply — NodePit

「PCA」と「PCA Compute」と「PCA Apply」何が違うの???と自分は最初に思いました.「PCA」は指定されたデータに対してPCAを行い,主成分スコアだけを表示するノードです.
「PCA Compute」も同様にデータに対するPCAを行いますが,ここではモデルを作るだけで,実際には「PCA Apply」によってPCAを実行します.

Components of PCA dimensions? - KNIME Analytics Platform - KNIME Community Forum
この記事でも言われていますが,「PCA」だけだと固有ベクトルや累積寄与率の数字は出てこないので,「PCA Compute」と「PCA Apply」を使用するほうが望ましいと思います.

KNIME WorkflowのPCAノードに関する部分について,以下に詳細を示します.
f:id:t_kahi:20190612200132p:plain

「PCA applay」と「PCA compute」については,主成分分析を行う列を選んで実行するだけです.
「PCA compute」では主成分をいくつにするか選択することができます.
f:id:t_kahi:20190612200443p:plain

「PCA Compute」を実行すると下左表のような主成分スコアを得ることができました.
「Color Manager」で種ごとに色を付けて「Scatter Plot」で可視化した図が下右図です.

f:id:t_kahi:20190612200733p:plain

続いて固有ベクトルや累積寄与率ですが,こちらは「PCA Compute」の2番目のポート(Spectral Decomposition)からデータを得ることができます.
f:id:t_kahi:20190612201451p:plain

固有ベクトルについては,「Column Filter」で固有値を除いて,「Transpose」で行列を入れ替えて,カラム名をRと同じく「"PC1","PC2","PC3","PC4"」そろえると,iris.pc$rotationの結果と一致しました.

f:id:t_kahi:20190612202547p:plain

一方で,累積寄与率については直接は出てこないようですので,少し計算をして算出します.
まず,「Math Formula」で固有値から寄与率を計算します.

$eigenvalue$/COL_SUM($eigenvalue$)

f:id:t_kahi:20190612204114p:plain

続いて,「Moving Aggregation」というノードを使って,数値を足し合わせます.(このノード,知らなかったので勉強になりました.)
Moving Aggregation — NodePit その結果,累積寄与率を算出することができました!
f:id:t_kahi:20190612203805p:plain

おまけ(Rノードで主成分分析)

おまけで,Rノードで同じように主成分スコア・固有ベクトル・累積寄与率を出したので,簡単に示しておきます.
f:id:t_kahi:20190612204316p:plain

主成分スコアについては,iris.pc$xを出力しました.

#R snippet
column_list <- c("Sepal.Length","Sepal.Width","Petal.Length","Petal.Width")
iris.pc<-prcomp(knime.in[, column_list]) 
Species <- as.character(knime.in$"Species")
summary(iris.pc) 
results<-data.frame(Species,iris.pc$x)
knime.out <- results

続いて,固有ベクトルについてはiris.pc$rotationで求めました.

#R snippet
column_list <- c("Sepal.Length","Sepal.Width","Petal.Length","Petal.Width")
iris.pc<-prcomp(knime.in[, column_list]) 
Species <- as.character(knime.in$"Species")
summary(iris.pc)
results <- data.frame(iris.pc$rotation)
knime.out <- results

主成分スコアと固有ベクトルを出力するのは,R素人の自分でも簡単でした.

続いて累積寄与率についてですが,以下のように出力しました.

#R snippet
column_list <- c("Sepal.Length","Sepal.Width","Petal.Length","Petal.Width")
iris.pc<-prcomp(knime.in[, column_list]) 
Species <- as.character(knime.in$"Species")
summary(iris.pc)
stats:::print.summary.princomp
vars <- iris.pc$sdev^2
vars <- vars/sum(vars)
Cumulative_Proportion <- cumsum(vars)
name <- names(data.frame(summary(iris.pc)$rotation))
names(Cumulative_Proportion) <- name
knime.out <- data.frame(Cumulative_Proportion)

当たり前かもしれないのですが,Summary関数で出てきたCumulative Proportionは,そのままでは取ってこれません(気づくのに時間がかかった…) 同じ事を質問している方がいて...
pca - How to get "proportion of variance" vector from princomp in R - Stack Overflow

Its calculated in the print method, and is not returned. Look at stats:::print.summary.princomp ti see where it is generated.

stats:::print.summary.princompを実行して確認をすると,ちゃんと書いてありました.

> stats:::print.summary.princomp
function (x, digits = 3L, loadings = x$print.loadings, cutoff = x$cutoff, 
    ...) 
{
    vars <- x$sdev^2
    vars <- vars/sum(vars)
    cat("Importance of components:\n")
    print(rbind(`Standard deviation` = x$sdev, `Proportion of Variance` = vars, 
        `Cumulative Proportion` = cumsum(vars)))
    if (loadings) {
        cat("\nLoadings:\n")
        cx <- format(round(x$loadings, digits = digits))
        cx[abs(x$loadings) < cutoff] <- strrep(" ", nchar(cx[1, 
            1], type = "w"))
        print(cx, quote = FALSE, ...)
    }
    invisible(x)
}
<bytecode: 0x0000017dc9a49250>
<environment: namespace:stats>
> 

というわけで,標準偏差から計算をして求めました.

おわりに

主成分分析(PCA)ノードを使って,主成分スコア・固有ベクトル・累積寄与率を出すことを紹介しました.
RノードでPCAで何をやっているのか確認してからKNIMEのノードを使うと,より意味が分かって勉強になると感じたので,今後もRの勉強は続けていきます.