【Python入門:主成分分析編#3】主成分はいくつ残す? 寄与率とスクリープロットで決める
10個の主成分、全部使う?
前回までで、PCAの仕組みを理解しました。共分散行列を固有値分解すれば、主成分の方向と、その方向での分散が求まるのでしたね。
でも、ここで新たな疑問が生まれます。
元のデータが10次元なら、主成分も10個得られます。では、その10個全部使いますか?
...それでは次元削減になりません。
かといって、1個だけでは情報が失われすぎるかもしれません。
「結局、いくつ残せばいいの?」
今回は、この問いに答えるための道具を学びます。キーワードは「寄与率」と「スクリープロット」です。
🎯 この記事で学べること
寄与率・累積寄与率の意味と計算方法
スクリープロットの描き方と読み方
主成分数を決める3つの基準(肘法・累積寄与率・カイザー基準)
実際にIrisデータを2次元に圧縮して可視化する
寄与率って何?売上分析で考えてみよう
寄与率を理解するために、ビジネスの例で考えてみましょう。
ある会社の売上に影響する要因として、「広告費」「営業人数」「商品数」「立地」「価格」など10個の変数があるとします。
PCAを適用すると、10個の主成分が得られます。
もし第1主成分が売上変動の40%を説明し、第2主成分が25%を説明するとしたら、この2つだけで全体の65%を説明できることになります。残りの8個は合わせて35%しか説明しません。
この「全体の分散のうち、各主成分が何%を説明するか」を表す指標が寄与率(explained variance ratio)です。
数式で書くと、こうなります。
$$
\text{第k主成分の寄与率} = \frac{\lambda_k}{\sum_{i=1}^{p} \lambda_i}
$$
分子:第k主成分の固有値(その方向での分散)
分母:すべての固有値の合計(全分散)
つまり、固有値が大きい主成分ほど、情報をたくさん持っているということです。
準備:Irisデータセットを使う
今回は、機械学習の定番データセット「Iris(アヤメ)」を使います。
3種類のアヤメ(Setosa、Versicolor、Virginica)について、4つの特徴量が測定されています。
以下、コードです。
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import seaborn as sns
import japanize_matplotlib # 日本語表示
np.random.seed(42)
# データ読み込み
iris = load_iris()
X = iris.data
y = iris.target
feature_names = iris.feature_names
target_names = iris.target_names
# DataFrameで確認
df_iris = pd.DataFrame(X, columns=feature_names)
df_iris['species'] = [target_names[i] for i in y]
print(f"サンプル数: {len(df_iris)}")
print(f"特徴量: {feature_names}")
print(f"品種: {list(target_names)}")
print()
print(df_iris.head())以下、実行結果です。
サンプル数: 150
特徴量: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
品種: ['setosa', 'versicolor', 'virginica']
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) \
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
species
0 setosa
1 setosa
2 setosa
3 setosa
4 setosa
150サンプル、4次元のデータです。これを2次元に圧縮して、3品種がどう分布するか可視化するのが今回の目標です。
データの標準化
PCAの前に、データを標準化(平均0、標準偏差1に変換)します。変数ごとにスケールが異なる場合、これは必須の前処理です。
以下、コードです。
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
print("標準化後の平均:", X_scaled.mean(axis=0).round(2))
print("標準化後の標準偏差:", X_scaled.std(axis=0).round(2))以下、実行結果です。
標準化後の平均: [-0. -0. -0. -0.]
標準化後の標準偏差: [1. 1. 1. 1.]
共分散行列の固有値分解
標準化したデータで固有値分解を行います。
以下、コードです。
# 共分散行列を計算
n = len(X_scaled)
cov_matrix = (X_scaled.T @ X_scaled) / (n - 1)
# 固有値分解
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)
# 固有値が大きい順にソート
idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx].real
eigenvectors = eigenvectors[:, idx].real
print("固有値(各主成分の分散):")
for i, ev in enumerate(eigenvalues):
print(f" 第{i+1}主成分: {ev:.4f}")以下、実行結果です。
固有値(各主成分の分散):
第1主成分: 2.9381
第2主成分: 0.9202
第3主成分: 0.1477
第4主成分: 0.0209
4つの固有値が表示されます。第1主成分が最も大きく、順に小さくなっていきます。
📊 寄与率と累積寄与率を計算する
いよいよ寄与率の計算です。
以下、コードです。
# 寄与率を計算
total_variance = np.sum(eigenvalues)
explained_ratio = eigenvalues / total_variance
# 累積寄与率を計算
cumulative_ratio = np.cumsum(explained_ratio)
print(f"{'主成分':<8} {'固有値':>10} {'寄与率':>10} {'累積寄与率':>10}")
print("-" * 45)
for i in range(len(eigenvalues)):
print(
f"PC{i+1:<6} {eigenvalues[i]:>10.4f} "
f"{explained_ratio[i]*100:>9.2f}% "
f"{cumulative_ratio[i]*100:>9.2f}%")以下、実行結果です。
主成分 固有値 寄与率 累積寄与率
---------------------------------------------
PC1 2.9381 72.96% 72.96%
PC2 0.9202 22.85% 95.81%
PC3 0.1477 3.67% 99.48%
PC4 0.0209 0.52% 100.00%
結果を見ると、第1・第2主成分だけで累積寄与率が約95%に達しています。
これは、「4次元のデータを2次元に減らしても、情報の95%は保持できる」ということです。すごい圧縮率ですね!
📈 スクリープロットを描く
スクリープロット(scree plot)は、固有値や寄与率を主成分の順にグラフ化したものです。
「scree」は山の斜面にたまった岩屑のこと。グラフの形が岩屑の斜面に似ていることから名付けられました。
以下、コードです。
fig, axes = plt.subplots(2, 1, figsize=(10, 10))
# 上: 固有値のスクリープロット
ax = axes[0]
x = np.arange(1, len(eigenvalues) + 1)
ax.bar(
x, eigenvalues,
alpha=0.7, color='steelblue'
)
ax.plot(
x, eigenvalues,
'ro-', markersize=8, linewidth=2
)
ax.set_xlabel('主成分')
ax.set_ylabel('固有値')
ax.set_title('スクリープロット(固有値)')
ax.set_xticks(x)
ax.set_xticklabels([f'PC{i}' for i in x])
ax.grid(True, axis='y', alpha=0.3)
# 下: 寄与率と累積寄与率
ax = axes[1]
ax.bar(
x, explained_ratio * 100,
alpha=0.7, color='steelblue',
label='寄与率'
)
ax.plot(
x, cumulative_ratio * 100,
'ro-', markersize=8, linewidth=2,
label='累積寄与率'
)
ax.axhline(
y=80,
color='green', linestyle='--', linewidth=2,
label='80%ライン'
)
ax.set_xlabel('主成分')
ax.set_ylabel('寄与率 (%)')
ax.set_title('寄与率と累積寄与率')
ax.set_xticks(x)
ax.set_xticklabels([f'PC{i}' for i in x])
ax.set_ylim(0, 105)
ax.legend(loc='center right')
ax.grid(True, axis='y', alpha=0.3)
plt.tight_layout()
plt.show()以下、実行結果です。

