データとかITとか統計とか好きなこと

データや統計に関すること&ITなどが好きで仕事にも生かせてます。音楽とかyoutubeも好き。

Pokemon Challenge - ポケモンデータセットを使った解析トライ

Pokemon Challenge - ポケモンデータセットを使った解析トライ

Kaggleを見ていると面白そうなデータセットを見つけたので、暇つぶしに考察してみます。素人ですので本格的な解析を求める方はKaggleに行ってください。 https://www.kaggle.com/terminus7/pokemon-challenge/kernels ポケモンの対戦データセットです。データセットの中身は,combats.csv, pokemon.csv, tests.csvの3ファイル。 combats.csvはバトル結果が記録されています。お互いの図鑑番号と勝者側番号です。先に書いてある方が先制側です。50,000戦の結果があります。 pokemon.csvポケモン図鑑ですね。番号、名前、タイプ1、タイプ2、各種族値(攻撃、防御、特攻、特防、素早さ)、世代、伝説系か。800体分あります。 tests.csvはテストデータです。対戦組合せが提示されているのでその勝敗を予想しなさいということです。 とりあえず今回はデータからどの様な傾向があるのかなーっていう解析をしてみたいと思います。

ポケモンルビサファまでしかやってない軽度ユーザーの自分からしても、努力値のデータも技構成のデータなどもない種族値だけのデータでは何を予想しても仕方ない気がするのですが、とりあえず触ってみます。この対戦結果はどの様な環境から取ってきたものなんですかね。

データセットを軽く見ていきます。combats.csvから。

import pandas as pd
combats = pd.read_csv('combats.csv')
combats.head(3)

266番と298番が闘って298番が勝ってます。266番と298番ってどのポケモンでしょうか?pokemon.csvから調べます。

pokemon = pd.read_csv('pokemon.csv')
pokemon_266_298 = pokemon[pokemon['#'].isin([266, 298])]
pokemon_266_298

LavitarとNuzleafです。和名はLavitarヨーギラスとNuzleafコノハナです。こいつらです。

勝ったのはヨーギラスヨーギラスは岩地面で、コノハナは草悪。タイプ面でも普通にコノハナの方が有利ですね。ちなみにタイプ相性はこちら

そんな感じで対戦結果の上から3つはこんな感じ

names_dict = dict(zip(pokemon['#'], pokemon['Name']))
cols = ["First_pokemon","Second_pokemon","Winner"]
combats_name = combats[cols].replace(names_dict)
combats_name.head(3)

Virizionビリジオン(格闘草):Terrakionテラキオン(格闘岩)-> テラキオン勝利 Togeticトゲチック(フェアリー飛行):Beheeyemオーベムエスパー)-> オーベム勝利 そんな感じらしいです。うーんあまり傾向読めませんね。でも比較的同レベル同士が対戦してる感じがします。ポケモン種族値合計で700族や600族などのクラス分けされる傾向があるので調べてみましょう。 その前にまずはそもそもの全ポケモン種族値分布を。

pokemon["stats_sum"] = pokemon["HP"] + pokemon["Attack"] + pokemon["Defense"] + pokemon["Sp. Atk"] + pokemon["Sp. Def"] + pokemon["Speed"]
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
print(pokemon["stats_sum"].describe())
sns.distplot(pokemon["stats_sum"])
plt.show()
count    800.00000
mean     435.10250
std      119.96304
min      180.00000
25%      330.00000
50%      450.00000
75%      515.00000
max      780.00000
Name: stats_sum, dtype: float64

300族と500族あたりに山がありますね。さてでは戦闘組み合わせの種族値差分布を見ます。

stats_sum_dict = dict(zip(pokemon['#'], pokemon['stats_sum']))
combats_stats_sum = combats[cols].replace(stats_sum_dict)
diff_stats_sum = abs(combats_stats_sum["First_pokemon"]-combats_stats_sum["Second_pokemon"])
print(diff_stats_sum.describe())
sns.distplot(diff_stats_sum)
plt.xlabel("diff_stats_sum")
plt.show()
count    50000.000000
mean       136.551440
std        101.221212
min          0.000000
25%         54.000000
50%        118.000000
75%        200.000000
max        590.000000
dtype: float64

