📚

Rでメタアナリシス:DiagrammeRパッケージでPRISMAフローチャートを作る

2022/10/22に公開約9,700字

1. 始めに

1.1. この記事の趣旨

こんにちは、nekometaと申します。「Rでメタアナリシス」では、GNU R(以下、R)で要約データを用いたランダム化比較試験のメタアナリシスを行い、代表的な図表を作成する方法を説明します。今回はRの DiagrammeR パッケージを利用してPRISMAフローチャートを作ります。

1.2. 背景

メタアナリシスの論文では、PRISMAガイドラインにしたがい、解析に使用した臨床試験をどのように選択したのか、所定の形式を備えたフローチャート(PRISMAフローチャート)で示す必要があります。InkscapeなどのドローソフトやMicrosoft PowerPointで描画してもよいのですが、Rで出力できたら手作業を減らせて楽ですね。そこで、Rの DiagrammeR パッケージを利用します。

DiagrammeR はRからViz.jsMermaid.jsといったJavaScriptのグラフ生成ライブラリを使用することで、テキストからグラフを生成できるパッケージです。今回はViz.jsを使用してフローチャートを生成します。

PRISMAフローチャートを生成することに特化したパッケージとして PRISMA2020があります。このパッケージは適切に整形したデータフレームを渡すとPRISMAフローチャートを生成することができ、対話的UIでPRISMAフローチャートを生成することもできます。ただ、個人的にはデータフレームを用意するよりDOT言語で構造を記述するほうが性に合うことと、他の作図にも応用できそうなパッケージを使いたいことから、DiagrammeR を紹介します。

1.3. 目標

The PRISMA 2020 statementのフローチャートと同レベルのものをRで出力します。

This is an example of PRISMA 2020 flowchart.

1.4. 環境

  • OS:Windows 10 Pro(バージョン:21H2、OSビルド19044.1645)
    • WSL2-Ubuntu 20.04.4 TLS
  • Docker Desktop 4.7.1(WSL2バックエンド)
    • Docker Engine version 20.10.14
    • Docker Compose version v2.4.1
      • rocker/rstudio:4.2.0
      • installed R packages: checkpoint, pacman, rmarkdown
      • checkpoint date: 2022-04-21

この記事で使用した解析プロジェクトの構成は以下の通りです。Quartoを使用することを想定して説明しますが、R Scriptで書く場合もコードの中身は同じです。

【解析プロジェクトのテンプレート】

【主要な設定ファイルの説明】

2. 解析コード

2.1. セットアップチャンク

2.1.1. コードの例

analysis.qmdというファイルに解析コードを記載します。冒頭に記載する一連のコード(通称:セットアップチャンク)の例を示します。

analysis.qmd
```{r}
#| label: setup
#| code-fold: false

# set working directory
setwd("~/project/codes")

# attach the package 'checkpoint'
library(checkpoint)

# set checkpoint date
checkpoint("2022-04-21", checkpoint_location = "../", scan_now = FALSE)

# attach packages
library(pacman)
p_load(DiagrammeR, DiagrammeRsvg)
``` 

2.1.2. コードの説明

# set working directory
setwd("~/project/codes")

analysis.qmdを配置した ~/project/codes ディレクトリをワーキングディレクトリに設定しています。

# attach the package 'checkpoint'
library(checkpoint)

# set checkpoint date
checkpoint("2022-04-21", checkpoint_location = "../", scan_now = FALSE)

checkpointパッケージをアタッチしてチェックポイント日を指定し、以後呼び出すパッケージのバージョンを固定します。

# attach packages
library(pacman)
p_load(DiagrammeR, DiagrammeRsvg)

pacman パッケージをアタッチし、p_load() で解析に使用するパッケージを読み込みます(パッケージがインストールされていない場合は、インストールされます)。

  • DiagrammeR :RでViz.js(≒Graphviz)を使用します。
  • DiagrammeRsvg :生成したフローチャートをsvg形式に変換します。

2.2. フローチャートの出力と保存

2.2.1. コードの例

R側のコードはたったこれだけです。

analysis.qmd
```{r}
#| label: flowchart
#| fig-cap: "Figure 1. Flowchart of study selection."

# create a PRISMA flowchart on Graphviz
fc <- grViz(diagram = "flowchart.gv", width = 795)
fc

# save the flowchart as a SVG file
export_svg(fc) |> 
  writeLines("figures/analysis_files/figure-html/flowchart.svg")
``` 

2.2.2. コードの説明

#| label: flowchart
#| fig-cap: "Figure 1. Flowchart of study selection."

Quartoのチャンクオプション fig-cap に、図のタイトルを記述します。既定値ではタイトルの位置は図の下ですが、QuartoのYAMLヘッダーで fig-cap-location の値を指定することで変更可能です。

# create a PRISMA flowchart on Graphviz
fc <- grViz(diagram = "flowchart.gv", width = 795)
fc

flowchart.gvというファイルにDOT言語でグラフ構造を記述しておき、DiagrammeR パッケージの grViz() 関数で読み込みます。width はQuartoで出力するHTMLレポートのメインカラムの幅に合わせて適当に設定しています(出力されるグラフ自体には影響しません)。

# save the flowchart as a SVG file
export_svg(fc) |> 
  writeLines("figures/analysis_files/figure-html/flowchart.svg") 

DiagrammeRsvg パッケージの export_svg() 関数で文字列として出力し、baseパッケージの writeLines() 関数でSVGファイルとして書きだします。なお、書きだした flowchart.svg はInkscapeなどのドローソフトで編集できます。

出力結果

