如何组织大型R程序?

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

我可以采用哪些做法,以便我的代码总是很乐意与之合作? 我正在考虑像这样的事情

  • 在源文件中放置函数
  • 何时将某些东西分解到另一个源文件
  • 主文件应该是什么
  • 使用函数作为组织单位(鉴于R使得难以访问全局状态,这是否值得)
  • 缩进/换行实践。
    • 对待(像{?
    • 把一些东西)} 1或2行?

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

标准答案是使用包 – 请参阅写作R扩展手册以及网上的不同教程。

它给你

  • 一个准自动的方式来组织你的代码的主题
  • 强烈鼓励你写一个帮助文件,让你思考界面
  • 通过R CMD check检查进行了很多健康检查
  • 一个添加回归testing的机会
  • 以及命名空间的手段。

只要在代码上运行source()就可以实现简短的代码片段。 其他所有东西都应该放在一个包里 – 即使你不打算发布它,因为你可以为内部存储库编写内部包。

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

更新2008年8月13日:大卫·史密斯刚刚博客的谷歌R风格指南 。

我喜欢把不同的function放在自己的文件中。

但是我不喜欢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的系统,其中每个文件都是一个模块。 这样做会更好,但是这似乎还行。

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

我不知道这是不是你的情况,但是当我在R工作的时候,我很less从一个大的复杂的程序开始。 我通常从一个脚本开始,将代码分成逻辑上可分的单元,通常使用函数。 数据操作和可视化代码被放置在他们自己的function等等。而这样的function被分组在一起的文件的一部分(数据操作在顶部,然后可视化等)。 最终,你想考虑如何让你更容易维护你的脚本,并降低缺陷率。

如何使您的function变得很好/粗糙,并且有各种经验法则:例如15行代码,或者“一个函数应该负责执行一个由它的名字标识的任务”,等等。 。 由于R不支持通过引用来调用,所以当通过传递数据框架或类似的结构时,我通常会使函数变得太细。 但是当我刚开始使用R时,这可能是对一些愚蠢的性能错误的过度补偿。

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

如果你打算部署你的function,那么你需要开始考虑软件包。 我不会将R代码部署到生产环境中,或者由于各种原因而被其他人重复使用(简单地说:org文化倾向于其他语言,关注性能,GPL等)。 另外,我倾向于不断地改进和添加我的源文件集合,而当我进行更改时,我宁愿不处理包。 所以你应该看看其他与包相关的答案,比如Dirk's,了解更多关于这方面的细节。

最后,我认为你的问题对于R来说不一定是特别的。我真的会推荐阅读Steve McConnell的Code Complete,它包含了许多关于这些问题和编码实践的知识。

我同意德克的build议! 恕我直言,组织你的程序从简单的脚本到文件化的软件包,对于R编程,就像从Word切换到TeX / LaTeX来编写。 我build议看看Friedrich Leisch编写的非常有用的创buildR包:教程

我简洁的回答:

  1. 仔细写下你的function,确定足够的输出和input;
  2. 限制使用全局variables;
  3. 使用S3对象和适当的S4对象;
  4. 把函数放在包里,特别是当你的函数调用C / Fortran的时候。

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

我一直在想如何编写软件包,但没有投入时间。 对于我的每个小型项目,我将所有低级函数都保存在一个名为“functions /”的文件夹中,并将它们源代码放入我明确创build的独立名称空间中。

下面几行代码将在searchpath中创build一个名为“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()))) 

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

这是我知道的kludgey,但避免必须过于正式(但如果你有机会,我鼓励包系统 – 希望我将在未来这样的迁移)。

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

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

关于使用[,drop = FALSE]和< – 还有其他的“约定”,作为赋值运算符在各种演示(通常是主题) 会议,但我不认为这些是严格的(尽pipe[,drop = FALSE]对于你不确定你所期望的input的程序是否有用)。

指望我是另一个赞成包的人。 我会承认,在写手册页和短片之前,我必须很穷,直到我必须(即被释放)之前,这样才能真正方便地捆绑源文件。 此外,如果你认真的维护你的代码,那么Dirk提出的观点都会进入Plya。

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

至于进入全球的环境,对于运营商而言,这是很容易的,尽pipe这是令人沮丧的。

由于还没有学会如何编写软件包,我总是通过采购子脚本来组织。 它类似于写作课,但不涉及。 它不是编程上的优雅,但我发现我build立分析随着时间的推移。 一旦我有一个大的部分工作,我经常把它移动到一个不同的脚本,只是源,因为它将使用工作区对象。 也许我需要从几个来源导入数据,对所有这些数据进行sorting并find交集。 我可能会把这部分放到一个额外的脚本中。 但是,如果你想为其他人分发你的“应用程序”,或者它使用一些交互式input,一个包可能是一个很好的路线。 作为研究人员,我很less需要分发我的分析代码,但是我经常需要对其进行扩充或调整。

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