如何组织大型R程序?

Dan*_*ein 157 r conventions code-organization project-organization package

当我承担任何复杂的R项目时,我的脚本会很快变得混乱.

我可以采用哪些做法,以便我的代码永远乐于与之合作?我在考虑类似的事情

  • 在源文件中放置函数
  • 何时将某些内容分解为另一个源文件
  • 什么应该在主文件中
  • 使用函数作为组织单元(鉴于R使得难以访问全局状态,这是否值得)
  • 缩进/换行做法.
    • 治疗(如{?
    • 把东西放在1或2行?

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

Dir*_*tel 69

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

它给你

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

只是运行source()代码适用于非常短的代码片段.其他所有内容都应该在一个包中 - 即使您不打算发布它,因为您可以编写内部存储库的内部包.

至于"如何编辑"部分,R Internals手册在第6节中具有优秀的R编码标准.否则,我倾向于在Emacs的ESS模式中使用默认值.

更新2008年8月13日: David Smith刚刚发布了关于Google R风格指南的博文.

  • 如果你"有机地"发展你的源树/分析,难道你不觉得这很难/麻烦吗?如果您发现代码中存在错误(在探索新问题空间时常见),则必须(i)修复源代码; (ii)重新安装包裹; (iii)将其重新加载到您的工作区?有没有办法调用库(...)以重新加载已经加载的包(上面的步骤iii)?你不必杀死你的工作区,重新启动R然后重新加载你的库/包,看看它是否正确? (8认同)
  • @SteveLianoglou我知道这已经很老了,但是Hadley的devtools软件包使得重新加载所有代码非常容易. (3认同)

Bre*_*nor 50

我喜欢在自己的文件中添加不同的功能.

但我不喜欢R的包装系统.它很难使用.

我更喜欢轻量级的替代方法,将文件的函数放在一个环境中(所有其他语言都称之为"命名空间")并附加它.例如,我创建了一个'util'组函数,如下所示:

util = new.env()

util$bgrep = function [...]

util$timeit = function [...]

while("util" %in% search())
  detach("util")
attach(util)
Run Code Online (Sandbox Code Playgroud)

这都在文件util.R中.当您获取它时,您将获得环境'util'以便您可以调用util$bgrep()等等; 但此外,这个attach()电话使它如此公正,bgrep()并且直接进行了这样的工作.如果你没有将所有这些函数放在他们自己的环境中,他们会污染解释器的顶级命名空间(ls()显示的那个).

我试图模拟Python的系统,其中每个文件都是一个模块.那会更好,但这似乎没问题.

  • 刚刚看到这个,它适用于一个包替代品:https://github.com/klmr/modules (5认同)
  • 所以你可以反复做源("util.R"),如果你想调整它等等. (2认同)

ars*_*ars 33

这可能听起来有点明显,特别是如果你是一个程序员,但这就是我如何考虑逻辑和物理代码单元.

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

你的功能如何细/粗粒度会有所不同,并且有各种经验法则:例如15行代码,或者"一个函数应该负责执行一个由其名称标识的任务",等等.你的里程会有所不同.由于R不支持call-by-reference,因此当我涉及传递数据帧或类似结构时,我通常会使我的函数太精细.但是,当我第一次开始使用R时,这可能会对一些愚蠢的性能错误进行过度补偿.

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

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

最后,我认为你的问题不一定特别适用于R.我真的建议阅读Steve McConnell的Code Complete,其中包含了很多关于此类问题和编码实践的智慧.

  • 非常有帮助的评论,ars,谢谢.我是一名程序员,但与其他人一起检查是件好事.当你说"由于R不支持按引用调用时,我通常会担心使我的函数太精细",我听到了.我习惯于编写像ReadData()这样的函数; CleanData(); AnalyzeData(); GraphData(); 和R使得这很麻烦.我正在意识到我需要使用"源",就像我在其他语言中使用函数一样. (3认同)
  • 你是对的,丹.我发现自己使用"源"这种方式进行数据集准备任务,因此我可以在其他脚本中使用准备好的data.frame进行真正的分析.我不知道这是不是很好的做法,因为它相对于其他语言感觉很奇怪 - 更像是shell脚本.比较笔记很好.:) (2认同)

Pao*_*olo 19

我同意Dirk的建议!恕我直言,将程序从简单的脚本组织到文档化的软件包,对于R编程,就像从Word切换到TeX/LaTeX进行编写一样.我建议看一下非常有用的创建R包: Friedrich Leisch 的教程.

  • 包看起来很吸引人.但是,我担心他们可能有点矫枉过正.我不是在写通用代码.我正在做的大部分是测试这个假设,测试该假设,绘制此图,调整绘图参数,绘制图表,重新整形数据,绘制该图.我正在做的事情,一旦完成,可能永远不会重新运行. (6认同)
  • 在这种情况下,您应该看看 Sweave。它结合了 R 代码和 LaTeX。因此,您将分析和报告源放在一起。 (2认同)

gap*_*ppy 15

我的简明回答:

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

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


hat*_*rix 11

我一直想知道如何编写包但没有投入时间.对于我的每个迷你项目,我将所有低级函数保存在一个名为"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())))
Run Code Online (Sandbox Code Playgroud)

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

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

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

我知道这是kludgey,但避免过于正式(但如果你有机会我鼓励包系统 - 希望我将来会以这种方式迁移).

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

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

关于使用[,drop = FALSE]和< - 作为赋值运算符在useR的各种演示文稿(通常是主题演讲)中建议,还有其他"约定"!会议,但我不认为这些都是严格的(虽然[,drop = FALSE]对于你不确定你期望的输入的程序是有用的).


geo*_*try 6

算我另一个人赞成包裹.我承认在编写手册页和小插图时非常差,直到我必须(即被释放),但它是一个真正方便的方法来捆绑源母公司.另外,如果你认真对待维护你的代码,那么Dirk带来的所有观点都会进入plya.