2012-01-06 14 views
19

Estoy revisando un paquete y scripts que utilizan el paquete y me gustaría identificar dependencias externas. El objetivo es modificar los scripts para especificar library(pkgName) y modificar las funciones en el paquete para usar require(pkgName), de modo que estas dependencias serán más obvias más adelante.Identificación de dependencias de funciones R y scripts

Estoy revisando el código para dar cuenta de cada paquete dependiente externamente. Como ejemplo, aunque de ninguna manera es definitivo, ahora me resulta difícil identificar el código que depende de data.table. Podría reemplazar data.table con Matrix, ggplot2, bigmemory, plyr o muchos otros paquetes, así que no dude en responder con ejemplos basados ​​en otros paquetes.

Esta búsqueda no es particularmente fácil. Los enfoques que he probado hasta ahora incluyen:

  • de búsqueda de códigos para library y require declaraciones
  • Buscar menciones de data.table (por ejemplo library(data.table))
  • Intente ejecutar codetools::checkUsage para determinar dónde puede haber algunos problemas. Para los scripts, mi programa inserta el script en una función local y aplica checkUsage a esa función. De lo contrario, uso checkUsagePackage para el paquete.
  • Busque declaraciones que sean un tanto exclusivas de data.table, como :=.
  • Busque donde las clases de objetos pueden ser identificados a través de la notación húngara, como DT

La esencia de mi búsqueda es encontrar:

  • carga de data.table,
  • objetos con nombres que indican que son objetos data.table,
  • métodos que parecen ser data.table -específicos

La única parte fácil de esto parece ser encontrar dónde se carga el paquete. Desafortunadamente, no todas las funciones pueden cargar o requerir explícitamente el paquete externo; pueden suponer que ya se ha cargado. Esta es una mala práctica, y estoy tratando de arreglarlo. Sin embargo, buscar objetos y métodos parece ser un desafío.

Esto (data.table) es solo un paquete, y uno con un uso que parece limitado y algo único. Supongamos que quisiera buscar usos de funciones ggplot, donde las opciones son más extensas, y el texto de la sintaxis no es tan idiosincrásico (es decir, el uso frecuente de + no es idiosincrásico, mientras que := parece serlo).

No creo que el análisis estático dé una respuesta perfecta, p. uno podría pasar un argumento a una función, que especifica un paquete que se cargará. No obstante, ¿hay herramientas o paquetes básicos que puedan mejorar este enfoque de fuerza bruta, ya sea mediante análisis estático o dinámico?

Por lo que vale, tools::pkgDepends solo trata las dependencias a nivel de paquete, no la función o el nivel de script, que es el nivel en el que estoy trabajando.


Actualización 1: Un ejemplo de herramienta de análisis dinámico que debería funcionar es aquella que informa qué paquetes se cargan durante la ejecución del código. No sé si existe esa capacidad en R, sin embargo, sería como Rprof informando la salida de search() en lugar de la pila de códigos.

+3

¿Podría probar 'foodweb' en el paquete' mvbutils'? No tengo experiencia con él pero me parece prometedor (excepto que no sé qué tan profundo lo busca). Algo como 'foodweb (where = 'paquete: data.table', prune = 'function_youre_examining')'? –

+0

@ mathematical.coffee Eso es muy intrigante. Parece que sería bastante útil dentro del paquete; Todavía no tengo claro qué puede hacer entre paquetes, pero voy a darle un giro, ¡gracias! – Iterator

+0

@ mathematical.coffee Esto es muy interesante. Me estoy ocupando un poco más. ¿Puedes publicar tu comentario como respuesta? Puedo ayudar a editarlo en una solución, suponiendo que pueda hacerlo funcionar. Anteriormente comenté que no parecía funcionar en todos los paquetes, pero eso no es correcto. El argumento 'where' parece ser el truco para administrar el espacio de búsqueda. Por cierto, la ayuda para los genes cambiados. Lee casi como escribiría sobre algunas de mis experiencias recientes. :) – Iterator

Respuesta

13

En primer lugar, gracias a @ mathematical.coffee para ponerme en el camino de usar el paquete mvbutils de Mark Bravington. La función foodweb es más que satisfactoria.

Para recapitular, lo que quería saber acerca sobre la comprobación de un solo paquete, por ejemplo myPackage frente a otro, por ejemplo externalPackage, y sobre la comprobación de secuencias de comandos en el externalPackage. Demostraré cómo hacer cada uno. En este caso, el paquete externo es data.table.

1: Para myPackage frente data.table, los siguientes comandos suficiente:

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

Esto produce una excelente gráfico que muestra que las funciones dependen de funciones en data.table. Aunque el gráfico incluye dependencias dentro de data.table, no es excesivamente oneroso: puedo ver fácilmente cuáles de mis funciones dependen de data.table, y qué funciones utilizan, como as.data.table, data.table, :=, key, y así sucesivamente. En este punto, uno podría decir que el problema de dependencia del paquete está resuelto, pero foodweb ofrece mucho más, así que echemos un vistazo a eso. La parte buena es la matriz de dependencia.

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,] 

Esto es muy bueno: ahora muestra las dependencias de funciones en el paquete, donde estoy usando nombres detallados, por ejemplo, myPackage.cleanData, en las funciones no en mi paquete, es decir, funciona en data.table, y elimina las filas y columnas donde no hay dependencias. Esto es conciso, me permite estudiar dependencias rápidamente, y puedo encontrar el conjunto complementario para mis funciones con bastante facilidad, también, procesando rownames(depMat).

NB: plotting = FALSE parece que no impide la creación de un dispositivo de trazado, al menos la primera vez que se llama al foodweb en una secuencia de llamadas. Eso es molesto, pero no terrible. Tal vez estoy haciendo algo mal.

2: Para las secuencias de comandos en comparación con data.table, esto se pone un poco más interesante. Para cada script, necesito crear una función temporal y luego verificar las dependencias. Tengo una pequeña función a continuación que hace precisamente eso.

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) 
} 

Ahora, sólo tiene que mirar a listDeps, y tengo el mismo tipo de pequeñas ideas maravillosas que tengo desde el depMat anteriormente. Modifiqué checkScriptDependencies desde otro código que escribí que envía scripts para ser analizados por codetools::checkUsage; es bueno tener una pequeña función como esta para analizar el código independiente. Felicitaciones a @Spacedman y @Tommy para obtener información que mejoró la llamada al foodweb, utilizando environment().

(True hungaRians notará que fui inconsistente con el orden del nombre y el tipo - tooBad. :) Hay una razón más larga para esto, pero este no es precisamente el código que estoy usando, de todos modos.)


Aunque no he colgado fotos de los gráficos producidos por foodweb de mi código, se pueden ver algunos ejemplos agradables al http://web.archive.org/web/20120413190726/http://www.sigmafield.org/2010/09/21/r-function-of-the-day-foodweb. En mi caso, su salida definitivamente captura el uso de data.table de := y J, junto con las funciones con nombre estándar, como key y as.data.table. Parece obviar mis búsquedas de texto y es una mejora de varias maneras (por ejemplo, encontrar funciones que había pasado por alto).

Con todo, foodweb es una excelente herramienta, y animo a otros a explorar el paquete mvbutils y algunos de los otros paquetes agradables de Mark Bravington, como debug. Si instala mvbutils, simplemente consulte ?changed.funs si cree que solo le cuesta gestionar el código R en evolución. :)

Cuestiones relacionadas