用于可重复科学分析的最佳 R 工具
用于可重复科学分析的 R 依赖于四个层:项目结构、版本控制环境、文学化文档格式和依赖管理器。R 生态系统为最后两层提供了至少六个成熟的选项 —— renv、targets、Quarto、R Markdown、带有 rocker 镜像的 Docker 和 workflowr —— 而 Software Carpentry 的 R for Reproducible Scientific Analysis 课程自 2014 年以来一直在教授这一技术栈。
关键要点
- R 中用于可重复科学分析的可重复性是一个技术栈,而非单个工具:项目布局 + 依赖项固定 + 文学化报告 + 工作流自动化。
renv(CRAN,2020 年首次发布)是每个项目包库的事实标准;它在renv.lock中记录确切的版本。targets使用依赖图替换临时的source()脚本,该依赖图会跳过已更新的步骤并缓存结果。- Quarto(Posit,2022)在新项目中已在很大程度上取代了 R Markdown,但 R Markdown 仍得到充分支持并在教学中被广泛使用。
- 容器(rocker/verse、Bioconductor 镜像)固定了
renv无法触及的系统层:R 本身、系统库和编译的依赖项。 - Software Carpentry 课程仍然是最好的免费入门途径;它在为期两天的课程中教授项目布局、
knitr和ggplot2。
R 中“可重复”实际需要什么
R 中的可重复性意味着第二个人在稍后的日期,在第二台机器上可以重新运行您的分析并获得相同的数字和图形。四种故障模式会破坏这一保证,每种模式都对应于一类特定的工具。
环境漂移 (Environment drift)。 您的脚本调用 library(dplyr) 并且今天可以运行;六个月后,CRAN 更新更改了一个默认参数,您的流水线会默默地产生不同的输出。修复方法是固定每个项目的包版本,renv 通过编写锁定文件来实现这一点。
隐藏状态 (Hidden state)。 您的脚本假定了一个工作目录、一个已加载的 .RData 文件,或您一小时前在交互界面定义的变量。修复方法是采用面向项目的工作流,其中工作目录始终是项目根目录,且没有会话状态泄漏。
手动步骤 (Manual steps)。 您运行脚本 1,然后运行脚本 2,然后手动编辑 CSV,最后运行脚本 3。修复方法是使用工作流工具,它对依赖图进行编码并仅重新运行发生更改的部分。
系统层漂移 (System layer drift)。 您的分析取决于特定的 BLAS 实现、编译的 C++ 库或 Bioconductor 版本。补救方案是使用容器镜像,将操作系统、R 二进制文件和系统库一起冻结。
Related: — Project-based data-science paths with a guided terminal and real datasets.
大多数已发表的“可重复”R 分析仅关注前两个方面。以下工具根据它们所覆盖的剩余领域进行分类。
比较:六种工具,按范围排名
| 工具 | 固定层 | 学习曲线 | 最适合 | 弱点 |
|---|---|---|---|---|
renv | R 包版本 | 低 | 每个项目,始终使用 | 无法固定 R 本身或系统库 |
targets | 流水线步骤 + 缓存输出 | 中等 | 多阶段分析,运行时间长 | 对单脚本工作来说过于复杂 |
| Quarto | 文档 + 代码 + 输出 | 低-中 | 从单一源生成论文、报告、幻灯片 | 不是依赖管理器 |
| R Markdown | 文档 + 代码 + 输出 | 低 | 教学、遗留项目 | 新工作被 Quarto 取代 |
| Docker + rocker | 操作系统、R 二进制文件、系统库 | 高 | HPC、归档、跨机构共享 | 较重;需要容器素养 |
| workflowr | 项目 + 版本化网站 | 中等 | 以网站形式发布的实验室笔记本 | 布局过于主观;社区较小 |
对于使用 R 进行可重复科学分析的计算物理或生物信息学小组的实用建议是:在每个项目中使用 renv 和 Quarto;一旦流水线超过大约三个依赖阶段,就加入 targets;仅在需要将分析移交给机构外部人员或为论文存档时才进行容器化。
renv:固定包层
renv 是 Packrat 的继承者,由 Kevin Ushey 开发并由 Posit 维护。它在 renv/library/ 下创建一个项目本地库,并在纯文本 renv.lock 文件中记录每个包的版本。协作者克隆您的存储库,运行 renv::restore(),即可获得字节级相同的包版本 —— 包括 Bioconductor 包,renv 会根据锁定文件中记录的 Bioconductor 版本进行解析。
Worth a look: — One subscription for university-backed Python and data-science certificates.
在实践中,有两个细节很重要。首先,renv::snapshot() 仅记录它可以检测到正在使用的包;如果您在函数中通过 require() 动态加载包,或者对仅在字符串中引用的包使用 library(),请使用 renv::snapshot(type = "all") 显式添加,或将其列在项目的 DESCRIPTION 文件中。其次,renv 本身并不固定 R 版本 —— 它会保存您使用的 R 版本,并且 renv::restore() 在版本不匹配时会发出警告,但它不会为您安装该 R 版本。为此,您需要容器层来确保 R 的可重复科学分析。
因此,一个最小的可重复项目如下所示:
my-analysis/
├── renv.lock
├── renv/
├── R/
│ ├── 01-load.R
│ └── 02-model.R
├── data/
├── report.qmd
└── README.md
README.md 应说明 R 版本、恢复依赖项的命令以及构建报告的命令。三行文档可以防止大多数“在我的机器上运行不了”的电子邮件。
targets:对依赖图进行编码
由 Will Landau 开发的 targets 将流水线转换为命名目标的有向无环图 (DAG)。每个目标都是一个带有声明输入的函数调用;targets 对输入进行哈希处理,并在下次运行时忽略任何输入和代码未更改的目标。对于一个需要 20 分钟分析轨迹的分子动力学后处理流水线来说,这就是迭代时间从半小时缩短到几秒钟的区别。
思维转变是从“按顺序运行的脚本”转变为“声明其所需内容的函数”。_targets.R 文件定义了该图:
library(targets)
list(
tar_target(raw_files, list.files("data", full.names = TRUE)),
tar_target(parsed, parse_trajectory(raw_files), pattern = map(raw_files)),
tar_target(summary_stats, summarize(parsed)),
tar_target(report, render_report(summary_stats), format = "file")
)
pattern = map(...) 结构是“动态分支”功能:它为每个输入文件创建一个子目标,并使用 tar_make_future() 或 tar_make_clustermq() 将其并行化。在 HPC 集群上,这自然对应于一个任务表。
注意事项:targets 缓存非常激进,依赖于随机种子或墙钟时间 (wall clock time) 的目标将不断失效,除非您在目标内部定义种子并将时间作为显式参数传递。将非确定性视为图中的 Bug,而非琐事。
Quarto 和 R Markdown:文学化报告
Quarto 是 Posit 的下一代发布系统,于 2022 年发布,它可以在同一文档中执行 R、Python、Julia 和 Observable 代码。对于以 R 为中心的团队,它比 R Markdown 具有三个具体优势:单一的 .qmd 格式可渲染为 PDF、HTML、Word 和幻灯片,无需针对特定格式进行 YAML 复杂操作;原生支持对图形、表格和方程的交叉引用;以及项目级的 _quarto.yml,可以从文档目录构建整个实验室网站或书籍。
R Markdown 并没有消亡。底层的 knitr 引擎与 Quarto 使用的相同,且 Software Carpentry 的 R for Reproducible Scientific Analysis 课程(全球教授最广泛的 R 课程)完全基于 R Markdown 构建。如果您正在学习,请学习 knitr 概念(代码块、块选项、cache=TRUE、fig.width),因为它们可以直接迁移到 Quarto。
文学化文档可重复性的优势在于正文和代码不能脱节。摘要中引用的数字是一段内联代码,而非手动输入的值。当数据更新时,数字随之更新。这种独特的实践消除了计算类论文中与撤稿最相关的最常见错误类别。
容器:固定系统层
renv 固定包;但它不固定 C 编译器、HDF5 库版本,或 NumPy 和 R 共同链接的 BLAS。对于依赖于编译代码的分析 —— Rcpp 包、用于地理空间工作的 sf、具有 C 依赖项的 Bioconductor 包 —— 容器是唯一完整的答案。
rocker 项目(GitHub 上的 rocker-org,由 Dirk Eddelbuettel 和 Carl Boettiger 维护)发布了官方 R Docker 镜像。rocker/r-ver 固定特定的 R 版本;rocker/verse 添加了 Tidyverse、开发工具和发布工具;rocker/bioconductor 紧随 Bioconductor 的发布版本。一个从 rocker/r-ver:4.4.1 开始并运行 renv::restore() 的 Dockerfile 能为您提供从内核到包的完全固定的技术栈。
诚实的妥协:容器增加了构建步骤、注册表和许多实验室成员尚未学习的词汇。对于机构集群上的两人项目,renv 加上记录的 R 版本通常就足够了。对于有外部协作者的文章,容器是能够在未来三年系统升级中幸存的产物。
如何决定:标准清单
按顺序检查,在第一个“是”处停止。
- 您实验室之外的人会执行此操作吗? 如果是,请从一开始就计划使用容器;事后改造比直接构建更困难。
- 流水线是否具有超过三个依赖阶段,或某个阶段运行时间超过五分钟? 如果是,请使用
targets进行可重复科学分析。 - 输出是否包含供人类阅读的文档、图形或表格? 如果是,请使用 Quarto(如果您的团队已有模板,则使用 R Markdown)。
- 分析是否依赖于编译包或特定的 Bioconductor 版本? 如果是,请添加基于 rocker 的容器。
- 这是否是一个产生单个图形的单脚本? 使用
renv和项目文件,然后到此为止。过度设计一次性分析浪费的时间比节省的更多。
值得您花时间的学习资源
Software Carpentry 课程 R for Reproducible Scientific Analysis (swcarpentry.github.io/r-novice-gapminder) 仍然是权威的免费入门教程。它涵盖了项目布局、数据结构、ggplot2、dplyr 和 knitr,其格式专为有现场讲师的两天研讨会而设计。该课程的 GitHub 存储库接受贡献,因此能与 R 的版本保持同步。
对于工具层,官方文档非常出色:renv 的入门 vignette、targets 手册(包含 HPC 用户指南)以及 Quarto 文档的“Computations”章节。CRAN 上的 Reproducible Research Task View 是该领域所有包的维护索引,是在采用新工具前检查的正确位置。
关于概念背景,美国国家科学院的 Reproducibility and Replicability in Science 共识研究 (2019) 精确定义了相关词汇,值得在拨款申请中引用。数据管理的 FAIR Guiding Principles 为如何发布输出提供了补充框架。
资料来源和进一步阅读
- Scientific method — Wikipedia:科学方法是一种通过仔细观察、严格怀疑、假设检验和实验验证来获取知识的经验方法……
常见问题
用于可重复分析的最佳 R 包是什么?
renv 是可重复 R 分析中最重要的单个包,因为它将每个项目的确切包版本固定在协作者可以恢复的锁定文件中。它必须与文学化文档工具(Quarto 或 R Markdown)结合使用,对于多阶段流水线,还需结合 targets。没有单个包能涵盖可重复性的所有四个层,因此答案是一个小型技术栈而非单个工具。
R Markdown 还是 Quarto 更适合可重复研究?
对于新项目,Quarto 是更好的选择:它可以从一个源渲染为更多格式,原生支持交叉引用,并能在同一文档中配合 R、Python 和 Julia 使用。R Markdown 仍得到充分支持,且仍是 Software Carpentry 课程使用的格式,因此现有的教学材料和模板并未过时。两者使用相同的 knitr 执行引擎,因此技能可以直接迁移。
如何使我的 R 分析在不同机器上可重复?
在 README 中记录 R 版本,提交 renv.lock 文件,并指示协作者在打开任何脚本前运行 renv::restore()。避免使用绝对路径和 setwd() 调用;使用项目相对路径或 here 包。如果您的分析依赖于编译包或特定的 Bioconductor 版本,请提供基于 rocker 镜像的 Dockerfile,以便系统层也能被固定。
renv 可以与 Bioconductor 包一起使用吗?
是的。 renv 检测 Bioconductor 软件包并将 Bioconductor 发行版本与软件包版本一起保存在锁定文件中。然后,renv::restore() 从相应的 Bioconductor 版本而不是从 CRAN 安装,从而防止版本偏差破坏生物信息学管道。如果您混合使用 CRAN 和 Bioconductor 包,请检查 snapshot() 之后的锁定文件,以确认两个源都被记录。
targets 和 Makefile 在 R 管道中的区别 之间有什么区别?
targets 是 R 原生的:目标是 R 对象,依赖图是从函数参数推断出来的,结果缓存在 R 可读存储中。 Makefile 要求您手动声明文件级依赖项,并在文件级而不是对象级进行操作。 targets 还支持动态分支,因此一个目标定义可以分散到数百个输入文件并自动并行化,这在 Make 中难以实现。
我可以使用 R 在 HPC 集群上进行可重复的分析吗?
是的,使用 R 进行可重复的科学分析的标准模式是带有集群后端的targets(tar_make_clustermq() 或 tar_make_future())加上从rocker/r-ver或 Bioconductor 映像构建的容器映像。大多数 HPC 中心支持 Apptainer 或 Singularity,而不是 Docker,因此在本地构建映像并进行转换。将 R 版本固定在镜像中,并将软件包版本固定在 renv.lock 中;两者结合在一起使得该工作可以在集群升级后依然可重复。
Frequently asked questions
用于可重复分析的最佳 R 包是什么?
renv 是可重复 R 分析中最重要的包,因为它将每个项目的确切包版本固定到协作者可以恢复的锁定文件中。它必须与文字文档工具(Quarto 或 R Markdown)结合使用,并且对于多阶段管道,还必须与目标结合使用。没有一个包能够涵盖所有四个层的可重复性,因此答案是一个小堆栈而不是单个工具。
R Markdown 还是 Quarto 更适合可重复研究?
对于新项目来说,Quarto 是更好的选择:它可以从一个源呈现更多格式,本身支持交叉引用,并且可以在同一文档中与 R、Python 和 Julia 配合使用。 R Markdown 仍然得到完全支持,并且仍然是 Software Carpentry 课程使用的格式,因此现有的教材和模板并没有过时。两者都使用相同的knitr执行引擎,因此技能可以直接转移。
如何使我的 R 分析可以在不同的机器上重现?
在自述文件中记录 R 版本,提交 renv.lock 文件,并指示协作者在打开任何脚本之前运行 renv::restore()。避免绝对路径和 setwd() 调用;使用项目相对路径或此处的包。如果您的分析依赖于已编译的包或特定的 Bioconductor 版本,请提供基于摇杆图像的 Dockerfile,以便系统层也被固定。
renv 可以与 Bioconductor 包一起使用吗?
是的。 renv 检测 Bioconductor 软件包并将 Bioconductor 发行版本与软件包版本一起保存在锁定文件中。然后,renv::restore() 从相应的 Bioconductor 版本而不是从 CRAN 安装,从而防止版本偏差破坏生物信息学管道。如果您混合使用 CRAN 和 Bioconductor 包,请在 snapshot() 之后检查锁定文件,以确认两个源均已记录。
R 管道的目标和 Makefile 之间有什么区别?
目标是 R 原生的:目标是 R 对象,依赖图是从函数参数推断出来的,结果缓存在 R 可读存储中。 Makefile 要求您手动声明文件级依赖项,并在文件级而不是对象级进行操作。目标还支持动态分支,因此一个目标定义可以分散到数百个输入文件并自动并行化,这在 Make 中表达起来很尴尬。
我可以使用 R 在 HPC 集群上进行可重复的分析吗?
是的,使用 R 进行可重复的科学分析的标准模式是具有集群后端(tar_make_clustermq() 或 tar_make_future())的目标加上从 rocker/r-ver 或 Bioconductor 映像构建的容器映像。大多数 HPC 中心支持 Apptainer 或 Singularity,而不是 Docker,因此在本地构建映像并进行转换。固定镜像中的 R 版本和 renv.lock 中的软件包版本;两者结合在一起使得该工作可以在集群升级中重现。
Learn Python by coding in your browser
Interactive Python and data-science courses you code directly in the browser