【问题标题】:writing functions vs. line-by-line interpretation in an R workflow在 R 工作流程中编写函数与逐行解释
【发布时间】:2011-07-18 23:14:35
【问题描述】:

这里已经写了很多关于在 R 中为统计项目开发工作流的文章。最受欢迎的工作流程似乎是Josh Reich's LCFD model。使用包含代码的main.R

source('load.R')
source('clean.R')
source('func.R')
source('do.R')

以便单个source('main.R') 运行整个项目。

问:是否有理由更喜欢这种工作流程,而不是在 load.Rclean.Rdo.R 中完成的逐行解释工作被 main.R 调用的函数替换?

我现在找不到链接,但我在某处读过 SO,当在 R 中编程时,必须克服他们希望根据函数调用编写所有内容的愿望 --- R 本来就是要编写的逐行解释形式。

问:真的吗?为什么?

我对 LCFD 方法感到沮丧,并且可能会根据函数调用编写所有内容。但在此之前,我想听听 SO 的好心人说这是否是个好主意。

编辑:我现在正在做的项目是 (1) 读取一组财务数据,(2) 清理它(非常复杂),(3) 使用我的估算器估算与数据相关的一些数量(4) 使用传统估算器估算相同数量 (5) 报告结果。我的程序应该以这样一种方式编写,即(1)针对不同的经验数据集,(2)针对模拟数据,或(3)使用不同的估计器来完成工作是轻而易举的事。此外,它应该遵循有文化的编程和可重复的研究指南,以便代码新手能够轻松运行程序、了解正在发生的事情以及如何调整它。

【问题讨论】:

  • 解释一下你在做什么工作可能会有所帮助。例如,探索性数据分析与重复处理日常数据集相比,适用于完全不同的工作流程。
  • @Sharpie:我在我的编辑中添加了我当前项目的一些背景。它重于探索,轻于探索。
  • 在某处是否有示例说明此工作流如何与函数一起使用?大多数这些文件将有多个输出变量。你只返回一个大列表吗?如果有很多大而复杂的变量,这会不会变得相当低效?
  • 我建议将项目设置为“研究纲要”,如此处 (github.com/ropensci/rrrpkg) 中的详细描述。所谓研究纲要是做可重复数据科学的天赐之物

标签: r workflow statistics


【解决方案1】:

我认为在源文件中创建的任何临时内容都不会被清理。如果我这样做:

x=matrix(runif(big^2),big,big)
z=sum(x)

并将其作为文件来源,x 会挂起,尽管我不需要它。但如果我这样做:

ff=function(big){
 x = matrix(runif(big^2),big,big)
 z=sum(x)
 return(z)
}

在我的脚本中执行 z=ff(big) 而不是 source,x 矩阵超出范围,因此被清理。

函数可以实现整洁的、可重复使用的小型封装,并且不会污染外部。一般来说,它们没有副作用。您的逐行脚本可能会使用与当前使用的数据集相关联的全局变量和名称,这使得它们无法重用。

我有时会逐行工作,但当我得到超过 5 行代码时,我就会发现我真正需要的东西变成了一个适当的可重用函数,而且我常常最终会重用它。

【讨论】:

  • 阅读您的帖子时,我一直在点头。正是我的想法。事实上,在我当前的项目中,我已经通过全局变量开发了一些不需要的依赖项,需要回去调试。不确定这种 LCFD 逐行编程范式是如何为 R 中的统计项目开发的。这似乎很容易引起误解。
  • 我怀疑它更像是人们使用 SAS 的方式,这可能更难使其更具可重用性和功能性,本质上是 1970 年代的宏语言......
  • @brianjd:对我来说,将数据输入和清理步骤与分析分开有助于防止这些依赖关系!这样做可以很容易地始终使用干净的工作区开始分析,并且不会留下输入和清理的残留物。
【解决方案2】:

我认为没有单一的答案。最好的办法是牢记相对优点,然后针对这种情况选择一种方法。

1) 函数。 不使用函数的好处是所有变量都留在工作区中,您可以在最后检查它们。如果您遇到问题,这可能会帮助您弄清楚发生了什么。

另一方面,设计良好的函数的优点是您可以对它们进行单元测试。也就是说,您可以将它们与其他代码分开进行测试,从而使它们更易于测试。此外,当您使用函数时,以某些较低级别的构造为模,您知道一个函数的结果不会影响其他函数,除非它们被传递出去,这可能会限制一个函数的错误处理可能对另一个函数造成的损害。您可以使用 R 中的 debug 工具来调试您的函数,并且能够单步执行它们是一个优势。

2) LCFD.关于是否应该使用 load/clean/func/do 的分解,无论它是通过source 还是函数完成是第二个问题。无论是通过source 还是函数完成,这种分解的问题在于您需要运行一个才能测试下一个,因此您无法真正独立地测试它们。从这个角度来看,它不是理想的结构。

另一方面,它确实有一个优势,如果您想在不同的数据上尝试它,您可以独立于其他步骤替换加载步骤,并且可以独立于加载和清理步骤替换其他步骤如果你想尝试不同的处理。

3) 没有。文件数 在您询问的内容中可能隐含第三个问题,是否所有内容都应该在一个或多个源文件中。将内容放在不同的源文件中的好处是您不必查看不相关的项目。特别是如果您有未使用的例程或与您正在查看的当前函数无关的例程,它们不会中断流程,因为您可以将它们安排在其他文件中。

另一方面,从 (a) 部署的角度来看,将所有内容放在一个文件中可能会有优势,即您可以只向某人发送该单个文件,以及 (b) 编辑方便,因为您可以将整个例如,这有助于搜索,因为您可以使用编辑器的功能搜索整个程序,因为您不必确定例程所在的文件。连续的撤消命令也允许您向后移动程序的所有单元和一次保存将保存所有模块的当前状态,因为只有一个。 (c) 速度,即如果您在慢速网络上工作,将单个文件保留在本地计算机中然后偶尔将其写出来可能会更快,而不必来回访问慢速远程。

注意:要考虑的另一件事是,相对于最初的采购文件,使用包可能更能满足您的需求。

【讨论】:

  • 非常有帮助的评论。甚至没有想过函数方法能够使用debug 设施的优势。您回答“3)”中的隐含问题是正确的。我也一直在思考这个问题。最终,我现在在编写代码时要非常小心,以便最终可以将所有内容放入一个包中。
【解决方案3】:

在编写函数时,没有人提到过一个重要的考虑因素:除非你一次又一次地重复某些动作,否则编写它们没有多大意义。在分析的某些部分,您将进行一次性操作,因此为它们编写函数没有多大意义。如果您必须重复多次,则值得投入时间和精力来编写可重用的函数。

【讨论】:

  • 相反,Spacedman 提到了另一个好处:清理临时变量。有没有办法在不使用函数的情况下做到这一点?或者至少,一种比使用不可重用函数更简洁的方法?
【解决方案4】:

工作流程:

我使用非常相似的东西:

  1. Base.r:提取原始数据,调用其他文件(第 2 到 5 项)
  2. Functions.r:加载函数
  3. Plot Options.r:加载一些我经常使用的常规绘图选项
  4. Lists.r:加载列表,我有很多列表,因为公司名称、报表等会随着时间而变化
  5. Recodes.r:大部分工作都在这个文件中完成,本质上是数据清理和排序

到目前为止,尚未进行任何分析。这仅用于数据清理和排序。

在 Recodes.r 结束时,我保存环境以重新加载到我的实际分析中。

save(list=ls(), file="Cleaned.Rdata")

清理完成、函数和绘图选项准备就绪后,我开始进行分析。同样,我继续将其分解为更小的文件,这些文件专注于主题或主题,例如:人口统计、客户请求、相关性、对应分析、情节等。我几乎总是自动运行前 5 个来设置我的环境,然后我逐行运行其他的以确保准确性和探索。

在每个文件的开头,我都会加载清理过的数据环境并繁荣昌盛。

load("Cleaned.Rdata")

对象命名法:

我不使用列表,但我确实为我的对象使用命名法。

