俺のOneNote

俺のOneNote

データ分析が仕事な人のOneNote愛とか、分析小話とか。

ネットワークグラフをつくるためのTableauとPython

Tableauでネットワークグラフを可視化しようとすると、座標データをどう用意するかが一番ネックになると思います。
現状、Tableau内でネットワークグラフを自動生成する機能は無さそうです。

ので、この辺の処理はPythonを噛ませると楽にできるので、ちゃんとできるか試行します。

networkxによる処理

そもそもnetworkxを使って完結しちゃえばいいじゃん、という話ではありますが、
TableauはじめBIプラットフォーム上で表現できれば色々使用用途が高いです。

まずはリファレンスに従い、乱数を固定しておいたほうが良さそうです。

networkx.github.io

import random
random.seed(246) 
import numpy
numpy.random.seed(4812)

pandas.DataFrameで以下のようなサンプルデータを用意。

import pandas as pd
edge_df = pd.DataFrame({
    "source":["A","B","B","C","A","C","C","A","B","F","E","A","H"],
    "target":["B","C","D","D","E","E","F","G","G","G","H","I","I"],
    "weight":[0.6,0.1,0.2,0.9,0.8,0.7,0.5,0.3,0.8,0.1,0.7,0.4,0.8]
})

networkxを使用して、任意のアルゴリズムによるノードの座標を取得しておきます。

import networkx as nx
G = nx.from_pandas_edgelist(edge_df,edge_attr=True)
pos = nx.spring_layout(G)

この座標情報をテーブル化してTableauに読ませることになります。

なお、普通に描写するとこんな感じ。

edges = G.edges()
weights = [G[u][v]['weight'] for u,v in edges]
nx.draw(G, pos, edges=edges, width=weights, node_size=400, node_color="c",with_labels=True,font_weight="bold")

f:id:kopaprin:20200501153406p:plain

エッジのweight以外も属性をつけることが可能です。
その場合、上記の例ではinput_dfに別カラムをつけ、edge_attr=TrueをしておけばOK。

これと同じネットワークグラフをTableauで描写するのが目的です。 posはdict型なので、テーブル化しておきます。

print(type(pos))
print(pos)
>>> <class 'dict'>
>>> {'A': array([ 0.05494571, -0.43277963]), 'B': array([0.38390294, 0.05184413]), 'C': array([-0.08115845,  0.52158239]), 'D': array([-0.08585107,  0.92455717]), 'E': array([-0.38630969, -0.13289213]), 'F': array([0.5411944 , 0.85869962]), 'G': array([ 0.75986786, -0.13710168]), 'H': array([-0.73922077, -0.65390988]), 'I': array([-0.44737093, -1.        ])}
import pandas as pd
node = []
x = []
y = []
for k,v in nodePos.items():
  node.append(k)
  x.append(v[0])
  y.append(v[1])

node_df=pd.DataFrame({
    "item":node,
    "X":x,
    "Y":y
})

f:id:kopaprin:20200501154446p:plain

作成したノードの座標情報データnode_dfと、
最初につくったエッジの情報データedge_dfをTableauへ送り込む用に加工します。

result_df_1 = pd.merge(edge_df,node_df,how="left",left_on="source",right_on="item")
result_df_2 = pd.merge(edge_df,node_df,how="left",left_on="target",right_on="item")
result_df = pd.concat([result_df_1, result_df_2])
result_df["edge_name"] = result_df["source"] + "_" + result_df["target"]

edge_dfを縦積みにして、source,targetそれぞれのX座標,Y座標をjoinします。
おまけにfrom-toを表すedge_nameをつけておきます。

f:id:kopaprin:20200501173837p:plain

よーし、準備OK!
result_dfをTableauに持っていくぞー。

Tableauによる表現

まずはX,Y座標によるscatter plotにして二重軸にします。
X,Y座標のメジャーを「合計」にしないように注意。
「平均」とか「最大」とかを選びましょう。

f:id:kopaprin:20200501175501p:plain

1つめの軸はディメンションのedge_nameを詳細にドラッグ(itemよりも上の階層)してマークの種類は「線」、
edgeの太さや色はweightでお好きに加工。
edgeの完成です。

f:id:kopaprin:20200501175817p:plain

2つめの軸のマークは「点」、詳細はitemだけでOKで、適当にラベルとか大きさを調整します。 これでnodeの完成。

f:id:kopaprin:20200501175941p:plain

あとは二重軸を同期して完成です~

f:id:kopaprin:20200501180039p:plain

無事、networkxと同じ描写ができましたね。
BIに載せることができると、表示範囲のフィルタリング等がしやすいので重宝しそうです。
データも最低限source, targetの情報さえあれば描写できちゃいますしね。