grasys blog

Sankey Diagram という可視化方法

こんにちは

grasys でデータ分析をしている t.watanabe です!

みなさんは「web サイトのお客さんの移動ログ」や「ゲーム内のユーザさんの行動ログ」から「商品を買ってくれるお客さんがどのような行動をしているか」など考えることはありませんか?

ただ、人やモノの移動をどうやって可視化すべきか迷うときはないでしょうか?

そのようなときに使いやすい Sankey Diagram を紹介したいと思います

Sankey Diagram とは?

なにかしらの遷移の可視化に特化した図表です

python のライブラリの plotly で表示することを前提とします

こちらのグラフは単純な Sankey Diagram です

plotly のイメージを引用しました

各青い矩形が遷移のターニングポイント(node)で帯の幅が遷移数、帯の結びつきが遷移を表しています

では、Sankey Diagram を描いてみましょう

Sankey Diagram を描いてみる

まずは環境設定からです

今回は後々のデータ分析などをできる環境まで作りますので jupyter notebook に描くことを目指します

Windows の場合

powershell -c "irm https://astral.sh/uv/install.ps1 | more"

MacOS, Linux の場合

curl -LsSf https://astral.sh/uv/install.sh | less

uv を入れたら作業ディレクトリに移動し

uv init .
uv add plotly jupyter
uv sync
uv run jupyter notebook --allow-root --port=8888 --ip=0.0.0.0 --NotebookApp.token=''

と打ち込み jupyter を起動します

ブラウザで

http://localhost:8888

にアクセスしてください

このような画面がでれば成功です(最初はホワイトテーマなので好みのものに変えてください)

右クリック/New Notebook/

で notebook を作成し、セルに下記のコードをコピー・アンド・ペーストしてください

import plotly.graph_objects as go

fig = go.Figure(data=[go.Sankey(
    node = dict(
      pad = 15,
      thickness = 20,
      line = dict(color = "black", width = 0.5),
      label = ["A1", "A2", "B1", "B2", "C1", "C2"],
      color = "blue"
    ),
    link = dict(
      source = [0, 1, 0, 2, 3, 3], 
      target = [2, 3, 3, 4, 4, 5],
      value = [8, 4, 2, 8, 4, 2]
  )
)])

fig.update_layout(title_text="Basic Sankey Diagram", font_size=10)
fig.show()

実行すると冒頭の Sankey Diagram が表示されます

実データに適応する前に一つずつ確認します

import plotly.graph_objects as go

ここは plotly の Sankey Diagram を呼び出します

fig = go.Figure(data=[go.Sankey(
   ...
)])

Sankey Diagram をつくります

次はオプションです

    node = dict(
      pad = 15,
      thickness = 20,
      line = dict(color = "black", width = 0.5),
      label = ["A1", "A2", "B1", "B2", "C1", "C2"],
      color = "blue"
    ),

ポイントとなる “ノード” の設定を作ります

      pad = 15,
      thickness = 20,

これは表示時の空白やサイズの設定です

  line = dict(color = "black", width = 0.5),

帯の設定です

黒い色の 0.5 サイズです

  label = ["A1", "A2", "B1", "B2", "C1", "C2"],

ノードのラベルをつけます

あとでこの順序が重要になるので変数か何かにメモしておきましょう

  color = "blue"

ノードの色です、青で設定しています

次は帯の設定です

    link = dict(
      source = [0, 1, 0, 2, 3, 3], 
      target = [2, 3, 3, 4, 4, 5],
      value = [8, 4, 2, 8, 4, 2]
  )

数字が設定されていますね

なんの数字でしょうか?

そこで先程メモした順番のラベルが登場です

label = ["A1", "A2", "B1", "B2", "C1", "C2"],

これですね

左から A1:0, A2: 1, … C2:5 となります

source には遷移元となるラベルに対応する index を入れます

target は遷移先となる index を入れます

source = [0, 1, 0, 2, 3, 3], 
target = [2, 3, 3, 4, 4, 5],
は
source = ['A1', 'A2', 'A1', 'B1', 'B2', 'B2'], 
target = ['B1', 'B2', 'B2', 'C1', 'C1', 'C2'],
ということです
つまり

index[0] が A1 -> B1
index[1] が A2 -> B2
index[2] が A1 -> B2
index[3] が B1 -> C1
index[4] が B2 -> C1
index[5] が B2 -> C2

となります

value はそれぞれの index の帯の幅です

次に細かな表示の調整をします

fig.update_layout(title_text="Basic Sankey Diagram", font_size=10)

フォントサイズは 10 で Basic Sankey Diagram というタイトルで設定します

fig.show()

これで表示まで行きました

ちょっと、分かりづらいですね

ただ、一回表示までできてしまえばなれるかもしれないです

サンプル描画

最後に実データで表示するサンプルを置いてきます

pandas のデータフレームをユーザごとに行動の順番を stage として定義し A, B, C として並べ、Sankey Diagram に起こします

stage は各ユーザが閲覧した web ページのようなイメージです

例えば下記の図でいうと「サトウ」さんは A -> B の順にページを移動したと読み取れます(図は時系列で表現されており、0番と6番でサトウさんが登場しています)

import pandas as pd
df = pd.DataFrame(
    {
        "user": ["サトウ", "タカハシ", "タナカ", "ワタナベ", "タカハシ", "タナカ", "サトウ", "タカハシ", "タナカ", "ワタナベ"],
        "stage": ["A", "A", "A", "A", "B", "B", "B", "C", "C", "C"],
    }
)

Sankey Diagram では個別の行動ログを全体のボリュームとして集約して描画するため、全体の傾向を掴むのに適しています。

一方で、「特定のユーザーがどう動いたか」というミクロな追跡には不向きなので注意が必要です。


def df2sankey_diagram(df):
    import plotly.graph_objects as go
    
    df = pd.concat([df, df.groupby("user").shift(-1).rename(columns={"stage": "next_stage"})], axis=1)
    df = df.dropna()

    channel2index = {k: i for i, k in enumerate(list(set(df["stage"].tolist()) | set(df["next_stage"].tolist())))}
    index2channel = {v: k for k, v in channel2index.items()}

    labels = [x for x, _ in channel2index.items()]
    src = [channel2index[k] for k in df["stage"].tolist() if k in channel2index.keys()]
    dst = [channel2index[k] for k in df["next_stage"].tolist() if k in channel2index.keys()]
    
    fig = go.Figure(data=[go.Sankey(
        node = dict(
            pad = 15,
            thickness = 20,
            line = dict(color="black", width=0.5),
            label = labels,
        ),
        link = dict(
            source = src,
            target = dst,
            value = [1 for _ in src]
        )
    )])
    fig.update_layout(
        title_text="Sankey Diagram による状態遷移", 
        font_size=10, 
        width=2400, 
        height=400,
        legend=dict(
            title="Sample",
            traceorder='normal',
            itemsizing='constant'
        )
    )
    fig.show()

結び

色々なグラフがありますが、適切なグラフを用いてわかりやすく伝えることは難しいです

しかしながら、協力していく中で可視化は重要なファクタだと思います!

出典

plotly, Sankey Diagram in Python

Installing uv


採用情報
お問い合わせ