search
LoginSignup
19

More than 1 year has passed since last update.

posted at

Pythonで描くサンキーダイアグラム

この記事はデータ可視化Advent Calendar 2020の10日目です。筆者のPyData FukuokaでのLTの内容を元にしています。

TL;DR

サンキーダイアグラムは、量を伴う、時間・空間・状態の遷移の表現に最適です。PythonではPlotlyとHoloViewsに実装されています。この記事では、HoloViewsを用いた例を示します。

サンキーダイアグラムとは

サンキーダイアグラムはフローチャートに似た有向グラフで(下図1)、ネットワーク図の一種と見なすことができます。以下の性質があり、量を伴う、時間・空間・状態の遷移の表現に最適2です。「サンキー」はこのような形式のグラフをほぼ最初に用いた人の名前です。

  • ノード間の関係性と順序 = 流れの⽅向
  • ⽮印(線)の幅 = 流量

Sankeysteam.png

Pythonでの実装

筆者の知る限り、Pythonのデータ可視化パッケージでサンキーダイアグラムをサポートしているものとしては、HoloViewsPlotlyの2つがあります。いずれも、矢印ではなく線でノードをつなぐので、一見すると無向グラフのようですが、左から右へという向きがあるようです。

  • HoloViews
    • holoviews.Sankey
    • バックエンド:BokehとMatplotlib
  • Plotly
    • plotly.graph_objects.Sankey

Plotlyにはいい本があるので、ここではHoloViewsを使用してサンキーダイアグラムを描いてみます。

準備

インストール

condapipでインストールできます。ご自身の環境に合わせて選んで下さい。公式サイトによるとcondaの場合は以下のコマンドが良いようです。

conda install -c pyviz holoviews bokeh
pip install holoviews

import

ここではホバーなど動的なグラフを作成するためにbokehを使います。matplotlibを用いて静的なグラフを描画することもできます。

from bokeh.models import HoverTool
import holoviews as hv
from holoviews import opts, dim

hv.extension('bokeh')

データ

今回サンキーダイアグラムの作成に使用するのは、UN Comtradeという国連の貿易統計部門がまとめているデータで、2018年のスパークリングワインの輸出入に関するものです(参考:American Association of Wine EconomistsのTwitter投稿)。APIでも入手可能ですが、その説明は本題ではないので、以下のようにして用意します。

# 上位30ペアのみ(単位:キロトン)
sparklingwine = [
    ["Europe", "France", "North America", "USA", 736],
    ["Europe", "France", "Europe", "UK", 520],
    ["Europe", "Italy", "Europe", "UK", 513],
    ["Europe", "Italy", "North America", "USA", 394],
    ["Europe", "France", "Asia", "Singapore", 337],
    ["Europe", "France", "Europe", "Germany", 317],
    ["Asia", "Singapore", "Asia", "Japan", 225],
    ["Europe", "France", "Asia", "Japan", 207],
    ["Europe", "France", "Europe", "Belgium", 193],
    ["Europe", "France", "Europe", "Italy", 190],
    ["Europe", "France", "Europe", "Switzerland", 132],
    ["Europe", "Italy", "Europe", "Germany", 121],
    ["Europe", "France", "Europe", "Spain", 103],
    ["Europe", "France", "Oceania", "Australia", 103],
    ["Europe", "Spain", "North America", "USA", 90],
    ["Europe", "Spain", "Europe", "Germany", 85],
    ["Europe", "France", "Europe", "Sweden", 81],
    ["Europe", "France", "North America", "Canada", 78],
    ["Asia", "Singapore", "Oceania", "Australia", 72],
    ["Europe", "France", "Europe", "Netherlands", 71],
    ["Europe", "Italy", "Europe", "Switzerland", 71],
    ["Europe", "Spain", "Europe", "Belgium", 70],
    ["Europe", "Spain", "Europe", "UK", 65],
    ["Europe", "Italy", "Europe", "France", 63],
    ["Europe", "France", "Middle East", "UAE", 59],
    ["Europe", "Italy", "Europe", "Russia", 56],
    ["Europe", "Italy", "Europe", "Belgium", 54],
    ["Europe", "Italy", "Europe", "Sweden", 50],
    ["Europe", "Spain", "Europe", "France", 44],
    ["Europe", "Italy", "North America", "Canada", 43],
    ["Europe", "Italy", "Asia", "Japan", 40],
    ["Europe", "Spain", "Asia", "Japan", 39],
    ["Europe", "France", "Asia", "Hong Kong", 39],
    ["Europe", "France", "Europe", "Denmark", 38],
    ["Europe", "France", "Europe", "Austria", 37],
    ["Europe", "Netherlands", "Asia", "Japan", 34],
]
sparklingwine_df = pd.DataFrame(sparklingwine).rename(
    {0: "region_exp", 1: "country_exp", 2: "region_imp", 3: "country_imp", 4: "value"},
    axis="columns",
)

