🔁

Docker + R + checkpointで再現可能な解析プロジェクトを作る

2022/10/22に公開

1. はじめに

1.1. 統計解析の再現性

近年、科学の広い分野で研究結果の再現性を確保する取り組みが進められています。再現性を確保するための要件はさまざまな角度から議論されており、大雑把に言えば以下の2つが重要です。

  • 解析前に、根拠に基づいて研究計画書(プロトコル)を作成し、公開する
  • 解析後に、第三者が追試できるような状態を整え、提供する

解析前の段階では、多くの分野でプロトコルを作成し公開することが強く推奨されており、すでに主要なジャーナルはプロトコルの登録番号がないと原稿を受理しません。たとえば、メタアナリシスではいくつかのガイドラインを遵守したうえで、審査を受けたプロトコルをPROSPEROで事前に公開することが、査読に進むための最低条件です。[1]

一方、解析後の段階については状況はより混沌としており、具体的な手段に関する合意は形成されていません。2022年4月現在、いくつかのジャーナルがデータセットと解析コードの公開を原稿受理の条件としたり、専門のレビュワーがデータとコードセットから研究結果を再現できるか確認したりしていますが、こうした取り組みはまだ稀です。とはいえ、今のうちから再現性を確保できるような解析方法を仕組みとして実装しておくことは必要でしょうし、研究の品質向上にも役立つと思います。[2]

1.2. 実装

1.2.1. Rのバージョンを管理し解析環境をコンテナー化する

解析環境をどこまで再現できるようしておくかは悩むところですが、[3] オペレーティングシステム(OS)や統合解析環境(IDE)も解析結果に影響を与えることがありますし、追試者がそこまで統一するのはなかなか困難です。そこでコンテナー技術を活用して、これらも簡単に再現できるようにします。今回はDockerを採用し、Rocker Project が作成している rocker/rstudio イメージで、OS、IDEおよびRのバージョンを管理します。

1.2.2. Rのパッケージ群を管理する

CRAN Task View: Reproducible Researchに、再現性の確保に役立つパッケージが紹介されており、"Package Reproducibility"の項でさまざまなRのパッケージ管理方法がまとめられています。その中から、今回は論文の執筆に向いた便利な特徴を備えている checkpoint を選びました。

  • CRANに存在するパッケージの特定日のスナップショットインストールすることができる
    • 全パッケージのリストを確認しなくても、スナップショット日と、Rのバージョンと主要な解析パッケージ名というわずかな情報から、解析に使用したソフトウェア群を特定できる
    • 簡単に複数のスナップショット日を切り替えることができるので、解析パッケージ群の変更によって解析結果が変化したかどうかを検証するのが楽
  • 使い方がシンプル
    • 解析コードの冒頭に1つの関数を記述しておけば解析パッケージの固定も復元も可能
    • checkpointMicrosoft R Archived Networkからパッケージだけではなく、スナップショット日のドキュメントも保存してくれるので、過去のドキュメントを探しに行かなくてもいい
  • 動作が安定している
    • checkpoint のバージョン1.0以降はそれ以前のバージョンとは別物で、パッケージのキャッシュが利用でき、リストアが早い

パッケージの初回ビルドはコンパイルが必要なので時間がかかりますが、2回目以降はキャッシュを読み込むだけなので高速です。初回ビルド中はコーヒーブレイクということで。

1.2.3. 解析データと解析レポートを統合する

先に挙げた "CRAN Task View" の "Literate Programming(文芸的プログラミング)"の項にあるように、解析コード、解析結果および解釈をひとつのドキュメントにまとめて直接解析レポートを生成すると改ざんの余地がなく、研究の作業フローもシンプルになります。そこで、この解析プロジェクトのテンプレートではQuartoまたはR Markdownを使用することを前提にしました。

QuartoとR Markdownは、以下のような特徴を持つドキュメント生成システムです。

  • Markdown記法を採用している
  • ひとつのファイル(.qmd または .Rmd ファイル)にRやPythonなどのコードと通常の文章を同時に書くことができる
  • HTML、PDF、ワードファイルなど、さまざまな形式のドキュメントを出力できる
  • 出力結果にコードの実行結果を含めることができる

