diff --git a/R/plot_predicted_curve.R b/R/plot_predicted_curve.R index 3a0d4654..d54b2cd9 100644 --- a/R/plot_predicted_curve.R +++ b/R/plot_predicted_curve.R @@ -36,6 +36,14 @@ #' @param ylab (Optional) A string for the y-axis label. If `NULL` (default), #' the label is automatically set to "ELISA units" or "ELISA units (log scale)" #' based on the `log_y` argument. +#' @param assay (Optional) A string specifying the assay type to determine the +#' y-axis label. If provided, overrides automatic label inference. +#' Supported values (case-insensitive): `"ELISA_OD"`, `"Kinetic_ELISA"`, +#' `"multiplex-bg"`. If `NULL` (default), the function attempts to infer the +#' assay type from the `dataset` (checking columns named `assay`, `assay_type`, +#' or `assay_units`). If no assay information is found, falls back to the +#' existing default label. If an unsupported assay type is provided, a warning +#' is issued and the default label is used. #' @param facet_by_id [logical]; if [TRUE], facets the plot by 'id'. #' Defaults to [TRUE] when multiple IDs are provided. #' @param ncol [integer]; number of columns for faceting. @@ -58,9 +66,67 @@ plot_predicted_curve <- function(model, alpha_samples = 0.3, xlim = NULL, ylab = NULL, + assay = NULL, facet_by_id = length(ids) > 1, ncol = NULL) { + # Helper function to get assay-specific Y-axis label + get_assay_label <- function(assay_type, log_scale = FALSE) { + # Normalize to lowercase for case-insensitive matching + assay_lower <- tolower(as.character(assay_type)) + + # Map assay types to labels + label <- switch(assay_lower, + "elisa_od" = "Optical density (OD)", + "kinetic_elisa" = "Kinetic ELISA signal", + "multiplex-bg" = "MFI (background-subtracted)", + NULL + ) + + if (is.null(label)) { + # Unsupported assay type + cli::cli_warn(c( + "Unsupported assay type: {.val {assay_type}}", + "i" = "Using default label instead.", + "i" = paste( + "Supported assay types: {.val ELISA_OD}, {.val Kinetic_ELISA},", + "{.val multiplex-bg}" + ) + )) + # Return NULL to trigger default behavior + return(NULL) + } + + # Append log scale notation if needed + if (log_scale && !is.null(label)) { + label <- paste0(label, " (log scale)") + } + + return(label) + } + + # Helper function to infer assay from dataset + infer_assay_from_dataset <- function(data) { + if (is.null(data)) { + return(NULL) + } + + # Check for common column names that might contain assay information + assay_cols <- c("assay", "assay_type", "assay_units") + + for (col in assay_cols) { + if (col %in% names(data)) { + # Get unique values (should typically be one value) + assay_values <- unique(data[[col]]) + if (length(assay_values) > 0 && !is.na(assay_values[1])) { + return(assay_values[1]) + } + } + } + + return(NULL) + } + # Filter to the subject(s) & antigen of interest: sr_model_sub <- model |> dplyr::filter( @@ -120,10 +186,24 @@ plot_predicted_curve <- function(model, # Determine Y-axis label if (is.null(ylab)) { - if (log_y) { - ylab <- "ELISA units (log scale)" - } else { - ylab <- "ELISA units" + # Try to use provided assay or infer from dataset + assay_to_use <- assay + if (is.null(assay_to_use)) { + assay_to_use <- infer_assay_from_dataset(dataset) + } + + # If we have an assay type, try to get assay-specific label + if (!is.null(assay_to_use)) { + ylab <- get_assay_label(assay_to_use, log_scale = log_y) + } + + # Fall back to default if no assay-specific label was found + if (is.null(ylab)) { + if (log_y) { + ylab <- "ELISA units (log scale)" + } else { + ylab <- "ELISA units" + } } } diff --git a/inst/examples/examples-plot_predicted_curve.R b/inst/examples/examples-plot_predicted_curve.R index 582b49c9..5efe47d3 100644 --- a/inst/examples/examples-plot_predicted_curve.R +++ b/inst/examples/examples-plot_predicted_curve.R @@ -51,3 +51,43 @@ p4 <- plot_predicted_curve( facet_by_id = TRUE ) print(p4) + +# Example with assay-specific Y-axis labels: +# Using ELISA_OD assay type +p5 <- plot_predicted_curve( + model = sees_model, + dataset = sees_data, + id = "sees_npl_128", + antigen_iso = "HlyE_IgA", + show_quantiles = TRUE, + log_y = FALSE, + show_all_curves = FALSE, + assay = "ELISA_OD" +) +print(p5) + +# Using Kinetic_ELISA assay type with log scale +p6 <- plot_predicted_curve( + model = sees_model, + dataset = sees_data, + id = "sees_npl_128", + antigen_iso = "HlyE_IgA", + show_quantiles = TRUE, + log_y = TRUE, + show_all_curves = FALSE, + assay = "Kinetic_ELISA" +) +print(p6) + +# Using multiplex-bg assay type +p7 <- plot_predicted_curve( + model = sees_model, + dataset = sees_data, + id = "sees_npl_128", + antigen_iso = "HlyE_IgA", + show_quantiles = TRUE, + log_y = FALSE, + show_all_curves = FALSE, + assay = "multiplex-bg" +) +print(p7) diff --git a/man/plot_predicted_curve.Rd b/man/plot_predicted_curve.Rd index 7e2fabde..5387eace 100644 --- a/man/plot_predicted_curve.Rd +++ b/man/plot_predicted_curve.Rd @@ -18,6 +18,7 @@ plot_predicted_curve( alpha_samples = 0.3, xlim = NULL, ylab = NULL, + assay = NULL, facet_by_id = length(ids) > 1, ncol = NULL ) @@ -67,6 +68,15 @@ limits.} the label is automatically set to "ELISA units" or "ELISA units (log scale)" based on the \code{log_y} argument.} +\item{assay}{(Optional) A string specifying the assay type to determine the +y-axis label. If provided, overrides automatic label inference. +Supported values (case-insensitive): \code{"ELISA_OD"}, \code{"Kinetic_ELISA"}, +\code{"multiplex-bg"}. If \code{NULL} (default), the function attempts to infer the +assay type from the \code{dataset} (checking columns named \code{assay}, \code{assay_type}, +or \code{assay_units}). If no assay information is found, falls back to the +existing default label. If an unsupported assay type is provided, a warning +is issued and the default label is used.} + \item{facet_by_id}{\link{logical}; if \link{TRUE}, facets the plot by 'id'. Defaults to \link{TRUE} when multiple IDs are provided.} @@ -138,4 +148,44 @@ p4 <- plot_predicted_curve( facet_by_id = TRUE ) print(p4) + +# Example with assay-specific Y-axis labels: +# Using ELISA_OD assay type +p5 <- plot_predicted_curve( + model = sees_model, + dataset = sees_data, + id = "sees_npl_128", + antigen_iso = "HlyE_IgA", + show_quantiles = TRUE, + log_y = FALSE, + show_all_curves = FALSE, + assay = "ELISA_OD" +) +print(p5) + +# Using Kinetic_ELISA assay type with log scale +p6 <- plot_predicted_curve( + model = sees_model, + dataset = sees_data, + id = "sees_npl_128", + antigen_iso = "HlyE_IgA", + show_quantiles = TRUE, + log_y = TRUE, + show_all_curves = FALSE, + assay = "Kinetic_ELISA" +) +print(p6) + +# Using multiplex-bg assay type +p7 <- plot_predicted_curve( + model = sees_model, + dataset = sees_data, + id = "sees_npl_128", + antigen_iso = "HlyE_IgA", + show_quantiles = TRUE, + log_y = FALSE, + show_all_curves = FALSE, + assay = "multiplex-bg" +) +print(p7) } diff --git a/tests/testthat/test-plot_predicted_curve.R b/tests/testthat/test-plot_predicted_curve.R index d0631294..5e459a7c 100644 --- a/tests/testthat/test-plot_predicted_curve.R +++ b/tests/testthat/test-plot_predicted_curve.R @@ -110,3 +110,199 @@ testthat::test_that( vdiffr::expect_doppelganger("predicted-curve-multi-id-4", plot_multi) } ) + +# Tests for assay-specific Y-axis labels +testthat::test_that( + "plot_predicted_curve() uses correct Y-axis label for ELISA_OD assay", + { + plot_elisa_od <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = serodynamics::nepal_sees, + show_quantiles = TRUE, + log_y = FALSE, + assay = "ELISA_OD" + ) + + # Check that the plot is a ggplot object + testthat::expect_s3_class(plot_elisa_od, "ggplot") + + # Check that the Y-axis label is correct + testthat::expect_equal(plot_elisa_od$labels$y, "Optical density (OD)") + } +) + +testthat::test_that( + "plot_predicted_curve() uses correct Y-axis label for Kinetic_ELISA assay", + { + plot_kinetic <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = serodynamics::nepal_sees, + show_quantiles = TRUE, + log_y = FALSE, + assay = "Kinetic_ELISA" + ) + + testthat::expect_s3_class(plot_kinetic, "ggplot") + testthat::expect_equal(plot_kinetic$labels$y, "Kinetic ELISA signal") + } +) + +testthat::test_that( + "plot_predicted_curve() uses correct Y-axis label for multiplex-bg assay", + { + plot_multiplex <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = serodynamics::nepal_sees, + show_quantiles = TRUE, + log_y = FALSE, + assay = "multiplex-bg" + ) + + testthat::expect_s3_class(plot_multiplex, "ggplot") + testthat::expect_equal( + plot_multiplex$labels$y, + "MFI (background-subtracted)" + ) + } +) + +testthat::test_that( + "plot_predicted_curve() adds log scale notation to assay-specific labels", + { + plot_log <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = serodynamics::nepal_sees, + show_quantiles = TRUE, + log_y = TRUE, + assay = "ELISA_OD" + ) + + testthat::expect_s3_class(plot_log, "ggplot") + testthat::expect_equal( + plot_log$labels$y, + "Optical density (OD) (log scale)" + ) + } +) + +testthat::test_that( + "plot_predicted_curve() handles case-insensitive assay types", + { + plot_lower <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = serodynamics::nepal_sees, + show_quantiles = TRUE, + log_y = FALSE, + assay = "elisa_od" # lowercase + ) + + plot_upper <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = serodynamics::nepal_sees, + show_quantiles = TRUE, + log_y = FALSE, + assay = "ELISA_OD" # uppercase + ) + + # Both should have the same label + testthat::expect_equal(plot_lower$labels$y, plot_upper$labels$y) + testthat::expect_equal(plot_lower$labels$y, "Optical density (OD)") + } +) + +testthat::test_that( + "plot_predicted_curve() warns and uses default label for unknown assay", + { + testthat::expect_warning( + plot_unknown <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = serodynamics::nepal_sees, + show_quantiles = TRUE, + log_y = FALSE, + assay = "Unknown_Assay" + ), + regexp = "Unsupported assay type" + ) + + # Should fall back to default label + testthat::expect_equal(plot_unknown$labels$y, "ELISA units") + } +) + +testthat::test_that( + "plot_predicted_curve() can infer assay from dataset with assay column", + { + # Create a modified dataset with an assay column + test_data <- serodynamics::nepal_sees + test_data$assay <- "Kinetic_ELISA" + + plot_inferred <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = test_data, + show_quantiles = TRUE, + log_y = FALSE + # Note: no assay parameter provided, should infer from dataset + ) + + testthat::expect_s3_class(plot_inferred, "ggplot") + testthat::expect_equal(plot_inferred$labels$y, "Kinetic ELISA signal") + } +) + +testthat::test_that( + "plot_predicted_curve() prioritizes explicit assay over dataset inference", + { + # Create a modified dataset with an assay column + test_data <- serodynamics::nepal_sees + test_data$assay <- "Kinetic_ELISA" + + # But explicitly specify a different assay + plot_explicit <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = test_data, + show_quantiles = TRUE, + log_y = FALSE, + assay = "ELISA_OD" # Override the dataset value + ) + + testthat::expect_s3_class(plot_explicit, "ggplot") + # Should use the explicit assay, not the one in dataset + testthat::expect_equal(plot_explicit$labels$y, "Optical density (OD)") + } +) + +testthat::test_that( + "plot_predicted_curve() uses default label when no assay info available", + { + # No assay parameter and dataset doesn't have assay columns + plot_default <- plot_predicted_curve( + model = serodynamics::nepal_sees_jags_output, + ids = "sees_npl_128", + antigen_iso = "HlyE_IgA", + dataset = serodynamics::nepal_sees, # No assay column + show_quantiles = TRUE, + log_y = FALSE + ) + + testthat::expect_s3_class(plot_default, "ggplot") + testthat::expect_equal(plot_default$labels$y, "ELISA units") + } +) diff --git a/vignettes/articles/getting-started.qmd b/vignettes/articles/getting-started.qmd index 75a55253..067cf62e 100644 --- a/vignettes/articles/getting-started.qmd +++ b/vignettes/articles/getting-started.qmd @@ -177,6 +177,20 @@ plot_predicted_curve( ) ``` +You can also customize the Y-axis label by specifying the assay type. The function supports assay-specific labels for common assay types: + +```{r} +#| label: plot-curves-assay +#| eval: false +# Example with assay-specific Y-axis label +plot_predicted_curve( + fitted_model, + ids = serocalculator::ids(simulated_data)[1], + antigen_iso = simulated_data$antigen_iso[1], + assay = "ELISA_OD" # Options: "ELISA_OD", "Kinetic_ELISA", "multiplex-bg" +) +``` + ## Post-Processing Results Extract and summarize the posterior estimates: