初代ポケモン種族値による分類モデル比較：物理型 vs 特殊型


## 🔍 概要

本記事では、**初代ポケモン151種の「攻撃（Attack）」および「特攻（Sp. Atk）」の種族値**を用いて、「物理型（0）」か「特殊型（1）」かを分類するモデルを構築・比較しました。

使用した分類アルゴリズムは以下の8種類です：

* ロジスティック回帰（Logistic Regression）
* k近傍法（k-NN）
* 決定木（Decision Tree）
* ランダムフォレスト（Random Forest）
* サポートベクタマシン（SVM）
* XGBoost
* ナイーブベイズ（Gaussian Naive Bayes）
* 多層パーセプトロン（MLP）

## 🧪 分析の流れ

### 1. データセット

対象は初代151体から、以下のような\*\*攻撃・特攻・ラベル（0:物理, 1:特殊）\*\*を抜粋したものです：

| Name      | Attack | Sp. Atk | Label |
| --------- | ------ | ------- | ----- |
| Charizard | 84     | 109     | 1     |
| Machamp   | 130    | 65      | 0     |
| Gengar    | 65     | 130     | 1     |
| ...       | ...    | ...     | ...   |

### 2. 前処理

* `StandardScaler` により Attack / Sp. Atk を標準化
* 訓練・テストに 8:2 で分割

### 3. 分類器の訓練と評価

各モデルについて以下を実施：

* 学習（`model.fit()`）
* 正解率（Accuracy）の算出とプロットタイトルへの表示
* **決定領域の可視化**
* **混同行列の出力**
* **F1スコア・精度・再現率の出力**

---

## 📊 結果の可視化と評価指標

各モデルごとに以下のような図とともに、テストデータでの性能が評価されます：

* **プロット**：決定境界と分類結果
* **Accuracy**：テストデータに対する分類正解率
* **Confusion Matrix**
* **Classification Report（精度 / 再現率 / F1スコア）**

---

## ✅ 比較まとめ（一部例）

| モデル                 | Accuracy | 特徴               |
| ------------------- | -------- | ---------------- |
| Logistic Regression | 0.91     | 境界が直線的で分かりやすい    |
| Random Forest       | 0.95     | 非線形で柔軟、安定した性能    |
| XGBoost             | 0.96     | 強力な分類性能、微調整可能    |
| k-NN                | 0.89     | パラメータに敏感（kの選定重要） |
| SVM                 | 0.93     | 境界がシャープで適合性高い    |
| MLP                 | 0.92     | 多層による抽象化、過学習注意   |

---


