Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7934950
refactor: batch-10 server-side splines, tables, misc functions
timcadman Apr 16, 2026
50afcf3
refactor: add server-side .checkClass for getWGSRDS, rowColCalcDS
timcadman Apr 16, 2026
cbbfb0a
docs: updated authorship
timcadman Apr 16, 2026
5fb83af
docs: redocumented
timcadman Apr 16, 2026
508a1e6
Merge remote-tracking branch 'origin/v7.0-dev' into refactor/perf-bat…
timcadman Sep 15, 2026
f7b5267
tidied up refactor
timcadman Sep 15, 2026
754f335
Remove skip
StuartWheater Sep 18, 2026
119ee0f
merged latest changes
timcadman Sep 21, 2026
a77ac73
Removal of testthat 'skip's
StuartWheater Sep 21, 2026
323e86a
Merge branch 'datashield:refactor/perf-batch-10' into refactor/perf-b…
StuartWheater Sep 21, 2026
648d72b
Update perfence profile
StuartWheater Sep 21, 2026
fd20e4b
Merge branch 'refactor/perf-batch-10' of github.com:StuartWheater/dsB…
StuartWheater Sep 21, 2026
d8d7969
refactor: tidied up PR
timcadman Sep 21, 2026
6dbf845
chore: ignore plots
timcadman Sep 21, 2026
41046bd
Fix setting of PERF.PROFILE env variable
StuartWheater Sep 21, 2026
1923a11
Update to Circle CI code coverage
StuartWheater Sep 21, 2026
f670258
Fixed Comments, I hope
StuartWheater Sep 21, 2026
eec1ebe
Fixed Comments
StuartWheater Sep 21, 2026
c867e6e
Fixed Comments
StuartWheater Sep 21, 2026
824fb5d
Fix CircleCI performance
StuartWheater Sep 21, 2026
4577895
Added debugging output
StuartWheater Sep 21, 2026
cfead15
Added debugging output
StuartWheater Sep 21, 2026
a3540d1
Remove debug code and adjust perf profile
StuartWheater Sep 21, 2026
68f6016
Update logging
StuartWheater Sep 21, 2026
fd3db55
Fix code typo
StuartWheater Sep 21, 2026
59d76de
Fix perf profile
StuartWheater Sep 21, 2026
c3eaedd
Merge branch 'datashield:refactor/perf-batch-10' into refactor/perf-b…
StuartWheater Sep 21, 2026
06319bb
Merge pull request #513 from StuartWheater/refactor/perf-batch-10
StuartWheater Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 2 additions & 16 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,8 @@ jobs:
- run:
command: |
echo "options(Ncpus=4)" >> ~/.Rprofile
sudo Rscript -e "install.packages('RANN', dependencies=TRUE)"
sudo Rscript -e "install.packages('stringr', dependencies=TRUE)"
sudo Rscript -e "install.packages('lme4', dependencies=TRUE)"
sudo Rscript -e "install.packages('dplyr', dependencies=TRUE)"
sudo Rscript -e "install.packages('reshape2', dependencies=TRUE)"
sudo Rscript -e "install.packages('polycor', dependencies=TRUE)"
sudo Rscript -e "install.packages('splines', dependencies=TRUE)"
sudo Rscript -e "install.packages('gamlss', dependencies=TRUE)"
sudo Rscript -e "install.packages('gamlss.dist', dependencies=TRUE)"
sudo Rscript -e "install.packages('mice', dependencies=TRUE)"
sudo Rscript -e "install.packages('childsds', dependencies=TRUE)"
sudo Rscript -e "install.packages('xml2', dependencies=TRUE)"
sudo Rscript -e "install.packages('covr', dependencies=TRUE)"
sudo Rscript -e "install.packages('devtools', dependencies=TRUE)"
sudo Rscript -e "install.packages('DSI', dependencies=TRUE)"
sudo Rscript -e "install.packages('DSLite', dependencies=TRUE)"

