diff --git a/DESCRIPTION b/DESCRIPTION index a337023c..463c91ba 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -62,6 +62,7 @@ Imports: tidyr, tools, utils, + units, rlang, yaml, zip diff --git a/NAMESPACE b/NAMESPACE index 42d7eec8..4030400e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -18,6 +18,7 @@ export(g_pkconc_ind_log) export(general_lineplot) export(general_meanplot) export(geometric_mean) +export(get_conversion_factor) export(get_label) export(has_label) export(lambda_slope_plot) @@ -122,6 +123,7 @@ importFrom(tern,g_ipp) importFrom(tidyr,pivot_longer) importFrom(tidyr,pivot_wider) importFrom(tools,file_ext) +importFrom(units,set_units) importFrom(utils,read.csv) importFrom(utils,write.csv) importFrom(yaml,read_yaml) diff --git a/R/calculate_summary_stats.R b/R/calculate_summary_stats.R index 57e1a89b..3e168feb 100644 --- a/R/calculate_summary_stats.R +++ b/R/calculate_summary_stats.R @@ -61,15 +61,15 @@ calculate_summary_stats <- function(res_pknca, input_groups = "DOSNO") { group_by(across(all_of(c(input_groups, "PPTESTCD")))) %>% unique() %>% summarise( - geomean = exp(mean(log(PPORRES), na.rm = TRUE)), # nolint - geocv = (sd(PPORRES, na.rm = TRUE) / exp(mean(log(PPORRES), na.rm = TRUE))) * 100, - mean = mean(PPORRES, na.rm = TRUE), - CV = (sd(PPORRES, na.rm = TRUE) / mean(PPORRES, na.rm = TRUE)) * 100, - sd = sd(PPORRES, na.rm = TRUE), - min = min(PPORRES, na.rm = TRUE), - max = max(PPORRES, na.rm = TRUE), - median = median(PPORRES, na.rm = TRUE), - count.missing = sum(is.na(PPORRES)), + geomean = exp(mean(log(PPSTRES), na.rm = TRUE)), + geocv = (sd(PPSTRES, na.rm = TRUE) / exp(mean(log(PPSTRES), na.rm = TRUE))) * 100, + mean = mean(PPSTRES, na.rm = TRUE), + CV = (sd(PPSTRES, na.rm = TRUE) / mean(PPSTRES, na.rm = TRUE)) * 100, + sd = sd(PPSTRES, na.rm = TRUE), + min = min(PPSTRES, na.rm = TRUE), + max = max(PPSTRES, na.rm = TRUE), + median = median(PPSTRES, na.rm = TRUE), + count.missing = sum(is.na(PPSTRES)), count.total = n() ) %>% ungroup() %>% diff --git a/R/export_cdisc.R b/R/export_cdisc.R index 583b0b5f..e1b8f9df 100644 --- a/R/export_cdisc.R +++ b/R/export_cdisc.R @@ -128,7 +128,7 @@ export_cdisc <- function(res_nca) { unname(unlist(res_nca$data$conc$columns$groups)), "start", "end", "PPTESTCD" ))) ) %>% - arrange(USUBJID, DOSNO, !is.na(PPORRES)) %>% + arrange(USUBJID, DOSNO, !is.na(PPSTRES)) %>% # Identify all dulicates (fromlast and fromfirst) and keep only the first one filter(!duplicated(paste0(USUBJID, DOSNO, PPTESTCD))) %>% ungroup() %>% @@ -166,14 +166,14 @@ export_cdisc <- function(res_nca) { # Specific ID variables PPSPID = "TBD", # TODO Results in Standard Units if ORRESU is not in standard units - PPSTRESN = as.numeric(PPORRES), - PPSTRESC = as.character(PPORRES), - PPSTRESU = PPORRESU, + PPSTRESN = as.numeric(PPSTRES), + PPSTRESC = as.character(PPSTRES), + PPSTRESU = PPSTRESU, # Status and Reason for Exclusion - PPSTAT = ifelse(is.na(PPORRES) | (PPORRES == 0 & PPTESTCD == "CMAX"), "NOT DONE", ""), + PPSTAT = ifelse(is.na(PPSTRES) | (PPSTRES == 0 & PPTESTCD == "CMAX"), "NOT DONE", ""), PPREASND = case_when( !is.na(exclude) ~ exclude, - is.na(PPORRES) ~ "Unspecified", + is.na(PPSTRES) ~ "Unspecified", TRUE ~ "" ), # Datetime diff --git a/R/flexible_violinboxplot.R b/R/flexible_violinboxplot.R index 2ff12ca2..d8864453 100644 --- a/R/flexible_violinboxplot.R +++ b/R/flexible_violinboxplot.R @@ -61,12 +61,12 @@ flexible_violinboxplot <- function(boxplotdata, # ylabel of violin/boxplot ylabel <- { - if (box_data$PPORRESU[1] == "unitless" || - is.na(box_data$PPORRESU[1]) || - is.null(box_data$PPORRESU)) { + if (box_data$PPSTRESU[1] == "unitless" || + is.na(box_data$PPSTRESU[1]) || + is.null(box_data$PPSTRESU)) { parameter } else { - paste(parameter, " [", box_data$PPORRESU[1], "]") + paste(parameter, " [", box_data$PPSTRESU[1], "]") } } @@ -75,7 +75,7 @@ flexible_violinboxplot <- function(boxplotdata, data = box_data %>% arrange(!!!syms(colorvars)), aes( x = interaction(!!!syms(xvars), sep = "\n"), - y = PPORRES, + y = PPSTRES, color = interaction(!!!syms(colorvars)) ) ) diff --git a/R/get_conversion_factor.R b/R/get_conversion_factor.R new file mode 100644 index 00000000..1d886c68 --- /dev/null +++ b/R/get_conversion_factor.R @@ -0,0 +1,28 @@ +#' Transform Units +#' +#' This function transforms a value from an initial unit to a target unit. +#' +#' @param initial_unit A character string representing the initial unit. +#' @param target_unit A character string representing the target unit. +#' @returns A numeric value for the conversion factor from the initial to the target unit, +#' or NA if the units are not convertible. +#' @examples +#' get_conversion_factor("meter", "kilometer") +#' get_conversion_factor("sec", "min") +#' @importFrom units set_units +#' @export +get_conversion_factor <- Vectorize(function(initial_unit, target_unit) { + tryCatch({ + conversion <- units::set_units( + units::set_units(1, initial_unit, mode = "standard"), + target_unit, mode = "standard" + ) + unname(as.numeric(conversion)) + }, error = function(e) { + if (initial_unit == target_unit) { + 1 + } else { + NA + } + }) +}, USE.NAMES = FALSE) diff --git a/R/pivot_wider_pknca_results.R b/R/pivot_wider_pknca_results.R index e7d36941..58ef28b9 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -21,24 +21,24 @@ pivot_wider_pknca_results <- function(myres) { # Get all names with units and make a dictionary structure dict_pttestcd_with_units <- myres$result %>% - select(PPTESTCD, PPORRESU) %>% + select(PPTESTCD, PPSTRESU) %>% distinct() %>% - pull(PPORRESU, PPTESTCD) + pull(PPSTRESU, PPTESTCD) # Filter out infinite AUCs and pivot the data to incorporate # the parameters into columns with their units infinite_aucs_vals <- myres$result %>% distinct() %>% filter(type_interval == "main") %>% - select(-PPORRESU, -exclude, -type_interval) %>% - pivot_wider(names_from = PPTESTCD, values_from = PPORRES) + select(-PPSTRESU, -PPORRES, -PPORRESU, -exclude, -type_interval) %>% + pivot_wider(names_from = PPTESTCD, values_from = PPSTRES) infinite_aucs_exclude <- myres$result %>% distinct() %>% filter(type_interval == "main") %>% - select(-PPORRES, -PPORRESU, -type_interval) %>% - mutate(PPTESTCD = paste0("exclude.", PPTESTCD)) %>% - pivot_wider(names_from = PPTESTCD, values_from = exclude) + select(-PPSTRES, -PPSTRESU, -PPORRES, -PPORRESU, -type_interval) %>% + mutate(exclude.PPTESTCD = paste0("exclude.", PPTESTCD)) %>% + pivot_wider(names_from = exclude.PPTESTCD, values_from = exclude) infinite_aucs <- inner_join(infinite_aucs_vals, infinite_aucs_exclude) @@ -70,10 +70,10 @@ pivot_wider_pknca_results <- function(myres) { interval_name = paste0(signif(start), "-", signif(end)), interval_name_col = paste0(PPTESTCD, "_", interval_name) ) %>% - select(-exclude, -PPORRESU, -start, -end, + select(-exclude, -PPSTRESU, -PPORRES, -PPORRESU, -start, -end, -PPTESTCD, -interval_name, -type_interval) %>% pivot_wider(names_from = interval_name_col, - values_from = PPORRES) + values_from = PPSTRES) interval_aucs_exclude <- myres$result %>% filter(type_interval == "manual", startsWith(PPTESTCD, "aucint")) %>% @@ -81,7 +81,7 @@ pivot_wider_pknca_results <- function(myres) { interval_name = paste0(signif(start), "-", signif(end)), interval_name_col = paste0("exclude.", PPTESTCD, "_", interval_name) ) %>% - select(-PPORRES, -PPORRESU, -start, -end, + select(-PPSTRES, -PPSTRESU, -PPORRES, -PPORRESU, -start, -end, -PPTESTCD, -interval_name, -type_interval) %>% pivot_wider(names_from = interval_name_col, values_from = exclude) @@ -93,7 +93,7 @@ pivot_wider_pknca_results <- function(myres) { .x )) - all_aucs <- inner_join(infinite_aucs_with_lambda, interval_aucs, all = TRUE) + all_aucs <- inner_join(infinite_aucs_with_lambda, interval_aucs) } else { all_aucs <- infinite_aucs_with_lambda } diff --git a/inst/shiny/data/DummyRO_ADNCA.csv b/inst/shiny/data/DummyRO_ADNCA.csv index 4d85ce9c..7d8b031f 100644 --- a/inst/shiny/data/DummyRO_ADNCA.csv +++ b/inst/shiny/data/DummyRO_ADNCA.csv @@ -536,4 +536,4 @@ "XX01",25201,"Analyte01","SERUM","EVERY 3 WEEKS",1,7.96166666666667,7.96166666666667,8,8,5.83,"Analyte01","ug/mL","intravascular",35,51,"M","WHITE",1.53,"Hours","mg",0,"Analyte01","Cycle 1 Day 1",5.83,"ug/mL",0,"2022-09-13 11:34:00",339203,"Dummy Treatment Arm A","Dummy Treatment Arm P","2022-08-23 11:59:00",176.5,81.6,"kg","cm",1.58,0 "XX01",25201,"Analyte01","SERUM","EVERY 3 WEEKS",1,25.3016666666667,25.3016666666667,48,48,4.28,"Analyte01","ug/mL","intravascular",35,51,"M","WHITE",1.53,"Hours","mg",0,"Analyte01","Cycle 1 Day 2",4.28,"ug/mL",0,"2022-09-13 11:34:00",339203,"Dummy Treatment Arm A","Dummy Treatment Arm P","2022-08-23 11:59:00",176.5,81.6,"kg","cm",1.53,0 "XX01",25201,"Analyte01","SERUM","EVERY 3 WEEKS",1,75.6816666666667,75.6816666666667,144,144,1.95,"Analyte01","ug/mL","intravascular",35,51,"M","WHITE",1.53,"Hours","mg",0,"Analyte01","Cycle 1 Day 4",1.95,"ug/mL",0,"2022-09-13 11:34:00",339203,"Dummy Treatment Arm A","Dummy Treatment Arm P","2022-08-23 11:59:00",176.5,81.6,"kg","cm",1.54,0 -"XX01",25201,"Analyte01","SERUM","EVERY 3 WEEKS",1,171.701666666667,171.701666666667,336,336,0.258,"Analyte01","ug/mL","intravascular",35,51,"M","WHITE",1.53,"Hours","mg",0,"Analyte01","Cycle 1 Day 8",0.258,"ug/mL",0,"2022-09-13 11:34:00",339203,"Dummy Treatment Arm A","Dummy Treatment Arm P","2022-08-23 11:59:00",176.5,81.6,"kg","cm",1.5,0 +"XX01",25201,"Analyte01","SERUM","EVERY 3 WEEKS",1,171.701666666667,171.701666666667,336,336,0.258,"Analyte01","ug/mL","intravascular",35,51,"M","WHITE",1.53,"Hours","mg",0,"Analyte01","Cycle 1 Day 8",0.258,"ug/mL",0,"2022-09-13 11:34:00",339203,"Dummy Treatment Arm A","Dummy Treatment Arm P","2022-08-23 11:59:00",176.5,81.6,"kg","cm",1.5,0 \ No newline at end of file diff --git a/inst/shiny/global.R b/inst/shiny/global.R index 99abbd5c..bc9bc5e7 100644 --- a/inst/shiny/global.R +++ b/inst/shiny/global.R @@ -4,6 +4,8 @@ LABELS <- read.csv(system.file("shiny/data/adnca_labels.csv", package = "aNCA")) source("modules/slope_selector.R") +source("modules/units_table.R") + source("functions/partial_auc_input.R") source("modules/tlg_plot.R") diff --git a/inst/shiny/modules/slope_selector.R b/inst/shiny/modules/slope_selector.R index cbd6bfff..c318d7aa 100644 --- a/inst/shiny/modules/slope_selector.R +++ b/inst/shiny/modules/slope_selector.R @@ -123,6 +123,7 @@ slope_selector_server <- function( observeEvent(list( plot_data(), res_nca(), input$plots_per_page, input$search_patient, current_page() ), { + req(res_nca()) log_trace("{id}: Updating displayed plots") # Make sure the search_patient input is not NULL diff --git a/inst/shiny/modules/tab_visuals.R b/inst/shiny/modules/tab_visuals.R index 5b2a1ead..92e87065 100644 --- a/inst/shiny/modules/tab_visuals.R +++ b/inst/shiny/modules/tab_visuals.R @@ -391,6 +391,7 @@ tab_visuals_server <- function(id, data, grouping_vars, res_nca) { # Reactive expression for summary table based on selected group and parameters summary_stats <- reactive({ req(input$summary_groupby, input$select_display_parameters) + req(res_nca()) # Calculate summary stats and filter by selected parameters calculate_summary_stats(res_nca(), input$summary_groupby) %>% @@ -444,7 +445,7 @@ tab_visuals_server <- function(id, data, grouping_vars, res_nca) { # Create formatted Box plot data: PKNCA + PP results, linking DOSEA + PPTESTCD boxplotdata <- reactive({ group_columns <- unname(unlist(res_nca()$data$conc$columns$groups)) - + req(res_nca()) left_join( res_nca()$result %>% filter( diff --git a/inst/shiny/modules/units_table.R b/inst/shiny/modules/units_table.R new file mode 100644 index 00000000..b15d5aa4 --- /dev/null +++ b/inst/shiny/modules/units_table.R @@ -0,0 +1,198 @@ +units_table_ui <- function(id) { + ns <- NS(id) + # Button to open a module message with the parameter units table # + tagList( + actionButton( + ns("open_units_table"), + icon = icon("scale-balanced"), + label = "Parameter Units", + disabled = TRUE + ) + ) +} + +units_table_server <- function(id, mydata, res_nca = reactiveVal(NULL)) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + + #' Allow user to open the units table when data is available + observeEvent(mydata(), { + updateActionButton(session = session, + inputId = "open_units_table", + disabled = FALSE) + }) + + # Define the modal message displayed with the parameter units table # + observeEvent(input$open_units_table, { + + # Keep in a variable all analytes available + analyte_choices <- unique(mydata()$conc$data[[mydata()$conc$columns$groups$group_analyte]]) + + # Show the modal message with the units table and an analyte selector + showModal(modalDialog( + title = tagList( + span("Units of NCA parameter results") + ), + selectInput( + inputId = ns("select_unitstable_analyte"), + multiple = TRUE, + label = "Select Analyte:", + choices = analyte_choices, + selected = analyte_choices + ), + DTOutput(ns("modal_units_table")), + footer = tagList( + modalButton("Close"), + actionButton(ns("save_units_table"), "Save Units Table") + ), + size = "l" + )) + }) + + # Define the parameter units table and how is displayed to the user # + modal_units_table <- reactiveVal(NULL) + observeEvent(list(mydata(), input$select_unitstable_analyte), { + req(mydata()) + req(input$select_unitstable_analyte) + analyte_column <- mydata()$conc$columns$groups$group_analyte + modal_units_table_data <- mydata()$units %>% + group_by(PPTESTCD, PPORRESU, PPSTRESU, conversion_factor) %>% + filter(!!sym(analyte_column) %in% input$select_unitstable_analyte) %>% + rename(`Parameter` = PPTESTCD, + `Default unit` = PPORRESU, + `Conversion Factor` = conversion_factor, + `Custom unit` = PPSTRESU) %>% + mutate(Analytes = paste(!!sym(analyte_column), collapse = ", ")) %>% + ungroup() %>% + select(`Analytes`, `Parameter`, `Default unit`, `Custom unit`, `Conversion Factor`) + modal_units_table(modal_units_table_data) + }) + + # Define which parameters where choosen by the user + params_to_calculate <- reactive({ + names(purrr::keep(mydata()$intervals, ~ is.logical(.x) && any(.x))) + }) + + params_to_calculate_array_str <- reactive({ + paste0("['", paste(params_to_calculate(), collapse = "','"), "']") + }) + + #' Rendering the modal units table + output$modal_units_table <- DT::renderDT({ + datatable( + data = modal_units_table() %>% + mutate(`Conversion Factor` = signif(`Conversion Factor`, 3)), + escape = FALSE, + class = "table table-striped table-bordered", + rownames = FALSE, + editable = list( + target = "cell", + disable = list( + columns = c(0, 1, 2) + ) + ), + options = list( + order = list(2, "desc"), + paging = FALSE, + searching = TRUE, + autoWidth = TRUE, + dom = "ft", + # Display only rows with the parameters to run for the NCA + rowCallback = htmlwidgets::JS( + paste0(" + function(row, data, index) { + var paramsToCalculate = ", params_to_calculate_array_str(), + "; + if (paramsToCalculate.indexOf(data[1]) === -1) { + $(row).hide(); + } + }" + ) + ), + columnDefs = list( + list( + visible = FALSE, + targets = c() + ) + ) + ) + ) + }) + + # Accept user modifications in the modal units table + observeEvent(input$modal_units_table_cell_edit, { + info <- input$modal_units_table_cell_edit + modal_units_table <- modal_units_table() + modal_units_table[info$row, info$col + 1] <- info$value + + if (names(modal_units_table)[info$col + 1] == "Custom unit") { + def_unit <- modal_units_table[info$row, "Default unit"] + cust_unit <- modal_units_table[info$row, "Custom unit"] + modal_units_table[info$row, "Conversion Factor"] <- get_conversion_factor(def_unit, + cust_unit) + } + + modal_units_table(modal_units_table) + }) + + # When save button is pressed substitute the original units table based on the modal one + observeEvent(input$save_units_table, { + + # Make sure there are no missing entries (no NA in conversion factor) + if (any(is.na(modal_units_table()$`Conversion Factor`))) { + + invalid_entries <- modal_units_table() %>% + filter(is.na(`Conversion Factor`)) %>% + mutate(entry = paste0(Parameter, " (", Analytes, ")")) %>% + pull(entry) + + showNotification( + paste0("Please, make sure to use only recognised convertible units in `Custom Unit`", + "(i.e, day, hr, min, sec, g/L).", + " If not, introduce yourself the corresponding `Conversion Factor` value in: ", + paste(invalid_entries, collapse = ", ")), + duration = NULL, + closeButton = TRUE, + type = "warning" + ) + return() + } + + # Tranforms the modal units table back to the original one + modal_units_table <- modal_units_table() %>% + mutate(Analytes = strsplit(Analytes, ", ")) %>% + unnest(Analytes) %>% + rename(ANALYTE = `Analytes`, + PPTESTCD = `Parameter`, + PPORRESU = `Default unit`, + PPSTRESU = `Custom unit`, + conversion_factor = `Conversion Factor`) + + # Close the modal message window for the user + removeModal() + + # Updates units table of mydata and res_nca according to the user's changes + mydata <- mydata() + mydata$units <- modal_units_table + mydata(mydata) + + # If there are already results produced, make sure they are also adapted + if (!is.null(res_nca())) { + res_nca <- res_nca() + res_nca$data$units <- modal_units_table + res_nca$result <- res_nca$result %>% + select(-PPSTRESU, -PPSTRES) %>% + left_join( + modal_units_table, + by = intersect(names(.), names(modal_units_table)) + ) %>% + mutate(PPSTRES = PPORRES * conversion_factor) %>% + select(-conversion_factor) + res_nca(res_nca) + + } + + }) + + }) +} diff --git a/inst/shiny/tabs/nca.R b/inst/shiny/tabs/nca.R index d3498a91..b82db7a0 100644 --- a/inst/shiny/tabs/nca.R +++ b/inst/shiny/tabs/nca.R @@ -240,6 +240,13 @@ observeEvent(input$submit_analyte, priority = 2, { ) ) + # Redefine units for each analyte and for potential customizations + unique_analytes <- unique(mydata$conc$data[[mydata$conc$columns$groups$group_analyte]]) + analyte_column <- mydata$conc$columns$groups$group_analyte + mydata$units <- tidyr::crossing(mydata$units, + !!sym(analyte_column) := unique_analytes) %>% + dplyr::mutate(PPSTRESU = PPORRESU, conversion_factor = 1) + mydata(mydata) }) @@ -353,8 +360,10 @@ observeEvent(input$removeAUC, { auc_counter(auc_counter() - 1) } }) + # NCA button object myres <- reactiveVal(NULL) + observeEvent(input$nca, { req(mydata()) @@ -447,7 +456,8 @@ observeEvent(input$nca, { updateTabsetPanel(session, "ncapanel", selected = "Results") }) -res_nca <- eventReactive(pk_nca_trigger(), { +res_nca <- reactiveVal(NULL) +observeEvent(pk_nca_trigger(), { req(mydata()) withProgress(message = "Calculating NCA...", value = 0, { myres <- PKNCA::pk.nca(data = mydata(), verbose = FALSE) @@ -463,10 +473,15 @@ res_nca <- eventReactive(pk_nca_trigger(), { dplyr::select(names(myres$result)) # Return the result - return(myres) + res_nca(myres) }) }) +# Parameter unit changes option: Opens a modal message with a units table to edit +# It updates $units table of mydata & res_nca when the user saves their changes +units_table_server("units_table_preNCA", mydata, res_nca) +units_table_server("units_table_postNCA", mydata, res_nca) + # TABSET: Results ============================================================== # In the result tabset we can view the NCA results, slope caclulation und exclusions table. @@ -479,7 +494,7 @@ final_res_nca <- reactiveVal(NULL) # creative final_res_nca, aiming to present the results in a more comprehensive way observeEvent(res_nca(), { - + req(res_nca()) # Create a reshaped object that will be used to display the results in the UI final_res_nca <- pivot_wider_pknca_results(res_nca()) @@ -503,9 +518,9 @@ observeEvent(res_nca(), { # Include units for all column names dict_pttestcd_with_units <- res_nca()$result %>% - select(PPTESTCD, PPORRESU) %>% + select(PPTESTCD, PPSTRESU) %>% unique() %>% - pull(PPORRESU, PPTESTCD) + pull(PPSTRESU, PPTESTCD) final_res_nca <- final_res_nca %>% rename_with(~ifelse( diff --git a/inst/shiny/ui.R b/inst/shiny/ui.R index 3ca5472c..9e461980 100644 --- a/inst/shiny/ui.R +++ b/inst/shiny/ui.R @@ -69,6 +69,7 @@ fluidPage( ), selected = "lin up/log down" ), + units_table_ui("units_table_preNCA"), h4("Data imputation"), tags$div( checkboxInput( @@ -152,7 +153,7 @@ fluidPage( fluidRow( column( width = 6, - checkboxInput("rule_aucpext_pred", "AUCPEP (% ext.predicted): "), + checkboxInput("rule_aucpext_pred", "AUCPEP (% ext.predicted): ") ), column( width = 6, @@ -176,7 +177,7 @@ fluidPage( fluidRow( column( width = 6, - checkboxInput("rule_span_ratio", "SPAN: "), + checkboxInput("rule_span_ratio", "SPAN: ") ), column( width = 6, @@ -198,7 +199,7 @@ fluidPage( ) ), - tabPanel("Slope Selector", slope_selector_ui("slope_selector")), + tabPanel("Slope Selector", slope_selector_ui("slope_selector")) ) ), @@ -216,10 +217,11 @@ fluidPage( multiple = TRUE, options = list(`actions-box` = TRUE) ), + units_table_ui("units_table_postNCA"), DTOutput("myresults"), tableOutput("summaryTable"), actionButton("download", "Download the NCA Data"), - downloadButton("local_download_NCAres", "Download locally the NCA Data"), + downloadButton("local_download_NCAres", "Download locally the NCA Data") ), tabPanel( "Slopes", diff --git a/man/get_conversion_factor.Rd b/man/get_conversion_factor.Rd new file mode 100644 index 00000000..00277899 --- /dev/null +++ b/man/get_conversion_factor.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_conversion_factor.R +\name{get_conversion_factor} +\alias{get_conversion_factor} +\title{Transform Units} +\usage{ +get_conversion_factor(initial_unit, target_unit) +} +\arguments{ +\item{initial_unit}{A character string representing the initial unit.} + +\item{target_unit}{A character string representing the target unit.} +} +\value{ +A numeric value for the conversion factor from the initial to the target unit, +or NA if the units are not convertible. +} +\description{ +This function transforms a value from an initial unit to a target unit. +} +\examples{ +get_conversion_factor("meter", "kilometer") +get_conversion_factor("sec", "min") +} diff --git a/tests/testthat/test-get_conversion_factor.R b/tests/testthat/test-get_conversion_factor.R new file mode 100644 index 00000000..32a74f68 --- /dev/null +++ b/tests/testthat/test-get_conversion_factor.R @@ -0,0 +1,51 @@ +describe("get_conversion_factor", { + it("handles simple time units", { + expect_equal(get_conversion_factor("hr", "minute"), 60) + expect_equal(get_conversion_factor("minute", "second"), 60) + expect_equal(get_conversion_factor("day", "hour"), 24) + expect_equal(get_conversion_factor("second", "minute"), 1 / 60) + expect_equal(get_conversion_factor("hour", "second"), 3600) + expect_equal(get_conversion_factor("minute", "hour"), 1 / 60) + expect_equal(get_conversion_factor("day", "minute"), 1440) + }) + + it("handles simple concentration units", { + expect_equal(get_conversion_factor("mg/L", "g/L"), 0.001) + expect_equal(get_conversion_factor("g/dL", "kg/L"), 0.01) + expect_equal(get_conversion_factor("ug/mL", "mg/L"), 1) + expect_equal(get_conversion_factor("kg/L", "g/L"), 1000) + expect_equal(get_conversion_factor("mg/dL", "g/L"), 0.01) + expect_equal(get_conversion_factor("g/L", "mg/L"), 1000) + expect_equal(get_conversion_factor("kg/L", "mg/L"), 1e6) + }) + + it("handles combined units", { + expect_equal(get_conversion_factor("Hours*ug/mL", "Hours*mg/L"), 1) + expect_equal(get_conversion_factor("Hours^2*ug/mL", "Hours^2*mg/L"), 1) + expect_equal(get_conversion_factor("(Hours*ug/mL)/mg", "(Hours*mg/L)/g"), 1000) + expect_equal(get_conversion_factor("mg/(Hours*ug/mL)", "g/(Hours*mg/L)"), 0.001) + expect_equal(get_conversion_factor("(ug/mL)/(Hours*ug/mL)", "(mg/L)/(Hours*mg/L)"), 1) + expect_equal(get_conversion_factor("Hours*mg/L", "Hours*ug/mL"), 1) + expect_equal(get_conversion_factor("Hours^2*mg/L", "Hours^2*ug/mL"), 1) + expect_equal(get_conversion_factor("Hours*kg/L", "Hours*g/L"), 1000) + expect_equal(get_conversion_factor("Hours*mg/L", "Hours*kg/L"), 1e-6) + }) + + it("returns NA for non-convertible units", { + expect_true(is.na(get_conversion_factor("meter", "second"))) + expect_true(is.na(get_conversion_factor("kg", "hour"))) + expect_true(is.na(get_conversion_factor("liter", "gram"))) + expect_true(is.na(get_conversion_factor("meter", "liter"))) + expect_true(is.na(get_conversion_factor("second", "gram"))) + expect_true(is.na(get_conversion_factor("hour", "meter"))) + }) + + it("handles vector inputs", { + expect_equal(get_conversion_factor(c("hr", "minute"), c("minute", "second")), c(60, 60)) + expect_equal(get_conversion_factor(c("day", "mg/L"), c("hour", "g/L")), c(24, 0.001)) + expect_equal(get_conversion_factor(c("second", "kg/L"), c("minute", "g/L")), c(1 / 60, 1000)) + expect_equal(get_conversion_factor(c("hour", "ug/mL"), c("second", "mg/L")), c(3600, 1)) + expect_equal(get_conversion_factor(c("minute", "day"), c("second", "hour")), c(60, 24)) + expect_equal(get_conversion_factor(c("g/L", "kg/L"), c("mg/L", "g/L")), c(1000, 1000)) + }) +})