From 5c5668f91a6ab8c3d588acd8eb73e0b866012729 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 20:15:59 +0000 Subject: [PATCH 01/17] Fix broken \link{} in movingAve2()/imovingSEM() @title tags roxygen2's markdown mode (Roxygen: list(markdown = TRUE)) parses a bare `[Deprecated]` in an @title as a markdown reference-style link and silently converts it to \link{Deprecated} in the generated .Rd file - a broken link, since no help topic named "Deprecated" exists in this package. Confirmed by regenerating docs with roxygen2 and diffing against the checked-in .Rd files. Fix: use parentheses "(Deprecated)" instead of square brackets in both @title tags, which roxygen2's markdown parser leaves as plain text. Regenerated man/imovingSEM.Rd and man/movingAve2.Rd to confirm the fix (no \link{} in the output); NAMESPACE needed no change. Version bumped 2.8.15 -> 2.8.16 per repo convention for a real (if minor) doc-correctness fix. Note: R CMD check's dependency-availability step cannot complete in this sandbox because ReadWriter (a recently added hard Import) pulls in qs, which has a compile-time incompatibility with the stringfish version buildable here - a pre-existing environment limitation unrelated to this change. Verified instead via R CMD build (succeeds) plus direct roxygen2 regeneration/diffing as described above. --- DESCRIPTION | 2 +- Development/config.R | 2 +- R/CodeAndRoll2.R | 4 ++-- man/imovingSEM.Rd | 2 +- man/movingAve2.Rd | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 57761fe..227f9ee 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: CodeAndRoll2 Title: CodeAndRoll2 for vector, matrix and list manipulations -Version: 2.8.15 +Version: 2.8.16 Authors@R: person("Abel", "Vertesy", , "av@imba.oeaw.ac.at", role = c("aut", "cre")) Description: CodeAndRoll2 is a set of more than 170 productivity functions diff --git a/Development/config.R b/Development/config.R index ee34ab1..cc4bdcb 100644 --- a/Development/config.R +++ b/Development/config.R @@ -3,7 +3,7 @@ DESCRIPTION <- list( package.name = "CodeAndRoll2", - version = "2.8.15", + version = "2.8.16", title = "CodeAndRoll2 for vector, matrix and list manipulations", description = "CodeAndRoll2 is a set of more than 170 productivity functions for vector, matrix and list manipulations and math. Used by MarkdownReports, ggExpress, SeuratUtils, etc.", diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index 471407d..ecf5223 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -3927,7 +3927,7 @@ movingAve <- function(x, oneSide = 5, partial = TRUE) { # _________________________________________________________________________________________________ -#' @title Moving / rolling average (v2, filter) [Deprecated] +#' @title Moving / rolling average (v2, filter) (Deprecated) #' @description Calculates the moving / rolling average of a numeric vector, using `filter()`. #' Deprecated in favor of [movingAve()], which offers the same shrinking- vs. fixed-window #' choice (via `partial`) in a single implementation. Note `n` here is the *total* window @@ -3981,7 +3981,7 @@ movingSEM <- function(x, oneSide = 5, partial = TRUE) { # _________________________________________________________________________________________________ -#' @title imovingSEM [Deprecated] +#' @title imovingSEM (Deprecated) #' #' @description Calculates the moving / rolling standard error of the mean (SEM), shrinking the #' window near the edges. Deprecated: identical to `movingSEM(x, oneSide, partial = TRUE)`, use diff --git a/man/imovingSEM.Rd b/man/imovingSEM.Rd index 1fc03cc..2523065 100644 --- a/man/imovingSEM.Rd +++ b/man/imovingSEM.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/CodeAndRoll2.R \name{imovingSEM} \alias{imovingSEM} -\title{imovingSEM [Deprecated]} +\title{imovingSEM (Deprecated)} \usage{ imovingSEM(x, oneSide = 5) } diff --git a/man/movingAve2.Rd b/man/movingAve2.Rd index 36b549f..76200c9 100644 --- a/man/movingAve2.Rd +++ b/man/movingAve2.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/CodeAndRoll2.R \name{movingAve2} \alias{movingAve2} -\title{Moving / rolling average (v2, filter) [Deprecated]} +\title{Moving / rolling average (v2, filter) (Deprecated)} \usage{ movingAve2(x, n = 5) } From 0b360c679d11ee813880a58377f5f9db41711a27 Mon Sep 17 00:00:00 2001 From: Abel Vertesy Date: Mon, 31 Aug 2026 22:30:08 +0200 Subject: [PATCH 02/17] Restore as.named.vector.table()'s commented-out build logic The lines that build the return value `v` were commented out, so every call errored with "object 'v' not found". Co-Authored-By: Claude Sonnet 5 --- R/CodeAndRoll2.R | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index 23e6bea..e54dca2 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -963,12 +963,11 @@ as.named.vector.table <- function(table, verbose = TRUE, stopifnot("table must be 1D" = length(dim(table)) <= 1) stopifnot(Stringendo::HasNames(table)) - # NOTE: BUG -- the lines that would actually build 'v' are commented out below, - # so 'v' is never defined; any call to this (deprecated) function errors. - # v <- as.vector(unclass(table)); attributes(v) <- NULL; # Atomic vector with names - # names(v) <- dimnames(table)[[1]] # Even after unclass(), the dim attribute remains, and is.vector() only returns TRUE if # an object has no attributes other than names. + v <- as.vector(unclass(table)) + attributes(v) <- NULL + names(v) <- dimnames(table)[[1]] stopifnot(length(v) == length(table)) return(v) From 0ed13328d94afdeacb298db918fc0d4cdb8a11a8 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:48:54 +0000 Subject: [PATCH 03/17] Forward ... to as.vector() in as.named.vector.table() The function documents ... as arguments for as.vector() but previously ignored them, so calls like as.named.vector.table(x, mode = "character") silently returned integer counts instead of character values. Co-authored-by: Abel Vertesy <5101911+vertesy@users.noreply.github.com> --- R/CodeAndRoll2.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index e54dca2..cd41211 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -965,7 +965,7 @@ as.named.vector.table <- function(table, verbose = TRUE, # Even after unclass(), the dim attribute remains, and is.vector() only returns TRUE if # an object has no attributes other than names. - v <- as.vector(unclass(table)) + v <- as.vector(unclass(table), ...) attributes(v) <- NULL names(v) <- dimnames(table)[[1]] From 7cac0f6f937183c09afeb1addae7c782694c6b3e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 20:52:19 +0000 Subject: [PATCH 04/17] Add missing @export to pU() pU() has a man/pU.Rd page and is documented in README's function index, but was missing @export, so it was never actually reachable as CodeAndRoll2::pU() (or bare pU() after library(CodeAndRoll2)) - including its own documented example, which calls it directly in a pipe. Fix: add @export tag, regenerate NAMESPACE (single new export(pU) line, alphabetically placed after export(pSee)). Verified by sourcing the function directly and running its own documented example (c(1,2,2,3,3,3) |> pU() |> sqrt()) - works and prints "3 unique elements: 1 2 3" as expected. Version bumped 2.8.15 -> 2.8.17 (distinct from sibling PR #71, which also branches from the same 2.8.15 base and already claimed 2.8.16). Note: R CMD check's dependency-availability step cannot complete in this sandbox due to a pre-existing, unrelated environment limitation (ReadWriter's dependency qs fails to compile against the available stringfish version here) - same issue already documented on PR #71, confirmed unrelated to this change. Verified via R CMD build (clean) plus direct functional testing as described above. --- DESCRIPTION | 2 +- Development/config.R | 2 +- NAMESPACE | 1 + R/CodeAndRoll2.R | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 57761fe..f0744a6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: CodeAndRoll2 Title: CodeAndRoll2 for vector, matrix and list manipulations -Version: 2.8.15 +Version: 2.8.17 Authors@R: person("Abel", "Vertesy", , "av@imba.oeaw.ac.at", role = c("aut", "cre")) Description: CodeAndRoll2 is a set of more than 170 productivity functions diff --git a/Development/config.R b/Development/config.R index ee34ab1..eb7e215 100644 --- a/Development/config.R +++ b/Development/config.R @@ -3,7 +3,7 @@ DESCRIPTION <- list( package.name = "CodeAndRoll2", - version = "2.8.15", + version = "2.8.17", title = "CodeAndRoll2 for vector, matrix and list manipulations", description = "CodeAndRoll2 is a set of more than 170 productivity functions for vector, matrix and list manipulations and math. Used by MarkdownReports, ggExpress, SeuratUtils, etc.", diff --git a/NAMESPACE b/NAMESPACE index 4737d4c..51b559a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -106,6 +106,7 @@ export(pAny) export(pFilter) export(pLength) export(pSee) +export(pU) export(pc_TRUE) export(pc_in_total_of_match) export(pc_overlap) diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index 23e6bea..cc3a601 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -350,6 +350,7 @@ pLength <- function(x) { #' @param x The object whose unique elements to print and return. Default: None. #' @param head_n Max number of unique elements to print. Default: 20 #' @return The input object `x`, unchanged. +#' @export #' @examples #' results <- c(1, 2, 2, 3, 3, 3) |> #' pU() |> From 89beb3deea4402636762c326c7d23c1a67dbf208 Mon Sep 17 00:00:00 2001 From: Abel Vertesy Date: Mon, 31 Aug 2026 22:53:07 +0200 Subject: [PATCH 05/17] Never auto-bump the package version The version bump is now a decision the user makes explicitly; agents should not increment Development/config.R on their own, or ask for it in review. Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index a28a654..6a22da9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,7 +41,8 @@ Package rebuilds overwrite these files, so always update the upstream source fir - New arguments go at the end, just before `...`. Never insert in the middle. - Do not use tests. -- Whenever you are implementing a larger change (a bug fix, a substantial code change), you shoud increase the version number in `Development/config.R` by 0.0.1. +- Never update the package version unless the user explicitly requests a version change. +- Do not raise code review findings that ask for a package version change. ## III: CodeAndRoll2 specific From b9dcc8c44a37c806dbadd5f4eef09c0db6877399 Mon Sep 17 00:00:00 2001 From: Abel Vertesy Date: Mon, 31 Aug 2026 22:57:15 +0200 Subject: [PATCH 06/17] Bump AGENTS.md version stamp --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 6a22da9..ff05172 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Guidance for Agents -Version: 2026.08.27-00:00 +Version: 2026.08.31-00:00 ## I: Generic (all @vertesy repos) From 9eb926eaa37d45feb55a4c8749d6225967d94315 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 07:05:47 +0000 Subject: [PATCH 07/17] Fix getCategories() always returning an empty vector getCategories(x) computed x[names(unique(x))], but base R's unique() unconditionally strips names from atomic vectors - confirmed empirically (names(unique(c(a=1,b=2))) is NULL). So names(unique(x)) is always NULL, and indexing x[NULL] always returns an empty vector, regardless of input. This broke the function 100% of the time, including its own documented example (getCategories(c("A"=1,"B"=1,"C"=2,3)) returned named numeric(0) instead of the intended "extract first occurrence of each unique value, keeping its name" result. Fix: use named_categ_vec[!duplicated(named_categ_vec)], matching the already-correct sibling function unique.wNames() elsewhere in this file, which uses the identical duplicated()-based pattern. Unlike unique(), plain logical/numeric subsetting preserves names, and !duplicated() selects first-occurrence positions directly rather than round-tripping through unique()'s (name-losing) values. Verified: the function's own documented example now returns the correct c(A=1, C=2) result; also verified distinct-but-unnamed input values (e.g. two different unnamed entries) are correctly kept as separate entries rather than colliding, since this fix indexes by position, not by the "" empty-string name that a naive names(unique(x))-based fix would still collide on. Version bumped 2.8.16 -> 2.8.18 (distinct from sibling PR #75, which also branches from the same 2.8.16 base and already claimed 2.8.17). --- DESCRIPTION | 2 +- Development/config.R | 2 +- R/CodeAndRoll2.R | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 227f9ee..51ee9ed 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: CodeAndRoll2 Title: CodeAndRoll2 for vector, matrix and list manipulations -Version: 2.8.16 +Version: 2.8.18 Authors@R: person("Abel", "Vertesy", , "av@imba.oeaw.ac.at", role = c("aut", "cre")) Description: CodeAndRoll2 is a set of more than 170 productivity functions diff --git a/Development/config.R b/Development/config.R index cc4bdcb..6712e01 100644 --- a/Development/config.R +++ b/Development/config.R @@ -3,7 +3,7 @@ DESCRIPTION <- list( package.name = "CodeAndRoll2", - version = "2.8.16", + version = "2.8.18", title = "CodeAndRoll2 for vector, matrix and list manipulations", description = "CodeAndRoll2 is a set of more than 170 productivity functions for vector, matrix and list manipulations and math. Used by MarkdownReports, ggExpress, SeuratUtils, etc.", diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index efafc51..347c850 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -834,7 +834,7 @@ table_decreasing_hybrid <- function(vec, first_elements = FALSE, useNA = "ifany" #' @export #' @examples getCategories(c("A" = 1, "B" = 1, "C" = 2, 3)) getCategories <- function(named_categ_vec) { - named_categ_vec[names(unique(named_categ_vec))] + named_categ_vec[!duplicated(named_categ_vec)] } From ee50823d6f465b53d2f5b389dc3191141d930157 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 07:06:48 +0000 Subject: [PATCH 08/17] Fix select_rows_and_columns() silently returning a vector for single row/column selections Both subset operations, df[true_rownames, ] and df[, true_colnames], were missing drop = FALSE. Base R's default drop = TRUE collapses a data.frame subset to a plain vector whenever exactly one row or one column is selected, silently changing the return type from data.frame to vector for any caller who requests a single RowID or ColID. This also broke the function's own trailing Stringendo::iprint(dim(df)) diagnostic, which prints NULL for a vector instead of the actual dimensions. Fix: add drop = FALSE to both subset operations, matching the same fix already applied to other functions in this file (combine.matrices.by.rowname.intersect, merge_numeric_df_by_rn). Verified: selecting a single column (ColIDs = "a") and a single row (RowIDs = "r1") now both correctly return a data.frame with the expected dim() printed, instead of silently degrading to a vector; the normal multi-row/multi-col case is unaffected. Version bumped 2.8.16 -> 2.8.19 (distinct from sibling PRs #75/#76, which also branch from the same 2.8.16 base and already claimed 2.8.17/2.8.18). --- DESCRIPTION | 2 +- Development/config.R | 2 +- R/CodeAndRoll2.R | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 227f9ee..c5c46bd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: CodeAndRoll2 Title: CodeAndRoll2 for vector, matrix and list manipulations -Version: 2.8.16 +Version: 2.8.19 Authors@R: person("Abel", "Vertesy", , "av@imba.oeaw.ac.at", role = c("aut", "cre")) Description: CodeAndRoll2 is a set of more than 170 productivity functions diff --git a/Development/config.R b/Development/config.R index cc4bdcb..5cd2eeb 100644 --- a/Development/config.R +++ b/Development/config.R @@ -3,7 +3,7 @@ DESCRIPTION <- list( package.name = "CodeAndRoll2", - version = "2.8.16", + version = "2.8.19", title = "CodeAndRoll2 for vector, matrix and list manipulations", description = "CodeAndRoll2 is a set of more than 170 productivity functions for vector, matrix and list manipulations and math. Used by MarkdownReports, ggExpress, SeuratUtils, etc.", diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index efafc51..536fa2f 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -2547,7 +2547,7 @@ select_rows_and_columns <- function(df, RowIDs = NULL, ColIDs = NULL) { } else { Stringendo::iprint("All row IDs found") } # if - df <- df[true_rownames, ] + df <- df[true_rownames, , drop = FALSE] } # if if (length(ColIDs)) { true_colnames <- intersect(colnames(df), ColIDs) @@ -2557,7 +2557,7 @@ select_rows_and_columns <- function(df, RowIDs = NULL, ColIDs = NULL) { } else { Stringendo::iprint("All column IDs found") } - df <- df[, true_colnames] + df <- df[, true_colnames, drop = FALSE] } # if Stringendo::iprint(dim(df)) return(df) From 26ea755cad30f6959cff6248dea62550764938fe Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 07:07:39 +0000 Subject: [PATCH 09/17] Fix rescale() silent divide-by-zero on constant/single-element input rescale(vec, from, upto) computed vec - min(vec), then divided by max(vec) of that shifted vector. For a constant vector (or any single-element vector), the shifted vector is all zeros, so max(vec) is 0, and dividing by it silently produces Inf/NaN for every element instead of an error or a meaningful value - no warning is raised. Fix: detect the zero-range case (max == min) up front and return the midpoint of the target range, (from + upto) / 2, for all non-NA elements instead. This matches the well-established convention used by scales::rescale() for the identical degenerate-input case (verified: scales::rescale(c(5,5,5), to = c(0,100)) returns c(50,50,50)) - using an existing, widely-used package's behavior as the precedent avoids guessing a novel convention for this edge case. Verified: normal (non-degenerate) input is unaffected; constant vectors and single-element vectors now return the range midpoint instead of all-NaN; NA elements are left as NA rather than being assigned the midpoint; a custom target range on constant input scales correctly too. Version bumped 2.8.16 -> 2.8.20 (distinct from sibling PRs #75/#76/#77, which also branch from the same 2.8.16 base and already claimed 2.8.17/2.8.18/2.8.19). --- DESCRIPTION | 2 +- Development/config.R | 2 +- R/CodeAndRoll2.R | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 227f9ee..444ef2d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: CodeAndRoll2 Title: CodeAndRoll2 for vector, matrix and list manipulations -Version: 2.8.16 +Version: 2.8.20 Authors@R: person("Abel", "Vertesy", , "av@imba.oeaw.ac.at", role = c("aut", "cre")) Description: CodeAndRoll2 is a set of more than 170 productivity functions diff --git a/Development/config.R b/Development/config.R index cc4bdcb..8b1745c 100644 --- a/Development/config.R +++ b/Development/config.R @@ -3,7 +3,7 @@ DESCRIPTION <- list( package.name = "CodeAndRoll2", - version = "2.8.16", + version = "2.8.20", title = "CodeAndRoll2 for vector, matrix and list manipulations", description = "CodeAndRoll2 is a set of more than 170 productivity functions for vector, matrix and list manipulations and math. Used by MarkdownReports, ggExpress, SeuratUtils, etc.", diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index efafc51..05bc70d 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -1273,8 +1273,14 @@ translate <- function(vec, old, new) { #' @param upto max, Default: 100 #' @export rescale <- function(vec, from = 0, upto = 100) { - vec <- vec - min(vec, na.rm = TRUE) - vec <- vec * ((upto - from) / max(vec, na.rm = TRUE)) + vmin <- min(vec, na.rm = TRUE) + vmax <- max(vec, na.rm = TRUE) + if (vmax == vmin) { + vec[!is.na(vec)] <- (from + upto) / 2 + return(vec) + } + vec <- vec - vmin + vec <- vec * ((upto - from) / (vmax - vmin)) vec <- vec + from return(vec) } # fun From 86cc33995f22122af04bd668957deacba66664a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 07:09:11 +0000 Subject: [PATCH 10/17] Fix as_tibble_from_namedVec() returning a character matrix, not a tibble, for its default argument With transpose = TRUE (the default), the function built a 2-column (name, value) tibble, then did t(as.matrix(tbl)). as.matrix() on a mixed-type tibble coerces every column to a common type (character here, since one column is character), so the "transposed" result was always a character matrix - not a tibble as the function's own name and @description promise ("Convert a vector with names into a tibble"), and the original numeric type of the input vector was lost (e.g. 1 became "1"). Fix: use tibble::as_tibble_row(vec.w.names) for the transpose = TRUE branch - the standard tibble-package primitive for converting a named vector into a single-row, names-as-columns tibble, which preserves the input's original type. transpose = FALSE is unchanged (already returned a valid two-column tibble). Also removed a leftover commented-out alternative implementation of this same logic that was superseded by the current code before this fix. Verified: as_tibble_from_namedVec() (default args) now returns an actual tibble with the numeric type preserved, instead of a character matrix; as_tibble_from_namedVec(transpose = FALSE) is unaffected. Version bumped 2.8.16 -> 2.8.21 (distinct from sibling PRs #75/#76/#77/#78, which also branch from the same 2.8.16 base and already claimed 2.8.17-2.8.20). --- DESCRIPTION | 2 +- Development/config.R | 2 +- R/CodeAndRoll2.R | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 227f9ee..b910e18 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: CodeAndRoll2 Title: CodeAndRoll2 for vector, matrix and list manipulations -Version: 2.8.16 +Version: 2.8.21 Authors@R: person("Abel", "Vertesy", , "av@imba.oeaw.ac.at", role = c("aut", "cre")) Description: CodeAndRoll2 is a set of more than 170 productivity functions diff --git a/Development/config.R b/Development/config.R index cc4bdcb..28dd1ac 100644 --- a/Development/config.R +++ b/Development/config.R @@ -3,7 +3,7 @@ DESCRIPTION <- list( package.name = "CodeAndRoll2", - version = "2.8.16", + version = "2.8.21", title = "CodeAndRoll2 for vector, matrix and list manipulations", description = "CodeAndRoll2 is a set of more than 170 productivity functions for vector, matrix and list manipulations and math. Used by MarkdownReports, ggExpress, SeuratUtils, etc.", diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index efafc51..b865b3d 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -1092,15 +1092,15 @@ tibble_summary_to_namedVec <- function( #' @export as_tibble_from_namedVec <- function(vec.w.names = c("a" = 1, "b" = 2), transpose = TRUE) { stopifnot(!is.null(names(vec.w.names))) - # tbl <- dplyr::bind_rows(vec.w.names) - # if (transpose) t(tbl) else tbl - tbl <- tibble::tibble( + if (transpose) { + return(tibble::as_tibble_row(vec.w.names)) + } + + tibble::tibble( name = names(vec.w.names), value = unname(vec.w.names) ) - - if (transpose) t(as.matrix(tbl)) else tbl } From f78d297b03a65a723ceacd44dcfde078ec6734ad Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 07:10:06 +0000 Subject: [PATCH 11/17] Fix df.row.2.named.vector() always returning a list, never a vector df.row.2.named.vector() computed as.vector(df[row, , drop = TRUE]). Base R's drop = TRUE only lets a single-COLUMN selection simplify to an atomic vector; extracting one ROW across multiple columns never simplifies this way, regardless of drop, because a row spanning mixed-type columns can't automatically collapse to one atomic type. So df[row, , drop = TRUE] is always a one-row data.frame/tibble (list-like), and as.vector() on that just returns the equivalent list - not an atomic vector as the function's own name and @description promise ("Convert a dataframe row into a vector"). Confirmed this fails for plain data.frame input too, not only tibbles as originally suspected - the function never actually worked for its stated purpose, for any input. Fix: use unlist(df[row, , drop = TRUE], use.names = FALSE) instead of as.vector(...) - unlist() correctly flattens the one-row list-like object into a proper atomic vector (coercing to a common type across columns where needed, same as any other unlist() call on mixed-type data). Verified: returns a proper named atomic vector for both a plain data.frame and a tibble (previously both returned a list); the existing names = feature (naming from a separate ID column) is unaffected. Version bumped 2.8.16 -> 2.8.22 (distinct from sibling PRs #75/#76/#77/#78/#79, which also branch from the same 2.8.16 base and already claimed 2.8.17-2.8.21). --- DESCRIPTION | 2 +- Development/config.R | 2 +- R/CodeAndRoll2.R | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 227f9ee..2b3b417 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: CodeAndRoll2 Title: CodeAndRoll2 for vector, matrix and list manipulations -Version: 2.8.16 +Version: 2.8.22 Authors@R: person("Abel", "Vertesy", , "av@imba.oeaw.ac.at", role = c("aut", "cre")) Description: CodeAndRoll2 is a set of more than 170 productivity functions diff --git a/Development/config.R b/Development/config.R index cc4bdcb..a75d39b 100644 --- a/Development/config.R +++ b/Development/config.R @@ -3,7 +3,7 @@ DESCRIPTION <- list( package.name = "CodeAndRoll2", - version = "2.8.16", + version = "2.8.22", title = "CodeAndRoll2 for vector, matrix and list manipulations", description = "CodeAndRoll2 is a set of more than 170 productivity functions for vector, matrix and list manipulations and math. Used by MarkdownReports, ggExpress, SeuratUtils, etc.", diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index efafc51..f4af48a 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -1048,7 +1048,7 @@ df.col.2.named.vector <- function(df, col, names = NULL) { df.row.2.named.vector <- function(df, row, names = NULL) { stopifnot(length(row) == 1) - vec <- as.vector(df[row, , drop = TRUE]) + vec <- unlist(df[row, , drop = TRUE], use.names = FALSE) names(vec) <- if (is.null(names)) colnames(df) else as.vector(unlist(df[names])) return(vec) } From 819c585460d0aeb55ab4644edd23264032a12940 Mon Sep 17 00:00:00 2001 From: Abel Vertesy Date: Tue, 1 Sep 2026 09:11:28 +0200 Subject: [PATCH 12/17] Require concise, structured PR descriptions New PR-description rule: lead with a few bullets per major change (what was wrong / how it was fixed / behavior impact), scaled to the change's size, capped at 250 words -- split the PR instead of writing more. Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ff05172..29a97ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Guidance for Agents -Version: 2026.08.31-00:00 +Version: 2026.08.31-00:01 ## I: Generic (all @vertesy repos) @@ -31,7 +31,14 @@ For each finding, state: Keep only findings that can be explained clearly and concisely. Do not flag formatting, line length, or missing tests. -### 3. Update the Source, Not Just the Documentation +### 3. Pull Request Descriptions + +Open each PR with a few bullets per major change: what was wrong, how it was fixed, and whether it changes the function's output or behavior. + +- Scale the description to the change: a typo or comment-only fix needs one short line, not a paragraph. +- Keep the whole description under 250 words; reserve that ceiling for genuinely complex PRs. If it doesn't fit, split the PR instead of writing more. + +### 4. Update the Source, Not Just the Documentation Documentation is generated from upstream sources: `.Rd` files from roxygen annotations and `DESCRIPTION` from `Development/Dependencies.R` via `config.R`. From e386a56546c95810b92e566c8992068e551ecdca Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 07:12:03 +0000 Subject: [PATCH 13/17] Fix overflow in rescale()'s constant-input midpoint calculation The constant-input branch computed (from + upto) / 2, which can overflow to Inf before the division when from and upto are both large finite numbers (e.g. from = 1e308, upto = 1.1e308 - their sum exceeds .Machine$double.xmax, so the "midpoint" was Inf instead of the correct finite 1.05e308). Fix: compute the midpoint as from + (upto - from) / 2 instead - the subtraction of two same-magnitude numbers doesn't overflow, and adding a bounded increment to from doesn't either. Verified: the flagged overflow case now returns the correct finite midpoint (1.05e+308) instead of Inf; normal constant-input and non-degenerate cases are unchanged. --- R/CodeAndRoll2.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index 05bc70d..599dcf7 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -1276,7 +1276,7 @@ rescale <- function(vec, from = 0, upto = 100) { vmin <- min(vec, na.rm = TRUE) vmax <- max(vec, na.rm = TRUE) if (vmax == vmin) { - vec[!is.na(vec)] <- (from + upto) / 2 + vec[!is.na(vec)] <- from + (upto - from) / 2 return(vec) } vec <- vec - vmin From 5d86ed19e9f1dcf526a0e0b35b886fcece50df4b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 07:14:27 +0000 Subject: [PATCH 14/17] Fix as_tibble_from_namedVec() rejecting duplicate/empty names after the tibble fix tibble::as_tibble_row() defaults to .name_repair = "check_unique", which errors on a vector with duplicate names (e.g. c(a=1, a=2)) - a regression versus the previous (buggy in a different way) implementation, which never validated column-name uniqueness since names were just data values in a "name" column, not column headers. Fix: pass .name_repair = "minimal" to as_tibble_row(), which accepts the names as-is without validation, matching the previous implementation's permissiveness. Verified: as_tibble_from_namedVec(c(a=1, a=2)) now succeeds instead of erroring; the default and transpose = FALSE cases are unaffected. --- R/CodeAndRoll2.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index b865b3d..7286ee0 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -1094,7 +1094,7 @@ as_tibble_from_namedVec <- function(vec.w.names = c("a" = 1, "b" = 2), transpose stopifnot(!is.null(names(vec.w.names))) if (transpose) { - return(tibble::as_tibble_row(vec.w.names)) + return(tibble::as_tibble_row(vec.w.names, .name_repair = "minimal")) } tibble::tibble( From 9e3e65ba3e4a5e337042eacce1ce23382dc64b66 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 07:15:26 +0000 Subject: [PATCH 15/17] Fix df.row.2.named.vector() silently converting factor labels to integer codes unlist() silently replaces factor values with their underlying integer level codes when combined with non-factor elements in the same list (a well-known R gotcha - e.g. unlist(list(factor("b", levels=c("a","b")), 5)) is c(2, 5), not c("b","5")). Since df[row, , drop=TRUE] is a list of per-column values, any factor column in a mixed-type row silently became its integer code instead of its label (e.g. "control" became "1"). Fix: convert factor elements to character before unlist()-ing, so their displayed labels are preserved instead of their internal integer codes. Verified: a data.frame row with a factor column mixed with a numeric column now correctly returns the factor's label ("control") instead of its integer code; all previously-verified scenarios (plain data.frame, tibble, the names= column-source feature) are unaffected. --- R/CodeAndRoll2.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index f4af48a..40c3b0a 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -1048,7 +1048,8 @@ df.col.2.named.vector <- function(df, col, names = NULL) { df.row.2.named.vector <- function(df, row, names = NULL) { stopifnot(length(row) == 1) - vec <- unlist(df[row, , drop = TRUE], use.names = FALSE) + row_list <- lapply(df[row, , drop = TRUE], function(x) if (is.factor(x)) as.character(x) else x) + vec <- unlist(row_list, use.names = FALSE) names(vec) <- if (is.null(names)) colnames(df) else as.vector(unlist(df[names])) return(vec) } From ebd759115c824c02ffbb557c82af7a9b6e4689a2 Mon Sep 17 00:00:00 2001 From: Abel Vertesy Date: Tue, 1 Sep 2026 11:01:35 +0200 Subject: [PATCH 16/17] Consolidate list-of-functions cleanup into one file.remove() call Replaces the per-file file.remove() call with a single call that matches every generated list.of.functions.in.*.det.md report by pattern (this repo generates 3: CodeAndRoll2, less.used, deprecated -- only the first was ever cleaned up before). The old line is commented out, not deleted. Co-Authored-By: Claude Sonnet 5 --- Development/Create_the_CodeAndRoll2_Package.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Development/Create_the_CodeAndRoll2_Package.R b/Development/Create_the_CodeAndRoll2_Package.R index 17f6b9c..7795738 100644 --- a/Development/Create_the_CodeAndRoll2_Package.R +++ b/Development/Create_the_CodeAndRoll2_Package.R @@ -85,7 +85,8 @@ for (scriptX in ls.scripts.full.path) { } file.edit(paste0(repository.dir, "/R/list.of.functions.in.", package.name, ".det.md")) file.edit(paste0(repository.dir, "/README.md")) -file.remove(paste0(repository.dir, "/R/list.of.functions.in.", package.name, ".det.md")) +# file.remove(paste0(repository.dir, "/R/list.of.functions.in.", package.name, ".det.md")) +file.remove(list.files(file.path(repository.dir, "R"), pattern = "^list\\.of\\.functions\\.in\\..+\\.det\\.md$", full.names = TRUE)) r$PackageTools() PackageTools::copy_github_badge("active") # Add badge to readme via clipboard From 25558cb1551eae1c6964e2492d3c13a9a93e7061 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Sep 2026 09:48:45 +0000 Subject: [PATCH 17/17] Avoid rescale midpoint overflow Co-authored-by: vertesy <5101911+vertesy@users.noreply.github.com> --- R/CodeAndRoll2.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/CodeAndRoll2.R b/R/CodeAndRoll2.R index 57fc09f..24738ae 100644 --- a/R/CodeAndRoll2.R +++ b/R/CodeAndRoll2.R @@ -1278,7 +1278,7 @@ rescale <- function(vec, from = 0, upto = 100) { vmin <- min(vec, na.rm = TRUE) vmax <- max(vec, na.rm = TRUE) if (vmax == vmin) { - vec[!is.na(vec)] <- from + (upto - from) / 2 + vec[!is.na(vec)] <- from / 2 + upto / 2 return(vec) } vec <- vec - vmin