如何组织大型 R 程序?

Posted

技术标签:

【中文标题】如何组织大型 R 程序?【英文标题】:How to organize large R programs? 【发布时间】:2010-11-18 23:20:31 【问题描述】:

当我承担任何复杂的 R 项目时,我的脚本很快就会变得冗长和混乱。

我可以采用哪些实践来使我的代码始终令人愉快地工作?我正在考虑类似的事情

在源文件中放置函数 何时将某些内容拆分到另一个源文件中 主文件中应该包含什么 将函数用作组织单位(考虑到 R 使得访问全局状态变得困难,这是否值得) 缩进/换行做法。 对待(如? 将 ) 之类的内容放在 1 行或 2 行上?

基本上,您组织大型 R 脚本的经验法则是什么?

【问题讨论】:

您可能还想查看ProjectTemplate 包。 【参考方案1】:

标准答案是使用包 - 请参阅 Writing R Extensions 手册以及网络上的不同教程。

它给你

一种按主题组织代码的准自动方式 强烈建议您编写帮助文件,让您考虑界面 通过R CMD check 进行了大量的健全性检查 有机会添加回归测试 以及命名空间的一种方式。

只需在代码上运行source() 即可实现非常短的 sn-ps。其他所有内容都应该在一个包中——即使您不打算发布它,因为您可以为内部存储库编写内部包。

至于“如何编辑”部分,R Internals 手册在第 6 节中有出色的R 编码标准。否则,我倾向于使用 Emacs' ESS mode 中的默认值。

2008 年 8 月 13 日更新: David Smith 刚刚发布了关于 Google R Style Guide 的博客。

【讨论】:

如果你正在“有机地”增长你的源代码树/分析,你不觉得这很难做到/很麻烦吗?如果您发现代码中有错误(在探索新问题空间时很常见),您必须 (i) 修复源代码; (ii) 重新安装软件包; (iii) 将其重新加载到您的工作区?有没有办法调用 library(...) 以重新加载已经加载的包(上面的步骤 iii)?您不必杀死您的工作区,重新启动 R 然后重新加载您的库/包以查看它是否正确吗? 尝试用谷歌搜索 R 编码风格。 @SteveLianoglou 我知道这已经很老了,但是 Hadley 的 devtools 包让重新加载所有代码变得非常容易。 这篇博文提供了一个(我认为)非常好的快速教程来构建一个基本的第一个包:hilaryparker.com/2014/04/29/writing-an-r-package-from-scratch 这是Google R Style Guide的工作链接【参考方案2】:

我也同意。使用 package.skeleton() 函数开始。即使您认为您的代码可能永远不会再次运行,它也可能有助于激励您创建更通用的代码,从而节省您以后的时间。

至于访问全局环境,使用

【讨论】:

【参考方案3】:

我一直想弄清楚如何编写包,但没有投入时间。对于我的每个小项目,我将所有低级函数保存在一个名为“functions/”的文件夹中,并将它们源到我明确创建的单独命名空间中。

以下代码行将在搜索路径上创建一个名为“myfuncs”的环境(如果它不存在)(使用附加),并使用我的“functions/”中的 .r 文件中包含的函数填充它目录(使用 sys.source)。我通常将这些行放在我的主脚本的顶部,用于调用高级函数(调用低级函数)的“用户界面”。

if( length(grep("^myfuncs$",search()))==0 )
  attach("myfuncs",pos=2)
for( f in list.files("functions","\\.r$",full=TRUE) )
  sys.source(f,pos.to.env(grep("^myfuncs$",search())))

当您进行更改时,您始终可以使用相同的行重新获取它,或者使用类似的东西

evalq(f <- function(x) x * 2, pos.to.env(grep("^myfuncs$",search())))

评估您创建的环境中的添加/修改。

我知道这很笨拙,但避免过于正式(但如果你有机会我确实鼓励包系统 - 希望我将来会以这种方式迁移)。

至于编码约定,这是我见过的唯一关于美学的东西(我喜欢它们并且松散地遵循,但我不会在 R 中使用太多花括号):

