Google ColaboratoryでPlotlyを使用してネットワークグラフを作成

Pocket

Google ColaboratoryでPlotlyを使用してネットワークグラフを作成

はじめに

Google ColaboratoryでPlotlyを使用してネットワークグラフを作成する方法について記載しています。

ネットワークグラフはNetworkX+Matplotlibで作成できますが、Plotlyを使用するとグラフの拡大・縮小の他に、特定の領域を選択したり動かして確認することができます。
記事中では、2Dと3Dのネットワークグラフを作成します。作成方法はPlotlyの公式サイトのスクリプトを流用していますが、ところどころ良しなに手を加えています。

表題のとおり、コードは全てGoogle Colaboratoryで実行していきます。

なお、当記事は可視化することが目的になっており、『グラフ理論』『複雑ネットワーク』といった学問には踏み込んでいません。

1.準備

まずは必要なライブラリをインポートします。

import networkx as nx
import plotly.graph_objects as go

下記のデータでネットワークグラフを作成していきます。
データは、国内線の空港と路線です。全て網羅していないかもしれません。

graph_data = {
    'nodes': [{'group': 0, 'id': 0, 'label': '稚内', 'region': '北海道'},
              {'group': 0, 'id': 1, 'label': '利尻', 'region': '北海道'},
              {'group': 0, 'id': 2, 'label': '紋別', 'region': '北海道'},
              {'group': 0, 'id': 3, 'label': '女満別', 'region': '北海道'},
              {'group': 0, 'id': 4, 'label': '中標津', 'region': '北海道'},
              {'group': 0, 'id': 5, 'label': '釧路', 'region': '北海道'},
              {'group': 0, 'id': 6, 'label': '旭川', 'region': '北海道'},
              {'group': 0, 'id': 7, 'label': '帯広', 'region': '北海道'},
              {'group': 0, 'id': 8, 'label': '丘珠', 'region': '北海道'},
              {'group': 0, 'id': 9, 'label': '札幌', 'region': '北海道'},
              {'group': 0, 'id': 10, 'label': '奥尻', 'region': '北海道'},
              {'group': 0, 'id': 11, 'label': '函館', 'region': '北海道'},
              {'group': 1, 'id': 12, 'label': '青森', 'region': '東北'},
              {'group': 1, 'id': 13, 'label': '三沢', 'region': '東北'},
              {'group': 1, 'id': 14, 'label': '大館能代', 'region': '東北'},
              {'group': 1, 'id': 15, 'label': '秋田', 'region': '東北'},
              {'group': 1, 'id': 16, 'label': '花巻', 'region': '東北'},
              {'group': 1, 'id': 17, 'label': '山形', 'region': '東北'},
              {'group': 1, 'id': 18, 'label': '庄内', 'region': '東北'},
              {'group': 1, 'id': 19, 'label': '仙台', 'region': '東北'},
              {'group': 1, 'id': 20, 'label': '福島', 'region': '東北'},
              {'group': 2, 'id': 21, 'label': '新潟', 'region': '中部・北陸'},
              {'group': 2, 'id': 22, 'label': '富山', 'region': '中部・北陸'},
              {'group': 2, 'id': 23, 'label': '小松', 'region': '中部・北陸'},
              {'group': 2, 'id': 24, 'label': '能登', 'region': '中部・北陸'},
              {'group': 2, 'id': 25, 'label': '松本', 'region': '中部・北陸'},
              {'group': 2, 'id': 26, 'label': '中部', 'region': '中部・北陸'},
              {'group': 2, 'id': 27, 'label': '名古屋/小牧', 'region': '中部・北陸'},
              {'group': 2, 'id': 28, 'label': '静岡', 'region': '中部・北陸'},
              {'group': 3, 'id': 29, 'label': '東京/成田', 'region': '関東'},
              {'group': 3, 'id': 30, 'label': '東京/羽田', 'region': '関東'},
              {'group': 3, 'id': 31, 'label': '茨城', 'region': '関東'},
              {'group': 3, 'id': 32, 'label': '調布', 'region': '関東'},
              {'group': 3, 'id': 33, 'label': '大島', 'region': '関東'},
              {'group': 3, 'id': 34, 'label': '利島', 'region': '関東'},
              {'group': 3, 'id': 35, 'label': '新島', 'region': '関東'},
              {'group': 3, 'id': 36, 'label': '神津島', 'region': '関東'},
              {'group': 3, 'id': 37, 'label': '三宅島', 'region': '関東'},
              {'group': 3, 'id': 38, 'label': '御蔵島', 'region': '関東'},
              {'group': 3, 'id': 39, 'label': '八丈島', 'region': '関東'},
              {'group': 3, 'id': 40, 'label': '青ヶ島', 'region': '関東'},
              {'group': 4, 'id': 41, 'label': '白浜', 'region': '近畿'},
              {'group': 4, 'id': 42, 'label': '大阪/伊丹', 'region': '近畿'},
              {'group': 4, 'id': 43, 'label': '大阪/関西', 'region': '近畿'},
              {'group': 4, 'id': 44, 'label': '大阪/神戸', 'region': '近畿'},
              {'group': 4, 'id': 45, 'label': '但馬', 'region': '近畿'},
              {'group': 5, 'id': 46, 'label': '鳥取', 'region': '中国'},
              {'group': 5, 'id': 47, 'label': '隠岐', 'region': '中国'},
              {'group': 5, 'id': 48, 'label': '米子', 'region': '中国'},
              {'group': 5, 'id': 49, 'label': '出雲', 'region': '中国'},
              {'group': 5, 'id': 50, 'label': '岡山', 'region': '中国'},
              {'group': 5, 'id': 51, 'label': '広島', 'region': '中国'},
              {'group': 5, 'id': 52, 'label': '石見', 'region': '中国'},
              {'group': 5, 'id': 53, 'label': '山口宇部', 'region': '中国'},
              {'group': 5, 'id': 54, 'label': '岩国', 'region': '中国'},
              {'group': 6, 'id': 55, 'label': '徳島', 'region': '四国'},
              {'group': 6, 'id': 56, 'label': '高松', 'region': '四国'},
              {'group': 6, 'id': 57, 'label': '高知', 'region': '四国'},
              {'group': 6, 'id': 58, 'label': '松山', 'region': '四国'},
              {'group': 7, 'id': 59, 'label': '北九州', 'region': '九州'},
              {'group': 7, 'id': 60, 'label': '福岡', 'region': '九州'},
              {'group': 7, 'id': 61, 'label': '佐賀', 'region': '九州'},
              {'group': 7, 'id': 62, 'label': '長崎', 'region': '九州'},
              {'group': 7, 'id': 63, 'label': '壱岐', 'region': '九州'},
              {'group': 7, 'id': 64, 'label': '対馬', 'region': '九州'},
              {'group': 7, 'id': 65, 'label': '五島福江', 'region': '九州'},
              {'group': 7, 'id': 66, 'label': '大分', 'region': '九州'},
              {'group': 7, 'id': 67, 'label': '熊本', 'region': '九州'},
              {'group': 7, 'id': 68, 'label': '宮崎', 'region': '九州'},
              {'group': 7, 'id': 69, 'label': '鹿児島', 'region': '九州'},
              {'group': 7, 'id': 70, 'label': '種子島', 'region': '九州'},
              {'group': 7, 'id': 71, 'label': '屋久島', 'region': '九州'},
              {'group': 7, 'id': 72, 'label': '奄美大島', 'region': '九州'},
              {'group': 7, 'id': 73, 'label': '喜界島', 'region': '九州'},
              {'group': 7, 'id': 74, 'label': '徳之島', 'region': '九州'},
              {'group': 7, 'id': 75, 'label': '沖永良部', 'region': '九州'},
              {'group': 7, 'id': 76, 'label': '与論', 'region': '九州'},
              {'group': 7, 'id': 77, 'label': '天草', 'region': '九州'},
              {'group': 8, 'id': 78, 'label': '沖縄', 'region': '沖縄'},
              {'group': 8, 'id': 79, 'label': '北大東', 'region': '沖縄'},
              {'group': 8, 'id': 80, 'label': '南大東', 'region': '沖縄'},
              {'group': 8, 'id': 81, 'label': '久米', 'region': '沖縄'},
              {'group': 8, 'id': 82, 'label': '宮古', 'region': '沖縄'},
              {'group': 8, 'id': 83, 'label': '多良間', 'region': '沖縄'},
              {'group': 8, 'id': 84, 'label': '石垣', 'region': '沖縄'},
              {'group': 8, 'id': 85, 'label': '与那国', 'region': '沖縄'},
              {'group': 8, 'id': 86, 'label': '下地島', 'region': '沖縄'}],
    'edges': [(0, 9), (0, 30), (1, 8), (2, 30), (3, 9), (3, 8), (3, 29), (3, 30), (4, 30), (4, 9), (5, 9), (5, 29), (5, 43), (5, 8), (5, 30), (6, 30), (7, 30), (8, 13), (8, 11), (9, 78), (9, 43), (9, 23), (9, 31), (9, 21), (9, 11), (9, 42), (9, 16), (9, 51), (9, 28), (9, 19), (9, 12), (9, 22), (9, 60), (9, 30), (9, 26), (9, 17), (9, 20), (9, 44), (9, 50), (9, 25), (9, 15), (9, 29), (10, 11), (11, 26), (11, 42), (11, 30), (12, 44), (12, 27), (12, 42), (12, 30), (13, 30), (13, 42), (14, 30), (15, 30), (15, 42), (15, 26), (16, 42), (16, 60), (16, 27), (17, 27), (17, 30), (17, 42), (18, 30), (19, 26), (19, 78), (19, 44), (19, 49), (19, 51), (19, 43), (19, 60), (19, 42), (19, 29), (20, 42), (21, 60), (21, 78), (21, 29), (21, 42), (22, 30), (23, 78), (23, 30), (23, 60), (24, 30), (25, 44), (25, 60), (26, 60), (26, 66), (26, 58), (26, 84), (26, 67), (26, 78), (26, 82), (26, 30), (26, 68), (26, 29), (26, 62), (26, 69), (27, 60), (27, 67), (27, 49), (28, 59), (28, 60), (28, 69), (28, 78), (28, 49), (29, 57), (29, 78), (29, 42), (29, 69), (29, 72), (29, 68), (29, 67), (29, 43), (29, 51), (29, 58), (29, 56), (29, 61), (29, 84), (29, 66), (29, 62), (29, 60), (30, 58), (30, 82), (30, 51), (30, 61), (30, 54), (30, 44), (30, 42), (30, 48), (30, 46), (30, 59), (30, 49), (30, 69), (30, 68), (30, 62), (30, 52), (30, 78), (30, 55), (30, 50), (30, 39), (30, 57), (30, 41), (30, 60), (30, 67), (30, 53), (30, 72), (30, 84), (30, 66), (30, 86), (30, 43), (30, 56), (31, 78), (31, 44), (31, 60), (32, 36), (32, 35), (32, 37), (32, 33), (33, 34), (33, 37), (37, 38), (38, 39), (39, 40), (42, 62), (42, 67), (42, 58), (42, 45), (42, 49), (42, 71), (42, 69), (42, 68), (42, 47), (42, 78), (42, 57), (42, 60), (42, 66), (42, 72), (43, 62), (43, 69), (43, 84), (43, 68), (43, 60), (43, 72), (43, 78), (43, 82), (44, 69), (44, 86), (44, 57), (44, 78), (44, 62), (44, 49), (47, 49), (49, 60), (50, 78), (51, 78), (54, 78), (55, 60), (56, 78), (57, 60), (58, 78), (58, 69), (58, 60), (60, 72), (60, 78), (60, 71), (60, 69), (60, 68), (60, 77), (60, 64), (60, 65), (60, 84), (62, 78), (62, 63), (62, 64), (62, 65), (67, 77), (67, 78), (68, 78), (69, 73), (69, 72), (69, 78), (69, 75), (69, 71), (69, 74), (69, 70), (69, 76), (72, 76), (72, 78), (72, 73), (72, 74), (74, 75), (75, 78), (76, 78), (78, 80), (78, 86), (78, 85), (78, 84), (78, 82), (78, 81), (78, 79), (79, 80), (82, 83), (82, 84), (84, 85)]
}