QuartoとR Markdownとの使用感はほぼ同じで、Rユーザーにとっては「Quarto = 次世代版R Markdown」という認識で良いと思います。Quartoはまだ開発版ですが、仕様の変化が減ってきている(気がする)ので、この記事ではQuartoの設定例を紹介します。

1.2.4. データセットと解析コードを公開する

追試者や、将来の研究者の利便性を考えると、データセットと解析コードをリポジトリに公開してデジタルオブジェクト識別子(DOI)を付与し、論文で引用するのがスムーズだと思います。今回はGit/GitHubを使用し、Zenodoと連携することを想定しています。

  • Git/GitHubでリポジトリを作成し、データセット、解析コードおよび解析レポートを公開
  • リポジトリをZenodoでアーカイブし、DOIを付与 + ORCiDに紐づけ
  • DOIを論文で引用

リポジトリができ上がった後の話なのでこの記事では詳しく書きませんが、GitHubとORCiD/Zenodoは簡単に連携できます。下の画像は、Zenodoの操作説明ですが、このぐらいシンプルです。

Zenodo and GitHub can be linked through a simple procedure. The explanation screen is also simple.

1.3. やりたいこと

以下のように解析プロジェクトを構成します。

  • DockerでRStudio Serverを動かす
    • rocker/rstudio イメージを使用する
    • MakeとDocker Composeで、簡単に起動・終了できるようにする
  • checkpoint でプロジェクト固有のパッケージライブラリを作成・管理する
    • Rパッケージのキャッシュはホストからマウントする
  • QuartoまたはR Markdownが使用できるようにする
  • Git/GitHubでソースコードとデータセットをバージョン管理する

1.4. テストした環境

  • OS:Windows 10 Pro(バージョン:21H2、OSビルド19044.1586)
    • WSL2-Ubuntu 20.04.4 TLS
      • gitとmakeが使用可能で、GitHubに接続可能
  • Docker Desktop 4.6.0(WSL2バックエンド)
    • Docker version 20.10.13, build a224086
    • Docker Compose version v2.3.3

2. 成果物

2.1. 解析プロジェクトのテンプレートリポジトリ

今回作ったテンプレートリポジトリをGitHubで公開しました。踏み台として使っていただけたら幸いです。

2.2. リポジトリの構成

主な設定ファイルは以下の通りです。

.
├── .Rprofile          【R:セッション開始時に実行するスクリプト】
├── .checkpoint/       【R:checkpointによるパッケージキャッシュ】
├── .env.example       【Docker Compose:環境変数の設定テンプレート】
├── .git/
├── .gitignore
├── LICENSE
├── Makefile           【Make:起動コマンド短縮】
├── README.md
├── codes/
│   ├── .gitignore
│   ├── README.md
│   ├── analysis.qmd   【Quarto:解析コードと結果の解釈】
│   ├── analysis_cache/
│   ├── analysis_files/
│   ├── datasets/      【Quarto:データセット】
│   ├── figures/       【Quarto:プロットの画像データ(.svg)】
│   └── templates/     【Quarto:引用文献の書誌要素とスタイル定義】
├── compose.yml        【Docker Compose:設定ファイル】
├── dev/
│   ├── docker/
│   │   ├── .Renviron
│   │   ├── .ssh/
│   │   ├── Dockerfile 【Docker:設定ファイル】
│   │   └── startup.sh*
│   └── dotfiles/     【RStudio:設定ファイル】
└── research.Rproj     【R:Rプロジェクトファイル】

3. 主な設定ファイル

3.1. Makefile

DockerとDocker composeのコマンド群を短縮します。ひとまず最低限の内容としましたが、シェルスクリプトを書いて色々仕込むこともできそうです。

Makefile
.DEFAULT_GOAL := help

