ポケモン×データサイエンス(2) - ポケモン剣盾のパーティ構築をネットワーク分析から考える 試行編

- [ポケモン×データサイエンス(1) - ポケモン剣盾のランクバトルデータを解析してTableau上で可視化してみた](https://qiita.com/b_aka/items/7d2b768dfa7817f34fc2)
- 【今回】[ポケモン×データサイエンス(2) - ポケモン剣盾のパーティ構築をネットワーク分析から考える 試行編](https://qiita.com/b_aka/items/9020e3237ff1a3e676e4) 
- [ポケモン×データサイエンス(3) - ポケモン剣盾のパーティ構築をネットワーク分析から考える ネットワークの中心はどこ編](https://qiita.com/b_aka/items/9a28630012d58d7d60dd)

こんにちは、[前回の記事](https://qiita.com/b_aka/items/7d2b768dfa7817f34fc2)に引き続きポケモンの可視化についてです。

ポケモンの対戦環境においては、いかにしてパーティを構築するかが鍵であり、それによって勝敗が決まるといっても過言ではありません。

今回はそのパーティ構築を考える上で、ポケモン同士がどのような関係性にあるのかを把握するため、ネットワークの力を借りたいと思います。

コード全文は[こちら](https://github.com/moxak/pokemon-rankbattle-network-analysis/blob/master/001.ipynb)から

## データインポート

データは前回の記事で作成したデータの内、一緒に採用されているランキング`ALL_SEASON_TEMOTI_POKEMON.csv`を使用します。
（※[こちら](https://github.com/moxak/pokemon-rankbattle-data-transformer)の`main.py`を実行すると自動で作成されます。）

このデータへのパスを

```python
file_path = 'ALL_SEASON_TEMOTI_POKEMON.csvへのパス'
```

としたうえで、以下の関数を定義して

```python
import pandas as pd

def make_edge_list(file_path):
    df = pd.read_csv(file_path, encoding='utf-8')
    df = df.groupby(['Pokemon','併用率の高いポケモン'])['Rank_併用率の高いポケモン'].mean()
    df = pd.DataFrame(df).reset_index()
    df.columns = ['From', 'To', 'Weight']
    df['Weight'] = 10-df['Weight']
    return df
```

実行！

```python
network = make_edge_list(file_path)
```

以下の様なデータが得られていれば成功です。
有向グラフ(エッジに向きがある)を描画するのでノードの始点と終点、エッジの重みが最低限必要になります。
今回はあるポケモンをエッジの始点(From)、一緒に採用されることが多いポケモンをエッジの終点(To)、エッジの重み(Weight)を平均順位の逆数としました。


| From | To | Weights |
|:------------:|:------------:|:------------:|
| アイアント | アシレーヌ | 6.60 |
| アイアント | イエッサン | 3.00 |
| アイアント | ウインディ | 4.22 |
| ... | ... | ... |
35732 rows × 3 columns

## データの加工

こちらのデータをそのままネットワークに放り込んでしまうと、ノードとエッジが多すぎて大変なことになる(計算時間も去ることながら可視化が極めて困難になる)ので、データの絞り込みをします。
取り急ぎ以下のコードでシーズン12における採用率ランキングをcsvとして保存します。

```python
import urllib.request
import json
import datetime

def make_pokemon_rank(ids):
    for season_number in ids['list'].keys():
        for  season_id in ids['list'][season_number].keys():
            rst = ids['list'][season_number][season_id]['rst']
            ts2 = ids['list'][season_number][season_id]['ts2']
            url = f'https://resource.pokemon-home.com/battledata/ranking/{season_id}/{rst}/{ts2}/pokemon'
            file_name = f'Season{season_number}_{"Single" if season_id[4]=="1" else "Double"}_採用率ランキング.json'
            if get_response_pokemon_rank(url, file_name):
                with open('log', 'a') as f:
                    print(f'{datetime.datetime.now()} | Generated: {file_name}', file=f)

def get_response_pokemon_rank(url, file_name):
    try:
        with urllib.request.urlopen(url) as response:
            body = json.loads(response.read())
            with open(f'{file_name}', 'w') as f:
                json.dump(body, f, indent=4)  
        return True
    except urllib.error.URLError as e:
        print(e.reason)
        return False

def translate_pokemion_name(num, form):
    if num == 876:
        if form == 0:
            name = "イエッサン♂"
        else: 
            name = "イエッサン♀"
    elif num == 479:
        if form == 0:
            name = "ロトム（デフォ）"
        elif form == 1:
            name = "ロトム（火）"
        elif form == 2:
            name = "ロトム（水）"
        elif form == 3:
            name = "ロトム（氷）"
        elif form == 4:
            name = "ロトム（飛行）"
        elif form == 5:
            name = "ロトム（草）"
    else:
        name = pokedex['poke'][int(num) -1]
    return name
```

[こちらのリポジトリ](https://github.com/moxak/pokemon-rankbattle-data-transformer)の[`datachunk\IDs.json`](https://github.com/moxak/pokemon-rankbattle-data-transformer/blob/main/datachunk/IDs.json)と[`datachunk\bundle.json`](https://github.com/moxak/pokemon-rankbattle-data-transformer/blob/main/datachunk/bundle.json)を読み込みます。

```python
ids = "";
with open('.//datachunk//IDs.json', 'r', encoding='utf-8') as json_open:
    ids = json.load(json_open)

pokedex = "";
with open('.//datachunk//bundle.json', 'r', encoding='utf-8') as json_open:
    pokedex = json.load(json_open)
```

以下を実行してシーズン1から12までのランキングデータを取得します。

```python
make_pokemon_rank(ids)
```

採用率ランキングをjsonからcsvに変形します。

```python
season_num = 12
rule = 'Single'
with open(f'Season{season_num}_{rule}_採用率ランキング.json', 'r', encoding='utf-8') as json_open:
    data = json.load(json_open)

seasons = []
rules = []
pokemons = []
ranks = []
for index, pokemon in enumerate(data):
    seasons += [season_num]
    rules += [rule]
    pokemons += [translate_pokemion_name(pokemon['id'], pokemon['form'])]
    ranks += [index+1]
df_a = pd.DataFrame(data=[seasons, rules, pokemons, ranks]).T
df_a.columns = ['Season', 'Rule', 'Pokemon', 'Rank']
df_a.to_csv(f'Season{season_num}_{rule}_Ado_Rank.csv', index=False)
```

あとはこのデータを読み込んでシーズン12時点で採用率ランキング上位150位に絞り込みをします。

```python
top150_list = list(df_a['Pokemon'])

network_top150 = network[network['From'].isin(top150_list[:150])]
network_top150 = network_top150[network_top150['To'].isin(top150_list[:150])]
```

## Networkxによる可視化

NetworkxのPythonでの操作方法については[こちら](https://qiita.com/kzm4269/items/081ff2fdb8a6b0a6112f)をご参照ください。

```python
import matplotlib.pyplot as plt
import networkx as nx
plt.rcParams['font.family'] = 'IPAexGothic'

network_np = network_top150.values
G = nx.DiGraph()
G.add_weighted_edges_from(network_np)
pos=nx.spring_layout(G)
fig = plt.figure(figsize=(40, 40), dpi=100,facecolor='w', linewidth=0, edgecolor='w')
nx.draw_networkx(G,pos,font_size=16,font_family='IPAexGothic')
fig.show()
```

![networkx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487123/6191e8c8-298c-ce5e-5c50-72bd616886b8.png)


何も読み取れない...泣

ノード・エッジの多さが凄まじく、Networkxのグラフを"イイ感じ"にしてくれる`spring_layout()`が今回のデータセットと相性が悪そうです。

Networkxの描画に関係する属性をいじれば変わりそうですが、いずれにしてもpythonでネットワークの可視化を行うのは限界を感じたのでCytoscapeを試してみます。

## Cytoscapeによる可視化

続いて[Cytoscape](https://cytoscape.org/)を試してみます。

Cytoscapeの使い方については[こちらの記事](https://qiita.com/keiono/items/85cbb721467709113e2f)が大変詳しく書かれています。

まずデータを読み込みます。以下のコードを実行してファイルを出力します。

```python
make_edge_list(file_path).to_csv("FILE-NAME.csv", index = False)
```

Cytoscapeを起動し、左上の`file` > `import` > `Network from File`から、先ほど出力したcsvファイルをインポートします。

![スクリーンショット2020-11-05021410.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487123/7f3c78d5-9aa4-acc1-2a76-66883962f587.png)


下図のような画面が出ると思います。今回は有向グラフなのでそれぞれのノードが何を意味するものなのかを指定してあげます。`From`はエッジの始点となるノードなので`Source Node`、`To`はエッジの終点となるノードなので`Target Node`、`Weight`はエッジの重みを表すので`Edge Attribute`とします。

![スクリーンショット2020-11-05021707.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487123/6b71d336-70e7-5616-444a-5a1cacf8a118.png)


上部タブ`Layout`や左タブ`Style`を適当にいじってみると、かなり求めていたものに近いものが得られました。

対戦環境はグラフの中央付近にいて多数のエッジが伸ばされているエースバーン、パッチラゴン、ドラパルト、トゲキッスを中心に回っていることが分かります。

![tableau_viz_test.csv1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487123/8840f324-0c70-79a4-7500-aff55ba1eb6b.png)


Cytoscapeはノードのドラッグによる移動などインタラクティブな操作に対応しており、スタイルを変更などの諸々の操作をGUI操作でできる点で直感的な試行錯誤ができて大変Goodです。

次回はこちらのネットワークを用いて分析をしていきたいと思います。
それではまた。

## 出典

- [ポケモン剣盾のランクバトルデータを解析してTableau上で可視化してみた - Qiita](https://qiita.com/b_aka/items/7d2b768dfa7817f34fc2)
- [【Python】NetworkX 2.0の基礎的な使い方まとめ - Qiita](https://qiita.com/kzm4269/items/081ff2fdb8a6b0a6112f)
- [鉄道路線データをグラフとしてCytoscapeで可視化する 3](https://qiita.com/keiono/items/85cbb721467709113e2f)
- [Cytoscape.org](https://cytoscape.org/)

©2020 Pokémon ©1995-2020 Nintendo/Creatures Inc./GAME FREAK inc. ポケットモンスター・ポケモン・Pokémonは任天堂・クリーチャーズ・ゲームフリークの登録商標です。