df.YYYY # Data for a certain year
demo.describe.YYYY ## Demographic data for a certain year
po.describe ## Plot option
list.describe.YYYY ## lists
f.describe ## Functions

用友好的助记词代替上面的“describe”。

评论

我一直在努力让自己养成使用 comment(x) 的习惯,我发现它非常有用。代码中的注释很有帮助,但通常还不够。

清理

同样,在这里,我总是尝试使用相同的对象来轻松清理。例如 tmp, tmp1, tmp2, tmp3 并确保在最后删除它们。

功能

在其他帖子中有一些评论说,如果你要多次使用它,就只为某个东西编写一个函数。我想调整一下,如果你认为有可能再次使用它,你应该把它扔到一个函数中。我什至无法计算我希望为我逐行创建的进程编写函数的次数。

另外,在我更改函数之前,我再次将其放入名为 Deprecated Functions.r 的文件中,以防止“我到底是怎么做到的”效果。

【讨论】:

    【解决方案5】:

    我经常以类似的方式划分我的代码(尽管我通常将 Load 和 Clean 放在一个文件中),但我从不只获取所有文件来运行整个项目;对我来说,这违背了划分它们的目的。

    就像 Sharpie 的评论一样,我认为您的工作流程应该很大程度上取决于您正在从事的工作类型。我主要做探索性工作,在这种情况下,将数据输入(加载和清理)与分析(函数和执行)分开,这意味着我第二天回来时不必重新加载和重新清理;我可以在清理后保存数据集,然后再次导入。

    我几乎没有重复处理日常数据集的经验,但我想我会发现不同的工作流程会有所帮助;正如哈德利回答的那样,如果您只做一次事情(就像我在加载/清理数据时所做的那样),那么编写函数可能没有帮助。但是,如果你一遍又一遍地这样做(看起来你会这样做),它可能会更有帮助。

    简而言之,我发现拆分代码有助于探索性分析,但可能会为重复分析做一些不同的事情,就像您正在考虑的那样。

    【讨论】:

      【解决方案6】:

      一段时间以来,我一直在考虑工作流权衡。

      这是我为任何涉及数据分析的项目所做的:

      1. 加载和清理:为项目创建原始数据集的清理版本,就像我在构建本地关系数据库一样。因此,我尽可能以 3n 范式构建表格。我执行基本的修改,但在此步骤中我合并或过滤表;同样,我只是为给定项目创建一个规范化数据库。我将此步骤放在它自己的文件中,最后我将使用save 将对象保存到磁盘。

      2. 函数:我创建了一个函数脚本,其中包含用于数据过滤、合并和聚合任务的函数。这是工作流程中最具智力挑战性的部分,因为我不得不考虑如何创建适当的抽象以使函数可重用。这些函数需要泛化,以便我可以灵活地合并和聚合来自加载和清理步骤的数据。与 LCFD 模型一样,此脚本没有副作用,因为它只加载函数定义。

      3. 函数测试:我创建了一个单独的脚本来测试和优化步骤 2 中定义的函数的性能。我明确定义了函数的输出应该是什么,所以这一步作为一种文档(想想单元测试)。

      4. Main:我加载步骤 1 中保存的对象。如果表太大而无法放入 RAM,我可以使用 SQL 查询过滤表,与数据库思维保持一致.然后我通过调用步骤 2 中定义的函数来过滤、合并和聚合表。这些表作为参数传递给我定义的函数。函数的输出是适合绘图、建模和分析形式的数据结构。显然,我可能有一些额外的逐行步骤,在这些步骤中创建新函数几乎没有意义。

      这个工作流程让我可以在 Main.R 步骤进行闪电般的快速探索。这是因为我已经构建了清晰、可概括和优化的函数。与 LCFD 模型的主要区别在于我不执行逐行过滤、合并或聚合;我假设作为探索的一部分,我可能希望以不同的方式过滤、合并或聚合数据。此外,我不想用冗长的逐行脚本污染我的全球环境;正如 Spacedman 所指出的,函数对此有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-06-11
        • 2015-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-11
        相关资源
        最近更新 更多