35 フローチャート・サンキー図・タイムライン

この章では、以下の図を作成するためのコードについて説明します。

  • DiagrammeR と DOT 言語を使ったフローチャート
  • 沖積図(Alluvial diagram)やサンキー図(Sankey diagram)
  • イベントタイムライン

35.1 準備

パッケージを読み込む

以下のコードを実行すると、図の作成に必要なパッケージが読み込まれます。このハンドブックでは、パッケージを読み込むために、pacman パッケージの p_load() を主に使用しています。p_load() は、必要に応じてパッケージをインストールし、現在の R セッションで使用するためにパッケージを読み込む関数です。また、すでにインストールされたパッケージは、R の基本パッケージである baselibrary() を使用して読み込むこともできます。R のパッケージについての詳細は、R の基礎 を参照してください。

pacman::p_load(
  DiagrammeR,     # フロー図
  networkD3,      # 沖積図・サンキー図
  tidyverse)      # データ管理と図作成

データをインポート

この章では、ほとんどデータセットを必要としません。しかし、サンキー図(Sankey diagram) のセクションでは、エボラ出血熱の流行をシミュレートした症例ラインリストを使用します。お手元の環境でこの章の内容を実行したい方は、こちら をクリックして「前処理済みの」ラインリストをダウンロードしてください(.rds 形式でダウンロードされます)。データは rio パッケージの import() を利用してインポートしましょう(rio パッケージは、.xlsx、.csv、.rds など様々な種類のファイルを取り扱うことができます。詳細は、データのインポート・エクスポート の章をご覧ください)。

# ラインリストをインポート
linelist <- import("linelist_cleaned.rds")

ラインリストの最初の 50 行を以下に表示します。

35.2 フローチャート

R パッケージの DiagrammeR を使って、フローチャートを作成することができます。グラフは静的なものもあれば、データセットの変化に基づいて動的に調整することもできます。

ツール

“Graphviz” で図を作成するには、grViz() を用います。この関数は、図を作成するための指示を含む文字列の入力を受け付けます。この文字列の中には、DOT と呼ばれる別の言語で書かれた指示が含まれていますが、基本は容易に学ぶことができます。