HoloViewsのサンキーダイアグラムは循環参照ができないので、輸出国と輸入国を区別するために輸出国の名前の後ろにExpを付与します(シンガポールとオランダを除いている訳は完成したグラフを見ると分かります)。

def set_exp(df):
    if df.country_exp in ["Singapore", "Netherlands"]:
        return df.country_exp
    else:
        return df.country_exp + " Exp"

sparklingwine_df = sparklingwine_df.assign(
    country_exp=lambda x: x.apply(set_exp, axis=1)
)

描画

ホバー(bokehの機能)

サンキーダイアグラムの「線」の部分にマウスを当てた時にグラフの上に表示される内容を指定します。ここでは、輸出国、輸入国、量を表示させます。名前と値のペアをタプルにし、値を@カラム名と指定するとDataFrameのカラムから値を取得できます。

hover = HoverTool(
    tooltips=[
        ("From", "@country_exp"),
        ("To", "@country_imp"),
        ("Value [kt]", "@value"),
    ]
)

グラフ本体

hv.Sankeyクラスにデータセットsparklingwine_df、始点ノードと終点ノードのカラム名kdims、流量(線の太さ)vdimsを指定します。labelはグラフのタイトルです。

更に、optsメソッドを使って、サイズ(widthheight)や色(cmapedge_color)などを指定します。ここでは輸出国ごとに線を塗り分けるため、edge_color=dim("country_exp").str()としています。

sparklingwine_sankey = hv.Sankey(
    sparklingwine_df,
    kdims=["country_exp", "country_imp"],
    vdims=["value"],
    label="World's Top Sparkling Wine Trade Routes in 2018 (Value in million USD)",
).opts(
    width=900,
    height=800,
    cmap="glasbey_hv",
    edge_line_alpha=0,
    edge_color=dim("country_exp").str(),
    tools=[hover],
    node_sort=True,
) * hv.Text(
    500,
    -15,
    "Based on AAWE's post on Facebook https://www.facebook.com/wineecon/posts/3557583497601764/",
    fontsize=10,
)

グラフオブジェクトsparklingwine_sankeyを呼び出すと、グラフが表示されます。シンガポールとオランダは貿易の中継地点であり、日本の輸入量においてシンガポール経由が大きな割合を占めていることが分かります。
sparklinkwine_sankey.png

出力

hv.save関数を用いて、htmlやpngとして出力することができます。

hv.save(sparklingwine_sankey, 'sparklingwine_sankey.html')
hv.save(sparklingwine_sankey, 'sparklinkwine_sankey.png', dpi=600)

まとめ

サンキーダイアグラムは、量を伴う、時間・空間・状態の遷移の表現に最適です。PythonではPlotlyとHoloViewsに実装されていて、この記事ではHoloViewsを用いた例を示しました。是非使ってみて下さい!


  1. https://en.wikipedia.org/wiki/Sankey_diagram 

  2. 例えば、ECサイト訪問者が購入に至るまでの過程を人数を含めて示す場合などです。 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
19