それと、後ほど使う辞書も作っておきます。

id2label = {i["id"]: i["label"] for i in graph_data["nodes"]}
id2group = {i["id"]: i["group"] for i in graph_data["nodes"]}

2.ネットワークグラフ作成(2D)

それでは、2Dのネットワークグラフを作ります。
下記のコードで、グラフを作成&表示します。
ノードの頂点は現実の緯度・経度由来のものではなく、Fruchterman-Reingold アルゴリズムによって適当に設定しています。

G = nx.Graph()
G.add_edges_from(graph_data["edges"])

pos = nx.spring_layout(G)
edge_x = []
edge_y = []
for edge in G.edges():
    x0, y0 = pos[edge[0]]
    x1, y1 = pos[edge[1]]
    edge_x.append(x0)
    edge_x.append(x1)
    edge_x.append(None)
    edge_y.append(y0)
    edge_y.append(y1)
    edge_y.append(None)

edge_trace = go.Scatter(
    x=edge_x, y=edge_y,
    line=dict(width=0.5, color='#888'),
    hoverinfo='none',
    mode='lines')

node_x = []
node_y = []
for node in G.nodes():
    x, y = pos[node]
    node_x.append(x)
    node_y.append(y)

node_trace = go.Scatter(
    x=node_x, y=node_y,
    mode='markers',
    hoverinfo='text',
    marker=dict(
        showscale=True,
        colorscale='YlGnBu',
        reversescale=True,
        color=[],
        size=10,
        colorbar=dict(
            thickness=15,
            title='Node Connections',
            xanchor='left',
            titleside='right'),
        line_width=2))