http://www1.maths.lth.se/help/R/RCC/

还有其他关于使用 [,drop=FALSE] 和

【讨论】:

【参考方案4】:

R 可以用于交互式使用和小型脚本,但我不会将它用于大型程序。我会使用主流语言进行大部分编程并将其封装在 R 接口中。

【讨论】:

那里有非常大的包(即程序)。你是认真地建议他们应该用其他语言重写吗?为什么??? 一个考虑因素是效率。我经常将 R 代码重写为 C++ 代码,并使其速度提高了 100 倍。另一个是工具支持。 R 无法与 Eclipse 或 Visual Studio 等 IDE 相媲美。最后,如果一个程序非常大,它可能会执行 R 不太适合的非统计任务。 有一个可用的插件 (Stat-ET) 允许 Eclipse 与 R 交互。我同意 C++ 可以比 R 更快地工作。但是你需要多少时间将 R 的东西重新编码成C++?除非您可以经常重用代码,否则与在 C++ 中重新编码的努力相比,更快的代码所带来的好处并不值得。 是的,有一个权衡(生产力与性能)。对于纯粹的数据分析/统计工作,R 通常会胜出。但是对于编写其他任务,例如GUI、Web 等,我不确定是不是这样。我们经常在 R 中进行原型设计和工作,但在 Python/C++ 中部署生产代码。使用后者,您可以获得性能非常成熟且可重用的库/框架,可用于各种任务。但是,这是一个不稳定的情况,R 生态系统在不断发展。 使用Rcpp 包,在R 程序中包含C++ 代码变得非常简单。因此重写 R 代码的某些部分可以很容易地集成到 R 中。此外,RStudio 的出现为 R 引入了一个 IDE,尽管可能还没有 Visual Studio 强大。【参考方案5】:

我还没有学习如何编写包,我总是通过采购子脚本来组织。它类似于写作课程,但没有那么复杂。它在编程上并不优雅,但我发现我会随着时间的推移建立分析。一旦我有一个可以工作的大部分,我经常将它移动到不同的脚本并直接获取它,因为它将使用工作区对象。也许我需要从多个来源导入数据,对所有数据进行排序并找到交叉点。我可能会将该部分放入一个附加脚本中。但是,如果您想为其他人分发您的“应用程序”,或者它使用一些交互式输入,那么包可能是一个不错的途径。作为一名研究人员,我很少需要分发我的分析代码,但我经常需要对其进行扩充或调整。

【讨论】:

我使用了这种方法,但后来意识到函数和包比 source("next_script.R") 更好。我在这里写过:***.com/questions/25273166/…【参考方案6】:

这可能听起来有点明显,特别是如果您是一名程序员,但这是我对代码的逻辑和物理单元的看法。

我不知道这是否是你的情况,但是当我在 R 中工作时,我很少会从一个大型复杂程序开始。我通常从一个脚本开始,将代码分成逻辑上可分离的单元,通常使用函数。数据操作和可视化代码被放置在它们自己的函数中,等等。这些函数被组合在文件的一个部分中(顶部的数据操作,然后是可视化等)。最终,您要考虑如何让您更轻松地维护脚本并降低缺陷率。

您对函数的细粒度/粗粒度会有所不同,并且有各种经验法则:例如15 行代码,或“一个函数应该负责完成一项由其名称标识的任务”等。您的里程会有所不同。由于 R 不支持按引用调用,因此当涉及传递数据帧或类似结构时,我通常会使我的函数过于细化。但这可能是对我刚开始使用 R 时一些愚蠢的性能错误的过度补偿。

何时将逻辑单元提取到它们自己的物理单元中(如源文件和更大的分组如包)?我有两个案例。首先,如果文件变得太大并且在逻辑上不相关的单元之间滚动会很烦人。其次,如果我有其他程序可以重用的功能。我通常首先将一些分组单元(例如数据操作函数)放入一个单独的文件中。然后我可以从任何其他脚本中获取此文件。