うーん差が少ない方向に偏ってはいますが、種族値差が開いている組合せも結構あります。中間値が118です。100の差ってどうなんですかね、結構重要そうに思えます。次は種族値が高い方がちゃんと勝ってるのか調べましょう。

combats_stats_sum["Loser"] = combats_stats_sum.apply(lambda x: x["First_pokemon"] if x["First_pokemon"] !=  x["Winner"] else x["Second_pokemon"], axis = 1)
diff_win_lose_stats = combats_stats_sum["Winner"] - combats_stats_sum["Loser"]
print(diff_win_lose_stats.describe())
sns.distplot(diff_win_lose_stats)
plt.xlabel("diff_win_lose_stats")
plt.show()
count    50000.000000
mean        79.776320
std        150.093351
min       -562.000000
25%        -20.000000
50%         80.000000
75%        186.000000
max        590.000000
dtype: float64

まぁ中間値80と比較的種族値が高い方が勝つ傾向は見て取れます。種族値というものが大きな勝利要因になっていることは言えるでしょう。でも種族値200以上も差がついていながら低い方が勝利するという下剋上な結果もあります。 下剋上を起こした組み合わせはどの様な組合せか見てみます。ここでの下剋上の定義は種族値100以上差がありながら低い方が勝つことととします。

stats_sum_dict_re = dict(zip(pokemon['stats_sum'], pokemon['#']))
combats_stats_sum["diff"] = diff_win_lose_stats
surpassing_stats_sum = combats_stats_sum[combats_stats_sum["diff"] < -100]
print ("Surpassing one's superiors Battle number : " + str(len(surpassing_stats_sum)))
surpassing_id = surpassing_stats_sum[cols].replace(stats_sum_dict_re)
surpassing_name = surpassing_id[cols].replace(names_dict)
surpassing_name.join(combats_stats_sum["diff"]).head(8)
Surpassing one's superiors Battle number : 5716

下剋上の組合せは5716組あります。全体の約10%です。上から8個取り出すとこんな感じ。

結構面白いですね。自分でも見たことある映画伝説勢のボルケニオンがいかにも一般勢なゴチミルというポケモンに負けてます。最大の下剋上の組み合わせが気になってきたので調べてみます。

surpassing_name.join(combats_stats_sum["diff"]).sort_values(by="diff").head(4)

メガレックウザやられすぎですね。ピィがメガレックウザに勝つことなんてあるんでしょうか??562の種族値差です。

ではこれらの下剋上はなぜ起こったのかを探っていきましょう。思いつく理由としては、やはり先制攻撃ではないでしょうか。最近のポケモンは技も多様化しているので、種族値差があっても先攻を取ることで一気に落としてしまうことも見られる気がします。(まぁ上記のメガレックウザvsピィは後攻のピィが連勝していますが)

combats_stats_sum["First_Win"] =  combats_stats_sum.apply(lambda x: 1 if x["First_pokemon"] ==  x["Winner"] else 0, axis = 1)
surpassing_stats_sum = combats_stats_sum[combats_stats_sum["diff"] < -100]
sns.distplot(surpassing_stats_sum[surpassing_stats_sum["First_Win"]==0]["diff"], label="First_pokemon_win=0")
sns.distplot(surpassing_stats_sum[surpassing_stats_sum["First_Win"]==1]["diff"], label="First_pokemon_win=1")
plt.legend()
plt.show()

うーん全く差がないですね。。下剋上組合せだけでなく全体でも同じでしょうか

sns.distplot(combats_stats_sum[combats_stats_sum["First_Win"]==0]["diff"], label="First_Win=0")
sns.distplot(combats_stats_sum[combats_stats_sum["First_Win"]==1]["diff"], label="First_Win=1")
print ("-First_Win=0-")
print (combats_stats_sum[combats_stats_sum["First_Win"]==0]["diff"].describe())
print ("-First_Win=1-")
print (combats_stats_sum[combats_stats_sum["First_Win"]==1]["diff"].describe())
plt.legend()
plt.show()
-First_Win=0-
count    26065.000000
mean        76.155764
std        152.292707
min       -562.000000
25%        -27.000000
50%         79.000000
75%        185.000000
max        585.000000
Name: diff, dtype: float64
-First_Win=1-
count    23935.000000
mean        83.719072
std        147.563182
min       -544.000000
25%        -13.000000
50%         82.000000
75%        190.000000
max        590.000000
Name: diff, dtype: float64

全体でもほとんど差がない上に、むしろ先攻の方が勝ち数少ないですw なんなんでしょうかね1ターン目はステータスアップ系の技でも使う傾向が高いんですかね。とりあえず先制後攻はほとんど関係ないみたいです。 じゃあ次はタイプ相性を見ます。ポケモンといえばやはりタイプ相性でしょう。まずは存在するタイプ一覧を回収。

print ("There are {} Types.".format(len(pokemon["Type 1"].drop_duplicates())))
list(pokemon["Type 1"].drop_duplicates())
There are 18 Types

['Grass',
 'Fire',
 'Water',
 'Bug',
 'Normal',
 'Poison',
 'Electric',
 'Ground',
 'Fairy',
 'Fighting',
 'Psychic',
 'Rock',
 'Ghost',
 'Ice',
 'Dragon',
 'Dark',
 'Steel',
 'Flying']

18種類あるみたいですね。今はタイプが2つ持てますからその組み合わせまで考えると何通りあるんでしょうか。

type_cols = ["Type 1", "Type 2"]
print ("There are {} type-combinations.".format(len(pokemon[type_cols].drop_duplicates())))
There are 154 type-combinations.

154種類です。考えられる組合せは18*18/2=162ですからほぼ全ての組合せがいますね。これらの分布を可視化してみます。

pokemon["Type 2"] = pokemon["Type 2"].fillna("None")
type_cross = pd.crosstab(pokemon["Type 1"], pokemon["Type 2"])
type_cross.plot.bar(stacked=True, figsize=(8,6))
plt.legend(bbox_to_anchor=(0.01, 0.99), loc='upper left', ncol=3, fontsize=8, title="Type 2")
plt.show()

これだけの組合せがありますから比較検証するのは大変そうですね。。 ひとまずタイプ相関も数値化します。基本的にはダメージ変動は効果バツグンなら2倍、効果はいまひとつで0.5倍なので、それに応じた対応表を作ります。対応表見ながら手づくりします。

Normal = {"Normal": 1, "Fighting": 1, "Poison": 1, "Ground": 1, "Flying": 1, "Bug": 1, "Rock": 0.5, "Ghost": 0, "Steel": 0.5, "Fire": 1, "Water": 1, "Electric": 1, "Grass": 1, "Ice": 1, "Psychic": 1, "Dragon": 1, "Dark": 1, "Fairy": 1}
Fighting = {"Normal": 2, "Fighting": 1, "Poison": 0.5, "Ground": 1, "Flying": 0.5, "Bug": 0.5, "Rock": 2, "Ghost": 0, "Steel": 2, "Fire": 1, "Water": 1, "Electric": 1, "Grass": 1, "Ice": 2, "Psychic": 0.5, "Dragon": 1, "Dark": 2, "Fairy": 0.5}
Poison = {"Normal": 1, "Fighting": 1, "Poison": 0.5, "Ground": 0.5, "Flying": 1, "Bug": 1, "Rock": 0.5, "Ghost": 0.5, "Steel": 0, "Fire": 1, "Water": 1, "Electric": 1, "Grass": 2, "Ice": 1, "Psychic": 1, "Dragon": 1, "Dark": 1, "Fairy": 2}
Ground = {"Normal": 1, "Fighting": 1, "Poison": 2, "Ground": 1, "Flying": 0, "Bug": 0.5, "Rock": 2, "Ghost": 1, "Steel": 2, "Fire": 2, "Water": 1, "Electric": 2, "Grass": 0.5, "Ice": 1, "Psychic": 1, "Dragon": 1, "Dark": 1, "Fairy": 1}
Flying = {"Normal": 1, "Fighting": 2, "Poison": 1, "Ground": 1, "Flying": 1, "Bug": 2, "Rock": 0.5, "Ghost": 1, "Steel": 0.5, "Fire": 1, "Water": 1, "Electric": 0.5, "Grass": 2, "Ice": 1, "Psychic": 1, "Dragon": 1, "Dark": 1, "Fairy": 1}
Bug = {"Normal": 1, "Fighting": 0.5, "Poison": 0.5, "Ground": 1, "Flying": 0.5, "Bug": 1, "Rock": 1, "Ghost": 0.5, "Steel": 0.5, "Fire": 0.5, "Water": 1, "Electric": 1, "Grass": 2, "Ice": 1, "Psychic": 2, "Dragon": 1, "Dark": 2, "Fairy": 0.5}
Rock = {"Normal": 1, "Fighting": 0.5, "Poison": 1, "Ground": 0.5, "Flying": 2, "Bug": 2, "Rock": 1, "Ghost": 1, "Steel": 0.5, "Fire": 2, "Water": 1, "Electric": 1, "Grass": 1, "Ice": 2, "Psychic": 1, "Dragon": 1, "Dark": 1, "Fairy": 1}
Ghost = {"Normal": 0, "Fighting": 1, "Poison": 1, "Ground": 1, "Flying": 1, "Bug": 1, "Rock": 1, "Ghost": 2, "Steel": 1, "Fire": 1, "Water": 1, "Electric": 1, "Grass": 1, "Ice": 1, "Psychic": 2, "Dragon": 1, "Dark": 0.5, "Fairy": 1}
Steel = {"Normal": 1, "Fighting": 1, "Poison": 1, "Ground": 1, "Flying": 1, "Bug": 1, "Rock": 2, "Ghost": 1, "Steel": 0.5, "Fire": 0.5, "Water": 0.5, "Electric": 0.5, "Grass": 1, "Ice": 2, "Psychic": 1, "Dragon": 1, "Dark": 1, "Fairy": 0.5}
Fire = {"Normal": 1, "Fighting": 1, "Poison": 1, "Ground": 1, "Flying": 1, "Bug": 2, "Rock": 0.5, "Ghost": 1, "Steel": 2, "Fire": 0.5, "Water": 0.5, "Electric": 1, "Grass": 2, "Ice": 2, "Psychic": 1, "Dragon": 0.5, "Dark": 1, "Fairy": 1}
Water = {"Normal": 1, "Fighting": 1, "Poison": 1, "Ground": 2, "Flying": 1, "Bug": 1, "Rock": 2, "Ghost": 1, "Steel": 1, "Fire": 2, "Water": 0.5, "Electric": 1, "Grass": 0.5, "Ice": 1, "Psychic": 1, "Dragon": 0.5, "Dark": 1, "Fairy": 1}
Electric = {"Normal": 1, "Fighting": 1, "Poison": 1, "Ground": 0, "Flying": 2, "Bug": 1, "Rock": 1, "Ghost": 1, "Steel": 1, "Fire": 1, "Water": 2, "Electric": 0.5, "Grass": 0.5, "Ice": 1, "Psychic": 1, "Dragon": 0.5, "Dark": 1, "Fairy": 1}
Grass = {"Normal": 1, "Fighting": 1, "Poison": 0.5, "Ground": 2, "Flying": 0.5, "Bug": 0.5, "Rock": 2, "Ghost": 1, "Steel": 0.5, "Fire": 0.5, "Water": 2, "Electric": 1, "Grass": 0.5, "Ice": 1, "Psychic": 1, "Dragon": 0.5, "Dark": 1, "Fairy": 1}
Ice = {"Normal": 1, "Fighting": 1, "Poison": 1, "Ground": 2, "Flying": 2, "Bug": 1, "Rock": 1, "Ghost": 1, "Steel": 0.5, "Fire": 0.5, "Water": 0.5, "Electric": 1, "Grass": 2, "Ice": 0.5, "Psychic": 1, "Dragon": 2, "Dark": 1, "Fairy": 1}
Psychic = {"Normal": 1, "Fighting": 1, "Poison": 2, "Ground": 2, "Flying": 1, "Bug": 1, "Rock": 1, "Ghost": 1, "Steel": 0.5, "Fire": 1, "Water": 1, "Electric": 1, "Grass": 1, "Ice": 1, "Psychic": 0.5, "Dragon": 1, "Dark": 0, "Fairy": 1}
Dragon = {"Normal": 1, "Fighting": 1, "Poison": 1, "Ground": 1, "Flying": 1, "Bug": 1, "Rock": 1, "Ghost": 1, "Steel": 0.5, "Fire": 1, "Water": 1, "Electric": 1, "Grass": 1, "Ice": 1, "Psychic": 1, "Dragon": 2, "Dark": 1, "Fairy": 0}
Dark = {"Normal": 1, "Fighting": 0.5, "Poison": 1, "Ground": 1, "Flying": 1, "Bug": 1, "Rock": 1, "Ghost": 2, "Steel": 1, "Fire": 1, "Water": 1, "Electric": 1, "Grass": 1, "Ice": 1, "Psychic": 2, "Dragon": 1, "Dark": 0.5, "Fairy": 0.5}
Fairy = {"Normal": 1, "Fighting": 2, "Poison": 0.5, "Ground": 1, "Flying": 1, "Bug": 1, "Rock": 1, "Ghost": 1, "Steel": 0.5, "Fire": 0.5, "Water": 1, "Electric": 1, "Grass": 1, "Ice": 1, "Psychic": 1, "Dragon": 2, "Dark": 2, "Fairy": 1}

