[Python]NetworkXでQiitaのタグ関係図を描く

# はじめに
Pythonのライブラリ、[NetworkX](https://networkx.github.io/)の使い方を、Qiitaの投稿に付けられたタグの関係グラフの作成を例にして説明します。
NetworkXを使うと、下に示すような、ノードとエッジで構成されるグラフを描くことができます。
![sample.png](https://qiita-image-store.s3.amazonaws.com/0/61468/bfba6d1a-1ef5-244c-d0e3-b1238ac21e29.png)

##実行環境

* Windows 10
* Python 3.5.2 (Anaconda)
* Jupyter notebook


#元データの取得
Qiitaは、投稿を取得するAPIを公開しているので、簡単に投稿を取得することができます。
JSON形式で返ってくるデータを下記コードでPythonの辞書に変換します。
なお、非認証の場合、1リクエストごとに最大100記事、1時間ごとに60回の制限があるので、
今回は、100*60 = 6000 記事を対象とします。

```py3
import requests
import json

items = []
params = {"page":1, "per_page":100}
for i in range(60):
    print("fetching... page " + str(i+1))
    params["page"] = i + 1
    res = requests.get("https://qiita.com/api/v2/items", params=params)
    items.extend(json.loads(res.text))
```

#データの下準備
APIで取得したデータについて、タグのみを抽出して、`[ [tag1, tag2], [tag3], ... ]`の形式に変換します。

```py3
tags_list = []
for item in items:
    tags = [tag["name"] for tag in item["tags"]]
    tags_list.append(tags)
```

また、`collections.Counter`を用いてタグの出現回数をカウントします。
このとき、`itertools.chain.from_iterable(tags_list)`で多重配列をflattenしています。
あまり、ノード数が多いと図がごちゃごちゃするので、上位50タグを抽出します。

```py3
import collections
import itertools

tag_count = collections.Counter(itertools.chain.from_iterable(tags_list)).most_common(50)
```

#NetworkXの利用
ここから、NetworkXを使って、グラフを作成していきます。
##初期化とノードの追加
`G = nx.Graph()`で新しいグラフをつくり、タグ名のノードを追加していきます。
後で描画時にノードの大きさを決定するために、ノードの属性に出現回数`count`を入れています。

```py3
import networkx as nx
G = nx.Graph()
G.add_nodes_from([(tag, {"count":count}) for tag,count in tag_count])
```

##エッジの追加
1つの投稿に複数のタグが含まれた場合、すべての組み合わせにエッジを追加します
例えば、この記事のように「Python, networkx, Qiita」がタグづけられている投稿があった場合、
Pythonノードとnetworkxノード間、networkxとQiita間、QiitaとPython間にエッジを作成します。
すでにエッジが存在する場合には、エッジの`weight`を増加させます。

```py3
for tags in tags_list:
    for node0,node1 in itertools.combinations(tags, 2):
        if not G.has_node(node0) or not G.has_node(node1):
            continue
        if G.has_edge(node0, node1):
            G.edge[node0][node1]["weight"] += 1
        else:
            G.add_edge(node0, node1, {"weight":1})
```

##ひとます、グラフの描画してみる
ここでいったんグラフを描いてみます。

```py3
%matplotlib inline
import matplotlib.pyplot as plt

plt.figure(figsize=(15,15))
pos = nx.spring_layout(G)
nx.draw_networkx(G,pos)

plt.axis("off")
plt.savefig("default.png")
plt.show()
```
![default.png](https://qiita-image-store.s3.amazonaws.com/0/61468/85e13ea6-1a6e-1ceb-a1fc-1c3a37ec9e92.png)

よくわからないグラフができてしまいました。


##グラフ描画の調整
ここからグラフをきれいにするため、いろいろ調整していきます。

###エッジの枝刈り
出現回数が少ないエッジを削除します。

```py3
for (u,v,d) in G.edges(data=True):
    if d["weight"] <= 4:
        G.remove_edge(u, v)
```
###反発力の調整
`pos = nx.spring_layout(G)`では、ノード間の反発力と、エッジの`weight`の大きさによる吸引力でノードの位置が決定されます。
ノード間の反発力は、引数`k`を指定することで設定でき、`k`が大きいほどノードの配置は円形に近くなります。

```py3
pos = nx.spring_layout(G, k=0.3)
```

###ノードの大きさと、ノードラベルの日本語の表示
ノードの円の大きさを、`count`が大きいほど大きくします。
また、先ほど出力した図では、日本語が四角形になって表示されないので日本語表示可能なフォントを設定します。

```py3
node_size = [ d["count"]*20 for (n,d) in G.nodes(data=True)]
nx.draw_networkx_nodes(G, pos, node_color="w",alpha=0.6, node_size=node_size)
nx.draw_networkx_labels(G, pos, fontsize=14, font_family="Yu Gothic", font_weight="bold")
```
### エッジの太さ
エッジの`weight`に応じてエッジを太くします。

```py3
edge_width = [ d["weight"]*0.2 for (u,v,d) in G.edges(data=True)]
nx.draw_networkx_edges(G, pos, alpha=0.4, edge_color="c", width=edge_width)
```

###描画してみるとこちら
このコードで描画します。できたグラフがしっくりくるまで何回か実行してみたり、パラメータを変更したりしてみましょう。

```py3
%matplotlib inline
import matplotlib.pyplot as plt
import math

for (u,v,d) in G.edges(data=True):
    if d["weight"] <= 4:
        G.remove_edge(u, v)
    
plt.figure(figsize=(15,15))
pos = nx.spring_layout(G, k=0.3)

node_size = [ d['count']*20 for (n,d) in G.nodes(data=True)]
nx.draw_networkx_nodes(G, pos, node_color='w',alpha=0.6, node_size=node_size)
nx.draw_networkx_labels(G, pos, fontsize=14, font_family="Yu Gothic", font_weight="bold")

edge_width = [ d['weight']*0.2 for (u,v,d) in G.edges(data=True)]
nx.draw_networkx_edges(G, pos, alpha=0.4, edge_color='C', width=edge_width)

plt.axis('off')
plt.savefig("g2.png")
plt.show()
```

![graph2.png](https://qiita-image-store.s3.amazonaws.com/0/61468/f596770e-29e9-22cb-248b-74c819cfcaba.png)
上にWeb(Ruby(on Rails), JavaScript, PHP...)関係、左下にPython(機械学習)関係、真ん中にiOS関係、右下にBashが使えるようになったWindows10の話題...といったように、人気のタグとその関係性が見える図ができました。