基本的な構造

  1. 指示を開始する grViz("
  2. グラフの方向性と名前を指定し、波括弧を開く - 例:digraph my_flow_chart {
  3. グラフ文(Graph 文) - レイアウト、ランク方向
  4. ノード文 (Node 文) - ノードを作成
  5. エッジ文(Edge 文) - ノード間のリンクを与える
  6. 指示を閉じる }"

簡単な例

以下に 2 つの簡単な例を示します。

最小限のプロットを作成します。

# 最小限のプロット
DiagrammeR::grViz("digraph {
  
graph[layout = dot, rankdir = LR]

a
b
c

a -> b -> c
}")

もう一つは、公衆衛生の文脈でもう少し応用が効く例です。

grViz("                           # すべての指示は大きな文字列の中にある
digraph surveillance_diagram {    # digraph は directional graph という意味で、その後にグラフ名が入る
  
  # graph statement
  #################
  graph [layout = dot,
         rankdir = TB,
         overlap = true,
         fontsize = 10]
  
  # nodes
  #######
  node [shape = circle,           # shape = circle
       fixedsize = true
       width = 1.3]               # circle の大きさ
  
  1番目                          # ノードの名称
  2番目
  3番目

  # edges
  #######
  1番目   -> 2番目 [label = '症例移送']
  2番目   -> 3番目 [label = '症例移送']
}
")

構文

基本的な構文

ノード名とエッジ文は、スペース、セミコロン、改行で区切ることができます。

ランクの方向

グラフ文の rankdir 引数を調整することで、プロットを左から右へと移動させることができます。デフォルトは TB(top-to-bottom)ですが、LR(left-to-right)、RL、BT のいずれかを指定できます。

ノード名

ノード名は、上の例のように単一の単語で構いません。複数の単語や特殊文字(括弧やダッシュなど)を使用する場合は、ノード名を一重引用符(' ')で囲みます。ノード名を短くして、以下のように角括弧([ ])で囲んでラベルを割り当てる方が簡単な場合があります。ノード名の中に改行を入れたい場合は、ラベルを介して行う必要があります。以下のように、一重引用符で囲んだノードラベルの中に \n を使用してください。

サブグループ

エッジ文の中で、サブグループはエッジの両側に波括弧({ })で作成できます。エッジは括弧内のすべてのノードに適用され、省略可能です。

レイアウト

  • dot(rankdir を TB、LR、RL、BT のいずれかに設定)
  • neato
  • twopi
  • circo

ノード - 属性値

  • label(テキスト、半角スペースを含む場合は一重引用符で囲む)

  • fillcolor(多数の色が使用可能)

  • fontcolor

  • alpha(透明度 0-1)

  • shape(ellipse、oval、diamond、egg、plaintext、point、square、triangle)

  • style

  • sides

  • peripheries

  • fixedsize(縦(高さ)x 横(幅))

  • height

  • width

  • distortion

  • penwidth (境界線の太さ)

  • x(左・右への変位)

  • y(上・下への変位)

  • fontname

  • fontsize

  • icon

エッジ - 属性値

  • arrowsize

  • arrowhead(normal、box、crow、curve、diamond、dot、inv、none、tee、vee)

  • arrowtail

  • dir(direction, )

  • style(dashed, …)

  • color

  • alpha

  • headport (矢印の前のテキスト)

  • tailport (矢印の後のテキスト)

  • fontname

  • fontsize

  • fontcolor

  • penwidth (矢の太さ)

  • minlen (長さの最長値)

色名:16 進数の値または ‘X11’ の色名、X11 の詳細は こちら を参照してください。

複雑な例

以下の例は、上で作成した surveillance_diagram を発展させたもので、複雑なノード名、グループ化されたエッジ、色、スタイルを追加しています。

DiagrammeR::grViz("               # すべての指示は大きな文字列の中にあります。
digraph surveillance_diagram {    # digraph は directional graph という意味で、その後にグラフ名が入ります。
  
  # graph 文
  #################
  graph [layout = dot,
         rankdir = TB,            # layout top-to-bottom
         fontsize = 10]
  

  # ノード (円)
  #################
  node [shape = circle,           # shape = circle
       fixedsize = true
       width = 1.3]                      
  
  Primary   [label = '第一\n施設'] 
  Secondary [label = '第二\n施設'] 
  Tertiary  [label = '第三\n施設'] 
  SC        [label = 'サーベイランス\n調整',
             fontcolor = darkgreen] 
  
  # エッジ
  #######
  Primary   -> Secondary [label = ' 症例移送',
                          fontcolor = red,
                          color = red]
  Secondary -> Tertiary [label = ' 症例移送',
                          fontcolor = red,
                          color = red]
  
  # エッジのグループ
  {Primary Secondary Tertiary} -> SC [label = '症例報告',
                                      fontcolor = darkgreen,
                                      color = darkgreen,
                                      style = dashed]
}
")

サブグラフのクラスター

ノードをボックス型のクラスターにまとめるには、同じ名前のサブグラフ(subgraph name {})の中にノードを入れます。各サブグラフをバウンディングボックス内で識別するには、以下の 4 つのボックスで示すように、サブグラフの名前を “cluster” で始めます。

DiagrammeR::grViz("             # すべての指示は大きな文字列の中にある
digraph surveillance_diagram {  # digraph は directional graph という意味で、その後にグラフ名が入る
  
  # グラフ文
  #################
  graph [layout = dot,
         rankdir = TB,            
         overlap = true,
         fontsize = 10]
  

  # ノード (円)
  #################
  node [shape = circle,                  # shape = circle
       fixedsize = true
       width = 1.3]                      # circle の大きさ
  
  subgraph cluster_passive {
    Primary   [label = '第一\n施設] 
    Secondary [label = '第二\n施設'] 
    Tertiary  [label = '第三\n施設'] 
    SC        [label = 'サーベイランス\n調整',
               fontcolor = darkgreen] 
  }
  
  # ノード (ボックス)
  ###############
  node [shape = box,                     # ノードの形状
        fontname = Helvetica]            # ノード中のフォント
  
  subgraph cluster_active {
    Active [label = '能動的\nサーベイランス'] 
    HCF_active [label = 'HCF\n能動的探索']
  }
  
  subgraph cluster_EBD {
    EBS [label = 'イベントベース\nサーベイランス (EBS)'] 
    社会メディア
    ラジオ
  }
  
  subgraph cluster_CBS {
    CBS [label = '地域参加型\nサーベイランス (CBS)']
    RECOs
  }


  # エッジ
  #######
  {Primary Secondary Tertiary} -> SC [label = '症例報告']

  Primary   -> Secondary [label = '症例移送',
                          fontcolor = red]
  Secondary -> Tertiary [label = '症例移送',
                          fontcolor = red]
  
  HCF_active -> Active
  
  {社会メディア ラジオ} -> EBS
  
  RECOs -> CBS
}
")

ノードの形状

以下の例は、こちらのチュートリアル から引用したもので、適用されたノード形状と、連続するエッジ接続の略記法を示しています。

DiagrammeR::grViz("digraph {

graph [layout = dot, rankdir = LR]

# ノードのグローバル・スタイルを定義します。必要に応じてボックス内でこれらを上書きすることができます。
node [shape = rectangle, style = filled, fillcolor = Linen]

data1 [label = 'データ 1', shape = folder, fillcolor = Beige]
data2 [label = 'データ 2', shape = folder, fillcolor = Beige]
process [label =  'データ \n 処理']
statistical [label = '統計 \n 解析']
results [label= '結果']

# ノードIDを持つエッジ定義
{data1 data2}  -> process -> statistical -> results
}")

出力

出力の取り扱いと保存方法

  • 出力結果は、RStudio の Viewer ペインに表示されます。デフォルトでは右下に Files、Plots、Packages、Help と並んで表示されます。
  • 出力結果をエクスポートしたい場合は、Viewer ペインから “Save as image” (「画像として保存」)または “Copy to clipboard” (「クリップボードにコピー」)を選択してください。画像は指定したサイズに調整されます。

パラメータ化された図形

以下は、こちらのチュートリアル からの引用です。

「パラメータ化された図形::R で図形を設計することの大きな利点は、R の値をフローチャートに直接読み込んで、図形を分析に直結させることができることです。例えば、プロセスの各段階の後に値を削除するフィルタリングプロセスを作成したとすると、プロセスの各段階の後にデータセットに残っている値の数を図に表示することができます。これを実現するには、図の中で @@X 記号を直接使用し、プロットのフッターで [X]: を使用して参照します(X は一意の数値インデックス)。」

パラメータ化された図形に興味をお持ちの方は、引用元のチュートリアルをご覧ください。

35.3 沖積図・サンキー図

パッケージを読み込む

以下のコードを実行すると、図の作成に必要なパッケージが読み込まれます。このハンドブックでは、パッケージを読み込むために、pacman パッケージの p_load() を主に使用しています。p_load() は、必要に応じてパッケージをインストールし、現在の R セッションで使用するためにパッケージを読み込む関数です。また、すでにインストールされたパッケージは、R の基本パッケージである baselibrary() を使用して読み込むこともできます。R パッケージの詳細については、R の基礎 の章を参照してください。

ここでは、図を作成するために networkD3 パッケージを読み込み、データ操作のための tidyverse パッケージも読み込んでいます。

pacman::p_load(
  networkD3,
  tidyverse)

データセットからプロット

データセット内のつながりをプロットします。以下では、networkD3 パッケージを linelist という症例ラインリストで使ってみます。オンラインチュートリアルをご覧になりたい方は、こちら を参照ください。

まず、年齢区分と病院の組み合わせごとに、症例数を集計します。わかりやすくするために、年齢区分が欠落している症例は除外しました。また、hospital 列と age_cat 列の列名をそれぞれ sourcetarget に変更します。これらの列は、沖積図の 2 つの面になります。

# 病院と年齢区分で集計
links <- linelist %>% 
  drop_na(age_cat) %>% 
  select(hospital, age_cat) %>%
  count(hospital, age_cat) %>% 
  rename(source = hospital,
         target = age_cat)

データセットは次のようになります。

次に、すべてのダイアグラム・ノードのデータフレームを、name という列で作成します。name 列は、hospital 列と age_cat 列のすべての値で構成されます。name 列を作成する前に、hospital 列と age_cat 列のデータ型が文字型となっているか確認してください。また、後述のコードで作成する ID 列(IDsource 列と IDtarget 列)をラベルではなく数字型に変更します。

# ユニークなノード名
nodes <- data.frame(
  name=c(as.character(links$source), as.character(links$target)) %>% 
    unique()
  )

nodes  # 表示
##                                    name
## 1                      Central Hospital
## 2                     Military Hospital
## 3                               Missing
## 4                                 Other
## 5                         Port Hospital
## 6  St. Mark's Maternity Hospital (SMMH)
## 7                                   0-4
## 8                                   5-9
## 9                                 10-14
## 10                                15-19
## 11                                20-29
## 12                                30-49
## 13                                50-69
## 14                                  70+

次に、上述の count() で作成した links データフレームを編集します。 2 つの数値列 IDsourceIDtarget を追加します。これらはノード間のリンクを実際に反映・作成します。 これらの列にはソースノードとターゲットノードの行番号(位置)が入ります。 ポジション番号が(1 ではなく)0 から始まるように、1 を引きます。

# 名前ではなく数値にマッチング
links$IDsource <- match(links$source, nodes$name)-1 
links$IDtarget <- match(links$target, nodes$name)-1

links データセットは以下のようになります。

では、sankeyNetwork() を使用してサンキー図をプロットしてみましょう。 コンソールで ?sankeyNetwork を実行すると、関数内で使用される各引数について詳細を確認することができます。 なお、iterations = 0 を設定しないと、ノードの順番が期待通りにならないことがあります。

# plot
######
p <- sankeyNetwork(
  Links = links,
  Nodes = nodes,
  Source = "IDsource",
  Target = "IDtarget",
  Value = "n",
  NodeID = "name",
  units = "TWh",
  fontSize = 12,
  nodeWidth = 30,
  iterations = 0)        # ノードの順序をデータ順にする
p

次に、患者のアウトカムも含まれている例を示します。 なお、データ準備・前処理の段階で、年齢層と 病院、またこれとは別に病院とアウトカムの間の症例の数を計算し、bind_rows() で両者のカウントを結合しています。

# 病院と年齢区分で集計
age_hosp_links <- linelist %>% 
  drop_na(age_cat) %>% 
  select(hospital, age_cat) %>%
  count(hospital, age_cat) %>% 
  rename(source = age_cat,          # 列名の変更
         target = hospital)

hosp_out_links <- linelist %>% 
    drop_na(age_cat) %>% 
    select(hospital, outcome) %>% 
    count(hospital, outcome) %>% 
    rename(source = hospital,       # 列名の変更
           target = outcome)

# リンクを結合
links <- bind_rows(age_hosp_links, hosp_out_links)

# ユニークなノード名
nodes <- data.frame(
  name=c(as.character(links$source), as.character(links$target)) %>% 
    unique()
  )

# ID番号を生成
links$IDsource <- match(links$source, nodes$name)-1 
links$IDtarget <- match(links$target, nodes$name)-1

# プロットする
######
p <- sankeyNetwork(Links = links,
                   Nodes = nodes,
                   Source = "IDsource",
                   Target = "IDtarget",
                   Value = "n",
                   NodeID = "name",
                   units = "TWh",
                   fontSize = 12,
                   nodeWidth = 30,
                   iterations = 0)
p

https://www.displayr.com/sankey-diagrams-r/

35.4 イベントのタイムライン

特定のイベントを表示するタイムラインを作るには、vistime パッケージを使用します。

詳細は、こちらのドキュメント をご覧ください。

# パッケージの読み込み
pacman::p_load(vistime,  # タイムラインを作成
               plotly    # インタラクティブな可視化
               )

ここでは、まず関心のあるイベントが含まれているデータセットをご紹介します。

p <- vistime(data)    # vistime を適用  

library(plotly)

# step 1: リストに変換  
pp <- plotly_build(p)

# step 2: マーカーの大きさ  
for(i in 1:length(pp$x$data)){
  if(pp$x$data[[i]]$mode == "markers") pp$x$data[[i]]$marker$size <- 10
}

# step 3: テキストの大きさ  
for(i in 1:length(pp$x$data)){
  if(pp$x$data[[i]]$mode == "text") pp$x$data[[i]]$textfont$size <- 10
}


# step 4: テキストの位置  
for(i in 1:length(pp$x$data)){
  if(pp$x$data[[i]]$mode == "text") pp$x$data[[i]]$textposition <- "right"
}

# 表示  
pp

35.5 DAGs

前述のように、DiagammeR パッケージと DOT 言語を使って手動で DAG を構築することができます。

また、ggdagdaggity などのパッケージもあります。

DAG の紹介 ggdag に関するドキュメント

R で dags を使用した統計的因果推論

35.6 参考資料

本章の DOT言語に関する大部分はこちら のチュートリアルを参考にしています。

より詳細な DiagammeR に関するチュートリアルは、こちら を参照ください。

サンキー図については、こちら のウェブサイトも参考にしてください。