見出し画像

【Python入門:主成分分析編#5】PCAと因子分析、何が違う?― 矢印の向きで理解する本質的な違い

これまで4回にわたって主成分分析(PCA)を学んできました。

共分散行列の固有値分解によって「データが最も広がる方向」を見つけ、次元を削減する手法でしたね。

PCAと似た手法に因子分析(Factor Analysis)があります。

どちらも「多くの変数を少数にまとめる」という目的で使われますが、実は根本的な考え方が異なります。

「PCAと因子分析って何が違うの?」
「どう使い分ければいいの?」

今回は、この疑問にしっかり答えていきます。



🎓 身近な例で考える ― 5教科のテスト結果

高校生の5教科(国語、数学、英語、物理、化学)のテスト結果があるとします。

これらの成績の「背後」には何があるでしょうか?


PCAの考え方:「データ → 要約」

PCAは、5教科の点数をできるだけ情報を失わずに少ない変数にまとめたいという発想です。

「第1主成分は総合学力、第2主成分は文理の傾向を表している」などと解釈することはありますが、それはあくまで結果の解釈であり、最初から「総合学力」を想定しているわけではありません。

【PCAの矢印】
データ(5教科の点数)→ 要約(主成分)

因子分析の考え方:「潜在因子 → データ」

一方、因子分析は、観測できない「潜在的な能力」が各科目の点数を生み出していると考えます。

たとえば、「言語能力」という見えない因子が国語と英語の点数に影響し、「数理能力」という因子が数学・物理・化学の点数に影響している、というモデルを想定します。

【因子分析の矢印】
潜在因子(言語能力・数理能力)→ データ(5教科の点数)

この「矢印の向き」の違いが、両者の本質的な違いです。


📐 数学的な違い

PCAのモデル

PCAは、厳密には統計モデルではありません。単に共分散行列を固有値分解して、分散が最大になる方向を見つけているだけです。

$${\mathbf{z} = W^T \mathbf{x}}$$

  • $${\mathbf{x}}$$:観測データ

  • $${W}$$:固有ベクトルを並べた行列

  • $${\mathbf{z}}$$:主成分得点

主成分得点は一意に計算できます。


因子分析のモデル

因子分析は、データがどのように生成されたかを仮定する統計モデルです。

$${\mathbf{x} = \Lambda \mathbf{F} + \boldsymbol{\varepsilon}}$$

因子分析では、「因子 $${\mathbf{F}}$$ があって、それが各科目の点数 $${\mathbf{x}}$$ を生み出している」と考えます。

因子得点は推定値であり、一意には決まりません。


🛠️ 準備

ライブラリの準備

因子分析にはfactor_analyzerライブラリを使います。

以下、コードです。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import japanize_matplotlib

# PCA用
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# 因子分析用
from factor_analyzer import FactorAnalyzer

# 乱数のシードを固定
np.random.seed(42)

インストールされていない場合は pip install factor_analyzer でインストールしてください。


サンプルデータの生成

「言語能力」と「数理能力」という2つの潜在因子から5教科の成績が生成される、というシナリオでデータを作成します。

以下、コードです。

# サンプル数
n = 200

# 2つの潜在因子を生成(これは通常観測できない)
F_言語能力 = np.random.normal(0, 1, n)
F_数理能力 = np.random.normal(0, 1, n)

# 国語:言語能力が強く影響
国語 = 0.8 * F_言語能力 + 0.1 * F_数理能力 + np.random.normal(0, 0.3, n)

# 英語:言語能力が強く影響
英語 = 0.7 * F_言語能力 + 0.2 * F_数理能力 + np.random.normal(0, 0.3, n)

# 数学:数理能力が強く影響
数学 = 0.2 * F_言語能力 + 0.8 * F_数理能力 + np.random.normal(0, 0.3, n)

# 物理:数理能力が強く影響
物理 = 0.1 * F_言語能力 + 0.7 * F_数理能力 + np.random.normal(0, 0.3, n)

# 化学:数理能力が強く影響
化学 = 0.2 * F_言語能力 + 0.6 * F_数理能力 + np.random.normal(0, 0.3, n)

# DataFrameにまとめる
成績データ = pd.DataFrame({
    '国語': 国語, 
    '英語': 英語, 
    '数学': 数学, 
    '物理': 物理, 
    '化学': 化学
})

このデータは因子分析のモデル $${\mathbf{x} = \Lambda \mathbf{F} + \boldsymbol{\varepsilon}}$$ に従って生成しています。


📊 PCA(主成分分析)を実施

以下、コードです。

