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傑です!とりあえず今回のデータセット内ではこいつらを使えば勝ちやすくなるみたいです!