From 4c780f2facec731a7b924779f6769b54d24d7fff Mon Sep 17 00:00:00 2001 From: toscm Date: Sun, 11 Jan 2026 16:41:30 +0100 Subject: [PATCH 1/6] Add match_rts & match_keys to adjust_frm --- DESCRIPTION | 2 +- NEWS.md | 15 ++ R/train.R | 253 ++++++++++++++++++++----------- man/adjust_frm.Rd | 25 ++- tests/testthat/test-adjust_frm.R | 60 +++++++- 5 files changed, 258 insertions(+), 97 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 36f5df4..8b0d5e2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: FastRet Title: Retention Time Prediction in Liquid Chromatography -Version: 1.3.0 +Version: 1.3.1 Authors@R: c( person(given = "Tobias", family = "Schmidt", role = c("aut", "cre", "cph"), email = "tobias.schmidt331@gmail.com", comment = c(ORCID = "0000-0001-9681-9253")), person(given = "Christian", family = "Amesoeder", role = c("aut", "cph"), email = "christian-amesoeder@web.de", comment = c(ORCID = "0000-0002-1668-8351")), diff --git a/NEWS.md b/NEWS.md index 69f70ef..db26206 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,19 @@ + +# FastRet 1.3.1 + +API Improvements: + +1. Added arguments `match_rts` and `match_keys` to `adjust_frm()`: + - If `match_rts=TRUE` (default), RTs are obtained by matching rows in + `new_data` to rows in `frm$df` based on `match_keys`. + - If `match_rts=FALSE`, RTs are obtained by applying the base model to + `new_data`. + - `match_keys` can be any combination of "INCHIKEY", "SMILES" and "NAME". If + left at default NULL, SMILES+INCHIKEY is used if both columns are present + in the adjusted and the original training data. Otherwise, SMILES+NAME is + used. + # FastRet 1.3.0 API Improvements: diff --git a/R/train.R b/R/train.R index 9e84b5b..b0bb844 100644 --- a/R/train.R +++ b/R/train.R @@ -209,12 +209,26 @@ train_frm <- function(df, method = "lasso", verbose = 1, nfolds = 5, nw = 1, #' A logical value indicating whether to add chemical descriptors as predictors #' to new data. Default is TRUE if `adj_type` is "lasso", "ridge" or "gbtree" #' and FALSE if `adj_type` is "lm". +#' @param match_rts +#' Logical indicating how to obtain the RT feature used during training of the +#' adjustment model. If TRUE (default), RT is obtained by matching rows in +#' `new_data` to rows in `frm$df` based on `match_keys`. If FALSE, RT is obtained +#' by applying the base model to `new_data`. +#' @param match_keys +#' Which columns to use for matching `new_data` to `frm$df` if `match_rts` is +#' TRUE. Can be any combination of "INCHIKEY", "SMILES" and "NAME". If left at +#' NULL, matching is done via `c("SMILES", "INCHIKEY")` if INCHIKEYs are +#' available, else via `c("SMILES", "NAME")`. See 'Details' for more information +#' on the matching procedure. #' #' @details -#' Matching is done via "SMILES"+"INCHIKEY" if both datasets have non-missing -#' INCHIKEYs for all rows; otherwise via "SMILES"+"NAME". If multiple rows in -#' `frm$df` match the same row in `new_data`, their RT values are averaged -#' first, and this average is used for training the adjustment model. +#' Matching is done via `c("SMILES", "INCHIKEY")` if both datasets have non-missing +#' INCHIKEYs for all rows; otherwise via `c("SMILES", "NAME")`. This can be +#' overridden by supplying `match_keys`. If `match_rts = FALSE`, no matching is +#' performed and retention times are generated by calling `predict(frm, new_data, +#' adjust = FALSE)`. If multiple rows in `frm$df` match the same row in +#' `new_data`, their RT values are averaged first, and this average is used for +#' training the adjustment model. #' #' Example: if `frm$df` equals data.frame OLD shown below and `new_data` equals #' data.frame NEW, then the resulting, paired data.frame will look like PAIRED. @@ -291,7 +305,9 @@ adjust_frm <- function(frm, seed = NULL, do_cv = TRUE, adj_type = "lm", - add_cds = NULL) { + add_cds = NULL, + match_rts = TRUE, + match_keys = NULL) { # Stubs for interactive Development if (FALSE) { @@ -310,7 +326,10 @@ adjust_frm <- function(frm, is.null(seed) || is.numeric(seed), is.logical(do_cv), is.character(adj_type), adj_type %in% c("lm", "lasso", "ridge", "gbtree"), - is.null(add_cds) || is.logical(add_cds) + is.null(add_cds) || is.logical(add_cds), + is.logical(match_rts), length(match_rts) == 1, !is.na(match_rts), + is.null(match_keys) || (is.character(match_keys) && length(match_keys) >= 1 && + all(match_keys %in% c("INCHIKEY", "SMILES", "NAME"))) ) if (length(predictors) == 1 && predictors != 7 && adj_type != "lm") { fmt <- "Adjustment via '%s' requires 2+ predictors. Setting 'adj_type' to 'lm'." @@ -335,8 +354,8 @@ adjust_frm <- function(frm, # Map new and old data logf("Mapping new and old data") - args <- named(predictors, nfolds, verbose, seed, do_cv, adj_type) - df <- merge_dfs(old = frm$df, new = new_data) + args <- named(predictors, nfolds, verbose, seed, do_cv, adj_type, match_rts, match_keys) + df <- build_adjustment_df(frm, new_data, match_rts, match_keys) df <- preprocess_data( df, degree_polynomial = 1, interaction_terms = FALSE, verbose = verbose, nw = 1, rm_near_zero_var = FALSE, rm_na = FALSE, add_cds = add_cds, @@ -373,6 +392,7 @@ adjust_frm <- function(frm, models <- mapply( adjust_frm, list(frm), train_dfs, list(predictors), nfolds, verbose, seeds, FALSE, adj_type, + MoreArgs = list(add_cds = add_cds, match_rts = match_rts, match_keys = match_keys), SIMPLIFY = FALSE, USE.NAMES = FALSE ) dbgf("Predicting RT_ADJ for hold-out data in each fold") @@ -693,107 +713,160 @@ clip_predictions <- function(yhat, y) { # Preprocessing ##### #' @noRd -#' @title Merge training and adjustment data.frames +#' @title Build adjustment data #' @description -#' Merges the original data frame used to train a FastRet model with a new -#' data frame provided by the user for adjusting the original model. +#' Creates the data.frame used to train an adjustment model either by matching +#' the new measurements against the original training data or by predicting the +#' missing RTs with the base model. #' -#' @param old The original data frame used to train `frm`. Must contain columns -#' "NAME", "SMILES", "RT" and optionally "INCHIKEY" as well the chemical -#' descriptors listed in `CDFeatures` -#' @param new The new data frame provided by the user. Must contain columns -#' "NAME", "SMILES", "RT" and optionally "INCHIKEY". +#' @param frm FastRet model that provides the original training data and base model. +#' @param new The new adjustment data frame. +#' @param match_rts Logical indicating whether RTs should be taken from +#' `frm$df` via matching. +#' @param match_keys Character vector with the columns used for matching. #' -#' @return A data frame with following columns: -#' - NAME: Molecule names from `new` -#' - SMILES: Molecule SMILES from `new` -#' - RT: Corresponding retention times taken from `old` -#' - RT_ADJ: Retention times from `new` +#' @return A data.frame containing NAME, SMILES, optional INCHIKEY, RT, and RT_ADJ. #' #' @examples -#' new <- data.frame( -#' NAME = c("A", "B", "B", "B"), -#' SMILES = c("C", "CC", "CC", "CC"), -#' RT = c(2.5, 5.5, 5.7, 5.6) -#' ) -#' old <- data.frame( -#' NAME = c("A", "B", "B", "C"), -#' SMILES = c("C", "CC", "CC", "CCC"), -#' RT = c(5.0, 8.0, 8.2, 9.0), -#' nAtom = c(1, 2, 2, 3) -#' ) -#' merged <- merge_dfs(old, new) -#' print(merged) -#' ## data.frame( -#' ## NAME = c("A", "B", "B", "B"), -#' ## SMILES = c("C", "CC", "CC", "CC"), -#' ## RT_ADJ = c(2.5, 5.5, 5.7, 5.6), -#' ## RT = c(5, 8.1, 8.1, 8.1), -#' ## nAtom = c(1, 2, 2, 2) -#' ## ) -merge_dfs <- function(old, new) { - use_inchi <- { - !is.null(old$INCHIKEY) && all(!is.na(old$INCHIKEY)) && - !is.null(new$INCHIKEY) && all(!is.na(new$INCHIKEY)) +#' frm <- read_rp_lasso_model_rds() +#' new_data <- read_rpadj_xlsx()[1:3, ] +#' build_adjustment_df(frm, new_data, match_rts = TRUE) +build_adjustment_df <- function(frm, new, match_rts = TRUE, match_keys = NULL) { + df <- data.frame(NAME = new$NAME, SMILES = new$SMILES, RT_ADJ = new$RT) + if ("INCHIKEY" %in% colnames(new)) { + df$INCHIKEY <- new$INCHIKEY } - if (use_inchi) { - new <- data.frame( - NAME = new$NAME, - INCHIKEY = new$INCHIKEY, - SMILES = new$SMILES, - RT_ADJ = new$RT, - KEY = paste0(new$SMILES, "_", new$INCHIKEY) - ) - old <- data.frame( - RT = old$RT, - KEY = paste0(old$SMILES, "_", old$INCHIKEY) - ) - } else { - new <- data.frame( - NAME = new$NAME, - SMILES = new$SMILES, - RT_ADJ = new$RT, - KEY = paste0(new$SMILES, "_", new$NAME) - ) - old <- data.frame( - RT = old$RT, - KEY = paste0(old$SMILES, "_", old$NAME) - ) + if (!match_rts) { + df$RT <- as.numeric(predict(frm, new, adjust = FALSE, verbose = 0)) + return(df) } - old <- old[old$KEY %in% unique(new$KEY), ] - - # At this point, we can still have multiple measurements per KEY in old. To - # deal with this, we average their RTs first, then map every new entry to - # the average. - old_RT_avgs <- tapply(old$RT, old$KEY, mean) - old <- old[!duplicated(old$KEY), ] - old$RT <- as.numeric(old_RT_avgs[old$KEY]) # as.numeric drops dimnames attr - - # Now finally merge new and old data to get the paired data.frame for model - # fitting. Make sure to restore the original order after merging, as - # `merge()` returns rows in an 'unspecified' order (even with sort=FALSE). - new$ID <- seq_len(nrow(new)) - df <- merge(new, old, by = "KEY", sort = FALSE, all = FALSE) - df <- df[order(df$ID), ] + keys <- resolve_match_keys(frm$df, new, match_keys) + df$KEY <- build_match_key(new, keys, "new data") + df$ID <- seq_len(nrow(df)) + key <- build_match_key(frm$df, keys, "original data") + old <- data.frame(RT = frm$df$RT, KEY = key) + old <- old[old$KEY %in% unique(df$KEY), , drop = FALSE] + if (!nrow(old)) { + key_str <- paste(keys, collapse = "+") + fmt <- "Could not map any new entries to original data based on %s." + stop(sprintf(fmt, key_str)) + } + rt_avg <- tapply(old$RT, old$KEY, mean) + old <- old[!duplicated(old$KEY), , drop = FALSE] + old$RT <- as.numeric(rt_avg[old$KEY]) + df <- merge(df, old, by = "KEY", all = FALSE, sort = FALSE) + df <- df[order(df$ID), , drop = FALSE] df$ID <- NULL df$KEY <- NULL - if (nrow(df) < nrow(new)) { - key <- if (use_inchi) "SMILES+INCHIKEY" else "SMILES+NAME" + key_str <- paste(keys, collapse = "+") + miss <- nrow(new) - nrow(df) fmt <- "Could not map %d new entries to original data based on %s." - stop(sprintf(fmt, nrow(new) - nrow(df), key)) - } else if (nrow(df) > nrow(new)) { - # This can't happen due to averaging above. We check anyway to be safe. - msg <- sprintf("Ambiguous mapping: %d new -> %d old", nrow(new), nrow(df)) - stop(msg) + stop(sprintf(fmt, miss, key_str)) } df } +#' @noRd +#' @title Resolve match keys +#' @description Determine which columns should be used to match `new` against the training data. +#' @param old Original training data stored in `frm$df`. +#' @param new New adjustment data frame. +#' @param match_keys Optional character vector supplied by the caller. +#' @return Character vector of column names used for matching. +#' @examples +#' old <- data.frame(SMILES = "C", NAME = "A", INCHIKEY = "AAA", RT = 1) +#' new <- data.frame(SMILES = "C", NAME = "A", INCHIKEY = "AAA", RT = 2) +#' resolve_match_keys(old, new, NULL) # c("SMILES", "INCHIKEY") +#' resolve_match_keys(old, new, c("SMILES", "NAME")) # c("SMILES", "NAME") +#' resolve_match_keys(old, new, c("SMILES", "FOO")) # error +resolve_match_keys <- function(old, new, match_keys) { + allowed <- c("INCHIKEY", "SMILES", "NAME") + keys <- if (is.null(match_keys)) { + has_inchi <- ("INCHIKEY" %in% colnames(old)) && + ("INCHIKEY" %in% colnames(new)) && + all(!is.na(old$INCHIKEY)) && + all(!is.na(new$INCHIKEY)) + if (has_inchi) c("SMILES", "INCHIKEY") else c("SMILES", "NAME") + } else { + invalid <- setdiff(match_keys, allowed) + if (length(invalid)) { + fmt <- "Invalid match_keys supplied: %s" + stop(sprintf(fmt, paste(invalid, collapse = ", "))) + } + match_keys + } + ensure_cols_exist(old, keys, "original data") + ensure_cols_exist(new, keys, "new data") + ensure_no_NAs(old, keys, "original data") + ensure_no_NAs(new, keys, "new data") + keys +} + +#' @noRd +#' @title Build composite match key +#' @description Concatenate selected columns to create deterministic match keys. +#' @param df Data frame containing the columns of interest. +#' @param cols Character vector with column names used in the key. +#' @param label Short label used in error messages. +#' @return Character vector with unique keys per row. +#' @examples +#' df <- data.frame(SMILES = c("C", "CC"), NAME = c("A", "B")) +#' build_match_key(df, c("SMILES", "NAME"), "demo") +#' ## +build_match_key <- function(df, cols, label) { + parts <- lapply(cols, function(col) df[[col]]) + parts <- lapply(parts, as.character) + key <- do.call(paste, c(parts, list(sep = "__"))) + if (any(is.na(key))) { + fmt <- "Missing values detected while creating %s keys." + stop(sprintf(fmt, label)) + } + key +} + +#' @noRd +#' @title Ensure required columns are present +#' @description Stop with an informative message when required matching columns are missing. +#' @param df Data frame to inspect. +#' @param cols Character vector with required columns. +#' @param label Text describing the data frame (e.g. "new data"). +#' @examples +#' ensure_cols_exist(data.frame(SMILES = "C"), "SMILES", "demo") +ensure_cols_exist <- function(df, cols, label) { + missing <- setdiff(cols, colnames(df)) + if (length(missing)) { + fmt <- "%s missing columns required for matching: %s" + stop(sprintf(fmt, label, paste(missing, collapse = ", "))) + } +} + +#' @noRd +#' @title Ensure matching columns have no missing values +#' @description Validate that the supplied columns do not contain `NA`s before matching. +#' @param df Data frame to inspect. +#' @param cols Character vector with required columns. +#' @param label Text describing the data frame (e.g. "original data"). +#' @examples +#' ensure_no_NAs(data.frame(SMILES = "C"), "SMILES", "demo") +ensure_no_NAs <- function(df, cols, label) { + has_na <- sapply(cols, function(col) any(is.na(df[[col]]))) + if (any(has_na)) { + cols_na <- cols[has_na] + fmt <- "Columns %s contain NA values in %s." + stop(sprintf(fmt, paste(cols_na, collapse = ", "), label)) + } +} + #' @noRd #' @description Get RMSE, Rsquared, MAE and %below1min for a specific dataset and model. #' @param data dataframe with retention time in the first column #' @param model object useable as input for [predict()] +#' @examples +#' df <- data.frame(RT = c(1, 2)) +#' df[[CDFeatures[1]]] <- c(0.3, 0.8) +#' lm_mod <- stats::lm(RT ~ ., data = df) +#' get_stats(df, lm_mod) get_stats <- function(df, model) { X <- as.matrix(df[, colnames(df) %in% CDFeatures]) y <- df$RT diff --git a/man/adjust_frm.Rd b/man/adjust_frm.Rd index 764b0a6..4b3fbc1 100644 --- a/man/adjust_frm.Rd +++ b/man/adjust_frm.Rd @@ -13,7 +13,9 @@ adjust_frm( seed = NULL, do_cv = TRUE, adj_type = "lm", - add_cds = NULL + add_cds = NULL, + match_rts = TRUE, + match_keys = NULL ) } \arguments{ @@ -45,6 +47,17 @@ the \code{cv} element in the returned adjustment object will be NULL.} \item{add_cds}{A logical value indicating whether to add chemical descriptors as predictors to new data. Default is TRUE if \code{adj_type} is "lasso", "ridge" or "gbtree" and FALSE if \code{adj_type} is "lm".} + +\item{match_rts}{Logical indicating how to obtain the RT feature used during training of the +adjustment model. If TRUE (default), RT is obtained by matching rows in +\code{new_data} to rows in \code{frm$df} based on \code{match_keys}. If FALSE, RT is obtained +by applying the base model to \code{new_data}.} + +\item{match_keys}{Which columns to use for matching \code{new_data} to \code{frm$df} if \code{match_rts} is +TRUE. Can be any combination of "INCHIKEY", "SMILES" and "NAME". If left at +NULL, matching is done via \code{c("SMILES", "INCHIKEY")} if INCHIKEYs are +available, else via \code{c("SMILES", "NAME")}. See 'Details' for more information +on the matching procedure.} } \value{ An object of class \code{frm}, as returned by \code{\link[=train_frm]{train_frm()}}, but with an @@ -80,10 +93,12 @@ the original column) and to attach this adjustment model to an existing FastRet model. } \details{ -Matching is done via "SMILES"+"INCHIKEY" if both datasets have non-missing -INCHIKEYs for all rows; otherwise via "SMILES"+"NAME". If multiple rows in -\code{frm$df} match the same row in \code{new_data}, their RT values are averaged -first, and this average is used for training the adjustment model. +Matching is done via \code{c("SMILES", "INCHIKEY")} if both datasets have non-missing +INCHIKEYs for all rows; otherwise via \code{c("SMILES", "NAME")}. This can be +overridden by supplying \code{match_keys}. If \code{match_rts = FALSE}, no matching is +performed and retention times are generated by calling \code{predict(frm, new_data, adjust = FALSE)}. If multiple rows in \code{frm$df} match the same row in +\code{new_data}, their RT values are averaged first, and this average is used for +training the adjustment model. Example: if \code{frm$df} equals data.frame OLD shown below and \code{new_data} equals data.frame NEW, then the resulting, paired data.frame will look like PAIRED. diff --git a/tests/testthat/test-adjust_frm.R b/tests/testthat/test-adjust_frm.R index 5e92685..8d65084 100644 --- a/tests/testthat/test-adjust_frm.R +++ b/tests/testthat/test-adjust_frm.R @@ -62,7 +62,7 @@ test_that("adjust_frm merges by INCHIKEY when available", { # Now change a few NAMES as well, so that even the SMILES+NAME fallback # cannot find matches for all entries - new$NAME[8:10] <- NA + new$NAME[8:10] <- paste0("missing_", seq_len(3)) expect_error( afm3 <- adjust_frm(frm, new, nfolds = 2, verbose = 0, seed = 42, predictors = 1), "Could not map 3 new entries" @@ -79,6 +79,64 @@ test_that("adjust_frm merges by INCHIKEY when available", { }) +test_that("adjust_frm can skip RT matching and use predictions", { + + frm <- readRDS(pkg_file("extdata/RP_lasso_model.rds")) + idx <- seq(2, 200, by = 20) + new <- frm$df[idx, c("NAME", "SMILES", "RT")] + set.seed(19) + new$RT <- new$RT + runif(nrow(new), min = -0.2, max = 0.2) + + base_preds <- predict(frm, df = new, adjust = FALSE, verbose = 0) + afm <- adjust_frm( + frm = frm, + new_data = new, + nfolds = 2, + verbose = 0, + seed = 19, + predictors = 1:2, + adj_type = "lasso", + match_rts = FALSE + ) + + expect_equal(afm$adj$df$RT, base_preds) + expect_equal(afm$adj$df$RT_ADJ, new$RT) + expect_false(afm$adj$args$match_rts) + expect_true(is.null(afm$adj$args$match_keys)) + expect_equal(nrow(afm$adj$df), nrow(new)) +}) + + +test_that("adjust_frm respects custom match_keys", { + + frm <- readRDS(pkg_file("extdata/RP_lasso_model.rds")) + frm$df$INCHIKEY <- NULL + idx <- seq(3, 120, by = 15) + new <- frm$df[idx, c("NAME", "SMILES", "RT")] + new$NAME <- paste0(new$NAME, "__new") + + expect_error( + adjust_frm(frm, new, nfolds = 2, verbose = 0, seed = 5), + regexp = "Could not map" + ) + + afm <- adjust_frm( + frm, new, + nfolds = 2, + verbose = 0, + seed = 5, + predictors = 1:2, + adj_type = "lasso", + match_keys = "SMILES" + ) + + expect_equal(nrow(afm$adj$df), nrow(new)) + expect_identical(afm$adj$args$match_keys, "SMILES") + expect_true(all(afm$adj$df$SMILES == new$SMILES)) + expect_equal(afm$adj$df$RT_ADJ, new$RT) +}) + + test_that("adjust_frm works with do_cv = FALSE", { # Load pretrained model for speed-up frm <- readRDS(pkg_file("extdata/RP_lasso_model.rds")) From dc67c1b09ac1bc60099a043dd2365797d1aa8e58 Mon Sep 17 00:00:00 2001 From: toscm Date: Sun, 11 Jan 2026 17:12:33 +0100 Subject: [PATCH 2/6] Fix lasso adjustment with one predictor --- DESCRIPTION | 2 +- NEWS.md | 9 +++++++++ R/train.R | 6 +++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8b0d5e2..e502e13 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: FastRet Title: Retention Time Prediction in Liquid Chromatography -Version: 1.3.1 +Version: 1.3.2 Authors@R: c( person(given = "Tobias", family = "Schmidt", role = c("aut", "cre", "cph"), email = "tobias.schmidt331@gmail.com", comment = c(ORCID = "0000-0001-9681-9253")), person(given = "Christian", family = "Amesoeder", role = c("aut", "cph"), email = "christian-amesoeder@web.de", comment = c(ORCID = "0000-0002-1668-8351")), diff --git a/NEWS.md b/NEWS.md index db26206..138e5ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,13 @@ +# FastRet 1.3.2 + +Bugfix: + +1. `adjust_frm()` automatically switched to "lm" adjustment if `predictors` + contained only one predictor, regardless of whether `add_cds` was TRUE or + FALSE. This has now been fixed, so that "lasso", "ridge" and "gbtree" + adjustment is now possible if either `add_cds` is TRUE, `add_cds` is + `NULL` or `predictors` contains more than one predictor. # FastRet 1.3.1 diff --git a/R/train.R b/R/train.R index b0bb844..5c6100d 100644 --- a/R/train.R +++ b/R/train.R @@ -331,13 +331,13 @@ adjust_frm <- function(frm, is.null(match_keys) || (is.character(match_keys) && length(match_keys) >= 1 && all(match_keys %in% c("INCHIKEY", "SMILES", "NAME"))) ) - if (length(predictors) == 1 && predictors != 7 && adj_type != "lm") { + if (is.null(seed)) seed <- sample.int(.Machine$integer.max, 1) + if (is.null(add_cds)) add_cds <- if (adj_type == "lm") FALSE else TRUE + if (length(predictors) == 1 && isFALSE(add_cds) && adj_type != "lm") { fmt <- "Adjustment via '%s' requires 2+ predictors. Setting 'adj_type' to 'lm'." warning(sprintf(fmt, adj_type)) adj_type <- "lm" } - if (is.null(seed)) seed <- sample.int(.Machine$integer.max, 1) - if (is.null(add_cds)) add_cds <- if (adj_type == "lm") FALSE else TRUE # Configure logging logf <- if (verbose >= 1) catf else null From 8bef59dd789f2bf35f51d9c27a07b72567aef2dc Mon Sep 17 00:00:00 2001 From: toscm Date: Sun, 11 Jan 2026 18:31:11 +0100 Subject: [PATCH 3/6] Allow max or inf in selective_measuring --- DESCRIPTION | 2 +- NEWS.md | 12 +++++++ R/sm.R | 67 +++++++++++++++++++++++++------------- R/train.R | 30 +++++++++++++++-- man/selective_measuring.Rd | 14 ++++---- 5 files changed, 93 insertions(+), 32 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e502e13..57e19c8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: FastRet Title: Retention Time Prediction in Liquid Chromatography -Version: 1.3.2 +Version: 1.3.3 Authors@R: c( person(given = "Tobias", family = "Schmidt", role = c("aut", "cre", "cph"), email = "tobias.schmidt331@gmail.com", comment = c(ORCID = "0000-0001-9681-9253")), person(given = "Christian", family = "Amesoeder", role = c("aut", "cph"), email = "christian-amesoeder@web.de", comment = c(ORCID = "0000-0002-1668-8351")), diff --git a/NEWS.md b/NEWS.md index 138e5ad..57dd9d0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,16 @@ + +# FastRet 1.3.3 + +API Improvements: + +1. `selective_measuring()` accepts `"max"` and `"inf"` as additional values + for its `rt_coef` parameter: + - `"max"` is an alias for the existing `"max_ridge_coef"` + - `"inf"` sets all chemical descriptor features to zero before clustering so that + RT alone drives the distance metric (i.e., it is "infinitely" more important + than the chemical descriptors). + # FastRet 1.3.2 Bugfix: diff --git a/R/sm.R b/R/sm.R index 22ee9be..231d403 100644 --- a/R/sm.R +++ b/R/sm.R @@ -13,7 +13,7 @@ #' re-measurement. The selection process includes: #' #' 1. Generating chemical descriptors from the SMILES strings of the molecules. -#' These are the features used by [train_frm()] and [adjust_frm()]. +#' These are the features used by [train_frm()] and [adjust_frm()].˝ #' 2. Standardizing chemical descriptors to have zero mean and unit variance. #' 3. Training a Ridge Regression model with the standardized chemical #' descriptors as features and the retention times as the target variable. @@ -40,15 +40,17 @@ #' #' @param rt_coef #' Which coefficient to use for scaling RT before clustering. Options are: -#' - max_ridge_coef: scale with the maximum absolute coefficient obtained in -#' ridge regression. I.e., RT will have approximately the same weight as the -#' most important chemical descriptor. -#' - 1: do not scale RT any further, i.e., use standardized RT. The effect of +#' - `0`: exclude RT from the clustering. +#' - 'max' / 'max_ridge_coef': scale with the maximum absolute coefficient +#' obtained in ridge regression. I.e., RT will have approximately the same +#' weight as the most important chemical descriptor. +#' - `1`: do not scale RT any further, i.e., use standardized RT. The effect of #' leaving RT unscaled is kind of unpredictable, as the ridge coefficients #' depend on the dataset. If the maximum absolute coefficient is much smaller #' than 1, RT will dominate the clustering. If it is much larger than 1, RT #' will have little influence on the clustering. -#' - 0: exclude RT from the clustering. +#' - 'inf': set all chemical descriptor values to zero, i.e., RT is "infinitely" +#' more important than any chemical descriptor. #' #' @return #' A list containing the following elements: @@ -77,6 +79,15 @@ selective_measuring <- function(raw_data, rt_coef = "max_ridge_coef" ) { + stopifnot( + is.data.frame(raw_data), + all(c("NAME", "SMILES", "RT") %in% colnames(raw_data)), + is.numeric(k_cluster), length(k_cluster) == 1, !is.na(k_cluster), k_cluster >= 2, + is.logical(verbose) || is.numeric(verbose), + is.null(seed) || is.numeric(seed), + is.numeric(rt_coef) || is.character(rt_coef), length(rt_coef) == 1, !is.na(rt_coef) + ) + rt_coef <- try_as_numeric(rt_coef, fallback = rt_coef) logf <- if (verbose >= 1) catf else null logf("Starting Selective Measuring") @@ -91,25 +102,31 @@ selective_measuring <- function(raw_data, df <- df[, nonmeta, drop = FALSE] dfz <- as.data.frame(scale(df)) # z-score standardized (mean 0, sd 1) - logf("Training Ridge Regression model") - Xz <- as.matrix(dfz[, colnames(dfz) != "RT", drop = FALSE]) - y <- as.numeric(dfz[, "RT"]) - model <- fit_glmnet(Xz, y, method = "ridge", seed = seed) + if (rt_coef %in% c(Inf, "inf")) { + logf("Setting CDs to zero because rt_coef is Inf") + coefs <- rep(0, ncol(dfz) - 1) + names(coefs) <- colnames(dfz)[colnames(dfz) != "RT"] + model <- NULL + dfzb <- data.frame(RT = dfz$RT) + } else { + logf("Training Ridge Regression model") + Xz <- as.matrix(dfz[, colnames(dfz) != "RT", drop = FALSE]) + y <- as.numeric(dfz[, "RT"]) + model <- fit_glmnet(Xz, y, method = "ridge", seed = seed) - logf("Scaling features by coefficients of Ridge Regression model") - coef_mat <- glmnet::coef.glmnet(model) # (m+1) x 1 matrix - coefs <- as.numeric(coef_mat)[-1] # remove intercept - coefs <- setNames(coefs, rownames(coef_mat)[-1]) - coefs <- coefs[colnames(Xz)] # ensure correct order - Xzb <- sweep(Xz, 2, coefs, `*`) # z-score standardized and beta-scaled + logf("Scaling features by coefficients of Ridge Regression model") + coef_mat <- glmnet::coef.glmnet(model) # (m+1) x 1 matrix + coefs <- as.numeric(coef_mat)[-1] # remove intercept + coefs <- setNames(coefs, rownames(coef_mat)[-1]) + coefs <- coefs[colnames(Xz)] # ensure correct order + Xzb <- sweep(Xz, 2, coefs, `*`) # z-score standardized and beta-scaled - logf("Scaling RT by %s before clustering", rt_coef) - rtc <- if (rt_coef == "max_ridge_coef") { - max(abs(coefs), na.rm = TRUE) - } else { - as.numeric(rt_coef) + logf("Scaling RT by %s before clustering", rt_coef) + rtc <- if (grepl("max", rt_coef)) max(abs(coefs), na.rm = TRUE) + else if (is.numeric(rt_coef)) as.numeric(rt_coef) + else stop("Invalid value for rt_coef: ", rt_coef) + dfzb <- data.frame(RT = dfz$RT * rtc, Xzb) } - dfzb <- data.frame(RT = dfz$RT * rtc, Xzb) logf("Applying PAM clustering") clobj <- cluster::pam(dfzb, k = as.numeric(k_cluster)) @@ -124,3 +141,9 @@ selective_measuring <- function(raw_data, ) ret } + +try_as_numeric <- function(x, fallback = x) { + if (length(x) != 1) stop("scalar expected") + y <- suppressWarnings(as.numeric(x)) + if (!is.na(y)) y else fallback +} \ No newline at end of file diff --git a/R/train.R b/R/train.R index 5c6100d..5a2e645 100644 --- a/R/train.R +++ b/R/train.R @@ -88,7 +88,7 @@ train_frm <- function(df, method = "lasso", verbose = 1, nfolds = 5, nw = 1, stopifnot( is.data.frame(df), all(c("NAME", "RT", "SMILES") %in% colnames(df)), - method %in% c("lasso", "ridge", "gbtree", "gbtreeDefault", "gbtreeRP"), + is.character(method), length(method) == 1, !is.na(method), is.numeric(nfolds), nfolds >= 2, is.numeric(nw), nw >= 1, is.numeric(degree_polynomial), degree_polynomial >= 1, @@ -101,6 +101,7 @@ train_frm <- function(df, method = "lasso", verbose = 1, nfolds = 5, nw = 1, ) # Init variables + method <- normalize_train_method(method) if (is.numeric(seed)) set.seed(seed) if (method == "gbtree") method <- "gbtreeDefault" args <- named( @@ -325,12 +326,13 @@ adjust_frm <- function(frm, is.logical(verbose) || is.numeric(verbose), is.null(seed) || is.numeric(seed), is.logical(do_cv), - is.character(adj_type), adj_type %in% c("lm", "lasso", "ridge", "gbtree"), + is.character(adj_type), length(adj_type) == 1, !is.na(adj_type), is.null(add_cds) || is.logical(add_cds), is.logical(match_rts), length(match_rts) == 1, !is.na(match_rts), is.null(match_keys) || (is.character(match_keys) && length(match_keys) >= 1 && all(match_keys %in% c("INCHIKEY", "SMILES", "NAME"))) ) + adj_type <- normalize_adj_type(adj_type) if (is.null(seed)) seed <- sample.int(.Machine$integer.max, 1) if (is.null(add_cds)) add_cds <- if (adj_type == "lm") FALSE else TRUE if (length(predictors) == 1 && isFALSE(add_cds) && adj_type != "lm") { @@ -710,7 +712,29 @@ clip_predictions <- function(yhat, y) { yhat } -# Preprocessing ##### +# Helpers ##### + +normalize_train_method <- function(method) { + stopifnot(is.character(method), length(method) == 1, !is.na(method)) + igrepl <- function(pattern, x) grepl(pattern, x, ignore.case = TRUE) + if (igrepl("^(lasso)$", method)) return("lasso") + if (igrepl("^(ridge)$", method)) return("ridge") + if (igrepl("^(gbtree|gbtreeDefault|BRT)$", method)) return("gbtree") + if (igrepl("^(gbtreeRP|BRTRP)$", method)) return("gbtreeRP") + fmt <- "Invalid method '%s'. Allowed values: lasso, ridge, gbtree, gbtreeRP." + stop(sprintf(fmt, method)) +} + +normalize_adj_type <- function(adj_type) { + stopifnot(is.character(adj_type), length(adj_type) == 1, !is.na(adj_type)) + igrepl <- function(pattern, x) grepl(pattern, x, ignore.case = TRUE) + if (igrepl("^(lm)$", adj_type)) return("lm") + if (igrepl("^(lasso)$", adj_type)) return("lasso") + if (igrepl("^(ridge)$", adj_type)) return("ridge") + if (igrepl("^(gbtree|gbtreeDefault|BRT)$", adj_type)) return("gbtree") + fmt <- "Invalid adj_type '%s'. Allowed values: lm, lasso, ridge, gbtree." + stop(sprintf(fmt, adj_type)) +} #' @noRd #' @title Build adjustment data diff --git a/man/selective_measuring.Rd b/man/selective_measuring.Rd index aec9f19..a418d1b 100644 --- a/man/selective_measuring.Rd +++ b/man/selective_measuring.Rd @@ -25,15 +25,17 @@ function.} \item{rt_coef}{Which coefficient to use for scaling RT before clustering. Options are: \itemize{ -\item max_ridge_coef: scale with the maximum absolute coefficient obtained in -ridge regression. I.e., RT will have approximately the same weight as the -most important chemical descriptor. -\item 1: do not scale RT any further, i.e., use standardized RT. The effect of +\item \code{0}: exclude RT from the clustering. +\item 'max' / 'max_ridge_coef': scale with the maximum absolute coefficient +obtained in ridge regression. I.e., RT will have approximately the same +weight as the most important chemical descriptor. +\item \code{1}: do not scale RT any further, i.e., use standardized RT. The effect of leaving RT unscaled is kind of unpredictable, as the ridge coefficients depend on the dataset. If the maximum absolute coefficient is much smaller than 1, RT will dominate the clustering. If it is much larger than 1, RT will have little influence on the clustering. -\item 0: exclude RT from the clustering. +\item 'inf': set all chemical descriptor values to zero, i.e., RT is "infinitely" +more important than any chemical descriptor. }} } \value{ @@ -59,7 +61,7 @@ selects a sensible subset of molecules from the original dataset for re-measurement. The selection process includes: \enumerate{ \item Generating chemical descriptors from the SMILES strings of the molecules. -These are the features used by \code{\link[=train_frm]{train_frm()}} and \code{\link[=adjust_frm]{adjust_frm()}}. +These are the features used by \code{\link[=train_frm]{train_frm()}} and \code{\link[=adjust_frm]{adjust_frm()}}.˝ \item Standardizing chemical descriptors to have zero mean and unit variance. \item Training a Ridge Regression model with the standardized chemical descriptors as features and the retention times as the target variable. From 344eb00899bcd508351888a87ec8f4e4a26d8f58 Mon Sep 17 00:00:00 2001 From: Tobias Schmidt Date: Mon, 12 Jan 2026 09:53:04 +0100 Subject: [PATCH 4/6] Improve future handling --- DESCRIPTION | 2 +- NEWS.md | 8 +++++ R/app.R | 6 ++-- TODOS.md | 87 +++++++++++++++++++++++++++++++++++------------------ 4 files changed, 68 insertions(+), 35 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 57e19c8..3754cbc 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: FastRet Title: Retention Time Prediction in Liquid Chromatography -Version: 1.3.3 +Version: 1.3.4 Authors@R: c( person(given = "Tobias", family = "Schmidt", role = c("aut", "cre", "cph"), email = "tobias.schmidt331@gmail.com", comment = c(ORCID = "0000-0001-9681-9253")), person(given = "Christian", family = "Amesoeder", role = c("aut", "cph"), email = "christian-amesoeder@web.de", comment = c(ORCID = "0000-0002-1668-8351")), diff --git a/NEWS.md b/NEWS.md index 57dd9d0..01c22d0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,13 @@ +# FastRet 1.3.4 + +Internal Improvements: + +1. `start_gui()` and `start_gui_in_devmode()` now rely on + `with(future::plan(...), local = TRUE)` so that temporary future plans are + restored automatically without manual bookkeeping. + # FastRet 1.3.3 API Improvements: diff --git a/R/app.R b/R/app.R index 5a2ab22..87cab5c 100644 --- a/R/app.R +++ b/R/app.R @@ -49,8 +49,7 @@ start_gui <- function(port = 8080, # Use [start_gui_in_devmode()] for development catf("Checking CDK version") check_cdk_version() - oldplan <- future::plan("multisession", workers = nw) - on.exit(future::plan(oldplan), add = TRUE) + with(future::plan("multisession", workers = nw), local = TRUE) catf("Starting FastRet GUI") app <- fastret_app(port, host, reload, nsw) shiny::runApp(app) @@ -140,8 +139,7 @@ start_gui_in_devmode <- function(strategy = "sequential", )) catf("Initializing cluster") - oldplan <- future::plan(strategy) - on.exit(future::plan(oldplan), add = TRUE, after = FALSE) + with(future::plan(strategy), local = TRUE) catf("Starting FastRet GUI in development mode") pkg_root <- dirname(system.file("DESCRIPTION", package = "FastRet")) diff --git a/TODOS.md b/TODOS.md index a3d480a..01b615f 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,45 +1,22 @@ # Todos - [Open](#open) - - [Make sure tests run on Linux and MacOS](#make-sure-tests-run-on-linux-and-macos) - [Update included Measurements](#update-included-measurements) - [Add tests for the shiny GUI](#add-tests-for-the-shiny-gui) - [Remove RP](#remove-rp) - [Ensure test coverage](#ensure-test-coverage) - [Ensure backwards compatibility](#ensure-backwards-compatibility) - - [Resubmit to CRAN](#resubmit-to-cran) - [Done](#done) - [Allow disabling of cross-validation](#allow-disabling-of-cross-validation) - [Fix failing tests in Github CI](#fix-failing-tests-in-github-ci) - [Allow Adjustment based on chemical descriptors](#allow-adjustment-based-on-chemical-descriptors) - [Add gbtree and glmnet as adjustment models](#add-gbtree-and-glmnet-as-adjustment-models) + - [Make sure tests run on Linux and MacOS](#make-sure-tests-run-on-linux-and-macos) + - [Resubmit to CRAN](#resubmit-to-cran) + - [Improve Future Handling](#improve-future-handling) ## Open -### Make sure tests run on Linux and MacOS - -Currently failing tests: - -```log -══ Failed tests ════════════════════════════════════════════════════════════════ -── Error ('test-fit_gbtree.R:47:5'): fit_gbtree works with xgboost 1.7 ───────── - -Error: Error: ! in callr subprocess. -Caused by error: -! Expected `packageVersion("xgboost", lib.loc = new_lib) == package_version("1.7.9.1")` to be TRUE. -Differences: -`actual`: FALSE -`expected`: TRUE - -Installed xgboost version: 1.7.11.1 - -[ FAIL 1 | WARN 0 | SKIP 1 | PASS 94 ] -Error: -! Test failures. -Execution halted -``` - - ### Update included Measurements - Remove `inst/extdata/Measurements_v8.xlsx` @@ -84,10 +61,6 @@ FastRet version 1.2.2 or earlier. Write testcase to ensure that all *.frm functions like adjust_frm, predict.frm, get_predictors, coef.frm, plot.frm work as expected with those. -### Resubmit to CRAN - -Resubmit FastRet v1.3.0 to CRAN. - ## Done ### Allow disabling of cross-validation @@ -174,3 +147,57 @@ and `gbtree` models (as returned by `fit_glmnet()` and `fit_gbtree()`) should be supported as well. Introduce a new argument `adj_type` to `adjust_frm()` that can take values `"lm"` (default), `"glmnet"`, and `"gbtree"` and based on this value decide which adjustment model to fit. + +### Make sure tests run on Linux and MacOS + +Currently failing tests: + +```log +══ Failed tests ════════════════════════════════════════════════════════════════ +── Error ('test-fit_gbtree.R:47:5'): fit_gbtree works with xgboost 1.7 ───────── + +Error: Error: ! in callr subprocess. +Caused by error: +! Expected `packageVersion("xgboost", lib.loc = new_lib) == package_version("1.7.9.1")` to be TRUE. +Differences: +`actual`: FALSE +`expected`: TRUE + +Installed xgboost version: 1.7.11.1 + +[ FAIL 1 | WARN 0 | SKIP 1 | PASS 94 ] +Error: +! Test failures. +Execution halted +``` + +### Resubmit to CRAN + +Resubmit FastRet v1.3.0 to CRAN. + +### Improve Future Handling + +In issue, HenrikBengtsson recommends the following: + +FYI, instead of: + +FastRet/R/app.R (Lines 52 to 53 in 62ede6d) + +> oldplan <- future::plan("multisession", workers = nw) +> on.exit(future::plan(oldplan), add = TRUE) + +you can now do: + +> with(future::plan("multisession", workers = nw), local = TRUE) +> It works the same, but you might find new alternative cleaner. + +There's also: + +FastRet/R/app.R (Lines 143 to 144 in 62ede6d) + +> oldplan <- future::plan(strategy) +> on.exit(future::plan(oldplan), add = TRUE, after = FALSE) + +which can be replaced by: + +> with(future::plan(strategy), local = TRUE) From dc3957ea32e0dcc35e769a260fa2c04c40babb9c Mon Sep 17 00:00:00 2001 From: Tobias Schmidt Date: Tue, 13 Jan 2026 14:04:12 +0100 Subject: [PATCH 5/6] Implement imputation of NAs during adjustment --- .gitignore | 1 + DESCRIPTION | 2 +- NEWS.md | 13 ++++++ R/train.R | 71 ++++++++++++++++++++++--------- tests/testthat/test-predict_frm.R | 22 ++++++++++ 5 files changed, 87 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index d0dd721..acbb8ee 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .Rproj.user .vscode/ *.bkp +*.log check/ docs/ FastRet.Rproj diff --git a/DESCRIPTION b/DESCRIPTION index 3754cbc..a0aa549 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: FastRet Title: Retention Time Prediction in Liquid Chromatography -Version: 1.3.4 +Version: 1.3.5 Authors@R: c( person(given = "Tobias", family = "Schmidt", role = c("aut", "cre", "cph"), email = "tobias.schmidt331@gmail.com", comment = c(ORCID = "0000-0001-9681-9253")), person(given = "Christian", family = "Amesoeder", role = c("aut", "cph"), email = "christian-amesoeder@web.de", comment = c(ORCID = "0000-0002-1668-8351")), diff --git a/NEWS.md b/NEWS.md index 01c22d0..59f4412 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,17 @@ +# FastRet 1.3.5 + +Bugfix: + +1. `predict.frm()` now imputes missing/non-finite values not only for base model + predictors, but also for adjustment model predictors. This prevents `NA` + predictions if the adjustment model depends on predictors that are + missing/non-finite in the new data. + +Internal Improvements: + +1. Consolidated the imputation logic in `predict.frm()` into a shared internal + helper. # FastRet 1.3.4 diff --git a/R/train.R b/R/train.R index 5a2e645..75f78f3 100644 --- a/R/train.R +++ b/R/train.R @@ -297,7 +297,7 @@ train_frm <- function(df, method = "lasso", verbose = 1, nfolds = 5, nw = 1, #' frm <- read_rp_lasso_model_rds() #' new_data <- read_rpadj_xlsx() #' frm_adj <- adjust_frm(frm, new_data, verbose = 0) -#' +#' frm_adj <- adjust_frm(frm, new_data, verbose = 0, adj_type = "lasso") adjust_frm <- function(frm, new_data, predictors = 1:6, @@ -508,9 +508,9 @@ predict.frm <- function(object = train_frm(), # Init locals logf <- if (verbose >= 1) catf else null - cd_pds <- get_predictors(object, base = TRUE, adjust = FALSE) - dgp <- get_dgp(cd_pds) - iat <- any(grepl(":", cd_pds)) + pds <- get_predictors(object, base = TRUE, adjust = FALSE) + dgp <- get_dgp(pds) + iat <- any(grepl(":", pds)) rm_nzv <- FALSE rm_na <- FALSE add_cds <- TRUE @@ -520,47 +520,40 @@ predict.frm <- function(object = train_frm(), errmsg <- "Model has not been adjusted yet. Please adjust the model first using `adjust_frm()`." stop(errmsg) } - if (!all(cd_pds %in% colnames(df))) { - logf("Chemical descriptors not found in newdata. Trying to calculate them from the provided SMILES.") + if (!all(pds %in% colnames(df))) { + logf("Predictors not found in newdata. Trying to calculate them from the provided SMILES.") df <- preprocess_data( df, dgp, iat, verbose, 1, rm_nzv, rm_na, add_cds, rm_ucs = TRUE, rt_terms = 1, mandatory = c("NAME", "SMILES") ) } - if (!all(cd_pds %in% colnames(df))) { - missing <- paste(setdiff(cd_pds, colnames(df)), collapse = ", ") - errmsg <- paste("The following cd_pds are missing in `df`: ", missing) + if (!all(pds %in% colnames(df))) { + missing <- paste(setdiff(pds, colnames(df)), collapse = ", ") + errmsg <- paste("The following predictors are missing in `df`: ", missing) stop(errmsg) } # Impute missing values - if (impute && any(is.na(df[, cd_pds]))) { - nap <- cd_pds[colSums(is.na(df[, cd_pds])) > 0] - napstr <- paste(nap, collapse = ", ") - logf("NA values found for following cd_pds: %s", napstr) - logf("Replacing NA values by column means of training data") + if (impute) { train_df <- object$df - if (!all(cd_pds %in% colnames(train_df))) { + if (!all(pds %in% colnames(train_df))) { # We don't store interaction terms and/or polynomial features in # the training data frame, so we need to preprocess it again to get - # these features. + # these features, if they are required for imputation. train_df <- preprocess_data( train_df, dgp, iat, verbose, nw = 1, rm_near_zero_var = FALSE, rm_na = FALSE, add_cds = TRUE, rm_ucs = TRUE, rt_terms = 1, mandatory = c("NAME", "SMILES") ) } - for (p in nap) { - col_mean <- mean(train_df[[p]], na.rm = TRUE) - df[[p]][is.na(df[[p]])] <- col_mean - } + df <- impute_missing_predictors(df, pds, train_df, logf) } # Predict retention times using base model adjust <- !is.null(object$adj) && (isTRUE(adjust) || is.null(adjust)) logf("Predicting retention times") - yhat <- as.numeric(predict(object$model, as.matrix(df[, cd_pds]))) + yhat <- as.numeric(predict(object$model, as.matrix(df[, pds]))) # Adjust predictions if requested if (adjust) { @@ -580,6 +573,11 @@ predict.frm <- function(object = train_frm(), ) } adj_df <- adj_df[, adj_pds, drop = FALSE] + + # Impute missing values for adjustment predictors (can happen for + # exotic compounds where CD calculation fails for a few features). + if (impute) adj_df <- impute_missing_predictors(adj_df, adj_pds, object$adj$df, logf) + X_adj <- if (inherits(object$adj$model, "lm")) adj_df else as.matrix(adj_df) x <- try(yhat <- as.numeric(predict(object$adj$model, X_adj))) } @@ -595,6 +593,37 @@ predict.frm <- function(object = train_frm(), as.numeric(yhat) } +impute_missing_predictors <- function(df, pds, train_df, logf = null) { + if (length(pds) == 0) return(df) + if (!all(pds %in% colnames(df))) { + missing <- paste(setdiff(pds, colnames(df)), collapse = ", ") + stop(sprintf("The following predictors are missing in `df`: %s", missing)) + } + if (!all(pds %in% colnames(train_df))) { + missing <- paste(setdiff(pds, colnames(train_df)), collapse = ", ") + fmt <- "Required predictors missing in training data: %s. This indicates a model/training-data mismatch." + stop(sprintf(fmt, missing)) + } + + bad <- !is.finite(as.matrix(df[, pds, drop = FALSE])) + if (any(bad)) df[, pds][bad] <- NA + if (!anyNA(df[, pds, drop = FALSE])) return(df) + + nap <- pds[colSums(is.na(df[, pds, drop = FALSE])) > 0] + if (length(nap) == 0) return(df) + + logf("NA values found for following predictors: %s", paste(nap, collapse = ", ")) + logf("Replacing NA values by column means of training data") + + for (p in nap) { + col_mean <- mean(train_df[[p]], na.rm = TRUE) + if (!is.finite(col_mean) || is.na(col_mean)) col_mean <- 0 + df[[p]][is.na(df[[p]])] <- col_mean + } + + df +} + get_req_pkgs <- function(frm) { model_types <- get_model_type(frm) pkgs <- c( diff --git a/tests/testthat/test-predict_frm.R b/tests/testthat/test-predict_frm.R index 5ff9c70..6e9a9cd 100644 --- a/tests/testthat/test-predict_frm.R +++ b/tests/testthat/test-predict_frm.R @@ -57,4 +57,26 @@ test_that("predict.frm imputes CDs if they are NA", { # Check Results expect_equal(round(yhat, 2), c(4.31, 4.54)) expect_equal(round(yhat_no_imputing, 2), c(NaN, 4.54)) + + # Now test imputation for adjusted predictions. + # + # Adjustment dataframes never remove NAs or NZV-predictors during training. + # Therefore the predict function assumes them to be present during + # prediction as well. To mimic this behavior, store an apprpriately + # preprocessed dataframe in frm$adj$df. + frm$adj$model <- frm$model # Reuse same model, which depends on Kier3 + frm$adj$df <- preprocess_data( + frm$df, degree_polynomial = 1, interaction_terms = FALSE, verbose = 0, + nw = 1, rm_near_zero_var = FALSE, rm_na = FALSE, add_cds = TRUE, + rm_ucs = TRUE, rt_terms = 1 + ) + + # Without imputation, NA in adjustment predictors yields NA predictions. + yhat_no_impute <- predict.frm(frm, new_data, adjust = TRUE, impute = FALSE, clip = FALSE, verbose = 0) + expect_true(anyNA(yhat_no_impute)) + + # With imputation, predictions must be finite. + yhat <- predict.frm(frm, new_data, adjust = TRUE, impute = TRUE, clip = FALSE, verbose = 0) + expect_false(anyNA(yhat)) + expect_true(all(is.finite(yhat))) }) From c9d7d544055bd017542026df936af6a14f1b4609 Mon Sep 17 00:00:00 2001 From: toscm Date: Thu, 19 Mar 2026 19:51:17 +0100 Subject: [PATCH 6/6] Remove Measurements_v10.xlsx and TODOS.md --- TODOS.md | 203 ---------------------------- misc/datasets/Measurements_v10.xlsx | Bin 102705 -> 0 bytes 2 files changed, 203 deletions(-) delete mode 100644 TODOS.md delete mode 100644 misc/datasets/Measurements_v10.xlsx diff --git a/TODOS.md b/TODOS.md deleted file mode 100644 index 01b615f..0000000 --- a/TODOS.md +++ /dev/null @@ -1,203 +0,0 @@ -# Todos - -- [Open](#open) - - [Update included Measurements](#update-included-measurements) - - [Add tests for the shiny GUI](#add-tests-for-the-shiny-gui) - - [Remove RP](#remove-rp) - - [Ensure test coverage](#ensure-test-coverage) - - [Ensure backwards compatibility](#ensure-backwards-compatibility) -- [Done](#done) - - [Allow disabling of cross-validation](#allow-disabling-of-cross-validation) - - [Fix failing tests in Github CI](#fix-failing-tests-in-github-ci) - - [Allow Adjustment based on chemical descriptors](#allow-adjustment-based-on-chemical-descriptors) - - [Add gbtree and glmnet as adjustment models](#add-gbtree-and-glmnet-as-adjustment-models) - - [Make sure tests run on Linux and MacOS](#make-sure-tests-run-on-linux-and-macos) - - [Resubmit to CRAN](#resubmit-to-cran) - - [Improve Future Handling](#improve-future-handling) - -## Open - -### Update included Measurements - -- Remove `inst/extdata/Measurements_v8.xlsx` -- Add `data/datasets.rda`, containing datasets described in - [../freda/README.md](../freda/README.md), i.e., HILIC_1, HILIC_2, - HILIC_Retip_1, HILIC_Retip_2, RP_1, RP_2, RP_AXMM_1, RP_AXMM_2, RP_B_1, - RP_B_2, RP_Flat, RP_FR25_Flat, RP_Steep, RP_T25_Flat, RP_T25_FR25_Flat, - RP_T25_FR25_Steep, RP_Val, RP_Val_Flat, RP_Val_FR25_Flat, RP_Val_Steep, - RP_Val_T25_Flat, RP_Val_T25_FR25_Flat, RP_Val_T25_FR25_Steep. - -### Add tests for the shiny GUI - -Do some research about best-practices for testing shiny applications. -If no good options exists, define a minimal set of tests that ensure that the -shiny application works as expected. And then execute each test manually. - -### Remove RP - -Replace all mentions of the following objects with corresponding objects from -the new `datasets.rda` object. See issue 'Update included Measurements'. - -- `RP.rda` -- `RP.xlsx` -- `RP_adj.xlsx` -- `RP_lass_model.rds` -- `Measurements_v8.xlsx` - -Then mark the all old objects as deprecated in the documentation - -### Ensure test coverage - -Ensure we have tests for the following scenarios: - -1. Train a ridge model, predict with it, adjust with lm/RT, predict with adjusted model -2. Train a lasso model, predict with it, adjust with gbtree/RTTCD, predict with adjusted model -3. Train a gbtree model, predict with it, adjust with lasso/RTCD, predict with adjusted model - -### Ensure backwards compatibility - -Include an adjusted lasso and gbtree model that was trained and adjusted with -FastRet version 1.2.2 or earlier. Write testcase to ensure that all *.frm -functions like adjust_frm, predict.frm, get_predictors, coef.frm, plot.frm work -as expected with those. - -## Done - -### Allow disabling of cross-validation - -In `adjust_frm` allow `docv = FALSE` (do-cross-validation). In `train_frm` allow -`docv = FALSE` (do-cross-validation). The corresponding `cv` element of the -returned object should be `NULL` in those cases. The docs and tests must be -adjusted accordingly. - -### Fix failing tests in Github CI - -The following tests: - -```txt -── Error ('test-train_frm-gbtree.R:5:5'): train_frm works if `method == "GBTree"` ── - -Error in `FUN(X[[i]], ...)`: subscript out of bounds -Backtrace: -1. └─FastRet::train_frm(...) at test-train_frm-gbtree.R:5:5 -2. └─base::lapply(tmp, "[[", 2) - -── Error ('test-fit_gbtree.R:8:5'): fit.gbtrees works as expected ────────────── -Error in `begin_iteration:end_iteration`: argument of length 0 -Backtrace: -1. └─FastRet:::fit_gbtree(df, verbose = 0) at test-fit_gbtree.R:8:5 -2. └─FastRet:::fit_gbtree_grid(...) -3. └─xgboost::xgb.train(...) - -── Error ('test-fit_gbtree.R:16:5'): fit.gbtrees works for data from reverse phase column ── -Error in `begin_iteration:end_iteration`: argument of length 0 -Backtrace: -1. └─FastRet:::fit_gbtree(df, verbose = 0) at test-fit_gbtree.R:16:5 -2. └─FastRet:::fit_gbtree_grid(...) -3. └─xgboost::xgb.train(...) -``` - -fail in: - -- https://www.r-project.org/nosvn/R.check/r-devel-linux-x86_64-fedora-clang/FastRet-00check.html -- https://www.r-project.org/nosvn/R.check/r-devel-linux-x86_64-debian-gcc/FastRet-00check.html -- https://www.r-project.org/nosvn/R.check/r-devel-linux-x86_64-fedora-gcc/FastRet-00check.html -- https://www.r-project.org/nosvn/R.check/r-release-linux-x86_64/FastRet-00check.html -- https://www.r-project.org/nosvn/R.check/r-oldrel-windows-x86_64/FastRet-00check.html - -I strongly assume, it has to do with the update of xgboost from version 1.7.11 -to 3.1.2.1 that was published on 2025-12-03. I.e., we need to check if we can -find a way that works with both xgboost versions (1.x.x) and (3.x.x). If we -cannot, we should introduce a version check and call different code paths -depending on the installed xgboost version. In this case, we should add tests in -.github/workflows for both xgboost versions to make sure that both code paths are -tested in the future. E.g. doing something linke this: - -- {os: macos-latest, r: 'release', xgboost: 'lastest'} -- {os: windows-latest, r: 'release', xgboost: 'lastest'} -- {os: ubuntu-latest, r: 'devel', xgboost: 'lastest', http-user-agent: 'release'} -- {os: ubuntu-latest, r: 'release', xgboost: 'lastest'} -- {os: ubuntu-latest, r: 'release', xgboost: '1.7.11'} -- {os: ubuntu-latest, r: 'oldrel-1', xgboost: 'lastest'} -- {os: ubuntu-latest, r: '4.1', xgboost: 'lastest'} - -Instead of - -- {os: macos-latest, r: 'release'} -- {os: windows-latest, r: 'release'} -- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} -- {os: ubuntu-latest, r: 'release'} -- {os: ubuntu-latest, r: 'release'} -- {os: ubuntu-latest, r: 'oldrel-1'} -- {os: ubuntu-latest, r: '4.1'} - -in `.github/workflows/r-cmd-check.yaml`. - -### Allow Adjustment based on chemical descriptors - -In `adjust_frm` add argument `add_cds`. If `add_cds = TRUE` chemical descriptors -should be calculated using `preprocess_data()` (as done in `train_frm()`) but -always without interaction terms and without polynomial terms. The resulting CDs -should be included in the adjustment model. - -### Add gbtree and glmnet as adjustment models - -Currently, adjustment models are only fitted using `lm`. In addition, `glmnet` -and `gbtree` models (as returned by `fit_glmnet()` and `fit_gbtree()`) should be -supported as well. Introduce a new argument `adj_type` to `adjust_frm()` that -can take values `"lm"` (default), `"glmnet"`, and `"gbtree"` and based on this -value decide which adjustment model to fit. - -### Make sure tests run on Linux and MacOS - -Currently failing tests: - -```log -══ Failed tests ════════════════════════════════════════════════════════════════ -── Error ('test-fit_gbtree.R:47:5'): fit_gbtree works with xgboost 1.7 ───────── - -Error: Error: ! in callr subprocess. -Caused by error: -! Expected `packageVersion("xgboost", lib.loc = new_lib) == package_version("1.7.9.1")` to be TRUE. -Differences: -`actual`: FALSE -`expected`: TRUE - -Installed xgboost version: 1.7.11.1 - -[ FAIL 1 | WARN 0 | SKIP 1 | PASS 94 ] -Error: -! Test failures. -Execution halted -``` - -### Resubmit to CRAN - -Resubmit FastRet v1.3.0 to CRAN. - -### Improve Future Handling - -In issue, HenrikBengtsson recommends the following: - -FYI, instead of: - -FastRet/R/app.R (Lines 52 to 53 in 62ede6d) - -> oldplan <- future::plan("multisession", workers = nw) -> on.exit(future::plan(oldplan), add = TRUE) - -you can now do: - -> with(future::plan("multisession", workers = nw), local = TRUE) -> It works the same, but you might find new alternative cleaner. - -There's also: - -FastRet/R/app.R (Lines 143 to 144 in 62ede6d) - -> oldplan <- future::plan(strategy) -> on.exit(future::plan(oldplan), add = TRUE, after = FALSE) - -which can be replaced by: - -> with(future::plan(strategy), local = TRUE) diff --git a/misc/datasets/Measurements_v10.xlsx b/misc/datasets/Measurements_v10.xlsx deleted file mode 100644 index b6ef79f2b0fbd759f56f87d81c903a60ae1f041b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102705 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 CHIjM&