确保R环境中的重现性

时间:2010-11-03 23:01:30

标签: r

我在一个计算生物学实验室工作,在那里我们有几个人从事多个项目,主要是在R(这是我关心的这篇文章)。在过去,人们只需为每个项目开发代码,这些代码可能涉及也可能不涉及从先前项目复制的样板代码。多年来我推动的一件事就是为这个混乱带来一些集中式结构,并让人们识别出常见的模式,这样我们就可以将这些重复/共同的代码块转换成包,因为人们可能会认为这是很多原因。一件好事。所以现在我们的人们在他们的项目特定脚本中使用了集中的包/例程。

这里有一个问题。我们拥有权力的授权,即每个项目的每个脚本都需要尽可能100%可重复(这包括我们可以直接访问的所有代码的100%,包括我们的包)。也就是说,如果我在包条中使用参数A调用函数foo来获取结果X,那么4年后我应该得到完全相同的结果。 (由于错误导致的错误输出在这里除外)

再现性的主题现在已经出现在各个圈内的R中,但通常似乎在过程的再现性(例如,晕影)方面进行讨论。这不是一回事 - 我今天可以运行一个小插图,然后在6个月后运行相同的代码,使用更新的软件包并获得截然不同的结果。

已经达成一致的解决方案(我不是其中的粉丝)是,如果需要在非向后兼容的更改中更改功能或包,则只需获取新名称。因此,如果我们需要从根本上改变函数foo(),它将被称为foo2(),如果需要彻底改变,它将被称为foo3()。这可以确保调用foo()的任何脚本始终获得原始结果,同时允许事物在包存储库中前进。它有效,但我真的不喜欢这个 - 它看起来在美学上非常混乱,我担心随着时间的推移它会导致包裹bar,bar2,bar3,bar4 ...函数foo1,foo2,foo3等等。

问题在于我没有提出一个更好的替代解决方案。一种可能性是注意软件包的版本号,R等,并确保加载它们,但这有多个问题 - 其中最重要的是它依赖于正确的软件包版本规则并且容易出错。此外,这种替代方案已被拒绝;)理想情况下,我们所拥有的是某种发展概念。释放,因为大多数这些变化往往更早发生,然后平稳发生变化的频率更低。 OTOH devel在这里真正意味着“实际上并不是在一个包中”(我们这样做),但很难确切地确定在什么时候运送东西是正确的。总而言之,当你认为自己是安全的时候,那就是你意识到自己不安全的时候。

因此,考虑到这一切,我很好奇是否有其他人处理过类似情况,以及他们如何解决问题。

编辑:只是为了清楚,通过非向后兼容,我不只是讨论API等,而是输出一组给定的输入。

9 个答案:

答案 0 :(得分:20)

这确实是一个重要的考虑事项,我认为最终需要将几个不同的过程制度化。

  1. 版本控制(svn,git,bzr,cvs等)
  2. 单元测试
  3. 我的第一反应是你需要将某种代码管理系统制度化。这将使它更容易,因为旧版本的foo()仍然可用,如果你真的想要它。根据您所说的,听起来您需要打包常用功能并制定某种发布计划。需要向后兼容的脚本必须包含软件包名称和发布信息。通过这种方式,可以始终像编写脚本时那样获得foo()。你还应该确保人们只在他们的工作中使用官方发布版本,否则这可能会变得非常痛苦。

    我同意,有一个foo的集合:foo99注定要失败。但至少它将是一个光荣迷茫的失败。除了美学,它将驱动你所有的疯狂。如果foo2()是foo()的改进(更准确,更快等),那么它应该被称为foo()并根据您公司范围的发布计划发布使用。如果它做了不同的事情,它就不再是foo()了。它可能是fooo()或superFoo()或fooMe(),但它不是foo()。

    最后,您需要开始测试您的功能。 (单元测试)对于发布并可供其他人使用的每个功能,您应该有一个明确定义的测试套件。除非有人修复了foo()中的错误,否则结果应该保持不变。如果有人修复了错误,那么结果应该更准确,并且在大多数情况下可能更合适。如果确实需要重现旧的,不正确的结果,可以从版本控制系统中挖掘出旧版本的foo()。通过严格的单元测试,您将知道foo的结果是否/何时发生了变化。这些知识应该有助于最小化您需要的foo()函数的数量。每次有人调整某些内容时,您可以测试新版本以查看结果是否符合预期,而不是创建版本。但是,这很棘手,因为你必须确保你的测试涵盖了该功能可能会看到的任何内容,包括奇怪的边缘情况。在研究环境中,我认为这可能成为一种挑战。

