STEP 16 / 16

再現性研究

RStudio Projects、renv、RMarkdown / Quarto、Git──讓三年後的你也跑得動。

RStudio Projects, renv, RMarkdown / Quarto, Git — so future-you can rerun it three years later.

一、為什麼要再現性?

三年後請打開三年前的 R 腳本,重新產出同樣的圖。」這是學界與業界對研究最基本的要求。再現性失敗的常見場景:

  • 套件大版本更新後,函式參數預設值改了,結果不同。
  • 原始資料路徑寫死,換電腦就找不到。
  • 沒記錄「跑分析時用的 R 版本、套件版本」。
  • 結果是手動 Excel 加工──沒人知道哪些步驟。

本章四大武器:Project + renv + Quarto/RMarkdown + Git

"Three years from now, open this script and reproduce the same figure." Common failure modes:

  • Package major update silently changes a default argument; result drifts.
  • Hard-coded paths break on another machine.
  • R version + package versions weren't recorded.
  • Results were hand-tweaked in Excel — no audit trail.

Four weapons: Project + renv + Quarto/RMarkdown + Git.

二、RStudio Projects + here

已在 Setup 章詳述。複習要點:

Covered in Setup. Recap:

my_project/
├── my_project.Rproj      # 專案錨點,路徑自動設定
├── README.md             # 專案目的、樣本來源、執行步驟
├── renv.lock             # 套件版本鎖檔 (見下節)
├── data/
│   ├── raw/              # 唯讀
│   └── processed/
├── R/                    # 腳本依執行順序編號 01_, 02_, ...
├── results/
│   ├── tables/
│   └── figures/
└── reports/              # Quarto / RMarkdown 報告
# 在腳本中永遠用 here() 寫路徑,跨電腦零修改 / Always use here() — portable
# library(here)
# here()                                  # 偵測到專案根
# read.csv(here('data', 'raw', 'x.csv'))
# saveRDS(dds, here('results', 'dds.rds'))
# ggsave(here('results','figures','volcano.pdf'), p, width = 6, height = 5)

cat('Project + here = portable, no setwd() needed.\n')

三、renv:套件版本時光機

renv 把專案使用的所有套件版本「鎖」到 renv.lock,並在專案內維護獨立的套件 library。換電腦或多年後重跑,只要 renv::restore() 就能裝回完全一樣的版本。

renv "locks" all package versions in renv.lock and maintains a project-local library. On a new machine or years later, renv::restore() brings back the exact versions.

# install.packages('renv')

# === 第一次設定 / First-time setup ===
# 在專案內 / In project root
# renv::init()
#   建立 renv/ 資料夾、renv.lock、修改 .Rprofile
#   專案內的 install.packages() 都裝到專案 library,不污染全局

# === 日常使用 / Daily ===
# install.packages('newPkg')        # 裝到專案 library
# library(newPkg); ...              # 用
# renv::snapshot()                  # 把當前版本寫進 renv.lock 並 commit

# === 把專案搬到新電腦 / Move project ===
# git clone ; cd 
# 開 RStudio → 自動偵測 renv,提示是否還原
# renv::restore()                   # 一鍵裝回所有原始版本

# === 其他常用 ===
# renv::status()                    # 查看 lockfile 與實際 library 是否一致
# renv::update()                    # 更新套件並可選擇 snapshot
# renv::dependencies()              # 掃描程式碼用了哪些套件

# === renv.lock 範例片段 ===
# {
#   'R': { 'Version': '4.4.1' },
#   'Packages': {
#     'DESeq2': {
#       'Package': 'DESeq2',
#       'Version': '1.46.0',
#       'Source': 'Bioconductor',
#       'Hash': '...'
#     }
#   }
# }
cat('renv demo — see renv vignette for full guide.\n')
⚠️
系統相依:renv 鎖 R 套件,但鎖 R 本身、作業系統、外部 C/Fortran library。要完整再現可考慮 Docker (rocker/rstudio image)。 System deps: renv locks R packages but not R itself, OS, or external C/Fortran libraries. For full reproducibility, consider Docker (rocker/rstudio image).

四、RMarkdown 與 Quarto──「程式碼即報告」

RMarkdown (.Rmd)Quarto (.qmd)
推出年份20142022
支援語言R + 少量 Python/SQL via knitrR + Python + Julia + Observable
輸出HTML / PDF / Word / slides同上 + 書 + 網站
維護仍維護,但 Quarto 是繼任官方主推

新專案建議直接用 Quarto。Quarto 完全相容 .Rmd 語法,但更現代化、跨語言一致。

For new projects, go with Quarto. It's fully compatible with .Rmd syntax but more modern and language-agnostic.

---
title: "DEG analysis report"
author: "Charlene"
date: today
format:
  html:
    toc: true
    toc-depth: 3
    code-fold: true        # 摺疊程式碼預設
    theme: cosmo
    fig-width: 7
    fig-height: 5
execute:
  warning: false
  echo: true               # 顯示程式碼
  cache: true              # chunk 結果快取
---

# Loading data

```{r setup}
library(DESeq2); library(here)
dds <- readRDS(here("results", "dds.rds"))
```

# Volcano plot

```{r volcano, fig.cap="Volcano plot of DEGs"}
EnhancedVolcano::EnhancedVolcano(results(dds),
  lab = rownames(dds), x = 'log2FoldChange', y = 'padj')
```

# Session info

```{r session}
sessionInfo()
```
# Render in R / 在 R 中渲染
# rmarkdown::render('report.Rmd')         # RMarkdown
# quarto::quarto_render('report.qmd')      # Quarto (or hit Render in RStudio)

