7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【第12回】Pythonで学ぶデータ分析実践 〜次元削減〜

7
Last updated at Posted at 2026-07-19

第12回 次元削減

はじめに

本シリーズ「Pythonで学ぶデータ分析実践」の最終回となる第12回では、次元削減について解説します。

実際のデータ分析では、数十〜数百の特徴量(変数)を持つデータを扱うことが珍しくありません。しかし、変数が多すぎると計算コストの増大、過学習、可視化の困難さなどの問題が生じます。次元削減は、データの重要な情報を保ちながら変数の数を減らす技術です。

本記事では、最も広く使われる主成分分析(PCA)を中心に、寄与率による次元数の決定方法と、低次元空間での可視化手法を実践的に解説します。

シリーズ全体の構成(全12回)

テーマ
第1回 データ分析とは
第2回 データの前処理
第3回 データの把握
第4回 相関分析
第5回 統計的推定
第6回 統計的検定
第7回 分散分析
第8回 回帰分析
第9回 時系列データ分析
第10回 クラス分類
第11回 クラスタリング
第12回 次元削減(本記事)

12-1. 次元削減とは

次元削減とは、データが持つ重要な情報(分散)をできるだけ保持しながら、特徴量の数(次元数)を減らす手法です。

なぜ次元削減が必要か

問題 説明
次元の呪い 高次元ではデータがスパースになり、距離ベースの手法が機能しにくい
計算コスト 特徴量が多いほど学習に時間がかかる
過学習 特徴量が多すぎるとノイズも学習してしまう
可視化の困難さ 人間は3次元までしか直感的に理解できない
多重共線性 相関の高い変数が多いと回帰分析が不安定になる

次元削減の手法

手法 種類 特徴
主成分分析(PCA) 線形 最も広く使われる。分散最大化
カーネルPCA 非線形 非線形構造を持つデータに対応
t-SNE 非線形 高次元データの可視化に特化
UMAP 非線形 t-SNEの高速版。構造保持性が高い
LDA 線形 教師ありの次元削減(クラス間分離最大化)

12-2. 主成分分析(PCA)

**主成分分析(PCA: Principal Component Analysis)**は、データの分散が最大となる方向(主成分)を見つけ、その方向に射影することで次元を削減する手法です。

PCAの仕組み

1. データを標準化する(平均0、分散1)
2. 共分散行列を計算する
3. 共分散行列の固有値と固有ベクトルを求める
4. 固有値の大きい順に固有ベクトルを並べる
5. 上位k個の固有ベクトルで新たな座標系を構成する
6. 元データを新座標系に射影する

主成分の意味

  • 第1主成分: データの分散が最大となる方向
  • 第2主成分: 第1主成分と直交し、残りの分散が最大となる方向
  • 第k主成分: 第1〜第(k-1)主成分と直交し、残りの分散が最大となる方向

Pythonでの実装

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import japanize_matplotlib

# データの準備
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# 標準化(PCAの前に必須)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# PCAの実行(全主成分を計算)
pca = PCA()
X_pca = pca.fit_transform(X_scaled)

print("=== PCA結果 ===")
print(f"元の次元数: {X.shape[1]}")
print(f"固有値(分散): {pca.explained_variance_}")
print(f"寄与率: {pca.explained_variance_ratio_}")
print(f"累積寄与率: {np.cumsum(pca.explained_variance_ratio_)}")

主成分負荷量(各特徴量の寄与)

# 主成分負荷量(各特徴量が各主成分にどの程度寄与するか)
loadings = pd.DataFrame(
    pca.components_.T,
    columns=[f"PC{i+1}" for i in range(4)],
    index=iris.feature_names
)
print("\n=== 主成分負荷量 ===")
print(loadings.round(4))

# ヒートマップで可視化
import seaborn as sns
plt.figure(figsize=(8, 5))
sns.heatmap(loadings, annot=True, cmap='coolwarm', fmt='.3f',
            vmin=-1, vmax=1, center=0)
plt.title("主成分負荷量(各特徴量の主成分への寄与)")
plt.tight_layout()
plt.show()

12-3. 寄与率

寄与率は、各主成分がデータの全分散のうちどの程度を説明するかを表す指標です。累積寄与率を見て、何次元に削減するかを決定します。

寄与率の定義

$$寄与率_k = \frac{\lambda_k}{\sum_{i=1}^{p} \lambda_i}$$

  • $\lambda_k$: 第k主成分の固有値(分散)
  • p: 元の次元数

次元数の決定基準

基準 説明
累積寄与率80%以上 全分散の80%以上を説明できる次元数を選ぶ
累積寄与率90%以上 より保守的な基準
固有値1以上(カイザー基準) 固有値が1以上の主成分のみ採用
スクリープロット 固有値の減少が鈍化する点で切る

寄与率の可視化

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 寄与率の棒グラフ
axes[0].bar(range(1, 5), pca.explained_variance_ratio_, 
            color='steelblue', alpha=0.8, edgecolor='black')
