如何确定你在R中使用了哪些包

49

我有一个非常长的R脚本,其中包含许多if语句和异常情况。在编写脚本时,我一边导入和测试库,一边进行操作,并没有很好地记录它们。问题是,如果我从干净的安装中运行此脚本,我不确定脚本将运行哪些语句,因此需要哪些库。

我的问题是:是否有任何R函数可以测试脚本中正在使用的库?

编辑:我并没有使用已安装的所有库,因此print(sessionInfo())不会有用,我只想使用install.packages函数启动脚本


这是你要找的内容:https://dev59.com/iGox5IYBdhLWcg3wOx5i - Ethaan
4
@Ethaan,那并不是他真正想问的。 - nico
我认为你正在寻找脚本作者的剖腹/绝育工具。 我认为你注定要运行脚本并逐步安装软件包,弄清楚哪个函数来自哪个软件包。祝你在不同软件包中使用相同名称函数时好运(这就是工具派上用场的地方)。我发现library("sos");findFn("foo")很方便查找函数。 - Roman Luštrik
1
@Ethaan 不用担心,它实际上也可以是一个有用的链接! - nico
我认为eh21的答案应该被接受。 - Matteo
7个回答

28

3
为什么这个被踩了?为什么这不是首选项呢? - LCM
4
请注意:您需要先加载软件包,否则 NCmisc 将不知道该函数来自哪个软件包。 - RobertMyles
2
如果您正在使用RStudio并希望使用它来检查您打开的脚本,请运行list.functions.in.file(rstudioapi::getSourceEditorContext()$path, alphabetic = TRUE) - Matthew Law
这种方法的一个问题是它实际上并没有注意包加载的顺序,因此当实际脚本中的函数来自特定包时,它会显示函数来自多个包。你知道有什么更好的替代方案吗? - Bryan Shalloway
我编写了一个名为{funspotr}的程序包,它本质上执行的是list.functions.in.file,但以数据框格式输出结果,并进行了一些其他小改动:https://github.com/brshallo/funspotr - Bryan Shalloway
如果您的代码文件中包含西班牙语或其他罕见字符,则“list.functions.in.file”无法正常工作。 - Captain Tyler

16

如今,'renv'软件包通过renv::dependencies提供了一个强大的解决方案。

renv::dependencies通过正确的静态分析,可靠地找到软件包依赖关系,即使它们以非标准的方式声明(例如通过box::use)或通过软件包DESCRIPTION文件而不是通过library::


作为一个快速的hack,在“renv”之前,我曾经使用一个shell脚本来进行此操作:

#!/usr/bin/env bash

source_files=($(git ls-files '*.R'))
grep -hE '\b(require|library)\([\.a-zA-Z0-9]*\)' "${source_files[@]}" | \
    sed '/^[[:space:]]*#/d' | \
    sed -E 's/.*\(([\.a-zA-Z0-9]*)\).*/\1/' | \
    sort -uf \
    > DEPENDS

使用Git收集项目中所有受版本控制的R文件。由于您应该始终使用版本控制,因此这通常是一个好的解决方案(尽管您可能需要调整版本控制系统)。对于少数不受版本控制的项目,您应该(1)将其纳入版本控制。或者,如果失败了,(2)使用find . -regex '.*\.[rR]'而不是git ls-files '*.R'

它会生成一个DEPENDS文件,其中包含非常简单的依赖关系列表。

但是要注意它只查找直接调用libraryrequire,如果您将这些调用包装起来,则脚本将无法正常工作。