出力したフローチャートを示しました。シンプルに作りましたが、必要な要素はすべて含まれています。

This is a rendered flowchart.

2.3. DOT言語によるグラフ構造の記述

2.3.1. グラフ構造の記述例

flowchart.gvというファイルにグラフ構造を記述します。グラフ構造の記述例を示します(長くなるので、記述例のコードは折りたたみます)。

flowchart.gv
flowchart.gv
digraph PRISMA_flowchart {
  
  # general Settings
  graph [
    newrank = true, 
    ranksep = "0.4", 
    nodesep = "0.4", 
    splines = ortho]; 

  node [
    shape = rectangle, 
    style = "filled, rounded", 
    fillcolor = gray95, 
    fontsize = 10, 
    width = 2.2];
    
  edge [
    penwidth = 2, 
    arrowsize = 0.7];
  
  # node relationships between clusters
  {rank = same; c1_1; c2_1; c3_1; c4_1};
  {rank = same; c2_2; c3_2};
  {rank = same; c2_3; c3_3; c4_3; c5_3};
  {rank = same; c2_4; c3_4; c4_4; c5_4};
  {rank = same; c2_5};
  
  # edges between clusters
  c1_1 -> res;
  c2_5 -> res;
  c2_1 -> c3_1;
  c2_2 -> c3_2;
  c2_3 -> c3_3;
  c2_4 -> c3_4;
  c4_3 -> c5_3;
  c4_4 -> c2_5;
  c4_4 -> c5_4;
  
  res [
    width = 3, 
    style = "filled, rounded", 
    fillcolor = white, 
    fontname="times-bold", 
    label = "Total studies included in review (n = )\l\lReports of total included studies (n = )\l" ]; 
  
  subgraph cluster_A {
    label = "Previous studies"; 
    fontsize = 12;
    
    c1_1 [
      height = 1.2, 
      label = "Studies included in previous \lversion of review (n = )\l\lReports of studies included in\lprevious version of review (n = )\l"]
      }
  
  subgraph cluster_B {
    label = "Identification of new studies via databases and registers"; 
    fontsize = 12;
    
    subgraph column_3 { 
      label= "";
      
      c3_1 [
        height = 1.2, 
        label = "Records removed before screening:\l  - Records marked as ineligible\l     by automation tools (n = )\l\l  - Records removed for\l     other reasons(n = )\l"];
      c3_2 [label = "Records excluded\l(n = )\l"]; 
      c3_3 [label = "Reports not retrieved\l(n = )\l"]; 
      c3_4 [label = "Reports excluded:\l  - Reason 1 (n = )\l  - Reason 2 (n = )\l"];
      } 
    
    subgraph column_2 { 
      label = "";
      
      c2_1 [label = "Records identified from:\l  - Databases (n = )\l  - Registers (n = )\l"]
      c2_2 [label = "Records screened\l(n = )\l"]; 
      c2_3 [label = "Reports sought for retrieval\l(n = )\l"]; 
      c2_4 [label = "Reports assessed for eligibility\l(n = )\l"]; 
      c2_5 [label = "New studies included in review\l(n = )\l\lReports of new included studies\l(n = )\l"];
      
      # edges
      c2_1 -> c2_2 -> c2_3 -> c2_4 -> c2_5
      }
  }
  
  subgraph cluster_C { 
    label = "Identification of new studies via other methods"; 
    fontsize = 12;
    
    subgraph column_5 {
      label = "";

      c5_3 [label = "Reports not retrieved\l(n = )\l"]; 
      c5_4 [label = "Reports excluded:\l  - Reason 1 (n = )\l  - Reason 2 (n = )\l"]
      }
    
    subgraph column_4 {
      label = "";
      
      c4_1 [
        height = 1.2, 
        label = "Records identified from:\l  - Websites (n = )\l  - Organisations (n = )\l  - Citation searching (n = )\l"];
      c4_3 [label = "Reports sought for retrieval\l(n = )\l"];
      c4_4 [label = "Reports assessed for eligibility\l(n = )\l"];
    
      # edges
      c4_1 -> c4_3 -> c4_4
      }
    }
  }
}

2.3.2. 記述の説明

DOT言語自体はかなりシンプルに書けるので、先人の記事(日本語)を一読のうえで、必要に応じて公式ドキュメントを検索すれば良いものと思います。

Graphvizは、データドリブンなグラフ生成が本分なので、縦横を揃えたフローチャートを生成するためには、ある程度レイアウトをコントロールする必要があります。ここでは、レイアウト上悩みがちなポイント、もとい、私が悩んだポイントを説明します。

縦方向のレイアウトを制限する

  • ノードの幅を統一する(例:node [width = 2.2]
  • 列単位でノードをまとめる(例:subgraph cluster{} または subgraph{}
  • 各列の最初のノードの高さを揃える(例:c1_1 [height = 1.2]
  • subgraph をネストする場合は記載順に気を付ける

横方向のレイアウトを制限する

  • 行単位でノードをまとめる(例:{rank = same; c1_1; c2_1; c3_1; c4_1}
  • 異なる subgraph のノード間で rank を設定するときは graph [newrank = true] とする

エッジをコントロールする

  • エッジを直線にする:(例:graph [splines = ortho]

ノードのlabel(文字)の体裁を調整する

  • \n で中央寄せ改行、\l で左寄せ改行、\r で右寄せ改行できる
  • HTML様のタグを使用することができる

まとめ

今回は DiagrammeR パッケージを使用して、The PRISMA 2020 statementのフローチャートと同レベルのものをRで出力しました。これで面倒な描画作業とおさらばです。

Discussion

ログインするとコメントできます