答案 1 :(得分:8)

我不确定是否将其与R集成,但Sumatra可能值得研究。它似乎允许您跟踪代码和结果。因此,如果您需要从4年前重新运行该模拟,那么代码就应该存在。

答案 2 :(得分:5)

好吧,问问自己如何用其他语言做到这一点。除了好书记我真的没什么可怕的:

  • 记录所涉及的所有软件的版本号
  • 将代码放在可管理的块中,比如在包中。
  • 确保所有涉及的软件/软件包在5年内仍然可用。

R可轻松实现便携,包括所有已安装的软件包。将可移植版本的R与所使用的软件包,代码和数据保存在CD-ROM上进行每次分析,您确定可以随时重现。好的,你错过了操作系统,但不能全部拥有它们。在任何情况下,如果操作系统的重要性足以使分析无法重现,那么很可能是您的分析问题。您不想告诉任何人您的结果取决于您使用的Windows版本,对吗?

PS:请进入人们的头脑,他们永远不应该在他们的生活中复制粘贴代码。他们应该将它包装在函数中并使用它们。更容易,更容易出错。我的意思是,复制

之间有什么区别
x <- read.table("sometable")
y <- ColSums(x)/4.3

并调整值或键入

myfun <- function(i,j){
  x <- read.table(i)
  y <- ColSums(x)/j
}

为您和许多其他人节省了大量的复制粘贴麻烦。 (怎么样,找不到物体?什么物体?)

答案 3 :(得分:5)

每当您想要以“永久”可再现的方式冻结代码时,例如,当您的论文发布时,最安全的方法是创建一个包含所有代码和数据的虚拟机,运行它所需的软件(包括操作系统)。 University of Washington site上有一个示例。

答案 4 :(得分:3)

这正是导致Microsoft在Excel中维护bug兼容性的一种思维方式。而不是试图遵循这样的要求,你应该尽力证明这不是一个好主意。

这种想法意味着所有错误都是错误,以保持一致性。它的想法是从公司官僚机构转移而来,在科学实验室没有业务。

执行此操作的唯一方法是使用您的代码保存所有包的副本和R的版本。没有中央公司会对错误的兼容性感到满意,因为它会为你解决这个问题。

答案 5 :(得分:3)

如果操作系统发生变化导致结果发生变化,该怎么办?也许Microsoft修复了Windows XP for Windows 7中的一个错误,然后升级时 - 所有输出都不同。

如果您想要处理这个问题,那么我认为最好的工作方式是在关闭分析时保留虚拟机的快照,并存储VM映像供以后使用。当然,在五年的时间里,你将没有运行Windows XP的许可证,这是另一个问题 - 通过使用开源操作系统(如Linux)解决了这个问题。

答案 6 :(得分:2)

我会选择码头图片 这是重现操作系统和所有依赖项的非常方便的方法 您构建了一个映像,以后可以随时将其部署到docker,它将完全配置 您可以找到多个R泊坞窗图像,因此您可以轻松地在其上构建图像 已经构建了映像,您可以使用它来部署到测试环境,然后再部署到生产。

答案 7 :(得分:1)

这可能是一个迟到的答案,但我发现创建一个像下面这样的通用包装器很有用,特别是在我开发新函数时快速迭代时:

myFunction <- function(..., version = "latest"){
  if((version == "latest") || (version == 6)){
    return(myFunction06(...))
  } ...
  if((version == 1)){
    return(myFunction01(...))
  }
 }

然后,代码应该简单地说明它想要的版本。一旦实际功能稳定,我删除了对旧版本功能的支持,快速搜索我的代码让我找到任何违规的电话。使用“最新”意味着我可以确保调用者和函数匹配一些相当固定的定义。

当然,所有代码都在版本控制系统中维护,所以即使我删除了之前的代码,它也只能来自当前可用的源代码。只要可以获得该时间点的数据,我就可以从任何时间点重现任何行为,包括错误。

答案 8 :(得分:1)