# データを標準化
scaler = StandardScaler()
X_標準化 = scaler.fit_transform(成績データ)

# PCAを実行(2成分を抽出)
pca = PCA(n_components=2)
pca得点 = pca.fit_transform(X_標準化)

# 寄与率を表示
print("=== PCAの結果 ===")
print(f"第1主成分の寄与率: {pca.explained_variance_ratio_[0]*100:.1f}%")
print(f"第2主成分の寄与率: {pca.explained_variance_ratio_[1]*100:.1f}%")
print(f"累積寄与率: {sum(pca.explained_variance_ratio_)*100:.1f}%")

以下、実行結果です。

=== PCAの結果 ===
第1主成分の寄与率: 66.6%
第2主成分の寄与率: 24.8%
累積寄与率: 91.5%

2つの主成分で全体の分散の大部分を説明できています。


主成分負荷量を確認

以下、コードです。

# 主成分負荷量を計算(固有ベクトル × √固有値)
主成分負荷量 = pca.components_.T * np.sqrt(pca.explained_variance_)

主成分負荷量df = pd.DataFrame(
    主成分負荷量,
    index=成績データ.columns,
    columns=['PC1', 'PC2']
)

print("=== PCA:主成分負荷量 ===")
print(主成分負荷量df.round(3))

以下、実行結果です。

=== PCA:主成分負荷量 ===
       PC1    PC2
国語  0.682  0.691
英語  0.749  0.610
数学  0.904 -0.323
物理  0.834 -0.469
化学  0.900 -0.272

PC1にはすべての科目がプラスに寄与していて「総合学力」のように見え、PC2は文系(プラス方向)と理系(マイナス方向)を区別する軸になっています。


🔬 因子分析を実施

因子分析(回転なし)

以下、コードです。

# 因子分析を実行(2因子を抽出、回転なし)
fa = FactorAnalyzer(
    n_factors=2,        # 抽出する因子数
    rotation=None,      # 回転なし
    method='principal', # 主成分法
)
fa.fit(X_標準化)

# 因子負荷量を取得
因子負荷量df = pd.DataFrame(
    fa.loadings_,
    index=成績データ.columns,
    columns=['因子1', '因子2']
)

print("=== 因子分析:因子負荷量(回転なし) ===")
print(因子負荷量df.round(3))

以下、実行結果です。

=== 因子分析:因子負荷量(回転なし) ===
       因子1   因子2
国語  0.680  0.689
英語  0.747  0.609
数学  0.902 -0.322
物理  0.832 -0.468
化学  0.897 -0.271

回転なしの状態では、PCAの主成分負荷量と似た結果になります。


因子分析(バリマックス回転あり)

因子分析の大きな特徴が回転です。

回転は、因子の解釈をしやすくするための操作です。回転しても、因子が説明する分散の合計は変わりません。

以下、コードです。

# バリマックス回転を適用した因子分析を実行
fa_回転あり = FactorAnalyzer(
    n_factors=2,        # 抽出する因子数
    rotation='varimax', # バリマックス回転
    method='principal', # 主成分法
)
fa_回転あり.fit(X_標準化)

# 因子負荷量を取得
因子負荷量_回転あり_df = pd.DataFrame(
    fa_回転あり.loadings_,
    index=成績データ.columns,
    columns=['因子1', '因子2']
)

print("=== 因子分析:因子負荷量(バリマックス回転後) ===")
print(因子負荷量_回転あり_df.round(3))

以下、実行結果です。

=== 因子分析:因子負荷量(バリマックス回転後) ===
       因子1   因子2
国語  0.165  0.954
英語  0.266  0.926
数学  0.925  0.250
物理  0.950  0.090
化学  0.892  0.289

回転後は「国語・英語が因子2に」「数学・物理・化学が因子1に」というように、元の因子構造がより明確に復元されています!


🔄 回転方法のまとめ

直交回転:回転後も因子同士は無相関を保つ(解釈がシンプル)

斜交回転:因子間の相関を許す(より柔軟だが解釈は複雑)

初学者はまず'varimax'から始めることをおすすめします。


🎯 独自性(uniqueness)― 因子分析にあってPCAにない概念

因子分析には独自性という重要な概念があります。

  • 共通性:因子によって説明される分散の割合

  • 独自性:因子では説明できない、その変数固有の分散の割合

以下、コードです。

# 共通性と独自性を計算
共通性 = fa_回転あり.get_communalities()
独自性 = fa_回転あり.get_uniquenesses()

結果df = pd.DataFrame({
    '共通性': 共通性,
    '独自性': 独自性
}, index=成績データ.columns)