上のグラフでは、第1・第2主成分の固有値が大きく、第3以降は急に小さくなっています。
下のグラフでは、累積寄与率が80%を超えるポイントが一目でわかります。
🔍 主成分数を決める3つの基準
1. 肘法(エルボー法)
スクリープロットの形を見て、曲線が急に平らになる「肘(elbow)」の位置で打ち切る方法です。
先ほどのグラフでは、第2主成分と第3主成分の間で傾きが急に緩やかになっています。この「肘」の位置で打ち切り、第1・第2主成分を採用します。
メリット: 視覚的にわかりやすい デメリット: 肘がはっきり見えないこともある
2. 累積寄与率による判断
最もよく使われる方法です。「どの程度まで情報を残したいか」を決めて、その割合を超える最小の主成分数を採用します。
よく使われる目安は以下の通りです。
可視化・簡易分析: 80%程度
分析の前処理: 90%前後
情報損失を最小限に: 95%程度
今回の例では、2主成分で約95%に達しているので、「95%を保持する」という基準なら主成分数は2と判断できます。
3. カイザー基準(固有値 > 1)
標準化したデータでは、「固有値が1より大きい主成分のみを採用する」という経験則があります。
固有値1未満の主成分は、元の変数1個分にも満たない情報しか持たないという考え方です。
今回は第1主成分の固有値が1を大きく上回り、第2主成分は1未満。この基準だと第1主成分のみという、かなり厳しめの判断になります。
どれが正解?
唯一の正解はありません。
分析の目的、データの性質、後続の処理との兼ね合いで判断します。
今回は、肘法と累積寄与率(95%)の両方が「2主成分で十分」を支持しているので、2主成分を採用するのが妥当でしょう。
🎨 主成分得点:データを新しい座標系で見る
主成分の方向が分かったら、各データ点をその方向に投影します。この投影後の座標を主成分得点(principal component score)と呼びます。
数式で書くと、こうなります。
$$
Z = X \cdot V
$$
X: 中心化されたデータ
V: 固有ベクトルを並べた行列
Z: 主成分得点(次元削減後のデータ)
以下、コードです。
# 上位2つの固有ベクトルを取得
W = eigenvectors[:, :2] # 4×2の行列
# 主成分得点を計算
Z = X_scaled @ W # 150×2の行列
print("主成分得点の形状:", Z.shape)以下、実行結果です。
主成分得点の形状: (150, 2)
4次元から2次元に圧縮されました!
散布図で3品種を可視化する
いよいよ、圧縮したデータを散布図にプロットします。
以下、コードです。
plt.figure(figsize=(10, 8))
colors = ['#E74C3C', '#2ECC71', '#3498DB']
for i, (species, color) in enumerate(zip(target_names, colors)):
mask = y == i
plt.scatter(
Z[mask, 0], Z[mask, 1],
c=color, label=species,
s=80, alpha=0.7, edgecolors='white'
)
plt.xlabel(f'第1主成分 (寄与率: {explained_ratio[0]*100:.1f}%)')
plt.ylabel(f'第2主成分 (寄与率: {explained_ratio[1]*100:.1f}%)')
plt.title('Irisデータセットの主成分分析')
plt.legend(title='品種')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='gray', linestyle='-', linewidth=0.5)
plt.axvline(x=0, color='gray', linestyle='-', linewidth=0.5)
plt.show()以下、実行結果です。

