如何组织大型R程序?


161

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

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

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

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


11
应该是社区Wiki
SilentGhost

您可能还需要查看ProjectTemplate包装。
ctbrown 2014年

Answers:


71

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

它给你

  • 一种准自动方式,可以按主题组织代码
  • 强烈鼓励您编写帮助文件,使您考虑界面
  • 通过很多检查 R CMD check
  • 有机会添加回归测试
  • 以及命名空间的方法。

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

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

更新时间2008年8月13日: David Smith刚刚写了有关Google R样式指南的博客。


8
如果您正在“有机地”进行源代码树/分析,难道您觉得这很难/麻烦吗?如果您发现代码错误(在探索新的问题空间时很常见),则必须(i)修复源代码;(ii)重新安装软件包;(iii)将其重新加载到您的工作空间中?有没有一种方法可以调用library(...)来重新加载已经加载的包(上面的步骤iii)?您是否不必杀死工作区,重新启动R然后重新加载您的库/包,以查看是否正确?
2009年

1
尝试谷歌搜索R编码样式。
hadley

3
@SteveLianoglou我知道这已经很老了,但是Hadley的devtools软件包使重新加载所有代码非常容易。
戴森2012年

1
这篇博客文章提供了一个(我认为)非常好的快速教程,以构建一个无骨的第一个软件包:hilaryparker.com/2014/04/29/writing-an-r-package-from-scratch
panterasBox 2015年


51

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

但是我不喜欢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)
Dan Goldstein在2009年

2
因此,如果要调整source(“ util.R”)等,可以一次又一次地执行。
布伦丹·奥康纳

您实际上不需要while循环-您只需要它detach(util)。我不记得它是否给出了错误或者是否尚未加载,但这是最安全的并且可以正常工作。建议欢迎。
布伦丹·奥康纳

1
创建特定于功能的环境并附加它是我的最佳选择。另一种以不同方式(具有更多模块化)实现相同目的的方法是使用sys.sourceMyEnv <- attach(NULL, name=s_env); sys.source(file, MyEnv)。我什至在启动时声明了(在其自己的环境中!)一个函数sys.source2,该函数将查找是否已经存在相同名称的环境,并提供给这个环境而不是创建一个新环境。它可以快速,轻松且有条理地添加个人功能:-)
AntoineLizée2013年

5
今天刚刚看到,它适用于替代软件包:github.com/klmr/modules
ctbrown 2014年

34

这听起来似乎有点明显,尤其是如果您是一名程序员,但是这就是我对逻辑和物理代码单元的看法。

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

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

何时将逻辑单元提取到自己的物理单元(例如源文件和更大的分组(如包))中?我有两种情况。首先,如果文件太大,并且在逻辑上无关的单位之间滚动会很麻烦。第二,如果我具有可以被其他程序重用的功能。我通常首先将一些分组的单元(例如数据操作函数)放入一个单独的文件中。然后,我可以从任何其他脚本中获取该文件。

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

最后,我认为您的问题并不一定是R所特有的。我真的建议阅读Steve McConnell撰写的Code Complete,其中包含有关此类问题和整个编码实践的很多知识。


3
非常有用的评论,ars,谢谢。我是一名程序员,但是最好和其他人一起检查。当您说“由于R不支持按引用调用,我通常会担心我的函数的粒度过细”,我听到了您的声音。我习惯于编写类似ReadData()的函数;CleanData(); AnalyzeData(); GraphData(); 而R则使工作变得繁琐。我开始意识到我需要像使用其他语言的函数一样使用“源代码”。
丹·戈德斯坦

2
你说得对,丹。我发现自己以这种方式使用“源”来进行数据集准备任务,因此我可以在完成实际分析的其他脚本中使用准备好的data.frame。我不确定这是否是一个好习惯,因为相对于其他语言,它感觉很奇怪-更像是shell脚本。比较笔记是很好的。:)
ars

好的答案和评论。我发现这在R中特别令人讨厌,因为您经常使用特定格式的数据,为此很难编写可重用的函数。那么,即使您知道它永远不会被重复使用,您是否仍在编写出色的功能代码,还是只是为了在大型对象上获得一点点额外的效率而编写快速而令人讨厌的过程代码?有点两难。.R将是绝对很棒的按引用调用..
naught101

好吧,这是可以做到的,但是并没有提高效率……
naught101

19

我同意德克的建议!恕我直言,将程序从简单脚本组织到已记录的软件包,对于R编程而言,就像从Word切换到TeX / LaTeX进行编写一样。我建议看一下Friedrich Leisch撰写的非常有用的《创建R包:教程》


6
包装看起来引人注目。但是,我担心它们可能会过大。我不是在写通用代码。我正在做的大部分工作是测试该假设,测试该假设,绘制此图,调整绘制参数,绘制该图,重塑数据,绘制该图。我正在做的事情一旦完成,可能永远不会重新运行。
丹·戈德斯坦

1
在这种情况下,您应该看看Sweave。它结合了R代码和LaTeX。因此,您可以将分析和报告源一起使用。
Thierry

15

我的简洁答案:

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

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


11

