From 59b6c795e4bde039f388e0044f491ac60351ff32 Mon Sep 17 00:00:00 2001 From: Emily Boyer Date: Thu, 30 Jul 2026 14:47:58 -0600 Subject: [PATCH 1/2] Strip literal NA tokens from COG bar-chart labels --- R/plots_featureimportance.R | 41 +++++++++++++++++++++++++-- tests/testthat/test-makeCogBarChart.R | 23 +++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/R/plots_featureimportance.R b/R/plots_featureimportance.R index 9d3bb24..5709521 100644 --- a/R/plots_featureimportance.R +++ b/R/plots_featureimportance.R @@ -284,6 +284,34 @@ makeFeatureImportancePlot <- function( } +#' Drop literal "NA" tokens from a COG_name string +#' +#' The annotation source records unnamed COGs as the literal string "NA" and +#' space-joins them into `COG_name` (e.g. "Alanine racemase NA NA NA"). This +#' removes those standalone tokens, returning `NA` when nothing meaningful is +#' left so callers can fall back to the bare COG id. +#' +#' @param x Character vector of `COG_name` values. +#' @return A character vector with `NA` tokens removed; `NA_character_` where no +#' real name remains. +#' @keywords internal +#' @noRd +.clean_cog_name <- function(x) { + vapply( + x, + function(s) { + if (is.na(s)) { + return(NA_character_) + } + toks <- setdiff(strsplit(s, "[[:space:]]+")[[1]], c("NA", "")) + if (!length(toks)) NA_character_ else paste(toks, collapse = " ") + }, + character(1), + USE.NAMES = FALSE + ) +} + + #' Horizontal bar chart of the most common COGs #' #' Counts COG occurrences across the features in an annotation-enriched @@ -316,9 +344,16 @@ makeCogBarChart <- function(enriched_tbl, top_n = 15) { rows <- do.call(rbind, lapply(seq_len(nrow(cog_df)), function(i) { cogs <- trimws(strsplit(cog_df$COG[i], ",", fixed = TRUE)[[1]]) - names <- if ("COG_name" %in% names(cog_df) && - !is.na(cog_df$COG_name[i])) { - n <- trimws(strsplit(cog_df$COG_name[i], ";", fixed = TRUE)[[1]]) + # The annotation source stores unnamed COGs as the literal token "NA" and + # space-joins them into COG_name (e.g. "Alanine racemase NA NA NA"); + # .clean_cog_name() strips those so they don't leak into the bar labels. + clean <- if ("COG_name" %in% names(cog_df)) { + .clean_cog_name(cog_df$COG_name[i]) + } else { + NA_character_ + } + names <- if (!is.na(clean)) { + n <- trimws(strsplit(clean, ";", fixed = TRUE)[[1]]) rep_len(n, length(cogs)) } else { rep(NA_character_, length(cogs)) diff --git a/tests/testthat/test-makeCogBarChart.R b/tests/testthat/test-makeCogBarChart.R index c3f8482..1c39d9e 100644 --- a/tests/testthat/test-makeCogBarChart.R +++ b/tests/testthat/test-makeCogBarChart.R @@ -60,3 +60,26 @@ test_that("makeCogBarChart respects top_n parameter", { result <- makeCogBarChart(df, top_n = 5) expect_s3_class(result, "plotly") }) + +test_that(".clean_cog_name strips standalone NA tokens", { + expect_equal(.clean_cog_name("Alanine racemase NA NA NA"), "Alanine racemase") + expect_equal( + .clean_cog_name("D-serine deaminase NA"), "D-serine deaminase" + ) + # NA tokens in the middle collapse too + expect_equal(.clean_cog_name("foo NA bar"), "foo bar") +}) + +test_that(".clean_cog_name returns NA when nothing meaningful remains", { + expect_true(is.na(.clean_cog_name("NA"))) + expect_true(is.na(.clean_cog_name("NA NA NA"))) + expect_true(is.na(.clean_cog_name(NA_character_))) +}) + +test_that(".clean_cog_name leaves clean names untouched and is vectorised", { + expect_equal(.clean_cog_name("Beta-lactamase class C"), "Beta-lactamase class C") + expect_equal( + .clean_cog_name(c("Helicase NA", "NA", "Transcription")), + c("Helicase", NA, "Transcription") + ) +}) From 7f2b951e7086a84170c2a7269e35341d7c12e02d Mon Sep 17 00:00:00 2001 From: Alexander McKim Date: Fri, 31 Jul 2026 13:50:16 -0600 Subject: [PATCH 2/2] fixing semicolon parse --- R/plots_featureimportance.R | 37 +++++++++++---------------- tests/testthat/test-makeCogBarChart.R | 11 ++++++++ 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/R/plots_featureimportance.R b/R/plots_featureimportance.R index e59818e..6c79849 100644 --- a/R/plots_featureimportance.R +++ b/R/plots_featureimportance.R @@ -284,31 +284,26 @@ makeFeatureImportancePlot <- function( } -#' Drop literal "NA" tokens from a COG_name string +#' Strip standalone "NA" tokens from a COG_name string #' -#' The annotation source records unnamed COGs as the literal string "NA" and -#' space-joins them into `COG_name` (e.g. "Alanine racemase NA NA NA"). This -#' removes those standalone tokens, returning `NA` when nothing meaningful is -#' left so callers can fall back to the bare COG id. +#' The annotation source stores unnamed COG name slots as the literal string +#' "NA". Splits on `;` first so tokens adjacent to a semicolon get caught too. #' #' @param x Character vector of `COG_name` values. -#' @return A character vector with `NA` tokens removed; `NA_character_` where no -#' real name remains. +#' @return Cleaned vector; `NA_character_` where no real name remains. #' @keywords internal #' @noRd .clean_cog_name <- function(x) { - vapply( - x, - function(s) { - if (is.na(s)) { - return(NA_character_) - } - toks <- setdiff(strsplit(s, "[[:space:]]+")[[1]], c("NA", "")) - if (!length(toks)) NA_character_ else paste(toks, collapse = " ") - }, - character(1), - USE.NAMES = FALSE - ) + clean_one <- function(s) { + if (is.na(s)) return(NA_character_) + kept <- character(0) + for (p in trimws(strsplit(s, ";", fixed = TRUE)[[1]])) { + toks <- setdiff(strsplit(p, "[[:space:]]+")[[1]], c("NA", "")) + if (length(toks)) kept <- c(kept, paste(toks, collapse = " ")) + } + if (!length(kept)) NA_character_ else paste(kept, collapse = "; ") + } + vapply(x, clean_one, character(1), USE.NAMES = FALSE) } @@ -342,9 +337,7 @@ makeCogBarChart <- function(enriched_tbl, top_n = 15) { rows <- do.call(rbind, lapply(seq_len(nrow(cog_df)), function(i) { cogs <- trimws(strsplit(cog_df$COG[i], ",", fixed = TRUE)[[1]]) - # The annotation source stores unnamed COGs as the literal token "NA" and - # space-joins them into COG_name (e.g. "Alanine racemase NA NA NA"); - # .clean_cog_name() strips those so they don't leak into the bar labels. + # .clean_cog_name() strips "NA" placeholder tokens the source stuffs in. clean <- if ("COG_name" %in% names(cog_df)) { .clean_cog_name(cog_df$COG_name[i]) } else { diff --git a/tests/testthat/test-makeCogBarChart.R b/tests/testthat/test-makeCogBarChart.R index 1c39d9e..cde925d 100644 --- a/tests/testthat/test-makeCogBarChart.R +++ b/tests/testthat/test-makeCogBarChart.R @@ -76,6 +76,17 @@ test_that(".clean_cog_name returns NA when nothing meaningful remains", { expect_true(is.na(.clean_cog_name(NA_character_))) }) +test_that(".clean_cog_name strips NA tokens adjacent to semicolons", { + expect_equal( + .clean_cog_name("Foo NA NA NA; Bar NA NA"), + "Foo; Bar" + ) + expect_equal( + .clean_cog_name("Foo NA; NA; Bar"), + "Foo; Bar" + ) +}) + test_that(".clean_cog_name leaves clean names untouched and is vectorised", { expect_equal(.clean_cog_name("Beta-lactamase class C"), "Beta-lactamase class C") expect_equal(