axes[0].set_xlabel("主成分", fontsize=12)
axes[0].set_ylabel("寄与率", fontsize=12)
axes[0].set_title("各主成分の寄与率", fontsize=13)
axes[0].set_xticks(range(1, 5))
axes[0].grid(axis='y', alpha=0.3)

for i, v in enumerate(pca.explained_variance_ratio_):
    axes[0].text(i+1, v+0.01, f'{v:.3f}', ha='center', fontsize=10)

# 累積寄与率の折れ線グラフ
cum_ratio = np.cumsum(pca.explained_variance_ratio_)
axes[1].plot(range(1, 5), cum_ratio, 'o-', color='steelblue', 
             markersize=10, linewidth=2)
axes[1].axhline(0.8, color='red', linestyle='--', alpha=0.7, label='80%ライン')
axes[1].axhline(0.9, color='orange', linestyle='--', alpha=0.7, label='90%ライン')
axes[1].set_xlabel("主成分数", fontsize=12)
axes[1].set_ylabel("累積寄与率", fontsize=12)
axes[1].set_title("累積寄与率", fontsize=13)
axes[1].set_xticks(range(1, 5))
axes[1].set_ylim(0, 1.05)
axes[1].legend(fontsize=10)
axes[1].grid(alpha=0.3)

for i, v in enumerate(cum_ratio):
    axes[1].text(i+1.1, v, f'{v:.3f}', fontsize=10)

plt.tight_layout()
plt.show()

# 次元数の決定
for threshold in [0.8, 0.9, 0.95]:
    n_components = np.argmax(cum_ratio >= threshold) + 1
    print(f"累積寄与率{threshold*100:.0f}%以上 → {n_components}次元で十分")

実践:指定した次元数でPCA

# 2次元に削減
pca_2d = PCA(n_components=2)
X_2d = pca_2d.fit_transform(X_scaled)

print(f"\n=== 2次元PCA ===")
print(f"累積寄与率: {sum(pca_2d.explained_variance_ratio_):.4f}")
print(f"(元データの情報の{sum(pca_2d.explained_variance_ratio_)*100:.1f}%を保持)")

12-4. 可視化

次元削減の最も重要な応用の一つが、高次元データの2次元・3次元での可視化です。

PCAによる2次元可視化

plt.figure(figsize=(8, 6))
for i, name in enumerate(iris.target_names):
    mask = y == i
    plt.scatter(X_2d[mask, 0], X_2d[mask, 1], s=60, alpha=0.7, 
                edgecolors='black', linewidths=0.5, label=name)

plt.xlabel(f"第1主成分(寄与率: {pca_2d.explained_variance_ratio_[0]:.3f}", fontsize=11)
plt.ylabel(f"第2主成分(寄与率: {pca_2d.explained_variance_ratio_[1]:.3f}", fontsize=11)
plt.title("PCAによるIrisデータの2次元可視化", fontsize=13)
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.show()

バイプロット(データ点と主成分負荷量の同時表示)

fig, ax = plt.subplots(figsize=(9, 7))

# データ点のプロット
for i, name in enumerate(iris.target_names):
    mask = y == i
    ax.scatter(X_2d[mask, 0], X_2d[mask, 1], s=50, alpha=0.6,
               edgecolors='black', linewidths=0.5, label=name)

# 主成分負荷量の矢印
scale = 3
for j, feature in enumerate(iris.feature_names):
    ax.arrow(0, 0, pca_2d.components_[0, j]*scale, pca_2d.components_[1, j]*scale,
             head_width=0.05, head_length=0.03, fc='red', ec='red', alpha=0.8)
    ax.text(pca_2d.components_[0, j]*scale*1.15, pca_2d.components_[1, j]*scale*1.15,
            feature, color='red', fontsize=9, ha='center')

ax.set_xlabel("第1主成分", fontsize=11)
ax.set_ylabel("第2主成分", fontsize=11)
ax.set_title("バイプロット(データ点 + 特徴量の寄与方向)", fontsize=13)
ax.legend(fontsize=10)
ax.grid(alpha=0.3)
ax.axhline(0, color='gray', linewidth=0.5)
ax.axvline(0, color='gray', linewidth=0.5)
plt.tight_layout()
plt.show()

t-SNEによる可視化

PCAは線形変換のため、非線形構造を持つデータの可視化にはt-SNEが有効です。

from sklearn.manifold import TSNE

# t-SNEで2次元に変換
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
X_tsne = tsne.fit_transform(X_scaled)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# PCA
for i, name in enumerate(iris.target_names):
    mask = y == i
    axes[0].scatter(X_2d[mask, 0], X_2d[mask, 1], s=50, alpha=0.7,
                    edgecolors='black', linewidths=0.5, label=name)
axes[0].set_title("PCA", fontsize=13)
axes[0].set_xlabel("第1主成分")
axes[0].set_ylabel("第2主成分")
axes[0].legend()
axes[0].grid(alpha=0.3)