.PHONY: help
help: ## show a command list ## N/A
 @echo "Command list:"
 @printf "\033[36m%-9s\033[0m %-48s %s\n" "[Target]" "[Description]" "[Product]"
 @grep -E '^[/a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | perl -pe 's%^([/a-zA-Z_-]+):.*?(##)%$$1 $$2%' | awk -F " *?## *?" '{printf "\033[36m%-9s\033[0m %-48s %s\n", $$1, $$2, $$3}'

.PHONY: up
up: ## build and run RStudio Server in the background ## docker compose up -d --build
 @docker compose up -d --build
 @echo "💡 Access: http://localhost:8787/"

.PHONY: down
down: ## stop and remove running containers and networks ## docker compose down
 @docker compose down

.PHONY: stop
stop: ## stop running containers without deleting it ## docker compose stop
 @docker compose stop

.PHONY: start
start: ## (re-)start stopped containers ## docker compose start
 @docker compose start

.PHONY: ps
ps: ## list containers for the compose-project ## docker compose ps
 @docker compose ps

.PHONY: prune
prune: ## delete unused Docker objects ## docker system prune -f
 @docker system prune -f

.PHONY: bash
bash: ## start bash in the rstudio container ## docker compose exec rstudio /bin/bash
 @docker compose exec rstudio /bin/bash

この研究プロジェクトを配置したディレクトリ(Makefileのある場所)で make up コマンドを実行すると、バックグラウンドでRStudio Serverを起動します。ブラウザからRStudio Serverにアクセスすれば、解析が開始できます。同様に、make down で終了、make でコマンドのヘルプを閲覧できます。

【起動】

bash
make up

【実行結果(抜粋)】

[+] Building 0.3s (12/12) FINISHED
[+] Running 1/0
 ⠿ Container r-project-rstudio-1  Running        0.0s
💡 Access: http://localhost:8787/

【終了】

bash
make down

【実行結果】

[+] Running 2/2
 ⠿ Container r-project-rstudio-1  Removed       11.8s
 ⠿ Network r-project_default      Removed        0.7s

【ヘルプを開く】

bash
make

【実行結果】

Command list:
[Target]  [Description]                                    [Product]
help      show a command list                              N/A
up        build and run RStudio Server in the background   docker compose up -d --build
down      stop and remove running containers and networks  docker compose down
stop      stop running containers without deleting it      docker compose stop
start     (re-)start stopped containers                    docker compose start
ps        list containers for the compose-project          docker compose ps
prune     delete unused Docker objects                     docker system prune -f
bash      start bash in the rstudio container              docker compose exec rstudio /bin/bash

3.2. .env.example

環境変数を設定する .env ファイルのテンプレートです。.env.example をコピーして .env ファイルをつくり環境変数を設定すると、その値はCompose fileに記載された既定値より優先されます。.env.gitignore でGitの管理から外されているので、自分のマシン環境に依存した設定内容はリポジトリにアップロードされません。ここではコンテナー内のタイムゾーン、RStudioの一連の設定ファイル(dotfiles)のディレクトリ、コンテナー内のGitからGitHubへ接続するためのSSHキーのパスを指定できます。

.env.example
# Time zone of the Docker host machine
# Default value is "UTC"
LOCAL_TZ="UTC"

# The path to your universal dotfiles directory 
# on the Docker host machine.
# Default value is "./dev/dotfiles"
DOTFILES_ROOT="./dev/dotfiles"

# The path to your SSH private key file to connect GitHub.
# Default value is "/dev/null"
GITHUB_SSH_IDENTITY="/dev/null"

3.2.1. .envの設定例

3.2.1.1. LOCAL_TZ の設定

rocker/rstudio:4.1.3 のコンテナー内OSはDebian "bullseye" で、時刻系が協定世界時(UTC)になっています。Debian系のディストリビューションの場合は、環境変数 TZAsia/Tokyo とすることで、日本標準時(JST)に切り替えることができます。.envで LOCAL_TZ の値を設定し、Compose fileでこの値を環境変数 TZ に渡します。

  • .envの LOCAL_TZAsia/Tokyo と記載する

3.2.1.2. DOTFILES_ROOT の設定

デフォルトでは、RStudio Serverの設定ファイルは各プロジェクトに固有となっています。以下の手順で、他の端末・研究プロジェクトでも設定を統一できます。

  • GitHubで研究用Rプロジェクトの ./dev/dotfiles と同じファイルを含む UserName/dotfiles_rstudio リポジトリを作成する
    • .gitignoreは空ファイルにする
  • UserName/dotfiles_rstudio リポジトリをWSL2-Ubuntuの ~/res ディレクトリにクローンする
  • .envの DOTFILES_ROOT~/res/dotfiles_rstudio と記載する

3.2.1.3. GITHUB_SSH_IDENTITY の設定

コンテナー内からGitHubに接続できるように設定します。前回の記事では、SSH keyは生成しませんでしたので、まずSSH keyを生成します。なお、既存のSSH keyがある場合は上書きされてしまうので注意が必要です。-C オプションにはGitHubに登録したメールアドレスを入力します。

bash
cd
ssh-keygen -t ed25519 -C "youremail@domain.com"

~/.ssh ディレクトリが作成され、公開鍵と秘密鍵のペアが生成されるので、.env ファイルの GITHUB_SSH_IDENTITY にSSH keyのパスを記載した後、ターミナルソフトで公開鍵を表示・コピペし、公式ドキュメントにしたがってGitHubに登録します。

bash
cat ~/.ssh/id_ed25519.pub

3.2.1.4. コンテナー内からGitHubへ接続

RStudio ServerのTerminalペイン(Consoleではない)で以下のコマンドを実行します。これで、コンテナー内のRStudio ServerからGitHubに接続できます。

Terminal
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"

ここで設定した内容は ~/res/dotfiles_rstudio/.gitconfig に記録されます。

3.3. Compose file

解析プロジェクト、RStudioの設定ファイルおよびGitHub接続用のSSHキーを、ホストからコンテナーにバインドマウントします。SSHキーはDocker側からの意図せぬ書き換えを防止するため、読み取り専用です。

compose.yml
services:
  rstudio:
    build: dev/docker
    ports:
      - "8787:8787"
    environment:
      - TZ=${LOCAL_TZ:-UTC}
      - PASSWORD=password
      - DISABLE_AUTH=true
    volumes:
      - type: bind
        source: "."
        target: "/home/rstudio/project"
      - type: bind
        source: "${DOTFILES_ROOT:-./dev/dotfiles}"
        target: "/home/rstudio/dotfiles"
      - type: bind
        source: "${GITHUB_SSH_IDENTITY:-/dev/null}"
        target: "/home/rstudio/.ssh/id_github"
        read_only: true

3.4. Dockerfile

ベースとなるイメージは rocker/rstudio:4.1.3 です。もし、ggplot2 などで日本語フォントを使用する場合は必要なaptパッケージ(fonts-ipaexfont など)をインストールします。Rのユーザーライブラリには checkpointpacman および rmarkdown をインストールします。

Dockerfile
FROM rocker/rstudio:4.1.3

# Install apt packages
RUN apt-get update \
  && apt-get install -y --no-install-recommends \
     ssh \
     libxt-dev \
     libxml2-dev \
     libgit2-dev \
     libfontconfig1-dev \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

# Install R packages
RUN install2.r -e -s -n -1 \
     checkpoint \
     pacman \
     rmarkdown \
  && rm -rf /tmp/downloaded_packages/ /tmp/*.rds

# Add github.com to known_hosts in SSH
RUN mkdir -p /home/rstudio/.ssh \
  && ssh-keyscan -t rsa, ed25519 github.com > /home/rstudio/.ssh/known_hosts \
  && chown -R rstudio:rstudio /home/rstudio \
  && chmod 700 /home/rstudio/.ssh

# Copy the configuration files
COPY --chown=rstudio:rstudio .Renviron /home/rstudio/.Renviron
COPY --chown=rstudio:rstudio .ssh/config /home/rstudio/.ssh/config

# Copy the startup script
COPY startup.sh /startup

# Change the startup command
CMD [ "/startup" ]

3.5. .Rprofile

各種エラーメッセージを有効化しておきます。ユーザーライブラリにインストールした checkpointpacman および rmarkdown はQuartoで呼び出すので、library() でアタッチしておく必要はありません。

  • warn = 1 警告メッセージが発生したら、すべて表示する
  • warnPartialMatchArgs = TRUE 部分一致の使用警告(引数)を有効にする
  • warnPartialMatchAttr = TRUE 部分一致の使用警告(属性抽出)を有効にする
  • warnPartialMatchDollar = TRUE 部分一致の使用警告(リスト要素)を有効にする
  • showWarnCalls = TRUE 警告メッセージの呼び出し履歴の概要を表示する
  • showErrorCalls = TRUE エラーメッセージの呼び出し履歴の概要を表示する
.Rprofile
options(
  warn = 1, 
  warnPartialMatchArgs = TRUE, 
  warnPartialMatchAttr = TRUE, 
  warnPartialMatchDollar = TRUE, 
  showWarnCalls = TRUE, 
  showErrorCalls = TRUE
)

3.6. Quarto

3.6.1. YAML front matter

QuartoやR Markdownでは、YAML front matter(YAMLヘッダー)という形式で、コード処理やレンダリング処理に必要なメタ情報を、knitrrmarkdown およびPandocに渡すことができます。設定の詳細はQuartoの公式ドキュメントを見ていただくのが一番良いと思います。どんな設定でもいいと思いますが、以下の項目は比較的オススメです。

項目 説明
self-contained: true htmlファイルに画像データなどを埋め込みます。解析レポートの共有に便利です。
fig-format: svg, extract-media: "./figures" プロットの画像データをsvg形式でエクスポートします。
bibliography 引用文献の書誌要素を記述した.bibファイルを参照します。
csl 引用文献のスタイル定義を記述した.cslファイルを参照します。
Quarto
---
title: "Analysis report"
subtitle: " `r Sys.time() ` "
author: "Your Name"
format: 
  html: 
    toc: true
    number-sections: true
    theme: simplex
    backgroundcolor: "#e3e3e3"
    monobackgroundcolor: "#ffffff"
    highlight-style: solarized
    code-block-bg: "#dcd6d2"
    mainfont: Times New Roman, YuMincho, Hiragino Mincho ProN, Yu Mincho,  
              MS PMincho, serif;
    linestretch: "2.5"
    self-contained: true
    code-tools: true
    code-fold: true
    link-external-icon: true
    link-external-newwindow: true
    fig-format: svg
    fig-width: 10
extract-media: "./figures"
bibliography: "./templates/0000_r-project.bib"
csl: "./templates/sage-vancouver.csl"
---

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

checkpoint パッケージをアタッチしてチェックポイント日を指定した後、必要なパッケージを library()p_load() といった関数で読み込むと、指定した日付のスナップショットが読み込まれます。

Quarto
```{r}
#| label: setup
#| code-fold: false

# attach the package 'checkpoint'
library(checkpoint)

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

# attach packages
library(pacman)
p_load(kableExtra, tidyverse)

# save default graphics parameters
oldpar <- par(no.readonly = TRUE) 

# load data sets
df_01 <- read.csv("datasets/01_bcg.csv", na.strings = c("", "NULL"))

``` 

4. 解析レポートの出力例

4.1. Title

解析レポートをレンダリングした日時が記載されます。引用文献はZoteroの「クイック・コピー」機能を使用すれば、Zotero上のレコードをRStudio Serverのエディターにコピーアンドペーストするだけで、Markdown書式で引用できます。
This is a screenshot of the title section of the analysis report. It will include the date and time the report was rendered.

4.2. Table

KableExtra パッケージを使用した出力例です。このまま、Microsoft Wordに書式なしの表として貼り付け、編集できます。
Here is an example of table output.

4.3. Figure

ここでは、タブセットを用いて、データセット、解析結果、複数のFigureを切り替えられるようにしています。Figureをコピーするとsvg形式で保存できます。また、Figureは所定のフォルダーにsvg形式で出力されます。
Here is an example of the output of the figure.

だいたい、こんな感じです。工夫次第で色々できると思います。

脚注
  1. プロトコルを公開すること自体は解析計画の妥当性を保証しませんが、恣意的な解析手法の変更を防ぐうえで有用です。 ↩︎

  2. データセットやソースコードを公開すること自体は解析結果の妥当性を保証しませんが、解析結果を検証・批判可能な状態にし、将来の累積研究のハードルを下げるうえで有用です。 ↩︎

  3. 少し昔の論文ですが、計算機科学の再現性について述べた2011年の論文の、Fig.1が大変わかりやすいです。少なくとも、論文を出しただけでは不十分ということですね。Science. 2011 Dec 2;334(6060):1226-7. ↩︎

Discussion