如果您要部署您的功能,那么您需要开始考虑包。由于各种原因(简而言之:组织文化更喜欢其他语言、对性能的担忧、GPL 等),我不会在生产中部署 R 代码或供其他人重用。此外,我倾向于不断完善并添加到我的源文件集合中,并且我宁愿在进行更改时不处理包。因此,您应该查看其他与软件包相关的答案,例如 Dirk 的,以了解有关这方面的更多详细信息。

最后,我认为您的问题不一定针对 R。我强烈建议您阅读 Steve McConnell 的 Code Complete,其中包含有关此类问题和编码实践的大量智慧。

【讨论】:

【参考方案7】:

我的简洁回答:

    仔细编写函数,确定足够通用的输出和输入; 限制使用全局变量; 使用 S3 对象,并在适当的情况下使用 S4 对象; 将函数放入包中,尤其是在函数调用 C/Fortran 时。

我相信 R 在生产中的使用越来越多,因此对可重用代码的需求比以前更大。我发现解释器比以前强大得多。毫无疑问,R 比 C 慢 100-300 倍,但通常瓶颈集中在几行代码,可以委托给 C/C++。我认为将 R 在数据处理和统计分析方面的优势委托给另一种语言是错误的。在这些情况下,性能损失很低,无论如何都值得节省开发工作量。如果只考虑执行时间,我们都会编写汇编程序。

【讨论】:

【参考方案8】:

把我算作另一个支持包裹的人。我承认在我必须(即被释放)之前/当我必须(即被释放)之前,我在编写手册页和小插曲方面非常糟糕,但它为捆绑源代码提供了一种真正方便的方式。另外,如果您认真维护您的代码,Dirk 提出的所有要点都会体现在 plya 中。

【讨论】:

【参考方案9】:

我喜欢将不同的功能放在他们自己的文件中。

但我不喜欢 R 的包系统。比较难用。

我更喜欢轻量级的替代方案,将文件的函数放置在环境中(所有其他语言都称为“命名空间”)并附加它。例如,我制作了一组“util”函数,如下所示:

util = new.env()

util$bgrep = function [...]

util$timeit = function [...]

while("util" %in% search())
  detach("util")
attach(util)

这一切都在一个文件util.R 中。当您获取它时,您将获得环境“util”,因此您可以调用util$bgrep() 等;但此外,attach() 调用使它变得如此简单bgrep() 和直接工作。如果您没有将所有这些函数放在它们自己的环境中,它们会污染解释器的***命名空间(ls() 显示的命名空间)。

我试图模拟 Python 的系统,其中每个文件都是一个模块。那会更好,但这似乎还可以。

【讨论】:

谢谢,布伦丹。这是非常有用的。 while 循环是怎么回事? if (!("util" %in% search())) attach(util) 有什么问题 所以如果你想调整它等等,你可以一次又一次地做 source("util.R") 。 你真的不需要 while 循环——你只需要它 detach(util)。如果它尚未加载,我不记得它是否给出错误,但这是最安全的并且确实有效。欢迎提出建议。 创建特定于功能的环境并附加它是我要走的路。另一种以不同方式(具有更多模块化)实现相同目标的方法是使用sys.sourceMyEnv &lt;- attach(NULL, name=s_env); sys.source(file, MyEnv)。我什至在启动时声明(在它自己的环境中!)一个函数sys.source2,如果同名的环境已经存在,它将查找并提供这个环境而不是创建一个新环境。它使添加个人功能变得快速、简单且有条理:-) 今天刚看到这个,它适用于包替代:github.com/klmr/modules【参考方案10】:

我也一直在寻找合适的工作流程的圣杯来组织一个 R 大型项目。去年,我发现了这个名为rsuite 的包,当然,这正是我要找的。这个 R 包是专门为部署大型 R 项目而开发的,但我发现它可以用于小型、中型和大型 R 项目。我将在一分钟内提供真实示例的链接(如下),但首先,我想解释一下使用 rsuite 构建 R 项目的新范式。

