[Python]PlotlyをつかってSankey Diagram(サンキーダイアグラム)を描いてみた。

[Python]PlotlyをつかってSankey Diagram(サンキーダイアグラム)を描いてみた。
今回はplotlyを使ってSankey Diagram(サンキーダイアグラム)を作成してみます。最初、グラフの名前がわからず調べるのに苦労しました。

今回のプロットするための疑似データを作成してみます。データはCRMデータの会員離脱を想定してます。ただ、傾向を付与するものは面倒なので「年代」と「性別」とは独立であるとしてます。すみません。

In [1]:
import pandas as pd
import numpy as np

#性別
gender_labels = ["男性", "女性"]
weight = np.array([.3,.7])
gender = np.random.choice(gender_labels, 100, p=weight)

#年代
age_labels = ["10代", "20代", "30代", "40代", "50代"]
weight = np.array([.2, .2, .2, .2, .2])
age = np.random.choice(age_labels, 100, p=weight)

#離脱と生存
survival_labels = ["維持", "離脱"]
weight = np.array([.6,.4])
survival = np.random.choice(survival_labels, 100, p=weight)

Data = pd.DataFrame({'gender':gender, 'age':age, 'surv':survival})
Data.head()
Out[1]:
gender age surv
0 女性 40代 離脱
1 女性 20代 維持
2 女性 20代 離脱
3 女性 30代 離脱
4 女性 20代 維持
大体、SQLで抽出したらこんな感じではないでしょうか?

前処理

ノード(性別や年代など)の接続パターンごとに接続の大きさ(リンク)を集計していきます。今回は性別から年代、年代から離脱状況へと2段階のプロットしていきます。なので、性別から年代年代から離脱状況の集計を行い、それらを結合させてリンクデータを作成します。source_labelsが元ノードの名称、target_labelsが接続先ノードの名称、valueが人数になります。

In [2]:
#性別から年代の集計
Gender2Age = Data.groupby(['gender','age']).agg({'age':'count'})
Gender2Age.columns = ['value']
Gender2Age = Gender2Age.reset_index(['gender','age'])
Gender2Age.columns = ['source_labels','target_labels','value'] 
Gender2Age.head()
Out[2]:
source_labels target_labels value
0 女性 10代 11
1 女性 20代 14
2 女性 30代 21
3 女性 40代 14
4 女性 50代 12
In [3]:
#年代から離脱状況の集計
Age2Sur = Data.groupby(['age','surv']).agg({'age':'count'})
Age2Sur.columns = ['value']
Age2Sur = Age2Sur.reset_index(['age','surv'])
Age2Sur.columns = ['source_labels','target_labels','value']
Age2Sur.head()
Out[3]:
source_labels target_labels value
0 10代 維持 8
1 10代 離脱 5
2 20代 維持 12
3 20代 離脱 7
4 30代 維持 13
In [4]:
#結合
Link_df = pd.concat([Gender2Age,Age2Sur])
Link_df.head()
Out[4]:
source_labels target_labels value
0 女性 10代 11
1 女性 20代 14
2 女性 30代 21
3 女性 40代 14
4 女性 50代 12

plotlyに渡すデータの形に整形します。plotlyにデータを渡す際にノードを数字に変換して置かなければならないので、名称ごとのインデックス辞書を作って変換します。

In [5]:
labels = np.unique(np.hstack([Link_df['source_labels'],Link_df['target_labels']]))
labels = ['男性','女性','10代','20代','30代','40代','50代','維持','離脱'] #手動で並べ替え
index = np.arange(0,len(labels))
labels_dict = dict(zip(labels, index))
labels_dict
Out[5]:
{'男性': 0,
 '女性': 1,
 '10代': 2,
 '20代': 3,
 '30代': 4,
 '40代': 5,
 '50代': 6,
 '維持': 7,
 '離脱': 8}
In [6]:
Link_df['source'] = Link_df['source_labels'].map(labels_dict)
Link_df['target'] = Link_df['target_labels'].map(labels_dict)
Link_df.head()
Out[6]:
source_labels target_labels value source target
0 女性 10代 11 1 2
1 女性 20代 14 1 3
2 女性 30代 21 1 4
3 女性 40代 14 1 5
4 女性 50代 12 1 6

ついでに、ノードの設定もデータフレームの形で整形しておきます。

In [7]:
Node_df = pd.DataFrame({'labels':labels,'index':index})
Node_df['color'] = ['skyblue','pink','green','green','green','green','green','blue','red']
Node_df
Out[7]:
labels index color
0 男性 0 skyblue
1 女性 1 pink
2 10代 2 green
3 20代 3 green
4 30代 4 green
5 40代 5 green
6 50代 6 green
7 維持 7 blue
8 離脱 8 red
colorの部分は、RGB指定などもできるのでお好みで。

Snakey diagram(サンキーダイアグラム)の描画

plotlyを呼び出します。

In [8]:
import plotly.plotly as py
import plotly.offline as offline
offline.init_notebook_mode(connected=False)

プロットするデータと条件を指定します。

In [9]:
data_trace = dict(
    type='sankey',
    domain = dict( #作図領域
      x =  [0,1],
      y =  [0,1]
    ),
    orientation = "h", #フローの方向
    valueformat = ".0f", #数値フォーマット
    valuesuffix = "人", #単位
    node = dict(
      pad = 15, #ノード間の距離
      thickness = 20, #ノードの幅
      line = dict(  #ノードの枠線の設定
        color = "black",
        width = 0.5
      ),
      label = Node_df['labels'], #ノードの枠線
      color = Node_df['color'] #ノードのカラー
    ),
    link = dict(
      source = Link_df['source'], #リンク元のノード番号
      target = Link_df['target'], #リンク先のノード番号
      value = Link_df['value'] #リンクの太さ
  ))

次にグラフのレイアウトを指定します。取り敢えずタイトルだけ付けた感じです。背景色などもここで指定します。より詳しい設定は、公式ページのsankeyをご参考ください。

In [10]:
layout =  dict(
    title = "会員の離脱傾向",
    font = dict(
      size = 10
    )
)

描画を実行します。ここではオフラインで描写しているので、ブラウザに別のウィンドが立ち上がると思います。記事上は、オフラインで作成したHTMLをiframeを使って読み込ませています。

In [11]:
fig = dict(data=[data_trace], layout=layout)
offline.plot(fig)

年代の順序が少し残念ですが、ドラッグすることで順番は入れ替えることが可能です。インタラクティブだとこういうところが便利ですね。

まとめ

今回はplotlyを使ってサンキーダイアグラムを書いてみました。Linkの色なども指定できるのですが、どうにも色彩センスが皆無のためここまで。tableauだとサンキーダイアグラムは作成するのが困難ですが、plotlyならば比較的簡単に作成できると思います。ぜひ使ってみてください。

Pythonカテゴリの最新記事