library(riskmetric)
library(dplyr)
pkgs <- c("admiral", "rtables", "tern", "gtsummary", "survival", "renv", "xportr")
assessment <- pkg_ref(pkgs) |>
pkg_assess() |>
pkg_score()
assessment |>
select(package, version, pkg_score, has_vignettes, has_website,
covr_coverage, downloads_1yr, news_current, bugs_status) |>
arrange(desc(pkg_score))Qualifying R Packages for Clinical Use with riskmetric and renv
- Package qualification answers one question per package: is the evidence of quality sufficient for this intended use? Write the criteria first, then score.
riskmetriccomputes standardised metrics (documentation, tests, maintenance, adoption) and a composite score; the score informs the decision, it does not make it.- Operational qualification means running the package’s own tests in your environment and filing the log, not reading the CRAN check page.
renvpins the approved set. A change to the lockfile is a change-controlled event that triggers re-qualification of what changed.
The R ecosystem has twenty thousand packages and no central certification. That is exactly why a sponsor’s QA group asks how you choose and control them. The answer is a short procedure and a folder of evidence. Here is the procedure we deliver as part of an R validation plan.
Step 1: acceptance criteria
Write them before looking at any package. A workable set:
| Criterion | Threshold | Evidence |
|---|---|---|
| Source | CRAN, Bioconductor or approved internal repository | Repository record |
| Documentation | All exported functions documented; vignette or website | riskmetric doc metrics |
| Testing | Test suite present; coverage reported or test count substantial | riskmetric test metrics; OQ log |
| Maintenance | Release or commit within 24 months; bug tracker active | riskmetric maintenance metrics |
| Adoption | Meaningful download counts or multi-sponsor use | riskmetric community metrics |
| Licence | Compatible with intended use | DESCRIPTION |
| Reverse dependency risk | Dependencies themselves approved or assessed | Dependency tree |
Tiers help: core packages used in every analysis (base, stats, survival, admiral, rtables, gtsummary) get the full assessment and OQ; supporting packages (string handling, file IO) get the criteria check; convenience packages used only interactively are not qualified and not allowed in production code.
Step 2: score with riskmetric
riskmetric reports metrics such as whether a vignette and website exist, test coverage when available, downloads in the last year, whether the NEWS file is current, and the proportion of recent bug reports closed, then combines them into pkg_score. The composite is a convenience. The decision record should cite the individual metrics against the criteria in Step 1, because that is what an auditor can check.
The R Validation Hub, a cross-industry group under the R Consortium, maintains riskmetric and publishes a white paper describing this risk-based approach. Citing it in your SOP is appropriate and expected.
Step 3: operational qualification
Install the approved versions, then run each core package’s own tests in your environment:
res <- tools::testInstalledPackage("survival", types = "tests", outDir = "oq/survival")
res # 0L means all tests passed; logs land in oq/survival/Note that tools::testInstalledPackage() requires the package to have been installed with its tests (install.packages(..., INSTALL_opts = "--install-tests")); set that in the qualification build. For packages that use testthat, testthat::test_package() works after the same install option. Base R’s own suite runs with tools::testInstalledBasic("both").
File the logs with the R version, platform and the renv lockfile hash. This is the IQ/OQ record. It takes an afternoon the first time and is scripted thereafter.
Step 4: pin with renv
renv::init(bare = TRUE)
install.packages(pkgs) # approved versions from the approved repository
renv::snapshot() # writes renv.lockThe lockfile is the definition of the qualified environment. Three rules make it work in a regulated setting:
- Repositories are fixed. Point
renvat a dated snapshot (Posit Package Manager or an internal mirror) sorenv::restore()is reproducible years later. - Changes are controlled. Updating a package means a change request, a re-run of Steps 2-3 for the package and its changed dependencies, a new lockfile and a new environment version number.
- Production runs record the hash.
tools::md5sum("renv.lock")andsessionInfo()go into every output log.
Step 5: the decision record
One row per package per version:
| Package | Version | Tier | Criteria met | Score | OQ result | Decision | Approver | Date |
|---|---|---|---|---|---|---|---|---|
| admiral | 1.x | core | all | 0.xx | pass | approved | QA lead | 2026-10-08 |
Where a criterion is not met and the package is still needed, the decision says so and names the compensating control: “coverage not reported; all outputs depending on this package are dual-programmed.” That is a legitimate decision. Silence is not.
What about packages you wrote?
Internal migration packages (see from SAS macros to R functions) go through the same table. They will score low on adoption and high on tests if you built them properly. The compensating control is the dual-programming evidence they were built from. Keep them under version control, give them a NEWS file, and run R CMD check in CI so the evidence is generated, not assembled.
How often to re-qualify
- When the lockfile changes (that package and its changed dependencies).
- When R is upgraded (full OQ).
- Annually as a review of the decision records, even without changes, because maintenance status changes.
The procedure above fits on two pages of an SOP and produces a folder a reviewer can open. Together with the validation plan and the traceability matrix, it is the documentation layer of the SAS to R roadmap.