3品種がかなり明確に分離されています!
特にSetosa(赤)は他の2品種と完全に分離。VersicolorとVirginicaも、多少の重なりはあるものの、おおむね区別できます。
4次元のデータを直接見ることはできませんが、2次元に圧縮することで、データの構造が目で見えるようになりました。
📊 主成分負荷量:どの変数が効いているか
各特徴量が主成分にどれくらい寄与しているかを見てみましょう。これを主成分負荷量(loading)と呼びます。
$$
\text{負荷量} = \sqrt{\lambda_k} \cdot v_k
$$
固有ベクトル(方向)に、標準偏差(広がり)を掛け合わせたものです。
以下、コードです。
# 主成分負荷量を計算
loadings = pd.DataFrame(
eigenvectors[:, :2] * np.sqrt(eigenvalues[:2]),
index=feature_names,
columns=['PC1', 'PC2']
)
print("主成分負荷量:")
print(loadings.round(3))以下、実行結果です。
主成分負荷量:
PC1 PC2
sepal length (cm) 0.893 -0.362
sepal width (cm) -0.462 -0.886
petal length (cm) 0.995 -0.023
petal width (cm) 0.968 -0.064
PC1: petal length、petal width、sepal lengthの負荷量の絶対値が大きい → 「花のサイズ全般」を表す軸
PC2: sepal widthの負荷量の絶対値が大きい → 「がく片の幅」を表す軸
バイプロット:サンプルと変数を同時に可視化
主成分得点(サンプルの位置)と主成分負荷量(変数の寄与)を同じグラフに描いたものをバイプロットと呼びます。
以下、コードです。
plt.figure(figsize=(10, 8))
# サンプルをプロット
for i, (species, color) in enumerate(zip(target_names, colors)):
mask = y == i
plt.scatter(Z[mask, 0], Z[mask, 1],
c=color, label=species,
s=80, alpha=0.7, edgecolors='white')
# 変数の矢印をプロット
scale = 2 # 矢印の見やすさ調整
loadings_scaled = loadings * scale
for i in range(len(loadings)):
plt.arrow(0, 0,
loadings_scaled.iloc[i, 0],
loadings_scaled.iloc[i, 1],
color='black', alpha=0.6, head_width=0.05)
plt.text(loadings_scaled.iloc[i, 0] * 1.1,
loadings_scaled.iloc[i, 1] * 1.1,
loadings.index[i], fontsize=9)
plt.xlabel(f'第1主成分 (寄与率: {explained_ratio[0]*100:.1f}%)')
plt.ylabel(f'第2主成分 (寄与率: {explained_ratio[1]*100:.1f}%)')
plt.title('バイプロット')
plt.legend(title='品種')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='gray', linestyle='-', linewidth=0.5)
plt.axvline(x=0, color='gray', linestyle='-', linewidth=0.5)
plt.show()以下、実行結果です。

矢印の読み方は以下の通りです。
矢印が長い: その変数は主成分によってよく説明されている
矢印の向き: 主成分への正・負の影響
矢印同士の角度: 鋭角なら正の相関、鈍角なら負の相関
まとめ
今回は「主成分をいくつ残すか」を決める方法を学びました。
寄与率は、各主成分が全体の分散の何%を説明するかを表します。固有値を全固有値の合計で割って計算します。
累積寄与率は、上位k個の主成分で全体の何%の情報を保持できるかを表します。
スクリープロットは、固有値や寄与率をグラフ化して、主成分の重要度を視覚的に把握するツールです。
主成分数を決める基準には、累積寄与率(80〜95%)、カイザー基準(固有値>1)、肘法(エルボー法)などがあります。
主成分得点は、データを主成分方向に投影した座標です。これが次元削減後のデータになります。
主成分負荷量は、各変数が主成分にどれだけ寄与しているかを表します。
📚 参考にした情報源
次回予告
ここまで手計算でPCAの仕組みを理解してきました。
でも実務では、毎回こんな計算はしません。ライブラリを使えば数行で終わります。
次回は、scikit-learnを使ってPCAをサクッと実行する方法を学びます。手計算の知識があるからこそ、ライブラリの出力が理解できるようになります。
最後まで読んでいただきありがとうございました! 「スキ」を押していただけると励みになります 🙌
