Python

Pythonで数理最適化やってみた。【初級レベル:ポケモンリーグを救いたい①】

こんにちは。
分析官の望月です。

前回の記事では数理最適化ライブラリPuLPを用いてシンプルな線形計画問題の例として領域における最大・最小問題を解きました。
今回からは数理最適化の使い道を具体的な例をもとに紹介していきたいと思います。
初回はゲーム“ポケットモンスター ハートゴールド/ソウルシルバー”で登場するポケモンリーグを題材として扱っていきます。

ポケモンリーグとは?

基本的にポケモンのゲームは色々な町にあるポケモンジムのジムリーダーに勝利してジムバッジを獲得して旅を進めていくストーリー展開になっています。
このジムバッジをコンプリートして、チャンピオンロードという険しい道のりを潜り抜けたものだけが四天王とチャンピオンが待ち構えるポケモンリーグに挑戦することができます。
ポケモンリーグはいわばポケモン界の最後の砦といっても過言ではないでしょう。

ポケモンリーグの課題

そんなポケモンリーグで登場する四天王とチャンピオンの手持ちポケモンを確認してみます。

四天王1人目:イツキ
・ドータクン(はがね・エスパー)
・ブーピッグ(エスパー)
・ヤドラン(みず・エスパー)
・ルージュラ(こおり・エスパー)
・サーナイト(エスパー)
・ネイティオ(エスパー・ひこう)

四天王2人目:キョウ
・スカタンク(どく・あく)
・ドグロッグ(どく・かくとう)
・ベトベトン(どく)
・マルノーム(どく)
・モルフォン(むし・どく)
・クロバット(どく・ひこう)

四天王3人目:シバ
・カポエラー(かくとう)
・サワムラー(かくとう)
・エビワラー(かくとう)
・ハリテヤマ(かくとう)
・ルカリオ(かくとう・はがね)
・カイリキー(かくとう)

四天王4人目:カリン
・マニューラ(あく・こおり)
・アブソル(あく)
・ミカルゲ(ゴースト・あく)
・ヘルガー(あく・ほのお)
・ドンカラス(あく・ひこう)
・ブラッキー(あく)

チャンピオン:ワタル
・ボーマンダ(ドラゴン・ひこう)
・ギャラドス(みず・ひこう)
・リザードン(ほのお・ひこう)
・ガブリアス(ドラゴン・じめん)
・チルタリス(ドラゴン・ひこう)
・カイリュー(ドラゴン・ひこう)

ポケモンはそれぞれタイプによって得意・不得意な相手がいます。
例)ほのおタイプはみず, じめん, いわタイプに弱い

あらためて手持ちポケモンを確認してみてください。
そうなんです。タイプに偏りがありすぎて弱点を付かれやすいのがポケモンリーグ最大の弱点なのです。このため、全国の少年少女紳士淑女にボッコボコにされているのが現状。
ポケモントレーナーの最高峰に君臨する四天王・チャンピオンがこんな有様でいいのか?
ポケモンリーグを救いたい。
なにか自分に出来ることはないのか?そうだ!数理最適化が使えるんじゃないか!?

最適化ポイント

以下で最適化ポイントを整理していきます。

1. 各トレーナーでタイプを満遍なく散らしたい

いまさら新しいポケモンを育てるのも時間がかかってしまいます。そこで、今いるポケモンをシャッフルして満遍なくタイプを散らすことでタイプ偏り問題を解決していきます。

2. やっぱり1匹はもとから持っているポケモンを持っておきたい

シャッフルした結果、全部違うポケモンになってしまうと、さすがの四天王・チャンピオンと言えど参ってしまうでしょう。そこで、最低1匹はもとから持っているポケモンをパーティーに入れるようにします。今回は各トレーナーの代表的なポケモンを望月個人の独断と偏見で選びます。

イツキ ネイティオ
キョウ クロバット
シバ カイリキー
カリン ブラッキー
ワタル カイリュー

データの準備

