pkginspector::vis_package()It is often useful for package reviewers or developers to understand the relationships among functions within a package: the chains through which tasks are executed. Network diagrams aid in visualizing these relationships; in fact it is not uncommon for developers to sketch the package structure as they build out the functions. Reviewers benefit from network diagrams of completed packages as they offer a bird’s-eye view of the structure, and help identify questions that may lead to insight about the package. A network diagram of our package, pkginspector, reveals some interesting features:
devtools::load_all("~/pkginspector")
igraph_obj <- create_package_igraph("~/pkginspector")
plot(igraph_obj)
Why do functions exist that do not call other package functions? (We call these “orphan” functions.)
Several functions call create_package_igraph, which is a time-consuming function. Are there redundencies?
Would it make sense to consolidate the functions that rev_fn_summary calls in order to simplify the package structure?
As useful as they are, network diagrams are notoriously hard to draw. Even diagrams with relatively small number of nodes become complex and unreadable very quickly. Layout algorithms based on physical simulations of the attraction and repulsion of nodes with common edges help create an organized structure, but do not provide a one-size-fits-all solution. Plotting parameters that work well for one network may not be choices for another.
For these reasons, the clarity of network diagrams can be enhanced by the possibility of interacting with the layout. Interaction might take the form of moving nodes to alternate locations, highlighting nodes of interest, choosing which functions (nodes) to show, and/or choosing layout algorithm parameters.
pkginspector::vis_package() is designed to facilitate this type of interaction. To create the visual, it calls the visNetwork package, an R implementation of the JavaScript vis.js library (the same package used by drake).
At the moment, networks may be missing some edges. We hope to fix this soon.
The following examples demonstrate key options.
Hovering or clicking on a node (function) in the network will highlight all functions that depend on the seleted function (any degree) by greying out all other functions. This is generally the most powerful way to interact with the network, as it allows us to focus on one function at a time, rather than the entire tangle of nodes. (For large packages there may be a delay in response after hovering… be patient!)
physics is a logical that controls whether the network layout is recalculated after moving a node. With physics set to FALSE, the user has complete control of the nodes without concern that they will jump around to undesired locations:
vis_package("~/pkginspector", physics = FALSE)
With physics set to TRUE, the network readjusts after each tweak. This provides less control to the user but in general more sensible layouts. For example, if a node with many edges is moved, the connected nodes will move with it:
vis_package("~/pkginspector", physics = TRUE)
centralGravitycentralGravity is a measure that controls how tightly nodes are pulled into the center of the network. Higher numbers draw the nodes closer to the center; the default is .3. See here for more physics options that may be useful to include.
In practice, lowering the centralGravity measure has the effect of moving the representation in the direction of physics=FALSE, that is, one that offers the user more control over positioning. (After moving the node, we want the network to react but not overreact; this is a difficult effect to achieve, since the algorithm does not “know” exactly what we want to happen. This is the main challenge.)
vis_package("~/pkginspector", physics = TRUE, centralGravity = .05)
externalexternal is a logical that controls whether calls to external functions are included or not:
vis_package("~/pkginspector", external = TRUE)
iconsThe default is use font awesome icons, as in the examples above. with icons = FALSE, icons will be replaced by standard shapes:
vis_package("~/pkginspector", icons = FALSE)
We use functionMap::map_r_package() to determine function dependencies. It is not picking up functions called inside purrr::map functions. If you find other problems, let us know here.
For the physics=FALSE option, we use igraph to create the layout rather than visNetwork. This allows us to begin with a sensible layout and then alter it. (physics=FALSE with visNetwork alone produces a random arrangement of nodes.) igraph also helps speed up the visualization.
For the physics=TRUE option, we use visNetwork to calculate the layout, mainly so we can set centralGravity – an option that does not appear to be available in igraph.
Currently, vis_package() checks if an igraph object is stored in data-raw and loads it if available. If not, it creates the igraph object and then stores it for future use. If you wish to create a new igraph object regardless, for example, after making changes to a package, use overwrite = TRUE. (This is a temporary solution – would love to hear ideas about how a community collection of package igraph objects could be created to save time in drawing graphs.)
Source code for vis_package(), which uses visNetwork to draw the network, is here
Feedback welcome! jtr13@columbia.edu
library(magrittr)
df <- rev_fn_summary("~/pkginspector") %>%
dplyr::arrange(desc(dependents))
DT::datatable(df)