print("=== 共通性と独自性 ===")
print(結果df.round(3))

以下、実行結果です。

=== 共通性と独自性 ===
       共通性   独自性
国語  0.938  0.062
英語  0.928  0.072
数学  0.918  0.082
物理  0.911  0.089
化学  0.879  0.121

PCAではすべての分散を主成分で説明しようとしますが、因子分析では共通因子で説明できる部分と説明できない独自成分を区別します。


📈 真の因子との相関を比較

今回はデータを自分で生成したので、「真の因子」がわかっています。推定結果がどれだけ一致しているか確認してみましょう。

以下、コードです。

# 因子得点を計算
fa得点 = fa_回転あり.transform(X_標準化)

# 真の因子との相関を計算
print("=== 推定結果と真の因子の相関 ===")
print()
print("【PCA】")
print(f"  PC1 vs 言語能力: {np.corrcoef(pca得点[:, 0], F_言語能力)[0,1]:.3f}")
print(f"  PC1 vs 数理能力: {np.corrcoef(pca得点[:, 0], F_数理能力)[0,1]:.3f}")
print(f"  PC2 vs 言語能力: {np.corrcoef(pca得点[:, 1], F_言語能力)[0,1]:.3f}")
print(f"  PC2 vs 数理能力: {np.corrcoef(pca得点[:, 1], F_数理能力)[0,1]:.3f}")
print()
print("【因子分析(回転後)】")
print(f"  因子1 vs 言語能力: {np.corrcoef(fa得点[:, 0], F_言語能力)[0,1]:.3f}")
print(f"  因子1 vs 数理能力: {np.corrcoef(fa得点[:, 0], F_数理能力)[0,1]:.3f}")
print(f"  因子2 vs 言語能力: {np.corrcoef(fa得点[:, 1], F_言語能力)[0,1]:.3f}")
print(f"  因子2 vs 数理能力: {np.corrcoef(fa得点[:, 1], F_数理能力)[0,1]:.3f}")

以下、実行結果です。

=== 推定結果と真の因子の相関 ===

【PCA】
  PC1 vs 言語能力: 0.602
  PC1 vs 数理能力: 0.818
  PC2 vs 言語能力: 0.732
  PC2 vs 数理能力: -0.511

【因子分析(回転後)】
  因子1 vs 言語能力: 0.077
  因子1 vs 数理能力: 0.963
  因子2 vs 言語能力: 0.945
  因子2 vs 数理能力: 0.047

因子分析(回転後)の因子が真の因子とより高い相関を示しています。

これは、因子分析が「潜在因子を復元する」という目的に適していることを示しています。


📋 因子数の決め方と因子分析の流れ

実際の分析では、因子数も探索的に決める必要があります。

因子分析の5ステップ

  1. 全因子を抽出してスクリープロットを作成

  2. 因子数を決定(カイザー基準・肘法・累積寄与率)

  3. 決定した因子数で因子分析を再実行(回転あり)

  4. 因子負荷量を確認して因子を解釈・命名

  5. 因子得点を計算して活用


因子数を決める基準

  • カイザー基準:固有値が1以上の因子のみを採用

  • 肘法(エルボー法):スクリープロットで固有値が急に小さくなる位置で打ち切り

  • 累積寄与率:70〜80%程度を目安に

  • 理論的な根拠:「○○という潜在因子があるはずだ」という仮説


⚖️ PCAと因子分析の使い分け

PCAを選ぶべき場面

  • データの次元を減らして可視化したい

  • 機械学習の前処理として特徴量を圧縮したい

  • 多重共線性を解消したい

→ 潜在的な構造よりも「データの分散をできるだけ保存する」ことを重視


因子分析を選ぶべき場面

  • 観測変数の背後にある潜在的な構造を理解したい

  • 心理学のアンケート調査で「性格特性」を抽出したい

  • 社会科学で「潜在的な態度」を測定したい

→ 理論的に意味のある因子を想定できる場面で力を発揮


判断のポイント

「因子の解釈が重要か」を考える

潜在的な概念(能力、態度、特性など)の存在を仮定し、それを測定したいなら因子分析

単にデータを圧縮・可視化したいだけなら、PCAで十分。


📚 参考にした情報源


🔜 次回予告

第6回では「次元削減を回帰に活かす主成分回帰(PCR)」を学びます。PCAで次元削減した結果を、どうやって予測モデルに活用するのか見ていきましょう!


最後まで読んでいただきありがとうございました! 「スキ」を押していただけると励みになります 🙌


いいなと思ったら応援しよう!