9
我认为OP并不是在问这个,但我可能误解了问题。我认为他的问题是:他已经加载了几个库,不确定哪些是不必要的。 - nico
1
如果您使用[\.a-zA-Z0-9]而不是\w[[:alnum:]],则可以捕获所有有效的R软件包名称。 - calder-ty
这太棒了。我们能否以某种方式将其纳入 usethis 中?(此外,它目前无法处理未附加但通过 ::::: 访问的要求,http://adv-r.had.co.nz/Expressions.html#ast-funs 可能是更一般的、基于 R 的实现的良好起点)不过,我想我应该使用 roxygen... - jan-glx
1
@jan-glx 说实话,我今天不会使用这个代码片段。如果我必须自己实现它,我会进行适当的静态分析,可能基于“codetools”包。话虽如此,“renv”已经实现了这一点,并且做得更好,因为它还支持非标准的声明软件包依赖项的方式(例如通过box::use)。我已更新我的答案以反映这一点。 - Konrad Rudolph

6

根据大家的反应,特别是eh21提出的NCmisc包的建议,我编写了一个小函数,输出目录中所有R脚本使用的软件包以及它们的频率。

library(NCmisc)
library(stringr)
library(dplyr)

checkPacks<-function(path){

    ## get all R files in your directory
    ## by the way, extract R code from Rmd: http://felixfan.github.io/extract-r-code/
    files<-list.files(path)[str_detect(list.files(path), ".R$")]

    ## extract all functions and which package they are from 
    ## using NCmisc::list.functions.in.file
    funs<-unlist(lapply(paste0(path, "/", files), list.functions.in.file))
    packs<-funs %>% names()

    ## "character" functions such as reactive objects in Shiny
    characters<-packs[str_detect(packs, "^character")]

    ## user defined functions in the global environment
    globals<-packs[str_detect(packs, "^.GlobalEnv")]

    ## functions that are in multiple packages' namespaces 
    multipackages<-packs[str_detect(packs, ", ")]

    ## get just the unique package names from multipackages
    mpackages<-multipackages %>%
               str_extract_all(., "[a-zA-Z0-9]+") %>%
               unlist() %>%
               unique()
    mpackages<-mpackages[!mpackages %in% c("c", "package")]

    ## functions that are from single packages
    packages<-packs[str_detect(packs, "package:") & !packs %in% multipackages] %>%
              str_replace(., "[0-9]+$", "") %>%
              str_replace(., "package:", "") 

    ## unique packages
    packages_u<-packages %>%
                unique() %>%
                union(., mpackages)

    return(list(packs=packages_u, tb=table(packages)))

}

checkPacks("~/your/path")

好的工作很好,但它只检查已加载的库。解决方案是按照此博客文章中所述加载所有已安装的软件包:https://www.r-bloggers.com/loading-all-installed-r-packages/ 简而言之:lapply(.packages(all.available = TRUE), function(xx) library(xx, character.only = TRUE)) - Sebastian Müller
不错,但你应该让第一个list.files调用中的正则表达式为".R$|.r$",这样就可以使用带有.r扩展名的文件了(比如我 - 在与编程相关的文件夹中从不使用大写字母)。 - emilBeBri
你也可以使用正则表达式的模式参数来代替字符串,像这样: - emilBeBri
文件 <- list.files(路径, 模式='.R$|.r$') - emilBeBri

5

我不确定有一个好的自动化方法...但你可以这样做:

  1. Open a new R console
  2. Check with sessionInfo that you don't have extra packages loaded.
    You could check this using sessionInfo. If you, by default, load extra packages (e.g. using your .RProfile file) I suggest you avoid doing that, as it's a recipe for disaster.
    Normally you should only have the base packages loaded: stats, graphics, grDevices, utils, datasets, methods, and base.

    You can unload any extra libraries using:

    detach("package:<packageName>", unload=TRUE)
    
  3. Now run the script after commenting all of the library and require calls and see which functions give an error.

  4. To get which package is required by each function type in the console:

    ??<functionName>
    
  5. Load the required packages and re-run steps 3-5 until satisfied.


是的,这基本上就是我目前正在做的事情。我可能最终会在开头放置几个语句,安装我当前已下载的所有软件包 - 即使它们没有用处。 - aeongrail

2

您可能希望查看 Revolution Analytics 在 GitHub 上的 checkpoint 功能,链接为:https://github.com/RevolutionAnalytics/checkpoint

该功能可以解决一些问题,并确保可重复性。但是我认为它无法报告您正在使用的列表。

不过,如果您查看代码,可能会得到一些想法。


2
我在将代码转换为包时,有一个类似的需求,因此我需要识别每个包依赖项并导入或使用完全限定名称。
在阅读书籍《扩展R》时,我发现XRtools :: makeImports可以扫描软件包并找到所有需要导入的软件包。但这还不能解决我们的问题,因为它仅适用于现有软件包,但它提供了如何解决问题的主要见解。
我制作了一个函数并将其放入我的软件包mischelper中。您可以安装该软件包,使用RStudio addin菜单扫描当前文件或选定的代码,或使用命令行函数。每个外部函数(fun_inside)和调用它的函数(usage)都将列在表格中。
现在,您可以转到每个函数,按F1查找它属于哪个软件包。实际上,我还有另一个软件包,可以扫描所有已安装的软件包以获取函数名称并构建数据库,但对于此用法可能会导致更多的误报,因为如果您只加载了一些软件包,则按F1只搜索已加载的软件包。
请参阅我的软件包页面上的用法详细信息。
https://github.com/dracodoc/mischelper

1

对于识别软件包依赖项,我最信任基于{renv} 的解决方案。

虽然我写了一个名为funspotr的软件包,它具有与提到 NCmisc::list.functions.in.file() 相似的功能,并可用于解析文件或文件中的函数或软件包:

library(dplyr)

funspotr::spot_pkgs("https://gist.githubusercontent.com/brshallo/4b8c81bc1283a9c28876f38a7ad7c517/raw/b399b768e900a381d99f5120e44d119c7fb40ab9/source_rmd.R")
#> [1] "knitr"    "magrittr" "stringr"  "readr"    "purrr"    "glue"

funspotr::spot_funs("https://gist.githubusercontent.com/brshallo/4b8c81bc1283a9c28876f38a7ad7c517/raw/b399b768e900a381d99f5120e44d119c7fb40ab9/source_rmd.R") %>% 
  select(-in_multiple_pkgs)
#> # A tibble: 13 x 2
#>    funs        pkgs   
#>    <chr>       <chr>  
#>  1 tempfile    base   
#>  2 purl        knitr  
#>  3 getOption   base   
#>  4 options     base   
#>  5 .Call       base   
#>  6 source      base   
#>  7 library     base   
#>  8 read_file   readr  
#>  9 map         purrr  
#> 10 str_extract stringr
#> 11 glue        glue   
#> 12 str_c       stringr
#> 13 write_file  readr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接