When developing R packages, we should try to avoid directly setting dependencies on “heavy packages”. The “heaviness” for a package means, the number of additional dependency packages it brings to. If your package directly depends on a heavy package, it would bring several consequences:
In the DESCRIPTION file of your package, there are “direct dependency pakcages” listed in the
LinkingTo fields. There are also “indirect dependency packages” that can be found recursively for each of the direct dependency packages. Here what we called “dependency packages” are the union of the direct and indirect dependency packages.
There are also packages listed in
Enhances fields in DESCRIPTION file, but they are not enforced to be installed when installing your package. Of course, they also have “indirect dependency packages”. To get rid of the heavy packages that are not often used in your package, it is better to move them into the
Enhances fields and to load/install them only when they are needed.
Here the pkgndep package checks the heaviness of the dependency packages of your package. For each package listed in the
Enhances fields in the DESCRIPTION file, pkgndep checks how many additional packages your package requires. The summary of the dependency is visualized by a customized heatmap.
As an example, I am developing a package called cola which depends on a lot of other packages. The dependency heatmap looks like follows (please drag the figure to a new tab to see it in its actual size):
In the heatmap, rows are the packages listed in
Suggests fields, columns are the additional dependency packages required for each row package. The barplots on the right show the number of required package, the number of imported functions/methods/classes (parsed from NAMESPACE file) and the quantitative measure “heaviness” (the definition of heaviness will be introduced later).
We can see if all the packages are put in the
Imports field (i.e. movig all suggsted packages to
Imports), in total 248 packages are required, which are really a lot. Actually some of the heavy packages such as WGCNA, clusterProfiler and ReactomePA (the last three packages in the heatmap rows) are not very frequently used in cola, moving them to
Suggests field and using them only when they are needed greatly helps to reduce the heaviness of cola. Now the number of required packages are reduced to only 64.
To use this package:
library(pkgndep) = pkgndep("package-name") # if the package is already installed pkg plot(pkg)
= pkgndep("path-of-the-package") # if the package has not been installed yet pkg plot(pkg)
library(pkgndep) = pkgndep("ComplexHeatmap")pkg
## retrieve package database from CRAN/Bioconductor... ## - 21093 remote packages on CRAN/Bioconductor. ## - 728 packages installed locally. ## prepare dependency table... ## prepare reverse dependency table...
## ComplexHeatmap, version 2.11.1 ## 30 additional packages are required for installing 'ComplexHeatmap' ## 117 additional packages are required if installing packages listed in all fields in DESCRIPTION
pkgndep() first needs to retrieve package databases both from remote repositories and local libraries, as you can see the message from above code. This only happens once and the database is internally saved and re-used.
We can directly use
plot() function to create the dependency heatmap:
You can set the
file argument to directly save the image into a figure where the figure size is automatically calculated. Supported image formats are
plot(pkg, file = "test.png")
html_report() function can generate a html report for the dependency analysis on the package:
The heaviness of package dependency can be measured quantitatively. pkgndep provides two measures: the absolute measure and the relative measure.
The heaviness of a dependency package is calculated as follows. If package B is in the
LinkingTo fields of package A, which means, package B is directly required for package A, denote
v1 as the total number of packages for package A, and denote
v2 as the total number of required packages if moving package B to
Suggests in package A (which means, now B is not enforced to be installed for package A). The absolute measure of heaviness is simply
v1 - v2 and relative measure is
(v1 + a)/(v2 + a) where
a is a small constant, e.g. 10. So here the absolute heaviness for package B on package A is the number of additional packages that package B brings in.
In the second scenario, if package B is in the
Enhances fields of package A, now
v2 is the total number of required packages if moving package B to
Imports in package A, the absolute measure of heaviness is
v2 - v1 and relative measure is
(v2 + a)/(v1 + a).
The heaviness score can be calculated by the function
## grDevices graphics grid stats methods ## 0 0 0 0 0 ## RColorBrewer png matrixStats digest foreach ## 1 1 1 1 0 ## colorspace GlobalOptions clue doParallel GetoptLong ## 0 0 2 1 3 ## circlize IRanges dendsort jpeg tiff ## 2 4 1 1 1 ## fastcluster Cairo gridGraphics glue markdown ## 1 1 1 1 3 ## grImport gplots grImport2 knitr GenomicRanges ## 2 5 4 11 7 ## pheatmap gridtext rmarkdown testthat EnrichedHeatmap ## 11 14 18 29 12 ## dendextend ## 32
heaviness(pkg, rel = TRUE)
## grDevices graphics grid stats methods ## 1.000000 1.000000 1.000000 1.000000 1.000000 ## RColorBrewer png matrixStats digest foreach ## 1.025641 1.025641 1.025641 1.025641 1.000000 ## colorspace GlobalOptions clue doParallel GetoptLong ## 1.000000 1.000000 1.052632 1.025641 1.081081 ## circlize IRanges dendsort jpeg tiff ## 1.052632 1.111111 1.025000 1.025000 1.025000 ## fastcluster Cairo gridGraphics glue markdown ## 1.025000 1.025000 1.025000 1.025000 1.075000 ## grImport gplots grImport2 knitr GenomicRanges ## 1.050000 1.125000 1.100000 1.275000 1.175000 ## pheatmap gridtext rmarkdown testthat EnrichedHeatmap ## 1.275000 1.350000 1.450000 1.725000 1.300000 ## dendextend ## 1.800000
The package dependencies are based on “package database” which is normally retrieved by
available.packages(). In tools package, there is a
package_dependencies() function that can be used to get a list of dependency packages. In the following example code, we retrieve the dependency packages for package cola.
= available.packages(repos = BiocManager::repositories())db
system.time(p1 <- tools::package_dependencies("cola", db = db, recursive = TRUE)[])
## user system elapsed ## 0.224 0.000 0.225
In pkgndep, we implement a faster version of
package_dependencies() function. First the database needs to be reformatted by
reformat_db() function. The returned variable
db2 is a reference class object and its method
db2$package_dependencies() can be used to retrieve dependency packages.
## prepare dependency table... ## prepare reverse dependency table...
## A package database of 21790 packages. ## - 3418 Bioconductor / 18372 CRAN / 0 other packages.
system.time(p2 <- db2$package_dependencies("cola", recursive = TRUE, simplify = TRUE))
## user system elapsed ## 0.006 0.000 0.006
p2 are actually identical:
##  TRUE
pkgndep also integrates an web-based database of the dependency analysis of all pacakges on CRAN/Bioconductor. The packages of the analysis were retrieved on 2021-10-28. The database can be opened by the
Following is a screenshot of the database.