node_adjacencies = []
node_text = []
for node, adjacencies in enumerate(G.adjacency()):
    node_adjacencies.append(len(adjacencies[1]))
    node_text.append(id2label[adjacencies[0]])

node_trace.marker.color = node_adjacencies
node_trace.text = node_text

fig = go.Figure(data=[edge_trace, node_trace],
                layout=go.Layout(
                    title='<br>Network graph for AirLine',
                    titlefont_size=16,
                    showlegend=False,
                    hovermode='closest',
                    margin=dict(b=20,l=5,r=5,t=40),
                    annotations=[dict(
                        text="Python code: <a href='https://plotly.com/ipython-notebooks/network-graphs/'> https://plotly.com/ipython-notebooks/network-graphs/</a>",
                        showarrow=False,
                        xref="paper", yref="paper",
                        x=0.005, y=-0.002)],
                    xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                    yaxis=dict(showgrid=False, zeroline=False, showticklabels=False)))
fig.show()

下図のネットワークグラフが出力されました。

出力結果1

特定の領域を選択してみます。

出力結果2

なお、ノードの上にカーソルを移動するとラベル(ここでは空港名)が表示されます。

3.ネットワークグラフ作成(3D)

次に、3Dのネットワークグラフを作成します。
igraphというライブラリが必要なのでインストールします。

