NetworkXを用いた関係図の作成
最近、とある学会に提出する原稿をまとめている際に関係図を作成することがありました。
原稿に載せるのでデジタル化したかったのですが、どんな形になるか分からなかったので手書きで作成したところ、とてもカオスな感じになりました。
これでも全体の半分くらいのデータで、人間の手で全部反映させるのは現実的ではありません。さすがに自動化しようということで見つけたのがpythonのライブラリであるNetworkXでした。
NetworkXについて
NetworkXはグラフやネットワーク理論系の計算を行うためのライブラリで、関係図などの作成が容易に出来ます。
また、NetworkXはバージョンによってAPIの叩き方が異なる部分が多くあります。
google検索で上位に出てくるページは1.X系が多く、コピペしても動かないことがありますので注意が必要です。
※普通にNetworkXをインストールすると2.X系になります。
なお、ここで紹介するプログラムは2.x系を想定しています。
NetworkXのインストール
pipでインストールするだけです。
$ sudo pip3 install networkx
読み込むCSVデータ
001,025 001,038 001,069 001,037,038 001,067,069 ⋮ 070,089 080,082 083,084 088,089 089,092
実装
ファイルの読み込み
node_list = [] with open('sample.csv','r') as f: f_lines = f.readlines() for i in range(len(f_lines)): # 読み込んだCSVがBOMありのときは \ufeff を削除する if i == 0: f_lines[i] = f_lines[i].replace('\ufeff','') # 改行文字を削除 f_lines[i] = f_lines[i].replace('\n','').split(',') # リストに追加 node_list.append(f_lines[i])
普通に読み込んで普通にリストに追加しているだけです。
csvの作り方によってはBOMありになっていたりするので、1つ目のデータに \ufeff があったら削除してあげます。
グラフの作成
# ノードの出現回数をカウント node_count = collections.Counter(itertools.chain.from_iterable(node_list)) # グラフを作成 G = nx.Graph() # ノードに属性を追加するときは辞書で入れる G.add_nodes_from([(node, {'count':node_count[node]}) for node in node_count]) for nodes in node_list: #print(nodes) for node0,node1 in itertools.combinations(nodes, 2): # 万が一、ノードが存在しないときは何もしない if not G.has_node(node0) or not G.has_node(node1): continue # 既にエッジが存在するときは何もしない if G.has_edge(node0, node1): G.edges[node0, node1]['weight'] += 1 # エッジが存在しないときは新たに追加 else: G.add_edge(node0, node1) G.edges[node0, node1]['weight'] = 1
ノードの大きさを出現回数に応じて変化させるためにあらかじめ数えておきます。
ノードに属性を加える部分が1.x系と異なります。
1.x系では add_edge() で属性を追加できるようですが、同じように書くと2.x系ではエラーになります。
グラフの描画
plt.figure(figsize=(20,20)) # レイアウトの調整 pos = nx.spring_layout(G, k=0.4) # ノードの大きさを出現回数に応じて大きくする node_size = [ d["count"]*300 for (n,d) in G.nodes(data=True)] nx.draw_networkx_nodes(G, pos, node_color="#0000FF", alpha=0.5, node_size=node_size) # エッジのweightに応じて太さを変える edge_width = [ d["weight"]*0.9 for (u,v,d) in G.edges(data=True)] nx.draw_networkx_edges(G, pos, edge_color="#000000", alpha=0.7, width=edge_width) # フォントの設定 nx.draw_networkx_labels(G, pos, font_color="#000000") plt.axis('off') plt.savefig("sample.png") plt.show()
figure() で画像サイズを指定します。
それぞれ ×100 した値のピクセル数になります。この場合だと2000×2000です。
nx.spring_layout() でレイアウトの調整が出来ます。
k の値を大きくすると中心から離れたところに描画します。ノードの数が多いときはこの値を大きめに設定することでノードの位置が被りにくくなります。
draw_networkx_nodes() でノードを描画します。
オプションの node_color は16進数のカラーコードか色名(whiteやblackなど)で設定できます。ただし、3桁の短縮コードは設定できません。
alpha は透過度を示していて、1.0 から 0 に近づくほど透明になっていきます。
ノード数が多いとノードの位置が重なって見ずらくなってしまうので、少し透明にしてあげると良さげです。
draw_networkx_edges() でエッジを描画します。
オプションはノードと特に変わりません。
draw_networkx_labels() でフォントの設定ができます。
font_family = "hoge" で好きなフォントを設定できます。font_weight = "bold" にすることで太字にすることも可能です。
ソースコード
オプションに設定する値は少しずつ変えて、良さげな図が出来上がるまで調整するしかありません。
同じ値でもプログラムを実行して図を吐きだす度に微妙に配置が変わるので、お気に入りの図が出来上がるまで頑張りましょう。
成果物
手書きだとどうしても見落としがあったり、時間も掛かるものです。
しかし、プログラムで書いたら一瞬な上に見落としもありません(プログラムにミスが無い限り)。そしてカッコいい。
今後も使えそうなプログラムが出来たのではないかなと思います。