今回は手入力で準備しました。コードは以下の通りです。

# データ作成
Itsuki = pd.DataFrame(
    data={'trainer': 'イツキ',
          'name': ['ドータクン', 'ブーピッグ', 'ヤドラン', 'ルージュラ', 'サーナイト', 'ネイティオ'], 
          'type_1': ['はがね', 'エスパー', 'みず', 'こおり', 'エスパー', 'エスパー'],
          'type_2': ['エスパー', '', 'エスパー', 'エスパー', '', 'ひこう']}
)

Kyo = pd.DataFrame(
    data={'trainer': 'キョウ',
          'name': ['スカタンク', 'ドグロッグ', 'ベトベトン', 'マルノーム', 'モルフォン', 'クロバット'], 
          'type_1': ['どく', 'どく', 'どく', 'どく', 'むし', 'どく'],
          'type_2': ['あく', 'かくとう', '', '', 'どく', 'ひこう']}
)

Shiba = pd.DataFrame(
    data={'trainer': 'シバ',
          'name': ['カポエラー', 'サワムラー', 'エビワラー', 'ハリテヤマ', 'ルカリオ', 'カイリキー'], 
          'type_1': ['かくとう', 'かくとう', 'かくとう', 'かくとう', 'かくとう', 'かくとう'],
          'type_2': ['', '', '', '', 'はがね', '']}
)

Karin = pd.DataFrame(
    data={'trainer': 'カリン',
          'name': ['マニューラ', 'アブソル', 'ミカルゲ', 'ヘルガー', 'ドンカラス', 'ブラッキー'], 
          'type_1': ['あく', 'あく', 'ゴースト', 'あく', 'あく', 'あく'],
          'type_2': ['こおり', '', 'あく', 'ほのお', 'ひこう', '']}
)

Wataru = pd.DataFrame(
    data={'trainer': 'ワタル',
          'name': ['ボーマンダ', 'ギャラドス', 'リザードン', 'ガブリアス', 'チルタリス', 'カイリュー'], 
          'type_1': ['ドラゴン', 'みず', 'ほのお', 'ドラゴン', 'ドラゴン', 'ドラゴン'],
          'type_2': ['ひこう', 'ひこう', 'ひこう', 'じめん', 'ひこう', 'ひこう']}
)

df = pd.concat([Itsuki, Kyo, Shiba, Karin, Wataru], ignore_index = True)

 

データの前処理

タイプが2列に分かれている縦もちの状態から横もちの状態へデータを変形します。コードは以下の通りです。

# df_pivot
df_t1 = df.loc[:,['trainer', 'name', 'type_1', 'flg_special']]
df_t1.rename(columns={'type_1': 'type'}, inplace=True)
df_t2 = df.loc[df['type_2'] != "",['trainer', 'name', 'type_2', 'flg_special']]
df_t2.rename(columns={'type_2': 'type'}, inplace=True)
df_pivot = pd.concat([df_t1, df_t2], ignore_index = True)

# df_unpivot
df_pivot["flg_type"] = 1 
df_unpivot = df_pivot.pivot_table(values=['flg_type'], index=['name'], columns=['type'], fill_value=0)
df_unpivot.columns = df_unpivot.columns.droplevel(0)
df_unpivot = df_unpivot.reset_index()
df_unpivot = pd.merge(df_unpivot, df.loc[:,['name', 'trainer', 'flg_special']], on = 'name')

※2列に分かれているタイプを1列に統合(df_pivot) → 横もちに変換(df_unpivot)
といった流れで整形しています。

無事、タイプが横もちの状態になりました。

ここまでのまとめ

ここまででデータの準備は完了しました。次回以降の記事では改めて条件を整理するとともにPuLPを用いて実装していきたいと思います。

参考書籍

Pythonではじめる数理最適化 ケーススタディでモデリングのスキルを身につけよう(オーム社)

mochizuki
データサイエンティスト。筋トレ、温泉、時々スキー。