夕蛙のなく頃に

データアナリストとして学んだことや趣味で勉強し始めたIoTをアウトプットする

ユーザー行動フローをPlotlyのSankeyDiagramで可視化する

なにこれ

どのアクションで離脱しやすいか・思ったような行動をユーザーがしてくれているかを把握するために、アプリ内のユーザーの遷移状態・行動フローを可視化したいです。

今回はその手段として、PlotlyのSankeyDiagramを使おうと思います。
コードはCollaboratoryにまとめてあります。

https://drive.google.com/open?id=1ilGnpeOv7y49xBrQ5I0lrHNaT5H_tY6w

SankeyDiagramとは

サンキー・ダイアグラム(英Sankey diagram)は工程間の流量を表現する図表である。
サンキー ダイアグラム - Wikipedia

f:id:frogdusk:20191226113856p:plain

「アクションというノード」と「アクション間を遷移するユーザー数というリンク」で構成されたネットワークを可視化するのに使えそうです。

使ってみる

Plotlyに渡すためのデータ

リンク情報として、どのノードから(source)どのノードへ(target)どのくらいの量(value)を渡します。

ノード番号はindex番号として渡すため、例えばノード0からノード1へ100のvalue, ノード1からノード2へ50のvalueだとすると、以下のように渡します。

source = [0, 1]
target = [1, 2]
value = [100, 50]

集計の方法にもよりますが、今回は ユーザー毎にアクションした時刻が項目として横に広がるデータ を出発点とします。
そこからPlotlyに渡せるようデータを加工します。

セッティング

必要なライブラリをインポートします。

import numpy as np
import pandas as pd
import plotly
from IPython.display import HTML

テストデータ

df = pd.DataFrame({
  'user_id': list(range(8)),
  '登録': pd.Timestamp('20190101'),
  'アクションA': [pd.Timestamp('20190103') for _ in range(5)] + [pd.NaT] * 3,
  'アクションB': [pd.Timestamp('20190105') for _ in range(2)] + [pd.NaT] * 6,
  'アクションC': [pd.NaT] * 5 + [pd.Timestamp('20190110') for _ in range(3)]
})

f:id:frogdusk:20191226145943p:plain

  • 登録 -> A -> B が2人
  • 登録 -> A が3人
  • 登録 -> C が3人

となるような図ができあがるはずです。

データ加工

「user_id, アクション1, アクション2, ...」となっているデータを「user_id, アクション名, アクション時刻」とuser_id毎にアクション時刻でソートするように変換し、user_id毎にアクションのラグを取ります。

# user_id, アクション名, アクション時刻 というDataFrameに変換
lag_df = df.set_index('user_id').stack().reset_index().sort_values(by=['user_id', 0])

# ラグをとる
lag_df['after_level_1'] = lag_df.groupby('user_id')['level_1'].shift(-1)

f:id:frogdusk:20191226150706p:plain

こうすることで、1行毎にアクションからアクションへのリンクがわかるデータになるので、それを集計します。

# edgesディクトの初期化
edges = dict.fromkeys(lag_df['level_1'].drop_duplicates().values, {})
for k in edges:
  edges[k] = dict.fromkeys(lag_df['level_1'].drop_duplicates().values, 0)

# その後の行動がない行を除いて集計
for v in lag_df[~lag_df['after_level_1'].isnull()].values:
  edges[v[1]][v[3]] += 1
pd.DataFrame(edges).T

f:id:frogdusk:20191226150826p:plain

Plotlyに渡せるようなリストを作成します。

labels = list(edges.keys())
sources = []
targets = []
values = []

# labelsにアクション名リストを作成し、そのindex番号をsourcesとtargetsに入れる
for src in edges:
  for tgt, v in edges[src].items():
    sources.append(labels.index(src))
    targets.append(labels.index(tgt))
    values.append(v)

PlotlyのSankeyDiagramで可視化

data = dict(
    type='sankey',
    node = dict(
      label = labels
    ),
    link = dict(
      source = sources,
      target = targets,
      value = values
  ))

layout =  dict(
    title = "行動フロー",
    font = dict(
      size = 20
    )
)

fig = dict(data=[data], layout=layout)
plotly.offline.plot(fig, validate=False)
HTML('temp-plot.html')

f:id:frogdusk:20191226151451p:plain

いい感じに可視化されました(^ ^)

そのほかのライブラリとして

「d3-sankey-diagram」というjavascriptのライブラリを用いることでSankeyDiagramを作成可能です。

Pythonで扱うためのrapperとしてricklupton/ipysankeywidgetが個人開発されており、当初はこちらを使っていたのですが、Issueで報告されている通り、Layoutのカスタマイズに難があったりCollaboratoryでの可視化ができません

データの渡し方的にはこちらの方が好みだったのですが、やむなくPlotlyの方を使いました。