networkxによるポケモンのゲームタイトルプレイ傾向の可視化

# はじめに

とあるポケモン研究者が作成した、
**ポケモンらしさアンケート**の結果分析をお手伝いすることになった。

今回はアンケート回答者が遊んだ``ポケモンのゲーム18作品のプレイ傾向の可視化``を**networkxライブラリ**で行った件についての記事である。

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
お手伝いしたポケモン研究者の
**「ポケモンらしさ」についての研究記事その１**
https://pkmnheight.blogspot.com/2020/03/1.html
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

> 対象の18作品
1. '赤/緑/青 Red/Blue'
2. 'ピカチュウ Yellow'
3. '金/銀 Gold/Silver'
4. 'クリスタル Crystal'
5.  'ルビー/サファイア Ruby/Sapphire'
6.  'ファイアレッド/リーフグリーン FireRed/LeafGreen'
7. 'エメラルド Emerald'
8. 'ダイヤモンド/パール Diamond/Pearl'
9. 'プラチナ Platinum'
10. 'ハートゴールド/ソウルシルバー HeartGold/SoulSilver'
11. 'ブラック/ホワイト Black/White'
12. 'ブラック2/ホワイト2  Black 2/White 2'
13. 'X/Y'
14. 'オメガルビー/アルファサファイア Omega Ruby/Alpha Sapphire'
15. 'サン/ムーン Sun/Moon'
16. 'ウルトラサン/ウルトラムーン Ultra Sun/Ultra Moon'
17. "Let's Go!ピカチュウ/Let's Go!イーブイ Let's Go, Pikachu!/Let's Go, Eevee!"
18. 'ソード/シールド Sword/Shield'

<br>

## 18集合ベン図は今の人類には不可能？？？

元々そのポケモン研究者からの依頼では
**「18作品のプレイ傾向についてのベン図を作ってほしい」**とのことだった。（うろ覚え）

**ベン図**とは、複数の集合どうしの関係を視覚化したもの（下図）。
><img src="http://www.mathlion.jp/capture/ar093picture3.png" width="300">
http://www.mathlion.jp/article/ar093.html

調べてみると、人類はベン図との相性が良くないらしい。
**3集合** のベン図までは普段目にすることも多いが、
>![unnamed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/bc397b12-a545-4e91-8088-1d6a6ed5bdd0.png)

**7集合**の時点でこのザマである。<font color="Red">もはや人間には何も理解できないだろう</font>（R言語のvennライブラリで描けるらしい）
>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/3a19b9e8-4b37-951b-0e3a-2ea366e9d97f.png)
https://stackoverflow.com/questions/32440128/nice-looking-five-sets-venn-diagrams/40048520

そして2012年にヴィクトリア大学のKhalegh Mamakani,Frank Ruskey両氏によって<font color="Red">**11集合**のベン図</font>が発見された。これが現在、**人類が到達した最大次元のベン図**らしい。
>A New Rose : The First Simple Symmetric 11-Venn Diagram
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/7ba960dc-b4af-5f06-e8aa-75a9fca9117e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/4ed8c2a9-d36f-7771-181d-40cda071efc6.png)
https://arxiv.org/abs/1207.6452



今の自分に18集合のベン図は不可能だということがわかった。


# networkxによる可視化

ベン図で18集合は無理だが、最近趣味で始めたnetworkxでなら可視化できるかもしれない。目的は、ベン図を作ることではなく、

- ``どの作品が多く遊ばれているか。``
- ``ある作品を遊んでる人が、多く遊んでいるのはどれか。``

が可視化できればいいので、
最終的にこのようなグラフを作成した。
![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/30cb485b-3c71-955e-2ea2-60680ca91350.png)

各ノード（円のこと）の内側がポケモンのゲームタイトルを表す、ポケモンファンに馴染みの深い略称となっている。接頭句の「01_」はタイトルの発売された順番を表す。

ノードの大きさは**各作品を遊んだ人数の規模**。
エッジ（各ノードを結ぶ線）の太さは``各2作品を遊んだ人数の多さをjaccard係数によって表現。``
>jacccard係数

>```math
J(A,B) =  \frac {\begin{vmatrix}A \cap B\end{vmatrix}} {\begin{vmatrix}A \cup B\end{vmatrix}} 
```

ノードの色は
 
- <font color="DarkRed">濃い赤：完全新作</font>
- <font color="DarkOrange">オレンジ：リメイク版</font>
- <font color="Gold">薄いオレンジ：マイナーチェンジ版</font>

を表す。

**ある作品を遊んだ人はどの作品も遊んでるか。**の傾向が可視化できていると思う。
例えば、以下のような傾向が見える。

- ``DP,BWを遊んだ人数が特に多い。``
- ``マイナーチェンジ版は遊ばないが、完全新作は遊ぶ人がいる。``
- ``RS以降の完全新作と、最新作である剣盾を共に遊ぶ人は多い。``
- ``赤緑青と剣盾を共に遊んだことのある人が少ない。``


## pythonでの処理

*全コードと、元データは私のgithubにあげておきます。*
[https://github.com/mrok273/](https://github.com/mrok273/Qiita/tree/master/%E3%83%9D%E3%82%B1%E3%83%A2%E3%83%B3/%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E5%9B%B3%E3%81%AE%E4%BD%9C%E6%88%90)


まず、以下のようにone-hotの状態のデータフレームを作成。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/a426c3a2-526b-b5df-a0e3-69c05db07565.png)


