(2021/5/27 追記: もっと簡単に作る方法を書きました)
今回のテーマはSankey Diagramです。徹底的に解説します。
2018年7月末に以下の記事を書きました。
この記事は以下の内容に対する自分の勉強ノートでした。
自分の知識も増え、もう少し明確にSankey Diagram without data prepを理解できそうだなと思い、(雑じゃない)解説記事を書くことにしました。
以下のViz作成を通して解説します。今回は表計算を多用します。ややハードコアです。
今回の記事に使用されたTableau Workbookは以下からダウンロードできます。
データはSample Superstoreです。
【下準備】
Sankey用のディメンション2つとメジャー1つ、Data Densification用のカラムを1つ用意します(これはディメンションとメジャーの区別はしません)。
今回は以下を設定します。
Dimension 1
[Region]
Dimension 2
[Category]
Measure
SUM([Sales])
For Path Frame
[Sales]
補足
For Path Frameに使用するカラムは、この後に出てくるPath Frameの計算式の都合、最小値を与えるレコードに重複が無いカラムが望ましいように思います。
理想的にはレコードIDかもしれません。検証はしていませんが、補足まで。
【Data Densificationを行う】
Data Densificationについては以下のKenのブログがよくまとまっています。
曲線を使うテクニックで多用される技法です。
自分もそろそろData Densificationについて解説した記事を書くべきでしょうか。
詳細は上記Kenのブログに任せるとして、以下の計算フィールドを作成します。
Path Frame
IF [For Path Frame] = {MIN([For Path Frame])} THEN 0 ELSE 97 END
Path Frame (bin)
Path FrameからBinを作成。Bin Sizeは1。
Path Frame自体は「For Path Frameが全レコード中最低値なら0、それ以外なら97を返す」という計算フィールドです。それでBinを作った結果がこちらです。
最小値である0.444でのみPath Frameは0、それ以外では97を与えています。
その後、Path FrameのShow Missing Value (欠損値を表示)オプションをオンにすると、以下のように「本来存在しないデータが、Tableau内で作られる」ことになります。
本当にデータが存在しないので、Densificationによるデータ点の中身はこうなります。
Data Densificationはトリッキーですが、要は既存のデータから、既存のデータ間を埋めるような操作がTableau内で出来る、くらいの理解で大丈夫だと思います。
次に、以下の計算フィールドを作成します。
Path Index
INDEX() //Path Frame (bin)に沿って計算
イメージ的には、下図のようにPath Frame (bin)に対応した整数を与えています。
【Sigmoid曲線を描く】
Path IndexからSankey Diagramの曲線を描いていきます。
曲線にはSigmoid関数を用います。
まず、以下の計算フィールドを作成します。
T
IF [Path Index] <= 49
THEN (([Path Index] - 1) % 49) / 4 - 6
ELSE 12 - (([Path Index] - 1) % 49) / 4 - 6
END
計算式を解説します。
Path Indexが49以下のとき、[Path Index]-1は0から48の間です。
([Path Index] - 1) % 49 は、[Path Index] - 1 を49で割ったときの余りを返します。
[Path Index] - 1 が49未満なので、もちろん値がそのまま余りになります。
(([Path Index] - 1) % 49) / 4 は、0から48を4で割った数なので、もちろん0から12の間です。それを6で引くので、(([Path Index] - 1) % 49 ) / 4 - 6 は-6から6の間ですね。
上記、[Path Index] が50以上の時も同様です。
計算式が煩雑なので、Path_IndexとTをプロットしたものを見てみましょう。
Path_Indexが0~49でTは-6~6、Path_Indexが50~98で同じくTは-6~6となっています。
このTを用いて、以下の計算フィールドを作成します。
Sigmoid
1 / (1+EXP(1)^-[T])
イメージ的には以下のようになります。
ちなみに、Path_Index全体でSigmoid曲線が2本できるわけですが、2本必要な理由は
「Sankey Diagramでポリゴンを描く際に、上側と下側が必要だから」です。
【Sankey Diagramを描くための準備】
Sankey DiagramをPolygon(多角形マーク)で作成するのですが、そのための計算フィールドを作成していきます。
まずSankey Diagramの各Polygonのサイズを決めます。
Sankey Arm Size
[Chosen Measure] / TOTAL([Chosen Measure])
上記のように表計算を設定します。この結果出てくるものは以下のようなイメージです。
Path Frame (bin)はDensificationに使用するだけなので、必要ですが大した意味はありません。
本質的には「Dimension1とDimension2それぞれの組み合わせにおけるSUM(Chosen Measure)の、全体に対する割合」です。
(ちなみにPath Frame (bin)=0の個所は、厳密には0ではありません。0.0000193279 %です)
続いて、Sankey DiagramのPolygonについて、以下の計算式を作成します。
多いので大変かもしれませんが、コピペで大丈夫です。説明も続きます。
Max Position 1
RUNNING_SUM([Sankey Arm Size])
Max Position 1 Wrap
WINDOW_SUM([Max Position 1])
Max for Min Position 1
RUNNING_SUM([Sankey Arm Size])
Min Position 1
RUNNING_SUM([Max for Min Position 1]) - [Sankey Arm Size]
Min Position 1 Wrap
WINDOW_SUM([Min Position 1])
Max Position 2
RUNNING_SUM([Sankey Arm Size])
Max Position 2 Wrap
WINDOW_SUM([Max Position 2])
Max for Min Position 2
RUNNING_SUM([Sankey Arm Size])
Min Position 2
RUNNING_SUM([Max for Min Position 2]) - [Sankey Arm Size]
Min Position 2 Wrap
WINDOW_SUM([Min Position 2])
それぞれの表計算の設定は以下の表のようにしてください。
(Path IndexやSankey Arm Sizeで設定した画面を想定しています)
まず初めに、Max Positionについて解説します。
計算式の中身は RUNNING_SUM([Sankey Arm Size]) であり、Sankey Arm Sizeは
「Dimension1とDimension2それぞれの組み合わせにおけるSUM(Chosen Measure)の、全体に対する割合」
でした。
これをDimension1、Dimension2に沿ってRUNNING_SUMをとるので、
要は各割合を積み上げている、というイメージです。
Min Positionについては、先にMax for Min Positionの計算フィールドを解説します。
計算式は RUNNING_SUM([Sankey Arm Size]) でした。これはPath Frame (bin)に沿って行われます。
Path Frame (bin)によるData Densificationで作られたデータ点それぞれに、Sankey Arm Sizeの値を割り振っている、というイメージです。
表計算にPath Frame (bin)を含める計算は、この後のPolygonを意識しての設計です。
そしてMin Positionは以下の計算式で与えられます。
この計算はDimension 1とDimension 2に沿ったものでした。
RUNNING_SUM([Max for Min Position 1]) - [Sankey Arm Size]
こちらも基本思想は「各割合を積み上げている」ことです。
そして、積みあがった割合からSankey Arm Sizeを引くことにより、ポリゴンの下端を与えているイメージです。
以下の図がMax PositionとMin Positionの理解に役立つと思います。
左のGantt ChartがMax Position、右がMin Positionです。
Max Position WrapとMin Position Wrapについても、Max for Min Positionと同様です。
それぞれのPath Frame (bin)に値を割り振っています。
以上で、Polygon描写のための計算フィールドはそろいました。
表計算の設定が正しく設定されていることを、必ず確認してください。
【Sankey Diagramを描く】
やっとここまで来れました。Sankey Diagramを描くPolygonの計算フィールドを作ります。
まず、やりたいことの外観を説明します。
Dimension1とDimension2をPolygonで繋ぎたいわけですが、まずは直線でどうなる予定かをお見せします。
これを幅をもったPolygonにしたいわけですが、ここでPath IndexとSigmoid関数を使用し、Polygonの形を与える曲線を作ります。
おさらいとして、今回の場合、Sigmoidは2本の曲線を与えます。
Sigmoid関数自体は0~1の関数ですので、この関数に切片と係数を与えたときのふるまいを見てみましょう。
以下の計算フィールドを実験のために用意しました。Coefficient(係数)とIntercept(切片)はパラメータです。
Sigmoid for Experiment
[Coefficient] * [Sigmoid] + [Intercept]
上図の通り、CoefficientでSigmoidの範囲(上では0~1から0~1.2に)を変更しています。
Interceptが0の場合、このCoefficientが曲線の行き先を決めます。
また、簡単のため0に設定しましたが、InterceptでY軸方向に平行移動するため、Sigmoid曲線の起点を0からInterceptの値に変更できます。
パラメータを両方変えると以下のようになります。
Sigmoid曲線の始点はInterceptの値、終点はCoefficient + Interceptの値になります。
この仕組みを利用して
Dimension 1とDimension 2のMax Positionを結んだSigmoid曲線
Dimension 1とDimension 2のMin Positionを結んだSigmoid曲線
上2つの曲線を描写し、それを用いてSankey DiagramのPolygonを描写します。
ということでPolygonの式を見てみましょう。
Sankey Polygon
IF [Path Index] > 49
THEN [Max Position 1 Wrap]
+ ([Max Position 2 Wrap] - [Max Position 1 Wrap])
* [Sigmoid]
ELSE [Min Position 1 Wrap]
+ ([Min Position 2 Wrap] - [Min Position 1 Wrap])
* [Sigmoid]
END
ここで本質的には
InterceptはDimension 1のMin/Max Position
CoefficientはDimension 1とDimension 2のMin/Max Positionの差分
をとっています。MinとMaxを異ならせているのは、単に曲線が2本必要だからです。
Wrapを使っているのは、各Path Frame (bin)にMax/Min Positionの値を割り振るためでした。
さて、いきなりPolygonを描く前に、何が起きているか見てみましょう。
各Dimension 1、Dimension 2でSankey PolygonをLineとして描写しました。
Path Index = 49を境にして、曲線がジャンプしています。
これは、計算に使用する対象がMin Position WrapからMax Position Wrapへと変わっているからです。
そして、この曲線をPath Index = 49で折り返すことにより、帯状のPolygonを描写します。
計算フィールド T を使用して、Path Indexを折り返させています。
試しに1本を折り返してみた結果がこちらです(Lineマークで描写)。
ということで、DetailにDimension 1とDimension 2を、PolygonのPathにPath Frame (bin)を置けばSankey Diagramが作成されます。
この際にPolygonマーク(多角形マーク)にすることをお忘れなく。
詳細はWorkbookを見て頂くとして、体裁を整えると、下記のようなSankey Diagramが作成できます。
【最後に】
Data Prepを必要としないSankey Diagram、いかがでしたか?
Data Densificationと表計算がたっぷりでお腹いっぱいですね。胃もたれしそうです。
今回この記事を書いた意図は、1年前の自分が何となくでしか理解できなかったことへの挑戦でした。
表計算の経験も積み、昔よりは明確に仕組みを理解できたように思います。
一方で、下図のようにSankey Diagramの各Polygonに空白を置いてあげたい所があります。
方法やテンプレート自体はあるのですが、自分の知る限り、今回のようなData Prepを必要としないSankeyでの実装方法は見つかりませんでした。
しばらく実装方法を考えていましたが、今はちょっと分かりません。未来の自分に期待ですね。実装方法をご存知の方、教えて頂ければとっても嬉しいです。
できました。いけるものですね。
ご質問等はTwitterまたはLinkedinまでよろしくお願いします。 それでは。