!pip install python-igraph

また、3D出力用に新たなライブラリをインポートします。

import igraph as ig
from plotly.offline import iplot
import plotly.graph_objs as go

下記のコードで、グラフを作成&表示します。
ノードの頂点は前述と同様に現実の緯度・経度由来のものではなく、今度は、kamada-kawai アルゴリズムによって設定しています。

G = ig.Graph(graph_data["edges"], directed=False)

labels = [id2label[i.index] for i in G.vs]
group = [id2group[i.index] for i in G.vs]
layt = G.layout('kk', dim=3)
N = G.vcount()

Xn = [layt[k][0] for k in range(N)]
Yn = [layt[k][1] for k in range(N)]
Zn = [layt[k][2] for k in range(N)]
Xe = []
Ye = []
Ze = []
for e in G.es:
    Xe+=[layt[e.source][0],layt[e.target][0], None]
    Ye+=[layt[e.source][1],layt[e.target][1], None]
    Ze+=[layt[e.source][2],layt[e.target][2], None]

trace1=go.Scatter3d(
    x=Xe,
    y=Ye,
    z=Ze,
    mode='lines',
    line=dict(color='rgb(125,125,125)', width=1),
    hoverinfo='none')

trace2=go.Scatter3d(x=Xn,
                    y=Yn,
                    z=Zn,
                    mode='markers',
                    name='actors',
                    marker=dict(symbol='circle',
                                size=6,
                                color=group,
                                colorscale='Viridis',
                                line=dict(color='rgb(50,50,50)', width=0.5)),
                    text=labels,
                    hoverinfo='text')

axis=dict(showbackground=False,
          showline=False,
          zeroline=False,
          showgrid=False,
          showticklabels=False,
          title='')

layout = go.Layout(title="<br>Network graph for AirLine(3D)",
                   width=1000,
                   height=1000,
                   showlegend=False,
                   scene=dict(xaxis=dict(axis),
                              yaxis=dict(axis),
                              zaxis=dict(axis),),
                   margin=dict(t=100),
                   hovermode='closest',
                   annotations=[dict(
                       showarrow=False,
                       text="<a href='https://plotly.com/python/v3/3d-network-graph/'>Reference</a>",
                       xref='paper',
                       yref='paper',
                       x=0,
                       y=0.1,
                       xanchor='left',
                       yanchor='bottom',
                       font=dict(size=14))],)

data=[trace1, trace2]
fig=go.Figure(data=data, layout=layout)

iplot(fig, filename='AirLine_3D')

下図のネットワークグラフが出力されました。

出力結果3

特定の領域を選択してみます。

出力結果4

こちらは、グラフを回転させることもできます。

4.参照

この記事で参照したサイトです。

  • NetworkX:https://networkx.org/documentation/stable/index.html
  • Plotly:https://plotly.com/python/
Pocket

コメントを残す

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