From 415966d3099e83a21611ca0e879d2821843627cb Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:42:05 +0200 Subject: [PATCH 1/8] refactor: batch-7 first-pass modelling server functions --- R/gamlssDS.R | 10 ++++++---- R/glmPredictDS.ag.R | 7 +++---- R/glmPredictDS.as.R | 7 +++---- R/glmSummaryDS.ag.R | 5 +++-- R/glmSummaryDS.as.R | 3 ++- R/miceDS.R | 6 ++++-- man/gamlssDS.Rd | 2 ++ man/glmPredictDS.ag.Rd | 2 ++ man/glmPredictDS.as.Rd | 2 ++ man/glmSummaryDS.ag.Rd | 2 ++ man/glmSummaryDS.as.Rd | 2 ++ man/levelsDS.Rd | 5 ++--- man/miceDS.Rd | 2 ++ 13 files changed, 35 insertions(+), 20 deletions(-) diff --git a/R/gamlssDS.R b/R/gamlssDS.R index 533da0e1..1789b5af 100644 --- a/R/gamlssDS.R +++ b/R/gamlssDS.R @@ -69,6 +69,7 @@ #' residuals (the normalised quantile residuals of the model) are not disclosed to #' the client-side. #' @author Demetris Avraam for DataSHIELD Development Team +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @import gamlss #' @import gamlss.dist #' @export @@ -81,8 +82,9 @@ gamlssDS <- function(formula=formula, sigma.formula=sigma.formula, nu.formula=nu thr <- dsBase::listDisclosureSettingsDS() nfilter.glm <- as.numeric(thr$nfilter.glm) - - data <- eval(parse(text = data), envir = parent.frame()) + + data <- .loadServersideObject(data) + .checkClass(obj = data, obj_name = "data", permitted_classes = c("data.frame", "matrix")) family <- gsub("left_parenthesis", "(", family, fixed = TRUE) family <- gsub("right_parenthesis", ")", family, fixed = TRUE) @@ -187,8 +189,8 @@ gamlssDS <- function(formula=formula, sigma.formula=sigma.formula, nu.formula=nu base::assign(newobj, results$residuals, envir = parent.frame()) if(centiles==TRUE){ - xvar <- eval(parse(text=xvar), envir = parent.frame()) - centiles_out <- gamlss::centiles(obj = results, xvar = xvar, points = FALSE, + xvar <- .loadServersideObject(xvar) + centiles_out <- gamlss::centiles(obj = results, xvar = xvar, points = FALSE, save = TRUE) }else{ centiles_out <- NA diff --git a/R/glmPredictDS.ag.R b/R/glmPredictDS.ag.R index c4474079..9a40804e 100644 --- a/R/glmPredictDS.ag.R +++ b/R/glmPredictDS.ag.R @@ -34,6 +34,7 @@ #' ds.glmPredict and glmPredict.as and help in native R for predict.glm #' predict.glm in native R #' @author Paul Burton for DataSHIELD Development Team (20/7/20) +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export #' glmPredictDS.ag <- function(glmname.transmit, newdataname.transmit, @@ -148,13 +149,11 @@ if(!string.safe) } #Activate all arguments -#glmobj<-eval(parse(text=glmname.transmit)) -glmobj<-get(glmname.transmit) +glmobj <- .loadServersideObject(glmname.transmit) if(!is.null(newdataname.transmit)) { - newdf<-get(newdataname.transmit) -# newdf<-geeval(parse(text=newdataname.transmit)) + newdf <- .loadServersideObject(newdataname.transmit) }else{ newdf<-NULL } diff --git a/R/glmPredictDS.as.R b/R/glmPredictDS.as.R index e25e3b50..84417fc9 100644 --- a/R/glmPredictDS.as.R +++ b/R/glmPredictDS.as.R @@ -32,6 +32,7 @@ #' For more details see DataSHIELD help for ds.glmPredict and help for #' predict.glm in native R #' @author Paul Burton for DataSHIELD Development Team (20/7/20) +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export glmPredictDS.as <- function(glmname.transmit, newdataname.transmit, output.type,se.fit, dispersion, terms.transmit, na.action){ @@ -142,13 +143,11 @@ if(!string.safe) } #Activate all arguments -#glmobj<-eval(parse(text=glmname.transmit)) -glmobj<-get(glmname.transmit) +glmobj <- .loadServersideObject(glmname.transmit) if(!is.null(newdataname.transmit)) { - newdf<-get(newdataname.transmit) -# newdf<-geeval(parse(text=newdataname.transmit)) + newdf <- .loadServersideObject(newdataname.transmit) }else{ newdf<-NULL } diff --git a/R/glmSummaryDS.ag.R b/R/glmSummaryDS.ag.R index 880f5b79..7316eee4 100644 --- a/R/glmSummaryDS.ag.R +++ b/R/glmSummaryDS.ag.R @@ -17,6 +17,7 @@ #' elements (and only the non-disclosive elements) of a specified serverside glm #' and its corresponding summary_glm object. #' @author Paul Burton for DataSHIELD Development Team (20/7/20) +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export glmSummaryDS.ag <- function(x.transmit){ @@ -55,7 +56,7 @@ if(!string.safe) #create safe glm object with disclosive elements deleted for clientside ####################################################################### -input.obj<-eval(parse(text=x.transmit)) +input.obj <- .loadServersideObject(x.transmit) if (is.null(input.obj)) { @@ -90,7 +91,7 @@ glm.obj<-input.obj #create safe summary.glm object with disclosive elements deleted for clientside ############################################################################### -input.obj<-eval(parse(text=x.transmit)) +input.obj <- .loadServersideObject(x.transmit) summary.obj<-summary(input.obj) diff --git a/R/glmSummaryDS.as.R b/R/glmSummaryDS.as.R index 38cf4cf8..0025e74e 100644 --- a/R/glmSummaryDS.as.R +++ b/R/glmSummaryDS.as.R @@ -15,6 +15,7 @@ #' @return writes object to serverside which is precisely equivalent #' to summary(glm object) in native R #' @author Paul Burton for DataSHIELD Development Team (20/7/20) +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export glmSummaryDS.as <- function(x.transmit){ @@ -52,7 +53,7 @@ if(!string.safe) #create summary.glm object -input.obj<-eval(parse(text=x.transmit)) +input.obj <- .loadServersideObject(x.transmit) summary.obj<-summary(input.obj) diff --git a/R/miceDS.R b/R/miceDS.R index 98904dfe..7bf0bccd 100644 --- a/R/miceDS.R +++ b/R/miceDS.R @@ -43,7 +43,8 @@ #' The function also saves in each server the mids object and all completed datasets as #' dataframes. #' @author Demetris Avraam for DataSHIELD Development Team -#' @import mice +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands +#' @import mice #' @export #' miceDS <- function(data=data, m=m, maxit=maxit, method=method, post=post, seed=seed, @@ -57,7 +58,8 @@ miceDS <- function(data=data, m=m, maxit=maxit, method=method, post=post, seed=s seed <- getOption("datashield.seed") } - data <- eval(parse(text=data), envir = parent.frame()) + data <- .loadServersideObject(data) + .checkClass(obj = data, obj_name = "data", permitted_classes = c("data.frame", "matrix")) if(!is.null(method)){ method <- unlist(stringr::str_split(method, pattern=",")) diff --git a/man/gamlssDS.Rd b/man/gamlssDS.Rd index 218eb4ac..ca538529 100644 --- a/man/gamlssDS.Rd +++ b/man/gamlssDS.Rd @@ -116,4 +116,6 @@ functions in native R gamlss package. } \author{ Demetris Avraam for DataSHIELD Development Team + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmPredictDS.ag.Rd b/man/glmPredictDS.ag.Rd index 2de27645..cdeb0ef4 100644 --- a/man/glmPredictDS.ag.Rd +++ b/man/glmPredictDS.ag.Rd @@ -63,4 +63,6 @@ ds.glmPredict and glmPredict.as and help in native R for predict.glm } \author{ Paul Burton for DataSHIELD Development Team (20/7/20) + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmPredictDS.as.Rd b/man/glmPredictDS.as.Rd index 7395773e..ec757e8c 100644 --- a/man/glmPredictDS.as.Rd +++ b/man/glmPredictDS.as.Rd @@ -61,4 +61,6 @@ for predict.glm } \author{ Paul Burton for DataSHIELD Development Team (20/7/20) + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmSummaryDS.ag.Rd b/man/glmSummaryDS.ag.Rd index d64a6218..8b8632c9 100644 --- a/man/glmSummaryDS.ag.Rd +++ b/man/glmSummaryDS.ag.Rd @@ -32,4 +32,6 @@ and its corresponding summary_glm object. } \author{ Paul Burton for DataSHIELD Development Team (20/7/20) + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmSummaryDS.as.Rd b/man/glmSummaryDS.as.Rd index e2067100..68ddcce6 100644 --- a/man/glmSummaryDS.as.Rd +++ b/man/glmSummaryDS.as.Rd @@ -30,4 +30,6 @@ for glm() and summary.glm } \author{ Paul Burton for DataSHIELD Development Team (20/7/20) + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/levelsDS.Rd b/man/levelsDS.Rd index c54b7d13..4002c73c 100644 --- a/man/levelsDS.Rd +++ b/man/levelsDS.Rd @@ -10,9 +10,8 @@ levelsDS(x) \item{x}{a factor vector} } \value{ -a list with two elements: \code{Levels} (the factor levels present - in the vector) and \code{class} (the class of the input object, for - client-side consistency checking) +a list with one element: \code{Levels} (the factor levels present + in the vector) } \description{ This function is similar to R function \code{levels}. diff --git a/man/miceDS.Rd b/man/miceDS.Rd index dd04c30c..e9496da8 100644 --- a/man/miceDS.Rd +++ b/man/miceDS.Rd @@ -78,4 +78,6 @@ package. } \author{ Demetris Avraam for DataSHIELD Development Team + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } From 07b11e137485b4cd3a2d1485d7454de7f2866ee7 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Thu, 16 Apr 2026 01:10:48 +0200 Subject: [PATCH 2/8] refactor: batch-7 second-pass modelling server functions --- R/glmDS1.R | 4 +++- R/glmDS2.R | 4 +++- R/glmSLMADS1.R | 4 +++- R/glmSLMADS2.R | 4 +++- R/glmerSLMADS.assign.R | 4 +++- R/glmerSLMADS2.R | 4 +++- R/lmerSLMADS.assign.R | 4 +++- R/lmerSLMADS2.R | 4 +++- man/glmDS1.Rd | 2 ++ man/glmDS2.Rd | 2 ++ man/glmSLMADS1.Rd | 2 ++ man/glmSLMADS2.Rd | 2 ++ man/glmerSLMADS.assign.Rd | 2 ++ man/glmerSLMADS2.Rd | 2 ++ man/lmerSLMADS.assign.Rd | 2 ++ man/lmerSLMADS2.Rd | 2 ++ 16 files changed, 40 insertions(+), 8 deletions(-) diff --git a/R/glmDS1.R b/R/glmDS1.R index a76d615c..568e3070 100644 --- a/R/glmDS1.R +++ b/R/glmDS1.R @@ -19,6 +19,7 @@ #' @return List with values from GLM model. #' #' @author Burton PR for DataSHIELD Development Team +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export #' glmDS1 <- function(formula, family, weights, offset, data){ @@ -41,7 +42,8 @@ nfilter.glm <- as.numeric(thr$nfilter.glm) if(is.null(data)){ dataTable <- NULL }else{ - dataTable <- eval(parse(text=data), envir = parent.frame()) + dataTable <- .loadServersideObject(data) + .checkClass(obj = dataTable, obj_name = data, permitted_classes = c("data.frame", "matrix")) } formulatext <- Reduce(paste, deparse(formula)) diff --git a/R/glmDS2.R b/R/glmDS2.R index 9287075c..93e8edda 100644 --- a/R/glmDS2.R +++ b/R/glmDS2.R @@ -20,6 +20,7 @@ #' the data to be analysed under the specified model same #' #' @author Paul Burton, for DataSHIELD Development Team +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' #' @return List with values from GLM model #' @export @@ -39,7 +40,8 @@ glmDS2 <- function (formula, family, beta.vect, offset, weights, dataName) { # Same is done for offset and weights lower down function if(!is.null(dataName)){ - dataDF <- eval(parse(text=dataName), envir = parent.frame()) + dataDF <- .loadServersideObject(dataName) + .checkClass(obj = dataDF, obj_name = dataName, permitted_classes = c("data.frame", "matrix")) }else{ dataDF <- NULL } diff --git a/R/glmSLMADS1.R b/R/glmSLMADS1.R index 30346f4e..4cddefdb 100644 --- a/R/glmSLMADS1.R +++ b/R/glmSLMADS1.R @@ -17,6 +17,7 @@ #' such as test of model complexity (saturation). #' For more detailed information see help for ds.glmSLMA. #' @author Paul Burton for DataSHIELD Development Team (14/7/20) +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export glmSLMADS1<- function(formula, family, weights, offset, data){ @@ -51,7 +52,8 @@ final.family.object<-eval(parse(text=family)) if(is.null(data)){ dataTable <- NULL }else{ - dataTable <- eval(parse(text=data), envir = parent.frame()) + dataTable <- .loadServersideObject(data) + .checkClass(obj = dataTable, obj_name = data, permitted_classes = c("data.frame", "matrix")) } diff --git a/R/glmSLMADS2.R b/R/glmSLMADS2.R index 759263b5..b7374296 100644 --- a/R/glmSLMADS2.R +++ b/R/glmSLMADS2.R @@ -22,6 +22,7 @@ #' in particular including the study-specific regression coefficients and their corresponding #' standard errors. #' @author Paul Burton for DataSHIELD Development Team (14/7/20) +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export glmSLMADS2 <- function(formula, family, offset, weights, newobj, dataName){ @@ -52,7 +53,8 @@ errorMessage2<-"No errors" # Same is done for offset and weights lower down function if(!is.null(dataName)){ - dataDF <- eval(parse(text=dataName), envir = parent.frame()) + dataDF <- .loadServersideObject(dataName) + .checkClass(obj = dataDF, obj_name = dataName, permitted_classes = c("data.frame", "matrix")) }else{ dataDF<-NULL } diff --git a/R/glmerSLMADS.assign.R b/R/glmerSLMADS.assign.R index df367ea1..2105fea7 100644 --- a/R/glmerSLMADS.assign.R +++ b/R/glmerSLMADS.assign.R @@ -29,6 +29,7 @@ #' @return writes glmerMod object summarising the fitted model to the serverside. #' For more detailed information see help for ds.glmerSLMA. #' @author Demetris Avraam for DataSHIELD Development Team +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export glmerSLMADS.assign <- function(formula, offset, weights, dataName, family, control_type=NULL, control_value.transmit=NULL, nAGQ=1L, verbose = 0, theta = NULL, fixef = NULL){ @@ -46,7 +47,8 @@ glmerSLMADS.assign <- function(formula, offset, weights, dataName, family, # Same is done for offset and weights lower down function if(!is.null(dataName)){ - dataDF <- eval(parse(text=dataName), envir = parent.frame()) + dataDF <- .loadServersideObject(dataName) + .checkClass(obj = dataDF, obj_name = dataName, permitted_classes = c("data.frame", "matrix")) }else{ dataDF <- NULL } diff --git a/R/glmerSLMADS2.R b/R/glmerSLMADS2.R index 8fe54f83..d4b15f8c 100644 --- a/R/glmerSLMADS2.R +++ b/R/glmerSLMADS2.R @@ -36,6 +36,7 @@ #' function ds.glmerSLMA #' @return all key model components see help for ds.glmerSLMA #' @author Tom Bishop, with some additions by Paul Burton +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export glmerSLMADS2 <- function(formula, offset, weights, dataName, family, control_type=NULL, control_value.transmit=NULL, nAGQ=1L, verbose = 0, theta = NULL, fixef = NULL){ @@ -53,7 +54,8 @@ glmerSLMADS2 <- function(formula, offset, weights, dataName, family, # Same is done for offset and weights lower down function if(!is.null(dataName)){ - dataDF <- eval(parse(text=dataName), envir = parent.frame()) + dataDF <- .loadServersideObject(dataName) + .checkClass(obj = dataDF, obj_name = dataName, permitted_classes = c("data.frame", "matrix")) }else{ dataDF <- NULL } diff --git a/R/lmerSLMADS.assign.R b/R/lmerSLMADS.assign.R index 06b2bcd1..2343fe59 100644 --- a/R/lmerSLMADS.assign.R +++ b/R/lmerSLMADS.assign.R @@ -18,6 +18,7 @@ #' @return writes lmerMod object summarising the fitted model to the serverside. #' For more detailed information see help for ds.lmerSLMA. #' @author TDemetris Avraam for DataSHIELD Development Team +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export lmerSLMADS.assign <- function(formula, offset, weights, dataName, REML = TRUE, control_type, control_value.transmit, optimizer, verbose=0){ @@ -37,7 +38,8 @@ lmerSLMADS.assign <- function(formula, offset, weights, dataName, REML = TRUE, # Same is done for offset and weights lower down function if(!is.null(dataName)){ - dataDF <- eval(parse(text=dataName), envir = parent.frame()) + dataDF <- .loadServersideObject(dataName) + .checkClass(obj = dataDF, obj_name = dataName, permitted_classes = c("data.frame", "matrix")) }else{ dataDF <- NULL } diff --git a/R/lmerSLMADS2.R b/R/lmerSLMADS2.R index 145f90b9..a60b67c2 100644 --- a/R/lmerSLMADS2.R +++ b/R/lmerSLMADS2.R @@ -27,6 +27,7 @@ #' @param verbose see help for ds.lmerSLMA #' @return all key model components see help for ds.lmerSLMA #' @author Tom Bishop, with some additions by Paul Burton +#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands #' @export lmerSLMADS2 <- function(formula, offset, weights, dataName, REML = TRUE, control_type, control_value.transmit, optimizer, verbose=0){ @@ -46,7 +47,8 @@ lmerSLMADS2 <- function(formula, offset, weights, dataName, REML = TRUE, # Same is done for offset and weights lower down function if(!is.null(dataName)){ - dataDF <- eval(parse(text=dataName), envir = parent.frame()) + dataDF <- .loadServersideObject(dataName) + .checkClass(obj = dataDF, obj_name = dataName, permitted_classes = c("data.frame", "matrix")) }else{ dataDF <- NULL } diff --git a/man/glmDS1.Rd b/man/glmDS1.Rd index e0b0b332..95ddb710 100644 --- a/man/glmDS1.Rd +++ b/man/glmDS1.Rd @@ -36,4 +36,6 @@ been specified. For more details please see the extensive header for ds.glm. } \author{ Burton PR for DataSHIELD Development Team + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmDS2.Rd b/man/glmDS2.Rd index 36731d5f..7edc7007 100644 --- a/man/glmDS2.Rd +++ b/man/glmDS2.Rd @@ -40,4 +40,6 @@ For more details please see the extensive header for ds.glm. } \author{ Paul Burton, for DataSHIELD Development Team + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmSLMADS1.Rd b/man/glmSLMADS1.Rd index e08e7076..b8de3fb4 100644 --- a/man/glmSLMADS1.Rd +++ b/man/glmSLMADS1.Rd @@ -37,4 +37,6 @@ For more detailed information see help for ds.glmSLMA. } \author{ Paul Burton for DataSHIELD Development Team (14/7/20) + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmSLMADS2.Rd b/man/glmSLMADS2.Rd index d5cfa9b0..3b59bd15 100644 --- a/man/glmSLMADS2.Rd +++ b/man/glmSLMADS2.Rd @@ -43,4 +43,6 @@ For more detailed information see help for ds.glmSLMA. } \author{ Paul Burton for DataSHIELD Development Team (14/7/20) + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmerSLMADS.assign.Rd b/man/glmerSLMADS.assign.Rd index 6db1196d..a58f364e 100644 --- a/man/glmerSLMADS.assign.Rd +++ b/man/glmerSLMADS.assign.Rd @@ -67,4 +67,6 @@ from each single data source and saves the regression outcomes on the serverside } \author{ Demetris Avraam for DataSHIELD Development Team + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/glmerSLMADS2.Rd b/man/glmerSLMADS2.Rd index aee4d44c..1086da95 100644 --- a/man/glmerSLMADS2.Rd +++ b/man/glmerSLMADS2.Rd @@ -74,4 +74,6 @@ glmes using the glmer engine can be obtained using R help for glmer and the lme4 } \author{ Tom Bishop, with some additions by Paul Burton + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/lmerSLMADS.assign.Rd b/man/lmerSLMADS.assign.Rd index cc2ff9f0..e47edd87 100644 --- a/man/lmerSLMADS.assign.Rd +++ b/man/lmerSLMADS.assign.Rd @@ -52,4 +52,6 @@ effects - on data from each single data source and saves the regression outcomes } \author{ TDemetris Avraam for DataSHIELD Development Team + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } diff --git a/man/lmerSLMADS2.Rd b/man/lmerSLMADS2.Rd index 98a29e1e..ba1f4260 100644 --- a/man/lmerSLMADS2.Rd +++ b/man/lmerSLMADS2.Rd @@ -61,4 +61,6 @@ the lme4 package } \author{ Tom Bishop, with some additions by Paul Burton + +Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands } From 21e925248dce9aec5dda134bf14674d9bf009cb7 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:28:20 +0200 Subject: [PATCH 3/8] added missing unit test coverage --- tests/testthat/Rplots.pdf | Bin 0 -> 5562 bytes tests/testthat/test-smk-glmDS1.R | 63 +++++++++++++ tests/testthat/test-smk-glmDS2.R | 68 ++++++++++++++ tests/testthat/test-smk-glmPredictDS.ag.R | 61 +++++++++++++ tests/testthat/test-smk-glmPredictDS.as.R | 51 +++++++++++ tests/testthat/test-smk-glmSLMADS1.R | 63 +++++++++++++ tests/testthat/test-smk-glmSLMADS2.R | 68 ++++++++++++++ tests/testthat/test-smk-glmSummaryDS.ag.R | 58 ++++++++++++ tests/testthat/test-smk-glmSummaryDS.as.R | 51 +++++++++++ tests/testthat/test-smk-glmerSLMADS.assign.R | 65 +++++++++++++ tests/testthat/test-smk-glmerSLMADS2.R | 69 ++++++++++++++ tests/testthat/test-smk-lmerSLMADS.assign.R | 91 +++++++++++++++++++ tests/testthat/test-smk-lmerSLMADS2.R | 68 ++++++++++++++ 13 files changed, 776 insertions(+) create mode 100644 tests/testthat/Rplots.pdf create mode 100644 tests/testthat/test-smk-glmDS1.R create mode 100644 tests/testthat/test-smk-glmDS2.R create mode 100644 tests/testthat/test-smk-glmPredictDS.ag.R create mode 100644 tests/testthat/test-smk-glmPredictDS.as.R create mode 100644 tests/testthat/test-smk-glmSLMADS1.R create mode 100644 tests/testthat/test-smk-glmSLMADS2.R create mode 100644 tests/testthat/test-smk-glmSummaryDS.ag.R create mode 100644 tests/testthat/test-smk-glmSummaryDS.as.R create mode 100644 tests/testthat/test-smk-glmerSLMADS.assign.R create mode 100644 tests/testthat/test-smk-glmerSLMADS2.R create mode 100644 tests/testthat/test-smk-lmerSLMADS.assign.R create mode 100644 tests/testthat/test-smk-lmerSLMADS2.R diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ac3ab89f20cb92e29bf35287fb79c19aa27ae11c GIT binary patch literal 5562 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�xVfk4>yp)O z#bO-^Po1(Q@dnnt%N}ly-MhQZA0+?1%3&F(dU;v(;wPuJa23>4)%h#v{mq~!OTu4~ z9T5Xs8|Pl@c8Ld`2VZgA@%g4demkSMezsioy@X=C_f`d)G=@ zU+oXCqSwf;Hosf*Rk}K+JuwR=&B(0a_M6hZxh^!URjo-~Ne^23dU|txwK(1+Cx~e~ zVq@=LTetF`Ybf9kLr5D>^IkA`nj=JCTmO5q#^BZQB+O3$0M^2|x}s?=5Z&gn2g)me zaTq*>1XPBBy(k1S8jE%%10eG9U>5?-xFV4-K(HGTd4sm&B2g6js*A!9Q52jj7VQHB zlQCEq8iYflhy*+k>`X+{18JE^6pCgn0l+R8S}PKU1O&_C31k|pKxxrqO6i5-XnusOwND6aS9}w56Xu_7SVLzv{_BAvE9d?{RWa<$q!Fib^zl@b7UnMMslT|G*&ff5{~ikr*tR zNHbW0U~`Ngnl=L Date: Tue, 8 Sep 2026 13:55:42 +0200 Subject: [PATCH 4/8] fix: redact glmSummaryDS.as fields by name, not index --- R/glmSummaryDS.as.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/glmSummaryDS.as.R b/R/glmSummaryDS.as.R index 0025e74e..076ede7f 100644 --- a/R/glmSummaryDS.as.R +++ b/R/glmSummaryDS.as.R @@ -58,8 +58,8 @@ input.obj <- .loadServersideObject(x.transmit) summary.obj<-summary(input.obj) #block na.action and deviance residual components of summary object -summary.obj[[12]]<-NA -summary.obj[[11]]<-NA +if (!is.null(summary.obj$na.action)) summary.obj$na.action<-NA +summary.obj$deviance.resid<-NA summary.obj<-summary.obj From 18bf2017b877d274d071132a1c1788db2664569f Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:56:01 +0200 Subject: [PATCH 5/8] fix: use resolved offset/weights in lmerSLMADS.assign fit --- R/lmerSLMADS.assign.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/lmerSLMADS.assign.R b/R/lmerSLMADS.assign.R index 2343fe59..29aaae34 100644 --- a/R/lmerSLMADS.assign.R +++ b/R/lmerSLMADS.assign.R @@ -359,8 +359,7 @@ if(!is.null(optimizer)&&optimizer!="nloptwrap") } - mg <- lme4::lmer(formula2use, offset=offset, weights=weights, data=dataDF, REML = REML, verbose = verbose, control = control.obj) - #iterations <- utils::capture.output(try(mg <- lme4::lmer(formula2use, offset=offset.to.use, weights=weights.to.use, data=dataDF, REML = REML, verbose = verbose, control = control.obj))) + mg <- lme4::lmer(formula2use, offset=offset.to.use, weights=weights.to.use, data=dataDF, REML = REML, verbose = verbose, control = control.obj) outlist <- mg From 64239af9daeeda29dd379712900a2d4ae8b24cdf Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:56:26 +0200 Subject: [PATCH 6/8] use .loadServersideObject --- R/glmSLMADS2.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/glmSLMADS2.R b/R/glmSLMADS2.R index b7374296..58d1b374 100644 --- a/R/glmSLMADS2.R +++ b/R/glmSLMADS2.R @@ -104,8 +104,7 @@ errorMessage2<-"No errors" #bringing back in the mg output saved from that previous call # mg <- stats::glm(formula2use, family=final.family.object, x=TRUE, offset=offset.to.use, weights=weights.to.use, data=dataDF) -activate.text<- paste0("mg<-",newobj) -eval(parse(text=activate.text)) +mg <- .loadServersideObject(newobj) y.vect<-mg$y X.mat<-mg$x From 1fff70086121ce1a308045da1be5e0ebd7fffd58 Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:38:17 +0200 Subject: [PATCH 7/8] fixed environment issue --- R/lmerSLMADS.assign.R | 39 ++++++++++++++++++++++----------------- R/lmerSLMADS2.R | 37 +++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/R/lmerSLMADS.assign.R b/R/lmerSLMADS.assign.R index 29aaae34..0aabe6b6 100644 --- a/R/lmerSLMADS.assign.R +++ b/R/lmerSLMADS.assign.R @@ -126,39 +126,44 @@ lmerSLMADS.assign <- function(formula, offset, weights, dataName, REML = TRUE, ################################################################## #sort out offset and weights + # + # offset.to.use/weights.to.use are needed in two different environments: + # stats::glm() below resolves them via environment(formula2use), which is + # this function's *caller* (matching where the other formula variables are + # assigned, see the model.variables loop above); lme4::lmer() further down + # resolves them relative to its own call frame, i.e. this function's *own* + # frame. Assign to both so each modelling call finds them. if(is.null(offset)) { varname.offset<-NULL - #offset.to.use <- NULL - cbindtext.offset <- paste0("offset.to.use <- NULL") - eval(parse(text=cbindtext.offset), envir = parent.frame()) + offset.to.use <- NULL + assign("offset.to.use", NULL, envir = parent.frame()) }else{ varname.offset <- paste0(offset) } - + if(!(is.null(offset))) { - cbindtext.offset <- paste0("offset.to.use <- cbind(", offset,")") - eval(parse(text=cbindtext.offset), envir = parent.frame()) + cbindtext.offset <- paste0("cbind(", offset,")") + offset.to.use <- eval(parse(text=cbindtext.offset), envir = parent.frame()) + assign("offset.to.use", offset.to.use, envir = parent.frame()) } - + if(is.null(weights)) { varname.weights<-NULL - cbindtext.weights <- paste0("weights.to.use <- NULL") - eval(parse(text=cbindtext.weights), envir = parent.frame()) - #weights.to.use <- NULL + weights.to.use <- NULL + assign("weights.to.use", NULL, envir = parent.frame()) }else{ varname.weights <- paste0(weights) } - - + + if(!(is.null(weights))) { - cbindtext.weights <- paste0("weights.to.use <- cbind(", weights,")") - eval(parse(text=cbindtext.weights), envir = parent.frame()) - #cbindtext.weights <- paste0("cbind(", weights,")") - #weights.to.use <- eval(parse(text=cbindtext.weights), envir = parent.frame()) + cbindtext.weights <- paste0("cbind(", weights,")") + weights.to.use <- eval(parse(text=cbindtext.weights), envir = parent.frame()) + assign("weights.to.use", weights.to.use, envir = parent.frame()) } #### BEFORE going further we use the glm1 checks @@ -362,7 +367,7 @@ if(!is.null(optimizer)&&optimizer!="nloptwrap") mg <- lme4::lmer(formula2use, offset=offset.to.use, weights=weights.to.use, data=dataDF, REML = REML, verbose = verbose, control = control.obj) outlist <- mg - + } #tidy up in parent.frame() eval(quote(rm(offset.to.use)), envir = parent.frame()) diff --git a/R/lmerSLMADS2.R b/R/lmerSLMADS2.R index a60b67c2..68bcb465 100644 --- a/R/lmerSLMADS2.R +++ b/R/lmerSLMADS2.R @@ -135,39 +135,44 @@ lmerSLMADS2 <- function(formula, offset, weights, dataName, REML = TRUE, ################################################################## #sort out offset and weights + # + # offset.to.use/weights.to.use are needed in two different environments: + # stats::glm() below resolves them via environment(formula2use), which is + # this function's *caller* (matching where the other formula variables are + # assigned, see the model.variables loop above); lme4::lmer() further down + # resolves them relative to its own call frame, i.e. this function's *own* + # frame. Assign to both so each modelling call finds them. if(is.null(offset)) { varname.offset<-NULL - #offset.to.use <- NULL - cbindtext.offset <- paste0("offset.to.use <- NULL") - eval(parse(text=cbindtext.offset), envir = parent.frame()) + offset.to.use <- NULL + assign("offset.to.use", NULL, envir = parent.frame()) }else{ varname.offset <- paste0(offset) } - + if(!(is.null(offset))) { - cbindtext.offset <- paste0("offset.to.use <- cbind(", offset,")") - eval(parse(text=cbindtext.offset), envir = parent.frame()) + cbindtext.offset <- paste0("cbind(", offset,")") + offset.to.use <- eval(parse(text=cbindtext.offset), envir = parent.frame()) + assign("offset.to.use", offset.to.use, envir = parent.frame()) } - + if(is.null(weights)) { varname.weights<-NULL - cbindtext.weights <- paste0("weights.to.use <- NULL") - eval(parse(text=cbindtext.weights), envir = parent.frame()) - #weights.to.use <- NULL + weights.to.use <- NULL + assign("weights.to.use", NULL, envir = parent.frame()) }else{ varname.weights <- paste0(weights) } - - + + if(!(is.null(weights))) { - cbindtext.weights <- paste0("weights.to.use <- cbind(", weights,")") - eval(parse(text=cbindtext.weights), envir = parent.frame()) - #cbindtext.weights <- paste0("cbind(", weights,")") - #weights.to.use <- eval(parse(text=cbindtext.weights), envir = parent.frame()) + cbindtext.weights <- paste0("cbind(", weights,")") + weights.to.use <- eval(parse(text=cbindtext.weights), envir = parent.frame()) + assign("weights.to.use", weights.to.use, envir = parent.frame()) } #### BEFORE going further we use the glm1 checks From f1cd7782f02bbf92ff7d71b019e06835b6ab46ff Mon Sep 17 00:00:00 2001 From: Tim Cadman <41470917+timcadman@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:34:23 +0200 Subject: [PATCH 8/8] Delete Rplots.pdf that entered by mistake --- tests/testthat/Rplots.pdf | Bin 5562 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/testthat/Rplots.pdf diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf deleted file mode 100644 index ac3ab89f20cb92e29bf35287fb79c19aa27ae11c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5562 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�xVfk4>yp)O z#bO-^Po1(Q@dnnt%N}ly-MhQZA0+?1%3&F(dU;v(;wPuJa23>4)%h#v{mq~!OTu4~ z9T5Xs8|Pl@c8Ld`2VZgA@%g4demkSMezsioy@X=C_f`d)G=@ zU+oXCqSwf;Hosf*Rk}K+JuwR=&B(0a_M6hZxh^!URjo-~Ne^23dU|txwK(1+Cx~e~ zVq@=LTetF`Ybf9kLr5D>^IkA`nj=JCTmO5q#^BZQB+O3$0M^2|x}s?=5Z&gn2g)me zaTq*>1XPBBy(k1S8jE%%10eG9U>5?-xFV4-K(HGTd4sm&B2g6js*A!9Q52jj7VQHB zlQCEq8iYflhy*+k>`X+{18JE^6pCgn0l+R8S}PKU1O&_C31k|pKxxrqO6i5-XnusOwND6aS9}w56Xu_7SVLzv{_BAvE9d?{RWa<$q!Fib^zl@b7UnMMslT|G*&ff5{~ikr*tR zNHbW0U~`Ngnl=L