type_relation = {"Normal": Normal, "Fighting": Fighting, "Poison": Poison, "Ground": Ground, "Flying": Flying, "Bug": Bug, "Rock": Rock, "Ghost": Ghost, "Steel": Steel, "Fire": Fire, "Water": Water, "Electric": Electric, "Grass": Grass, "Ice": Ice, "Psychic": Psychic, "Dragon": Dragon, "Dark": Dark, "Fairy": Fairy}
df_type_relation = pd.DataFrame(type_relation)
print ("Row is Diffender, Column is Attacker")
df_type_relation
Row is Diffender, Column is Attacker

対応表ができたのでまずはタイプの勝敗結果への影響を見てみます。勝利側を攻撃側、敗退側を防御側として各タイプに対する数値を掛け合わせたもので比較してみます。無効の関係性にあるものは数値0だと掛け合わせ時に困るので0.25に変更します。

pokemon["Type"] = pokemon.apply(lambda x: x["Type 1"]+"/"+x["Type 2"], axis=1)
type_dict = dict(zip(pokemon['#'], pokemon['Type']))
combats_type = combats[cols].replace(type_dict)
combats_type["Loser"] = combats_type.apply(lambda x: x["First_pokemon"] if x["First_pokemon"] !=  x["Winner"] else x["Second_pokemon"], axis = 1)

zero_dict = {0: 0.25}
df_type_relation = df_type_relation[:].replace(zero_dict)

