はじめに
どうも、ポケモンマスターです。
はがねタイプって防御高くて、飛行タイプは素早さ高いですよね
・
・
・
え、ほんとに?
直感や体感だけで語るのもポケモンマスターらしくないので、実際に見てみましょう。
ということで、今回はPythonを使って全ポケモンの種族値データをタイプ別に集計し、本当にイメージ通りなのかを検証してみました。
🛠️ 設計と処理フロー
今回の検証を行うにあたり、裏側で動かしたデータ処理プロセスの設計図がこちらです。
Pythonを使って全ポケモンの種族値データをタイプ別に集計しました。
また、検証環境はGoogle Colabで行いました。
実行するソースコード①「CSVデータの作成」
import csv
import time
import requests
def get_pokemon_data(pokemon_id):
"""
指定されたIDのポケモンの基本情報と日本語名を取得する
"""
# 1. 種族値とタイプ情報を取得するためのエンドポイント
detail_url = f'https://pokeapi.co/api/v2/pokemon/{pokemon_id}'
# 2. 日本語の種族名を取得するためのエンドポイント
species_url = f'https://pokeapi.co/api/v2/pokemon-species/{pokemon_id}'
try:
# 詳細データの取得
detail_res = requests.get(detail_url)
if detail_res.status_code != 200:
return None
detail_data = detail_res.json()
# 日本語名の取得
species_res = requests.get(species_url)
if species_res.status_code != 200:
return None
species_data = species_res.json()
except requests.exceptions.RequestException as e:
print(f"\nID {pokemon_id} の取得中にエラーが発生しました: {e}")
return None
# 日本語名の抽出
japanese_name = None
for name_info in species_data.get('names', []):
if name_info['language']['name'] == 'ja':
japanese_name = name_info['name']
break
# 英語名しか存在しない特例ケース(あるいは万が一見つからない場合)のフォールバック
if not japanese_name:
japanese_name = detail_data['name']
# タイプの抽出 (1つまたは2つ)
types = [t_info['type']['name'] for t_info in detail_data.get('types', [])]
# 解析用のCSVが期待する「日本語タイプ名」へのマッピングテーブル
type_map_en_to_ja = {
"normal": "ノーマル", "fire": "ほのお", "water": "みず", "grass": "くさ",
"electric": "でんき", "ice": "こおり", "fighting": "かくとう", "poison": "どく",
"ground": "じめん", "flying": "ひこう", "psychic": "エスパー", "bug": "むし",
"rock": "いわ", "ghost": "ゴースト", "dragon": "ドラゴン", "steel": "はがね",
"fairy": "フェアリー", "dark": "あく"
}
# 英語のタイプ名を取得した上で日本語に変換
types_ja = [type_map_en_to_ja.get(t, t) for t in types]
type1 = types_ja[0] if len(types_ja) > 0 else "なし"
type2 = types_ja[1] if len(types_ja) > 1 else "" # 複合タイプがない場合は空文字
# 種族値(stats)の抽出
# PokéAPIのstats配列は、通常 [HP, Attack, Defense, Special-Attack, Special-Defense, Speed] の順に格納されています
stats_dict = {}
for stat_info in detail_data.get('stats', []):
stat_name = stat_info['stat']['name']
base_val = stat_info['base_stat']
stats_dict[stat_name] = base_val
return {
"名前": japanese_name,
"タイプ1": type1,
"タイプ2": type2,
"HP": stats_dict.get("hp", 0),
"攻撃": stats_dict.get("attack", 0),
"防御": stats_dict.get("defense", 0),
"特攻": stats_dict.get("special-attack", 0),
"特防": stats_dict.get("special-defense", 0),
"素早さ": stats_dict.get("speed", 0)
}
def main():
start_id = 1
end_id = 1025
filepath = "/content/drive/My Drive/Colab Notebooks/Qiita/ポケモン/"
filename = filepath + "pokemon_status.csv"
print(f"PokéAPIからID {start_id} から {end_id} までのデータを取得し、{filename} を作成します...")
headers = ["名前", "タイプ1", "タイプ2", "HP", "攻撃", "防御", "特攻", "特防", "素早さ"]
# newline='' を指定してCSV出力時の余計な空行を防ぐ
with open(filename, mode='w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=headers)
writer.writeheader()
success_count = 0
for p_id in range(start_id, end_id + 1):
# API負荷軽減のためのインターバル (0.2秒)
time.sleep(0.2)
data = get_pokemon_data(p_id)
if data:
writer.writerow(data)
success_count += 1
# 簡易的な進捗表示
print(f"\r進捗: {p_id}/{end_id} 件処理完了 (取得成功: {data['名前']})", end="", flush=True)
else:
print(f"\nID {p_id} のデータ取得に失敗しました。")
print(f"\nCSVファイルの作成が完了しました。計 {success_count} 件のデータを保存しました。")
if __name__ == "__main__":
main()
ソースコードの説明です。
非公式の「PokeAPI」を用いて、1025匹までのポケモンのデータを取得し、CSVデータとして保存します。
PIではたくさんの情報が取得できますが、今回の分析に使うのは「名前」「タイプ1」「タイプ2」と「種族値(HP、攻撃、防御、特攻、特防、素早さ)」です。
実行するソースコード②「Pandasで読込→可視化・分析結果の出力」
import pandas as pd
import numpy as np
# 全ポケモンのタイプ一覧
TYPES = [
"ノーマル", "ほのお", "みず", "くさ", "でんき", "こおり", "かくとう", "どく",
"じめん", "ひこう", "エスパー", "むし", "いわ", "ゴースト", "ドラゴン", "はがね", "フェアリー", "あく"
]
def load_and_clean_data(filepath):
"""
ポケモンデータを読み込み、タイプ1・タイプ2を統合して集計しやすい形に整形する
"""
df = pd.read_csv(filepath)
# 今回必要な列(名前、タイプ1、タイプ2、種族値6ステータス)のみを抽出
status_cols = ["HP", "攻撃", "防御", "特攻", "特防", "素早さ"]
df = df[["名前", "タイプ1", "タイプ2"] + status_cols]
return df, status_cols
def calculate_type_averages(df, status_cols):
"""
各タイプごとに種族値の平均値を算出する
"""
type_stats = {}
for t in TYPES:
# タイプ1またはタイプ2に指定のタイプが含まれるポケモンをフィルタリング
type_df = df[(df["タイプ1"] == t) | (df["タイプ2"] == t)]
# 各ステータスの平均値を計算
averages = type_df[status_cols].mean().round(1).to_dict()
averages["合計"] = round(type_df[status_cols].sum(axis=1).mean(), 1)
averages["該当数"] = len(type_df)
type_stats[t] = averages
return pd.DataFrame(type_stats).T
if __name__ == "__main__":
# データの読み込み
filepath = "/content/drive/My Drive/Colab Notebooks/Qiita/ポケモン/"
filename = filepath + "pokemon_status.csv"
df, cols = load_and_clean_data(filename)
result_df = calculate_type_averages(df, cols)
# 合計種族値の平均が高い順にソートして表示
print(result_df.sort_values(by="合計", ascending=False))
ソースコードの説明です。
1. CSVデータを読み込んで、スリムにする
まず、①で作成したCSVファイル(pokemon_status.csv)を読み込みます。
CSVにはたくさんの情報が入っていますが、今回の分析に使うのは「名前」「タイプ1」「タイプ2」と「種族値(HP、攻撃、防御、特攻、特防、素早さ)」だけです。
無駄のカット: Pandasを使って、不要な列を削ぎ落とし、今回の集計に必要な列だけに絞り込んだ綺麗なテーブル(DataFrame)をメモリ上に準備します。
2. タイプごとのループ処理とフィルタリング
次に、コード内で定義した18のタイプのループを回します。
各ループ内では、現在対象となっているタイプが「タイプ1」または「タイプ2」のどちらか一方にでも設定されているポケモンのみを抽出し、そのタイプ専用のサブデータフレームを生成します。
(例えば「ほのおタイプ」の処理のときは、タイプ1 または タイプ2 のどちらかに「ほのお」と書かれているポケモン(リザードンなど)だけを、データベースからガバッと引き抜いて集めます。
そうして集まった「ほのおタイプ」のメンバー全員のHP、攻撃、防御などの各ステータスについて、それぞれ平均値を計算します。)
さらに、各ポケモンの「6ステータスの合計値」の平均と、そのタイプに何匹のポケモンが所属していたか(分母)をカウントし、1つのデータセット(辞書型)にまとめます。
3. 1つに合体させて、強い順に並べ替えて出力する
すべてのタイプ(18回分)の計算が終わったら、バラバラだったタイプごとの集計結果を1つの大きな表に合体させます。
合体させた表を、合計(合計種族値の平均値)の列を基準にして、数値が高い順(降順)に並べ替えます。
最後に、綺麗にソートされた表をコンソール画面に表示します。これで、「どのタイプが最も強くて、どのタイプが悲しい結果になっているか」が一目でわかるランキングが完成します。
ということで、結果を見てみましょう。
💡 実行結果
| タイプ | 該当数 | HP | 攻撃 | 防御 | 特攻 | 特防 | 素早さ | 合計平均 |
|---|---|---|---|---|---|---|---|---|
| ドラゴン | 70 | 84.1 | 94.0 | 83.0 | 87.2 | 78.3 | 78.2 | 504.8 |
| はがね | 65 | 71.0 | 90.7 | 107.9 | 71.1 | 78.9 | 60.2 | 479.7 |
| かくとう | 73 | 77.8 | 104.3 | 76.4 | 63.6 | 69.7 | 75.4 | 467.1 |
| あく | 69 | 76.3 | 93.6 | 71.4 | 72.7 | 68.9 | 76.8 | 459.6 |
| エスパー | 102 | 73.8 | 67.3 | 72.6 | 88.0 | 84.2 | 71.6 | 457.5 |
| こおり | 48 | 80.1 | 82.3 | 75.7 | 74.9 | 75.2 | 65.4 | 453.6 |
| ほのお | 81 | 69.9 | 80.6 | 67.5 | 86.3 | 71.0 | 74.1 | 449.5 |
| ゴースト | 65 | 65.7 | 77.4 | 77.5 | 81.6 | 79.1 | 66.2 | 447.5 |
| でんき | 69 | 68.7 | 74.9 | 64.9 | 84.2 | 67.7 | 81.9 | 442.2 |
| いわ | 74 | 68.9 | 87.2 | 103.5 | 58.5 | 72.3 | 51.8 | 442.1 |
| ひこう | 109 | 70.4 | 76.7 | 65.6 | 71.6 | 68.5 | 82.4 | 435.2 |
| じめん | 75 | 77.1 | 88.4 | 85.5 | 57.5 | 64.8 | 58.5 | 431.7 |
| フェアリー | 64 | 67.2 | 64.7 | 70.1 | 77.2 | 83.9 | 65.7 | 428.9 |
| みず | 154 | 69.6 | 72.1 | 72.2 | 71.0 | 68.6 | 65.6 | 419.1 |
| くさ | 127 | 66.1 | 74.8 | 71.7 | 71.7 | 71.8 | 59.7 | 415.8 |
| どく | 83 | 66.0 | 71.6 | 66.3 | 71.0 | 69.4 | 68.5 | 412.8 |
| ノーマル | 131 | 76.6 | 74.0 | 60.3 | 59.1 | 63.9 | 68.9 | 402.8 |
| むし | 92 | 56.6 | 68.0 | 69.2 | 56.8 | 64.2 | 60.2 | 374.9 |
💡 分析結果と考察
番狂わせを期待していたのですが、結果は「直感の通り(というか、それ以上)」でした。
1. やっぱり「はがね」「ドラゴン」は強い🐉
- ドラゴンタイプ: 合計平均が 504.8 をマーク。伝説のポケモンや600族がひしめき合っているため、平均値の段階で他のタイプを圧倒しています。
- はがねタイプ: 防御(B)の平均が驚異の 107.9。今回の算出結果の中で一番大きい数値をたたき出しています。カッチカチですね。
2. 「むしタイプ」はあまりにも可哀想🐛
一方で、合計平均の最下位争いに目を向けると、そこには案の定むしタイプの姿が。
- 合計平均値は驚きの 374.9。ドラゴンの平均値と約 130 の差をつけられています。
- 序盤に出てくる虫ポケモンたち(キャタピーやビードルなど)が平均値を強烈に引き下げているのが要因ですが、それにしてもこの格差社会は涙を禁じ得ません😢。
📝 まとめ
今回のデータ分析の結果です↓。
- 直感は正しかった: 「はがねは硬い」「ドラゴンは最強」という認識は、データ上でも一切ブレずにそのまま表れていました。
- データの裏にある仕様: むしタイプの数値が低いのは、ゲームデザインにおける「序盤の育成のしやすさ・進化の早さ」というゲームバランス(内部仕様)がそのままデータに投影されているためと考えられます。
データは嘘をつきませんね。
次に旅に出るときや対戦メンバーを決めるときは、この無慈悲な数値を少しだけ思い出して、あえて「むしタイプ」を愛用してみるのもポケモンマスターの嗜みかもしれません。
それでは、良いポケモンライフを!
📊 いつか作成予定の記事
「進化のポケモン」が平均を下げているのではないか?という疑問が生じたため、最終進化系や進化前のみのようにフィルタリングして再集計をしてみようと思います。
むしタイプの下克上が起きたら面白いという希望にかけて。