我一直想弄清楚如何编写包,但没有花时间。对于我的每个微型项目,我都将所有低层函数保存在一个名为“ 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/

关于use [,drop = FALSE]和<-的其他“约定”,在useR的各种演示文稿(通常是主题演讲)中建议使用!会议,但我认为这些都不是严格的(尽管[,drop = FALSE]对于不确定您期望输入的程序很有用)。


6

算我为赞成包裹的另一个人。我承认在需要时(即被释放),在编写手册页和插页方面很差劲,但这确实是捆绑源doe的便捷方法。另外,如果您认真维护代码,那么Dirk提出的所有观点都会变得很重要。


4

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

至于访问全局环境,尽管不鼓励使用<<-运算符,但这很容易。


3

还没有学会如何编写软件包,我一直通过寻找子脚本来组织工作。它类似于编写类,但不涉及编写类。它在编程上不尽人意,但随着时间的流逝,我发现我会进行分析。一旦有了一个很大的部分可以工作,我经常将其移至其他脚本并仅提供其源代码,因为它将使用工作区对象。也许我需要从多个来源导入数据,对所有数据进行排序并找到交叉点。我可以将该部分放入其他脚本中。但是,如果您想将“应用程序”分发给其他人,或者使用一些交互式输入,则打包可能是一个不错的选择。作为研究人员,我很少需要分发我的分析代码,但是我经常需要对它进行扩充或调整。


我使用了这种方法,但是从那以后,我意识到函数和包比source(“ next_script.R”)更好。我在这里写一下吧:stackoverflow.com/questions/25273166/...
亚瑟伟业

1

我也一直在寻找正确的工作流程,以完成一个R大项目。去年,我发现了这个名为rsuite的软件包,当然,这正是我想要的。这个R包是为部署大型R项目而明确开发的,但是我发现它可以用于较小,中等大小和大型R项目。我将在一分钟内(下)提供指向真实示例的链接,但首先,我想解释用来构建R项目的新范例rsuite

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

  1. 我们一直在用RStudio进行所有错误的项目。目标不应该是创建项目或程序包,而应该是更大的范围。在rsuite中,您将创建一个超级项目或主项目,其中包含标准的R项目和R包,并且可能存在所有组合。

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

在此处输入图片说明

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

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

  3. 该文件夹deployment就是rsuite会写了指示的包,该包的所有二进制DESCRIPTION文件。因此,这本身就使您可以计划完全可复制的加速时间。

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

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

  6. 您还可以创建本地存储库,以在脱机时提取R软件包,或者希望更快地构建整个软件包。

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

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

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

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

我要做的第二件事是创建机器学习项目,但是rsuite顺便说一下。一个主要的,协调的项目在顶部,所有子项目和程序包都在该部分的控制之下。它确实改变了您使用R进行编码的方式,从而提高了工作效率。

之后,我开始在一个名为的新软件包中工作rTorch。之所以可能这样做,很大程度上是因为rsuite; 它使您能够思考并不断壮大。

一条建议。学习rsuite并不容易。因为它提供了创建R项目的新方法,所以很难。初次尝试时不要惊慌,继续爬上斜坡,直到爬上为止。它需要您的操作系统和文件系统的高级知识。

我希望有一天RStudio,我们可以像rsuite从菜单中一样生成编排项目。这一定非常棒。

链接:

RSuite GitHUb回购

r4ds预订

keras和闪亮教程

现代潜水书房

interpretable_ml-rsuite

IntroMachineLearningWithR-rsuite

clark-intro_ml-rsuite

hyndman-bookdown-rsuite

statistics_rethinking-rsuite

fread-benchmarks-rsuite

数据可视化

零售细分市场H2O教程

电信客户流失指南

sclerotinia_rsuite


-7

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


1
那里有非常大的软件包(即程序)。您是否在认真建议应将它们用其他某种语言重写?为什么???
爱德华多·莱昂尼

4
一个考虑因素是效率。我经常将R代码重写为C ++代码,并将其速度提高了100倍。另一个是工具支持。R没有像Eclipse或Visual Studio这样的IDE可以与之媲美。最后,如果程序很大,则可能正在执行R不太适合的非统计任务。
约翰D.库克

2
有一个可用的插件(Stat-ET),它允许Eclipse与R进行交互。我同意C ++的运行速度比R快得多。但是您需要多少时间将R东西重新编码为C ++?除非您可以频繁地重用代码,否则与使用C ++进行重新编码的努力相比,更快的代码所带来的好处并不值得。
Thierry

2
是的,需要权衡取舍(生产力与绩效)。对于纯粹的数据分析/统计工作,R通常会胜出。但是对于编写其他任务,例如GUI,Web等,我不确定是这种情况。我们经常在R中进行原型设计和工作,但在Python / C ++中部署生产代码。使用后者,您可以获得性能以及非常成熟和可重用的库/框架来完成各种任务。但是,这是一个不稳定的情况,R生态系统正在不断发展。
ARS

使用该Rcpp程序包(包括R程序中的C ++代码)变得非常简单。因此,重写R代码的某些部分可以很容易地集成到R中。此外,RStudio的问世引入了R的IDE,尽管可能还不如Visual Studio强大。
Paul Hiemstra,2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.