def calcRelation(combats_type):
    r0 = 1
    win_type1 = combats_type["Winner"].split("/")[0]
    win_type2 = combats_type["Winner"].split("/")[1]
    lose_type1 = combats_type["Loser"].split("/")[0]
    lose_type2 = combats_type["Loser"].split("/")[1]
    if win_type2 != "None" and lose_type2 != "None":
        r1 = df_type_relation[win_type1][lose_type1]
        r2 = df_type_relation[win_type1][lose_type2]
        r3 = df_type_relation[win_type2][lose_type1]
        r4 = df_type_relation[win_type2][lose_type2]
        r = r0 * r1 * r2 * r3 * r4
    elif win_type2 != "None" and lose_type2 == "None":
        r1 = df_type_relation[win_type1][lose_type1]
        r3 = df_type_relation[win_type2][lose_type1]
        r = r0 * r1 * r3
    elif win_type2 == "None" and lose_type2 != "None":
        r1 = df_type_relation[win_type1][lose_type1]
        r2 = df_type_relation[win_type1][lose_type2]
        r = r0 * r1 * r2
    elif win_type2 == "None" and lose_type2 == "None":
        r1 = df_type_relation[win_type1][lose_type1]
        r = r0 * r1
    return r

combats_type["Relation"] = combats_type.apply(lambda x: calcRelation(x), axis = 1)
print (combats_type["Relation"].describe())
sns.distplot(combats_type["Relation"])
plt.show()
count    50000.000000
mean         1.146062
std          0.869392
min          0.031250
25%          0.500000
50%          1.000000
75%          1.000000
max         16.000000
Name: Relation, dtype: float64

あまり特徴的な点はない様ですね。むしろ不利なタイプ相手の方が若干勝星多そうな感じがしますね。まぁ今回の係数の組合せ程度の比較ではあまり見えてこないということですね。この辺はまた時間ある時にでも考察できればと思います。

もうなんだか光が見えないですし、技構成とか戦闘環境とかわからなくて情報が少なすぎる!ってことで、最後に戦闘勝率が高いポケモンが何か調べて終わります! まずは組み合わせに出てくる頻度の高いポケモンは何でしょうか。

import numpy as np
from wordcloud import WordCloud, ImageColorGenerator
from PIL import Image

combats_names = combats[cols].replace(names_dict)
print (combats_names["Winner"].value_counts()[:10])
winners = list(combats_names["Winner"])
winners_str = [str(i) for i in winners]
winners_text = (",").join(winners_str)
maskPicture = np.array(Image.open("Pikachu.png", "r"))
imageColor = ImageColorGenerator(maskPicture)
wc = WordCloud(background_color= "black", random_state=1, margin=3, mask=maskPicture).generate(winners_text)
plt.figure(figsize=(10,10))
plt.axis("off")
plt.imshow(wc.recolor(color_func=imageColor))
plt.show()
Mewtwo                152
Aerodactyl            136
Infernape             136
Jirachi               134
Deoxys Speed Forme    133
Slaking               133
Murkrow               130
Mega Absol            130
Mega Houndoom         128
Mega Aerodactyl       127
Name: Winner, dtype: int64

WordCloud上で目立っているTherian FormeとIncarnate Formeは霊獣フォルムと化身フォルムで、ランドロスボルトロストルネロスという奴らの共通変化形態であり重複してる様です。

勝星数10傑はMewtwoミュウツー, Aerodactylプテラ, Infernapeゴウカザル, Jirachiジラーチ, Deoxys Speed Formeデオキシススピードフォルム, Slakingケッキング, Murkrowムクロー, Mega Absolメガアブソル, Mega Houndoomメガヘルガー, Mega Aerodactylメガプテラです。 ただこれらは単純に勝星数でありたくさん戦えばいいものなので求めるものとは違う気がするので勝率観点で調べてみましょう。

first_num = combats_names["First_pokemon"].value_counts()
second_num = combats_names["Second_pokemon"].value_counts()
battle_num = first_num + second_num
battle_win = pd.DataFrame({"battle": battle_num, "win": combats_names["Winner"].value_counts()}, columns=["battle", "win"])
battle_win["ratio"] = battle_win["win"]/battle_win["battle"]
battle_win.sort_values(by=["ratio"], ascending=False).head(10)

出ました。これが勝率10傑です!とりあえず今回のデータセット内ではこいつらを使えば勝ちやすくなるみたいです!

今回は以上にしておきます。次回は機械学習を使ってtests.csvを簡単に予測などしてみたいなと思っています。