コード
```python
# Program Name: pokemon_classifier_comparison.py
# Creation Date: 20250603
# Overview: Compare classification models on Pokémon data using Attack and Sp. Atk
# Usage: Run in Python environment with required libraries installed

# -------------------- Install Required Libraries --------------------
!pip install pandas numpy matplotlib scikit-learn xgboost

# -------------------- Import Libraries --------------------
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
from matplotlib.colors import ListedColormap
from xgboost import XGBClassifier

# -------------------- Parameters (一元管理) --------------------
RANDOM_STATE = 42
TEST_SIZE = 0.2
K_NEIGHBORS = 5
RESOLUTION = 0.1

# -------------------- Sample Data --------------------
data = [
    ['Bulbasaur', 49, 65, 1],
    ['Ivysaur', 62, 80, 1],
    ['Venusaur', 82, 100, 1],
    ['Charmander', 52, 60, 1],
    ['Charmeleon', 64, 80, 1],
    ['Charizard', 84, 109, 1],
    ['Squirtle', 48, 50, 1],
    ['Wartortle', 63, 65, 1],
    ['Blastoise', 83, 85, 1],
    ['Caterpie', 30, 20, 0],
    ['Metapod', 20, 25, 0],
    ['Butterfree', 45, 80, 1],
    ['Weedle', 35, 20, 0],
    ['Kakuna', 25, 25, 0],
    ['Beedrill', 90, 45, 0],
    ['Pidgey', 45, 35, 0],
    ['Pidgeotto', 60, 50, 0],
    ['Pidgeot', 80, 70, 0],
    ['Rattata', 56, 25, 0],
    ['Raticate', 81, 50, 0],
    ['Spearow', 60, 31, 0],
    ['Fearow', 90, 61, 0],
    ['Ekans', 60, 40, 0],
    ['Arbok', 85, 65, 0],
    ['Pikachu', 55, 50, 0],
    ['Raichu', 90, 90, 1],
    ['Sandshrew', 75, 20, 0],
    ['Sandslash', 100, 45, 0],
    ['Nidoran♀', 47, 40, 0],
    ['Nidorina', 62, 55, 0],
    ['Nidoqueen', 82, 75, 1],
    ['Nidoran♂', 57, 40, 0],
    ['Nidorino', 72, 55, 0],
    ['Nidoking', 92, 85, 1],
    ['Clefairy', 45, 60, 1],
    ['Clefable', 70, 85, 1],
    ['Vulpix', 41, 50, 1],
    ['Ninetales', 76, 81, 1],
    ['Jigglypuff', 45, 45, 0],
    ['Wigglytuff', 70, 75, 0],
    ['Zubat', 45, 30, 0],
    ['Golbat', 80, 65, 0],
    ['Oddish', 50, 75, 1],
    ['Gloom', 65, 85, 1],
    ['Vileplume', 80, 100, 1],
    ['Paras', 70, 55, 0],
    ['Parasect', 95, 60, 0],
    ['Venonat', 55, 40, 0],
    ['Venomoth', 65, 90, 1],
    ['Diglett', 55, 35, 0],
    ['Dugtrio', 80, 50, 0],
    ['Meowth', 45, 40, 0],
    ['Persian', 70, 65, 0],
    ['Psyduck', 52, 65, 1],
    ['Golduck', 82, 95, 1],
    ['Mankey', 80, 35, 0],
    ['Primeape', 105, 60, 0],
    ['Growlithe', 70, 50, 0],
    ['Arcanine', 110, 80, 0],
    ['Poliwag', 50, 40, 0],
    ['Poliwhirl', 65, 50, 0],
    ['Poliwrath', 85, 70, 0],
    ['Abra', 20, 105, 1],
    ['Kadabra', 35, 120, 1],
    ['Alakazam', 50, 135, 1],
    ['Machop', 80, 35, 0],
    ['Machoke', 100, 50, 0],
    ['Machamp', 130, 65, 0],
    ['Bellsprout', 75, 70, 1],
    ['Weepinbell', 90, 85, 1],
    ['Victreebel', 105, 100, 1],
    ['Tentacool', 40, 50, 1],
    ['Tentacruel', 70, 80, 1],
    ['Geodude', 80, 30, 0],
    ['Graveler', 95, 45, 0],
    ['Golem', 110, 55, 0],
    ['Ponyta', 85, 65, 0],
    ['Rapidash', 100, 80, 0],
    ['Slowpoke', 65, 40, 1],
    ['Slowbro', 75, 100, 1],
    ['Magnemite', 35, 95, 1],
    ['Magneton', 60, 120, 1],
    ['Farfetch’d', 65, 58, 0],
    ['Doduo', 85, 35, 0],
    ['Dodrio', 110, 60, 0],
    ['Seel', 45, 45, 0],
    ['Dewgong', 70, 70, 1],
    ['Grimer', 80, 40, 0],
    ['Muk', 105, 65, 0],
    ['Shellder', 65, 45, 0],
    ['Cloyster', 95, 85, 0],
    ['Gastly', 35, 100, 1],
    ['Haunter', 50, 115, 1],
    ['Gengar', 65, 130, 1],
    ['Onix', 45, 30, 0],
    ['Drowzee', 48, 43, 1],
    ['Hypno', 73, 73, 1],
    ['Krabby', 105, 25, 0],
    ['Kingler', 130, 50, 0],
    ['Voltorb', 30, 55, 1],
    ['Electrode', 50, 80, 1],
    ['Exeggcute', 40, 60, 1],
    ['Exeggutor', 95, 125, 1],
    ['Cubone', 50, 40, 0],
    ['Marowak', 80, 50, 0],
    ['Hitmonlee', 120, 35, 0],
    ['Hitmonchan', 105, 35, 0],
    ['Lickitung', 55, 60, 0],
    ['Koffing', 65, 60, 0],
    ['Weezing', 90, 85, 0],
    ['Rhyhorn', 85, 30, 0],
    ['Rhydon', 130, 45, 0],
    ['Chansey', 5, 35, 1],
    ['Tangela', 55, 100, 1],
    ['Kangaskhan', 95, 40, 0],
    ['Horsea', 40, 70, 1],
    ['Seadra', 65, 95, 1],
    ['Goldeen', 67, 35, 0],
    ['Seaking', 92, 65, 0],
    ['Staryu', 45, 70, 1],
    ['Starmie', 75, 100, 1],
    ['Mr. Mime', 45, 100, 1],
    ['Scyther', 110, 55, 0],
    ['Jynx', 50, 115, 1],
    ['Electabuzz', 83, 95, 1],
    ['Magmar', 95, 100, 1],
    ['Pinsir', 125, 55, 0],
    ['Tauros', 100, 40, 0],
    ['Magikarp', 10, 15, 0],
    ['Gyarados', 125, 60, 0],
    ['Lapras', 85, 85, 1],
    ['Ditto', 48, 48, 0],
    ['Eevee', 55, 45, 0],
    ['Vaporeon', 65, 110, 1],
    ['Jolteon', 65, 110, 1],
    ['Flareon', 130, 95, 0],
    ['Porygon', 60, 85, 1],
    ['Omanyte', 40, 90, 1],
    ['Omastar', 60, 115, 1],
    ['Kabuto', 80, 55, 0],
    ['Kabutops', 115, 65, 0],
    ['Aerodactyl', 105, 60, 0],
    ['Snorlax', 110, 65, 0],
    ['Articuno', 85, 95, 1],
    ['Zapdos', 90, 125, 1],
    ['Moltres', 100, 125, 1],
    ['Dratini', 64, 50, 0],
    ['Dragonair', 84, 70, 0],
    ['Dragonite', 134, 100, 0],
    ['Mewtwo', 110, 154, 1],
    ['Mew', 100, 100, 1]
]

df = pd.DataFrame(data, columns=['Name', 'Attack', 'Sp_Atk', 'Label'])

# -------------------- Feature & Target --------------------
X = df[['Attack', 'Sp_Atk']]
y = df['Label']

# -------------------- Train-Test Split --------------------
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)

# -------------------- Scaling --------------------
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# -------------------- Classifiers --------------------
models = {
    'Logistic Regression': LogisticRegression(),
    'k-NN': KNeighborsClassifier(n_neighbors=K_NEIGHBORS),
    'Decision Tree': DecisionTreeClassifier(random_state=RANDOM_STATE),
    'Random Forest': RandomForestClassifier(random_state=RANDOM_STATE),
    'SVM': SVC(probability=True),
    'XGBoost': XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=RANDOM_STATE),
    'Naive Bayes': GaussianNB(),
    'MLP': MLPClassifier(random_state=RANDOM_STATE, max_iter=1000)
}

# -------------------- Train & Plot --------------------
xx, yy = np.meshgrid(
    np.arange(X_train_scaled[:, 0].min() - 1, X_train_scaled[:, 0].max() + 1, RESOLUTION),
    np.arange(X_train_scaled[:, 1].min() - 1, X_train_scaled[:, 1].max() + 1, RESOLUTION)
)

fig, axes = plt.subplots(3, 3, figsize=(20, 15))
axes = axes.flatten()
cmap = ListedColormap(['#FFAAAA', '#AAAAFF'])

for idx, (name, model) in enumerate(models.items()):
    model.fit(X_train_scaled, y_train)
    y_pred = model.predict(X_test_scaled)
    accuracy = accuracy_score(y_test, y_pred)
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

    ax = axes[idx]
    ax.contourf(xx, yy, Z, alpha=0.3, cmap=cmap)
    ax.scatter(X_train_scaled[:, 0], X_train_scaled[:, 1], c=y_train, s=40, edgecolor='k', cmap=cmap)
    ax.set_title(f"{name}\nAccuracy: {accuracy:.2f}")
    ax.set_xlabel('Attack (Standardized)')
    ax.set_ylabel('Sp. Atk (Standardized)')

    # -------------------- Console Output --------------------
    print(f"===== {name} =====")
    print("Confusion Matrix:\n", confusion_matrix(y_test, y_pred))
    print("Classification Report:\n", classification_report(y_test, y_pred))

# -------------------- Hide Extra Axes --------------------
for ax in axes[len(models):]:
    ax.axis('off')

plt.tight_layout()
plt.show()

```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/d0329a4b-ee2e-41db-a704-6385bfb2710f.png)