コンテンツにスキップ

単元 3:次元削減 — PCA(主成分分析)

  • StandardScaler でデータを標準化し、変数のスケール差をならせる
  • sklearn.decomposition.PCA で高次元データを 2 次元・3 次元に投影できる
  • explained_variance_ratio_ で各主成分の寄与率を読める
  • 主成分得点の散布図から特徴を読み取り、主成分の意味を自分の言葉で説明できる

高次元データを「眺める」ための工夫

Section titled “高次元データを「眺める」ための工夫”

5 教科の試験データ、数十個の特徴量を持つ顧客データ、何百次元もの遺伝子発現データ。実データは 2 次元の散布図で見られないほど次元が高い ことが多くあります。

人間が一目で構造を把握できるのは 2 次元(散布図)か、せいぜい 3 次元まで。だから「高次元データを、できるだけ情報を保ったまま 2 次元に投影できないか」という発想が出てきます。これを実現する代表的な手法が PCA(主成分分析、Principal Component Analysis) です。

PCA は線形代数の手法ですが、本コースでは数学的な導出には踏み込まず、「何のためにあるか」「どう使うか」「結果をどう解釈するか」を中心に扱います。

PCA は、データの 分散(ばらつき)が最大になる方向(軸) を順に見つけ、その軸へデータを投影します。

  • 第 1 主成分:データのばらつきが最も大きい方向
  • 第 2 主成分:第 1 主成分に直交する方向のうち、次にばらつきが大きい方向
  • 第 3 主成分:これまでの主成分すべてに直交する方向のうち、次にばらつきが大きい方向 ……

「分散が大きい方向」とは、データを区別する力が強い方向 と言い換えられます。試験データなら「総合点が高い人と低い人をきれいに分ける」のが第 1 主成分、「文系寄りと理系寄りを分ける」のが第 2 主成分、というイメージです。

PCA をかける前に データの標準化(平均 0、標準偏差 1 への変換)が必要になるケースがあります。

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scores_scaled = scaler.fit_transform(scores)

PCA は分散の大きい方向を優先するため、変数のスケールが揃っていないと 「単位が大きい変数」が結果を支配してしまいます

  • 「年齢(10〜90)」と「年収(300〜5000)」を一緒に PCA にかけると、年収のスケールが圧倒的に大きいので、第 1 主成分はほぼ「年収方向」になってしまう
  • 一方、試験の点数のように すべて同じ単位(0〜100 点) なら、標準化は省いてもよい

迷ったときは標準化しておくのが安全です。本コースでも基本的に標準化を入れた流れで進めます。

scikit-learn の PCA クラスを使います:

from sklearn.decomposition import PCA
pca = PCA(n_components=3)
principal_components = pca.fit_transform(scores_scaled)
  • n_components=3 — 取り出す主成分の数(次元削減後の次元数)
  • fit_transform() — 「学習」と「変換」を一気に行うショートカット

返り値の principal_components元データの各行を主成分空間に射影した値(主成分得点) の 2 次元配列です。50 名分のデータなら (50, 3) の形になります。

各主成分がデータの分散をどれだけ説明するかを 寄与率 と呼びます。explained_variance_ratio_ 属性で取り出せます:

import numpy as np
print(pca.explained_variance_ratio_)
# 例: [0.555, 0.383, 0.048]
print(np.cumsum(pca.explained_variance_ratio_))
# 例: [0.555, 0.938, 0.986]

この例なら、

  • 第 1 主成分で 55.5%、第 2 主成分で 38.3%、第 3 主成分で 4.8% の分散を説明している
  • 上位 2 主成分の合計(累積寄与率)で 93.8%、上位 3 主成分で 98.6% の情報を保持している

という読み方になります。累積寄与率が 80〜90% を超える主成分数まで取れば、データの大部分が説明できている と判断するのが目安です。

ここが PCA で最も重要かつ難しい部分です。pca.components_ には 各主成分が元の変数のどんな線形結合か が記録されています。係数の大小から「この主成分はどんな特徴を表しているか」を 分析者が自分で解釈する 必要があります。アルゴリズムが「これは総合学力です」と教えてくれるわけではありません。

import pandas as pd
loadings_df = pd.DataFrame(
pca.components_.T,
columns=["第1主成分", "第2主成分", "第3主成分"],
index=["国語", "数学", "英語", "理科", "社会"],
)
loadings_df

たとえば:

  • 第 1 主成分:全教科の係数がほぼ正(0.5 前後)で揃っていれば、「すべての教科が高いほど第 1 主成分の値が高い」→ 総合学力 と解釈できる
  • 第 2 主成分:国語・英語・社会で正、数学・理科で負なら、「文系科目が高くて理系科目が低い人は第 2 主成分が正」→ 文系傾向 と解釈できる

「ラベル」は分析者が データから読み取って付ける もので、客観的な正解はありません。複数の人が同じ PCA 結果を見ても異なる解釈をすることがあります。解釈が分かれたときは、両方の解釈を併記して議論する、というのが実務的な流儀です。

第 1 主成分 × 第 2 主成分の散布図を描けば、データ全体の構造が 1 枚絵 で把握できます:

import matplotlib.pyplot as plt
plt.scatter(principal_components[:, 0], principal_components[:, 1])
plt.xlabel("第1主成分")
plt.ylabel("第2主成分")
plt.show()

総合力が高く文系寄りの人は右上、総合力が低く理系寄りの人は左下、というように、4 つの象限で集団の特徴を読み取れる のが PCA 散布図の強みです。点の集まり(クラスター)や外れ値が一目で分かるようになります。

  • 標準化を忘れる — 変数の単位が違うときに標準化をスキップすると、第 1 主成分が「単位が大きい変数の方向」に偏る
  • 主成分の符号は任意+/- が反転することがある。第 1 主成分が「総合学力(高いほど正)」とも「総合学力(高いほど負)」とも解釈できる。係数の符号と一緒に解釈すれば一貫する
  • 寄与率を見ずに使う — 累積寄与率が低い(たとえば 50% 以下)のに上位 2 主成分だけで判断すると、情報の半分以上を捨てていることになる
  • 「PCA でクラスタリングできる」と誤解する — PCA は次元を削減するだけで、グループ分けは行わない。クラスタリング(k-means など)と組み合わせる場合は別の手順

完成 Notebook を題材に、次を行いなさい。

  1. Notebook を Google Colab で開いて最後まで実行し、第 1〜第 3 主成分の寄与率と累積寄与率を確認する
  2. 第 1 主成分 × 第 2 主成分の 2 次元散布図を 1 枚生成する
  3. 主成分係数の表pca.components_)を見て、第 1 主成分・第 2 主成分が「どんな特徴を表しているか」を自分の言葉で書きなさい(解答例:「第 1 主成分は◯◯」のように 1〜2 文ずつ)

主成分の「意味」を言語化する 3 番が、この単元で身につけてほしい一番のスキルです。係数の正負と大小をセットで読むことで、PCA の出力に対する解釈力が育ちます。

  • 主成分係数を棒グラフで可視化し、各主成分の意味を視覚的に確認する
  • 3 主成分を取って、Plotly で 3D 散布図を作成する(完成 Notebook 後半にサンプルあり)
  • 自分でデータセットを 1 つ用意して PCA を適用し、寄与率と主成分の意味を整理してみる