diff --git a/.gitignore b/.gitignore index 95e38a9a9..76005b694 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ junit.xml **/*.quarto_ipynb *.knit.md +tests/vdiffr.Rout.fail diff --git a/DESCRIPTION b/DESCRIPTION index f85dbda03..2932c5995 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: serocalculator Title: Estimating Infection Rates from Serological Data -Version: 1.4.0.9003 +Version: 1.4.0.9004 Authors@R: c( person("Kristina", "Lai", , "kwlai@ucdavis.edu", role = c("aut", "cre")), person("Chris", "Orwa", role = "aut"), @@ -27,10 +27,11 @@ Imports: dplyr, foreach, ggplot2, - ggpubr, lifecycle, magrittr, + patchwork, Rcpp, + readr, rlang, rngtools, scales, @@ -53,7 +54,6 @@ Suggests: knitr, mixtools, pak, - readr, quarto, rmarkdown, spelling, diff --git a/NEWS.md b/NEWS.md index 20e004582..fb5bb901b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # serocalculator (development version) +## Bug fixes + +* `load_noise_params()` and `load_sr_params()` now fail gracefully with informative messages when internet resources are unavailable, complying with CRAN policy (#505) + +* Replaced `ggpubr` with `patchwork` for arranging panel plots in `autoplot.seroincidence.by()` and `graph_seroresponse_model_1()`, removing the indirect `ggrepel` dependency that required R >= 4.5.0 (#507) + # serocalculator 1.4.0 ## New features diff --git a/R/autoplot.seroincidence.by.R b/R/autoplot.seroincidence.by.R index da0cbc2cc..a7004a686 100644 --- a/R/autoplot.seroincidence.by.R +++ b/R/autoplot.seroincidence.by.R @@ -4,7 +4,7 @@ #' @param object a '"seroincidence.by"' object (from [est_seroincidence_by()]) #' @param ncol number of columns to use for panel of plots #' @inheritDotParams autoplot.seroincidence -#' @return a `"ggarrange"` object: a single or [list()] of [ggplot2::ggplot()]s +#' @return a `"patchwork"` object: a single or [list()] of [ggplot2::ggplot()]s #' @export #' @examples #'\donttest{ @@ -41,14 +41,19 @@ autoplot.seroincidence.by <- function( ncol = min(3, length(object)), ...) { if (length(object) == 0) { - stop("The input doesn't contain any fits. Did subsetting go wrong?") + cli::cli_abort( + "The input doesn't contain any fits. Did subsetting go wrong?" + ) } if (!attr(object, "graphs_included")) { - stop( - "Graphs cannot be extracted; ", - "`build_graph` was not `TRUE` in the call to `est_seroincidence_by()`" - ) + cli::cli_abort(c( + "Graphs cannot be extracted.", + "i" = paste0( + "`build_graph` was not `TRUE` in the call to", + " `est_seroincidence_by()`" + ) + )) figure <- NULL } @@ -63,7 +68,7 @@ autoplot.seroincidence.by <- function( nrow <- ceiling(length(figs) / ncol) figure <- do.call( what = function(...) { - ggpubr::ggarrange( + patchwork::wrap_plots( ..., ncol = ncol, nrow = nrow diff --git a/R/graph_seroresponse_model_1.R b/R/graph_seroresponse_model_1.R index e81049b70..e01cbca4a 100644 --- a/R/graph_seroresponse_model_1.R +++ b/R/graph_seroresponse_model_1.R @@ -59,7 +59,7 @@ graph_seroresponse_model_1 <- function( nrow <- ceiling(length(figs) / ncol) figure <- do.call( what = function(...) { - ggpubr::ggarrange( + patchwork::wrap_plots( ..., ncol = ncol, nrow = nrow diff --git a/R/load_noise_params.R b/R/load_noise_params.R index 116e80e10..507f8f2cf 100644 --- a/R/load_noise_params.R +++ b/R/load_noise_params.R @@ -11,18 +11,55 @@ #' with extra attribute `antigen_isos`) #' @export #' @examples -#' noise <- load_noise_params(serocalculator_example("example_noise_params.rds")) +#' noise <- load_noise_params( +#' serocalculator_example("example_noise_params.rds") +#' ) #' print(noise) #' load_noise_params <- function(file_path, antigen_isos = NULL) { - if (file_path %>% substr(1, 4) == "http") { + is_url <- file_path |> substr(1, 4) == "http" + + if (is_url) { file_path <- url(file_path) } - noise <- - file_path %>% - readRDS() %>% - as_noise_params() + noise <- tryCatch( + { + data <- if (is_url) { + withCallingHandlers( + readr::read_rds(file_path), + warning = function(w) { + invokeRestart("muffleWarning") + } + ) + } else { + readr::read_rds(file_path) + } + + as_noise_params(data, antigen_isos = antigen_isos) + }, + error = function(e) { + if (is_url) { + cli::cli_abort( + class = "internet_resource_unavailable", + message = c( + "Unable to load noise parameters from internet resource.", + "x" = paste( + "The resource at {.url {summary(file_path)$description}}", + "is not available or has changed." + ), + "i" = paste( + "Please check your internet connection", + "and verify the URL is correct." + ), + "i" = "Original error: {e$message}" + ) + ) + } else { + rlang::cnd_signal(e) + } + } + ) return(noise) } diff --git a/R/load_sr_params.R b/R/load_sr_params.R index 0d6439bf3..70cca875a 100644 --- a/R/load_sr_params.R +++ b/R/load_sr_params.R @@ -14,14 +14,49 @@ #' print(curve) #' load_sr_params <- function(file_path, antigen_isos = NULL) { - if (file_path |> substr(1, 4) == "http") { + is_url <- file_path |> substr(1, 4) == "http" + + if (is_url) { file_path <- url(file_path) } - curve_params <- - file_path |> - readRDS() |> - as_sr_params() + curve_params <- tryCatch( + { + data <- if (is_url) { + withCallingHandlers( + readr::read_rds(file_path), + warning = function(w) { + invokeRestart("muffleWarning") + } + ) + } else { + readr::read_rds(file_path) + } + + as_sr_params(data, antigen_isos = antigen_isos) + }, + error = function(e) { + if (is_url) { + cli::cli_abort( + class = "internet_resource_unavailable", + message = c( + "Unable to load seroresponse parameters from internet resource.", + "x" = paste( + "The resource at {.url {summary(file_path)$description}}", + "is not available or has changed." + ), + "i" = paste( + "Please check your internet connection", + "and verify the URL is correct." + ), + "i" = "Original error: {e$message}" + ) + ) + } else { + rlang::cnd_signal(e) + } + } + ) return(curve_params) } diff --git a/man/autoplot.seroincidence.by.Rd b/man/autoplot.seroincidence.by.Rd index a4540441a..b97eba450 100644 --- a/man/autoplot.seroincidence.by.Rd +++ b/man/autoplot.seroincidence.by.Rd @@ -19,7 +19,7 @@ or linear scale (\code{FALSE}, default)?} }} } \value{ -a \code{"ggarrange"} object: a single or \code{\link[=list]{list()}} of \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}}s +a \code{"patchwork"} object: a single or \code{\link[=list]{list()}} of \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}}s } \description{ Plots log-likelihood curves by stratum, for \code{seroincidence.by} objects diff --git a/man/load_noise_params.Rd b/man/load_noise_params.Rd index d0d68780d..bc8e82876 100644 --- a/man/load_noise_params.Rd +++ b/man/load_noise_params.Rd @@ -23,7 +23,9 @@ with extra attribute \code{antigen_isos}) Load noise parameters } \examples{ -noise <- load_noise_params(serocalculator_example("example_noise_params.rds")) +noise <- load_noise_params( + serocalculator_example("example_noise_params.rds") +) print(noise) } diff --git a/tests/testthat/_snaps/autoplot.seroincidence.by/seroinc-plot.svg b/tests/testthat/_snaps/autoplot.seroincidence.by/seroinc-plot.svg index f41658d6e..5db27ec6f 100644 --- a/tests/testthat/_snaps/autoplot.seroincidence.by/seroinc-plot.svg +++ b/tests/testthat/_snaps/autoplot.seroincidence.by/seroinc-plot.svg @@ -18,149 +18,160 @@ + - - + + - - + - - + + - - - - - - - - - - - - - - - - - - - - - - + + --400 --350 --300 - - - - - - - - -0.00 -0.25 -0.50 -0.75 -1.00 -incidence rate (events per person:year) -log(likelihood) - - - - - - - -HlyE_IgG + HlyE_IgA -est.incidence -lambda_start -Stratum 1 - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + --450 --400 --350 --300 --250 - - - - - - - - - - -0.00 -0.25 -0.50 -0.75 -1.00 -incidence rate (events per person:year) -log(likelihood) - - - - - - - -HlyE_IgG + HlyE_IgA -est.incidence -lambda_start -Stratum 2 +-400 +-350 +-300 + + + + + + + + +0.00 +0.25 +0.50 +0.75 +1.00 +incidence rate (events per person:year) +log(likelihood) + + + + + + + +HlyE_IgG + HlyE_IgA +est.incidence +lambda_start +Stratum 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-450 +-400 +-350 +-300 +-250 + + + + + + + + + + +0.00 +0.25 +0.50 +0.75 +1.00 +incidence rate (events per person:year) +log(likelihood) + + + + + + + +HlyE_IgG + HlyE_IgA +est.incidence +lambda_start +Stratum 2 +seroinc-plot diff --git a/tests/testthat/test-load_noise_params.R b/tests/testthat/test-load_noise_params.R new file mode 100644 index 000000000..394a53b74 --- /dev/null +++ b/tests/testthat/test-load_noise_params.R @@ -0,0 +1,35 @@ +test_that( + desc = "`load_noise_params()` produces expected results", + code = { + expect_no_error( + noise_params_true <- + load_noise_params( + serocalculator_example("example_noise_params.rds") + ) + ) + expect_s3_class(noise_params_true, "noise_params") + } +) + +test_that( + desc = "non-URL file error is re-thrown", + code = { + err <- tryCatch( + load_noise_params("nonexistent_file.rds"), + error = function(e) e + ) + + expect_true(inherits(err, "error")) + } +) + +test_that( + desc = "unavailable internet resource produces informative error", + code = { + expect_error( + load_noise_params("https://ucdserg.ucdavis.edu/nofile.rds"), + class = "internet_resource_unavailable", + regexp = "Unable to load noise parameters from internet resource" + ) + } +) diff --git a/tests/testthat/test-load_sr_params.R b/tests/testthat/test-load_sr_params.R index 323d05931..55b2882d0 100644 --- a/tests/testthat/test-load_sr_params.R +++ b/tests/testthat/test-load_sr_params.R @@ -26,9 +26,43 @@ test_that( desc = "non filepath produces error", code = { expect_error( - expect_warning( - load_sr_params("non file path") + load_sr_params("non file path") + ) + } +) + +test_that( + desc = "non-URL file error is re-thrown", + code = { + err <- tryCatch( + load_sr_params("nonexistent_file.rds"), + error = function(e) e + ) + + expect_true(inherits(err, "error")) + } +) + +test_that( + desc = "unavailable internet resource produces informative error", + code = { + expect_error( + load_sr_params("https://ucdserg.ucdavis.edu/nofile.rds"), + class = "internet_resource_unavailable", + regexp = "Unable to load seroresponse parameters from internet resource" + ) + } +) + +test_that( + desc = "deprecated load_curve_params() still works", + code = { + result <- suppressWarnings( + load_curve_params( + serocalculator_example("example_curve_params.rds") ) ) + + expect_s3_class(result, "curve_params") } )