全18作品の2作品組み合わせを作成

```python

import itertools
combination_list = list(itertools.combinations(list(node_list), 2))

"""
Out:
[('01_赤緑青', '02_ピ'),
 ('01_赤緑青', '03_金銀'),
 ('01_赤緑青', '04_ク'),
 ('01_赤緑青', '05_RS'),
...
"""
```

## 各2タイトル間のjaccard係数を計算。

```python
for (n1,n2) in combination_list:
    Union_df = df[(df[n1]==1) | (df[n2]==1)]
    Intersection_df =df[(df[n1]==1) & (df[n2]==1)]
    n1_list.append(n1)
    n2_list.append(n2)
    Union_list.append(Union_df.shape[0])
    Intersection_list.append(Intersection_df.shape[0])

df_ja = pd.DataFrame({"n1":n1_list,
             "n2":n2_list,
             "Union":Union_list,
             "Intersection":Intersection_list})
df_ja["jaccard_value"] = df_ja["Intersection"] / df_ja["Union"]
```
| n1        | n2      | Union | Intersection | jaccard_value |
|-----------|---------|-------|--------------|---------------|
| 01_赤緑青 | 02_ピ   | 13606 | 7823         | 0.574966926   |
| 01_赤緑青 | 03_金銀 | 14345 | 9902         | 0.690275357   |
| 01_赤緑青 | 04_ク   | 13697 | 6667         | 0.486748923   |
| 01_赤緑青 | 05_RS   | 17210 | 9507         | 0.552411389   |
| 01_赤緑青 | 06_FRLG | 16331 | 7868         | 0.481783112   |

## networkxによるグラフの設定

```python
import networkx as nx
import matplotlib.pyplot as plt
import japanize_matplotlib
import numpy as np

G = nx.Graph() #グラフの作成
G.add_nodes_from(node_list) #ノードの作成

# エッジの追加
for i in range(len(df_ja)):
    row_data = df_ja.iloc[i]
    G.add_edge(row_data['n1'], row_data['n2'], weight=row_data['jaccard_value'])

#グラフの表示
plt.figure(figsize=fig_size) 

#ネットワーク図の表示設定。circular_layoutを使用
pos = nx.circular_layout(G) 
```


networkxに他にどのようなレイアウトがあるかは
NetworkX本家のチュートリアルを参照
https://networkx.github.io/documentation/stable/reference/drawing.html


## グラフの見栄えをマシにする

各作品を遊んだユーザー数は、差はあれど、一番大きい値と小さい値でせいぜい2倍ちょっと。

これではグラフネットワークを作成しても、``人数の差が実感しにくくなってしまう。``
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/b80b956f-b99d-f6e4-8381-c194c08809f4.png)

そのため、各タイトルごとのプレイ人数差を、最大数によって正規化し、さらに**べき乗することで、 差を極端に開かせる処理を行った**


- 処理前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/3cea11f1-5d2c-4b60-90cb-56eb0bab4fcf.png)

- 処理後：各タイトルごとの相対的な差が広まっている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/8a155a29-12b2-d9d9-157c-dc14d2034030.png)

エッジの太さについても同様の処理を行う

```python
player_dict={} # 各タイトルのプレイヤー数を収納
for gen in node_list:ｄ
    player_dict[gen] = df[gen].sum()

# ノードの描画上の大きさの設定。数が多いと比較が難しくなるので、差を強調
#また、
node_size=[(v-4000)**2*10000/(max(player_dict.values())**2) for v in player_dict.values()]

def make_node_color(node_list):
    #マイナーチェンジ版。リメイク版に異なる色をつける処理
    minor_change=['02_ピ','04_ク','07_Em','09_Pt', '12_BW2','16_USUM']
    remake=['06_FRLG', '10_HGSS','14_ORAS', '17_ピカブイ']
    color_list=[]
    for node in node_list:
        if node in minor_change:
            color = 3000
        elif node in remake:
            color = 6000
        else:
            color = 10000
        color_list.append(color)
    return color_list

node_color = make_node_color(node_list)

# グラフにノードを描画。ノードの色・大きさはここで設定
nx.draw_networkx_nodes(G, pos, node_color=node_color,
                       cmap=plt.cm.Reds,
                       alpha=0.7,
                       node_size=node_size)

# 日本語ラベル
nx.draw_networkx_labels(G, pos, fontsize=20, font_family='IPAexGothic', font_weight="bold")

# エッジの太さ調節.こちらも差を強調する
edge_width = [np.exp(d["weight"]-0.3)**5 for (u, v, d) in G.edges(data=True)]

# エッジの描画
nx.draw_networkx_edges(G, pos, alpha=0.4, edge_color=edge_width, width=edge_width,edge_cmap=plt.cm.Blues)

plt.axis('off')

```

**これで各タイトルの関係性がより強調され、見やすくなった。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610426/2ba75ff2-74bc-f696-ae6d-7a833d0f4f83.png)



# 最後に

networkxは、pandasライブラリなどに比べて使う人が少ないため、目的に合った記事が見つかりにくいかもしれない。が、matplotlibやseabornでは表せない表現も可能ではある。
ただし、分かりやすい可視化のためには試行錯誤が必要で、色や大きさを整理することは必要。
自分もまだまだnetworkxについては知らないことが多い。