# 命令列 / Command line
# quarto render report.qmd
# quarto render report.qmd --to pdf

# 多輸出 / Multiple outputs
# quarto render report.qmd --to html --to pdf

# 投影片 / Slides
# format: revealjs
# format: pptx

cat('Render demo — install rmarkdown / quarto package or quarto CLI.\n')
💡
關鍵:每份報告最後務必加上 sessionInfo()──記錄 R 版本、OS、所有套件版本。論文 supplementary materials 必備! Key: always end every report with sessionInfo() — records R version, OS, and every package version. A must for paper supplementary materials.

五、Git──分析的時光機

RStudio 對 Git 有原生整合(Tools → Project Options → Git/SVN)。每次完成一階段就 commit,能精確記錄「我什麼時候改了什麼、為什麼」。

建議的 .gitignore:

RStudio integrates Git natively (Tools → Project Options → Git/SVN). Commit after every milestone — exact log of "what changed, when, why".

Recommended .gitignore:

# R 與 RStudio
.Rhistory
.RData
.Rproj.user/
.Ruserdata

# renv:要不要 commit library 看團隊──通常 commit lockfile 即可
renv/library/
renv/local/
renv/staging/
# (renv.lock 必須 commit!)

# 大型資料 (用 Git LFS 或 cloud)
data/raw/*.bam
data/raw/*.fastq.gz
*.h5
*.rds
results/figures/*.pdf

# 系統垃圾
.DS_Store
Thumbs.db

# IDE
*.swp
.vscode/
# 命令列基本 / Basic CLI
# git init
# git add R/01_load_data.R
# git commit -m '01: load raw counts and sample metadata'
# git log --oneline

# 與遠端同步 / Remote
# git remote add origin git@github.com:user/repo.git
# git push -u origin main
# git pull

# RStudio 內建:Git 分頁可勾選檔案、按 Commit、寫 message、Push
# 整合的好處:diff 直接視覺化、衝突解決有 GUI

# 建議 commit 頻率:
# - 每完成一個分析步驟 commit 一次
# - 每天結束 push 到遠端,避免硬碟壞掉一切歸零
cat('Git demo — install Git first; RStudio auto-detects.\n')

六、其他可加分的習慣

📝 README.md

每個專案都該有:1) 目的;2) 資料來源 / 引用;3) 必要套件;4) 執行步驟 (R/01_... → R/02_... 順序);5) 聯絡人。

Every project needs: 1) purpose; 2) data source / citation; 3) required packages; 4) execution order (R/01_... → R/02_...); 5) contact.

🎲 set.seed()

所有用到隨機(PCA tie、bootstrap、k-means 初始化)的程式碼,最前面寫 set.seed(42)。否則重跑結果會微小漂移。

Any code that uses randomness (PCA ties, bootstrap, k-means init) needs set.seed(42) at the top. Otherwise re-runs drift slightly.

🔄 Makefile / targets

targets 套件能自動偵測哪些步驟要重跑(類似 Snakemake / Make)。大型 pipeline 強烈建議。

The targets package auto-detects which steps need rerunning (like Snakemake / Make). Strongly recommended for big pipelines.

🐳 Docker

把 R 版本、OS、所有套件、外部工具(samtools, bowtie2)打包成 image。最終極可重現方案。rocker/tidyversebioconductor/bioconductor_docker 是好起點。

Bundle R + OS + packages + external tools (samtools, bowtie2) into an image. The ultimate reproducibility tool. Start with rocker/tidyverse or bioconductor/bioconductor_docker.

🧪 testthat 單元測試

對自訂函式寫測試,確保未來改動不會悄悄壞掉舊行為。usethis::use_testthat() 一鍵設定。

Write tests for custom functions so future edits don't silently break old behavior. usethis::use_testthat() sets it up.

📊 logging

大型 pipeline 用 loggerfutile.logger 把進度、警告、錯誤寫到檔案。除錯救命。

For long pipelines, use logger or futile.logger to write progress/warnings/errors to file. Debug-saver.

七、論文投稿前的再現性 checklist

✅ 投稿前自我檢查

1.所有路徑使用 here() 或相對路徑?
2.renv.lock 已 commit?
3.所有 random 使用 set.seed()?
4.有 README 描述執行順序?
5.主要 figure 都有對應的 R 腳本?
6.最終報告附 sessionInfo()?
7.乾淨環境重跑一次驗證?
8.原始資料有 GEO/SRA accession?
9.程式碼公開於 GitHub / Zenodo?

📝 自我檢測

1. 三年後重跑舊分析卻得不到相同結果,最可能原因?

1. Same analysis 3 years later produces different result — most likely cause?

A. R 自己壞掉A. R itself is broken
B. 電腦不同B. Different computer
C. 套件版本變了,預設參數不同;沒鎖 renv.lockC. Package versions changed (different defaults); no renv.lock pinning
D. 隨機種子不同D. Different random seed

2. RMarkdown / Quarto 的核心好處是?

2. The core benefit of RMarkdown / Quarto?

A. 比 Word 好看A. Prettier than Word
B. 文字、程式碼、結果同檔案;改一次資料、整份報告自動更新B. Text + code + output in one file; change data once, whole report regenerates
C. 比較快C. Faster
D. 不用學 MarkdownD. No need to learn Markdown

3. 想要團隊成員 git clone 後能重現你的分析環境,最關鍵的兩個檔案?

3. Two most critical files for a teammate to git-clone & reproduce your env?

A. .RData 與 .RhistoryA. .RData and .Rhistory
B. README 與 raw dataB. README and raw data
C. .Rproj 與 renv.lockC. .Rproj and renv.lock
D. 隨便哪兩個都可以D. Any two will do