Выявление зависимостей функций R и скриптов - PullRequest
24 голосов
/ 06 января 2012

Я просеиваю пакет и сценарии, которые его используют, и хотел бы определить внешние зависимости. Цель состоит в том, чтобы изменить сценарии для указания library(pkgName) и изменить функции в пакете для использования require(pkgName), чтобы эти зависимости стали более очевидными позже.

Я пересматриваю код для учета каждого внешне зависимого пакета. В качестве примера, хотя это ни в коем случае не является окончательным, мне сейчас трудно определить код, который зависит от data.table. Я мог бы заменить data.table на Matrix, ggplot2, bigmemory, plyr или многими другими пакетами, поэтому не стесняйтесь отвечать примерами, основанными на других пакетах.

Этот поиск не особенно прост. Подходы, которые я пробовал до сих пор, включают:

  • Поиск кода для операторов library и require
  • Поиск упоминаний 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 Ответ

20 голосов
/ 08 января 2012

Во-первых, благодаря @ математическому .coffee я понял, как использовать пакет mvbutils Марка Бравингтона.Функция foodweb более чем удовлетворительна.

Напомним, что я хотел узнать о проверке одного пакета, скажем myPackage по сравнению с другим, скажем externalPackage, и о проверке сценариев на соответствие externalPackage,Я покажу, как сделать каждый.В этом случае внешний пакет имеет вид data.table.

1: для myPackage против data.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, 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).

NB: plotting = FALSE, кажется, не мешает построению графикаустройство из создаваемого, по крайней мере, в первый раз, когда foodweb вызывается в последовательности вызовов.Это раздражает, но не страшно.Возможно, я делаю что-то не так.

2: Для сценариев вместо 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)
}

Теперь мне просто нужно взглянуть на listDeps, и у меня есть такие же замечательные маленькие идеи, которые я получил от depMat выше.Я изменил checkScriptDependencies из другого написанного мной кода, который отправляет скрипты для анализа codetools::checkUsage;хорошо иметь такую ​​небольшую функцию для анализа автономного кода.Престижность @ Spacedman и @ Tommy за понимание, которое улучшило вызов до foodweb, используя environment().

(Истинные HungaRians заметят, что я был непоследователенс порядком имени и типа - tooBad. :) Для этого есть более длинная причина, но в любом случае это не совсем тот код, который я использую.)


Хотя я не выкладывализображения графиков, созданных foodweb для моего кода, вы можете увидеть несколько хороших примеров на http://web.archive.org/web/20120413190726/http://www.sigmafield.org/2010/09/21/r-function-of-the-day-foodweb. В моем случае его вывод определенно отражает использование data.table := и J вместе сстандартные именованные функции, такие как key и as.data.table.Кажется, это устраняет мой текстовый поиск и является улучшением в нескольких отношениях (например, поиск функций, которые я упустил).

В общем, foodweb - отличный инструмент, и я призываю других изучитьmvbutils пакет и некоторые другие приятные пакеты Марка Брэвингтона, такие как debug.Если вы устанавливаете mvbutils, просто посмотрите ?changed.funs, если считаете, что только вы боретесь с управлением развивающимся R-кодом.:)

...