Sankey Chartの作成 (by R)
はじめに
業務でSankey Chartを作る機会があり、アウトプットと自分への備忘録も兼ねて、本記事を書きました。
本記事がSankey Chartを作成する機会のある方のお力になれれば幸いです。
目次
- Sankey Chart (Sankey Diagram) とは
- 扱うデータ
- RでSankey Chartを作成できるパッケージ
3.1. networkD3
3.2. ggsankey - まとめ
参考文献(リンク)
1. Sankey Chart (Sankey Diagram) とは
ざっくり言うと、人や物などの工程間での流量を可視化した図。
名前の由来は初めてこの図を投稿した、Matthew Henry Phineas Riall Sankeyにちなんで、
Sankey Chart (Sankey Diagram)と呼ばれているそう。
エネルギーなどの工業データや一部の医薬データにも扱われている。
2. 扱うデータ
以下のようなデータを想定して、本記事のプログラムを記している。
とある時期(X1, X2, X3)ごとに治療を選択するような状況で、治療選択の推移をSankey Chartで可視化したい、という狙いで進めていく。
ID | X1 | X2 | X3 |
---|---|---|---|
1 | Therapy A | Therapy B | Therapy A |
2 | Therapy C | Therapy A | Therapy A |
3 | Therapy B | Therapy B | Therapy C |
: | : | : | : |
各変数は
ID:個人識別番号
X1:初期治療(治療A~治療Dの4水準)
X2:次期治療(治療A~治療Cの3水準)
X3:最終治療(治療A~治療Eの5水準)
を表しているものとする。
set.seed(123)
data <- data.frame(
ID = c(1:200),
X1 = rbinom(200,3,0.5),
X2 = rbinom(200,2,0.4),
X3 = rbinom(200,4, 0.5)
)
data$X1 <- ifelse(data$X1==0, "Therapy A",
ifelse(data$X1==1, "Therapy B",
ifelse(data$X1==2, "Therapy C", "Therapy D")))
data$X2 <- ifelse(data$X2==0, "Therapy A",
ifelse(data$X2==1, "Therapy B","Therapy C"))
data$X3 <- ifelse(data$X3==0, "Therapy A",
ifelse(data$X3==1, "Therapy B",
ifelse(data$X3==2, "Therapy C",
ifelse(data$X3==3, "Therapy D", "Therapy E"))))
3. RでSankey Chartを作成できるパッケージ
本記事で紹介するパッケージは以下の2パッケージである。
- networkD3
- ggsankey
※ 「riverplot」パッケージも以前は実装されていたが、CRANのリポジトリから削除されていたため、本記事では紹介しない (2024年4月4日現在)。
3.1. networkD3 パッケージを用いた作図
networkD3は動的なグラフが作成できるパッケージで、JavaScriptの言語ベースのものである。
作成したSankey Chart上でカーソルを動かすと、どの水準からどの水準へどの程度推移するかが表示される。作成したノードの位置も自由に調整できる。
このnetworkD3を用いた作図では、以下の情報が必要となる。
links:工程間における、とある水準と別の水準の組み合わせとその量
nodes:工程ごとの水準の情報
- linksの作成
初期治療と次期治療の組み合わせとその人数を格納している。
links <- data %>%
select(X1, X2) %>%
count(X1, X2) %>%
rename(source = X1,
target = X2)
ここでのsourceは初期治療、targetは次期治療を意味する。
source | target | n |
---|---|---|
Therapy A | Therapy A | 4 |
Therapy A | Therapy B | 5 |
: | : | : |
- nodesの作成
初期治療と次期治療とが区別できるよう、visitという変数を設けた。
node1 <- data.frame(
name=c(as.character(link1$source)) %>%
unique()
)
node1$visit <- 1
node2 <- data.frame(
name=c(as.character(link1$target)) %>%
unique()
)
node2$visit <- 2
nodes <- rbind(node1,node2)
name | visit |
---|---|
Therapy A | 1 |
Therapy B | 1 |
: | : |
Therapy A | 2 |
: | : |
- linksとnodesの紐づけ
今はlinksで水準における組み合わせとその数、すなわち
治療Aから治療Aが××人いて、治療Aから治療Bが○○人いる~、という情報があり、
nodesで水準の一覧の情報がある。
networkD3のSankey Chartではnodeの位置を数値で認識しているらしく、linksに入っている治療名をそれぞれ対応する数値に変換したい。
そこで、linksとnodesを紐づけるために以下のコードを実行する。
links$IDsource <- match(links$source, nodes[nodes$visit==1,]$name) -1
links$IDtarget <- match(links$target, nodes[nodes$visit==2,]$name) + nrow(nodes[nodes$visit==1,]) -1
IDsourceとIDtargetは数値で0が基準のため、それぞれ1を引いている。
source | target | n | IDsource | IDtarget |
---|---|---|---|---|
Therapy A | Therapy A | 4 | 0 | 4 |
Therapy A | Therapy B | 5 | 0 | 5 |
: | : | : | : | : |
- 作図
長々となったが、ようやくSankey Chartの作図に移る。
sankeyNetwork(Links = links,
Nodes = nodes,
Source = "IDsource",
Target = "IDtarget",
Value = "n",
NodeID = "name",
units = "人",
fontSize = 10,
nodeWidth = 20,
iterations = 0)
オプションの意味(ざっくり)
Links:linksデータ
Nodes:nodesデータ
Source:出発点を表す変数(from links)
Target:到着先を表す変数(from links)
Value:量(from links)
NodeID:グラフ中に現れる文字をどこから拾うか(from nodes)
units:量の単位
fontSize:文字の大きさ
nodeWidth:ノードの幅
iterations:ノードの位置を変える何か(0にしておけば、linksのID変数の数値順に表示される)
(※ HTML形式でのアップロードが分からず、静的なグラフになりましたが、実際のものはカーソルをフロー上に置くと、フローの色が変化して、詳細な情報が得られます。)
ここまでは、X1→X2までの推移だったが、X1→X2→X3という風な複数の推移に対する作図も可能。
詳細は割愛するが、コードを以下に示す。
X1→X2→X3をX1→X2とX2→X3のパーツごとに作り、行結合でまとめ上げるイメージ。
# links 作成
link1 <- data %>%
select(X1, X2) %>%
count(X1, X2) %>%
rename(source = X1,
target = X2)
link2 <- data %>%
select(X2, X3) %>%
count(X2, X3) %>%
rename(source = X2,
target = X3)
# nodes作成
node1 <- data.frame(
name=c(as.character(link1$source)) %>%
unique()
)
node1$visit <- 1
node2 <- data.frame(
name=c(as.character(link2$source)) %>%
unique()
)
node2$visit <- 2
node3 <- data.frame(
name=c(as.character(link2$target)) %>%
unique()
)
node3$visit <- 3
nodes <- rbind(node1,node2,node3)
# linksとnodesの紐づけ
link1$IDsource <- match(link1$source, nodes[nodes$visit==1,]$name) -1
link1$IDtarget <- match(link1$target, nodes[nodes$visit==2,]$name) -1 + nrow(nodes[nodes$visit==1,])
link2$IDsource <- match(link2$source, nodes[nodes$visit==2,]$name) -1 + nrow(nodes[nodes$visit==1,])
link2$IDtarget <- match(link2$target, nodes[nodes$visit==3,]$name) -1 + nrow(nodes[nodes$visit==1,]) + nrow(nodes[nodes$visit==2,])
links <- rbind(link1,link2)
# 作図
sankeyNetwork(Links = links,
Nodes = nodes,
Source = "IDsource",
Target = "IDtarget",
Value = "n",
NodeID = "name",
units = "人",
fontSize = 10,
nodeWidth = 20,
iterations = 0)
こんな感じ。
3.2. ggsankeyパッケージを用いた作図
こちらはnetworkD3による動的なグラフではなく、静的なグラフとして作図されるもの。
Rのグラフパッケージで有名なggplot2と同じ指向なものなので、ggplot2を使ったことのある人はアレンジがしやすいだろう。
パッケージのインストールだが、GitHub上からインストールする({remotes}を用いた)。
remotes:::install_github("davidsjoberg/ggsankey")
ggsankeyを用いて作図を行う場合は、以下のデータ
ID | X1 | X2 |
---|---|---|
1 | Therapy A | Therapy B |
2 | Therapy C | Therapy A |
3 | Therapy B | Therapy B |
: | : | : |
を次のようなデータに変換させる必要がある。
x | node | next_x | next_node |
---|---|---|---|
X1 | Therapy A | X2 | Therapy B |
X2 | Therapy B | NA | NA |
X1 | Therapy C | X2 | Therapy A |
X2 | Therapy A | NA | NA |
: | : | : | : |
少しわかりにくい変換であるが、IDが1の人の推移を2行に分けている(推移の数)。
「next_x」「next_node」が到着先を表す変数でここがNA であると、そこで推移が終わり、別のデータレコードとして判断しているものだと考えられる。
変換自体は簡単で{ggsankey}に内蔵しているmake_long()を使ってできる。
data1 <- data %>%
make_long(X1,X2)
※複数の推移がある場合は、make_long()の引数に各推移を表す変数を追加する。
- 作図
ggplot(data1, aes(x = x, next_x = next_x, node = node, next_node = next_node, fill = node, label = node)) +
geom_sankey(flow.alpha = 0.4,
node.color = "white",
node.alpha = 0.4,
width = 0.01,
space = 20,
smooth = 10) +
geom_sankey_text(color = "black)
- オプション
flow(node).alpha:flow(node)の透過度
node.color:nodeの枠の色(デフォルトはフチなし)
width:nodeの幅
space:node間の間隔(大きくしすぎると、ラベルを入れた際にノードと文字にずれがある)
smooth:flowの滑らかさ(お好みの感じで!)
geom_sankey_text:ノード上に文字を入れる(ggplot()のlabelオプションで指定した文字)
上記のプログラムで作った図はこんな感じ。
上で作ったグラフの色はデフォルトの割り当てがされているものである。
そこで、ここからは色のアレンジを試みる。
ノードの文字とそれに対応する色を割り当てた情報を作る(colors)。
その上で先ほどの作図のプログラムに「scale_fill_manual」を追加して、colorsオブジェクト指定することで任意の色に指定ができる。
colors <- c(
"Therapy A"="indianred2",
"Therapy B"="goldenrod1",
"Therapy C"="springgreen3",
"Therapy D"="lightskyblue1",
"Therapy E"="slateblue1"
)
ggplot(data1, aes(x = x, next_x = next_x, node = node, next_node = next_node, fill = node, label = node)) +
geom_sankey(flow.alpha = 0.5,
node.color = "white",
node.alpha = 0.5,
width = 0.1,
space = 20,
smooth = 10) +
geom_sankey_text(color = "black")+
scale_fill_manual(values = colors,
breaks = c(
"Therapy A","Therapy B","Therapy C","Therapy D"
) )
指定した通りの色になっていることが確認できる。
scale_fill_manual()のbreaksオプションは凡例の順番を指定したもの。
(色のラベルなどは他のサイトなどを参考に)
最後にもうひとつ。
先ほどの図では凡例の順番と図に表れているノードの順番が逆転している。
これを解消するためにdata1におけるnodeの水準を変更する。
ggsankeyの中では水準の順番がグラフ内のノードが下から上に配置されることに相当しているらしく、あえて逆の水準を与えることをする。
Sankey Chartの灰色の背景や罫線を消す、「theme_sankey()」もオプションとして追加した。
data1$node <- factor(data1$node, levels = c(
"Therapy E","Therapy D","Therapy C","Therapy B","Therapy A"
))
ggplot(data1, aes(x = x, next_x = next_x, node = node, next_node = next_node, fill = node, label = node)) +
geom_sankey(flow.alpha = 0.4,
node.color = "white",
node.alpha = 0.4,
width = 0.01,
space = 20,
smooth = 10) +
geom_sankey_text(color = "black")+
scale_fill_manual(values = colors,
breaks = c(
"Therapy A","Therapy B","Therapy C","Therapy D"
)
) +
theme_sankey()
こんな感じ。
後はお好きにタイトルを追加したり、軸の設定、凡例表示など、ggplot2指向に基づいてアレンジを試してみてください。
4.まとめ
人や物などの工程間での流量を可視化した図である、Sankey Chartの作図の説明を行ってきました。
紹介したパッケージは2つで、それぞれ以下の通り。
-
networkD3
:動的なグラフが作成できる -
ggsankey
:ggplot2と同指向でアレンジがしやすそう
その他のオプションも多数あり、いろいろ試してみることをおすすめします。
もしおすすめのオプションやより良いコードの書き方など、何かありましたらコメントで教えて頂けると幸いです。
Discussion