如何组织大型 R 程序?

How to organize large R programs?

提问人:Dan Goldstein 提问时间:8/12/2009 最后编辑:smciDan Goldstein 更新时间:2/6/2021 访问量:33795

问:

当我进行任何复杂的 R 项目时,我的脚本很快就会变得冗长且令人困惑。

我可以采用哪些做法来使我的代码始终令人愉快地使用?我在想这样的事情

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

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

R 约定代码 项目组织

评论

0赞 ctbrown 4/16/2014
您可能还想查看包装。ProjectTemplate

答:

78赞 Dirk Eddelbuettel 8/12/2009 #1

标准答案是使用包 -- 请参阅编写 R 扩展手册以及 Web 上的其他教程。

它给你

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

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

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

2008 年 8 月 13 日更新:大卫·史密斯(David Smith)刚刚在博客上写了一篇关于Google R风格指南的文章。

评论

9赞 Steve Lianoglou 8/13/2009
如果你正在“有机地”发展你的源代码树/分析,你不觉得这很难做到/很麻烦吗?如果你发现你的代码中有错误(在探索新的问题空间时很常见),你必须 (i) 修复源代码;(ii) 重新安装软件包;(iii) 将其重新加载到您的工作区中?有没有办法调用 library(...) 以重新加载已加载的包(上面的第 iii 步)?难道你不必杀死你的工作区,重新启动R,然后重新加载你的库/包,看看它是否正确吗?
1赞 hadley 8/13/2009
尝试在谷歌上搜索 R 编码风格。
3赞 Dason 6/6/2012
@SteveLianoglou我知道这已经很老了,但 Hadley 的 devtools 包使重新加载所有代码变得非常容易。
1赞 statsNoob 9/3/2015
这篇博文给出了一个(我的观点)非常好的快速教程来构建一个基本的第一个包: hilaryparker.com/2014/04/29/writing-an-r-package-from-scratch
1赞 Denis Rasulev 2/7/2017
这是 Google R 风格指南的工作链接
4赞 cameron.bracken 8/13/2009 #2

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

至于访问全球环境,这对<<运营商来说很容易,尽管不鼓励这样做。

11赞 hatmatrix 8/13/2009 #3

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

以下代码行将在搜索路径上创建一个名为“myfuncs”的环境(如果该环境尚不存在)(使用 attach),并使用我的“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] 和 <-,还有其他“约定”,正如赋值运算符在 useR 的各种演示(通常是主题演讲)中建议的那样!会议,但我认为其中任何一个都不是严格的(尽管 [,drop=FALSE] 对于您不确定您期望的输入的程序很有用)。

-4赞 John D. Cook 8/13/2009 #4

R 可用于交互式使用和小型脚本,但我不会将其用于大型程序。我会在大多数编程中使用主流语言,并将其包装在 R 接口中。

评论

1赞 Eduardo Leoni 8/13/2009
那里有非常大的包(即程序)。你是认真地建议它们应该用其他语言重写吗?为什么???
4赞 John D. Cook 8/13/2009
一个考虑因素是效率。我经常将 R 代码重写为 C++ 代码,并使其速度提高 100 倍。另一个是工具支持。R 无法与 Eclipse 或 Visual Studio 等 IDE 相提并论。最后,如果一个程序非常大,它很可能正在执行 R 不太适合的非统计任务。
2赞 Thierry 8/13/2009
有一个插件(Stat-ET)允许Eclipse与R交互。我认为 C++ 的工作速度比 R 快得多。但是,将 R 内容重新编码为 C++ 需要多少时间?除非您可以经常重用代码,否则与在 C++ 中重新编码代码相比,更快代码的好处并不值钱。
2赞 ars 8/13/2009
是的,需要权衡(生产力与性能)。对于纯粹的数据分析/统计工作,R 经常获胜。但是对于编写其他任务,例如 GUI、Web 等,我不确定情况是否如此。我们经常在 R 中构建原型和工作,但在 Python/C++ 中部署生产代码。使用后者,您可以获得用于各种任务的性能以及非常成熟且可重用的库/框架。但是,这是一个不稳定的情况,R 生态系统在不断发展。
0赞 Paul Hiemstra 8/11/2015
使用包,包括 R 程序中的 C++ 代码变得非常简单。因此,重写 R 代码的某些部分可以很容易地集成到 R 中。此外,RStudio 的出现为 R 引入了一个 IDE,尽管它可能还没有 Visual Studio 那么强大。Rcpp
3赞 kpierce8 8/13/2009 #5

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

评论

