Подтвердить что ты не робот

Идентификация зависимостей R-функций и скриптов

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

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

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

  • Найдите код для операторов library и require
  • Поиск упоминаний data.table (например, library(data.table))
  • Попробуйте запустить codetools::checkUsage, чтобы определить, где могут быть некоторые проблемы. Для скриптов моя программа вставляет script в локальную функцию и применяет к этой функции checkUsage. В противном случае я использую checkUsagePackage для пакета.
  • Найдите выражения, которые несколько уникальны для data.table, например :=.
  • Ищите, где классы объектов могут быть идентифицированы с помощью венгерской нотации, например DT

Суть моего поиска - найти:

  • загрузка data.table,
  • объекты с именами, указывающими, что они являются data.table объектами,
  • которые выглядят как data.table -специфические

Единственная легкая часть этого, похоже, заключается в поиске места загрузки пакета. К сожалению, не все функции могут явно загружать или требовать внешний пакет - они могут считать, что он уже загружен. Это плохая практика, и я пытаюсь ее исправить. Однако поиск объектов и методов кажется сложным.

Это (data.table) - это всего лишь один пакет, и один с тем, что кажется ограниченным и несколько уникальным. Предположим, что я хотел искать возможности использования функций ggplot, где параметры более обширны, а текст синтаксиса не является таким же своеобразным (т.е. Частое использование + не является особенным, а := похоже).

Я не думаю, что статический анализ даст прекрасный ответ, например. можно передать аргумент функции, которая указывает пакет для загрузки. Тем не менее: существуют ли какие-либо основные инструменты или пакеты, которые могут улучшить этот подход с использованием грубой силы, либо посредством статического или динамического анализа?

Для того, что стоит, tools::pkgDepends учитывает только зависимости на уровне пакета, а не уровень функции или script, на котором я работаю.


Обновление 1: Примером инструмента для динамического анализа, который должен работать, является тот, который сообщает, какие пакеты загружаются во время выполнения кода. Я не знаю, существует ли такая возможность в R, но это будет похоже на Rprof сообщение о выходе search() вместо стека кода.

4b9b3361

Ответ 1

Во-первых, благодаря @Mathematical.coffee, чтобы поставить меня на путь использования пакета Mark Bravington 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 это становится немного интереснее. Для каждого script мне нужно создать временную функцию, а затем проверить зависимости. У меня есть небольшая функция ниже, которая делает именно это.

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; хорошо иметь такую ​​небольшую функцию для анализа автономного кода. Kudos @Spacedman и @Tommy для понимания, который улучшил вызов foodweb, используя environment().

(True 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 и некоторые другие приятные пакеты Mark Bravington, такие как debug. Если вы установите mvbutils, просто проверьте ?changed.funs, если считаете, что только вы боретесь с управлением эволюционирующим кодом R.:)