注意。我不是rsuite 的创建者或开发者。

    我们用 RStudio 做的项目都错了;目标不应该是创建一个项目或一个包,而是一个更大的范围。在 rsuite 中,您可以创建一个超级项目或主项目,其中包含标准 R 项目和 R 包,以所有可能的组合。

    通过拥有一个 R 超级项目,您不再需要 Unix make 来管理下面的 R 项目的较低级别;您在顶部使用 R 脚本。让我演示给你看。当你创建一个 rsuite 主项目时,你会得到这个文件夹结构:

    文件夹R 是您放置项目管理脚本的地方,这些脚本将替换make

    文件夹packagesrsuite 保存构成超级项目的所有包的文件夹。您也可以复制粘贴一个无法从 Internet 访问的包,rsuite 也会构建它。

    文件夹deploymentrsuite 将写入包DESCRIPTION 文件中指示的所有包二进制文件的位置。因此,这本身就可以让您在整个时间范围内进行完全可重复的投影。

    rsuite 带有适用于所有操作系统的客户端。我已经测试了所有这些。但您也可以将其安装为 RStudio 的 addin

    rsuite 还允许您在其自己的文件夹 conda 中构建隔离的 conda 安装。这不是一个环境,而是从您的机器中的 Anaconda 派生的物理 Python 安装。这与 R 的 SystemRequirements 一起工作,您可以从它安装所有您想要的 Python 包,从您想要的任何 conda 频道。

    您还可以创建本地存储库以在离线时拉取 R 包,或者想要更快地构建整个东西。

    如果需要,您还可以将 R 项目构建为 zip 文件并与同事共享。如果您的同事安装了相同的 R 版本,它将运行。

    另一种选择是在 Ubuntu、Debian 或 CentOS 中构建整个项目的容器。因此,您无需与项目构建共享 zip 文件,而是与准备运行的项目共享整个 Docker 容器。

我一直在对rsuite 进行大量试验,以寻求完全的可重复性,并避免依赖于在全球环境中安装的软件包。这是错误的,因为一旦您安装了包更新,项目通常会停止工作,特别是那些对具有特定参数的函数进行非常具体的调用的包。

我开始尝试的第一件事是使用bookdown 电子书。我从来没有幸运地拥有一本能够经受住超过六个月的时间考验的书籍。所以,我所做的是将原始的 bookdown 项目转换为遵循rsuite 框架。现在,我不必担心更新我的全局 R 环境,因为该项目在 deployment 文件夹中有自己的一组包。

我接下来要做的是创建机器学习项目,但是以rsuite 的方式。一个master,顶部的编排项目,所有的子项目和包都在master的控制之下。它确实改变了您使用 R 编码的方式,让您更有效率。

在那之后,我开始使用我的一个名为rTorch 的新包。这在很大程度上是可能的,因为rsuite;它可以让您思考并发展壮大。

不过有一条建议。学习rsuite 并不容易。因为它提供了一种创建 R 项目的新方法,所以感觉很难。不要在第一次尝试时感到沮丧,继续爬斜坡直到你成功。它需要您的操作系统和文件系统的高级知识。

我希望有一天RStudio 允许我们像rsuite 从菜单中生成编排项目。会很棒的。

链接:

RSuite GitHUb repo

r4ds bookdown

keras and shiny tutorial

moderndive-book-rsuite

interpretable_ml-rsuite

IntroMachineLearningWithR-rsuite

clark-intro_ml-rsuite

hyndman-bookdown-rsuite

statistical_rethinking-rsuite

fread-benchmarks-rsuite

dataviz-rsuite

retail-segmentation-h2o-tutorial

telco-customer-churn-tutorial

sclerotinia_rsuite

【讨论】:

以上是关于如何组织大型 R 程序?的主要内容,如果未能解决你的问题,请参考以下文章

组织大型 javascript 应用程序的工具和最佳实践

在大型的 Angularjs 项目中,如何组织您的代码

如何使用 R Shiny 映射大型数据集?

Flask从入门到精通之大型程序的结构一

最佳实践:在大型 Backbone.js 应用程序中组织 CSS 文件,并考虑图像精灵?

模块与命名空间:组织大型打字稿项目的正确方法是啥?