识别R函数和脚本的依赖关系

时间:2012-01-06 17:27:29

标签: r dependencies code-analysis

我正在筛选使用该程序包的程序包和脚本,并希望识别外部依赖项。目标是修改脚本以指定library(pkgName)并修改包中的函数以使用require(pkgName),以便稍后这些依赖关系更加明显。

我正在修改代码以考虑每个外部相关的包。作为一个例子,虽然它绝不是决定性的,但我现在发现很难识别依赖于data.table的代码。我可以使用data.tableMatrixggplot2bigmemory或许多其他软件包替换plyr,因此请随意回答基于其他软件包的示例。

这种搜索并不是特别容易。到目前为止我尝试过的方法包括:

  • 搜索libraryrequire语句
  • 的代码
  • 搜索data.table(例如library(data.table)
  • 的提及
  • 尝试运行codetools::checkUsage以确定可能存在某些问题的位置。对于脚本,我的程序将脚本插入到本地函数中,并将checkUsage应用于该函数。否则,我使用checkUsagePackage作为包。
  • 查找对data.table有些独特的陈述,例如:=
  • 查找可通过匈牙利表示法识别对象类的位置,例如DT

我的搜索精髓是找到:

  • 加载data.table
  • 名称表明它们是data.table个对象的对象,
  • 看似data.table的具体方法

唯一容易的部分似乎是找到加载包的位置。不幸的是,并非所有函数都可以显式加载或需要外部包 - 这些可能假设它已经被加载。这是一个不好的做法,我正在努力解决它。但是,搜索对象和方法似乎具有挑战性。

这个(data.table)只是一个包,其中一个包含似乎有限且有些独特的用法。假设我想查找ggplot函数的用法,其中选项更广泛,并且语法文本不是特殊的(即+的频繁使用不是特殊的,而:=似乎定)。

我不认为静态分析会给出完美的答案,例如:可以将参数传递给函数,该函数指定要加载的包。尽管如此:是否有任何核心工具或软件包可以通过静态或动态分析改进这种强力方法?

对于它的价值,tools::pkgDepends仅解决包级别的依赖关系,而不是函数或脚本级别,这是我正在处理的级别。


更新1:应该工作的动态分析工具的一个示例是报告在代码执行期间加载哪些包的工具。我不知道R中是否存在这样的功能 - 就像Rprof报告search()的输出而不是代码堆栈。

1 个答案:

答案 0 :(得分:20)

首先,感谢@ mathematical.coffee让我走上使用Mark Bravington的mvbutils包的道路。 foodweb功能非常令人满意。

总结一下,我想知道关于检查一个软件包,比如说myPackage与另一个软件包,比如说externalPackage,以及关于检查externalPackage的脚本。我将演示如何做每一个。在这种情况下,外部包是data.table

1:对于myPackagedata.table,以下命令就足够了:

library(mvbutils)
library(myPackage)
library(data.table)
ixWhere <- match(c("myPackage","data.table"), search())
foodweb(where = ixWhere, prune = ls("package:data.table"), descendents = FALSE)

这会产生一个很好的图表,显示哪些函数依赖于data.table中的函数。虽然图表包含data.table内的依赖项,但它并不过分繁琐:我可以轻松地看到我的哪些函数依赖data.table,以及它们使用哪些函数,例如as.data.table,{{1 }},data.table:=等等。在这一点上,可以说包解决依赖问题已经解决,但是key提供了更多,所以让我们看一下。很酷的部分是依赖矩阵。

foodweb

这很酷:它现在显示了我的包中函数的依赖关系,其中我使用了详细的名称,例如: depMat <- foodweb(where = ixWhere, prune = ls("package:data.table"), descendents = FALSE, plotting = FALSE) ix_sel <- grep("^myPackage.",rownames(depMat)) depMat <- depMat[ix_sel,] depMat <- depMat[,-ix_sel] ix_drop <- which(colSums(depMat) == 0) depMat <- depMat[,-ix_drop] ix_drop <- which(rowSums(depMat) == 0) depMat <- depMat[-ix_drop,] ,关于功能不是 在我的包中,即myPackage.cleanData中的函数,它消除了没有依赖项的行和列。这很简洁,让我快速调查依赖关系,我也可以通过处理data.table轻松找到我的函数的补充集。

注意:rownames(depMat)似乎无法阻止创建绘图设备,至少第一次在一系列调用中调用plotting = FALSE。这很烦人,但并不可怕。也许我做错了什么。

2:对于脚本与foodweb,这会更有趣。对于每个脚本,我需要创建一个临时函数,然后检查依赖项。我在下面有一个小功能就是这样。

data.table

现在,我只需要查看listFiles <- dir(pattern = "myScript*.r") checkScriptDependencies <- function(fname){ require(mvbutils) rawCode <- readLines(fname) toParse <- paste("localFunc <- function(){", paste(rawCode, sep = "\n", collapse = "\n"), "}", sep = "\n", collapse = "") newFunc <- eval(parse(text = toParse)) ix <- match("data.table",search()) vecPrune <- c("localFunc", ls("package:data.table")) tmpRes <- foodweb(where = c(environment(),ix), prune = vecPrune, plotting = FALSE) tmpMat <- tmpRes$funmat tmpVec <- tmpMat["localFunc",] return(tmpVec) } listDeps <- list() for(selFile in listFiles){ listDeps[[selFile]] <- checkScriptDependencies(selFile) } ,我就可以从上面的depMat获得同样精彩的小见解。我从我编写的其他代码中修改了listDeps,这些代码发送的脚本由checkScriptDependencies进行分析;有一个像这样的小功能来分析独立代码是很好的。感谢@Spacedman@Tommy使用codetools::checkUsage改进了对foodweb的调用的见解。

(真正的匈牙利人会注意到我与名称和类型的顺序不一致 - tooBad。:)这有更长的原因,但这不正是我正在使用的代码,无论如何。)


虽然我没有为我的代码发布environment()生成的图表的图片,但您可以在http://web.archive.org/web/20120413190726/http://www.sigmafield.org/2010/09/21/r-function-of-the-day-foodweb看到一些不错的示例。在我的例子中,它的输出肯定捕获了data.table对foodweb:=的使用,以及标准的命名函数,如Jkey。它似乎可以避免我的文本搜索,并且在几个方面有所改进(例如找到我忽略的功能)。

总而言之,as.data.table是一个很好的工具,我鼓励其他人探索foodweb包以及Mark Bravington的其他一些不错的包,例如mvbutils。如果您确实安装了debug,只需查看mvbutils,如果您认为只有您在管理不断发展的R代码时才会遇到困难。 :)