Python で生成するサンキーダイアグラム

2022-08-05

はじめに

Python を用いてサンキーダイアグラム (Sankey Diagram) を生成する方法を紹介します。

サンキーダイアグラムとは工程間で取引量や金額の流量を可視化するのに有用な図です。 先日社内のプレゼンテーションで利用したのですが、この図の表現を知らなかった人も居たようなので触れたいと思います。

本記事ではまずサンキーダイアグラムの概要、Plotly を用いた List ベースでの生成、Holoviews を用いた DataFrame ベースでの生成について紹介します。

サンキーダイアグラム

サンキーダイアグラム は工程間で取引量や金額の流量を可視化するのに有用な図です。 例えば、ある商材の国家間での取引量の可視化や、物流の中継点及び取引のボリュームサイズの可視化をしたいときに使います。

物の流れを可視化するという目的ではフローチャートがよく使われますが、サンキーダイアグラムの場合は流れだけではなくその量の大きさも可視化する事ができます。

e.g. エネルギー消費予測 Plotly Sample Plotly の公式ページ より

Plotly で生成するサンキーダイアグラム

サンキーダイアグラムは matplotlib を用いても描画出来ますが、Plotly を用いると公式ページあるようにきれいな表現が出来ます。 Plotly で生成したものは、iframe などで HTML に埋め込んだり、Jupyter Notebook で埋め込むとマウスホバーで流れを追うことが出来ます( (残念ながらこのブログは Markdown -> iframe の実装を現時点でしていません!!))。

Plotly の公式ページの簡単なサンプルで見てみましょう。基本的にサンキーダイアグラムは以下の 2 つのポイントを考えたら良いです。

  1. ノードの生成
  2. リンクの生成

Plotly の場合、ノードの生成は名前 (label)、色、大きさを指定します。 リンクに関しては、リンクの元となるノード (source) と接続先のノード (target)、およびリンクの太さ (value) を指定します。 このとき、source と target は node 生成時に label として渡す list の index により指定します。

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], # indices correspond to labels, eg A1, A2, A1, B1, ...
      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()

Plotly Basic Sample

Plotly によるサンキーダイアグラムの生成はシンプルかつ綺麗ですが、source と target の指定が少々手間です。 Python でデータを扱うときは pandas を用いる事が多く、やはり DataFrame からそのまま描画がしたくなります。
この記事 に Plotly を用いて DataFrame からサンキーダイアグラムを生成する例がありますが、やはり Plotly の形に合わせた source と target を取得する部分が煩雑になります。

HoloViews を用いて DataFrame から サンキーダイアグラムを生成する

HoloViews は matplotlib や Bokeh などの描画ライブラリのラッパーのようなものです。 HoloViews の場合は以下の形で DataFrames をデータソースとして渡し、利用するフィールド名を指定することで DataFrames の構造を活かしてサンキーダイアグラムを生成する事が出来ます。

以下に Plotly と同様のデータセットを DataFrame として定義した形で描画を行います。 描画時には kdims にリンクの出力元と出力先になるフィールド名指定し、リンクの太さを指定する値は vdims に渡します。 また、オプションとしてリンクの色を出力元の色に合わせるように指定しています。

import pandas as pd

data = [
    ["A1", "B1",  8],
    ["A2", "B2",  4],
    ["A1", "B2",  2],
    ["B1", "C1",  8],
    ["B2", "C1",  4],
    ["B2", "C2",  2],
]

df = pd.DataFrame(data).rename(
    {0: "from", 1: "to", 2: "value"},
    axis="columns",
)


import holoviews as hv
hv.extension('bokeh')
from holoviews import opts, dim

basic_sankey = hv.Sankey(
    df,
    kdims=["from", "to"],
    vdims=["value"],
    label="Basic Sankey Diagram",
).opts(
    edge_color=dim("from").str(),
)

# Display the diagram in Jupyter.
hv.ipython.display(basic_sankey)

Plotly Basic Sample

ノードはデフォルトだと幅の太さ順に ascending でソートされるので逆になりましたが、DataFrames を用いて同様の図が生成されています。

JavaScript の場合

余談ですが、JavaScript ベースでサンキーダイアグラムを描画したい場合には Google Charts でもきれいな表現が可能そうでした。

終わりに

本記事では Python を用いたサンキーダイアグラムの生成について紹介しました。 データを可視化するには目的に沿った図を用いると意図がわかりやすなります。 もしデータの方向と量を可視化したいときにはサンキーダイアグラムを用いると良いかもしれません。

Belong のエンジニアリングでは一緒にサービスを育てる仲間を募集しています。 もし弊社に興味を持っていただけたら https://entrancebook.belonginc.dev/ をご覧いただけたら幸いです。