From 27adc3803b191cca72d4936c4b362ee45294869a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:20:56 +0000 Subject: [PATCH 1/6] Initial plan From 7e9f951975170ee1f995604bd209218045a45c55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:27:53 +0000 Subject: [PATCH 2/6] Support character factor-smooth grouping Co-authored-by: stemangiola <7232890+stemangiola@users.noreply.github.com> --- R/smooths.R | 9 ++++++++- tests/testthat/test-smooths.R | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/R/smooths.R b/R/smooths.R index 75de99a9..a84bed72 100644 --- a/R/smooths.R +++ b/R/smooths.R @@ -195,6 +195,13 @@ parse_formula_smooths <- function(fm, data) { eval_env$s <- mgcv::s eval_env$t2 <- mgcv::t2 sm_spec <- eval(smooth_calls[[k]], envir = as.list(data), enclos = eval_env) + smooth_data <- as.data.frame(data) + if (identical(sm_spec$bs, "fs")) { + grouping_var <- sm_spec$term[[length(sm_spec$term)]] + if (is.character(smooth_data[[grouping_var]])) { + smooth_data[[grouping_var]] <- factor(smooth_data[[grouping_var]]) + } + } # absorb.cons = TRUE removes the constant function from the basis so the # smooth doesn't fight the intercept (matches brms). @@ -203,7 +210,7 @@ parse_formula_smooths <- function(fm, data) { sm <- tryCatch( mgcv::smoothCon( sm_spec, - data = as.data.frame(data), + data = smooth_data, absorb.cons = TRUE, diagonal.penalty = TRUE )[[1]], diff --git a/tests/testthat/test-smooths.R b/tests/testthat/test-smooths.R index f08c31ee..40ade739 100644 --- a/tests/testthat/test-smooths.R +++ b/tests/testthat/test-smooths.R @@ -181,6 +181,24 @@ test_that("parse_formula_smooths flattens t2 / fs into multiple Xr blocks", { }) +test_that("parse_formula_smooths accepts character fs grouping columns", { + skip_if_not_installed("mgcv") + + dat <- data.frame( + x = seq(0, 6, length.out = 24), + tissue = rep(c("blood", "lymph", "tumor"), length.out = 24) + ) + + res <- sccomp:::parse_formula_smooths( + ~ s(x, tissue, bs = "fs", k = 5), + dat + ) + + expect_length(res$Xr_list, 3L) + expect_true(is.factor(res$smooth_specs[[1]]$fac)) +}) + + test_that("parse_formula_smooths returns identity for smooth-free formulas", { dat <- data.frame(type = letters[1:5], age = 1:5) res <- sccomp:::parse_formula_smooths(~ type + age, dat) From 3151b7f6a57738579b4a86d587fdd32549fecdfc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:28:10 +0000 Subject: [PATCH 3/6] Clean smooth grouping test formatting Co-authored-by: stemangiola <7232890+stemangiola@users.noreply.github.com> --- tests/testthat/test-smooths.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-smooths.R b/tests/testthat/test-smooths.R index 40ade739..683afe34 100644 --- a/tests/testthat/test-smooths.R +++ b/tests/testthat/test-smooths.R @@ -183,17 +183,17 @@ test_that("parse_formula_smooths flattens t2 / fs into multiple Xr blocks", { test_that("parse_formula_smooths accepts character fs grouping columns", { skip_if_not_installed("mgcv") - + dat <- data.frame( x = seq(0, 6, length.out = 24), tissue = rep(c("blood", "lymph", "tumor"), length.out = 24) ) - + res <- sccomp:::parse_formula_smooths( ~ s(x, tissue, bs = "fs", k = 5), dat ) - + expect_length(res$Xr_list, 3L) expect_true(is.factor(res$smooth_specs[[1]]$fac)) }) From 5582272749ba350e4f275d38a251c5e2861a3361 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:28:52 +0000 Subject: [PATCH 4/6] Handle factor smooth grouping terms robustly Co-authored-by: stemangiola <7232890+stemangiola@users.noreply.github.com> --- R/smooths.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/smooths.R b/R/smooths.R index a84bed72..bace09f7 100644 --- a/R/smooths.R +++ b/R/smooths.R @@ -197,9 +197,10 @@ parse_formula_smooths <- function(fm, data) { sm_spec <- eval(smooth_calls[[k]], envir = as.list(data), enclos = eval_env) smooth_data <- as.data.frame(data) if (identical(sm_spec$bs, "fs")) { - grouping_var <- sm_spec$term[[length(sm_spec$term)]] - if (is.character(smooth_data[[grouping_var]])) { - smooth_data[[grouping_var]] <- factor(smooth_data[[grouping_var]]) + for (term in sm_spec$term) { + if (is.character(smooth_data[[term]])) { + smooth_data[[term]] <- factor(smooth_data[[term]]) + } } } From f9d5a013c7a1e2bda1b0dbc99cad2384797f1a8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:29:19 +0000 Subject: [PATCH 5/6] Avoid unnecessary smooth data copies Co-authored-by: stemangiola <7232890+stemangiola@users.noreply.github.com> --- R/smooths.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/smooths.R b/R/smooths.R index bace09f7..8f6d70c5 100644 --- a/R/smooths.R +++ b/R/smooths.R @@ -195,8 +195,9 @@ parse_formula_smooths <- function(fm, data) { eval_env$s <- mgcv::s eval_env$t2 <- mgcv::t2 sm_spec <- eval(smooth_calls[[k]], envir = as.list(data), enclos = eval_env) - smooth_data <- as.data.frame(data) + smooth_data <- data if (identical(sm_spec$bs, "fs")) { + smooth_data <- as.data.frame(data) for (term in sm_spec$term) { if (is.character(smooth_data[[term]])) { smooth_data[[term]] <- factor(smooth_data[[term]]) From 4664155b00456beadb456daefaae01a674bcf13f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:29:43 +0000 Subject: [PATCH 6/6] Preserve smooth data frame input handling Co-authored-by: stemangiola <7232890+stemangiola@users.noreply.github.com> --- R/smooths.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/smooths.R b/R/smooths.R index 8f6d70c5..bace09f7 100644 --- a/R/smooths.R +++ b/R/smooths.R @@ -195,9 +195,8 @@ parse_formula_smooths <- function(fm, data) { eval_env$s <- mgcv::s eval_env$t2 <- mgcv::t2 sm_spec <- eval(smooth_calls[[k]], envir = as.list(data), enclos = eval_env) - smooth_data <- data + smooth_data <- as.data.frame(data) if (identical(sm_spec$bs, "fs")) { - smooth_data <- as.data.frame(data) for (term in sm_spec$term) { if (is.character(smooth_data[[term]])) { smooth_data[[term]] <- factor(smooth_data[[term]])