Identificación de dependencias de funciones R y scripts

Estoy revisando un paquete y scripts que utilizan el paquete y me gustaría identificar dependencias externas. El objective es modificar los scripts para especificar la 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.

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 u otros muchos 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 intentado hasta ahora incluyen:

  • Busque el código de la library y require declaraciones
  • Buscar menciones de data.table (por ejemplo, library(data.table) )
  • Intenta ejecutar codetools::checkUsage para determinar dónde puede haber algunos problemas. Para los scripts, mi progtwig 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 por ejemplo := .
  • Busque dónde se pueden identificar las clases de objetos 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

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 syntax no es tan idiosincrásico (es decir, el uso frecuente de + no es idiosincrásico, mientras que := parece ser).

No creo que el análisis estático dé una respuesta perfecta, por ejemplo, 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 dynamic?

Por lo que vale, tools::pkgDepends solo trata las dependencias en el nivel del 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 una herramienta de análisis dynamic que debería funcionar es aquella que informa qué paquetes se cargan durante la ejecución del código. No sé si existe tal capacidad en R, sin embargo, sería como si Rprof informara el resultado de search() lugar de la stack de códigos.

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

Para recapitular, quería saber acerca de cómo verificar un paquete, decir myPackage versus otro, decir externalPackage , y sobre verificar scripts contra el paquete externalPackage . Demostraré cómo hacer cada uno. En este caso, el paquete externo es data.table .

1: para myPackage versus data.table , los siguientes comandos son suficientes:

 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 un excelente gráfico que muestra qué funciones dependen de las 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 genial: ahora muestra dependencias de funciones en mi paquete, donde estoy usando nombres detallados, por ejemplo myPackage.cleanData , en funciones que no están en mi paquete, concretamente funciones en data.table , y elimina filas y columnas donde hay sin 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 no parece impedir la creación de un dispositivo de trazado, al menos la primera vez que se llama a foodweb en una secuencia de llamadas. Eso es molesto, pero no terrible. Tal vez estoy haciendo algo mal.

2: Para scripts versus data.table , esto se vuelve 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, solo necesito mirar listDeps , y tengo el mismo tipo de ideas maravillosas que tengo de depMat. checkScriptDependencies las checkScriptDependencies de checkScriptDependencies de 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 por las ideas que mejoraron la llamada a foodweb , usando environment() .

(Los verdaderos húngaros notarán que fui inconsistente con el orden de nombre y 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 foodweb imágenes de los gráficos producidos por foodweb para mi código, puede ver algunos buenos ejemplos en http://web.archive.org/web/20120413190726/http://www.sigmafield.org/2010/ 09/21 / r-función-del-día-foodweb . En mi caso, su salida definitivamente captura el uso de data.table de := y J , junto con las funciones nombradas 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 herramienta excelente, y animo a otros a explorar el paquete mvbutils y algunos de los otros buenos paquetes de Mark Bravington, como la debug . Si instalas mvbutils , solo revisa los mvbutils ?changed.funs si crees que solo tienes problemas para gestionar el código R en evolución. 🙂

    Intereting Posts