# t-SNE
for i, name in enumerate(iris.target_names):
    mask = y == i
    axes[1].scatter(X_tsne[mask, 0], X_tsne[mask, 1], s=50, alpha=0.7,
                    edgecolors='black', linewidths=0.5, label=name)
axes[1].set_title("t-SNE", fontsize=13)
axes[1].set_xlabel("t-SNE 次元1")
axes[1].set_ylabel("t-SNE 次元2")
axes[1].legend()
axes[1].grid(alpha=0.3)

plt.tight_layout()
plt.show()

PCAとt-SNEの使い分け

項目 PCA t-SNE
変換の種類 線形 非線形
目的 次元削減 + 可視化 可視化に特化
逆変換 可能 不可能
新規データへの適用 可能(transform) 不可能(再学習が必要)
計算速度 高速 遅い(大規模データ注意)
グローバル構造 保持される 保持されにくい
ローカル構造 保持されにくい よく保持される

補足:次元削減の実践的な活用

PCAによる前処理(機械学習パイプライン)

from sklearn.pipeline import Pipeline
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

# PCA + ロジスティック回帰のパイプライン
pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('pca', PCA(n_components=2)),
    ('classifier', LogisticRegression(max_iter=1000))
])

# 交差検証で評価
scores = cross_val_score(pipe, X, y, cv=5, scoring='accuracy')
print(f"=== PCA(2次元) + ロジスティック回帰 ===")
print(f"正解率: {scores.mean():.4f} ± {scores.std():.4f}")

# 次元数を変えて比較
print(f"\n=== 次元数と正解率の関係 ===")
for n in [1, 2, 3, 4]:
    pipe.set_params(pca__n_components=n)
    scores = cross_val_score(pipe, X, y, cv=5, scoring='accuracy')
    print(f"  {n}次元: {scores.mean():.4f} ± {scores.std():.4f}")

大規模データでの活用例

from sklearn.datasets import fetch_openml

# 手書き数字データ(64次元 → 2次元で可視化)
from sklearn.datasets import load_digits
digits = load_digits()
X_digits = digits.data  # 64特徴量(8×8ピクセル)
y_digits = digits.target

# PCAで2次元に削減
scaler_d = StandardScaler()
X_d_scaled = scaler_d.fit_transform(X_digits)
pca_digits = PCA(n_components=2)
X_d_2d = pca_digits.fit_transform(X_d_scaled)

print(f"元の次元数: {X_digits.shape[1]}")
print(f"削減後: 2次元")
print(f"累積寄与率: {sum(pca_digits.explained_variance_ratio_):.4f}")

# 可視化
plt.figure(figsize=(10, 8))
scatter = plt.scatter(X_d_2d[:, 0], X_d_2d[:, 1], c=y_digits, 
                      cmap='tab10', s=10, alpha=0.6)
plt.colorbar(scatter, label='数字')
plt.xlabel("第1主成分")
plt.ylabel("第2主成分")
plt.title("手書き数字データのPCA可視化(64次元→2次元)")
plt.grid(alpha=0.3)
plt.show()

まとめ

本記事では、次元削減として以下の内容を解説しました。

項目 ポイント
次元削減とは データの情報を保持しながら変数の数を減らす技術
PCA 分散最大化による線形次元削減。最も広く使用
寄与率 各主成分の説明力。累積80〜90%を基準に次元数を決定
可視化 PCAで全体構造、t-SNEで局所構造を把握

チェックリスト

  • PCAの前にデータを標準化したか
  • 寄与率と累積寄与率を確認したか
  • 適切な次元数を選択したか
  • 主成分負荷量で各主成分の意味を解釈したか
  • バイプロットで特徴量の寄与方向を確認したか
  • 必要に応じてt-SNEやUMAPで非線形構造を確認したか

シリーズ全体のまとめ

全12回を通じて、データ分析の基礎から実践までを体系的に学んできました。

テーマ キーワード
第1回 データ分析とは CRISP-DM、ビッグデータ、Python
第2回 データの前処理 欠損値、外れ値、スケーリング
第3回 データの把握 基本統計量、ヒストグラム、正規分布
第4回 相関分析 相関係数、相関比、連関係数
第5回 統計的推定 点推定、区間推定、信頼区間
第6回 統計的検定 p値、t検定、カイ二乗検定
第7回 分散分析 F値、多重比較、交互作用
第8回 回帰分析 単回帰、重回帰、GLM、R²
第9回 時系列データ分析 ARIMA、SARIMA、季節性
第10回 クラス分類 ロジスティック回帰、SVM、ランダムフォレスト
第11回 クラスタリング k-means、階層、エルボー法
第12回 次元削減 PCA、寄与率、t-SNE

本シリーズで学んだ内容は、機械学習やディープラーニングを学ぶ上での土台となります。ぜひ実際のデータに触れながら、各手法の理解を深めていただければ幸いです。

シリーズ記事一覧

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?