sudo R -q -e "options(repos=c(CRAN=paste0('https://packagemanager.posit.co/cran/__linux__/', system('lsb_release -cs', intern=TRUE), '/latest')), HTTPUserAgent=sprintf('R/%s R (%s)', getRversion(), paste(getRversion(), R.version\$platform, R.version\$arch, R.version\$os)), Ncpus=4); install.packages(c('devtools','RANN','stringr','lme4','dplyr','reshape2','polycor','splines','gamlss','gamlss.dist','mice','data.table','childsds'), dependencies=TRUE)"
- run:
command: |
sudo Rscript -e 'library(covr); covr::codecov(token = "'$CODECOV_TOKEN'")'
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
.Rapp.history
.claude
.idea
tests/testthat/Rplots.pdf
3 changes: 2 additions & 1 deletion R/elsplineDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#' @return an object of class "lspline" and "matrix", which its name is specified by the
#' \code{newobj} argument (or its default name "elspline.newobj"), is assigned on the serverside.
#' @author Demetris Avraam for DataSHIELD Development Team
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#' @export
#'
elsplineDS <- function(x = x, n = n, marginal = FALSE, names = NULL){
Expand Down Expand Up @@ -54,7 +55,7 @@ elsplineDS <- function(x = x, n = n, marginal = FALSE, names = NULL){
structure(rval, knots = knots, marginal = marginal, class = c("lspline", "matrix"))
}

x <- eval(parse(text=x), envir = parent.frame())
x <- .loadServersideObject(x)

stopifnot(n >= 2)
k <- seq(min(x, na.rm = TRUE), max(x, na.rm = TRUE), length.out = n + 1)[-c(1, n + 1)]
Expand Down
19 changes: 15 additions & 4 deletions R/getWGSRDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,26 @@
#' @return \code{ds.getWGSR} assigns a numeric vector that includes the z-scores for the
#' specified index.
#' @author Demetris Avraam for DataSHIELD Development Team
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#' @export
#'
getWGSRDS <- function(sex, firstPart, secondPart, index, standing=NA, thirdPart=NA){

sex <- eval(parse(text=sex), envir = parent.frame())
firstPart <- eval(parse(text=firstPart), envir = parent.frame())
secondPart <- eval(parse(text=secondPart), envir = parent.frame())
firstPart.name <- firstPart
sex <- .loadServersideObject(sex)
firstPart <- .loadServersideObject(firstPart)
.checkClass(obj = firstPart, obj_name = firstPart.name, permitted_classes = "numeric")
if (!is.na(secondPart)){
secondPart.name <- secondPart
secondPart <- .loadServersideObject(secondPart)
.checkClass(obj = secondPart, obj_name = secondPart.name, permitted_classes = "numeric")
}
if (!is.na(thirdPart)){
thirdPart <- eval(parse(text=thirdPart), envir = parent.frame())
thirdPart.name <- thirdPart
thirdPart <- .loadServersideObject(thirdPart)
if (index == "bfa"){
.checkClass(obj = thirdPart, obj_name = thirdPart.name, permitted_classes = "numeric")
}
}

# access the internal reference data
Expand Down
3 changes: 2 additions & 1 deletion R/hetcorDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@
#' any missing data were handled: "complete.obs" or "pairwise.complete.obs"; TRUE for ML estimates,
#' FALSE for two-step estimates.
#' @author Demetris Avraam for DataSHIELD Development Team
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#' @export
#'
hetcorDS <- function(data, ML, std.err, bins, pd, use){

data <- eval(parse(text=data), envir = parent.frame())
data <- .loadServersideObject(data)

out <- polycor::hetcor(data = data, ML = ML, std.err = std.err, bins = bins, pd = pd, use = use)

Expand Down
27 changes: 24 additions & 3 deletions R/isValidDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,33 @@
#' @title Checks if an input is valid
#' @description Tells if an object on the server side is valid.
#' @details This function checks if an object is valid.
#' @param obj, a vector (numeric, integer, factor, character), data.frame or matrix
#' @return a boolean, TRUE if input is valid or FALSE if not.
#' @param x a character string, the name of a vector (numeric, integer, factor, character,
#' logical), data.frame or matrix
#' @return a list with \code{valid}, a boolean that is TRUE if the input is valid or FALSE
#' if not, and \code{class}, the class of the input object for client-side consistency
#' checking
#' @author Gaye, A.
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#'@export
#'
isValidDS <- function(obj) {
isValidDS <- function(x) {

obj <- .loadServersideObject(x)
.checkClass(
obj = obj,
obj_name = x,
permitted_classes = c("character", "factor", "integer", "logical", "numeric", "data.frame", "matrix")
)

list(valid = .checkDisclosureSize(obj), class = class(obj))
}

#' Check a Server-Side Object Meets the Disclosure Size Threshold
#'
#' @param obj The object to check: a vector, data.frame or matrix.
#' @return A boolean, TRUE if the object meets the threshold or FALSE if not.
#' @noRd
.checkDisclosureSize <- function(obj) {

# this filter sets the minimum number of observations that are allowed

Expand Down
3 changes: 2 additions & 1 deletion R/lexisDS1.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#' @param exitCol a character string specifying the variable holding the time that each individual is censored or fails
#'
#' @author Burton PR
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#'
#' @return List with `max.time`
#' @export
Expand All @@ -28,7 +29,7 @@ lexisDS1 <- function(exitCol=NULL){
stop(errorMessage, call. = FALSE)
}

exposure <- eval(parse(text=exitCol), envir = parent.frame())
exposure <- .loadServersideObject(exitCol)

max.time <- max(exposure, na.rm=TRUE)
random.multiplier <- stats::runif(1,1.01,1.05)
Expand Down
32 changes: 20 additions & 12 deletions R/lexisDS2.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#' 'data' argument is set the full data.frame will be expanded and carried forward
#'
#' @author Burton PR
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#'
#' @return List with `expanded.table`
#' @export
Expand All @@ -46,10 +47,10 @@ lexisDS2 <- function(datatext=NULL, intervalWidth, maxmaxtime, idCol, entryCol,
#nfilter.string<-as.numeric(thr$nfilter.string)
#############################################################

starttime <- eval(parse(text=entryCol), envir = parent.frame())
endtime <- eval(parse(text=exitCol), envir = parent.frame())
cens <- eval(parse(text=statusCol), envir = parent.frame())
id.orig <- eval(parse(text=idCol), envir = parent.frame())
endtime <- .loadServersideObject(exitCol)
starttime <- if(is.null(entryCol)) rep(0, length(endtime)) else .loadServersideObject(entryCol)
cens <- .loadServersideObject(statusCol)
id.orig <- .loadServersideObject(idCol)

starttime <- as.numeric(starttime)
endtime <- as.numeric(endtime)
Expand All @@ -73,14 +74,21 @@ lexisDS2 <- function(datatext=NULL, intervalWidth, maxmaxtime, idCol, entryCol,

#IDENTIFY VARIABLES TO BE CARRIED WITH THE EXPANDED SURVIVAL DATA

if(is.null(vartext)){
datatext2<-paste0("data.frame(",datatext,")")
DF <- eval(parse(text=datatext2), envir = parent.frame())
}

if(!is.null(vartext)){
vartext2<-paste0("data.frame(",vartext,")")
DF<-eval(parse(text=vartext2), envir = parent.frame())
# 'vartext' takes precedence over 'datatext'. A data.frame name is unpacked into its
# columns and a single column such as D$var is named make.names("D$var"), matching
# how data.frame() named them when the names were parsed as R code.
carried.text <- if(is.null(vartext)) datatext else vartext
if(!is.null(carried.text)){
col.list <- list()
for(nm in unlist(strsplit(carried.text, split=","))){
obj <- .loadServersideObject(nm)
if(is.data.frame(obj)){
col.list <- c(col.list, as.list(obj))
}else{
col.list <- c(col.list, stats::setNames(list(obj), make.names(nm)))
}
}
DF <- data.frame(col.list)
}

if(is.null(datatext)&&is.null(vartext)){
Expand Down
3 changes: 2 additions & 1 deletion R/lsplineDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#' @return an object of class "lspline" and "matrix", which its name is specified by the
#' \code{newobj} argument (or its default name "lspline.newobj"), is assigned on the serverside.
#' @author Demetris Avraam for DataSHIELD Development Team
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#' @export
#'
lsplineDS <- function(x = x, knots = NULL, marginal = FALSE, names = NULL){
Expand Down Expand Up @@ -51,7 +52,7 @@ lsplineDS <- function(x = x, knots = NULL, marginal = FALSE, names = NULL){
structure(rval, knots = knots, marginal = marginal, class = c("lspline", "matrix"))
}

x <- eval(parse(text=x), envir = parent.frame())
x <- .loadServersideObject(x)

out <- lspline_copy(x = x, knots = knots, marginal = marginal, names = names)

Expand Down
3 changes: 2 additions & 1 deletion R/metadataDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#' @return a list containing the metadata. The elements of the list will depend
#' on the metadata available.
#' @author Stuart Wheater, for DataSHIELD Development Team
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#' @export
#'
metadataDS <- function(x)
Expand All @@ -16,7 +17,7 @@ metadataDS <- function(x)
if ((! is.character(x)) || (length(x) != 1))
stop("Variable's name isn't be single character vector", call. = FALSE)

x.var <- eval(parse(text=x), envir = parent.frame())
x.var <- .loadServersideObject(x)

if (is.null(x.var))
stop("Variable can't be NULL", call. = FALSE)
Expand Down
3 changes: 2 additions & 1 deletion R/nsDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#' arguments to ns, and explicitly give the knots, Boundary.knots etc for use by predict.ns().
#' The object is assigned at each serverside.
#' @author Demetris Avraam for DataSHIELD Development Team
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#' @export
#'
nsDS <- function(x, df, knots, intercept, Boundary.knots){
Expand All @@ -33,7 +34,7 @@ nsDS <- function(x, df, knots, intercept, Boundary.knots){
thr <- dsBase::listDisclosureSettingsDS()
nfilter.tab <- as.numeric(thr$nfilter.tab)

x <- eval(parse(text=x), envir = parent.frame())
x <- .loadServersideObject(x)

if(is.null(Boundary.knots)){
Boundary.knots <- range(x, na.rm=TRUE)
Expand Down
3 changes: 2 additions & 1 deletion R/qlsplineDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#' @return an object of class "lspline" and "matrix", which its name is specified by the
#' \code{newobj} argument (or its default name "qlspline.newobj"), is assigned on the serverside.
#' @author Demetris Avraam for DataSHIELD Development Team
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#' @export
#'
qlsplineDS <- function(x = x, q = q, na.rm = TRUE, marginal = FALSE, names = NULL){
Expand Down Expand Up @@ -59,7 +60,7 @@ qlsplineDS <- function(x = x, q = q, na.rm = TRUE, marginal = FALSE, names = NUL
structure(rval, knots = knots, marginal = marginal, class = c("lspline", "matrix"))
}

x <- eval(parse(text=x), envir = parent.frame())
x <- .loadServersideObject(x)

if (length(q) == 1 && q >= 2) {
q <- seq(0, 1, length.out = q + 1)[-c(1, q + 1)]
Expand Down
2 changes: 1 addition & 1 deletion R/quantileMeanDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ quantileMeanDS <- function (x) {
.checkClass(obj = xvect, obj_name = x, permitted_classes = c("numeric", "integer"))

# check if the input vector is valid (i.e. meets DataSHIELD criteria)
check <- isValidDS(xvect)
check <- .checkDisclosureSize(xvect)

if(check){
# if the input vector is valid
Expand Down
8 changes: 6 additions & 2 deletions R/rangeDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
#' @title returns the minimum and maximum of a numeric vector
#' @description this function is similar to R function \code{range} but instead to not return
#' the real minimum and maximum, the computed values are multiplied by a very small random number.
#' @param xvect a numerical
#' @param x a character string, the name of a numeric or integer vector
#' @return a numeric vector which contains the minimum and the maximum values of the vector
#' @author Amadou Gaye, Demetris Avraam for DataSHIELD Development Team
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#' @export
#'
rangeDS <- function(xvect) {
rangeDS <- function(x) {
xvect <- .loadServersideObject(x)
.checkClass(obj = xvect, obj_name = x, permitted_classes = c("numeric", "integer"))

# back-up current .Random.seed and revert on.exit
if (exists(x = ".Random.seed", envir = globalenv())) {
assign(x = ".old_seed", value = .Random.seed, envir = parent.frame());
Expand Down
2 changes: 1 addition & 1 deletion R/recodeLevelsDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ recodeLevelsDS <- function (x=NULL, classes=NULL){
dsBase::checkPermissivePrivacyControlLevel(c('permissive', 'banana', 'carrot'))

# check if the input vector is valid (i.e. meets DataSHIELD criteria)
check <- isValidDS(x)
check <- .checkDisclosureSize(x)

if(check){
# generate the new variable with the specified levels
Expand Down
2 changes: 1 addition & 1 deletion R/replaceNaDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ replaceNaDS <- function(x, replacements){
xvect <- .loadServersideObject(x)

# check if the input vector is valid (i.e. meets DataSHIELD criteria)
check <- dsBase::isValidDS(xvect)
check <- .checkDisclosureSize(xvect)

# if the input vector is a factor convert it to numeric
cls <- class(xvect)
Expand Down
14 changes: 11 additions & 3 deletions R/rowColCalcDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@
#' 'rowMeans' and 'colMeans'.
#' @details the output is returned to the user only the number of entries in the
#' output vector is greater or equal to the allowed size.
#' @param dataset an array of two or more dimensions.
#' @param dataset.name a character string providing the name of a server-side array of two or more dimensions.
#' @param operation an integer that indicates the operation to carry out:
#' 1 for 'rowSums', 2 for 'colSums', 3 for 'rowMeans' or 4 for 'colMeans'
#' @return a numeric vector
#' @export
#' @author Gaye, A.
#' @author Tim Cadman, Genomics Coordination Centre, UMCG, Netherlands
#'
rowColCalcDS <- function (dataset, operation) {
rowColCalcDS <- function (dataset.name, operation) {

dataset <- .loadServersideObject(dataset.name)
.checkClass(obj = dataset, obj_name = dataset.name, permitted_classes = c("data.frame", "matrix"))
column.classes <- lapply(as.data.frame(dataset), class)
if(!all(vapply(column.classes, function(cls) any(cls %in% c("numeric", "integer")), logical(1)))){
stop("One or more columns of '", dataset.name, "' are not of numeric type", call. = FALSE)
}

if(operation == 1){
result <- rowSums(dataset, na.rm=TRUE)
}
Expand All @@ -27,7 +35,7 @@ rowColCalcDS <- function (dataset, operation) {
}

# check if the output is valid (i.e. meets DataSHIELD criteria)
check <- isValidDS(result)
check <- .checkDisclosureSize(result)
if(check){
return(result)
}else{
Expand Down
Loading
Loading