NetworkXのグラフをpyvisのグラフに変換して可視化する

pyvisはグラフを手軽に可視化できますし、pyvis自体のメソッドでノードやエッジを追加してグラフを構築することもできるので基本的な処理はこれだけで完結させることももちろんできます。しかし、グラフデータの分析をしていると、各種アルゴリズムが充実しているNetworkXでいろんな分析を行って、それを最後にpyvisで可視化したい、ということはよくある話です。

こういう場合、pyvisのネットワークオブジェクトが持っている、from_nxってメソッドが使えます。
参考: Documentation — pyvis 0.1.3.1 documentation

この記事の主題は「from_nxが便利だよ」で終わりなのですが、それだけではあんまりなので細かい話をいろいろ書いていきます。

まず、基本的な使い方ですが、from_nxは pyvisモジュールから直接呼び出せるメソッドではなく、pyvisのpyvis.network.Network オブジェクトに実装されているメソッドなので、まずそのインスタンスを生成します。以前の記事でも書いていますが、可視化するときのキャンパスサイズとか背景色などを指定して生成するやつですね。

具体的に適当なネットワークでやってみると次の様になります。

import networkx as nx
from pyvis.network import Network


# サンプルとして、NetworkXのグラフを生成
nx_graph = nx.Graph()
nx_graph.add_node("a")
nx_graph.add_node("b")
nx_graph.add_node("c")
nx_graph.add_edge("a", "b")
nx_graph.add_edge("b", "c")
nx_graph.add_edge("c", "a")


# ネットワークのインスタンス生成
network = Network(
    height="500px",
    width="500px",
    notebook=True,
    bgcolor='#ffffff',
    directed=False, 
)

# pyvisのネットワークに、NetworkXのグラフのノードやエッジ情報を取り込む。
network.from_nx(nx_graph)
# 可視化
network.show("sample.html")

結果は省略しますが、これで、a,b,cの3個のノードを持ったネットワークが可視化されます。

NetworkXで設定されていなかったがpyvisで可視化するときに必要な種類の情報はデフォルト値で補完されています。

# NetworkXのノードの情報
print(dict(nx_graph.nodes))
# {'a': {'size': 10}, 'b': {'size': 10}, 'c': {'size': 10}}

# pyvisに取り込んだときに、colorやshape、sizeのデフォルト値が設定されている。
print(network.nodes)
"""
[{'color': '#97c2fc', 'size': 10, 'id': 'a', 'label': 'a', 'shape': 'dot'},
 {'color': '#97c2fc', 'size': 10, 'id': 'b', 'label': 'b', 'shape': 'dot'},
 {'color': '#97c2fc', 'size': 10, 'id': 'c', 'label': 'c', 'shape': 'dot'}]
"""

# NetworkXのエッジの情報
print(dict(nx_graph.edges))
# {('a', 'b'): {'width': 1}, ('a', 'c'): {'width': 1}, ('b', 'c'): {'width': 1}}

# エッジはほぼそのまま取り込まれている。
print(network.edges)
# [{'width': 1, 'from': 'a', 'to': 'b'}, {'width': 1, 'from': 'a', 'to': 'c'}, {'width': 1, 'from': 'b', 'to': 'c'}]

ノードのサイズやエッジの太さは、デフォルト値をそれぞれ、default_node_size, default_edge_weight という引数で指定することもできるので、有効に使っていきましょう。例えばWebサイトのページ遷移データのネットワーク等で、NetworkX時点ではPVなどの大きい値をsizeとして計算していたら、それを可視化時のサイズとして使ってしまうとえらいことになります。

また、node_size_transf , edge_weight_transf という引数で、関数を渡しておくことで、それぞれの値を変換することもできます。元々の値が非常に大きい、または小さい場合にこれを使って補正することができますね。
例えば、
node_size_transf = (lambda x: x/10) とすると、ノードのサイズを1/10にできます。

ここで注意というか、version 3.2時点の pyvisにはバグがあって、エッジを持たない孤立ノードには node_size_transf が適用されません。

該当部分のソースコードがこちらです。

        if len(edges) > 0:
            for e in edges:
                if 'size' not in nodes[e[0]].keys():
                    nodes[e[0]]['size']=default_node_size
                nodes[e[0]]['size']=int(node_size_transf(nodes[e[0]]['size']))
                if 'size' not in nodes[e[1]].keys():
                    nodes[e[1]]['size']=default_node_size
                nodes[e[1]]['size']=int(node_size_transf(nodes[e[1]]['size']))
                self.add_node(e[0], **nodes[e[0]])
                self.add_node(e[1], **nodes[e[1]])

                # if user does not pass a 'weight' argument
                if "value" not in e[2] or "width" not in e[2]:
                    if edge_scaling:
                        width_type = 'value'
                    else:
                        width_type = 'width'
                    if "weight" not in e[2].keys():
                        e[2]["weight"] = default_edge_weight
                    e[2][width_type] = edge_weight_transf(e[2]["weight"])
                    # replace provided weight value and pass to 'value' or 'width'
                    e[2][width_type] = e[2].pop("weight")
                self.add_edge(e[0], e[1], **e[2])

        for node in nx.isolates(nx_graph):
            if 'size' not in nodes[node].keys():
                nodes[node]['size'] = default_node_size
            self.add_node(node, **nodes[node])

エッジが存在するノードの情報を取り込むときは、node_size_transfしてるのに、その後の孤立ノードの取り込みでは元のsizeとデフォルトノードサイズしか考慮してませんね。

これは将来のバージョンで修正されると思うのですが、こういうバグもあるので、サイズを変換したい場合はnode_size_transfではなく、自分で元のデータを修正してform_nxに渡した方が良いでしょう。

さらに、便利な機能なのですがノードのサイズやエッジの太さ以外の属性については、一通り全部コピーしてくれます。これを使って、可視化するときに設定したい情報などをNetowrkXのグラフオブジェクトの時点で設定しておくことも可能です。これはもちろん、pyvisのネットワークに変換してから付与してももちろん大丈夫なのですが。

ただ、例えばノードのクラスタリング結果を可視化時の色に反映させたい等の、何かしらのアルゴリズムの結果を可視化に使いたい場合は、NetworkXの時点で設定する方がやりやすいことが多いです。ただ、可視化時点でしか必要のない情報をNetworkXのオブジェクトに付与していくことに抵抗がある人もいるかもしれないので好みの問題だと思います。

基本的な話なのですが、以下の様にして属性を付与していけます。ノードを1つ、赤い三角形にしてみたり、エッジの一つを黒く塗ってラベルつけたりしています。

# 事前にグラフにいろいろ属性を設定できる。
nx_graph.nodes["a"]["color"] = "red"
nx_graph.nodes["a"]["shape"] = "triangle"

nx_graph.edges[("b", "c")]["color"] = "black"
nx_graph.edges[("b", "c")]["label"] = "hogehoge"

# 以降の Networkオブジェクトを作って from_nxするところは同じ。

以上が from_nx の説明です。とても便利なのでぜひ使ってみてください。

逆に、pyvisのNetworkをNetworkXのグラフに変換するメソッドは無いのかな、とも思ったのですが、専用のものはなさそうですね。まぁ、それぞれのライブラリの用途を考えれば必要もなさそうですし、どうしてもやりたければノードとエッジの情報をそれぞれ取り出してNetworkXのグラフを構築すればいいだけの話なのでそんなに難しくもなさそうです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です