0赞 Arthur Yip 8/29/2019
我使用了这种方法,但后来意识到函数和包比 source(“next_script 更好。R").我在这里写了:stackoverflow.com/questions/25273166/......
34赞 ars 8/13/2009 #6

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

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

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

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

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

最后,我认为你的问题不一定是 R 特有的。我真的推荐阅读 Steve McConnell 的 Code Complete,其中包含了很多关于此类问题和编码实践的智慧。

17赞 gappy 8/16/2009 #7

我简明扼要的回答:

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

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

6赞 geoffjentry 8/19/2009 #8

把我算作另一个支持包裹的人。我承认在编写手册页和小插图方面非常糟糕,直到我必须(即被释放)为止,但它提供了一种非常方便的方式来捆绑源代码母鹿。另外,如果你认真地维护你的代码,Dirk 提出的要点都会出现在 plya 中。

55赞 Brendan OConnor 8/24/2009 #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 的系统,其中每个文件都是一个模块。那会更好,但这似乎没问题。

评论

0赞 Dan Goldstein 8/25/2009
谢谢,布兰登。这是非常有用的。while 循环是怎么回事?if (!(“ 有什么问题util“ %in% search())) attach(util)
2赞 Brendan OConnor 8/28/2009
所以你可以做source(“util.R“),如果你想调整它等等,一次又一次。
0赞 Brendan OConnor 8/28/2009
你真的不需要 while 循环 -- 你只需要它 detach(util)。如果它尚未加载,我不记得它是否出现错误,但这是最安全的并且确实有效。欢迎提出建议。
1赞 Antoine Lizée 8/16/2013
创建特定于功能的环境并附加它是我的必经之路。另一种以不同的方式(具有更多模块化)实现相同事物的方法是使用 : .我什至在启动时声明(在它自己的环境中!)一个函数,如果一个同名的环境已经在这里,它将查找并输入这个环境,而不是创建一个新环境。它使添加个人功能变得快速、简单且有条理:-)sys.sourceMyEnv <- attach(NULL, name=s_env); sys.source(file, MyEnv)sys.source2
5赞 ctbrown 4/16/2014
今天刚刚看到这个,它适用于一个包替代方案:github.com/klmr/modules
2赞 f0nzie 8/5/2019 #10

我也一直在寻找将 R 大型项目组合在一起的正确工作流程的圣杯。去年,我找到了这个叫做 rsuite 的软件包,当然,这就是我一直在寻找的。这个 R 包是为部署大型 R 项目而显式开发的,但我发现它可以用于小型、中型和大型 R 项目。我将在一分钟内提供指向实际示例的链接(如下所示),但首先,我想解释使用 .rsuite

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

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

  2. 通过拥有 R 超级项目,您不再需要 Unix 来管理底层 R 项目的较低级别;在顶部使用 R 脚本。让我告诉你。创建 rsuite 主项目时,将获得以下文件夹结构:make

enter image description here

  1. 该文件夹是放置项目管理脚本的位置,这些脚本将替换 .Rmake

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

  3. 该文件夹将写入包文件中指示的所有包二进制文件。因此,这本身就使得您投射出完全可重复的 accros 时间。deploymentrsuiteDESCRIPTION

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

  5. rsuite还允许您在其自己的文件夹中构建独立安装。这不是一个环境,而是从计算机中的 Anaconda 派生的物理 Python 安装。这与 R 一起使用,您可以从任何所需的 conda 通道安装所需的所有 Python 包。condacondaSystemRequirements

  6. 还可以创建本地存储库,以便在脱机时拉取 R 包,或者想要更快地生成整个内容。

  7. 如果需要,还可以将 R 项目生成为 zip 文件并与同事共享。它将运行,前提是你的同事安装了相同的 R 版本。

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

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

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

我做的下一件事是创建机器学习项目,但碍事了。主控项目,在顶部编排项目,所有子项目和包都在主控的控制之下。它确实改变了你使用 R 编码的方式,使你的工作效率更高。rsuite

在那之后,我开始在我的一个名为 .这在很大程度上是可能的,因为;它可以让你思考并做大事。rTorchrsuite

不过有一条建议。学习并不容易。因为它提供了一种创建 R 项目的新方法,所以感觉很难。第一次尝试时不要灰心,继续攀登斜坡,直到成功为止。它需要对操作系统和文件系统有深入的了解。rsuite

我希望有一天我们可以像从菜单中一样生成编排项目。那太棒了。RStudiorsuite

链接:

RSuite GitHUb 存储库

R4DS 预订

Keras 和 Shiny 教程

moderndive-book-rsuite

interpretable_ml-rsuite

IntroMachineLearningWithR-rsuite

克拉克-intro_ml-RSUITE

hyndman-bookdown-rsuite

statistical_rethinking-rsuite

fread-benchmarks-rsuite

dataviz-rsuite

零售-细分-h2o-教程

telco-customer-churn-tutorial (电信客户流失教程)

sclerotinia_rsuite