From e0e1571cde466313449944692487aa548215f3f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 3 Mar 2025 09:09:46 +0100 Subject: [PATCH 001/110] Add some initial definitions --- NAMESPACE | 14 +++++ R/delayed.R | 8 +++ R/ops_transform.R | 87 +++++++++++++++++++++++++++++ R/resolver.R | 3 + R/types.R | 69 +++++++++++++++++++++++ tests/testthat/test-ops_transform.R | 26 +++++++++ 6 files changed, 207 insertions(+) create mode 100644 R/delayed.R create mode 100644 R/ops_transform.R create mode 100644 R/resolver.R create mode 100644 R/types.R create mode 100644 tests/testthat/test-ops_transform.R diff --git a/NAMESPACE b/NAMESPACE index 209b1855..2f1d66ad 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,13 @@ # Generated by roxygen2: do not edit by hand +S3method("&",dataset) +S3method("&",transform) +S3method("&",variable) +S3method("|",dataset) +S3method(chooseOpsMethod,dataset) +S3method(chooseOpsMethod,transform) +S3method(chooseOpsMethod,value) +S3method(chooseOpsMethod,variable) S3method(data_extract_multiple_srv,FilteredData) S3method(data_extract_multiple_srv,list) S3method(data_extract_multiple_srv,reactive) @@ -12,6 +20,7 @@ S3method(merge_expression_module,reactive) S3method(merge_expression_srv,list) S3method(merge_expression_srv,reactive) S3method(print,choices_labeled) +S3method(print,dataset) S3method(print,delayed_choices_selected) S3method(print,delayed_data_extract_spec) S3method(print,delayed_filter_spec) @@ -19,6 +28,8 @@ S3method(print,delayed_select_spec) S3method(print,delayed_value_choices) S3method(print,delayed_variable_choices) S3method(print,filter_spec) +S3method(print,value) +S3method(print,variable) S3method(resolve,default) S3method(resolve,delayed_choices_selected) S3method(resolve,delayed_data_extract_spec) @@ -45,6 +56,7 @@ export(data_extract_spec) export(data_extract_srv) export(data_extract_ui) export(datanames_input) +export(dataset) export(filter_spec) export(first_choice) export(first_choices) @@ -68,7 +80,9 @@ export(select_spec) export(select_spec.default) export(select_spec.delayed_data) export(split_by_sep) +export(transform) export(value_choices) +export(variable) export(variable_choices) import(shiny) importFrom(dplyr,"%>%") diff --git a/R/delayed.R b/R/delayed.R new file mode 100644 index 00000000..9d421b09 --- /dev/null +++ b/R/delayed.R @@ -0,0 +1,8 @@ +delay <- function(x) { + class(x) <- "delayed" + x +} + +is.delayed <- function(x) { + inherits(x, "delayed") +} diff --git a/R/ops_transform.R b/R/ops_transform.R new file mode 100644 index 00000000..fd190982 --- /dev/null +++ b/R/ops_transform.R @@ -0,0 +1,87 @@ +#' @export +`&.transform` <- function(e1, e2) { + + if (is.transform(e1) && is.dataset(e2)) { + o <- e1 + o$dataset <- unique(c(e1$dataset, e2$dataset)) + } else if (is.transform(e1) && is.variable(e2)) { + o <- e1 + o$variable <- unique(c(e1$variable, e2$variable)) + } else if (is.transform(e1) && is.value(e2)) { + o <- e1 + o$value <- unique(c(e1$variable, e2$value)) + } + class(o) <- c("delayed", "transform") + o +} + +#' @export +`&.dataset` <- function(e1, e2) { + e1_var <- e1[["names"]] + e2_var <- e2[["names"]] + + if (is.character(e1_var) && is.character(e2_var)) { + x <- list(dataset = unique(c(e1_var, e2_var))) + } + class(x) <- c("delayed", "transform") + x +} + + +#' @export +`&.variable` <- function(e1, e2) { + dataset_n_var <- is.dataset(e1) && is.variable(e2) + e1_var <- e1[["names"]] + e2_var <- e2[["names"]] + + if (dataset_n_var && is.character(e1_var) && is.character(e2_var)) { + x <- list(dataset = e1_var, variable = e2_var) + } + if (is.variable(e1) && is.variable(e2)) { + x <- list(dataset = NA, variable = unique(c(e1_var, e2_var))) + } + + if (is.variable(e1) && is.dataset(e2)) { + x <- list(dataset = e2_var, variable = e1_var) + } + if (is.variable(e2) && is.dataset(e1)) { + x <- list(dataset = e1_var, variable = e2_var) + } + class(x) <- c("delayed", "transform") + x +} + +#' @export +`|.dataset` <- function(e1, e2) { + list() + + + class(x) <- c("delayed", "transform") + x +} + +#' @export +chooseOpsMethod.transform <- function(x, y, mx, my, cl, reverse) { + !is.transform(x) +} + +#' @export +chooseOpsMethod.dataset <- function(x, y, mx, my, cl, reverse) { + # cat("\nx\n") + # print(mx) + # cat("\ny\n") + # print(my) + # cat("\ncl\n") + # print(cl) + # cat("\nreverse\n") + # print(reverse) + is.transform(x) +} + +#' @export +chooseOpsMethod.variable <- function(x, y, mx, my, cl, reverse) TRUE + +#' @export +chooseOpsMethod.value <- function(x, y, mx, my, cl, reverse) TRUE + +# ?Ops diff --git a/R/resolver.R b/R/resolver.R new file mode 100644 index 00000000..e81a6535 --- /dev/null +++ b/R/resolver.R @@ -0,0 +1,3 @@ +# resolver.dataset +# resolver.variable +# resolver.value diff --git a/R/types.R b/R/types.R new file mode 100644 index 00000000..46041f3f --- /dev/null +++ b/R/types.R @@ -0,0 +1,69 @@ +#' @export +transform <- function() { + o <- list(dataset = NA, variables = NA, values = NA) + class(o) <- c("delayed", "transform") + o +} + +is.transform <- function(x) { + inherits(x, "transform") +} + +#' @export +dataset <- function(x, select = first_choice) { + o <- list(names = x, select = select) + class(o) <- c("delayed", "dataset") + o +} + +is.dataset <- function(x) { + inherits(x, "dataset") +} + +#' @export +print.dataset <- function(x) { + if (is.delayed(x)) { + cat("Delayed dataset for:", x$names) + } else { + cat("Dataset for:", x$names) + } +} + +#' @export +variable <- function(x, select = first_choice) { + o <- list(names = x, select = select) + class(o) <- c("delayed", "variable") + o +} + +is.variable <- function(x) { + inherits(x, "variable") +} + +#' @export +print.variable <- function(x) { + if (is.delayed(x)) { + cat("Delayed variable for:", x$names) + } else { + cat("Variable for:", x$names) + } +} + +value <- function(x, select = first_choice) { + o <- list(names = x, select = select) + class(o) <- c("delayed", "value") + o +} + +is.value <- function(x) { + inherits(x, "value") +} + +#' @export +print.value <- function(x) { + if (is.delayed(x)) { + cat("Delayed value for:", x$names) + } else { + cat("Value for:", x$names) + } +} diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R new file mode 100644 index 00000000..79701230 --- /dev/null +++ b/tests/testthat/test-ops_transform.R @@ -0,0 +1,26 @@ +test_that("datasets Ops work", { + dataset1 <- dataset("ABC") + dataset2 <- dataset("ABC2") + datasets <- dataset1 & dataset1 + expect_equal(datasets$dataset, "ABC") + datasets <- dataset1 & dataset2 + expect_equal(datasets$dataset, c("ABC", "ABC2")) + datasets2 <- datasets & dataset2 + expect_equal(datasets$dataset, c("ABC", "ABC2")) +}) + +test_that("variables Ops work", { + var1 <- variable("abc") + var2 <- variable("abc2") + vars <- var1 & var1 + expect_equal(vars$variable, "abc") + vars <- var1 & var2 + expect_equal(vars$variable, c("abc", "abc2")) +}) + +test_that("variables, datsets Ops work", { + dataset1 <- dataset("ABC2") + var1 <- variable("abc") + expect_equal(dataset1 & var1, var1 & dataset1) + expect_equal(vars$variable, c("ABC", "ABC2")) +}) From 707224eed1c704020da905ec4704c36bcc803ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 3 Mar 2025 18:08:19 +0100 Subject: [PATCH 002/110] Add some infrastructure --- NAMESPACE | 28 ++-- R/delayed.R | 59 ++++++- R/ops_transform.R | 100 ++++------- R/resolver.R | 246 +++++++++++++++++++++++++++- R/types.R | 136 +++++++++++---- man/resolver.Rd | 32 ++++ tests/testthat/test-delayed.R | 17 ++ tests/testthat/test-ops_transform.R | 134 ++++++++++++--- tests/testthat/test-resolver.R | 64 ++++++++ 9 files changed, 669 insertions(+), 147 deletions(-) create mode 100644 man/resolver.Rd create mode 100644 tests/testthat/test-delayed.R create mode 100644 tests/testthat/test-resolver.R diff --git a/NAMESPACE b/NAMESPACE index 2f1d66ad..72e9fbd7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,13 +1,12 @@ # Generated by roxygen2: do not edit by hand -S3method("&",dataset) S3method("&",transform) -S3method("&",variable) +S3method("[","type<-") +S3method("[",type) +S3method("[[","type<-") +S3method("[[",type) S3method("|",dataset) -S3method(chooseOpsMethod,dataset) -S3method(chooseOpsMethod,transform) -S3method(chooseOpsMethod,value) -S3method(chooseOpsMethod,variable) +S3method(c,type) S3method(data_extract_multiple_srv,FilteredData) S3method(data_extract_multiple_srv,list) S3method(data_extract_multiple_srv,reactive) @@ -15,12 +14,13 @@ S3method(data_extract_srv,FilteredData) S3method(data_extract_srv,list) S3method(filter_spec_internal,default) S3method(filter_spec_internal,delayed_data) +S3method(is.delayed,default) +S3method(is.delayed,type) S3method(merge_expression_module,list) S3method(merge_expression_module,reactive) S3method(merge_expression_srv,list) S3method(merge_expression_srv,reactive) S3method(print,choices_labeled) -S3method(print,dataset) S3method(print,delayed_choices_selected) S3method(print,delayed_data_extract_spec) S3method(print,delayed_filter_spec) @@ -28,8 +28,6 @@ S3method(print,delayed_select_spec) S3method(print,delayed_value_choices) S3method(print,delayed_variable_choices) S3method(print,filter_spec) -S3method(print,value) -S3method(print,variable) S3method(resolve,default) S3method(resolve,delayed_choices_selected) S3method(resolve,delayed_data_extract_spec) @@ -56,7 +54,7 @@ export(data_extract_spec) export(data_extract_srv) export(data_extract_ui) export(datanames_input) -export(dataset) +export(datasets) export(filter_spec) export(first_choice) export(first_choices) @@ -67,6 +65,7 @@ export(get_extract_datanames) export(get_merge_call) export(get_relabel_call) export(is.choices_selected) +export(is.delayed) export(is_single_dataset) export(last_choice) export(last_choices) @@ -76,14 +75,19 @@ export(merge_expression_module) export(merge_expression_srv) export(no_selected_as_NULL) export(resolve_delayed) +export(resolver) export(select_spec) export(select_spec.default) export(select_spec.delayed_data) export(split_by_sep) -export(transform) export(value_choices) -export(variable) +export(values) export(variable_choices) +export(variables) +export(variables.MultiAssayExperiment) +export(variables.data.frame) +export(variables.default) +export(variables.matrix) import(shiny) importFrom(dplyr,"%>%") importFrom(lifecycle,badge) diff --git a/R/delayed.R b/R/delayed.R index 9d421b09..f67c7e2c 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -1,8 +1,63 @@ delay <- function(x) { - class(x) <- "delayed" + class(x) <- c("delayed", class(x)) x } -is.delayed <- function(x) { +#' @export +#' @method is.delayed type +is.delayed.type <- function(x) { + !all(is.character(x$names)) || !all(is.character(x$select)) +} + +#' @export is.delayed +#' @method is.delayed transform +is.delayed.transform <- function(x) { + is.delayed(x$datasets) || is.delayed(x$variables) || is.delayed(x$values) +} + +#' @export +#' @method is.delayed default +is.delayed.default <- function(x) { inherits(x, "delayed") } + +#' @export +is.delayed <- function(x) { + UseMethod("is.delayed") +} + +resolved <- function(x, variable){ + s <- all(is.character(x$names)) && all(is.character(x$select)) + + if (!s && !all(x$select %in% x$names)) { + stop("Selected ", variable, " not available") + } + + cl <- class(x) + class(x) <- setdiff(cl, "delayed") + x +} + +get_datanames <- function(x) { + if (is.transform(x) && !is.delayed(x$datasets)) { + x$datasets$names + } else { + NULL + } +} + +get_variables <- function(x) { + if (is.transform(x) && !is.delayed(x$datasets) && !is.delayed(x$variables)) { + x$variables$names + } else { + NULL + } +} + +get_values <- function(x) { + if (is.transform(x) && !is.delayed(x)) { + x$values$names + } else { + NULL + } +} diff --git a/R/ops_transform.R b/R/ops_transform.R index fd190982..5a97d3df 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -1,87 +1,47 @@ #' @export `&.transform` <- function(e1, e2) { - - if (is.transform(e1) && is.dataset(e2)) { - o <- e1 - o$dataset <- unique(c(e1$dataset, e2$dataset)) - } else if (is.transform(e1) && is.variable(e2)) { - o <- e1 - o$variable <- unique(c(e1$variable, e2$variable)) - } else if (is.transform(e1) && is.value(e2)) { - o <- e1 - o$value <- unique(c(e1$variable, e2$value)) + if (!is.transform(e1) || !is.transform(e2)) { + stop("Method not available") } - class(o) <- c("delayed", "transform") - o -} - -#' @export -`&.dataset` <- function(e1, e2) { - e1_var <- e1[["names"]] - e2_var <- e2[["names"]] - - if (is.character(e1_var) && is.character(e2_var)) { - x <- list(dataset = unique(c(e1_var, e2_var))) + o <- transform() + if (has_dataset(e1) || has_dataset(e2)) { + o$datasets <- c(e1$datasets, e2$datasets) + o$datasets <- o$datasets[!is.na(o$datasets)] } - class(x) <- c("delayed", "transform") - x -} - - -#' @export -`&.variable` <- function(e1, e2) { - dataset_n_var <- is.dataset(e1) && is.variable(e2) - e1_var <- e1[["names"]] - e2_var <- e2[["names"]] - - if (dataset_n_var && is.character(e1_var) && is.character(e2_var)) { - x <- list(dataset = e1_var, variable = e2_var) + if (has_variable(e1) || has_variable(e2)) { + o$variables <- c(e1$variables, e2$variables) + o$variables <- o$variables[!is.na(o$variables)] } - if (is.variable(e1) && is.variable(e2)) { - x <- list(dataset = NA, variable = unique(c(e1_var, e2_var))) + if (has_value(e1) || has_value(e2)) { + o$values <- c(e1$values, e2$values) + o$values <- o$values[!is.na(o$values)] } - if (is.variable(e1) && is.dataset(e2)) { - x <- list(dataset = e2_var, variable = e1_var) - } - if (is.variable(e2) && is.dataset(e1)) { - x <- list(dataset = e1_var, variable = e2_var) - } - class(x) <- c("delayed", "transform") - x + class(o) <- c("delayed", "transform") + o } #' @export `|.dataset` <- function(e1, e2) { - list() - - + if (!is.transform(e1) || !is.transform(e2)) { + stop("Method not available") + } + s <- transform() class(x) <- c("delayed", "transform") x } -#' @export -chooseOpsMethod.transform <- function(x, y, mx, my, cl, reverse) { - !is.transform(x) -} - -#' @export -chooseOpsMethod.dataset <- function(x, y, mx, my, cl, reverse) { - # cat("\nx\n") - # print(mx) - # cat("\ny\n") - # print(my) - # cat("\ncl\n") - # print(cl) - # cat("\nreverse\n") - # print(reverse) - is.transform(x) -} - -#' @export -chooseOpsMethod.variable <- function(x, y, mx, my, cl, reverse) TRUE - -#' @export -chooseOpsMethod.value <- function(x, y, mx, my, cl, reverse) TRUE +# #' @export +# chooseOpsMethod.transform <- function(x, y, mx, my, cl, reverse) { +# # cat("\nx\n") +# # print(mx) +# # cat("\ny\n") +# # print(my) +# # cat("\ncl\n") +# # print(cl) +# # cat("\nreverse\n") +# # print(reverse) +# is.transform(x) +# } # ?Ops diff --git a/R/resolver.R b/R/resolver.R index e81a6535..0645206f 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -1,3 +1,243 @@ -# resolver.dataset -# resolver.variable -# resolver.value +#' Resolve the specification +#' +#' Given the specification of some data to extract find if they are available or not. +#' +#' @param spec A object extraction specification. +#' @param data A `qenv()`, or `teal.data::teal_data()` object. +#' +#' @returns A transform but resolved +#' @export +#' +#' @examples +#' dataset1 <- datasets("df", function(x){head(x, 1)}) +#' dataset2 <- datasets(is.matrix, function(x){head(x, 1)}) +#' spec <- dataset1 & variables("a", "a") +#' td <- within(teal.data::teal_data(), { +#' df <- data.frame(a = as.factor(LETTERS[1:5]), b = letters[1:5]) +#' m <- matrix() +#' }) +#' resolver(dataset2, td) +#' resolver(spec, td) +#' spec <- dataset1 & variables("a", is.factor) +#' resolver(spec, td) +resolver <- function(spec, data, ...) { + if (!is(data, "qenv")) { + stop("Please use qenv() or teal_data() objects.") + } + stopifnot(is.transform(spec), has_dataset(spec)) + specf <- spec + if (has_dataset(specf)) { + specf <- resolver.datasets(specf, data) + } else { + specf$datasets <- NULL + } + + if (has_variable(specf) && !is.delayed(specf$datasets)) { + specf <- resolver.variables(specf, data) + } else { + specf$variables <- NULL + } + + if (has_value(specf) && !is.delayed(specf$datasets) && !is.delayed(specf$variables)) { + specf <- resolver.values(specf, data) + } else { + specf$values <- NULL + } + + class(specf) <- setdiff(class(specf), "delayed") + specf +} + +functions_names <- function(unresolved, reference) { + + if (length(unresolved) == 1 && is.function(unresolved)) { + out <- tryCatch(unresolved(reference), error = function(x) unresolved) + if (is.logical(out) && length(out) == length(reference)) { + return(reference[out]) + } else { + return(NULL) + } + } + + is_fc <- vapply(unresolved, is.function, logical(1L)) + fc_unresolved <- unresolved[is_fc] + x <- vector("character") + for (f in fc_unresolved) { + + y <- tryCatch(f(reference), error = function(x) f ) + if (!is.logical(y)) { + stop("Provided functions should return a logical object.") + } + x <- c(x, reference[y[!is.na(y)]]) + } + unique(unlist(c(unresolved[!is_fc], x), FALSE, FALSE)) +} + +functions_data <- function(unresolved, data) { + if (length(unresolved) == 1 && is.function(unresolved)){ + out <- tryCatch(vapply(data, unresolved, logical(1L)), error = function(x) unresolved) + if (is.logical(out) && length(out) == length(data)) { + return(names(data)[out]) + } else { + return(NULL) + } + } + + fc_unresolved <- unresolved[vapply(unresolved, is.function, logical(1L))] + + # This is for variables + names <- names(data) + l <- lapply(fc_unresolved, function(f) {names[which(f(data))]}) + unique(unlist(l, FALSE, FALSE)) +} + +resolver.datasets <- function(spec, data) { + if (!is(data, "qenv")) { + stop("Please use qenv() or teal_data() objects.") + } + + sdatasets <- spec$datasets + data_names <- names(data) + + if (is.delayed(sdatasets) && all(is.character(sdatasets$names))) { + match <- intersect(data_names, sdatasets$names) + missing <- setdiff(sdatasets$names, data_names) + if (length(missing)) { + stop("Missing datasets ", paste(sQuote(missing), collapse = ", "), " were specified.") + } + sdatasets$names <- match + if (length(match) == 0) { + stop("No selected datasets matching the conditions requested") + } else if (length(match) == 1) { + sdatasets$select <- match + } else { + new_select <- c(functions_names(sdatasets$select, sdatasets$names), + functions_data(sdatasets$select, data[sdatasets$names])) + sdatasets$select <- unique(new_select[!is.na(new_select)]) + } + } else if (is.delayed(sdatasets)) { + new_names <- c(functions_names(sdatasets$names, data_names), + functions_data(sdatasets$names, data)) + sdatasets$names <- unique(new_names[!is.na(new_names)]) + + if (length(sdatasets$names) == 0) { + stop("No selected datasets matching the conditions requested") + } else if (length(sdatasets$names) == 1) { + svariables$select <- sdatasets$names + } else { + new_select <- c(functions_names(sdatasets$select, sdatasets$names), + functions_data(sdatasets$select, data[sdatasets$names])) + + sdatasets$select <- unique(new_select[!is.na(new_select)]) + } + } + + spec$datasets <- resolved(sdatasets, "dataset") + spec +} + +resolver.variables <- function(spec, data) { + if (!is(data, "qenv")) { + stop("Please use qenv() or teal_data() objects.") + } + + if (is.delayed(spec$datasets)) { + stop("Datasets not resolved yet") + } + datasets <- spec$datasets$select + data_selected <- data(data, datasets) + dataset <- data_selected[[datasets]] + names_data <- names(dataset) + + svariables <- spec$variables + + if (is.delayed(svariables) && all(is.character(svariables$names))) { + match <- intersect(names_data, svariables$names) + missing <- setdiff(svariables$names, names_data) + if (length(missing)) { + stop("Missing variables ", paste(sQuote(missing), collapse = ", "), " were specified.") + } + svariables$names <- match + if (length(match) == 1) { + svariables$select <- match + } else { + new_select <- c(functions_names(svariables$select, svariables$names), + functions_data(svariables$select, dataset)) + svariables$select <- unique(new_select[!is.na(new_select)]) + } + } else if (is.delayed(svariables)) { + new_names <- c(functions_names(svariables$names, names_data), + functions_data(svariables$names, dataset)) + svariables$names <- unique(new_names[!is.na(new_names)]) + if (length(match) == 1) { + svariables$select <- svariables$names + } else { + new_select <- c(functions_names(svariables$select, svariables$names), + functions_data(svariables$select, dataset)) + svariables$select <- unique(new_select[!is.na(new_select)]) + } + } + spec$variables <- resolved(svariables, "variables") + spec +} + +resolver.values <- function(spec, data) { + if (!is(data, "qenv")) { + stop("Please use qenv() or teal_data() objects.") + } + + variables <- spec$variables$names + svalues <- spec$values + spec$variables <- if (is.delayed(svalues) && all(is.character(svalues$names))) { + match <- intersect(datasets, svalues$names) + missing <- setdiff(svalues$names, datasets) + if (length(missing)) { + stop("Missing values ", paste(sQuote(missing), collapse = ", "), " were specified.") + } + svalues$names <- match + svalues$select <- functions_names(svalues$select, match) + svalues + } else if (is.delayed(svalues)) { + svalues$names <- functions_names(svalues$names, datasets) + svalues$select <- functions_names(svalues$select, svalues$names) + svalues + } + + spec$values <- resolved(svalues, "values") + spec +} + +#' @export +data.MultiAssayExperiment <- function(x, variable) { + # length(variable) == 1L + cd <- colData(x) + cd[[variable]] +} + +#' @export +data.matrix <- function(x, variable) { + # length(variable) == 1L + x[, variable, drop = TRUE] +} + +#' @export +#' @method data data.frame +data.data.frame <- function(x, variable) { + # length(variable) == 1L + x[, variable, drop = TRUE] +} + +#' @export +data.qenv <- function(x, variable) { + x[variable] +} + +#' @export +data.default <- function(x, variable) { + x[, variable, drop = TRUE] +} + +#' @export +data <- function(x, variable) { + UseMethod("data") +} diff --git a/R/types.R b/R/types.R index 46041f3f..d488dab2 100644 --- a/R/types.R +++ b/R/types.R @@ -1,6 +1,5 @@ -#' @export transform <- function() { - o <- list(dataset = NA, variables = NA, values = NA) + o <- list(datasets = na_type(), variables = na_type(), values = na_type()) class(o) <- c("delayed", "transform") o } @@ -9,61 +8,128 @@ is.transform <- function(x) { inherits(x, "transform") } -#' @export -dataset <- function(x, select = first_choice) { - o <- list(names = x, select = select) - class(o) <- c("delayed", "dataset") - o +has_dataset <- function(x) { + !anyNA(x[["datasets"]]) +} + +has_variable <- function(x) { + !anyNA(x[["variables"]]) } -is.dataset <- function(x) { - inherits(x, "dataset") +has_value <- function(x) { + !anyNA(x[["values"]]) +} + +na_type <- function() { + out <- NA + class(out) <- "type" + out } #' @export -print.dataset <- function(x) { - if (is.delayed(x)) { - cat("Delayed dataset for:", x$names) - } else { - cat("Dataset for:", x$names) - } +datasets <- function(x, select = first_choice) { + stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) + stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) + + type <- list(names = x, select = select) + class(type) <- c("delayed", "datasets", "type", "list") + o <- list(datasets = type, variables = na_type(), values = na_type()) + class(o) <- c("delayed", "transform", "list") + o } + #' @export -variable <- function(x, select = first_choice) { - o <- list(names = x, select = select) - class(o) <- c("delayed", "variable") +variables <- function(x, select = first_choice) { + stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) + stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) + + type <- list(names = x, select = select) + class(type) <- c("delayed", "variables", "type") + o <- list(datasets = na_type(), variables = type, values = na_type()) + class(o) <- c("delayed", "transform") o } -is.variable <- function(x) { - inherits(x, "variable") +#' @export +values <- function(x, select = first_choice) { + stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) + stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) + + type <- list(names = x, select = select) + class(type) <- c("delayed", "values", "type") + o <- list(datasets = na_type(), variables = na_type(), values = type) + class(o) <- c("delayed", "transform") + o } #' @export -print.variable <- function(x) { - if (is.delayed(x)) { - cat("Delayed variable for:", x$names) +c.type <- function(...) { + c1 <- class(..1) + c2 <- class(..2) + classes <- unique(c(c1, c2)) + other_classes <- setdiff(classes, c("delayed", "type")) + + if ("delayed" %in% classes) { + classes <- c("delayed", other_classes, "type") } else { - cat("Variable for:", x$names) + classes <- c(other_classes, "type") + } + + out <- NextMethod("c") + + if (all(is.na(out))) { + return(na_type()) + } else if (anyNA(out)) { + out <- out[!is.na(out)] } + nam <- names(out) + names <- nam == "names" + selects <- nam == "select" + + out <- list(names = unlist(out[names], FALSE, FALSE), + select = unlist(out[selects], FALSE, FALSE)) + + l <- lapply(out, unique) + class(l) <- classes + l } -value <- function(x, select = first_choice) { - o <- list(names = x, select = select) - class(o) <- c("delayed", "value") - o +#' @export +`[.type` <- function(x, i, j, ..., exact = TRUE) { + cx <- class(x) + out <- NextMethod("[") + class(out) <- cx + out } -is.value <- function(x) { - inherits(x, "value") +#' @export +`[.type<-` <- function(x, i, j, ..., value) { + cx <- class(x) + if (!"type" %in% class(value)) { + stop("Modifying the specification with invalid objects") + } + out <- NextMethod("[") + class(out) <- cx + out } #' @export -print.value <- function(x) { - if (is.delayed(x)) { - cat("Delayed value for:", x$names) - } else { - cat("Value for:", x$names) +`[[.type` <- function(x, i, ..., drop = TRUE) { + cx <- class(x) + out <- NextMethod("[[") + class(out) <- cx + out +} + + +#' @export +`[[.type<-` <- function(x, i, value) { + cx <- class(x) + if (!"type" %in% class(value)) { + stop("Modifying the specification with invalid objects") } + out <- NextMethod("[") + class(out) <- cx + out } diff --git a/man/resolver.Rd b/man/resolver.Rd new file mode 100644 index 00000000..1063d0ef --- /dev/null +++ b/man/resolver.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resolver.R +\name{resolver} +\alias{resolver} +\title{Resolve the specification} +\usage{ +resolver(spec, data, ...) +} +\arguments{ +\item{spec}{A object extraction specification.} + +\item{data}{A \code{qenv()}, or \code{teal.data::teal_data()} object.} +} +\value{ +A transform but resolved +} +\description{ +Given the specification of some data to extract find if they are available or not. +} +\examples{ +dataset1 <- datasets("df", function(x){head(x, 1)}) +dataset2 <- datasets(is.matrix, function(x){head(x, 1)}) +spec <- dataset1 & variables("a", "a") +td <- within(teal.data::teal_data(), { + df <- data.frame(a = as.factor(LETTERS[1:5]), b = letters[1:5]) + m <- matrix() +}) +resolver(dataset2, td) +resolver(spec, td) +spec <- dataset1 & variables("a", is.factor) +resolver(spec, td) +} diff --git a/tests/testthat/test-delayed.R b/tests/testthat/test-delayed.R new file mode 100644 index 00000000..b9408a34 --- /dev/null +++ b/tests/testthat/test-delayed.R @@ -0,0 +1,17 @@ +test_that("delay works", { + out <- 1 + dout <- delay(out) + expect_s3_class(dout, "delayed") + expect_true(is.delayed(dout)) + expect_equal(resolved(dout), out) +}) + +test_that("is.delayed works", { + d <- datasets("a") + v <- variables("b") + expect_true(is.delayed(d)) + expect_true(is.delayed(datasets("a", "a"))) + expect_true(is.delayed(v)) + expect_true(is.delayed(variables("b", "b"))) + expect_true(is.delayed(d & v)) +}) diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R index 79701230..847c0b55 100644 --- a/tests/testthat/test-ops_transform.R +++ b/tests/testthat/test-ops_transform.R @@ -1,26 +1,110 @@ -test_that("datasets Ops work", { - dataset1 <- dataset("ABC") - dataset2 <- dataset("ABC2") - datasets <- dataset1 & dataset1 - expect_equal(datasets$dataset, "ABC") - datasets <- dataset1 & dataset2 - expect_equal(datasets$dataset, c("ABC", "ABC2")) - datasets2 <- datasets & dataset2 - expect_equal(datasets$dataset, c("ABC", "ABC2")) -}) - -test_that("variables Ops work", { - var1 <- variable("abc") - var2 <- variable("abc2") - vars <- var1 & var1 - expect_equal(vars$variable, "abc") - vars <- var1 & var2 - expect_equal(vars$variable, c("abc", "abc2")) -}) - -test_that("variables, datsets Ops work", { - dataset1 <- dataset("ABC2") - var1 <- variable("abc") - expect_equal(dataset1 & var1, var1 & dataset1) - expect_equal(vars$variable, c("ABC", "ABC2")) +basic_ops <- function(fun) { + FUN <- match.fun(fun) + type1 <- FUN("ABC") + type2 <- FUN("ABC2") + types <- type1 & type1 + out <- list(names = "ABC", select = list(first_choice)) + class(out) <- c("delayed", fun, "type") + expect_equal(types[[fun]], out) + types <- type1 & type2 + expect_equal(types[[fun]]$names, c("ABC", "ABC2")) + types2 <- types & type2 + expect_equal(types[[fun]]$names, c("ABC", "ABC2")) + expect_s3_class(types[[fun]], class(out)) + type3 <- FUN("ABC2", select = all_choices) + types <- type1 & type3 + expect_length(types[[fun]]$select, 2) + type2b <- FUN(first_choice) + type2c <- FUN(last_choice) + out <- type2b & type2c + expect_length(out[[fun]]$names, 2) + expect_error(FUN("ABC") & 1) + out <- type1 & type2b +} + +test_that("datasets & work", { + basic_ops("datasets") +}) + + +test_that("variables & work", { + basic_ops("variables") +}) + +test_that("values & work", { + basic_ops("values") +}) + +test_that("datsets & variables work", { + dataset1 <- datasets("ABC2") + var1 <- variables("abc") + vars <- dataset1 & var1 + vars2 <- var1 & dataset1 + expect_equal(vars, vars2) + expect_equal(vars$datasets$names, "ABC2") + expect_equal(vars$variables$names, "abc") + expect_error(vars & 1) +}) + +test_that("datsets & values work", { + dataset1 <- datasets("ABC2") + val1 <- values("abc") + vars <- dataset1 & val1 + vars2 <- val1 & dataset1 + expect_equal(vars, vars2) + expect_equal(vars$datasets$names, "ABC2") + expect_equal(vars$values$names, "abc") + expect_error(vars & 1) +}) + +test_that("variables & values work", { + var1 <- variables("ABC2") + val1 <- values("abc") + vars <- var1 & val1 + vars2 <- val1 & var1 + expect_equal(vars, vars2) + expect_equal(vars$variables$names, "ABC2") + expect_equal(vars$values$names, "abc") + expect_error(vars & 1) +}) + +test_that("datasets & variables & values work", { + dataset1 <- datasets("ABC2") + var1 <- variables("ABC2") + val1 <- values("abc") + vars <- dataset1 & var1 & val1 + vars2 <- val1 & var1 & dataset1 + expect_equal(vars, vars2) + expect_equal(vars$datasets$names, "ABC2") + expect_equal(vars$variables$names, "ABC2") + expect_equal(vars$values$names, "abc") + expect_error(vars & 1) +}) + + + +test_that("datasets", { + first <- function(x){ + if (length(x) > 0) { + false <- rep(FALSE, length.out = length(x)) + false[1] <- TRUE + return(false) + } + return(FALSE) + } + + dataset1 <- datasets("df", first) + expect_true(is(dataset1$datasets$names, "vector")) + dataset2 <- datasets(is.matrix, first) + expect_true(is(dataset2$datasets$names, "vector")) + dataset3 <- datasets(is.data.frame, first) + mix <- dataset1 & dataset2 + expect_true(is(mix$datasets$names, "vector")) +}) + +test_that("variables", { + var1 <- variables("a", first) + var2 <- variables(is.factor, first) + var3 <- variables(is.factor, function(x){head(x, 1)}) + var4 <- variables(is.matrix, function(x){head(x, 1)}) }) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R new file mode 100644 index 00000000..7dfea733 --- /dev/null +++ b/tests/testthat/test-resolver.R @@ -0,0 +1,64 @@ +test_that("resolver datasets works", { + f <- function(x){head(x, 1)} + first <- function(x){ + if (length(x) > 0) { + false <- rep(FALSE, length.out = length(x)) + false[1] <- TRUE + return(false) + } + return(FALSE) + } + + dataset1 <- datasets("df", f) + dataset2 <- datasets("df", first) + dataset3 <- datasets(is.matrix, first) + dataset4 <- datasets("df", mean) + dataset5 <- datasets(median, mean) + td <- within(teal.data::teal_data(), { + df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) + m <- cbind(b = 1:5, c = 10:14) + m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) + }) + expect_no_error(resolver(dataset1, td)) + resolver(dataset2, td) + out <- resolver(dataset3, td) + expect_length(out$datasets$select, 1L) # Because we use first + expect_no_error(resolver(dataset4, td)) + expect_error(resolver(dataset5, td)) +}) + +test_that("resolver variables works", { + first <- function(x){ + if (length(x) > 0) { + false <- rep(FALSE, length.out = length(x)) + false[1] <- TRUE + return(false) + } + return(FALSE) + } + + dataset1 <- datasets("df", first) + dataset2 <- datasets(is.matrix, first) + dataset3 <- datasets(is.data.frame, first) + var1 <- variables("a", first) + var2 <- variables(is.factor, first) + var3 <- variables(is.factor, function(x){head(x, 1)}) + var4 <- variables(is.matrix, function(x){head(x, 1)}) + td <- within(teal.data::teal_data(), { + df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) + m <- cbind(b = 1:5, c = 10:14) + m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) + }) + + resolver(dataset1 & var1, td) + resolver(dataset1 & var2, td) + expect_error(resolver(dataset1 & var3, td)) + + resolver(dataset2 & var1, td) + resolver(dataset2 & var2, td) + resolver(dataset2 & var3, td) + + resolver(dataset3 & var1, td) + resolver(dataset3 & var2, td) + resolver(dataset3 & var3, td) +}) From 9f9aacb7358a5d5f1adbe100bfd92c82432bb353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 5 Mar 2025 10:38:11 +0100 Subject: [PATCH 003/110] Resolution tested up to variables --- NAMESPACE | 10 +++-- R/resolver.R | 66 ++++++++++++++--------------- R/types.R | 31 ++++++++++---- tests/testthat/test-delayed.R | 5 ++- tests/testthat/test-ops_transform.R | 36 +++------------- tests/testthat/test-resolver.R | 46 +++++++++----------- tests/testthat/test-types.R | 39 +++++++++++++++++ 7 files changed, 128 insertions(+), 105 deletions(-) create mode 100644 tests/testthat/test-types.R diff --git a/NAMESPACE b/NAMESPACE index 72e9fbd7..2eaaa365 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,11 @@ S3method("[[","type<-") S3method("[[",type) S3method("|",dataset) S3method(c,type) +S3method(data,MultiAssayExperiment) +S3method(data,data.frame) +S3method(data,default) +S3method(data,matrix) +S3method(data,qenv) S3method(data_extract_multiple_srv,FilteredData) S3method(data_extract_multiple_srv,list) S3method(data_extract_multiple_srv,reactive) @@ -49,6 +54,7 @@ export(check_no_multiple_selection) export(choices_labeled) export(choices_selected) export(compose_and_enable_validators) +export(data) export(data_extract_multiple_srv) export(data_extract_spec) export(data_extract_srv) @@ -84,10 +90,6 @@ export(value_choices) export(values) export(variable_choices) export(variables) -export(variables.MultiAssayExperiment) -export(variables.data.frame) -export(variables.default) -export(variables.matrix) import(shiny) importFrom(dplyr,"%>%") importFrom(lifecycle,badge) diff --git a/R/resolver.R b/R/resolver.R index 0645206f..ab63f4d7 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -49,16 +49,6 @@ resolver <- function(spec, data, ...) { } functions_names <- function(unresolved, reference) { - - if (length(unresolved) == 1 && is.function(unresolved)) { - out <- tryCatch(unresolved(reference), error = function(x) unresolved) - if (is.logical(out) && length(out) == length(reference)) { - return(reference[out]) - } else { - return(NULL) - } - } - is_fc <- vapply(unresolved, is.function, logical(1L)) fc_unresolved <- unresolved[is_fc] x <- vector("character") @@ -74,20 +64,26 @@ functions_names <- function(unresolved, reference) { } functions_data <- function(unresolved, data) { - if (length(unresolved) == 1 && is.function(unresolved)){ - out <- tryCatch(vapply(data, unresolved, logical(1L)), error = function(x) unresolved) - if (is.logical(out) && length(out) == length(data)) { - return(names(data)[out]) - } else { - return(NULL) - } - } - fc_unresolved <- unresolved[vapply(unresolved, is.function, logical(1L))] # This is for variables names <- names(data) - l <- lapply(fc_unresolved, function(f) {names[which(f(data))]}) + datasets <- names(data) + l <- lapply(fc_unresolved, function(f) { + v <- vapply(datasets, function(d) { + # Extract the data and apply the user supplied function + out <- f(data(data, d)) + if (!is.logical(out)) { + stop("Provided functions should return a logical object.") + } + if (length(out) > 1L) { + # Function resolution is unconventional... + return(FALSE) + } + out + }, logical(1L)) + datasets[v] + }) unique(unlist(l, FALSE, FALSE)) } @@ -123,13 +119,13 @@ resolver.datasets <- function(spec, data) { if (length(sdatasets$names) == 0) { stop("No selected datasets matching the conditions requested") } else if (length(sdatasets$names) == 1) { - svariables$select <- sdatasets$names - } else { - new_select <- c(functions_names(sdatasets$select, sdatasets$names), - functions_data(sdatasets$select, data[sdatasets$names])) - - sdatasets$select <- unique(new_select[!is.na(new_select)]) + sdatasets$select <- sdatasets$names } + + new_select <- c(functions_names(sdatasets$select, sdatasets$names), + functions_data(sdatasets$select, data[sdatasets$names])) + + sdatasets$select <- unique(new_select[!is.na(new_select)]) } spec$datasets <- resolved(sdatasets, "dataset") @@ -146,8 +142,7 @@ resolver.variables <- function(spec, data) { } datasets <- spec$datasets$select data_selected <- data(data, datasets) - dataset <- data_selected[[datasets]] - names_data <- names(dataset) + names_data <- names(data_selected) svariables <- spec$variables @@ -162,19 +157,20 @@ resolver.variables <- function(spec, data) { svariables$select <- match } else { new_select <- c(functions_names(svariables$select, svariables$names), - functions_data(svariables$select, dataset)) + functions_data(svariables$select, data_selected)) svariables$select <- unique(new_select[!is.na(new_select)]) } } else if (is.delayed(svariables)) { new_names <- c(functions_names(svariables$names, names_data), - functions_data(svariables$names, dataset)) + functions_data(svariables$names, data_selected)) svariables$names <- unique(new_names[!is.na(new_names)]) - if (length(match) == 1) { + # browser() + if (length(svariables$names) == 1) { svariables$select <- svariables$names } else { - new_select <- c(functions_names(svariables$select, svariables$names), - functions_data(svariables$select, dataset)) - svariables$select <- unique(new_select[!is.na(new_select)]) + new_select <- c(functions_names(svariables$select, svariables$names), + functions_data(svariables$select, data_selected)) + svariables$select <- unique(new_select[!is.na(new_select)]) } } spec$variables <- resolved(svariables, "variables") @@ -229,7 +225,7 @@ data.data.frame <- function(x, variable) { #' @export data.qenv <- function(x, variable) { - x[variable] + x[[variable]] } #' @export diff --git a/R/types.R b/R/types.R index d488dab2..a5fdc0ba 100644 --- a/R/types.R +++ b/R/types.R @@ -30,7 +30,12 @@ na_type <- function() { datasets <- function(x, select = first_choice) { stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) - + if (is.function(x)) { + x <- list(x) + } + if (is.function(select)) { + select <- list(select) + } type <- list(names = x, select = select) class(type) <- c("delayed", "datasets", "type", "list") o <- list(datasets = type, variables = na_type(), values = na_type()) @@ -43,9 +48,14 @@ datasets <- function(x, select = first_choice) { variables <- function(x, select = first_choice) { stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) - + if (is.function(x)) { + x <- list(x) + } + if (is.function(select)) { + select <- list(select) + } type <- list(names = x, select = select) - class(type) <- c("delayed", "variables", "type") + class(type) <- c("delayed", "variables", "type", "list") o <- list(datasets = na_type(), variables = type, values = na_type()) class(o) <- c("delayed", "transform") o @@ -55,9 +65,14 @@ variables <- function(x, select = first_choice) { values <- function(x, select = first_choice) { stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) - + if (is.function(x)) { + x <- list(x) + } + if (is.function(select)) { + select <- list(select) + } type <- list(names = x, select = select) - class(type) <- c("delayed", "values", "type") + class(type) <- c("delayed", "values", "type", "list") o <- list(datasets = na_type(), variables = na_type(), values = type) class(o) <- c("delayed", "transform") o @@ -68,12 +83,12 @@ c.type <- function(...) { c1 <- class(..1) c2 <- class(..2) classes <- unique(c(c1, c2)) - other_classes <- setdiff(classes, c("delayed", "type")) + other_classes <- setdiff(classes, c("delayed", "type", "list")) if ("delayed" %in% classes) { - classes <- c("delayed", other_classes, "type") + classes <- c("delayed", other_classes, "type", "list") } else { - classes <- c(other_classes, "type") + classes <- c(other_classes, "type", "list") } out <- NextMethod("c") diff --git a/tests/testthat/test-delayed.R b/tests/testthat/test-delayed.R index b9408a34..99bd1889 100644 --- a/tests/testthat/test-delayed.R +++ b/tests/testthat/test-delayed.R @@ -1,5 +1,5 @@ test_that("delay works", { - out <- 1 + out <- list(names = character(), select = character()) dout <- delay(out) expect_s3_class(dout, "delayed") expect_true(is.delayed(dout)) @@ -9,8 +9,9 @@ test_that("delay works", { test_that("is.delayed works", { d <- datasets("a") v <- variables("b") + da <- datasets("a", "a") expect_true(is.delayed(d)) - expect_true(is.delayed(datasets("a", "a"))) + expect_true(is.delayed(da)) expect_true(is.delayed(v)) expect_true(is.delayed(variables("b", "b"))) expect_true(is.delayed(d & v)) diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R index 847c0b55..c8c3ff36 100644 --- a/tests/testthat/test-ops_transform.R +++ b/tests/testthat/test-ops_transform.R @@ -1,12 +1,15 @@ basic_ops <- function(fun) { FUN <- match.fun(fun) type1 <- FUN("ABC") - type2 <- FUN("ABC2") types <- type1 & type1 out <- list(names = "ABC", select = list(first_choice)) - class(out) <- c("delayed", fun, "type") + class(out) <- c("delayed", fun, "type", "list") expect_equal(types[[fun]], out) + type2 <- FUN("ABC2") types <- type1 & type2 + out <- list(names = c("ABC", "ABC2"), select = list(first_choice)) + class(out) <- c("delayed", fun, "type", "list") + expect_equal(types[[fun]], out) expect_equal(types[[fun]]$names, c("ABC", "ABC2")) types2 <- types & type2 expect_equal(types[[fun]]$names, c("ABC", "ABC2")) @@ -20,6 +23,7 @@ basic_ops <- function(fun) { expect_length(out[[fun]]$names, 2) expect_error(FUN("ABC") & 1) out <- type1 & type2b + expect_true(is(out[[fun]]$names, "vector")) } test_that("datasets & work", { @@ -80,31 +84,3 @@ test_that("datasets & variables & values work", { expect_equal(vars$values$names, "abc") expect_error(vars & 1) }) - - - -test_that("datasets", { - first <- function(x){ - if (length(x) > 0) { - false <- rep(FALSE, length.out = length(x)) - false[1] <- TRUE - return(false) - } - return(FALSE) - } - - dataset1 <- datasets("df", first) - expect_true(is(dataset1$datasets$names, "vector")) - dataset2 <- datasets(is.matrix, first) - expect_true(is(dataset2$datasets$names, "vector")) - dataset3 <- datasets(is.data.frame, first) - mix <- dataset1 & dataset2 - expect_true(is(mix$datasets$names, "vector")) -}) - -test_that("variables", { - var1 <- variables("a", first) - var2 <- variables(is.factor, first) - var3 <- variables(is.factor, function(x){head(x, 1)}) - var4 <- variables(is.matrix, function(x){head(x, 1)}) -}) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 7dfea733..cba32daa 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -1,14 +1,14 @@ -test_that("resolver datasets works", { - f <- function(x){head(x, 1)} - first <- function(x){ - if (length(x) > 0) { - false <- rep(FALSE, length.out = length(x)) - false[1] <- TRUE - return(false) - } - return(FALSE) +f <- function(x){head(x, 1)} +first <- function(x){ + if (length(x) > 0) { + false <- rep(FALSE, length.out = length(x)) + false[1] <- TRUE + return(false) } + return(FALSE) +} +test_that("resolver datasets works", { dataset1 <- datasets("df", f) dataset2 <- datasets("df", first) dataset3 <- datasets(is.matrix, first) @@ -20,7 +20,7 @@ test_that("resolver datasets works", { m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) expect_no_error(resolver(dataset1, td)) - resolver(dataset2, td) + expect_no_error(resolver(dataset2, td)) out <- resolver(dataset3, td) expect_length(out$datasets$select, 1L) # Because we use first expect_no_error(resolver(dataset4, td)) @@ -28,15 +28,6 @@ test_that("resolver datasets works", { }) test_that("resolver variables works", { - first <- function(x){ - if (length(x) > 0) { - false <- rep(FALSE, length.out = length(x)) - false[1] <- TRUE - return(false) - } - return(FALSE) - } - dataset1 <- datasets("df", first) dataset2 <- datasets(is.matrix, first) dataset3 <- datasets(is.data.frame, first) @@ -50,15 +41,18 @@ test_that("resolver variables works", { m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) - resolver(dataset1 & var1, td) + expect_no_error(resolver(dataset1 & var1, td)) resolver(dataset1 & var2, td) expect_error(resolver(dataset1 & var3, td)) + expect_error(resolver(dataset1 & var4, td)) - resolver(dataset2 & var1, td) - resolver(dataset2 & var2, td) - resolver(dataset2 & var3, td) + expect_error(resolver(dataset2 & var1, td)) + expect_no_error(resolver(dataset2 & var2, td)) + expect_error(resolver(dataset2 & var3, td)) + expect_error(resolver(dataset2 & var4, td)) - resolver(dataset3 & var1, td) - resolver(dataset3 & var2, td) - resolver(dataset3 & var3, td) + expect_no_error(resolver(dataset3 & var1, td)) + expect_no_error(resolver(dataset3 & var2, td)) + expect_error(resolver(dataset3 & var3, td)) + expect_error(resolver(dataset3 & var4, td)) }) diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R new file mode 100644 index 00000000..28e74775 --- /dev/null +++ b/tests/testthat/test-types.R @@ -0,0 +1,39 @@ +first <- function(x){ + if (length(x) > 0) { + false <- rep(FALSE, length.out = length(x)) + false[1] <- TRUE + return(false) + } + return(FALSE) +} + +test_that("datasets", { + + expect_no_error(dataset0 <- datasets("df", "df")) + out <- list(names = "df", select = "df") + class(out) <- c("delayed", "datasets", "type", "list") + expect_equal(dataset0[["datasets"]], out) + expect_no_error(dataset1 <- datasets("df", first)) + expect_true(is.vector(dataset1$datasets$names)) + expect_no_error(dataset2 <- datasets(is.matrix, first)) + expect_true(is.vector(dataset2$datasets$names)) + expect_no_error(dataset3 <- datasets(is.data.frame, first)) +}) + +test_that("variables", { + expect_no_error(var0 <- variables("a", "a")) + expect_no_error(var1 <- variables("a", first)) + expect_no_error(var2 <- variables(is.factor, first)) + expect_no_error(var3 <- variables(is.factor, function(x){head(x, 1)})) + expect_no_error(var4 <- variables(is.matrix, function(x){head(x, 1)})) + +}) + +test_that("values", { + expect_no_error(val0 <- values("a", "a")) + expect_no_error(val1 <- values("a", first)) + expect_no_error(val2 <- values(is.factor, first)) + expect_no_error(val3 <- values(is.factor, function(x){head(x, 1)})) + expect_no_error(val4 <- values(is.matrix, function(x){head(x, 1)})) + +}) From 0d89dce2feb0ca2963eeb41a1bbabfa44f5ca300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 6 Mar 2025 14:15:32 +0100 Subject: [PATCH 004/110] Add function for when a new selection is chosen --- NAMESPACE | 1 + R/delayed.R | 1 + R/resolver.R | 110 ++++++++++++++++++++++++----- R/types.R | 37 ++++++++-- man/update_spec.Rd | 21 ++++++ tests/testthat/test-resolver.R | 122 +++++++++++++++++++++++---------- 6 files changed, 232 insertions(+), 60 deletions(-) create mode 100644 man/update_spec.Rd diff --git a/NAMESPACE b/NAMESPACE index 2eaaa365..07bb8964 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -86,6 +86,7 @@ export(select_spec) export(select_spec.default) export(select_spec.delayed_data) export(split_by_sep) +export(update_spec) export(value_choices) export(values) export(variable_choices) diff --git a/R/delayed.R b/R/delayed.R index f67c7e2c..13858f4f 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -6,6 +6,7 @@ delay <- function(x) { #' @export #' @method is.delayed type is.delayed.type <- function(x) { + !all(is.character(x$names)) || !all(is.character(x$select)) } diff --git a/R/resolver.R b/R/resolver.R index ab63f4d7..8b4e5cb6 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -26,9 +26,9 @@ resolver <- function(spec, data, ...) { } stopifnot(is.transform(spec), has_dataset(spec)) specf <- spec - if (has_dataset(specf)) { + if (has_dataset(specf) && is.delayed(specf$datasets)) { specf <- resolver.datasets(specf, data) - } else { + } else if (!has_dataset(specf)) { specf$datasets <- NULL } @@ -63,16 +63,21 @@ functions_names <- function(unresolved, reference) { unique(unlist(c(unresolved[!is_fc], x), FALSE, FALSE)) } -functions_data <- function(unresolved, data) { +functions_data <- function(unresolved, data, names_data) { fc_unresolved <- unresolved[vapply(unresolved, is.function, logical(1L))] # This is for variables names <- names(data) + # browser(expr = is.matrix(data)) datasets <- names(data) + # Matrix doesn't have a names method + if (is.null(datasets)) { + datasets <- colnames(data) + } l <- lapply(fc_unresolved, function(f) { v <- vapply(datasets, function(d) { # Extract the data and apply the user supplied function - out <- f(data(data, d)) + out <- tryCatch(f(data(data, d)), error = function(x){FALSE}) if (!is.logical(out)) { stop("Provided functions should return a logical object.") } @@ -91,10 +96,13 @@ resolver.datasets <- function(spec, data) { if (!is(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } - + if (is.null(spec[["datasets"]])) { + return(spec) + } sdatasets <- spec$datasets data_names <- names(data) - + orig_names <- sdatasets$names + orig_select <- sdatasets$select if (is.delayed(sdatasets) && all(is.character(sdatasets$names))) { match <- intersect(data_names, sdatasets$names) missing <- setdiff(sdatasets$names, data_names) @@ -112,6 +120,7 @@ resolver.datasets <- function(spec, data) { sdatasets$select <- unique(new_select[!is.na(new_select)]) } } else if (is.delayed(sdatasets)) { + old_names <- sdatasets$names new_names <- c(functions_names(sdatasets$names, data_names), functions_data(sdatasets$names, data)) sdatasets$names <- unique(new_names[!is.na(new_names)]) @@ -127,7 +136,8 @@ resolver.datasets <- function(spec, data) { sdatasets$select <- unique(new_select[!is.na(new_select)]) } - + attr(sdatasets$names, "original") <- attr(orig_names, "original") + attr(sdatasets$select, "original") <- attr(orig_select, "original") spec$datasets <- resolved(sdatasets, "dataset") spec } @@ -140,12 +150,20 @@ resolver.variables <- function(spec, data) { if (is.delayed(spec$datasets)) { stop("Datasets not resolved yet") } + if (is.null(spec[["variables"]])) { + return(spec) + } datasets <- spec$datasets$select data_selected <- data(data, datasets) - names_data <- names(data_selected) + if (is.null(names(data_selected))) { + names_data <- colnames(data_selected) + } else { + names_data <- names(data_selected) + } svariables <- spec$variables - + orig_names <- svariables$names + orig_select <- svariables$select if (is.delayed(svariables) && all(is.character(svariables$names))) { match <- intersect(names_data, svariables$names) missing <- setdiff(svariables$names, names_data) @@ -173,8 +191,12 @@ resolver.variables <- function(spec, data) { svariables$select <- unique(new_select[!is.na(new_select)]) } } + + attr(svariables$names, "original") <- attr(orig_names, "original") + attr(svariables$select, "original") <- attr(orig_select, "original") spec$variables <- resolved(svariables, "variables") spec + } resolver.values <- function(spec, data) { @@ -182,23 +204,42 @@ resolver.values <- function(spec, data) { stop("Please use qenv() or teal_data() objects.") } - variables <- spec$variables$names + if (is.null(spec[["values"]])) { + return(spec) + } + svalues <- spec$values - spec$variables <- if (is.delayed(svalues) && all(is.character(svalues$names))) { + orig_names <- svalues$names + orig_select <- svalues$select + spec$values <- if (is.delayed(svalues) && all(is.character(svalues$names))) { match <- intersect(datasets, svalues$names) missing <- setdiff(svalues$names, datasets) if (length(missing)) { stop("Missing values ", paste(sQuote(missing), collapse = ", "), " were specified.") } svalues$names <- match - svalues$select <- functions_names(svalues$select, match) - svalues + if (length(match) == 1) { + svalues$select <- match + } else { + new_select <- c(functions_names(svalues$select, svalues$names), + functions_data(svalues$select, data_selected)) + svalues$select <- unique(new_select[!is.na(new_select)]) + } } else if (is.delayed(svalues)) { - svalues$names <- functions_names(svalues$names, datasets) - svalues$select <- functions_names(svalues$select, svalues$names) - svalues + new_names <- c(functions_names(svalues$names, names_data), + functions_data(svalues$names, data_selected)) + svalues$names <- unique(new_names[!is.na(new_names)]) + # browser() + if (length(svalues$names) == 1) { + svalues$select <- svalues$names + } else { + new_select <- c(functions_names(svalues$select, svalues$names), + functions_data(svalues$select, data_selected)) + svalues$select <- unique(new_select[!is.na(new_select)]) + } } - + attr(svalues$names, "original") <- attr(orig_names, "original") + attr(svalues$select, "original") <- attr(orig_select, "original") spec$values <- resolved(svalues, "values") spec } @@ -237,3 +278,38 @@ data.default <- function(x, variable) { data <- function(x, variable) { UseMethod("data") } + +#' Update a spec +#' +#' Once a selection is made update the specification +#' @param spec A specification +#' @param type Which type was updated? +#' @param value What is the new selection? +#' @return The specification with restored choices and selection if caused by the update. +#' @export +update_spec <- function(spec, type, value) { + w <- c("datasets", "variables", "values") + type <- match.arg(type, w) + restart_types <- w[seq_along(w) > which(type == w)] + if (value %in% spec[[type]]$names) { + original_select <- attr(spec[[type]]$select, "original") + spec[[type]][["select"]] <- value + attr(spec[[type]][["select"]], "original") <- original_select + } + + # Restart to the original specs + for (type in restart_types) { + + # If the spec doesn't exist then there is nothing else to update + if (is.null(spec[[type]]) || !length(spec[[type]])) { + spec[[type]] <- na_type() + return(spec) + } + fun <- match.fun(type) + restored_type <- fun(x = attr(spec[[type]]$names, "original"), + select = attr(spec[[type]]$select, "original")) + spec[[type]] <- na_type() + spec <- spec & restored_type + } + spec +} diff --git a/R/types.R b/R/types.R index a5fdc0ba..f9b956c9 100644 --- a/R/types.R +++ b/R/types.R @@ -26,8 +26,17 @@ na_type <- function() { out } +first <- function(x){ + if (length(x) > 0) { + false <- rep(FALSE, length.out = length(x)) + false[1] <- TRUE + return(false) + } + return(FALSE) +} + #' @export -datasets <- function(x, select = first_choice) { +datasets <- function(x, select = first) { stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) if (is.function(x)) { @@ -38,6 +47,8 @@ datasets <- function(x, select = first_choice) { } type <- list(names = x, select = select) class(type) <- c("delayed", "datasets", "type", "list") + attr(type$names, "original") <- x + attr(type$select, "original") <- select o <- list(datasets = type, variables = na_type(), values = na_type()) class(o) <- c("delayed", "transform", "list") o @@ -45,7 +56,7 @@ datasets <- function(x, select = first_choice) { #' @export -variables <- function(x, select = first_choice) { +variables <- function(x, select = first) { stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) if (is.function(x)) { @@ -56,13 +67,15 @@ variables <- function(x, select = first_choice) { } type <- list(names = x, select = select) class(type) <- c("delayed", "variables", "type", "list") + attr(type$names, "original") <- x + attr(type$select, "original") <- select o <- list(datasets = na_type(), variables = type, values = na_type()) class(o) <- c("delayed", "transform") o } #' @export -values <- function(x, select = first_choice) { +values <- function(x, select = first) { stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) if (is.function(x)) { @@ -73,6 +86,8 @@ values <- function(x, select = first_choice) { } type <- list(names = x, select = select) class(type) <- c("delayed", "values", "type", "list") + attr(type$names, "original") <- x + attr(type$select, "original") <- select o <- list(datasets = na_type(), variables = na_type(), values = type) class(o) <- c("delayed", "transform") o @@ -82,6 +97,13 @@ values <- function(x, select = first_choice) { c.type <- function(...) { c1 <- class(..1) c2 <- class(..2) + + if (is.null(..1)) { + return(..2) + } else if (is.null(..2)) { + return(..1) + } + classes <- unique(c(c1, c2)) other_classes <- setdiff(classes, c("delayed", "type", "list")) @@ -102,11 +124,14 @@ c.type <- function(...) { names <- nam == "names" selects <- nam == "select" - out <- list(names = unlist(out[names], FALSE, FALSE), + new_l <- list(names = unlist(out[names], FALSE, FALSE), select = unlist(out[selects], FALSE, FALSE)) - l <- lapply(out, unique) + l <- lapply(new_l, unique) class(l) <- classes + + attr(l$names, "original") <- unique(unlist(lapply(out[names], attr, "original"), TRUE, FALSE)) + attr(l$select, "original") <- unique(unlist(lapply(out[selects], attr, "original"), TRUE, FALSE)) l } @@ -142,7 +167,7 @@ c.type <- function(...) { `[[.type<-` <- function(x, i, value) { cx <- class(x) if (!"type" %in% class(value)) { - stop("Modifying the specification with invalid objects") + stop("Modifying the specification with invalid objects.") } out <- NextMethod("[") class(out) <- cx diff --git a/man/update_spec.Rd b/man/update_spec.Rd new file mode 100644 index 00000000..ce82a3db --- /dev/null +++ b/man/update_spec.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resolver.R +\name{update_spec} +\alias{update_spec} +\title{Update a spec} +\usage{ +update_spec(spec, type, value) +} +\arguments{ +\item{spec}{A specification} + +\item{type}{Which type was updated?} + +\item{value}{What is the new selection?} +} +\value{ +The specification with restored choices and selection if caused by the update. +} +\description{ +Once a selection is made update the specification +} diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index cba32daa..383d355c 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -1,58 +1,106 @@ f <- function(x){head(x, 1)} -first <- function(x){ - if (length(x) > 0) { - false <- rep(FALSE, length.out = length(x)) - false[1] <- TRUE - return(false) - } - return(FALSE) -} test_that("resolver datasets works", { - dataset1 <- datasets("df", f) - dataset2 <- datasets("df", first) - dataset3 <- datasets(is.matrix, first) - dataset4 <- datasets("df", mean) - dataset5 <- datasets(median, mean) + df_head <- datasets("df", f) + df_first <- datasets("df") + matrices <- datasets(is.matrix) + df_mean <- datasets("df", mean) + median_mean <- datasets(median, mean) td <- within(teal.data::teal_data(), { df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) m <- cbind(b = 1:5, c = 10:14) m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) - expect_no_error(resolver(dataset1, td)) - expect_no_error(resolver(dataset2, td)) - out <- resolver(dataset3, td) + expect_no_error(resolver(df_head, td)) + expect_no_error(resolver(df_first, td)) + out <- resolver(matrices, td) expect_length(out$datasets$select, 1L) # Because we use first - expect_no_error(resolver(dataset4, td)) - expect_error(resolver(dataset5, td)) + expect_no_error(resolver(df_mean, td)) + expect_error(resolver(median_mean, td)) }) test_that("resolver variables works", { - dataset1 <- datasets("df", first) - dataset2 <- datasets(is.matrix, first) - dataset3 <- datasets(is.data.frame, first) - var1 <- variables("a", first) - var2 <- variables(is.factor, first) - var3 <- variables(is.factor, function(x){head(x, 1)}) - var4 <- variables(is.matrix, function(x){head(x, 1)}) + df <- datasets("df") + matrices <- datasets(is.matrix) + data_frames <- datasets(is.data.frame) + var_a <- variables("a") + factors <- variables(is.factor) + factors_head <- variables(is.factor, function(x){head(x, 1)}) + var_matrices_head <- variables(is.matrix, function(x){head(x, 1)}) td <- within(teal.data::teal_data(), { df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) m <- cbind(b = 1:5, c = 10:14) m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) - expect_no_error(resolver(dataset1 & var1, td)) - resolver(dataset1 & var2, td) - expect_error(resolver(dataset1 & var3, td)) - expect_error(resolver(dataset1 & var4, td)) + expect_no_error(resolver(df & var_a, td)) + expect_no_error(resolver(df & factors, td)) + expect_error(resolver(df & factors_head, td)) + expect_error(resolver(df & var_matrices_head, td)) - expect_error(resolver(dataset2 & var1, td)) - expect_no_error(resolver(dataset2 & var2, td)) - expect_error(resolver(dataset2 & var3, td)) - expect_error(resolver(dataset2 & var4, td)) + expect_error(resolver(matrices & var_a, td)) + expect_error(resolver(matrices & factors, td)) + expect_error(resolver(matrices & factors_head, td)) + expect_error(resolver(matrices & var_matrices_head, td)) - expect_no_error(resolver(dataset3 & var1, td)) - expect_no_error(resolver(dataset3 & var2, td)) - expect_error(resolver(dataset3 & var3, td)) - expect_error(resolver(dataset3 & var4, td)) + expect_no_error(resolver(data_frames & var_a, td)) + expect_no_error(resolver(data_frames & factors, td)) + expect_error(resolver(data_frames & factors_head, td)) + expect_error(resolver(data_frames & var_matrices_head, td)) }) + +test_that("names and variables are reported", { + td <- within(teal.data::teal_data(), { + df <- data.frame(A = as.factor(letters[1:5]), + Ab = LETTERS[1:5], + Abc = c(LETTERS[1:4], letters[1])) + m <- matrix() + }) + df_upper_variables <- datasets("df") & variables(function(x){x==toupper(x)}) + out <- resolver(df_upper_variables, td) + # This should select both A because the name is all capital letters and Ab values is all upper case. + expect_length(out$variables$names, 2) + df_all_upper_variables <- datasets("df") & variables(function(x){all(x==toupper(x))}) + out <- resolver(df_all_upper_variables, td) + expect_length(out$variables$names, 2) +}) + + +test_that("update_spec resolves correctly", { + td <- within(teal.data::teal_data(), { + df <- data.frame(A = as.factor(letters[1:5]), + Ab = LETTERS[1:5]) + df_n <- data.frame(C = 1:5, + Ab = as.factor(letters[1:5])) + }) + data_frames_factors <- datasets(is.data.frame) & variables(is.factor) + expect_false(is.null(attr(data_frames_factors$datasets$names, "original"))) + expect_false(is.null(attr(data_frames_factors$datasets$select, "original"))) + expect_false(is.null(attr(data_frames_factors$variables$names, "original"))) + expect_false(is.null(attr(data_frames_factors$variables$select, "original"))) + + res <- resolver(data_frames_factors, td) + expect_false(is.null(attr(res$datasets$names, "original"))) + expect_false(is.null(attr(res$datasets$select, "original"))) + expect_false(is.null(attr(res$variables$names, "original"))) + expect_false(is.null(attr(res$variables$select, "original"))) + + res2 <- update_spec(res, "datasets", "df_n") + expect_false(is.null(attr(res2$datasets$names, "original"))) + expect_false(is.null(attr(res2$datasets$select, "original"))) + expect_false(is.null(attr(res2$variables$names, "original"))) + expect_false(is.null(attr(res2$variables$select, "original"))) + + expect_no_error(res3 <- resolver(res2, td)) + expect_false(is.null(attr(res3$datasets$names, "original"))) + expect_false(is.null(attr(res3$datasets$select, "original"))) + expect_equal(attr(res3$datasets$names, "original"), attr(data_frames_factors$datasets$names, "original")) + expect_equal(attr(res3$datasets$select, "original"), attr(data_frames_factors$datasets$select, "original")) + expect_equal(res3$datasets$select, "df_n", check.attributes = FALSE) + expect_equal(res3$variables$select, "Ab", check.attributes = FALSE) + expect_false(is.null(attr(res3$variables$names, "original"))) + expect_false(is.null(attr(res3$variables$select, "original"))) + expect_equal(attr(res3$variables$names, "original"), attr(data_frames_factors$variables$names, "original")) + expect_equal(attr(res3$variables$select, "original"), attr(data_frames_factors$variables$select, "original")) +}) + From 58182d2d56df2470a668efba17dfabf26deaebf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 7 Mar 2025 11:28:37 +0100 Subject: [PATCH 005/110] Raising more errors and silencing some tests --- R/resolver.R | 58 +++++++++++++++++++++++------ tests/testthat/test-delayed.R | 5 ++- tests/testthat/test-ops_transform.R | 30 +++++++-------- tests/testthat/test-resolver.R | 13 ++++--- tests/testthat/test-types.R | 6 +-- 5 files changed, 75 insertions(+), 37 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index 8b4e5cb6..99cb5f3a 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -68,7 +68,6 @@ functions_data <- function(unresolved, data, names_data) { # This is for variables names <- names(data) - # browser(expr = is.matrix(data)) datasets <- names(data) # Matrix doesn't have a names method if (is.null(datasets)) { @@ -117,13 +116,21 @@ resolver.datasets <- function(spec, data) { } else { new_select <- c(functions_names(sdatasets$select, sdatasets$names), functions_data(sdatasets$select, data[sdatasets$names])) - sdatasets$select <- unique(new_select[!is.na(new_select)]) + new_select <- unique(new_select[!is.na(new_select)]) + if (!length(new_select)) { + stop("No datasets meet the requirements to be selected") + } + sdatasets$select <- new_select } } else if (is.delayed(sdatasets)) { old_names <- sdatasets$names new_names <- c(functions_names(sdatasets$names, data_names), functions_data(sdatasets$names, data)) - sdatasets$names <- unique(new_names[!is.na(new_names)]) + new_names <- unique(new_names[!is.na(new_names)]) + if (!length(new_names)) { + stop("No datasets meet the requirements") + } + sdatasets$names <- new_names if (length(sdatasets$names) == 0) { stop("No selected datasets matching the conditions requested") @@ -134,7 +141,11 @@ resolver.datasets <- function(spec, data) { new_select <- c(functions_names(sdatasets$select, sdatasets$names), functions_data(sdatasets$select, data[sdatasets$names])) - sdatasets$select <- unique(new_select[!is.na(new_select)]) + new_select <- unique(new_select[!is.na(new_select)]) + if (!length(new_select)) { + stop("No datasets meet the requirements to be selected") + } + sdatasets$select <- new_select } attr(sdatasets$names, "original") <- attr(orig_names, "original") attr(sdatasets$select, "original") <- attr(orig_select, "original") @@ -176,19 +187,30 @@ resolver.variables <- function(spec, data) { } else { new_select <- c(functions_names(svariables$select, svariables$names), functions_data(svariables$select, data_selected)) - svariables$select <- unique(new_select[!is.na(new_select)]) + new_select <- unique(new_select[!is.na(new_select)]) + if (!length(new_select)) { + stop("No variables meet the requirements to be selected") + } + svariables$select <- new_select } } else if (is.delayed(svariables)) { new_names <- c(functions_names(svariables$names, names_data), functions_data(svariables$names, data_selected)) - svariables$names <- unique(new_names[!is.na(new_names)]) - # browser() + new_names <- unique(new_names[!is.na(new_names)]) + if (!length(new_names)) { + stop("No variables meet the requirements") + } + svariables$names <- new_names if (length(svariables$names) == 1) { svariables$select <- svariables$names } else { new_select <- c(functions_names(svariables$select, svariables$names), functions_data(svariables$select, data_selected)) - svariables$select <- unique(new_select[!is.na(new_select)]) + new_select <- unique(new_select[!is.na(new_select)]) + if (!length(new_select)) { + stop("No variables meet the requirements to be selected") + } + svariables$select <- new_select } } @@ -223,19 +245,31 @@ resolver.values <- function(spec, data) { } else { new_select <- c(functions_names(svalues$select, svalues$names), functions_data(svalues$select, data_selected)) - svalues$select <- unique(new_select[!is.na(new_select)]) + new_select <- unique(new_select[!is.na(new_select)]) + if (!length(new_select)) { + stop("No variables meet the requirements to be selected") + } + svalues$select <- new_select } } else if (is.delayed(svalues)) { new_names <- c(functions_names(svalues$names, names_data), functions_data(svalues$names, data_selected)) - svalues$names <- unique(new_names[!is.na(new_names)]) - # browser() + new_names <- unique(new_names[!is.na(new_names)]) + if (!length(new_names)) { + stop("No variables meet the requirements") + } + svalues$names <- new_names + if (length(svalues$names) == 1) { svalues$select <- svalues$names } else { new_select <- c(functions_names(svalues$select, svalues$names), functions_data(svalues$select, data_selected)) - svalues$select <- unique(new_select[!is.na(new_select)]) + new_select <- unique(new_select[!is.na(new_select)]) + if (!length(new_select)) { + stop("No variables meet the requirements to be selected") + } + svalues$select <- new_select } } attr(svalues$names, "original") <- attr(orig_names, "original") diff --git a/tests/testthat/test-delayed.R b/tests/testthat/test-delayed.R index 99bd1889..01a89170 100644 --- a/tests/testthat/test-delayed.R +++ b/tests/testthat/test-delayed.R @@ -11,8 +11,9 @@ test_that("is.delayed works", { v <- variables("b") da <- datasets("a", "a") expect_true(is.delayed(d)) - expect_true(is.delayed(da)) - expect_true(is.delayed(v)) + # expect_true(is.delayed(da)) + # expect_true(is.delayed(v)) expect_true(is.delayed(variables("b", "b"))) expect_true(is.delayed(d & v)) + expect_false(is.delayed(1)) }) diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R index c8c3ff36..defeb9be 100644 --- a/tests/testthat/test-ops_transform.R +++ b/tests/testthat/test-ops_transform.R @@ -2,17 +2,17 @@ basic_ops <- function(fun) { FUN <- match.fun(fun) type1 <- FUN("ABC") types <- type1 & type1 - out <- list(names = "ABC", select = list(first_choice)) + out <- list(names = "ABC", select = list(first)) class(out) <- c("delayed", fun, "type", "list") - expect_equal(types[[fun]], out) + expect_equal(types[[fun]], out, check.attributes = FALSE) type2 <- FUN("ABC2") types <- type1 & type2 - out <- list(names = c("ABC", "ABC2"), select = list(first_choice)) + out <- list(names = c("ABC", "ABC2"), select = list(first)) class(out) <- c("delayed", fun, "type", "list") - expect_equal(types[[fun]], out) - expect_equal(types[[fun]]$names, c("ABC", "ABC2")) + expect_equal(types[[fun]], out, check.attributes = FALSE) + expect_equal(types[[fun]]$names, c("ABC", "ABC2"), check.attributes = FALSE) types2 <- types & type2 - expect_equal(types[[fun]]$names, c("ABC", "ABC2")) + expect_equal(types[[fun]]$names, c("ABC", "ABC2"), check.attributes = FALSE) expect_s3_class(types[[fun]], class(out)) type3 <- FUN("ABC2", select = all_choices) types <- type1 & type3 @@ -45,8 +45,8 @@ test_that("datsets & variables work", { vars <- dataset1 & var1 vars2 <- var1 & dataset1 expect_equal(vars, vars2) - expect_equal(vars$datasets$names, "ABC2") - expect_equal(vars$variables$names, "abc") + expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) + expect_equal(vars$variables$names, "abc", check.attributes = FALSE) expect_error(vars & 1) }) @@ -56,8 +56,8 @@ test_that("datsets & values work", { vars <- dataset1 & val1 vars2 <- val1 & dataset1 expect_equal(vars, vars2) - expect_equal(vars$datasets$names, "ABC2") - expect_equal(vars$values$names, "abc") + expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) + expect_equal(vars$values$names, "abc", check.attributes = FALSE) expect_error(vars & 1) }) @@ -67,8 +67,8 @@ test_that("variables & values work", { vars <- var1 & val1 vars2 <- val1 & var1 expect_equal(vars, vars2) - expect_equal(vars$variables$names, "ABC2") - expect_equal(vars$values$names, "abc") + expect_equal(vars$variables$names, "ABC2", check.attributes = FALSE) + expect_equal(vars$values$names, "abc", check.attributes = FALSE) expect_error(vars & 1) }) @@ -79,8 +79,8 @@ test_that("datasets & variables & values work", { vars <- dataset1 & var1 & val1 vars2 <- val1 & var1 & dataset1 expect_equal(vars, vars2) - expect_equal(vars$datasets$names, "ABC2") - expect_equal(vars$variables$names, "ABC2") - expect_equal(vars$values$names, "abc") + expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) + expect_equal(vars$variables$names, "ABC2", check.attributes = FALSE) + expect_equal(vars$values$names, "abc", check.attributes = FALSE) expect_error(vars & 1) }) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 383d355c..1db50b1f 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -38,7 +38,8 @@ test_that("resolver variables works", { expect_error(resolver(df & factors_head, td)) expect_error(resolver(df & var_matrices_head, td)) - expect_error(resolver(matrices & var_a, td)) + + expect_error(resolver(matrices & var_a, td)) # datasets selection overpasses variable choices. expect_error(resolver(matrices & factors, td)) expect_error(resolver(matrices & factors_head, td)) expect_error(resolver(matrices & var_matrices_head, td)) @@ -58,11 +59,13 @@ test_that("names and variables are reported", { }) df_upper_variables <- datasets("df") & variables(function(x){x==toupper(x)}) out <- resolver(df_upper_variables, td) - # This should select both A because the name is all capital letters and Ab values is all upper case. - expect_length(out$variables$names, 2) + # This should select A and Ab: + # A because the name is all capital letters and + # Ab values is all upper case. + # expect_length(out$variables$names, 2) df_all_upper_variables <- datasets("df") & variables(function(x){all(x==toupper(x))}) - out <- resolver(df_all_upper_variables, td) - expect_length(out$variables$names, 2) + expect_no_error(out <- resolver(df_all_upper_variables, td)) + # expect_length(out$variables$names, 2) }) diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index 28e74775..f9702f2d 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -12,11 +12,11 @@ test_that("datasets", { expect_no_error(dataset0 <- datasets("df", "df")) out <- list(names = "df", select = "df") class(out) <- c("delayed", "datasets", "type", "list") - expect_equal(dataset0[["datasets"]], out) + expect_equal(dataset0[["datasets"]], out, check.attributes = FALSE) expect_no_error(dataset1 <- datasets("df", first)) - expect_true(is.vector(dataset1$datasets$names)) + # expect_true(is.vector(dataset1$datasets$names)) expect_no_error(dataset2 <- datasets(is.matrix, first)) - expect_true(is.vector(dataset2$datasets$names)) + # expect_true(is.vector(dataset2$datasets$names)) expect_no_error(dataset3 <- datasets(is.data.frame, first)) }) From c21591f41da743e43fd41f4e5bc0631f197c54bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 10 Mar 2025 17:30:01 +0100 Subject: [PATCH 006/110] Improve update_spec --- R/resolver.R | 35 ++++++++++++++++++++++++++--------- R/types.R | 4 ++-- man/update_spec.Rd | 22 +++++++++++++++++----- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index 99cb5f3a..de2cc614 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -313,24 +313,37 @@ data <- function(x, variable) { UseMethod("data") } -#' Update a spec +#' Update a specification #' -#' Once a selection is made update the specification -#' @param spec A specification -#' @param type Which type was updated? -#' @param value What is the new selection? +#' Once a selection is made update the specification for different valid selection. +#' @param spec A specification such as one created with datasets and variables. +#' @param type Which type was updated? One of datasets, variables, values. +#' @param value What is the new selection? One that is a valid value for the given type and specification. #' @return The specification with restored choices and selection if caused by the update. #' @export +#' @examples +#' td <- within(teal.data::teal_data(), { +#' df <- data.frame(A = as.factor(letters[1:5]), +#' Ab = LETTERS[1:5]) +#' df_n <- data.frame(C = 1:5, +#' Ab = as.factor(letters[1:5])) +#' }) +#' data_frames_factors <- datasets(is.data.frame) & variables(is.factor) +#' res <- resolver(data_frames_factors, td) +#' update_spec(res, "datasets", "df_n") +#' # update_spec(res, "datasets", "error") update_spec <- function(spec, type, value) { w <- c("datasets", "variables", "values") type <- match.arg(type, w) restart_types <- w[seq_along(w) > which(type == w)] - if (value %in% spec[[type]]$names) { + + if (is.delayed(spec[[type]])) { + stop(type, " has not been resolved yet.\n", "Please resolve the specification before trying to apply ") + } else if (all(value %in% spec[[type]]$names)) { original_select <- attr(spec[[type]]$select, "original") spec[[type]][["select"]] <- value attr(spec[[type]][["select"]], "original") <- original_select } - # Restart to the original specs for (type in restart_types) { @@ -340,8 +353,12 @@ update_spec <- function(spec, type, value) { return(spec) } fun <- match.fun(type) - restored_type <- fun(x = attr(spec[[type]]$names, "original"), - select = attr(spec[[type]]$select, "original")) + if (!length(spec[[type]]) && is.na(spec[[type]])) { + restored_type <- fun(x = na_type(), select = na_type()) + } else { + restored_type <- fun(x = attr(spec[[type]]$names, "original"), + select = attr(spec[[type]]$select, "original")) + } spec[[type]] <- na_type() spec <- spec & restored_type } diff --git a/R/types.R b/R/types.R index f9b956c9..bcd53830 100644 --- a/R/types.R +++ b/R/types.R @@ -21,8 +21,8 @@ has_value <- function(x) { } na_type <- function() { - out <- NA - class(out) <- "type" + out <- NA_character_ + class(out) <- c("type", class(out)) out } diff --git a/man/update_spec.Rd b/man/update_spec.Rd index ce82a3db..c820ee90 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -2,20 +2,32 @@ % Please edit documentation in R/resolver.R \name{update_spec} \alias{update_spec} -\title{Update a spec} +\title{Update a specification} \usage{ update_spec(spec, type, value) } \arguments{ -\item{spec}{A specification} +\item{spec}{A specification such as one created with datasets and variables.} -\item{type}{Which type was updated?} +\item{type}{Which type was updated? One of datasets, variables, values.} -\item{value}{What is the new selection?} +\item{value}{What is the new selection? One that is a valid value for the given type and specification.} } \value{ The specification with restored choices and selection if caused by the update. } \description{ -Once a selection is made update the specification +Once a selection is made update the specification for different valid selection. +} +\examples{ +td <- within(teal.data::teal_data(), { + df <- data.frame(A = as.factor(letters[1:5]), + Ab = LETTERS[1:5]) + df_n <- data.frame(C = 1:5, + Ab = as.factor(letters[1:5])) +}) +data_frames_factors <- datasets(is.data.frame) & variables(is.factor) +res <- resolver(data_frames_factors, td) +update_spec(res, "datasets", "df_n") +# update_spec(res, "datasets", "error") } From 8023b1c3c23f06323c56dbb48868093ed9f56075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 10 Mar 2025 17:30:57 +0100 Subject: [PATCH 007/110] Simplify tests --- tests/testthat/test-resolver.R | 4 ++++ tests/testthat/test-types.R | 31 +++++++++++-------------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 1db50b1f..c07d8a30 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -105,5 +105,9 @@ test_that("update_spec resolves correctly", { expect_false(is.null(attr(res3$variables$select, "original"))) expect_equal(attr(res3$variables$names, "original"), attr(data_frames_factors$variables$names, "original")) expect_equal(attr(res3$variables$select, "original"), attr(data_frames_factors$variables$select, "original")) + + expect_error(update_spec(res, "datasets", "error")) + expect_error(update_spec(data_frames_factors, "datasets", "error")) + expect_no_error(update_spec(datasets(x = c("df", "df2")), "datasets", "df2")) }) diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index f9702f2d..2984cb85 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -1,39 +1,30 @@ -first <- function(x){ - if (length(x) > 0) { - false <- rep(FALSE, length.out = length(x)) - false[1] <- TRUE - return(false) - } - return(FALSE) -} - test_that("datasets", { expect_no_error(dataset0 <- datasets("df", "df")) out <- list(names = "df", select = "df") class(out) <- c("delayed", "datasets", "type", "list") expect_equal(dataset0[["datasets"]], out, check.attributes = FALSE) - expect_no_error(dataset1 <- datasets("df", first)) - # expect_true(is.vector(dataset1$datasets$names)) - expect_no_error(dataset2 <- datasets(is.matrix, first)) - # expect_true(is.vector(dataset2$datasets$names)) - expect_no_error(dataset3 <- datasets(is.data.frame, first)) + expect_no_error(dataset1 <- datasets("df")) + expect_true(is(dataset1$datasets$names, "vector")) + expect_no_error(dataset2 <- datasets(is.matrix)) + expect_true(is(dataset2$datasets$names, "vector")) + expect_no_error(dataset3 <- datasets(is.data.frame)) }) test_that("variables", { expect_no_error(var0 <- variables("a", "a")) - expect_no_error(var1 <- variables("a", first)) - expect_no_error(var2 <- variables(is.factor, first)) + expect_no_error(var1 <- variables("a")) + expect_no_error(var2 <- variables(is.factor)) + # Allowed to specify whatever we like, it is not until resolution that this raises errors expect_no_error(var3 <- variables(is.factor, function(x){head(x, 1)})) expect_no_error(var4 <- variables(is.matrix, function(x){head(x, 1)})) - }) test_that("values", { expect_no_error(val0 <- values("a", "a")) - expect_no_error(val1 <- values("a", first)) - expect_no_error(val2 <- values(is.factor, first)) + expect_no_error(val1 <- values("a")) + expect_no_error(val2 <- values(is.factor)) + # Allowed to specify whatever we like, it is not until resolution that this raises errors expect_no_error(val3 <- values(is.factor, function(x){head(x, 1)})) expect_no_error(val4 <- values(is.matrix, function(x){head(x, 1)})) - }) From 37225be8fecb95d75c18fe20e515fc75f902bd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 10 Mar 2025 17:43:56 +0100 Subject: [PATCH 008/110] Add print method --- NAMESPACE | 1 + R/resolver.R | 6 +++--- R/types.R | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 07bb8964..8f3d020d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -33,6 +33,7 @@ S3method(print,delayed_select_spec) S3method(print,delayed_value_choices) S3method(print,delayed_variable_choices) S3method(print,filter_spec) +S3method(print,type) S3method(resolve,default) S3method(resolve,delayed_choices_selected) S3method(resolve,delayed_data_extract_spec) diff --git a/R/resolver.R b/R/resolver.R index de2cc614..9f2456f1 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -337,12 +337,12 @@ update_spec <- function(spec, type, value) { type <- match.arg(type, w) restart_types <- w[seq_along(w) > which(type == w)] - if (is.delayed(spec[[type]])) { - stop(type, " has not been resolved yet.\n", "Please resolve the specification before trying to apply ") - } else if (all(value %in% spec[[type]]$names)) { + if (all(value %in% spec[[type]]$names)) { original_select <- attr(spec[[type]]$select, "original") spec[[type]][["select"]] <- value attr(spec[[type]][["select"]], "original") <- original_select + } else if (is.list(spec[[type]]$names) && !any(vapply(spec[[type]]$names, is.function, logical(1L)))) { + stop("value not in possible choices.") } # Restart to the original specs for (type in restart_types) { diff --git a/R/types.R b/R/types.R index bcd53830..c5fb9d17 100644 --- a/R/types.R +++ b/R/types.R @@ -173,3 +173,33 @@ c.type <- function(...) { class(out) <- cx out } + +#' @export +print.type <- function(x, ...) { + is_na <- length(x) == 1L && is.na(x) + if (is_na) { + cat("Nothing possible") + return(x) + } + + nam_list <- is.list(x$names) + nam_functions <- sum(is.function(x$names)) + nam_values <- length(x$names) - nam_functions + if (nam_functions) { + cat(nam_functions, "functions to select possible choices.\n") + } + if (nam_values) { + cat(x$names[is.character(x$names)], "as possible choices.\n") + } + + sel_list <- is.list(x$select) + sel_functions <- sum(is.function(x$select)) + sel_values <- length(x$select) - sel_functions + if (sel_functions) { + cat(sel_functions, "functions to select.\n") + } + if (sel_values) { + cat(x$select[is.character(x$select)], "selected.\n") + } + return(x) +} From e1ec4204c1f25a17745e5c5d20da95f966aa367f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 10 Mar 2025 17:44:17 +0100 Subject: [PATCH 009/110] Uncomment some tests --- tests/testthat/test-delayed.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-delayed.R b/tests/testthat/test-delayed.R index 01a89170..3b8c3bfa 100644 --- a/tests/testthat/test-delayed.R +++ b/tests/testthat/test-delayed.R @@ -11,8 +11,8 @@ test_that("is.delayed works", { v <- variables("b") da <- datasets("a", "a") expect_true(is.delayed(d)) - # expect_true(is.delayed(da)) - # expect_true(is.delayed(v)) + expect_true(is.delayed(da)) + expect_true(is.delayed(v)) expect_true(is.delayed(variables("b", "b"))) expect_true(is.delayed(d & v)) expect_false(is.delayed(1)) From a8bb22633316d90dceae2ebe631b791286d4e1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 10 Mar 2025 17:52:11 +0100 Subject: [PATCH 010/110] Make print work --- R/types.R | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/R/types.R b/R/types.R index c5fb9d17..64a920ba 100644 --- a/R/types.R +++ b/R/types.R @@ -183,23 +183,33 @@ print.type <- function(x, ...) { } nam_list <- is.list(x$names) - nam_functions <- sum(is.function(x$names)) + if (nam_list) { + nam_functions <- vapply(x$names, is.function, logical(1L)) + } else { + nam_functions <- FALSE + } + nam_values <- length(x$names) - nam_functions if (nam_functions) { - cat(nam_functions, "functions to select possible choices.\n") + cat(sum(nam_functions), "functions to select possible choices.\n") } if (nam_values) { - cat(x$names[is.character(x$names)], "as possible choices.\n") + cat(x$names[!nam_functions], "as possible choices.\n") } sel_list <- is.list(x$select) - sel_functions <- sum(is.function(x$select)) + if (sel_list) { + sel_functions <- vapply(x$select, is.function, logical(1L)) + } else { + sel_functions <- FALSE + } + sel_values <- length(x$select) - sel_functions - if (sel_functions) { - cat(sel_functions, "functions to select.\n") + if (any(sel_functions)) { + cat(sum(sel_functions), "functions to select.\n") } if (sel_values) { - cat(x$select[is.character(x$select)], "selected.\n") + cat(x$select[!sel_functions], "selected.\n") } return(x) } From f4bf89aca764c74290c828016406d57d9475ba83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 11:31:54 +0100 Subject: [PATCH 011/110] Move delayed to attributes --- R/delayed.R | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/R/delayed.R b/R/delayed.R index 13858f4f..419cb6ac 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -1,5 +1,8 @@ +# Only delay if the type or object really needs it and is not already delayed delay <- function(x) { - class(x) <- c("delayed", class(x)) + if (is.delayed(x)) { + attr(x, "delayed") <- TRUE + } x } @@ -7,10 +10,14 @@ delay <- function(x) { #' @method is.delayed type is.delayed.type <- function(x) { - !all(is.character(x$names)) || !all(is.character(x$select)) + na <- length(x) == 1L && is.na(x) + if (!na) { + return(!all(is.character(x$names)) || !all(is.character(x$select))) + } + FALSE } -#' @export is.delayed +#' @export #' @method is.delayed transform is.delayed.transform <- function(x) { is.delayed(x$datasets) || is.delayed(x$variables) || is.delayed(x$values) @@ -19,7 +26,7 @@ is.delayed.transform <- function(x) { #' @export #' @method is.delayed default is.delayed.default <- function(x) { - inherits(x, "delayed") + FALSE } #' @export @@ -33,13 +40,11 @@ resolved <- function(x, variable){ if (!s && !all(x$select %in% x$names)) { stop("Selected ", variable, " not available") } - - cl <- class(x) - class(x) <- setdiff(cl, "delayed") + attr(x, "delayed") <- NULL x } -get_datanames <- function(x) { +get_datasets <- function(x) { if (is.transform(x) && !is.delayed(x$datasets)) { x$datasets$names } else { From a1678fa120217caf1e7007f74ba24b993dab17cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 11:32:26 +0100 Subject: [PATCH 012/110] Add missing method! --- NAMESPACE | 1 + 1 file changed, 1 insertion(+) diff --git a/NAMESPACE b/NAMESPACE index 8f3d020d..138f6123 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,6 +20,7 @@ S3method(data_extract_srv,list) S3method(filter_spec_internal,default) S3method(filter_spec_internal,delayed_data) S3method(is.delayed,default) +S3method(is.delayed,transform) S3method(is.delayed,type) S3method(merge_expression_module,list) S3method(merge_expression_module,reactive) From 06c0eb995d7d30df811e907bdccd1290b22be9e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 11:41:25 +0100 Subject: [PATCH 013/110] Simplify type creation --- R/types.R | 82 ++++++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/R/types.R b/R/types.R index 64a920ba..5f665967 100644 --- a/R/types.R +++ b/R/types.R @@ -1,7 +1,9 @@ transform <- function() { - o <- list(datasets = na_type(), variables = na_type(), values = na_type()) - class(o) <- c("delayed", "transform") - o + o <- list(datasets = na_type("datasets"), + variables = na_type("variables"), + values = na_type("values")) + class(o) <- c("transform", "list") + delay(o) } is.transform <- function(x) { @@ -20,9 +22,9 @@ has_value <- function(x) { !anyNA(x[["values"]]) } -na_type <- function() { +na_type <- function(type) { out <- NA_character_ - class(out) <- c("type", class(out)) + class(out) <- c(type, "type") out } @@ -35,64 +37,56 @@ first <- function(x){ return(FALSE) } -#' @export -datasets <- function(x, select = first) { - stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) - stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) +check_input <- function(input) { + is.character(input) || is.function(input) || + (is.list(input) && all(vapply(input, is.function, logical(1L)))) +} + +type_helper <- function(x, select, type) { + stopifnot("Invalid options" = check_input(x), + "Invalid selection" = check_input(type)) if (is.function(x)) { x <- list(x) } if (is.function(select)) { select <- list(select) } - type <- list(names = x, select = select) - class(type) <- c("delayed", "datasets", "type", "list") - attr(type$names, "original") <- x - attr(type$select, "original") <- select - o <- list(datasets = type, variables = na_type(), values = na_type()) - class(o) <- c("delayed", "transform", "list") + out <- list(names = x, select = select) + class(out) <- c(type, "type", "list") + attr(out$names, "original") <- x + attr(out$select, "original") <- select + delay(out) +} + +#' @export +datasets <- function(x, select = first) { + o <- transform() + o$datasets <- type_helper(x, select, type = "datasets") o } #' @export variables <- function(x, select = first) { - stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) - stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) - if (is.function(x)) { - x <- list(x) - } - if (is.function(select)) { - select <- list(select) - } - type <- list(names = x, select = select) - class(type) <- c("delayed", "variables", "type", "list") - attr(type$names, "original") <- x - attr(type$select, "original") <- select - o <- list(datasets = na_type(), variables = type, values = na_type()) - class(o) <- c("delayed", "transform") + o <- transform() + o$variables <- type_helper(x, select, type = "variables") o } #' @export values <- function(x, select = first) { - stopifnot(is.character(x) || is.function(x) || (is.list(x) && all(vapply(x, is.function, logical(1L))))) - stopifnot(is.character(select) || is.function(select) || (is.list(select) && all(vapply(select, is.function, logical(1L))))) - if (is.function(x)) { - x <- list(x) - } - if (is.function(select)) { - select <- list(select) - } - type <- list(names = x, select = select) - class(type) <- c("delayed", "values", "type", "list") - attr(type$names, "original") <- x - attr(type$select, "original") <- select - o <- list(datasets = na_type(), variables = na_type(), values = type) - class(o) <- c("delayed", "transform") + o <- transform() + o$values <- type_helper(x, select, type = "values") o } +#' @export +c.transform <- function(...) { + transf <- mapply(c, ...) + class(transf) <- c("transform", "list") + delay(transf) +} + #' @export c.type <- function(...) { c1 <- class(..1) @@ -191,7 +185,7 @@ print.type <- function(x, ...) { nam_values <- length(x$names) - nam_functions if (nam_functions) { - cat(sum(nam_functions), "functions to select possible choices.\n") + cat(sum(nam_functions), "functions for possible choices.\n") } if (nam_values) { cat(x$names[!nam_functions], "as possible choices.\n") From 208fb535f2b3e30fb663408f2d41d259ab9e50f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 11:41:53 +0100 Subject: [PATCH 014/110] Simplify resolver --- R/resolver.R | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index 9f2456f1..2ec05ccb 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -63,11 +63,10 @@ functions_names <- function(unresolved, reference) { unique(unlist(c(unresolved[!is_fc], x), FALSE, FALSE)) } -functions_data <- function(unresolved, data, names_data) { +functions_data <- function(unresolved, data) { fc_unresolved <- unresolved[vapply(unresolved, is.function, logical(1L))] # This is for variables - names <- names(data) datasets <- names(data) # Matrix doesn't have a names method if (is.null(datasets)) { @@ -80,11 +79,12 @@ functions_data <- function(unresolved, data, names_data) { if (!is.logical(out)) { stop("Provided functions should return a logical object.") } - if (length(out) > 1L) { - # Function resolution is unconventional... + if (length(out) != 1L && length(out) != length(data(data, d))) { + # Function resolution is unconventional, but this would produce too many warnings... + # warning("The output of the function must be of length 1 or the same length as the data.") return(FALSE) } - out + all(out) }, logical(1L)) datasets[v] }) @@ -338,10 +338,10 @@ update_spec <- function(spec, type, value) { restart_types <- w[seq_along(w) > which(type == w)] if (all(value %in% spec[[type]]$names)) { - original_select <- attr(spec[[type]]$select, "original") + original_select <- orig(spec[[type]]$select) spec[[type]][["select"]] <- value attr(spec[[type]][["select"]], "original") <- original_select - } else if (is.list(spec[[type]]$names) && !any(vapply(spec[[type]]$names, is.function, logical(1L)))) { + } else if (is.list(orig(spec[[type]]$names)) && !any(vapply(spec[[type]]$names, is.function, logical(1L)))) { stop("value not in possible choices.") } # Restart to the original specs @@ -349,18 +349,23 @@ update_spec <- function(spec, type, value) { # If the spec doesn't exist then there is nothing else to update if (is.null(spec[[type]]) || !length(spec[[type]])) { - spec[[type]] <- na_type() + spec[[type]] <- na_type(type) return(spec) } + fun <- match.fun(type) - if (!length(spec[[type]]) && is.na(spec[[type]])) { - restored_type <- fun(x = na_type(), select = na_type()) + if (length(spec[[type]]) == 1L && is.na(spec[[type]])) { + restored_type <- na_type(type) } else { - restored_type <- fun(x = attr(spec[[type]]$names, "original"), - select = attr(spec[[type]]$select, "original")) + restored_type <- fun(x = orig(spec[[type]]$names), + select = orig(spec[[type]]$select)) } - spec[[type]] <- na_type() + spec[[type]] <- na_type(type) spec <- spec & restored_type } spec } + +orig <- function(x) { + attr(x, "original") +} From cb94d6d34485d58473061bc75e0db2d6cf8b0090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 14:44:08 +0100 Subject: [PATCH 015/110] Delayed takes into consideration the selection too --- tests/testthat/test-delayed.R | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/testthat/test-delayed.R b/tests/testthat/test-delayed.R index 3b8c3bfa..7a4eb07e 100644 --- a/tests/testthat/test-delayed.R +++ b/tests/testthat/test-delayed.R @@ -1,19 +1,13 @@ -test_that("delay works", { - out <- list(names = character(), select = character()) - dout <- delay(out) - expect_s3_class(dout, "delayed") - expect_true(is.delayed(dout)) - expect_equal(resolved(dout), out) -}) - test_that("is.delayed works", { d <- datasets("a") v <- variables("b") - da <- datasets("a", "a") expect_true(is.delayed(d)) - expect_true(is.delayed(da)) + expect_false(is.delayed(datasets("a", "a"))) expect_true(is.delayed(v)) - expect_true(is.delayed(variables("b", "b"))) + expect_false(is.delayed(variables("b", "b"))) expect_true(is.delayed(d & v)) expect_false(is.delayed(1)) + da <- datasets(is.data.frame) + expect_true(is.delayed(da)) + expect_true(is.delayed(da$datasets)) }) From 158eda90fb7b6108fbd3ddfa4357db7a6e0a6998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 14:44:37 +0100 Subject: [PATCH 016/110] Fix mistake and remove delayed class --- tests/testthat/test-ops_transform.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R index defeb9be..dd80e642 100644 --- a/tests/testthat/test-ops_transform.R +++ b/tests/testthat/test-ops_transform.R @@ -3,7 +3,7 @@ basic_ops <- function(fun) { type1 <- FUN("ABC") types <- type1 & type1 out <- list(names = "ABC", select = list(first)) - class(out) <- c("delayed", fun, "type", "list") + class(out) <- c(fun, "type", "list") expect_equal(types[[fun]], out, check.attributes = FALSE) type2 <- FUN("ABC2") types <- type1 & type2 @@ -23,7 +23,7 @@ basic_ops <- function(fun) { expect_length(out[[fun]]$names, 2) expect_error(FUN("ABC") & 1) out <- type1 & type2b - expect_true(is(out[[fun]]$names, "vector")) + expect_true(is.list(out[[fun]]$names)) } test_that("datasets & work", { From d545331abe418e0948733beaba7dc97d1e171c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 14:45:11 +0100 Subject: [PATCH 017/110] Resolve better by names --- tests/testthat/test-resolver.R | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index c07d8a30..231a2e45 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -55,19 +55,29 @@ test_that("names and variables are reported", { df <- data.frame(A = as.factor(letters[1:5]), Ab = LETTERS[1:5], Abc = c(LETTERS[1:4], letters[1])) + df2 <- data.frame(A = 1:5, + B = 1:5) m <- matrix() }) - df_upper_variables <- datasets("df") & variables(function(x){x==toupper(x)}) + d_df <- datasets("df") + df_upper_variables <- d_df & variables(function(x){x==toupper(x)}) out <- resolver(df_upper_variables, td) # This should select A and Ab: # A because the name is all capital letters and # Ab values is all upper case. - # expect_length(out$variables$names, 2) - df_all_upper_variables <- datasets("df") & variables(function(x){all(x==toupper(x))}) + expect_length(out$variables$names, 2) + v_all_upper <- variables(function(x){all(x==toupper(x))}) + df_all_upper_variables <- d_df & v_all_upper expect_no_error(out <- resolver(df_all_upper_variables, td)) - # expect_length(out$variables$names, 2) -}) + expect_length(out$variables$names, 1) + expect_no_error(out <- resolver(datasets("df2") & v_all_upper, td)) + expect_length(out$variables$names, 2) + expect_no_error(out <- resolver(datasets(function(x){is.data.frame(x) && all(colnames(x) == toupper(colnames(x)))}), td)) + expect_length(out$datasets$names, 1) + expect_no_error(out <- resolver(datasets(is.data.frame) & datasets(function(x){colnames(x) == toupper(colnames(x))}), td)) + expect_length(out$datasets$names, 2) +}) test_that("update_spec resolves correctly", { td <- within(teal.data::teal_data(), { From 99cadc29296d87fa22cd2db91ab8cfd46bb48907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 15:27:46 +0100 Subject: [PATCH 018/110] Fix issues --- R/resolver.R | 73 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index 2ec05ccb..b76fb4da 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -20,8 +20,8 @@ #' resolver(spec, td) #' spec <- dataset1 & variables("a", is.factor) #' resolver(spec, td) -resolver <- function(spec, data, ...) { - if (!is(data, "qenv")) { +resolver <- function(spec, data) { + if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } stopifnot(is.transform(spec), has_dataset(spec)) @@ -29,23 +29,23 @@ resolver <- function(spec, data, ...) { if (has_dataset(specf) && is.delayed(specf$datasets)) { specf <- resolver.datasets(specf, data) } else if (!has_dataset(specf)) { - specf$datasets <- NULL + specf$datasets <- na_type("datasets") } if (has_variable(specf) && !is.delayed(specf$datasets)) { specf <- resolver.variables(specf, data) } else { - specf$variables <- NULL + specf$variables <- na_type("variables") } if (has_value(specf) && !is.delayed(specf$datasets) && !is.delayed(specf$variables)) { specf <- resolver.values(specf, data) } else { - specf$values <- NULL + specf$values <- na_type("values") } - class(specf) <- setdiff(class(specf), "delayed") - specf + attr(specf, "delayed") <- NULL + delay(specf) } functions_names <- function(unresolved, reference) { @@ -92,7 +92,7 @@ functions_data <- function(unresolved, data) { } resolver.datasets <- function(spec, data) { - if (!is(data, "qenv")) { + if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } if (is.null(spec[["datasets"]])) { @@ -154,7 +154,7 @@ resolver.datasets <- function(spec, data) { } resolver.variables <- function(spec, data) { - if (!is(data, "qenv")) { + if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } @@ -222,7 +222,7 @@ resolver.variables <- function(spec, data) { } resolver.values <- function(spec, data) { - if (!is(data, "qenv")) { + if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } @@ -280,8 +280,10 @@ resolver.values <- function(spec, data) { #' @export data.MultiAssayExperiment <- function(x, variable) { - # length(variable) == 1L - cd <- colData(x) + if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { + stop("Required to have MultiAssayExperiment's package.") + } + cd <- MultiAssayExperiment::colData(x) cd[[variable]] } @@ -316,7 +318,7 @@ data <- function(x, variable) { #' Update a specification #' #' Once a selection is made update the specification for different valid selection. -#' @param spec A specification such as one created with datasets and variables. +#' @param spec A resolved specification such as one created with datasets and variables. #' @param type Which type was updated? One of datasets, variables, values. #' @param value What is the new selection? One that is a valid value for the given type and specification. #' @return The specification with restored choices and selection if caused by the update. @@ -336,32 +338,41 @@ update_spec <- function(spec, type, value) { w <- c("datasets", "variables", "values") type <- match.arg(type, w) restart_types <- w[seq_along(w) > which(type == w)] - - if (all(value %in% spec[[type]]$names)) { + speci <- spec + if (!is.character(value)) { + stop("The updated value is not a character.", + "\nDo you attempt to set a new specification? Please open an issue") + } + valid_names <- spec[[type]]$names + if (is.delayed(spec[[type]])) { + stop(type, " should be resolved before updating.") + } + if (!is.list(valid_names) && all(value %in% valid_names)) { original_select <- orig(spec[[type]]$select) spec[[type]][["select"]] <- value attr(spec[[type]][["select"]], "original") <- original_select - } else if (is.list(orig(spec[[type]]$names)) && !any(vapply(spec[[type]]$names, is.function, logical(1L)))) { - stop("value not in possible choices.") + } else if (!is.list(valid_names) && !all(value %in% valid_names)) { + original_select <- orig(spec[[type]]$select) + valid_values <- intersect(value, valid_names) + if (!length(valid_values)) { + stop("No valid value provided.") + } + spec[[type]][["select"]] <- valid_values + attr(spec[[type]][["select"]], "original") <- original_select + } else { + stop("It seems the specification needs to be resolved first.") } - # Restart to the original specs - for (type in restart_types) { - # If the spec doesn't exist then there is nothing else to update - if (is.null(spec[[type]]) || !length(spec[[type]])) { - spec[[type]] <- na_type(type) - return(spec) - } + # Restore to the original specs + for (type in restart_types) { - fun <- match.fun(type) if (length(spec[[type]]) == 1L && is.na(spec[[type]])) { - restored_type <- na_type(type) - } else { - restored_type <- fun(x = orig(spec[[type]]$names), - select = orig(spec[[type]]$select)) + next } - spec[[type]] <- na_type(type) - spec <- spec & restored_type + fun <- match.fun(type) + restored_transform <- fun(x = orig(spec[[type]]$names), + select = orig(spec[[type]]$select)) + spec[[type]] <- restored_transform[[type]] } spec } From b26695a8cb72f89b246dec0124fc48793fe94bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 15:28:25 +0100 Subject: [PATCH 019/110] Simplify c.type --- R/types.R | 113 ++++++++++++++++++------------------------------------ 1 file changed, 38 insertions(+), 75 deletions(-) diff --git a/R/types.R b/R/types.R index 5f665967..295c3c1b 100644 --- a/R/types.R +++ b/R/types.R @@ -39,7 +39,7 @@ first <- function(x){ check_input <- function(input) { is.character(input) || is.function(input) || - (is.list(input) && all(vapply(input, is.function, logical(1L)))) + (is.list(input) && all(vapply(input, function(x){is.function(x) || is.character(x)}, logical(1L)))) } type_helper <- function(x, select, type) { @@ -82,90 +82,46 @@ values <- function(x, select = first) { #' @export c.transform <- function(...) { - transf <- mapply(c, ...) + if (...length() > 2) { + stop("More than two specifications won't be considered. Use & to combine them", call. = FALSE) + } + transf <- mapply(c, ..., SIMPLIFY = FALSE) class(transf) <- c("transform", "list") delay(transf) } #' @export c.type <- function(...) { - c1 <- class(..1) - c2 <- class(..2) - if (is.null(..1)) { + if (length(..1) == 1L && is.na(..1)) { return(..2) - } else if (is.null(..2)) { + } else if (length(..2) == 1L && is.na(..2)) { return(..1) } - classes <- unique(c(c1, c2)) - other_classes <- setdiff(classes, c("delayed", "type", "list")) - - if ("delayed" %in% classes) { - classes <- c("delayed", other_classes, "type", "list") - } else { - classes <- c(other_classes, "type", "list") + objects <- list(...) + classes <- unlist(lapply(objects, class), FALSE,FALSE) + type <- setdiff(classes, c("type", "list")) + if (length(type) > 1L) { + stop("Combining different types", call. = FALSE) } - out <- NextMethod("c") - - if (all(is.na(out))) { - return(na_type()) - } else if (anyNA(out)) { - out <- out[!is.na(out)] - } - nam <- names(out) - names <- nam == "names" - selects <- nam == "select" - - new_l <- list(names = unlist(out[names], FALSE, FALSE), - select = unlist(out[selects], FALSE, FALSE)) - - l <- lapply(new_l, unique) - class(l) <- classes - - attr(l$names, "original") <- unique(unlist(lapply(out[names], attr, "original"), TRUE, FALSE)) - attr(l$select, "original") <- unique(unlist(lapply(out[selects], attr, "original"), TRUE, FALSE)) - l -} - -#' @export -`[.type` <- function(x, i, j, ..., exact = TRUE) { - cx <- class(x) - out <- NextMethod("[") - class(out) <- cx - out + names <- lapply(objects, "[[", i = "names") + select <- lapply(objects, "[[", i = "select") + names_orig <- lapply(names, orig) + select_orig <- lapply(select, orig) + type_f <- match.fun(type) + type_out <- type_f(x = simplify_c(names_orig), + select = simplify_c(select_orig)) + attr(type_out[[type]][["names"]], "original") <- NULL + attr(type_out[[type]][["names"]], "original") <- simplify_c(names_orig) + attr(type_out[[type]][["select"]], "original") <- NULL + attr(type_out[[type]][["select"]], "original") <- simplify_c(select_orig) + delay(type_out[[type]]) } -#' @export -`[.type<-` <- function(x, i, j, ..., value) { - cx <- class(x) - if (!"type" %in% class(value)) { - stop("Modifying the specification with invalid objects") - } - out <- NextMethod("[") - class(out) <- cx - out -} - -#' @export -`[[.type` <- function(x, i, ..., drop = TRUE) { - cx <- class(x) - out <- NextMethod("[[") - class(out) <- cx - out -} - - -#' @export -`[[.type<-` <- function(x, i, value) { - cx <- class(x) - if (!"type" %in% class(value)) { - stop("Modifying the specification with invalid objects.") - } - out <- NextMethod("[") - class(out) <- cx - out +simplify_c <- function(x) { + unique(unlist(x, FALSE, FALSE)) } #' @export @@ -183,12 +139,15 @@ print.type <- function(x, ...) { nam_functions <- FALSE } + msg_values <- character() nam_values <- length(x$names) - nam_functions - if (nam_functions) { - cat(sum(nam_functions), "functions for possible choices.\n") + if (any(nam_functions)) { + msg_values <- paste0(msg_values, sum(nam_functions), " functions for possible choices.", + collapse = "\n") } if (nam_values) { - cat(x$names[!nam_functions], "as possible choices.\n") + msg_values <- paste0(msg_values, x$names[!nam_functions], " as possible choices.", + collapse = "\n") } sel_list <- is.list(x$select) @@ -198,12 +157,16 @@ print.type <- function(x, ...) { sel_functions <- FALSE } + msg_sel <- character() sel_values <- length(x$select) - sel_functions if (any(sel_functions)) { - cat(sum(sel_functions), "functions to select.\n") + msg_sel <- paste0(msg_sel, sum(sel_functions), " functions to select.", + collapse = "\n") } if (sel_values) { - cat(x$select[!sel_functions], "selected.\n") + msg_sel <- paste0(msg_sel, x$select[!sel_functions], "selected.", + collapse = "\n") } + cat(msg_values, msg_sel) return(x) } From 6600d179d896a8d880d504db0bfc1dcc74f9d41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 15:29:25 +0100 Subject: [PATCH 020/110] Use Group Generic Functions --- NAMESPACE | 9 +++---- R/ops_transform.R | 66 ++++++++++++++++++---------------------------- man/resolver.Rd | 2 +- man/update_spec.Rd | 2 +- 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 138f6123..759061e4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,11 +1,8 @@ # Generated by roxygen2: do not edit by hand -S3method("&",transform) -S3method("[","type<-") -S3method("[",type) -S3method("[[","type<-") -S3method("[[",type) -S3method("|",dataset) +S3method(Ops,transform) +S3method(Ops,type) +S3method(c,transform) S3method(c,type) S3method(data,MultiAssayExperiment) S3method(data,data.frame) diff --git a/R/ops_transform.R b/R/ops_transform.R index 5a97d3df..768b2e5f 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -1,47 +1,33 @@ #' @export -`&.transform` <- function(e1, e2) { - if (!is.transform(e1) || !is.transform(e2)) { - stop("Method not available") +Ops.transform <- function(e1, e2) { + if (missing(e2)) { + # out <- switch(.Generic, + # "!" = Negate, + stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) + # return(out) } - o <- transform() - if (has_dataset(e1) || has_dataset(e2)) { - o$datasets <- c(e1$datasets, e2$datasets) - o$datasets <- o$datasets[!is.na(o$datasets)] - } - if (has_variable(e1) || has_variable(e2)) { - o$variables <- c(e1$variables, e2$variables) - o$variables <- o$variables[!is.na(o$variables)] - } - if (has_value(e1) || has_value(e2)) { - o$values <- c(e1$values, e2$values) - o$values <- o$values[!is.na(o$values)] - } - - class(o) <- c("delayed", "transform") - o + switch(.Generic, + "!=" = NextMethod(), + # "==" = NextMethod(), + # "|" = , + "&" = c(e1, e2), + stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE)) } #' @export -`|.dataset` <- function(e1, e2) { - if (!is.transform(e1) || !is.transform(e2)) { - stop("Method not available") +Ops.type <- function(e1, e2) { + if (missing(e2)) { + # out <- switch(.Generic, + # "!" = Negate, + stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) + # return(out) } - s <- transform() - class(x) <- c("delayed", "transform") - x + out <- switch(.Generic, + "!=" = NextMethod(), + # "==" = NextMethod(), + # "|" = , + "&" = c(e1, e2), + stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE)) + class(out) <- class(e1) + out } - -# #' @export -# chooseOpsMethod.transform <- function(x, y, mx, my, cl, reverse) { -# # cat("\nx\n") -# # print(mx) -# # cat("\ny\n") -# # print(my) -# # cat("\ncl\n") -# # print(cl) -# # cat("\nreverse\n") -# # print(reverse) -# is.transform(x) -# } - -# ?Ops diff --git a/man/resolver.Rd b/man/resolver.Rd index 1063d0ef..3d39c39f 100644 --- a/man/resolver.Rd +++ b/man/resolver.Rd @@ -4,7 +4,7 @@ \alias{resolver} \title{Resolve the specification} \usage{ -resolver(spec, data, ...) +resolver(spec, data) } \arguments{ \item{spec}{A object extraction specification.} diff --git a/man/update_spec.Rd b/man/update_spec.Rd index c820ee90..a85cf2f9 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -7,7 +7,7 @@ update_spec(spec, type, value) } \arguments{ -\item{spec}{A specification such as one created with datasets and variables.} +\item{spec}{A resolved specification such as one created with datasets and variables.} \item{type}{Which type was updated? One of datasets, variables, values.} From df47b484e1c8abdbd647b5168b83120e4c9dbb32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 15:30:11 +0100 Subject: [PATCH 021/110] Improve tests --- tests/testthat/test-resolver.R | 3 ++- tests/testthat/test-types.R | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 231a2e45..4210ca1b 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -118,6 +118,7 @@ test_that("update_spec resolves correctly", { expect_error(update_spec(res, "datasets", "error")) expect_error(update_spec(data_frames_factors, "datasets", "error")) - expect_no_error(update_spec(datasets(x = c("df", "df2")), "datasets", "df2")) + expect_error(update_spec(datasets(x = c("df", "df2")), "datasets", "df2")) + expect_no_error(update_spec(datasets(x = c("df", "df2"), "df"), "datasets", "df2")) }) diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index 2984cb85..2b1ae404 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -20,6 +20,12 @@ test_that("variables", { expect_no_error(var4 <- variables(is.matrix, function(x){head(x, 1)})) }) +test_that("raw combine of types", { + out <- c(datasets("df"), variables("df")) + expect_length(out, 3) + expect_error(c(datasets("df"), variables("df"), values("df"))) +}) + test_that("values", { expect_no_error(val0 <- values("a", "a")) expect_no_error(val1 <- values("a")) From e90025c1b48fc1e05a7a2b64cdf080e49d8b0459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 12 Mar 2025 17:27:41 +0100 Subject: [PATCH 022/110] Make it work for simple cases of values --- R/resolver.R | 36 ++++++++++++++++++---------------- R/types.R | 2 +- tests/testthat/test-resolver.R | 17 ++++++++++++++++ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index b76fb4da..c6a4f118 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -95,7 +95,7 @@ resolver.datasets <- function(spec, data) { if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } - if (is.null(spec[["datasets"]])) { + if (is.null(spec[["datasets"]]) || all(is.na(spec[["datasets"]]))) { return(spec) } sdatasets <- spec$datasets @@ -147,8 +147,8 @@ resolver.datasets <- function(spec, data) { } sdatasets$select <- new_select } - attr(sdatasets$names, "original") <- attr(orig_names, "original") - attr(sdatasets$select, "original") <- attr(orig_select, "original") + attr(sdatasets$names, "original") <- orig(orig_names) + attr(sdatasets$select, "original") <- orig(orig_select) spec$datasets <- resolved(sdatasets, "dataset") spec } @@ -161,7 +161,7 @@ resolver.variables <- function(spec, data) { if (is.delayed(spec$datasets)) { stop("Datasets not resolved yet") } - if (is.null(spec[["variables"]])) { + if (is.null(spec[["variables"]]) || all(is.na(spec[["variables"]]))) { return(spec) } datasets <- spec$datasets$select @@ -214,8 +214,8 @@ resolver.variables <- function(spec, data) { } } - attr(svariables$names, "original") <- attr(orig_names, "original") - attr(svariables$select, "original") <- attr(orig_select, "original") + attr(svariables$names, "original") <- orig(orig_names) + attr(svariables$select, "original") <- orig(orig_select) spec$variables <- resolved(svariables, "variables") spec @@ -226,16 +226,17 @@ resolver.values <- function(spec, data) { stop("Please use qenv() or teal_data() objects.") } - if (is.null(spec[["values"]])) { + if (is.null(spec[["values"]]) || all(is.na(spec[["values"]]))) { return(spec) } - svalues <- spec$values + dataset <- data(data, spec$datasets$select) + variable <- data(dataset, spec$variables$select) orig_names <- svalues$names orig_select <- svalues$select spec$values <- if (is.delayed(svalues) && all(is.character(svalues$names))) { - match <- intersect(datasets, svalues$names) - missing <- setdiff(svalues$names, datasets) + match <- intersect(variable, svalues$names) + missing <- setdiff(svalues$names, variable) if (length(missing)) { stop("Missing values ", paste(sQuote(missing), collapse = ", "), " were specified.") } @@ -243,8 +244,9 @@ resolver.values <- function(spec, data) { if (length(match) == 1) { svalues$select <- match } else { + match <- intersect(variable, svalues$names) new_select <- c(functions_names(svalues$select, svalues$names), - functions_data(svalues$select, data_selected)) + functions_data(svalues$select, variable)) new_select <- unique(new_select[!is.na(new_select)]) if (!length(new_select)) { stop("No variables meet the requirements to be selected") @@ -252,8 +254,8 @@ resolver.values <- function(spec, data) { svalues$select <- new_select } } else if (is.delayed(svalues)) { - new_names <- c(functions_names(svalues$names, names_data), - functions_data(svalues$names, data_selected)) + new_names <- c(functions_names(svalues$names, variable), + functions_data(svalues$names, variable)) new_names <- unique(new_names[!is.na(new_names)]) if (!length(new_names)) { stop("No variables meet the requirements") @@ -263,8 +265,8 @@ resolver.values <- function(spec, data) { if (length(svalues$names) == 1) { svalues$select <- svalues$names } else { - new_select <- c(functions_names(svalues$select, svalues$names), - functions_data(svalues$select, data_selected)) + new_select <- c(functions_names(svalues$select, variable), + functions_data(svalues$select, variable)) new_select <- unique(new_select[!is.na(new_select)]) if (!length(new_select)) { stop("No variables meet the requirements to be selected") @@ -272,8 +274,8 @@ resolver.values <- function(spec, data) { svalues$select <- new_select } } - attr(svalues$names, "original") <- attr(orig_names, "original") - attr(svalues$select, "original") <- attr(orig_select, "original") + attr(svalues$names, "original") <- orig(orig_names) + attr(svalues$select, "original") <- orig(orig_select) spec$values <- resolved(svalues, "values") spec } diff --git a/R/types.R b/R/types.R index 295c3c1b..b4d84a07 100644 --- a/R/types.R +++ b/R/types.R @@ -164,7 +164,7 @@ print.type <- function(x, ...) { collapse = "\n") } if (sel_values) { - msg_sel <- paste0(msg_sel, x$select[!sel_functions], "selected.", + msg_sel <- paste0(msg_sel, x$select[!sel_functions], " selected.", collapse = "\n") } cat(msg_values, msg_sel) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 4210ca1b..67f8cf06 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -50,6 +50,23 @@ test_that("resolver variables works", { expect_error(resolver(data_frames & var_matrices_head, td)) }) +test_that("resolver values works", { + df <- datasets("df") + matrices <- datasets(is.matrix) + data_frames <- datasets(is.data.frame) + var_a <- variables("a") + factors <- variables(is.factor) + factors_head <- variables(is.factor, function(x){head(x, 1)}) + var_matrices_head <- variables(is.matrix, function(x){head(x, 1)}) + val_A <- values("A") + td <- within(teal.data::teal_data(), { + df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) + m <- cbind(b = 1:5, c = 10:14) + m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) + }) + expect_no_error(resolver(df & var_a & val_A, td)) +}) + test_that("names and variables are reported", { td <- within(teal.data::teal_data(), { df <- data.frame(A = as.factor(letters[1:5]), From a92f051854bfbc61e91b83b7c5dd07b09b15b6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 13 Mar 2025 13:59:59 +0100 Subject: [PATCH 023/110] Allow OR combinations --- R/ops_transform.R | 10 ++++++-- tests/testthat/test-ops_transform.R | 37 +++++++++++++++++++---------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/R/ops_transform.R b/R/ops_transform.R index 768b2e5f..6ff26a9b 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -8,8 +8,8 @@ Ops.transform <- function(e1, e2) { } switch(.Generic, "!=" = NextMethod(), - # "==" = NextMethod(), - # "|" = , + "==" = NextMethod(), + "|" = combine_transform(e1, e2), "&" = c(e1, e2), stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE)) } @@ -31,3 +31,9 @@ Ops.type <- function(e1, e2) { class(out) <- class(e1) out } + +combine_transform <- function(e1, e2) { + l <- list(e1, e2) + class(l) <- c("transform", "list") + l +} diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R index dd80e642..de9918a8 100644 --- a/tests/testthat/test-ops_transform.R +++ b/tests/testthat/test-ops_transform.R @@ -39,48 +39,61 @@ test_that("values & work", { basic_ops("values") }) -test_that("datsets & variables work", { +test_that("&(datsets, variables) create a single transform", { dataset1 <- datasets("ABC2") var1 <- variables("abc") vars <- dataset1 & var1 vars2 <- var1 & dataset1 - expect_equal(vars, vars2) expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) expect_equal(vars$variables$names, "abc", check.attributes = FALSE) - expect_error(vars & 1) +}) + +test_that("&(datsets, number) errors", { + expect_error(datasets("abc") & 1) }) test_that("datsets & values work", { dataset1 <- datasets("ABC2") val1 <- values("abc") vars <- dataset1 & val1 - vars2 <- val1 & dataset1 - expect_equal(vars, vars2) expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) expect_equal(vars$values$names, "abc", check.attributes = FALSE) - expect_error(vars & 1) +}) + +test_that("&(datsets, number) errors", { + expect_error(variables("abc") & 1) }) test_that("variables & values work", { var1 <- variables("ABC2") val1 <- values("abc") vars <- var1 & val1 - vars2 <- val1 & var1 - expect_equal(vars, vars2) expect_equal(vars$variables$names, "ABC2", check.attributes = FALSE) expect_equal(vars$values$names, "abc", check.attributes = FALSE) - expect_error(vars & 1) }) -test_that("datasets & variables & values work", { +test_that("&(values, number) errors", { + expect_error(values("abc") & 1) +}) + +test_that("datasets & variables & values create a single specification", { dataset1 <- datasets("ABC2") var1 <- variables("ABC2") val1 <- values("abc") vars <- dataset1 & var1 & val1 vars2 <- val1 & var1 & dataset1 - expect_equal(vars, vars2) expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) expect_equal(vars$variables$names, "ABC2", check.attributes = FALSE) expect_equal(vars$values$names, "abc", check.attributes = FALSE) - expect_error(vars & 1) +}) + +test_that("&(transform, number) errors", { + expect_error(datasets("ABC2") & variables("ABC2") & values("abc") & 1) +}) + + +test_that("| combines two transformers", { + spec <- datasets("ABC") | datasets("abc") + expect_length(spec, 2) + expect_error(spec[[1]]$datasets | spec[[1]]$datasets) }) From 41800f34fa9fe70607899513f77dcb8037f34b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 17 Mar 2025 16:58:07 +0100 Subject: [PATCH 024/110] Make it easier to work with types --- R/types.R | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/R/types.R b/R/types.R index b4d84a07..08e36b04 100644 --- a/R/types.R +++ b/R/types.R @@ -28,6 +28,17 @@ na_type <- function(type) { out } +#' @export +#' @method is.na type +is.na.type <- function(x) { + anyNA(unclass(x)) +} + +#' @export +anyNA.type <- function(x) { + anyNA(unclass(x)) +} + first <- function(x){ if (length(x) > 0) { false <- rep(FALSE, length.out = length(x)) @@ -93,9 +104,9 @@ c.transform <- function(...) { #' @export c.type <- function(...) { - if (length(..1) == 1L && is.na(..1)) { + if (is.na(..1)) { return(..2) - } else if (length(..2) == 1L && is.na(..2)) { + } else if (is.na(..2)) { return(..1) } @@ -126,8 +137,7 @@ simplify_c <- function(x) { #' @export print.type <- function(x, ...) { - is_na <- length(x) == 1L && is.na(x) - if (is_na) { + if (is.na(x)) { cat("Nothing possible") return(x) } @@ -140,14 +150,14 @@ print.type <- function(x, ...) { } msg_values <- character() - nam_values <- length(x$names) - nam_functions + nam_values <- length(x$names) - sum(nam_functions) if (any(nam_functions)) { msg_values <- paste0(msg_values, sum(nam_functions), " functions for possible choices.", collapse = "\n") } if (nam_values) { - msg_values <- paste0(msg_values, x$names[!nam_functions], " as possible choices.", - collapse = "\n") + msg_values <- paste0(msg_values, paste0(sQuote(x$names[!nam_functions]), collapse = ", "), + " as possible choices.", collapse = "\n") } sel_list <- is.list(x$select) @@ -158,14 +168,14 @@ print.type <- function(x, ...) { } msg_sel <- character() - sel_values <- length(x$select) - sel_functions + sel_values <- length(x$select) - sum(sel_functions) if (any(sel_functions)) { msg_sel <- paste0(msg_sel, sum(sel_functions), " functions to select.", collapse = "\n") } if (sel_values) { - msg_sel <- paste0(msg_sel, x$select[!sel_functions], " selected.", - collapse = "\n") + msg_sel <- paste0(msg_sel, paste0(sQuote(x$select[!sel_functions]), collapse = ", "), + " selected.", collapse = "\n") } cat(msg_values, msg_sel) return(x) From 3a39de288b098d2456b0e7ea1f5437778a734da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 17 Mar 2025 16:59:33 +0100 Subject: [PATCH 025/110] Avoid conflicting names --- R/resolver.R | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index c6a4f118..e7b3bdcd 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -75,11 +75,11 @@ functions_data <- function(unresolved, data) { l <- lapply(fc_unresolved, function(f) { v <- vapply(datasets, function(d) { # Extract the data and apply the user supplied function - out <- tryCatch(f(data(data, d)), error = function(x){FALSE}) + out <- tryCatch(f(extract(data, d)), error = function(x){FALSE}) if (!is.logical(out)) { stop("Provided functions should return a logical object.") } - if (length(out) != 1L && length(out) != length(data(data, d))) { + if (length(out) != 1L && length(out) != length(extract(data, d))) { # Function resolution is unconventional, but this would produce too many warnings... # warning("The output of the function must be of length 1 or the same length as the data.") return(FALSE) @@ -230,8 +230,8 @@ resolver.values <- function(spec, data) { return(spec) } svalues <- spec$values - dataset <- data(data, spec$datasets$select) - variable <- data(dataset, spec$variables$select) + dataset <- extract(data, spec$datasets$select) + variable <- extract(dataset, spec$variables$select) orig_names <- svalues$names orig_select <- svalues$select spec$values <- if (is.delayed(svalues) && all(is.character(svalues$names))) { @@ -281,7 +281,7 @@ resolver.values <- function(spec, data) { } #' @export -data.MultiAssayExperiment <- function(x, variable) { +extract.MultiAssayExperiment <- function(x, variable) { if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { stop("Required to have MultiAssayExperiment's package.") } @@ -290,31 +290,31 @@ data.MultiAssayExperiment <- function(x, variable) { } #' @export -data.matrix <- function(x, variable) { +extract.matrix <- function(x, variable) { # length(variable) == 1L x[, variable, drop = TRUE] } #' @export -#' @method data data.frame -data.data.frame <- function(x, variable) { +#' @method extract data.frame +extract.data.frame <- function(x, variable) { # length(variable) == 1L x[, variable, drop = TRUE] } #' @export -data.qenv <- function(x, variable) { +extract.qenv <- function(x, variable) { x[[variable]] } #' @export -data.default <- function(x, variable) { +extract.default <- function(x, variable) { x[, variable, drop = TRUE] } #' @export -data <- function(x, variable) { - UseMethod("data") +extract <- function(x, variable) { + UseMethod("extract") } #' Update a specification From 6920d6e8200fedd70351b48c90aacc35770cce8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 17 Mar 2025 17:00:14 +0100 Subject: [PATCH 026/110] Simplify according to new methods --- R/delayed.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/delayed.R b/R/delayed.R index 419cb6ac..61520bd9 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -10,8 +10,7 @@ delay <- function(x) { #' @method is.delayed type is.delayed.type <- function(x) { - na <- length(x) == 1L && is.na(x) - if (!na) { + if (!is.na(x)) { return(!all(is.character(x$names)) || !all(is.character(x$select))) } FALSE From d7287394c79721ed0ef79c6ae21217b89dfe0288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 17 Mar 2025 17:04:26 +0100 Subject: [PATCH 027/110] Updating docs --- NAMESPACE | 14 +++++++----- R/resolver.R | 59 ++++++++++++++++++++++++++++++++++++++----------- man/resolver.Rd | 6 +++-- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 759061e4..c7f6c89d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,23 +2,25 @@ S3method(Ops,transform) S3method(Ops,type) +S3method(anyNA,type) S3method(c,transform) S3method(c,type) -S3method(data,MultiAssayExperiment) -S3method(data,data.frame) -S3method(data,default) -S3method(data,matrix) -S3method(data,qenv) S3method(data_extract_multiple_srv,FilteredData) S3method(data_extract_multiple_srv,list) S3method(data_extract_multiple_srv,reactive) S3method(data_extract_srv,FilteredData) S3method(data_extract_srv,list) +S3method(extract,MultiAssayExperiment) +S3method(extract,data.frame) +S3method(extract,default) +S3method(extract,matrix) +S3method(extract,qenv) S3method(filter_spec_internal,default) S3method(filter_spec_internal,delayed_data) S3method(is.delayed,default) S3method(is.delayed,transform) S3method(is.delayed,type) +S3method(is.na,type) S3method(merge_expression_module,list) S3method(merge_expression_module,reactive) S3method(merge_expression_srv,list) @@ -53,13 +55,13 @@ export(check_no_multiple_selection) export(choices_labeled) export(choices_selected) export(compose_and_enable_validators) -export(data) export(data_extract_multiple_srv) export(data_extract_spec) export(data_extract_srv) export(data_extract_ui) export(datanames_input) export(datasets) +export(extract) export(filter_spec) export(first_choice) export(first_choices) diff --git a/R/resolver.R b/R/resolver.R index e7b3bdcd..98593780 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -9,13 +9,15 @@ #' @export #' #' @examples -#' dataset1 <- datasets("df", function(x){head(x, 1)}) -#' dataset2 <- datasets(is.matrix, function(x){head(x, 1)}) +#' dataset1 <- datasets(is.data.frame) +#' dataset2 <- datasets(is.matrix) #' spec <- dataset1 & variables("a", "a") #' td <- within(teal.data::teal_data(), { #' df <- data.frame(a = as.factor(LETTERS[1:5]), b = letters[1:5]) +#' df2 <- data.frame(a = LETTERS[1:5], b = 1:5) #' m <- matrix() #' }) +#' resolver(spec | dataset2, td) #' resolver(dataset2, td) #' resolver(spec, td) #' spec <- dataset1 & variables("a", is.factor) @@ -24,7 +26,25 @@ resolver <- function(spec, data) { if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } - stopifnot(is.transform(spec), has_dataset(spec)) + stopifnot(is.transform(spec)) + + if (!is.delayed(spec)) { + return(spec) + } + + if (!is.null(names(spec))) { + rt <- resolver_transform(spec, data) + } else { + rt <- lapply(spec, resolver_transform, data = data) + if (length(rt) == 1) { + rt <- rt[[1]] + } + # FIXME: If there are several options invalidate whatever is below, until this is resolved. + } + rt +} + +resolver_transform <- function(spec, data) { specf <- spec if (has_dataset(specf) && is.delayed(specf$datasets)) { specf <- resolver.datasets(specf, data) @@ -165,7 +185,7 @@ resolver.variables <- function(spec, data) { return(spec) } datasets <- spec$datasets$select - data_selected <- data(data, datasets) + data_selected <- extract(data, datasets) if (is.null(names(data_selected))) { names_data <- colnames(data_selected) } else { @@ -337,18 +357,26 @@ extract <- function(x, variable) { #' update_spec(res, "datasets", "df_n") #' # update_spec(res, "datasets", "error") update_spec <- function(spec, type, value) { - w <- c("datasets", "variables", "values") - type <- match.arg(type, w) - restart_types <- w[seq_along(w) > which(type == w)] - speci <- spec if (!is.character(value)) { stop("The updated value is not a character.", "\nDo you attempt to set a new specification? Please open an issue") } - valid_names <- spec[[type]]$names - if (is.delayed(spec[[type]])) { - stop(type, " should be resolved before updating.") + + if (!is.null(names(spec))) { + updated_spec <- update_s_spec(spec, type, value) + } else { + update_multiple <- lapply(spec, update_s_spec, type, value) } + updated_spec +} + +update_s_spec <- function(spec, type, value) { + w <- c("datasets", "variables", "values") + type <- match.arg(type, w) + restart_types <- w[seq_along(w) > which(type == w)] + + valid_names <- spec[[type]]$names + if (!is.list(valid_names) && all(value %in% valid_names)) { original_select <- orig(spec[[type]]$select) spec[[type]][["select"]] <- value @@ -368,12 +396,12 @@ update_spec <- function(spec, type, value) { # Restore to the original specs for (type in restart_types) { - if (length(spec[[type]]) == 1L && is.na(spec[[type]])) { + if (is.na(spec[[type]])) { next } fun <- match.fun(type) restored_transform <- fun(x = orig(spec[[type]]$names), - select = orig(spec[[type]]$select)) + select = orig(spec[[type]]$select)) spec[[type]] <- restored_transform[[type]] } spec @@ -382,3 +410,8 @@ update_spec <- function(spec, type, value) { orig <- function(x) { attr(x, "original") } + +unorig <- function(x) { + attr(x, "original") <- NULL + x +} diff --git a/man/resolver.Rd b/man/resolver.Rd index 3d39c39f..dd7d1f1d 100644 --- a/man/resolver.Rd +++ b/man/resolver.Rd @@ -18,13 +18,15 @@ A transform but resolved Given the specification of some data to extract find if they are available or not. } \examples{ -dataset1 <- datasets("df", function(x){head(x, 1)}) -dataset2 <- datasets(is.matrix, function(x){head(x, 1)}) +dataset1 <- datasets(is.data.frame) +dataset2 <- datasets(is.matrix) spec <- dataset1 & variables("a", "a") td <- within(teal.data::teal_data(), { df <- data.frame(a = as.factor(LETTERS[1:5]), b = letters[1:5]) + df2 <- data.frame(a = LETTERS[1:5], b = 1:5) m <- matrix() }) +resolver(spec | dataset2, td) resolver(dataset2, td) resolver(spec, td) spec <- dataset1 & variables("a", is.factor) From 6a85027173afdc665f6cc7cf29bfdbf7e81c33ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 17 Mar 2025 17:18:16 +0100 Subject: [PATCH 028/110] Fix some checks --- R/types.R | 4 ++-- tests/testthat/test-resolver.R | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/types.R b/R/types.R index 08e36b04..03ff4b56 100644 --- a/R/types.R +++ b/R/types.R @@ -35,8 +35,8 @@ is.na.type <- function(x) { } #' @export -anyNA.type <- function(x) { - anyNA(unclass(x)) +anyNA.type <- function(x, recursive = FALSE) { + anyNA(unclass(x), recursive) } first <- function(x){ diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 67f8cf06..e89a5340 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -135,7 +135,7 @@ test_that("update_spec resolves correctly", { expect_error(update_spec(res, "datasets", "error")) expect_error(update_spec(data_frames_factors, "datasets", "error")) - expect_error(update_spec(datasets(x = c("df", "df2")), "datasets", "df2")) + expect_no_error(update_spec(datasets(x = c("df", "df2")), "datasets", "df2")) expect_no_error(update_spec(datasets(x = c("df", "df2"), "df"), "datasets", "df2")) }) From 9993468317aeac524a33285fe5d789a3d8457125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 18 Mar 2025 14:55:07 +0100 Subject: [PATCH 029/110] Simplify and reorganize delayed --- R/delayed.R | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/R/delayed.R b/R/delayed.R index 61520bd9..86f47380 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -7,30 +7,35 @@ delay <- function(x) { } #' @export -#' @method is.delayed type -is.delayed.type <- function(x) { +is.delayed <- function(x) { + UseMethod("is.delayed") +} - if (!is.na(x)) { - return(!all(is.character(x$names)) || !all(is.character(x$select))) - } +#' @export +#' @method is.delayed default +is.delayed.default <- function(x) { FALSE } #' @export #' @method is.delayed transform is.delayed.transform <- function(x) { - is.delayed(x$datasets) || is.delayed(x$variables) || is.delayed(x$values) + if (!is.null(names(x))) { + any(vapply(x, is.delayed, logical(1L))) + } else { + delayed <- vapply(x, is.delayed, logical(1L)) + any(delayed) + } } #' @export -#' @method is.delayed default -is.delayed.default <- function(x) { - FALSE -} +#' @method is.delayed type +is.delayed.type <- function(x) { -#' @export -is.delayed <- function(x) { - UseMethod("is.delayed") + if (!is.na(x)) { + return(!all(is.character(x$names)) || !all(is.character(x$select))) + } + FALSE } resolved <- function(x, variable){ From 3624441d127faf5f368b0b754c9cb6ffcbf23340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 19 Mar 2025 18:00:18 +0100 Subject: [PATCH 030/110] Basic operations work more flexible --- R/ops_transform.R | 96 ++++++++++++++++++++++++--- R/types.R | 161 ++++++++++++++++++++++++++++------------------ 2 files changed, 187 insertions(+), 70 deletions(-) diff --git a/R/ops_transform.R b/R/ops_transform.R index 6ff26a9b..b70d020e 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -9,8 +9,8 @@ Ops.transform <- function(e1, e2) { switch(.Generic, "!=" = NextMethod(), "==" = NextMethod(), - "|" = combine_transform(e1, e2), - "&" = c(e1, e2), + "|" = or_transform(e1, e2), + "&" = nd_transform(e1, e2), stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE)) } @@ -25,15 +25,93 @@ Ops.type <- function(e1, e2) { out <- switch(.Generic, "!=" = NextMethod(), # "==" = NextMethod(), - # "|" = , - "&" = c(e1, e2), + "|" = or_type(e1, e2), + "&" = nd_type(e1, e2), stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE)) - class(out) <- class(e1) out } -combine_transform <- function(e1, e2) { - l <- list(e1, e2) - class(l) <- c("transform", "list") - l +or_transform <- function(e1, e2) { + if (is.transform(e1) && is.type(e2) && !is.transform(e2)) { + opt2 <- e1 & e2 + out <- list(e1, opt2) + } else if (!is.transform(e1) && is.type(e1) && is.transform(e2)) { + opt2 <- e2 & e1 + out <- list(e2, opt2) + } else { + out <- list(e1, e2) + } + class(out) <- unique(c("transform", "list")) + out +} + +nd_transform <- function(e1, e2) { + if (is.transform(e1) && is.transform(e2)) { + types <- intersect(names(e1), names(e2)) + for (t in types) { + e1[[t]] <- unique(c(e1[[t]], e2[[t]])) + } + return(e1) + } + + if (is.type(e1) && is.transform(e2)) { + if (!is(e1) %in% names(e2)) { + e2[[is(e1)]] <- e1 + } else { + e2[[is(e1)]] <- c(e2[[is(e1)]], e1) + } + return(e2) + } else if (is.transform(e1) && is.type(e2)) { + if (!is(e2) %in% names(e1)) { + e1[[is(e2)]] <- e2 + } else { + e1[[is(e2)]] <- c(e1[[is(e2)]], e2) + } + out <- e1 + } else if (is.type(e1) && is.transform(e2)) { + out <- rev(c(e2, e1)) # To keep order in the list + } else { + stop("Method not implemented yet!") + } + out +} + +nd_type <- function(e1, e2) { + if (is.transform(e1) && !is.transform(e2)) { + out <- c(e1, list(e2)) + names(out)[length(out)] <- is(e2) + } else if (!is.transform(e1) && is.transform(e2)) { + out <- c(e2, list(e1)) + names(out)[length(out)] <- is(e1) + } else if (is.transform(e1) && is.transform(e2)){ + out <- c(e1, e2) + } else if (is.type(e1) && is.type(e2)) { + out <- list(e1, e2) + names(out) <- c(is(e1), is(e2)) + } else { + stop("Maybe we should decide how to apply a type to a list of transformers...") + } + class(out) <- c("transform", class(out)) + browser(expr = is(out) == "datasets" && length(table(names(out))) == 1L) + out +} + +or_type <- function(e1, e2) { + substitute <- is(e2) %in% names(e1) + if (substitute) { + out <- e1 + e1[[is(e2)]] <- e2 + return(add_type(out, e1)) + } + list(e1, e2) +} + + +# chooseOpsMethod.list <- function(x, y, mx, my, cl, reverse) TRUE +#' @export +chooseOpsMethod.transform <- function(x, y, mx, my, cl, reverse) { + # Apply one or other method + # !is.transform(x) + TRUE } +# chooseOpsMethod.type <- function(x, y, mx, my, cl, reverse) TRUE diff --git a/R/types.R b/R/types.R index 03ff4b56..fd7cb606 100644 --- a/R/types.R +++ b/R/types.R @@ -1,42 +1,26 @@ -transform <- function() { - o <- list(datasets = na_type("datasets"), - variables = na_type("variables"), - values = na_type("values")) - class(o) <- c("transform", "list") - delay(o) -} - is.transform <- function(x) { inherits(x, "transform") } -has_dataset <- function(x) { - !anyNA(x[["datasets"]]) -} - -has_variable <- function(x) { - !anyNA(x[["variables"]]) -} - -has_value <- function(x) { - !anyNA(x[["values"]]) -} - na_type <- function(type) { out <- NA_character_ class(out) <- c(type, "type") out } +is.type <- function(x) { + inherits(x, "type") +} + #' @export #' @method is.na type is.na.type <- function(x) { - anyNA(unclass(x)) + anyNA(unclass(x[c("names", "select")])) } #' @export anyNA.type <- function(x, recursive = FALSE) { - anyNA(unclass(x), recursive) + anyNA(unclass(x[c("names", "select")]), recursive) } first <- function(x){ @@ -71,64 +55,119 @@ type_helper <- function(x, select, type) { #' @export datasets <- function(x, select = first) { - o <- transform() - o$datasets <- type_helper(x, select, type = "datasets") - o + type_helper(x, select, type = "datasets") } #' @export variables <- function(x, select = first) { - o <- transform() - o$variables <- type_helper(x, select, type = "variables") - o + type_helper(x, select, type = "variables") } #' @export values <- function(x, select = first) { - o <- transform() - o$values <- type_helper(x, select, type = "values") - o -} + type_helper(x, select, type = "values") +} + +# #' @export +# c.type <- function(...) { +# +# if (is.na(..1)) { +# return(..2) +# } else if (is.na(..2)) { +# return(..1) +# } +# +# if (...length() > 2L) { +# stop("We can't combine this (yet)") +# } else if (all(class(..2) != class(..1))) { +# type_out <- ..1 +# type_out$child <- ..2 +# return(type_out) +# } +# out <- mapply(c, ..., SIMPLIFY = FALSE) +# out <- lapply(out, unique) +# class(out) <- c("transform", class(out)) +# delay(out) +# } #' @export c.transform <- function(...) { - if (...length() > 2) { - stop("More than two specifications won't be considered. Use & to combine them", call. = FALSE) + l <- list(...) + types <- lapply(l, names) + utypes <- unique(unlist(types, FALSE, FALSE)) + vector <- vector("list", length(utypes)) + names(vector) <- utypes + for (t in utypes) { + new_type <- vector("list", length = 2) + names(new_type) <- c("names", "select") + class(new_type) <- c("type", "list") + for (i in seq_along(l)) { + if (!t %in% names(l[[i]])) { + next + } + # Slower but less code duplication: + # new_type <- c(new_type, l[[i]][[t]]) + # then we need class(new_type) <- c(t, "type", "list") outside the loop + old_names <- new_type$names + old_select <- new_type$select + new_type$names <- c(old_names, l[[i]][[t]][["names"]]) + attr(new_type$names, "original") <- c(orig( + old_names), orig(l[[i]][[t]][["names"]])) + new_type$select <- c(old_select, l[[i]][[t]][["select"]]) + attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][[t]][["select"]])) + } + orig_names <- unique(orig(new_type$names)) + new_type$names <- unique(new_type$names) + attr(new_type$names, "original") <- orig_names + + orig_select <- unique(orig(new_type$select)) + new_type$select <- unique(new_type$select) + attr(new_type$select, "original") <- orig_select + class(new_type) <- c(t, "type", "list") + vector[[t]] <- new_type } - transf <- mapply(c, ..., SIMPLIFY = FALSE) - class(transf) <- c("transform", "list") - delay(transf) + class(vector) <- c("transform", "list") + vector } #' @export c.type <- function(...) { - - if (is.na(..1)) { - return(..2) - } else if (is.na(..2)) { - return(..1) + l <- list(...) + types <- lapply(l, is) + utypes <- unique(unlist(types, FALSE, FALSE)) + vector <- vector("list", length(utypes)) + names(vector) <- utypes + for (t in utypes) { + new_type <- vector("list", length = 2) + names(new_type) <- c("names", "select") + for (i in seq_along(l)) { + if (!t %in% names(l[[i]])) { + next + } + old_names <- new_type$names + old_select <- new_type$select + new_type$names <- c(old_names, l[[i]][[t]][["names"]]) + attr(new_type$names, "original") <- c(orig( + old_names), orig(l[[i]][[t]][["names"]])) + new_type$select <- c(old_select, l[[i]][[t]][["select"]]) + attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][[t]][["select"]])) + } + orig_names <- unique(orig(new_type$names)) + new_type$names <- unique(new_type$names) + attr(new_type$names, "original") <- orig_names + + orig_select <- unique(orig(new_type$select)) + new_type$select <- unique(new_type$select) + attr(new_type$select, "original") <- orig_select + class(new_type) <- c(t, "type", "list") + vector[[t]] <- new_type } - - objects <- list(...) - classes <- unlist(lapply(objects, class), FALSE,FALSE) - type <- setdiff(classes, c("type", "list")) - if (length(type) > 1L) { - stop("Combining different types", call. = FALSE) + if (length(vector) == 1) { + return(vector[[1]]) } - - names <- lapply(objects, "[[", i = "names") - select <- lapply(objects, "[[", i = "select") - names_orig <- lapply(names, orig) - select_orig <- lapply(select, orig) - type_f <- match.fun(type) - type_out <- type_f(x = simplify_c(names_orig), - select = simplify_c(select_orig)) - attr(type_out[[type]][["names"]], "original") <- NULL - attr(type_out[[type]][["names"]], "original") <- simplify_c(names_orig) - attr(type_out[[type]][["select"]], "original") <- NULL - attr(type_out[[type]][["select"]], "original") <- simplify_c(select_orig) - delay(type_out[[type]]) + class(vector) <- c("transform", "list") + vector } simplify_c <- function(x) { From 941b172a18c37cd03a4c2b3c43221931f3ede2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 10:48:34 +0100 Subject: [PATCH 031/110] Simplify code --- R/delayed.R | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/R/delayed.R b/R/delayed.R index 86f47380..d7416539 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -20,12 +20,7 @@ is.delayed.default <- function(x) { #' @export #' @method is.delayed transform is.delayed.transform <- function(x) { - if (!is.null(names(x))) { - any(vapply(x, is.delayed, logical(1L))) - } else { - delayed <- vapply(x, is.delayed, logical(1L)) - any(delayed) - } + any(vapply(x, is.delayed, logical(1L))) } #' @export @@ -38,36 +33,12 @@ is.delayed.type <- function(x) { FALSE } -resolved <- function(x, variable){ +resolved <- function(x, type = is(x)){ s <- all(is.character(x$names)) && all(is.character(x$select)) if (!s && !all(x$select %in% x$names)) { - stop("Selected ", variable, " not available") + stop("Selected ", type, " not resolved.") } attr(x, "delayed") <- NULL x } - -get_datasets <- function(x) { - if (is.transform(x) && !is.delayed(x$datasets)) { - x$datasets$names - } else { - NULL - } -} - -get_variables <- function(x) { - if (is.transform(x) && !is.delayed(x$datasets) && !is.delayed(x$variables)) { - x$variables$names - } else { - NULL - } -} - -get_values <- function(x) { - if (is.transform(x) && !is.delayed(x)) { - x$values$names - } else { - NULL - } -} From fe138e9e6b2d5080a6c2b6b29fc55538e5e5fa5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 10:50:40 +0100 Subject: [PATCH 032/110] Add more checks --- R/ops_transform.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/R/ops_transform.R b/R/ops_transform.R index b70d020e..5a4ae2f4 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -38,10 +38,13 @@ or_transform <- function(e1, e2) { } else if (!is.transform(e1) && is.type(e1) && is.transform(e2)) { opt2 <- e2 & e1 out <- list(e2, opt2) - } else { + } else if (is.transform(e1) && is.transform(e2)) { out <- list(e1, e2) + } else { + stop("Missing implementation method.") } - class(out) <- unique(c("transform", "list")) + # FIXME: Should we signal it is a transform or just a list of transform is enough? + # class(out) <- c("transform", "list") out } @@ -92,7 +95,6 @@ nd_type <- function(e1, e2) { stop("Maybe we should decide how to apply a type to a list of transformers...") } class(out) <- c("transform", class(out)) - browser(expr = is(out) == "datasets" && length(table(names(out))) == 1L) out } From cf2d634d02c102f78032ae057ec7ea456dcab646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 10:51:46 +0100 Subject: [PATCH 033/110] Resolve only what is possible. Select is resolved on the names of the variables selected not on the data itself. --- R/resolver.R | 487 ++++++++++++++++++++++----------------------------- 1 file changed, 206 insertions(+), 281 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index 98593780..1e3fe15d 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -1,7 +1,7 @@ #' Resolve the specification #' #' Given the specification of some data to extract find if they are available or not. -#' +#' The specification for selecting a variable shouldn't depend on the data of said variable. #' @param spec A object extraction specification. #' @param data A `qenv()`, or `teal.data::teal_data()` object. #' @@ -26,52 +26,80 @@ resolver <- function(spec, data) { if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } - stopifnot(is.transform(spec)) - if (!is.delayed(spec)) { return(spec) } + # Adding some default specifications if they are missing + if ("values" %in% names(spec) && !"variables" %in% names(spec)) { + spec <- variables(first) & spec + } - if (!is.null(names(spec))) { - rt <- resolver_transform(spec, data) - } else { - rt <- lapply(spec, resolver_transform, data = data) - if (length(rt) == 1) { - rt <- rt[[1]] - } - # FIXME: If there are several options invalidate whatever is below, until this is resolved. + if ("variables" %in% names(spec) && !"datasets" %in% names(spec)) { + spec <- datasets(first) & spec } - rt + + stopifnot(is.transform(spec)) + det <- determine(spec, data, spec = spec) + det$type } -resolver_transform <- function(spec, data) { - specf <- spec - if (has_dataset(specf) && is.delayed(specf$datasets)) { - specf <- resolver.datasets(specf, data) - } else if (!has_dataset(specf)) { - specf$datasets <- na_type("datasets") +#' A method that should take a type and resolve it. +#' +#' Generic that makes the minimal check on spec. +#' Responsible of subsetting/extract the data received and check that the type matches +#' @param type The specification to resolve. +#' @param data The minimal data required. +#' @return A list with two elements, the type resolved and the data extracted. +#' @keywords internal +#' @export +determine <- function(type, data, ...) { + stopifnot(is.type(type) || is.transform(type)) + if (!is.delayed(type)) { + return(list(type = type, data = data)) } + UseMethod("determine") +} - if (has_variable(specf) && !is.delayed(specf$datasets)) { - specf <- resolver.variables(specf, data) +#' @export +determine.default <- function(type, data, ..., spec) { + if (!is.null(names(spec)) && is.delayed(spec)) { + rt <- determine(spec, data) } else { - specf$variables <- na_type("variables") + rt <- lapply(spec, resolver, data = data, spec = spec) + if (length(rt) == 1) { + return(rt[[1]]) + } } + rt +} - if (has_value(specf) && !is.delayed(specf$datasets) && !is.delayed(specf$variables)) { - specf <- resolver.values(specf, data) - } else { - specf$values <- na_type("values") +#' @export +determine.transform <- function(type, data, ..., spec) { + stopifnot(inherits(data, "qenv")) + # Recursion for other transforms in a list spec | spec + if (is.null(names(spec))) { + specs <- lapply(type, data, spec = spec) + return(specs) } - attr(specf, "delayed") <- NULL - delay(specf) + for (i in seq_along(type)) { + di <- determine(type[[i]], data, spec = spec) + # orverwrite so that next type in line receives the corresponding data and specification + if (is.null(di$type)) { + next + } + type[[i]] <- di$type + data <- di$data + } + list(type = type, data = data) # It is the transform object resolved. } functions_names <- function(unresolved, reference) { + stopifnot(is.character(reference)) # Allows for NA characters is_fc <- vapply(unresolved, is.function, logical(1L)) fc_unresolved <- unresolved[is_fc] x <- vector("character") + for (f in fc_unresolved) { y <- tryCatch(f(reference), error = function(x) f ) @@ -83,328 +111,225 @@ functions_names <- function(unresolved, reference) { unique(unlist(c(unresolved[!is_fc], x), FALSE, FALSE)) } -functions_data <- function(unresolved, data) { +functions_data <- function(unresolved, data, names) { + stopifnot(!is.null(data)) # Must be something but not NULL fc_unresolved <- unresolved[vapply(unresolved, is.function, logical(1L))] - - # This is for variables - datasets <- names(data) - # Matrix doesn't have a names method - if (is.null(datasets)) { - datasets <- colnames(data) - } l <- lapply(fc_unresolved, function(f) { - v <- vapply(datasets, function(d) { - # Extract the data and apply the user supplied function - out <- tryCatch(f(extract(data, d)), error = function(x){FALSE}) - if (!is.logical(out)) { - stop("Provided functions should return a logical object.") - } - if (length(out) != 1L && length(out) != length(extract(data, d))) { - # Function resolution is unconventional, but this would produce too many warnings... - # warning("The output of the function must be of length 1 or the same length as the data.") - return(FALSE) - } - all(out) - }, logical(1L)) - datasets[v] + all_data <- tryCatch(f(data), error = function(x){FALSE}) + if (any(all_data)) { + return(names[all_data]) + } else { + return(NULL) + } }) unique(unlist(l, FALSE, FALSE)) } -resolver.datasets <- function(spec, data) { - if (!inherits(data, "qenv")) { - stop("Please use qenv() or teal_data() objects.") - } - if (is.null(spec[["datasets"]]) || all(is.na(spec[["datasets"]]))) { - return(spec) - } - sdatasets <- spec$datasets - data_names <- names(data) - orig_names <- sdatasets$names - orig_select <- sdatasets$select - if (is.delayed(sdatasets) && all(is.character(sdatasets$names))) { - match <- intersect(data_names, sdatasets$names) - missing <- setdiff(sdatasets$names, data_names) +# Checks that for the given type and data names and data it can be resolved +# The workhorse of the resolver +determine_helper <- function(type, data_names, data) { + orig_names <- type$names + orig_select <- type$select + names_variables_obj <- if (is.null(names(data))) { colnames(data)} else {names(data)} + if (is.delayed(type) && all(is.character(type$names))) { + match <- intersect(data_names, type$names) + missing <- setdiff(type$names, data_names) if (length(missing)) { - stop("Missing datasets ", paste(sQuote(missing), collapse = ", "), " were specified.") + return(NULL) + # stop("Missing datasets ", paste(sQuote(missing), collapse = ", "), " were specified.") } - sdatasets$names <- match + type$names <- match if (length(match) == 0) { - stop("No selected datasets matching the conditions requested") + return(NULL) + # stop("No selected ", is(type), " matching the conditions requested") } else if (length(match) == 1) { - sdatasets$select <- match + type$select <- match } else { - new_select <- c(functions_names(sdatasets$select, sdatasets$names), - functions_data(sdatasets$select, data[sdatasets$names])) + new_select <- functions_names(type$select, type$names) new_select <- unique(new_select[!is.na(new_select)]) if (!length(new_select)) { - stop("No datasets meet the requirements to be selected") + return(NULL) + # stop("No ", is(type), " meet the requirements to be selected") } - sdatasets$select <- new_select + type$select <- new_select } - } else if (is.delayed(sdatasets)) { - old_names <- sdatasets$names - new_names <- c(functions_names(sdatasets$names, data_names), - functions_data(sdatasets$names, data)) + } else if (is.delayed(type)) { + old_names <- type$names + new_names <- c(functions_names(type$names, names_variables_obj), + functions_data(type$names, data, data_names)) new_names <- unique(new_names[!is.na(new_names)]) if (!length(new_names)) { - stop("No datasets meet the requirements") + return(NULL) + # stop("No ", is(type), " meet the requirements") } - sdatasets$names <- new_names + type$names <- new_names - if (length(sdatasets$names) == 0) { - stop("No selected datasets matching the conditions requested") - } else if (length(sdatasets$names) == 1) { - sdatasets$select <- sdatasets$names + if (length(type$names) == 0) { + return(NULL) + # stop("No selected ", is(type), " matching the conditions requested") + } else if (length(type$names) == 1) { + type$select <- type$names } - new_select <- c(functions_names(sdatasets$select, sdatasets$names), - functions_data(sdatasets$select, data[sdatasets$names])) + new_select <- functions_names(type$select, type$names) new_select <- unique(new_select[!is.na(new_select)]) if (!length(new_select)) { - stop("No datasets meet the requirements to be selected") + return(NULL) + stop("No ", is(type), " meet the requirements to be selected") } - sdatasets$select <- new_select + type$select <- new_select } - attr(sdatasets$names, "original") <- orig(orig_names) - attr(sdatasets$select, "original") <- orig(orig_select) - spec$datasets <- resolved(sdatasets, "dataset") - spec + attr(type$names, "original") <- orig(orig_names) + attr(type$select, "original") <- orig(orig_select) + resolved(type) } -resolver.variables <- function(spec, data) { +#' @export +determine.datasets <- function(type, data, ...) { if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } - if (is.delayed(spec$datasets)) { - stop("Datasets not resolved yet") - } - if (is.null(spec[["variables"]]) || all(is.na(spec[["variables"]]))) { - return(spec) - } - datasets <- spec$datasets$select - data_selected <- extract(data, datasets) - if (is.null(names(data_selected))) { - names_data <- colnames(data_selected) - } else { - names_data <- names(data_selected) - } - - svariables <- spec$variables - orig_names <- svariables$names - orig_select <- svariables$select - if (is.delayed(svariables) && all(is.character(svariables$names))) { - match <- intersect(names_data, svariables$names) - missing <- setdiff(svariables$names, names_data) - if (length(missing)) { - stop("Missing variables ", paste(sQuote(missing), collapse = ", "), " were specified.") - } - svariables$names <- match - if (length(match) == 1) { - svariables$select <- match - } else { - new_select <- c(functions_names(svariables$select, svariables$names), - functions_data(svariables$select, data_selected)) - new_select <- unique(new_select[!is.na(new_select)]) - if (!length(new_select)) { - stop("No variables meet the requirements to be selected") - } - svariables$select <- new_select - } - } else if (is.delayed(svariables)) { - new_names <- c(functions_names(svariables$names, names_data), - functions_data(svariables$names, data_selected)) - new_names <- unique(new_names[!is.na(new_names)]) - if (!length(new_names)) { - stop("No variables meet the requirements") - } - svariables$names <- new_names - if (length(svariables$names) == 1) { - svariables$select <- svariables$names - } else { - new_select <- c(functions_names(svariables$select, svariables$names), - functions_data(svariables$select, data_selected)) - new_select <- unique(new_select[!is.na(new_select)]) - if (!length(new_select)) { - stop("No variables meet the requirements to be selected") - } - svariables$select <- new_select + l <- vector("list", length(data)) + for (i in seq_along(data)){ + data_name_env <- names(data)[i] + out <- determine_helper(type, data_name_env, data[[data_name_env]]) + if (!is.null(out)) { + l[[i]] <- out } } - attr(svariables$names, "original") <- orig(orig_names) - attr(svariables$select, "original") <- orig(orig_select) - spec$variables <- resolved(svariables, "variables") - spec + # Merge together all the types + type <- do.call(c, l[lengths(l) > 1]) + # Not possible to know what is happening + if (!is.delayed(type) && length(type$select) > 1) { + list(type = type, data = data[type$select]) + } else if (!is.delayed(type) && length(type$select) == 1) { + list(type = type, data = data[[type$select]]) + } else { + list(type = type, data = NULL) + } } -resolver.values <- function(spec, data) { - if (!inherits(data, "qenv")) { - stop("Please use qenv() or teal_data() objects.") +#' @export +determine.variables <- function(type, data, ...) { + if (length(dim(data)) != 2L) { + stop("Can't resolve variables from this object of class ", class(data)) } - if (is.null(spec[["values"]]) || all(is.na(spec[["values"]]))) { - return(spec) + if (ncol(data) <= 0L) { + stop("Can't pull variable: No variable is available.") } - svalues <- spec$values - dataset <- extract(data, spec$datasets$select) - variable <- extract(dataset, spec$variables$select) - orig_names <- svalues$names - orig_select <- svalues$select - spec$values <- if (is.delayed(svalues) && all(is.character(svalues$names))) { - match <- intersect(variable, svalues$names) - missing <- setdiff(svalues$names, variable) - if (length(missing)) { - stop("Missing values ", paste(sQuote(missing), collapse = ", "), " were specified.") - } - svalues$names <- match - if (length(match) == 1) { - svalues$select <- match - } else { - match <- intersect(variable, svalues$names) - new_select <- c(functions_names(svalues$select, svalues$names), - functions_data(svalues$select, variable)) - new_select <- unique(new_select[!is.na(new_select)]) - if (!length(new_select)) { - stop("No variables meet the requirements to be selected") - } - svalues$select <- new_select - } - } else if (is.delayed(svalues)) { - new_names <- c(functions_names(svalues$names, variable), - functions_data(svalues$names, variable)) - new_names <- unique(new_names[!is.na(new_names)]) - if (!length(new_names)) { - stop("No variables meet the requirements") - } - svalues$names <- new_names - if (length(svalues$names) == 1) { - svalues$select <- svalues$names - } else { - new_select <- c(functions_names(svalues$select, variable), - functions_data(svalues$select, variable)) - new_select <- unique(new_select[!is.na(new_select)]) - if (!length(new_select)) { - stop("No variables meet the requirements to be selected") - } - svalues$select <- new_select + # Assumes the object has colnames method (true for major object classes: DataFrame, tibble, Matrix, array) + # FIXME: What happens if colnames is null: array(dim = c(4, 2)) |> colnames() + l <- vector("list", ncol(data)) + for (i in seq_len(ncol(data))){ + out <- determine_helper(type, colnames(data)[i], data[, i]) + if (!is.null(out)) { + l[[i]] <- out } } - attr(svalues$names, "original") <- orig(orig_names) - attr(svalues$select, "original") <- orig(orig_select) - spec$values <- resolved(svalues, "values") - spec + + # Merge together all the types + type <- do.call(c, l[lengths(l) > 1]) + + # Not possible to know what is happening + if (is.delayed(type)) { + return(list(type = type, data = NULL)) + } + # This works for matrices and data.frames of length 1 or multiple + # be aware of drop behavior on tibble vs data.frame + list(type = type, data = data[, type$select]) } #' @export -extract.MultiAssayExperiment <- function(x, variable) { +determine.mae_colData <- function(type, data, ...) { if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { - stop("Required to have MultiAssayExperiment's package.") + stop("Requires 'MultiAssayExperiment' package.") } - cd <- MultiAssayExperiment::colData(x) - cd[[variable]] -} -#' @export -extract.matrix <- function(x, variable) { - # length(variable) == 1L - x[, variable, drop = TRUE] -} + new_data <- colData(data) + for (i in seq_along(new_data)){ + determine_helper(type, colnames(data)[i], new_data[, i]) + } + if (length(dim(new_data)) != 2L) { + stop("Can't resolve variables from this object of class ", class(new_data)) + } + if (ncol(new_data) <= 0L) { + stop("Can't pull variable: No variable is available.") + } + type <- determine_helper(type, colnames(data), data) -#' @export -#' @method extract data.frame -extract.data.frame <- function(x, variable) { - # length(variable) == 1L - x[, variable, drop = TRUE] -} + # Not possible to know what is happening + if (is.delayed(type)) { + return(list(type = type, data = NULL)) + } -#' @export -extract.qenv <- function(x, variable) { - x[[variable]] -} + if (length(type$select) > 1) { + list(type = type, data = data[type$select]) -#' @export -extract.default <- function(x, variable) { - x[, variable, drop = TRUE] + } else { + list(type = type, data = data[[type$select]]) + } } #' @export -extract <- function(x, variable) { - UseMethod("extract") +determine.mae_experiments <- function(type, data, ...) { + if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { + stop("Requires 'MultiAssayExperiment' package.") + } + new_data <- experiments(data) + type <- determine_helper(type, names(new_data), new_data) + + # Not possible to know what is happening + if (is.delayed(type)) { + } + + if (!is.delayed(type) && length(type$select) > 1) { + list(type = type, data = new_data[type$select]) + + } else if (!is.delayed(type) && length(type$select) == 1) { + list(type = type, data = new_data[[type$select]]) + } else { + return(list(type = type, data = NULL)) + } } -#' Update a specification -#' -#' Once a selection is made update the specification for different valid selection. -#' @param spec A resolved specification such as one created with datasets and variables. -#' @param type Which type was updated? One of datasets, variables, values. -#' @param value What is the new selection? One that is a valid value for the given type and specification. -#' @return The specification with restored choices and selection if caused by the update. #' @export -#' @examples -#' td <- within(teal.data::teal_data(), { -#' df <- data.frame(A = as.factor(letters[1:5]), -#' Ab = LETTERS[1:5]) -#' df_n <- data.frame(C = 1:5, -#' Ab = as.factor(letters[1:5])) -#' }) -#' data_frames_factors <- datasets(is.data.frame) & variables(is.factor) -#' res <- resolver(data_frames_factors, td) -#' update_spec(res, "datasets", "df_n") -#' # update_spec(res, "datasets", "error") -update_spec <- function(spec, type, value) { - if (!is.character(value)) { - stop("The updated value is not a character.", - "\nDo you attempt to set a new specification? Please open an issue") +determine.mae_sampleMap <- function(type, data, ...) { + if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { + stop("Requires 'MultiAssayExperiment' package.") } - if (!is.null(names(spec))) { - updated_spec <- update_s_spec(spec, type, value) - } else { - update_multiple <- lapply(spec, update_s_spec, type, value) + new_data <- sampleMap(data) + type <- determine_helper(type, names(new_data), new_data) + + # Not possible to know what is happening + if (is.delayed(type)) { + return(list(type = type, data = NULL)) } - updated_spec -} -update_s_spec <- function(spec, type, value) { - w <- c("datasets", "variables", "values") - type <- match.arg(type, w) - restart_types <- w[seq_along(w) > which(type == w)] - - valid_names <- spec[[type]]$names - - if (!is.list(valid_names) && all(value %in% valid_names)) { - original_select <- orig(spec[[type]]$select) - spec[[type]][["select"]] <- value - attr(spec[[type]][["select"]], "original") <- original_select - } else if (!is.list(valid_names) && !all(value %in% valid_names)) { - original_select <- orig(spec[[type]]$select) - valid_values <- intersect(value, valid_names) - if (!length(valid_values)) { - stop("No valid value provided.") - } - spec[[type]][["select"]] <- valid_values - attr(spec[[type]][["select"]], "original") <- original_select + if (length(type$select) > 1) { + list(type = type, data = data[type$select]) + } else { - stop("It seems the specification needs to be resolved first.") + list(type = type, data = data[[type$select]]) } +} - # Restore to the original specs - for (type in restart_types) { +#' @export +determine.values <- function(type, data, ...) { + type <- determine_helper(type, names(data), data) - if (is.na(spec[[type]])) { - next - } - fun <- match.fun(type) - restored_transform <- fun(x = orig(spec[[type]]$names), - select = orig(spec[[type]]$select)) - spec[[type]] <- restored_transform[[type]] + # Not possible to know what is happening + if (is.delayed(type)) { + return(list(type = type, data = NULL)) } - spec + + list(type = type, data = type$select) } orig <- function(x) { From 3385521aadf277ed374e0051c89413c0535ee88c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 10:52:59 +0100 Subject: [PATCH 034/110] Combine safely multiple types --- R/types.R | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/R/types.R b/R/types.R index fd7cb606..85fdc0d4 100644 --- a/R/types.R +++ b/R/types.R @@ -95,6 +95,10 @@ values <- function(x, select = first) { c.transform <- function(...) { l <- list(...) types <- lapply(l, names) + typesc <- vapply(l, is.transform, logical(1L)) + if (!all(typesc)) { + stop("An object in position ", which(!typesc), " is not a specification.") + } utypes <- unique(unlist(types, FALSE, FALSE)) vector <- vector("list", length(utypes)) names(vector) <- utypes @@ -135,6 +139,10 @@ c.transform <- function(...) { c.type <- function(...) { l <- list(...) types <- lapply(l, is) + typesc <- vapply(l, is.type, logical(1L)) + if (!all(typesc)) { + stop("An object in position ", which(!typesc), " is not a type.") + } utypes <- unique(unlist(types, FALSE, FALSE)) vector <- vector("list", length(utypes)) names(vector) <- utypes @@ -142,24 +150,27 @@ c.type <- function(...) { new_type <- vector("list", length = 2) names(new_type) <- c("names", "select") for (i in seq_along(l)) { - if (!t %in% names(l[[i]])) { + names_l <- names(l[[i]]) + if (!is(l[[i]], t)) { next } old_names <- new_type$names old_select <- new_type$select - new_type$names <- c(old_names, l[[i]][[t]][["names"]]) + new_type$names <- c(old_names, l[[i]][["names"]]) attr(new_type$names, "original") <- c(orig( - old_names), orig(l[[i]][[t]][["names"]])) - new_type$select <- c(old_select, l[[i]][[t]][["select"]]) - attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][[t]][["select"]])) + old_names), orig(l[[i]][["names"]])) + new_type$select <- unique(c(old_select, l[[i]][["select"]])) + attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][["select"]])) } orig_names <- unique(orig(new_type$names)) + orig_select <- unique(orig(new_type$select)) new_type$names <- unique(new_type$names) attr(new_type$names, "original") <- orig_names - orig_select <- unique(orig(new_type$select)) - new_type$select <- unique(new_type$select) + # From the possible names apply the original function + new_type$select <- functions_names(orig(new_type$select), new_type$names) attr(new_type$select, "original") <- orig_select + class(new_type) <- c(t, "type", "list") vector[[t]] <- new_type } From 2c42af5a649d49384f45a97c49da7db77f6376c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 10:53:32 +0100 Subject: [PATCH 035/110] Add documentation for types --- R/types.R | 39 ++++++++++++++++++++++++++++++++++++ man/types.Rd | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 man/types.Rd diff --git a/R/types.R b/R/types.R index 85fdc0d4..30633870 100644 --- a/R/types.R +++ b/R/types.R @@ -53,17 +53,56 @@ type_helper <- function(x, select, type) { delay(out) } + +#' @rdname types +#' @name Types +#' @title Type specification +#' @description +#' Define how to select and extract data +#' @param x Character specifying the names or functions to select them. The functions will be applied on the data or the names. +#' @param select Character of `x` or functions to select on x (only on names or positional not on the data of the variable). +#' @returns An object of the same class as the function with two elements: names the content of x, and select. +#' @examples +#' datasets("A") +#' datasets("A") | datasets("B") +#' datasets(is.data.frame) +#' datasets("A") & variables(is.numeric) +NULL + + + +#' @describeIn types Specify datasets. #' @export datasets <- function(x, select = first) { type_helper(x, select, type = "datasets") } +#' @describeIn types Specify variables. #' @export variables <- function(x, select = first) { type_helper(x, select, type = "variables") } +#' @describeIn types Specify variables of MultiAssayExperiment col Data. +#' @export +mae_colData <- function(x, select = first) { + type_helper(x, select, type = "mae_colData") +} + +#' @describeIn types Specify variables of MultiAssayExperiment sampleMap. +#' @export +mae_sampleMap <- function(x, select = first) { + type_helper(x, select, type = "mae_sampleMap") +} + +#' @describeIn types Specify variables of MultiAssayExperiment experiments. +#' @export +mae_experiments <- function(x, select = first) { + type_helper(x, select, type = "mae_experiments") +} + +#' @describeIn types Specify values. #' @export values <- function(x, select = first) { type_helper(x, select, type = "values") diff --git a/man/types.Rd b/man/types.Rd new file mode 100644 index 00000000..731ae80f --- /dev/null +++ b/man/types.Rd @@ -0,0 +1,56 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/types.R +\name{Types} +\alias{Types} +\alias{datasets} +\alias{variables} +\alias{mae_colData} +\alias{mae_sampleMap} +\alias{mae_experiments} +\alias{values} +\title{Type specification} +\usage{ +datasets(x, select = first) + +variables(x, select = first) + +mae_colData(x, select = first) + +mae_sampleMap(x, select = first) + +mae_experiments(x, select = first) + +values(x, select = first) +} +\arguments{ +\item{x}{Character specifying the names or functions to select them. The functions will be applied on the data or the names.} + +\item{select}{Character of \code{x} or functions to select on x (only on names or positional not on the data of the variable).} +} +\value{ +An object of the same class as the function with two elements: names the content of x, and select. +} +\description{ +Define how to select and extract data +} +\section{Functions}{ +\itemize{ +\item \code{datasets()}: Specify datasets. + +\item \code{variables()}: Specify variables. + +\item \code{mae_colData()}: Specify variables of MultiAssayExperiment col Data. + +\item \code{mae_sampleMap()}: Specify variables of MultiAssayExperiment sampleMap. + +\item \code{mae_experiments()}: Specify variables of MultiAssayExperiment experiments. + +\item \code{values()}: Specify values. + +}} +\examples{ +datasets("A") +datasets("A") | datasets("B") +datasets(is.data.frame) +datasets("A") & variables(is.numeric) +} From 3cd91329135b668d8045dfef1224511573663156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 12:16:32 +0100 Subject: [PATCH 036/110] Better handling of types not resolved --- R/types.R | 6 +++++- man/update_spec.Rd | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/R/types.R b/R/types.R index 30633870..82697a33 100644 --- a/R/types.R +++ b/R/types.R @@ -207,7 +207,11 @@ c.type <- function(...) { attr(new_type$names, "original") <- orig_names # From the possible names apply the original function - new_type$select <- functions_names(orig(new_type$select), new_type$names) + if (any(vapply(l, is.delayed, logical(1L)))) { + new_type$select <- orig_select + } else { + new_type$select <- functions_names(orig(new_type$select), new_type$names) + } attr(new_type$select, "original") <- orig_select class(new_type) <- c(t, "type", "list") diff --git a/man/update_spec.Rd b/man/update_spec.Rd index a85cf2f9..3e10ddf8 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/resolver.R +% Please edit documentation in R/update_spec.R \name{update_spec} \alias{update_spec} \title{Update a specification} From b780107f6dae8bc55d6de507514bc6d1fe7093a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 12:16:57 +0100 Subject: [PATCH 037/110] Better handling of resolver --- R/resolver.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/resolver.R b/R/resolver.R index 1e3fe15d..ebd5e9a2 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -95,7 +95,10 @@ determine.transform <- function(type, data, ..., spec) { } functions_names <- function(unresolved, reference) { - stopifnot(is.character(reference)) # Allows for NA characters + stopifnot(is.character(reference) || is.factor(reference) || is.null(reference)) # Allows for NA characters + if (is.null(reference)) { + return(NULL) + } is_fc <- vapply(unresolved, is.function, logical(1L)) fc_unresolved <- unresolved[is_fc] x <- vector("character") From 9cfb6f8f54f4591d8534d80af13844095976725b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 12:17:59 +0100 Subject: [PATCH 038/110] Better handling of composing --- NAMESPACE | 19 +++++++++++++------ R/ops_transform.R | 3 +-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index c7f6c89d..e5e1254d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,16 +5,20 @@ S3method(Ops,type) S3method(anyNA,type) S3method(c,transform) S3method(c,type) +S3method(chooseOpsMethod,transform) S3method(data_extract_multiple_srv,FilteredData) S3method(data_extract_multiple_srv,list) S3method(data_extract_multiple_srv,reactive) S3method(data_extract_srv,FilteredData) S3method(data_extract_srv,list) -S3method(extract,MultiAssayExperiment) -S3method(extract,data.frame) -S3method(extract,default) -S3method(extract,matrix) -S3method(extract,qenv) +S3method(determine,datasets) +S3method(determine,default) +S3method(determine,mae_colData) +S3method(determine,mae_experiments) +S3method(determine,mae_sampleMap) +S3method(determine,transform) +S3method(determine,values) +S3method(determine,variables) S3method(filter_spec_internal,default) S3method(filter_spec_internal,delayed_data) S3method(is.delayed,default) @@ -61,7 +65,7 @@ export(data_extract_srv) export(data_extract_ui) export(datanames_input) export(datasets) -export(extract) +export(determine) export(filter_spec) export(first_choice) export(first_choices) @@ -77,6 +81,9 @@ export(is_single_dataset) export(last_choice) export(last_choices) export(list_extract_spec) +export(mae_colData) +export(mae_experiments) +export(mae_sampleMap) export(merge_datasets) export(merge_expression_module) export(merge_expression_srv) diff --git a/R/ops_transform.R b/R/ops_transform.R index 5a4ae2f4..2c9c17d8 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -89,8 +89,7 @@ nd_type <- function(e1, e2) { } else if (is.transform(e1) && is.transform(e2)){ out <- c(e1, e2) } else if (is.type(e1) && is.type(e2)) { - out <- list(e1, e2) - names(out) <- c(is(e1), is(e2)) + out <- c(e1, e2) } else { stop("Maybe we should decide how to apply a type to a list of transformers...") } From bad225d0115a24dc366c88142fd98665587565fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 12:18:50 +0100 Subject: [PATCH 039/110] Adding some other files --- man/determine.Rd | 21 +++++++++++++++++++++ man/resolver.Rd | 1 + tests/testthat/test-ops_transform.R | 3 ++- tests/testthat/test-resolver.R | 25 +++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 man/determine.Rd diff --git a/man/determine.Rd b/man/determine.Rd new file mode 100644 index 00000000..6d2b4fe9 --- /dev/null +++ b/man/determine.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resolver.R +\name{determine} +\alias{determine} +\title{A method that should take a type and resolve it.} +\usage{ +determine(type, data, ...) +} +\arguments{ +\item{type}{The specification to resolve.} + +\item{data}{The minimal data required.} +} +\value{ +A list with two elements, the type resolved and the data extracted. +} +\description{ +Generic that makes the minimal check on spec. +Responsible of subsetting/extract the data received and check that the type matches +} +\keyword{internal} diff --git a/man/resolver.Rd b/man/resolver.Rd index dd7d1f1d..170a6c86 100644 --- a/man/resolver.Rd +++ b/man/resolver.Rd @@ -16,6 +16,7 @@ A transform but resolved } \description{ Given the specification of some data to extract find if they are available or not. +The specification for selecting a variable shouldn't depend on the data of said variable. } \examples{ dataset1 <- datasets(is.data.frame) diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R index de9918a8..2a8fb0dc 100644 --- a/tests/testthat/test-ops_transform.R +++ b/tests/testthat/test-ops_transform.R @@ -89,11 +89,12 @@ test_that("datasets & variables & values create a single specification", { test_that("&(transform, number) errors", { expect_error(datasets("ABC2") & variables("ABC2") & values("abc") & 1) + expect_error(datasets("ABC2") & values("abc") & 1) }) test_that("| combines two transformers", { spec <- datasets("ABC") | datasets("abc") expect_length(spec, 2) - expect_error(spec[[1]]$datasets | spec[[1]]$datasets) + expect_true(is.null(names(spec))) }) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index e89a5340..eb5e3b43 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -139,3 +139,28 @@ test_that("update_spec resolves correctly", { expect_no_error(update_spec(datasets(x = c("df", "df2"), "df"), "datasets", "df2")) }) + +test_that("OR resolver invalidates subsequent specifications", { + td <- within(teal_data(), { + df <- data.frame(A = 1:5, B = LETTERS[1:5]) + m <- cbind(A = 1:5, B = 5:10) + }) + var_a <- variables("A") + df_a <- datasets(is.data.frame) & var_a + matrix_a <- datasets(is.matrix) & var_a + df_or_m_var_a <- df_a | matrix_a + out <- resolver(df_or_m_var_a, td) +}) + +test_that("OR update_spec filters specifications", { + td <- within(teal_data(), { + df <- data.frame(A = 1:5, B = LETTERS[1:5]) + m <- cbind(A = 1:5, B = 5:10) + }) + var_a <- variables("A") + df_a <- datasets(is.data.frame) & var_a + matrix_a <- datasets(is.matrix) & var_a + df_or_m_var_a <- df_a | matrix_a + resolved <- resolver(df_or_m_var_a, td) + out <- update_spec(resolved, "datasets","df") +}) From c4c8e407283249b13ddd22ce2ab7c223545de589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 15:06:56 +0100 Subject: [PATCH 040/110] Add update_spec --- R/update_spec.R | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 R/update_spec.R diff --git a/R/update_spec.R b/R/update_spec.R new file mode 100644 index 00000000..2b6f4e28 --- /dev/null +++ b/R/update_spec.R @@ -0,0 +1,76 @@ +#' Update a specification +#' +#' Once a selection is made update the specification for different valid selection. +#' @param spec A resolved specification such as one created with datasets and variables. +#' @param type Which type was updated? One of datasets, variables, values. +#' @param value What is the new selection? One that is a valid value for the given type and specification. +#' @return The specification with restored choices and selection if caused by the update. +#' @export +#' @examples +#' td <- within(teal.data::teal_data(), { +#' df <- data.frame(A = as.factor(letters[1:5]), +#' Ab = LETTERS[1:5]) +#' df_n <- data.frame(C = 1:5, +#' Ab = as.factor(letters[1:5])) +#' }) +#' data_frames_factors <- datasets(is.data.frame) & variables(is.factor) +#' res <- resolver(data_frames_factors, td) +#' update_spec(res, "datasets", "df_n") +#' # update_spec(res, "datasets", "error") +update_spec <- function(spec, type, value) { + if (!is.character(value)) { + stop("The updated value is not a character.", + "\nDo you attempt to set a new specification? Please open an issue.") + } + + if (is.transform(spec) && is.null(names(spec))) { + updated_spec <- lapply(spec, update_s_spec, type, value) + class(updated_spec) <- class(spec) + updated_spec + } + if (is.transform(spec) && !is.null(names(spec))) { + updated_spec <- update_s_spec(spec, type, value) + } else if (is.type(spec)) { + updated_spec <- update_s_spec(spec, is(spec), value) + } else { + stop("Multiple or no specification is possible.") + } + updated_spec +} + +update_s_spec <- function(spec, type, value) { + spec_types <- names(spec) + type <- match.arg(type, spec_types) + restart_types <- spec_types[seq_along(spec_types) > which(type == spec_types)] + + valid_names <- spec[[type]]$names + + if (!is.list(valid_names) && all(value %in% valid_names)) { + original_select <- orig(spec[[type]]$select) + spec[[type]][["select"]] <- value + attr(spec[[type]][["select"]], "original") <- original_select + } else if (!is.list(valid_names) && !all(value %in% valid_names)) { + original_select <- orig(spec[[type]]$select) + valid_values <- intersect(value, valid_names) + if (!length(valid_values)) { + stop("No valid value provided.") + } + spec[[type]][["select"]] <- valid_values + attr(spec[[type]][["select"]], "original") <- original_select + } else { + stop("It seems the specification needs to be resolved first.") + } + + # Restore to the original specs + for (type_restart in restart_types) { + + if (is.na(spec[[type_restart]])) { + next + } + fun <- match.fun(type_restart) + restored_transform <- fun(x = orig(spec[[type_restart]]$names), + select = orig(spec[[type_restart]]$select)) + spec[[type_restart]] <- restored_transform + } + spec +} From 635ea9bc20bf30438e2d7319e178abafcc64a64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 25 Mar 2025 17:11:11 +0100 Subject: [PATCH 041/110] Module for simple specifications --- R/module_input.R | 85 +++++++++++++++++++++++++++++++++++++++++++++++ R/ops_transform.R | 2 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 R/module_input.R diff --git a/R/module_input.R b/R/module_input.R new file mode 100644 index 00000000..45114f32 --- /dev/null +++ b/R/module_input.R @@ -0,0 +1,85 @@ + +helper_input <- function(id, + label, + multiple = FALSE) { + shiny::selectInput( + id, + label, + choices = NULL, + selected = NULL, + multiple = multiple) +} + +module_input_ui <- function(id, label, spec) { + ns <- NS(id) + input <- tagList( + a(label), + ) + l <- lapply(spec, function(x) { + helper_input(ns(is(x)), + paste("Select", is(x), collapse = " "), + multiple = is(x) != "datasets") + }) + input <- tagList(input, l) +} + +module_input_server <- function(id, spec, data) { + moduleServer(id, function(input, output, session) { + + react_updates <- reactive({ + if (!anyNA(spec) && is.delayed(spec)) { + spec <- teal.transform::resolver(spec, data()) + } + for (i in seq_along(input)) { + variable <- names(input)[i] + x <- input[[variable]] + spec_v <- spec[[variable]] + # a <- !is.null(x) && all(x %in% $names) + # browser(expr = !isFALSE(a) && !isTRUE(a)) + if (!is.null(x) && all(x %in% spec_v$names) && !x %in% spec_v$select) { + spec |> + update_spec(variable, input[[variable]]) |> + teal.transform::resolver(data()) + } + } + spec + }) + + observe({ + req(react_updates()) + spec <- react_updates() + for (i in seq_along(spec)) { + variable <- names(spec)[i] + + # Relies on order of arguments + if (is.delayed(spec[[variable]])) { + break + } + shiny::updateSelectInput( + session, + variable, + choices = unorig(spec[[variable]]$names), + selected = unorig(spec[[variable]]$select) + ) + # FIXME set on gray the input + # FIXME: Hide input field if any type on specification cannot be solved + } + }) + + + # Full selection #### + react_selection <- reactive({ + spec <- req(react_updates()) + req(!is.delayed(spec)) + selection <- vector("list", length(spec)) + names(selection) <- names(spec) + for (i in seq_along(spec)) { + variable <- names(spec)[i] + selection[[variable]] <- unorig(spec[[variable]]$select) + } + selection + }) + }) +} + + diff --git a/R/ops_transform.R b/R/ops_transform.R index 2c9c17d8..3f9df81c 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -93,7 +93,7 @@ nd_type <- function(e1, e2) { } else { stop("Maybe we should decide how to apply a type to a list of transformers...") } - class(out) <- c("transform", class(out)) + class(out) <- unique(c("transform", class(out))) out } From a30ec58094856cc429e47d1a254b3e33acf4958b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 26 Mar 2025 10:08:22 +0100 Subject: [PATCH 042/110] Update for multiple selections --- R/module_input.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/module_input.R b/R/module_input.R index 45114f32..4537c61e 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -36,8 +36,8 @@ module_input_server <- function(id, spec, data) { spec_v <- spec[[variable]] # a <- !is.null(x) && all(x %in% $names) # browser(expr = !isFALSE(a) && !isTRUE(a)) - if (!is.null(x) && all(x %in% spec_v$names) && !x %in% spec_v$select) { - spec |> + if (!is.null(x) && all(x %in% spec_v$names) && any(!x %in% spec_v$select)) { + spec <- spec |> update_spec(variable, input[[variable]]) |> teal.transform::resolver(data()) } From 3031edca5b5240f7e1226c730704e15eba67f5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 26 Mar 2025 10:09:23 +0100 Subject: [PATCH 043/110] Make sure a list of transformators work --- NAMESPACE | 1 + R/delayed.R | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index e5e1254d..303a30a4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -22,6 +22,7 @@ S3method(determine,variables) S3method(filter_spec_internal,default) S3method(filter_spec_internal,delayed_data) S3method(is.delayed,default) +S3method(is.delayed,list) S3method(is.delayed,transform) S3method(is.delayed,type) S3method(is.na,type) diff --git a/R/delayed.R b/R/delayed.R index d7416539..fb5f3ab9 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -14,9 +14,17 @@ is.delayed <- function(x) { #' @export #' @method is.delayed default is.delayed.default <- function(x) { + # FIXME: A warning? FALSE } +# Handling a list of transformers e1 | e2 +#' @export +#' @method is.delayed list +is.delayed.list <- function(x) { + any(vapply(x, is.delayed, logical(1L))) +} + #' @export #' @method is.delayed transform is.delayed.transform <- function(x) { From 81c3a9b84cf7b92ee7563a14a798aa5bb238e086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 31 Mar 2025 16:33:04 +0200 Subject: [PATCH 044/110] Allow lists (should be of transforms) --- R/resolver.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index ebd5e9a2..b390f4c3 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -38,7 +38,7 @@ resolver <- function(spec, data) { spec <- datasets(first) & spec } - stopifnot(is.transform(spec)) + stopifnot(is.list(spec) || is.transform(spec)) det <- determine(spec, data, spec = spec) det$type } @@ -53,7 +53,7 @@ resolver <- function(spec, data) { #' @keywords internal #' @export determine <- function(type, data, ...) { - stopifnot(is.type(type) || is.transform(type)) + stopifnot(is.type(type) || is.list(type) || is.transform(type)) if (!is.delayed(type)) { return(list(type = type, data = data)) } From d7a005bf2fb3bda96b247adf2a21b2e2a3f52b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 31 Mar 2025 16:33:58 +0200 Subject: [PATCH 045/110] Remove some comments --- R/module_input.R | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/R/module_input.R b/R/module_input.R index 4537c61e..e3225d00 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -34,8 +34,6 @@ module_input_server <- function(id, spec, data) { variable <- names(input)[i] x <- input[[variable]] spec_v <- spec[[variable]] - # a <- !is.null(x) && all(x %in% $names) - # browser(expr = !isFALSE(a) && !isTRUE(a)) if (!is.null(x) && all(x %in% spec_v$names) && any(!x %in% spec_v$select)) { spec <- spec |> update_spec(variable, input[[variable]]) |> @@ -61,7 +59,7 @@ module_input_server <- function(id, spec, data) { choices = unorig(spec[[variable]]$names), selected = unorig(spec[[variable]]$select) ) - # FIXME set on gray the input + # FIXME: set on gray the input # FIXME: Hide input field if any type on specification cannot be solved } }) From fa89e055f5f1354800850426759b99857ba0846f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 31 Mar 2025 16:34:37 +0200 Subject: [PATCH 046/110] Merging basic block --- R/merge_dataframes.R | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 R/merge_dataframes.R diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R new file mode 100644 index 00000000..863aaeeb --- /dev/null +++ b/R/merge_dataframes.R @@ -0,0 +1,56 @@ + +# self_merge(df1, df2) almost equal to self_merge(df2, df1): Only changes on the column order. +self_merging <- function(e1, e2, ids, type) { + # Get the name of the variables to use as suffix. + # If we need the name at higher environments (ie: f(self_merging()) ) it could use rlang (probably) + name1 <- deparse(substitute(e1)) + name2 <- deparse(substitute(e2)) + suffix1 <- paste0(".", name1) + suffix2 <- paste0(".", name2) + ce1 <- colnames(e1) + ce2 <- colnames(e2) + type <- match.arg(type, c("inner", "left", "right", "full")) + + # Called by its side effects of adding the two variables the the current environment + switch(type, + inner = {all.x = FALSE; all.y = FALSE}, + full = {all.x = TRUE; all.y = TRUE}, + left = {all.x = TRUE; all.y = FALSE}, + right = {all.x = FALSE; all.y = TRUE}, + {all.x = FALSE; all.y = FALSE} + ) + + if (!is.null(names(ids))) { + name_ids <- names(ids) + } else { + name_ids <- ids + } + + if (!all(ids %in% name_ids) && !all(ids %in% ce2)) { + stop("Not all ids are in both objects") + } + # The default generic should find the right method, if not we : + # a) ask for the method to be implemented or + # b) implement it ourselves here to be used internally. + mm <- merge(e1, e2, + all.x = all.x, all.y = all.y, + by.x = name_ids, by.y = ids, + suffixes = c(suffix1, suffix2)) + g <- grep(paste0("\\.[", "(", name1, ")|(", name2, ")]"), + colnames(mm)) + if (length(g)) { + mix_columns <- setdiff(intersect(ce1, ce2), ids) + for (column in mix_columns) { + mc1 <- paste0(mix_columns, suffix1) + mc2 <- paste0(mix_columns, suffix2) + + # Rename column and delete one if they are the same + if (identical(mm[, mc1], mm[, mc2])) { + mm[, mc2] <- NULL + colnames(mm)[mc1 == colnames(mm)] <- column + } + } + } + mm + +} From 0142b780d16b5e90f1727c3b2a9ddfce3c7bbcec Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 14:56:11 +0000 Subject: [PATCH 047/110] [skip style] [skip vbump] Restyle files --- R/delayed.R | 5 +-- R/merge_dataframes.R | 40 +++++++++++++------ R/module_input.R | 14 +++---- R/ops_transform.R | 26 +++++++------ R/resolver.R | 30 +++++++------- R/types.R | 34 ++++++++++------ R/update_spec.R | 29 ++++++++------ tests/testthat/test-resolver.R | 71 +++++++++++++++++++++++----------- tests/testthat/test-types.R | 17 +++++--- 9 files changed, 168 insertions(+), 98 deletions(-) diff --git a/R/delayed.R b/R/delayed.R index fb5f3ab9..8569aa18 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -34,18 +34,17 @@ is.delayed.transform <- function(x) { #' @export #' @method is.delayed type is.delayed.type <- function(x) { - if (!is.na(x)) { return(!all(is.character(x$names)) || !all(is.character(x$select))) } FALSE } -resolved <- function(x, type = is(x)){ +resolved <- function(x, type = is(x)) { s <- all(is.character(x$names)) && all(is.character(x$select)) if (!s && !all(x$select %in% x$names)) { - stop("Selected ", type, " not resolved.") + stop("Selected ", type, " not resolved.") } attr(x, "delayed") <- NULL x diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 863aaeeb..6b514ac9 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -1,4 +1,3 @@ - # self_merge(df1, df2) almost equal to self_merge(df2, df1): Only changes on the column order. self_merging <- function(e1, e2, ids, type) { # Get the name of the variables to use as suffix. @@ -13,11 +12,26 @@ self_merging <- function(e1, e2, ids, type) { # Called by its side effects of adding the two variables the the current environment switch(type, - inner = {all.x = FALSE; all.y = FALSE}, - full = {all.x = TRUE; all.y = TRUE}, - left = {all.x = TRUE; all.y = FALSE}, - right = {all.x = FALSE; all.y = TRUE}, - {all.x = FALSE; all.y = FALSE} + inner = { + all.x <- FALSE + all.y <- FALSE + }, + full = { + all.x <- TRUE + all.y <- TRUE + }, + left = { + all.x <- TRUE + all.y <- FALSE + }, + right = { + all.x <- FALSE + all.y <- TRUE + }, + { + all.x <- FALSE + all.y <- FALSE + } ) if (!is.null(names(ids))) { @@ -33,11 +47,14 @@ self_merging <- function(e1, e2, ids, type) { # a) ask for the method to be implemented or # b) implement it ourselves here to be used internally. mm <- merge(e1, e2, - all.x = all.x, all.y = all.y, - by.x = name_ids, by.y = ids, - suffixes = c(suffix1, suffix2)) - g <- grep(paste0("\\.[", "(", name1, ")|(", name2, ")]"), - colnames(mm)) + all.x = all.x, all.y = all.y, + by.x = name_ids, by.y = ids, + suffixes = c(suffix1, suffix2) + ) + g <- grep( + paste0("\\.[", "(", name1, ")|(", name2, ")]"), + colnames(mm) + ) if (length(g)) { mix_columns <- setdiff(intersect(ce1, ce2), ids) for (column in mix_columns) { @@ -52,5 +69,4 @@ self_merging <- function(e1, e2, ids, type) { } } mm - } diff --git a/R/module_input.R b/R/module_input.R index e3225d00..4d790422 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -1,4 +1,3 @@ - helper_input <- function(id, label, multiple = FALSE) { @@ -7,7 +6,8 @@ helper_input <- function(id, label, choices = NULL, selected = NULL, - multiple = multiple) + multiple = multiple + ) } module_input_ui <- function(id, label, spec) { @@ -17,15 +17,15 @@ module_input_ui <- function(id, label, spec) { ) l <- lapply(spec, function(x) { helper_input(ns(is(x)), - paste("Select", is(x), collapse = " "), - multiple = is(x) != "datasets") + paste("Select", is(x), collapse = " "), + multiple = is(x) != "datasets" + ) }) input <- tagList(input, l) } module_input_server <- function(id, spec, data) { moduleServer(id, function(input, output, session) { - react_updates <- reactive({ if (!anyNA(spec) && is.delayed(spec)) { spec <- teal.transform::resolver(spec, data()) @@ -34,7 +34,7 @@ module_input_server <- function(id, spec, data) { variable <- names(input)[i] x <- input[[variable]] spec_v <- spec[[variable]] - if (!is.null(x) && all(x %in% spec_v$names) && any(!x %in% spec_v$select)) { + if (!is.null(x) && all(x %in% spec_v$names) && any(!x %in% spec_v$select)) { spec <- spec |> update_spec(variable, input[[variable]]) |> teal.transform::resolver(data()) @@ -79,5 +79,3 @@ module_input_server <- function(id, spec, data) { }) }) } - - diff --git a/R/ops_transform.R b/R/ops_transform.R index 3f9df81c..4eb62408 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -2,16 +2,17 @@ Ops.transform <- function(e1, e2) { if (missing(e2)) { # out <- switch(.Generic, - # "!" = Negate, + # "!" = Negate, stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) # return(out) } switch(.Generic, - "!=" = NextMethod(), - "==" = NextMethod(), - "|" = or_transform(e1, e2), - "&" = nd_transform(e1, e2), - stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE)) + "!=" = NextMethod(), + "==" = NextMethod(), + "|" = or_transform(e1, e2), + "&" = nd_transform(e1, e2), + stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) + ) } #' @export @@ -23,11 +24,12 @@ Ops.type <- function(e1, e2) { # return(out) } out <- switch(.Generic, - "!=" = NextMethod(), - # "==" = NextMethod(), - "|" = or_type(e1, e2), - "&" = nd_type(e1, e2), - stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE)) + "!=" = NextMethod(), + # "==" = NextMethod(), + "|" = or_type(e1, e2), + "&" = nd_type(e1, e2), + stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) + ) out } @@ -86,7 +88,7 @@ nd_type <- function(e1, e2) { } else if (!is.transform(e1) && is.transform(e2)) { out <- c(e2, list(e1)) names(out)[length(out)] <- is(e1) - } else if (is.transform(e1) && is.transform(e2)){ + } else if (is.transform(e1) && is.transform(e2)) { out <- c(e1, e2) } else if (is.type(e1) && is.type(e2)) { out <- c(e1, e2) diff --git a/R/resolver.R b/R/resolver.R index b390f4c3..95742e4d 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -104,8 +104,7 @@ functions_names <- function(unresolved, reference) { x <- vector("character") for (f in fc_unresolved) { - - y <- tryCatch(f(reference), error = function(x) f ) + y <- tryCatch(f(reference), error = function(x) f) if (!is.logical(y)) { stop("Provided functions should return a logical object.") } @@ -118,7 +117,9 @@ functions_data <- function(unresolved, data, names) { stopifnot(!is.null(data)) # Must be something but not NULL fc_unresolved <- unresolved[vapply(unresolved, is.function, logical(1L))] l <- lapply(fc_unresolved, function(f) { - all_data <- tryCatch(f(data), error = function(x){FALSE}) + all_data <- tryCatch(f(data), error = function(x) { + FALSE + }) if (any(all_data)) { return(names[all_data]) } else { @@ -133,7 +134,11 @@ functions_data <- function(unresolved, data, names) { determine_helper <- function(type, data_names, data) { orig_names <- type$names orig_select <- type$select - names_variables_obj <- if (is.null(names(data))) { colnames(data)} else {names(data)} + names_variables_obj <- if (is.null(names(data))) { + colnames(data) + } else { + names(data) + } if (is.delayed(type) && all(is.character(type$names))) { match <- intersect(data_names, type$names) missing <- setdiff(type$names, data_names) @@ -158,8 +163,10 @@ determine_helper <- function(type, data_names, data) { } } else if (is.delayed(type)) { old_names <- type$names - new_names <- c(functions_names(type$names, names_variables_obj), - functions_data(type$names, data, data_names)) + new_names <- c( + functions_names(type$names, names_variables_obj), + functions_data(type$names, data, data_names) + ) new_names <- unique(new_names[!is.na(new_names)]) if (!length(new_names)) { return(NULL) @@ -195,7 +202,7 @@ determine.datasets <- function(type, data, ...) { } l <- vector("list", length(data)) - for (i in seq_along(data)){ + for (i in seq_along(data)) { data_name_env <- names(data)[i] out <- determine_helper(type, data_name_env, data[[data_name_env]]) if (!is.null(out)) { @@ -229,7 +236,7 @@ determine.variables <- function(type, data, ...) { # Assumes the object has colnames method (true for major object classes: DataFrame, tibble, Matrix, array) # FIXME: What happens if colnames is null: array(dim = c(4, 2)) |> colnames() l <- vector("list", ncol(data)) - for (i in seq_len(ncol(data))){ + for (i in seq_len(ncol(data))) { out <- determine_helper(type, colnames(data)[i], data[, i]) if (!is.null(out)) { l[[i]] <- out @@ -255,7 +262,7 @@ determine.mae_colData <- function(type, data, ...) { } new_data <- colData(data) - for (i in seq_along(new_data)){ + for (i in seq_along(new_data)) { determine_helper(type, colnames(data)[i], new_data[, i]) } if (length(dim(new_data)) != 2L) { @@ -273,7 +280,6 @@ determine.mae_colData <- function(type, data, ...) { if (length(type$select) > 1) { list(type = type, data = data[type$select]) - } else { list(type = type, data = data[[type$select]]) } @@ -293,12 +299,11 @@ determine.mae_experiments <- function(type, data, ...) { if (!is.delayed(type) && length(type$select) > 1) { list(type = type, data = new_data[type$select]) - } else if (!is.delayed(type) && length(type$select) == 1) { list(type = type, data = new_data[[type$select]]) } else { return(list(type = type, data = NULL)) - } + } } #' @export @@ -317,7 +322,6 @@ determine.mae_sampleMap <- function(type, data, ...) { if (length(type$select) > 1) { list(type = type, data = data[type$select]) - } else { list(type = type, data = data[[type$select]]) } diff --git a/R/types.R b/R/types.R index 82697a33..6b8da509 100644 --- a/R/types.R +++ b/R/types.R @@ -23,7 +23,7 @@ anyNA.type <- function(x, recursive = FALSE) { anyNA(unclass(x[c("names", "select")]), recursive) } -first <- function(x){ +first <- function(x) { if (length(x) > 0) { false <- rep(FALSE, length.out = length(x)) false[1] <- TRUE @@ -34,12 +34,16 @@ first <- function(x){ check_input <- function(input) { is.character(input) || is.function(input) || - (is.list(input) && all(vapply(input, function(x){is.function(x) || is.character(x)}, logical(1L)))) + (is.list(input) && all(vapply(input, function(x) { + is.function(x) || is.character(x) + }, logical(1L)))) } type_helper <- function(x, select, type) { - stopifnot("Invalid options" = check_input(x), - "Invalid selection" = check_input(type)) + stopifnot( + "Invalid options" = check_input(x), + "Invalid selection" = check_input(type) + ) if (is.function(x)) { x <- list(x) } @@ -156,7 +160,8 @@ c.transform <- function(...) { old_select <- new_type$select new_type$names <- c(old_names, l[[i]][[t]][["names"]]) attr(new_type$names, "original") <- c(orig( - old_names), orig(l[[i]][[t]][["names"]])) + old_names + ), orig(l[[i]][[t]][["names"]])) new_type$select <- c(old_select, l[[i]][[t]][["select"]]) attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][[t]][["select"]])) } @@ -197,7 +202,8 @@ c.type <- function(...) { old_select <- new_type$select new_type$names <- c(old_names, l[[i]][["names"]]) attr(new_type$names, "original") <- c(orig( - old_names), orig(l[[i]][["names"]])) + old_names + ), orig(l[[i]][["names"]])) new_type$select <- unique(c(old_select, l[[i]][["select"]])) attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][["select"]])) } @@ -246,11 +252,14 @@ print.type <- function(x, ...) { nam_values <- length(x$names) - sum(nam_functions) if (any(nam_functions)) { msg_values <- paste0(msg_values, sum(nam_functions), " functions for possible choices.", - collapse = "\n") + collapse = "\n" + ) } if (nam_values) { msg_values <- paste0(msg_values, paste0(sQuote(x$names[!nam_functions]), collapse = ", "), - " as possible choices.", collapse = "\n") + " as possible choices.", + collapse = "\n" + ) } sel_list <- is.list(x$select) @@ -264,12 +273,15 @@ print.type <- function(x, ...) { sel_values <- length(x$select) - sum(sel_functions) if (any(sel_functions)) { msg_sel <- paste0(msg_sel, sum(sel_functions), " functions to select.", - collapse = "\n") + collapse = "\n" + ) } if (sel_values) { msg_sel <- paste0(msg_sel, paste0(sQuote(x$select[!sel_functions]), collapse = ", "), - " selected.", collapse = "\n") + " selected.", + collapse = "\n" + ) } - cat(msg_values, msg_sel) + cat(msg_values, msg_sel) return(x) } diff --git a/R/update_spec.R b/R/update_spec.R index 2b6f4e28..24083a8b 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -8,10 +8,14 @@ #' @export #' @examples #' td <- within(teal.data::teal_data(), { -#' df <- data.frame(A = as.factor(letters[1:5]), -#' Ab = LETTERS[1:5]) -#' df_n <- data.frame(C = 1:5, -#' Ab = as.factor(letters[1:5])) +#' df <- data.frame( +#' A = as.factor(letters[1:5]), +#' Ab = LETTERS[1:5] +#' ) +#' df_n <- data.frame( +#' C = 1:5, +#' Ab = as.factor(letters[1:5]) +#' ) #' }) #' data_frames_factors <- datasets(is.data.frame) & variables(is.factor) #' res <- resolver(data_frames_factors, td) @@ -19,8 +23,10 @@ #' # update_spec(res, "datasets", "error") update_spec <- function(spec, type, value) { if (!is.character(value)) { - stop("The updated value is not a character.", - "\nDo you attempt to set a new specification? Please open an issue.") + stop( + "The updated value is not a character.", + "\nDo you attempt to set a new specification? Please open an issue." + ) } if (is.transform(spec) && is.null(names(spec))) { @@ -30,10 +36,10 @@ update_spec <- function(spec, type, value) { } if (is.transform(spec) && !is.null(names(spec))) { updated_spec <- update_s_spec(spec, type, value) - } else if (is.type(spec)) { + } else if (is.type(spec)) { updated_spec <- update_s_spec(spec, is(spec), value) } else { - stop("Multiple or no specification is possible.") + stop("Multiple or no specification is possible.") } updated_spec } @@ -63,13 +69,14 @@ update_s_spec <- function(spec, type, value) { # Restore to the original specs for (type_restart in restart_types) { - if (is.na(spec[[type_restart]])) { next } fun <- match.fun(type_restart) - restored_transform <- fun(x = orig(spec[[type_restart]]$names), - select = orig(spec[[type_restart]]$select)) + restored_transform <- fun( + x = orig(spec[[type_restart]]$names), + select = orig(spec[[type_restart]]$select) + ) spec[[type_restart]] <- restored_transform } spec diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index eb5e3b43..b00b722d 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -1,4 +1,6 @@ -f <- function(x){head(x, 1)} +f <- function(x) { + head(x, 1) +} test_that("resolver datasets works", { df_head <- datasets("df", f) @@ -7,7 +9,7 @@ test_that("resolver datasets works", { df_mean <- datasets("df", mean) median_mean <- datasets(median, mean) td <- within(teal.data::teal_data(), { - df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) + df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) m <- cbind(b = 1:5, c = 10:14) m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) @@ -25,10 +27,14 @@ test_that("resolver variables works", { data_frames <- datasets(is.data.frame) var_a <- variables("a") factors <- variables(is.factor) - factors_head <- variables(is.factor, function(x){head(x, 1)}) - var_matrices_head <- variables(is.matrix, function(x){head(x, 1)}) + factors_head <- variables(is.factor, function(x) { + head(x, 1) + }) + var_matrices_head <- variables(is.matrix, function(x) { + head(x, 1) + }) td <- within(teal.data::teal_data(), { - df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) + df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) m <- cbind(b = 1:5, c = 10:14) m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) @@ -56,11 +62,15 @@ test_that("resolver values works", { data_frames <- datasets(is.data.frame) var_a <- variables("a") factors <- variables(is.factor) - factors_head <- variables(is.factor, function(x){head(x, 1)}) - var_matrices_head <- variables(is.matrix, function(x){head(x, 1)}) + factors_head <- variables(is.factor, function(x) { + head(x, 1) + }) + var_matrices_head <- variables(is.matrix, function(x) { + head(x, 1) + }) val_A <- values("A") td <- within(teal.data::teal_data(), { - df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) + df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) m <- cbind(b = 1:5, c = 10:14) m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) @@ -69,39 +79,54 @@ test_that("resolver values works", { test_that("names and variables are reported", { td <- within(teal.data::teal_data(), { - df <- data.frame(A = as.factor(letters[1:5]), - Ab = LETTERS[1:5], - Abc = c(LETTERS[1:4], letters[1])) - df2 <- data.frame(A = 1:5, - B = 1:5) + df <- data.frame( + A = as.factor(letters[1:5]), + Ab = LETTERS[1:5], + Abc = c(LETTERS[1:4], letters[1]) + ) + df2 <- data.frame( + A = 1:5, + B = 1:5 + ) m <- matrix() }) d_df <- datasets("df") - df_upper_variables <- d_df & variables(function(x){x==toupper(x)}) + df_upper_variables <- d_df & variables(function(x) { + x == toupper(x) + }) out <- resolver(df_upper_variables, td) # This should select A and Ab: # A because the name is all capital letters and # Ab values is all upper case. expect_length(out$variables$names, 2) - v_all_upper <- variables(function(x){all(x==toupper(x))}) + v_all_upper <- variables(function(x) { + all(x == toupper(x)) + }) df_all_upper_variables <- d_df & v_all_upper expect_no_error(out <- resolver(df_all_upper_variables, td)) expect_length(out$variables$names, 1) expect_no_error(out <- resolver(datasets("df2") & v_all_upper, td)) expect_length(out$variables$names, 2) - expect_no_error(out <- resolver(datasets(function(x){is.data.frame(x) && all(colnames(x) == toupper(colnames(x)))}), td)) + expect_no_error(out <- resolver(datasets(function(x) { + is.data.frame(x) && all(colnames(x) == toupper(colnames(x))) + }), td)) expect_length(out$datasets$names, 1) - expect_no_error(out <- resolver(datasets(is.data.frame) & datasets(function(x){colnames(x) == toupper(colnames(x))}), td)) + expect_no_error(out <- resolver(datasets(is.data.frame) & datasets(function(x) { + colnames(x) == toupper(colnames(x)) + }), td)) expect_length(out$datasets$names, 2) - }) test_that("update_spec resolves correctly", { td <- within(teal.data::teal_data(), { - df <- data.frame(A = as.factor(letters[1:5]), - Ab = LETTERS[1:5]) - df_n <- data.frame(C = 1:5, - Ab = as.factor(letters[1:5])) + df <- data.frame( + A = as.factor(letters[1:5]), + Ab = LETTERS[1:5] + ) + df_n <- data.frame( + C = 1:5, + Ab = as.factor(letters[1:5]) + ) }) data_frames_factors <- datasets(is.data.frame) & variables(is.factor) expect_false(is.null(attr(data_frames_factors$datasets$names, "original"))) @@ -162,5 +187,5 @@ test_that("OR update_spec filters specifications", { matrix_a <- datasets(is.matrix) & var_a df_or_m_var_a <- df_a | matrix_a resolved <- resolver(df_or_m_var_a, td) - out <- update_spec(resolved, "datasets","df") + out <- update_spec(resolved, "datasets", "df") }) diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index 2b1ae404..7c3dbdc8 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -1,5 +1,4 @@ test_that("datasets", { - expect_no_error(dataset0 <- datasets("df", "df")) out <- list(names = "df", select = "df") class(out) <- c("delayed", "datasets", "type", "list") @@ -16,8 +15,12 @@ test_that("variables", { expect_no_error(var1 <- variables("a")) expect_no_error(var2 <- variables(is.factor)) # Allowed to specify whatever we like, it is not until resolution that this raises errors - expect_no_error(var3 <- variables(is.factor, function(x){head(x, 1)})) - expect_no_error(var4 <- variables(is.matrix, function(x){head(x, 1)})) + expect_no_error(var3 <- variables(is.factor, function(x) { + head(x, 1) + })) + expect_no_error(var4 <- variables(is.matrix, function(x) { + head(x, 1) + })) }) test_that("raw combine of types", { @@ -31,6 +34,10 @@ test_that("values", { expect_no_error(val1 <- values("a")) expect_no_error(val2 <- values(is.factor)) # Allowed to specify whatever we like, it is not until resolution that this raises errors - expect_no_error(val3 <- values(is.factor, function(x){head(x, 1)})) - expect_no_error(val4 <- values(is.matrix, function(x){head(x, 1)})) + expect_no_error(val3 <- values(is.factor, function(x) { + head(x, 1) + })) + expect_no_error(val4 <- values(is.matrix, function(x) { + head(x, 1) + })) }) From 521fd09460dc4eb57f21bd5b022cee59f311804a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 14:59:26 +0000 Subject: [PATCH 048/110] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/update_spec.Rd | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/man/update_spec.Rd b/man/update_spec.Rd index 3e10ddf8..708e0909 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -21,10 +21,14 @@ Once a selection is made update the specification for different valid selection. } \examples{ td <- within(teal.data::teal_data(), { - df <- data.frame(A = as.factor(letters[1:5]), - Ab = LETTERS[1:5]) - df_n <- data.frame(C = 1:5, - Ab = as.factor(letters[1:5])) + df <- data.frame( + A = as.factor(letters[1:5]), + Ab = LETTERS[1:5] + ) + df_n <- data.frame( + C = 1:5, + Ab = as.factor(letters[1:5]) + ) }) data_frames_factors <- datasets(is.data.frame) & variables(is.factor) res <- resolver(data_frames_factors, td) From 54058d3eb678f2c782ba67f5dbf6f5bcc163c969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 2 Apr 2025 16:19:40 +0200 Subject: [PATCH 049/110] Rename to make it easier to read --- R/resolver.R | 92 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index b390f4c3..24853766 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -3,7 +3,6 @@ #' Given the specification of some data to extract find if they are available or not. #' The specification for selecting a variable shouldn't depend on the data of said variable. #' @param spec A object extraction specification. -#' @param data A `qenv()`, or `teal.data::teal_data()` object. #' #' @returns A transform but resolved #' @export @@ -29,6 +28,7 @@ resolver <- function(spec, data) { if (!is.delayed(spec)) { return(spec) } + # Adding some default specifications if they are missing if ("values" %in% names(spec) && !"variables" %in% names(spec)) { spec <- variables(first) & spec @@ -54,8 +54,10 @@ resolver <- function(spec, data) { #' @export determine <- function(type, data, ...) { stopifnot(is.type(type) || is.list(type) || is.transform(type)) - if (!is.delayed(type)) { - return(list(type = type, data = data)) + if (!is.delayed(type) && length(type$select) > 1L) { + return(list(type = type, data = data[unorig(type$select)])) + } else if (!is.delayed(type) && length(type$select) == 1L) { + return(list(type = type, data = data[[unorig(type$select)]])) } UseMethod("determine") } @@ -65,7 +67,7 @@ determine.default <- function(type, data, ..., spec) { if (!is.null(names(spec)) && is.delayed(spec)) { rt <- determine(spec, data) } else { - rt <- lapply(spec, resolver, data = data, spec = spec) + rt <- lapply(spec, resolver, data = data) if (length(rt) == 1) { return(rt[[1]]) } @@ -82,55 +84,63 @@ determine.transform <- function(type, data, ..., spec) { return(specs) } + d <- data for (i in seq_along(type)) { - di <- determine(type[[i]], data, spec = spec) - # orverwrite so that next type in line receives the corresponding data and specification + di <- determine(type[[i]], d, spec = spec) + # overwrite so that next type in line receives the corresponding data and specification if (is.null(di$type)) { next } type[[i]] <- di$type - data <- di$data + d <- di$data } list(type = type, data = data) # It is the transform object resolved. } -functions_names <- function(unresolved, reference) { - stopifnot(is.character(reference) || is.factor(reference) || is.null(reference)) # Allows for NA characters - if (is.null(reference)) { +functions_names <- function(spec_criteria, names) { + stopifnot(is.character(names) || is.factor(names) || is.null(names)) # Allows for NA characters + if (is.null(names)) { return(NULL) } - is_fc <- vapply(unresolved, is.function, logical(1L)) - fc_unresolved <- unresolved[is_fc] - x <- vector("character") - - for (f in fc_unresolved) { + is_fc <- vapply(spec_criteria, is.function, logical(1L)) + functions <- spec_criteria[is_fc] + new_names <- vector("character") - y <- tryCatch(f(reference), error = function(x) f ) - if (!is.logical(y)) { + for (fun in functions) { + names_ok <- tryCatch(fun(names), error = function(new_names) FALSE ) + if (!is.logical(names_ok)) { stop("Provided functions should return a logical object.") } - x <- c(x, reference[y[!is.na(y)]]) + if (any(names_ok)) { + new_names <- c(new_names, names[names_ok]) + } } - unique(unlist(c(unresolved[!is_fc], x), FALSE, FALSE)) + old_names <- unique(unlist(spec_criteria[!is_fc], FALSE, FALSE)) + c(new_names, old_names) } -functions_data <- function(unresolved, data, names) { +# Extract data and evaluate if the function +functions_data <- function(spec_criteria, data, names_data) { stopifnot(!is.null(data)) # Must be something but not NULL - fc_unresolved <- unresolved[vapply(unresolved, is.function, logical(1L))] - l <- lapply(fc_unresolved, function(f) { - all_data <- tryCatch(f(data), error = function(x){FALSE}) - if (any(all_data)) { - return(names[all_data]) + is_fc <- vapply(spec_criteria, is.function, logical(1L)) + functions <- spec_criteria[is_fc] + + l <- lapply(functions, function(fun) { + data_ok <- tryCatch(fun(data), error = function(x){FALSE}) + if (any(data_ok)) { + return(names_data[data_ok]) } else { return(NULL) } }) - unique(unlist(l, FALSE, FALSE)) + new_names <- unique(unlist(l, FALSE, FALSE)) + c(new_names, spec_criteria[!is_fc]) } # Checks that for the given type and data names and data it can be resolved # The workhorse of the resolver determine_helper <- function(type, data_names, data) { + stopifnot(!is.null(type)) orig_names <- type$names orig_select <- type$select names_variables_obj <- if (is.null(names(data))) { colnames(data)} else {names(data)} @@ -160,7 +170,7 @@ determine_helper <- function(type, data_names, data) { old_names <- type$names new_names <- c(functions_names(type$names, names_variables_obj), functions_data(type$names, data, data_names)) - new_names <- unique(new_names[!is.na(new_names)]) + new_names <- unlist(unique(new_names[!is.na(new_names)]), use.names = FALSE) if (!length(new_names)) { return(NULL) # stop("No ", is(type), " meet the requirements") @@ -194,23 +204,33 @@ determine.datasets <- function(type, data, ...) { stop("Please use qenv() or teal_data() objects.") } - l <- vector("list", length(data)) - for (i in seq_along(data)){ + l <- vector("list", length(names(data))) + # Somehow in some cases (I didn't explore much this was TRUE) + for (i in seq_along(l)){ data_name_env <- names(data)[i] - out <- determine_helper(type, data_name_env, data[[data_name_env]]) + out <- determine_helper(type, data_name_env, extract(data, data_name_env)) if (!is.null(out)) { l[[i]] <- out } } # Merge together all the types - type <- do.call(c, l[lengths(l) > 1]) + type <- do.call(c, l[lengths(l) > 1L]) + # FIXME: This combine the different selections too. + # Instead it should apply once the select function and keep that instead. + # helper_something(type$select, type$names, data) # Not possible to know what is happening - - if (!is.delayed(type) && length(type$select) > 1) { - list(type = type, data = data[type$select]) - } else if (!is.delayed(type) && length(type$select) == 1) { - list(type = type, data = data[[type$select]]) + for (name in type$names){ + data_name_env <- names(data)[i] + out <- determine_helper(type, data_name_env, extract(data, data_name_env)) + if (!is.null(out)) { + l[[i]] <- out + } + } + if (!is.delayed(type) && length(type$select) > 1L) { + list(type = type, data = data[unorig(type$select)]) + } else if (!is.delayed(type) && length(type$select) == 1L) { + list(type = type, data = data[[unorig(type$select)]]) } else { list(type = type, data = NULL) } From 957e59d3a7706455e1a7426a496cd5bd73594067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 2 Apr 2025 16:20:12 +0200 Subject: [PATCH 050/110] Minor tweaks --- R/types.R | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/R/types.R b/R/types.R index 82697a33..7f3d8884 100644 --- a/R/types.R +++ b/R/types.R @@ -25,7 +25,7 @@ anyNA.type <- function(x, recursive = FALSE) { first <- function(x){ if (length(x) > 0) { - false <- rep(FALSE, length.out = length(x)) + false <- rep_len(FALSE, length.out = length(x)) false[1] <- TRUE return(false) } @@ -207,11 +207,10 @@ c.type <- function(...) { attr(new_type$names, "original") <- orig_names # From the possible names apply the original function - if (any(vapply(l, is.delayed, logical(1L)))) { - new_type$select <- orig_select - } else { + if (is.delayed(new_type)) { new_type$select <- functions_names(orig(new_type$select), new_type$names) } + attr(new_type$select, "original") <- orig_select class(new_type) <- c(t, "type", "list") From b440e3fc9c5bada7d894b35452bdb89f1afd1639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 2 Apr 2025 16:21:12 +0200 Subject: [PATCH 051/110] Merging for multiple data.frames --- R/merge_dataframes.R | 53 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 863aaeeb..1499d118 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -1,3 +1,40 @@ +# Allows merging arbitrary number of data.frames by ids and type +merging <- function(..., ids, type) { + number_merges <- ...length() - 1L + stopifnot( + "Number of datasets is enough" = number_merges >= 1L, + "Number of arguments for ids matches data" = (length(ids) == number_merges && is.list(ids)) || length(ids) == 1L && is.character(ids), + "Number of arguments for type matches data" = length(type) == number_merges || length(type) == 1L) + list_df <- list(...) + + if (length(type) == 1L) { + type <- rep(type, number_merges) + } + if (length(ids) == 1L) { + ids <- rep(ids, number_merges) + } + + if (number_merges == 1L) { + return(self_merging(..1, ..2, ids = ids, type = type)) + } + + # l <- list("a", "b", "c", "d") + # number_merges <- length(l) - 1L + m <- list() + for (merge_i in seq_len(number_merges)) { + message(merge_i) + if (merge_i == 1L) { + out <- self_merging(list_df[[merge_i]], list_df[[merge_i + 1L]], + ids[[merge_i]], type = type[[merge_i]]) + } else { + out <- self_merging(out, list_df[[merge_i + 1L]], + ids[[merge_i]], type = type[[merge_i]]) + } + } + out +} + + # self_merge(df1, df2) almost equal to self_merge(df2, df1): Only changes on the column order. self_merging <- function(e1, e2, ids, type) { @@ -35,19 +72,21 @@ self_merging <- function(e1, e2, ids, type) { mm <- merge(e1, e2, all.x = all.x, all.y = all.y, by.x = name_ids, by.y = ids, - suffixes = c(suffix1, suffix2)) - g <- grep(paste0("\\.[", "(", name1, ")|(", name2, ")]"), - colnames(mm)) + suffixes = c(".e1", ".e2")) + g <- grep("\\.[(e1)(e2)]", colnames(mm)) if (length(g)) { mix_columns <- setdiff(intersect(ce1, ce2), ids) for (column in mix_columns) { - mc1 <- paste0(mix_columns, suffix1) - mc2 <- paste0(mix_columns, suffix2) - + mc1 <- paste0(column, ".e1") + mc2 <- paste0(column, ".e2") # Rename column and delete one if they are the same if (identical(mm[, mc1], mm[, mc2])) { mm[, mc2] <- NULL - colnames(mm)[mc1 == colnames(mm)] <- column + colnames(mm)[colnames(mm) %in% mc1] <- column + } else { + # Rename to keep the suffic of the data names + colnames(mm)[colnames(mm) %in% mc1] <- paste0(column, suffix1) + colnames(mm)[colnames(mm) %in% mc2] <- paste0(column, suffix2) } } } From 516c434b257219d028f4f977d3dbdb5d5bd4ec71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 2 Apr 2025 16:21:36 +0200 Subject: [PATCH 052/110] Add back the extract methods --- NAMESPACE | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 303a30a4..cb2b8038 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,6 +19,7 @@ S3method(determine,mae_sampleMap) S3method(determine,transform) S3method(determine,values) S3method(determine,variables) +S3method(extract,default) S3method(filter_spec_internal,default) S3method(filter_spec_internal,delayed_data) S3method(is.delayed,default) @@ -67,6 +68,7 @@ export(data_extract_ui) export(datanames_input) export(datasets) export(determine) +export(extract) export(filter_spec) export(first_choice) export(first_choices) From e82f500dff6d6dbcf8a4c4166723b191d5803ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 2 Apr 2025 16:23:02 +0200 Subject: [PATCH 053/110] Add Method and the default (to be able to expand) TODO: Remove comments to activate some --- R/extract.R | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 R/extract.R diff --git a/R/extract.R b/R/extract.R new file mode 100644 index 00000000..617000ca --- /dev/null +++ b/R/extract.R @@ -0,0 +1,53 @@ +#' Internal method to extract data from different objects +#' +#' Required to resolve a specification into something usable (by comparing with the existing data). +#' Required by merging data based on a resolved specification. +#' @export +#' @noRd +#' @keywords internal +extract <- function(x, variable, ...) { + UseMethod("extract") +} + +# Cases handled by the default method +# @export +# extract.MultiAssayExperiment <- function(x, variable) { +# # if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { +# # stop("Required to have MultiAssayExperiment's package.") +# # } +# x[, variable, drop = TRUE] +# } +# +# @export +# extract.DataFrame <- function(x, variable) { +# # if (!requireNamespace("S4Vectors", quietly = TRUE)) { +# # stop("Required to have S4Vectors's package.") +# # } +# x[, variable, drop = TRUE] +# } +# +# @export +# extract.matrix <- function(x, variable) { +# x[, variable, drop = TRUE] +# } + +#' @export +extract.default <- function(x, variable) { + if (length(dim(x)) == 2L) { + x[, variable, drop = TRUE] + } else { + x[[variable]] + } +} + +# @export +# @method extract data.frame +# extract.data.frame <- function(x, variable) { +# # length(variable) == 1L +# x[, variable, drop = TRUE] +# } + +# @export +# extract.qenv <- function(x, variable) { +# x[[variable]] +# } From d4d826ebde2ba865061f1d18a7dc3aef6bd44a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 4 Apr 2025 16:53:05 +0200 Subject: [PATCH 054/110] Extract multiple columns --- R/extract.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/extract.R b/R/extract.R index 617000ca..1624c01b 100644 --- a/R/extract.R +++ b/R/extract.R @@ -32,9 +32,9 @@ extract <- function(x, variable, ...) { # } #' @export -extract.default <- function(x, variable) { - if (length(dim(x)) == 2L) { - x[, variable, drop = TRUE] +extract.default <- function(x, variable, drop = TRUE) { + if (length(dim(x)) == 2L || length(variable) > 1L) { + x[, variable, drop = drop] } else { x[[variable]] } From 56fa80803f296e99b285c8c2aff42d6324e4edbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 4 Apr 2025 16:54:19 +0200 Subject: [PATCH 055/110] Improve naming and fix some bugs --- R/resolver.R | 88 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index 24853766..73243699 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -64,6 +64,7 @@ determine <- function(type, data, ...) { #' @export determine.default <- function(type, data, ..., spec) { + # Used when the type is of class list. if (!is.null(names(spec)) && is.delayed(spec)) { rt <- determine(spec, data) } else { @@ -107,7 +108,12 @@ functions_names <- function(spec_criteria, names) { new_names <- vector("character") for (fun in functions) { - names_ok <- tryCatch(fun(names), error = function(new_names) FALSE ) + names_ok <- tryCatch(fun(names), + error = function(x){FALSE}, + warning = function(x){ + if (isTRUE(x) || isFALSE(x)){ + x + } else {FALSE}} ) if (!is.logical(names_ok)) { stop("Provided functions should return a logical object.") } @@ -119,18 +125,26 @@ functions_names <- function(spec_criteria, names) { c(new_names, old_names) } -# Extract data and evaluate if the function -functions_data <- function(spec_criteria, data, names_data) { - stopifnot(!is.null(data)) # Must be something but not NULL +# Evaluate if the function applied to the data +# but we need to return the name of the data received +functions_data <- function(spec_criteria, names_data, data) { + stopifnot(!is.null(data), + length(names_data) == 1L) # Must be something but not NULL is_fc <- vapply(spec_criteria, is.function, logical(1L)) functions <- spec_criteria[is_fc] l <- lapply(functions, function(fun) { - data_ok <- tryCatch(fun(data), error = function(x){FALSE}) - if (any(data_ok)) { - return(names_data[data_ok]) - } else { - return(NULL) + data_ok <- tryCatch(fun(data), + error = function(x){FALSE}, + warning = function(x){ + if (isTRUE(x) || isFALSE(x)){ + x + } else {FALSE}}) + if (!is.logical(data_ok)) { + stop("Provided functions should return a logical object.") + } + if ((length(data_ok) == 1L && any(data_ok)) || all(data_ok)) { + return(names_data) } }) new_names <- unique(unlist(l, FALSE, FALSE)) @@ -143,19 +157,13 @@ determine_helper <- function(type, data_names, data) { stopifnot(!is.null(type)) orig_names <- type$names orig_select <- type$select - names_variables_obj <- if (is.null(names(data))) { colnames(data)} else {names(data)} if (is.delayed(type) && all(is.character(type$names))) { match <- intersect(data_names, type$names) - missing <- setdiff(type$names, data_names) - if (length(missing)) { - return(NULL) - # stop("Missing datasets ", paste(sQuote(missing), collapse = ", "), " were specified.") - } type$names <- match if (length(match) == 0) { return(NULL) # stop("No selected ", is(type), " matching the conditions requested") - } else if (length(match) == 1) { + } else if (length(match) == 1L) { type$select <- match } else { new_select <- functions_names(type$select, type$names) @@ -168,8 +176,8 @@ determine_helper <- function(type, data_names, data) { } } else if (is.delayed(type)) { old_names <- type$names - new_names <- c(functions_names(type$names, names_variables_obj), - functions_data(type$names, data, data_names)) + new_names <- c(functions_names(type$names, data_names), + functions_data(type$names, data_names, data)) new_names <- unlist(unique(new_names[!is.na(new_names)]), use.names = FALSE) if (!length(new_names)) { return(NULL) @@ -184,7 +192,8 @@ determine_helper <- function(type, data_names, data) { type$select <- type$names } - new_select <- functions_names(type$select, type$names) + new_select <- c(functions_names(type$select, type$names), + functions_data(type$select, type$names, data)) new_select <- unique(new_select[!is.na(new_select)]) if (!length(new_select)) { @@ -216,17 +225,9 @@ determine.datasets <- function(type, data, ...) { # Merge together all the types type <- do.call(c, l[lengths(l) > 1L]) - # FIXME: This combine the different selections too. - # Instead it should apply once the select function and keep that instead. - # helper_something(type$select, type$names, data) - # Not possible to know what is happening - for (name in type$names){ - data_name_env <- names(data)[i] - out <- determine_helper(type, data_name_env, extract(data, data_name_env)) - if (!is.null(out)) { - l[[i]] <- out - } - } + # Evaluate the selection based on all possible choices. + type <- eval_type_select(type, data) + if (!is.delayed(type) && length(type$select) > 1L) { list(type = type, data = data[unorig(type$select)]) } else if (!is.delayed(type) && length(type$select) == 1L) { @@ -258,6 +259,8 @@ determine.variables <- function(type, data, ...) { # Merge together all the types type <- do.call(c, l[lengths(l) > 1]) + # Check the selected values as they got appended. + type <- eval_type_select(type, data) # Not possible to know what is happening if (is.delayed(type)) { @@ -276,7 +279,7 @@ determine.mae_colData <- function(type, data, ...) { new_data <- colData(data) for (i in seq_along(new_data)){ - determine_helper(type, colnames(data)[i], new_data[, i]) + determine_helper(type, colnames(new_data)[i], new_data[, i]) } if (length(dim(new_data)) != 2L) { stop("Can't resolve variables from this object of class ", class(new_data)) @@ -284,7 +287,7 @@ determine.mae_colData <- function(type, data, ...) { if (ncol(new_data) <= 0L) { stop("Can't pull variable: No variable is available.") } - type <- determine_helper(type, colnames(data), data) + type <- determine_helper(type, colnames(new_data), new_data) # Not possible to know what is happening if (is.delayed(type)) { @@ -363,3 +366,24 @@ unorig <- function(x) { attr(x, "original") <- NULL x } + + +eval_type_select <- function(type, data) { + l <- vector("list", length(type$names)) + names(l) <- type$names + orig_select <- orig(type$select) + for (name in type$names){ + out <- functions_data(orig_select, name, extract(data, name)) + if (!is.null(out)) { + l[[name]] <- unlist(out) + } + } + + new_select <- c(functions_names(orig(type$select), type$names), + unlist(l, FALSE, FALSE)) + + new_select <- unique(new_select) + attr(new_select, "original") <- orig_select + type$select <- new_select + type +} From bcbf30443ec6f27cc86cee506bb5d12307a65c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 4 Apr 2025 16:55:12 +0200 Subject: [PATCH 056/110] Handle bare types --- R/update_spec.R | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/R/update_spec.R b/R/update_spec.R index 2b6f4e28..c9a9b9e5 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -23,22 +23,34 @@ update_spec <- function(spec, type, value) { "\nDo you attempt to set a new specification? Please open an issue.") } - if (is.transform(spec) && is.null(names(spec))) { + if (!is.transform(spec) || !is.list(spec) && !is.type(spec)) { + stop("Unexpected object used as specification") + } + + if (is.null(names(spec))) { updated_spec <- lapply(spec, update_s_spec, type, value) class(updated_spec) <- class(spec) - updated_spec + return(updated_spec) } - if (is.transform(spec) && !is.null(names(spec))) { + if (!is.null(names(spec))) { updated_spec <- update_s_spec(spec, type, value) } else if (is.type(spec)) { updated_spec <- update_s_spec(spec, is(spec), value) - } else { - stop("Multiple or no specification is possible.") } updated_spec } update_s_spec <- function(spec, type, value) { + + if (is.type(spec)) { + l <- list(spec) + names(l) <- is(spec) + out <- update_s_spec(l, type, value) + return(out[[is(spec)]]) + } + + + spec_types <- names(spec) type <- match.arg(type, spec_types) restart_types <- spec_types[seq_along(spec_types) > which(type == spec_types)] From dabf7310ecefb018f58b31ae3de8ef31b73003ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 4 Apr 2025 16:55:41 +0200 Subject: [PATCH 057/110] Add some checks on the input --- R/module_input.R | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/R/module_input.R b/R/module_input.R index e3225d00..a0c8656f 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -24,20 +24,27 @@ module_input_ui <- function(id, label, spec) { } module_input_server <- function(id, spec, data) { + stopifnot(is.transform(spec)) + stopifnot(is.reactive(data)) + stopifnot(is.character(id)) moduleServer(id, function(input, output, session) { - react_updates <- reactive({ + d <- data() if (!anyNA(spec) && is.delayed(spec)) { - spec <- teal.transform::resolver(spec, data()) + spec <- teal.transform::resolver(spec, d) } - for (i in seq_along(input)) { + for (i in seq_along(names(input))) { variable <- names(input)[i] x <- input[[variable]] spec_v <- spec[[variable]] - if (!is.null(x) && all(x %in% spec_v$names) && any(!x %in% spec_v$select)) { + # resolved <- !is.character(spec_v$names) && all(x %in% spec_v$names) && any(!x %in% spec_v$select) + + if (!is.null(x) && any(nzchar(x))) { spec <- spec |> - update_spec(variable, input[[variable]]) |> - teal.transform::resolver(data()) + update_spec(variable, x) |> + resolver(d) + } else { + spec <- resolver(spec, d) } } spec From 78a8e1f4654e86f54ccc699134dbd28447a03413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 4 Apr 2025 17:25:14 +0200 Subject: [PATCH 058/110] Merging features --- R/merge_dataframes.R | 77 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 1499d118..25de52f2 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -1,34 +1,85 @@ +merge_module_ui <- function(id) { + ns <- NS(id) + renderText(ns("a")) +} + +merge_module_srv <- function(id, data = data, input_list = input_list, ids, type) { + stopifnot(is.list(input_list)) + stopifnot(is.reactive(data)) + stopifnot(is.character(id)) + moduleServer(id, function(input, output, session) { + out <- reactive({ + input_data <- lapply(input_list, extract_input, data = data) + merging(input_data, ids, type) + }) + output$out <- out + }) +} + +extract_input <- function(input, data) { + for (i in input) { + # Extract data recursively: only works on lists and square objects (no MAE or similar) + # To work on new classes implement an extract.class method + # Assumes order of extraction on the input: qenv > datasets > variables + # IF datasetes > variables > qenv order + data <- extract(data, i, drop = FALSE) + } + data +} + # Allows merging arbitrary number of data.frames by ids and type + merging <- function(..., ids, type) { - number_merges <- ...length() - 1L + input_as_list <- is.list(..1) & ...length() == 1L + if (input_as_list) { + list_df <- ..1 + } else { + list_df <- list(...) + } + number_merges <- length(list_df) - 1L stopifnot( "Number of datasets is enough" = number_merges >= 1L, - "Number of arguments for ids matches data" = (length(ids) == number_merges && is.list(ids)) || length(ids) == 1L && is.character(ids), - "Number of arguments for type matches data" = length(type) == number_merges || length(type) == 1L) - list_df <- list(...) + "Number of arguments for type matches data" = length(type) == number_merges || length(type) == 1L + ) - if (length(type) == 1L) { + if (!missing(ids)) { + stopifnot("Number of arguments for ids matches data" = !(is.list(ids) && length(ids) == number_merges)) + } + if (length(type) != number_merges) { type <- rep(type, number_merges) } - if (length(ids) == 1L) { + if (!missing(ids) && length(ids) != number_merges) { ids <- rep(ids, number_merges) } - if (number_merges == 1L) { + if (number_merges == 1L && !input_as_list && !missing(ids)) { return(self_merging(..1, ..2, ids = ids, type = type)) + } else if (number_merges == 1L && !input_as_list && missing(ids)) { + return(self_merging(..1, ..2, type = type)) + } else if (number_merges == 1L && input_as_list && missing(ids)) { + return(self_merging(list_df[[1]], list_df[[2]], type = type)) + } else if (number_merges == 1L && input_as_list && !missing(ids)) { + return(self_merging(list_df[[1]], list_df[[2]], ids = ids, type = type)) } - # l <- list("a", "b", "c", "d") - # number_merges <- length(l) - 1L - m <- list() for (merge_i in seq_len(number_merges)) { message(merge_i) if (merge_i == 1L) { + if (missing(ids)) { + ids <- intersect(colnames(list_df[[merge_i]]), colnames(list_df[[merge_i + 1L]])) + } else { + ids <- ids[[merge_i]] + } out <- self_merging(list_df[[merge_i]], list_df[[merge_i + 1L]], - ids[[merge_i]], type = type[[merge_i]]) + ids, type = type[[merge_i]]) } else { + if (missing(ids)) { + ids <- intersect(colnames(out, colnames(list_df[[merge_i + 1L]]))) + } else { + ids <- ids[[merge_i]] + } out <- self_merging(out, list_df[[merge_i + 1L]], - ids[[merge_i]], type = type[[merge_i]]) + ids, type = type[[merge_i]]) } } out @@ -37,7 +88,7 @@ merging <- function(..., ids, type) { # self_merge(df1, df2) almost equal to self_merge(df2, df1): Only changes on the column order. -self_merging <- function(e1, e2, ids, type) { +self_merging <- function(e1, e2, ids = intersect(colnames(e1), colnames(e2)), type) { # Get the name of the variables to use as suffix. # If we need the name at higher environments (ie: f(self_merging()) ) it could use rlang (probably) name1 <- deparse(substitute(e1)) From ee357b072b2ece56e78d15c5bc63bb96e531d328 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:44:56 +0000 Subject: [PATCH 059/110] [skip style] [skip vbump] Restyle files --- R/merge_dataframes.R | 17 +++++++----- R/module_input.R | 2 +- R/resolver.R | 62 +++++++++++++++++++++++++++++--------------- R/update_spec.R | 1 - 4 files changed, 53 insertions(+), 29 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 514cbfd8..b0d175f5 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -39,7 +39,7 @@ merging <- function(..., ids, type) { number_merges <- length(list_df) - 1L stopifnot( "Number of datasets is enough" = number_merges >= 1L, - "Number of arguments for type matches data" = length(type) == number_merges || length(type) == 1L + "Number of arguments for type matches data" = length(type) == number_merges || length(type) == 1L ) if (!missing(ids)) { @@ -71,7 +71,9 @@ merging <- function(..., ids, type) { ids <- ids[[merge_i]] } out <- self_merging(list_df[[merge_i]], list_df[[merge_i + 1L]], - ids, type = type[[merge_i]]) + ids, + type = type[[merge_i]] + ) } else { if (missing(ids)) { ids <- intersect(colnames(out, colnames(list_df[[merge_i + 1L]]))) @@ -79,7 +81,9 @@ merging <- function(..., ids, type) { ids <- ids[[merge_i]] } out <- self_merging(out, list_df[[merge_i + 1L]], - ids, type = type[[merge_i]]) + ids, + type = type[[merge_i]] + ) } } out @@ -135,9 +139,10 @@ self_merging <- function(e1, e2, ids = intersect(colnames(e1), colnames(e2)), ty # a) ask for the method to be implemented or # b) implement it ourselves here to be used internally. mm <- merge(e1, e2, - all.x = all.x, all.y = all.y, - by.x = name_ids, by.y = ids, - suffixes = c(".e1", ".e2")) + all.x = all.x, all.y = all.y, + by.x = name_ids, by.y = ids, + suffixes = c(".e1", ".e2") + ) g <- grep("\\.[(e1)(e2)]", colnames(mm)) if (length(g)) { mix_columns <- setdiff(intersect(ce1, ce2), ids) diff --git a/R/module_input.R b/R/module_input.R index 85c5f8ee..f632d891 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -40,7 +40,7 @@ module_input_server <- function(id, spec, data) { spec_v <- spec[[variable]] # resolved <- !is.character(spec_v$names) && all(x %in% spec_v$names) && any(!x %in% spec_v$select) - if (!is.null(x) && any(nzchar(x))) { + if (!is.null(x) && any(nzchar(x))) { spec <- spec |> update_spec(variable, x) |> resolver(d) diff --git a/R/resolver.R b/R/resolver.R index 64b19763..77f096a4 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -109,11 +109,17 @@ functions_names <- function(spec_criteria, names) { for (fun in functions) { names_ok <- tryCatch(fun(names), - error = function(x){FALSE}, - warning = function(x){ - if (isTRUE(x) || isFALSE(x)){ - x - } else {FALSE}} ) + error = function(x) { + FALSE + }, + warning = function(x) { + if (isTRUE(x) || isFALSE(x)) { + x + } else { + FALSE + } + } + ) if (!is.logical(names_ok)) { stop("Provided functions should return a logical object.") } @@ -128,18 +134,26 @@ functions_names <- function(spec_criteria, names) { # Evaluate if the function applied to the data # but we need to return the name of the data received functions_data <- function(spec_criteria, names_data, data) { - stopifnot(!is.null(data), - length(names_data) == 1L) # Must be something but not NULL + stopifnot( + !is.null(data), + length(names_data) == 1L + ) # Must be something but not NULL is_fc <- vapply(spec_criteria, is.function, logical(1L)) functions <- spec_criteria[is_fc] l <- lapply(functions, function(fun) { data_ok <- tryCatch(fun(data), - error = function(x){FALSE}, - warning = function(x){ - if (isTRUE(x) || isFALSE(x)){ - x - } else {FALSE}}) + error = function(x) { + FALSE + }, + warning = function(x) { + if (isTRUE(x) || isFALSE(x)) { + x + } else { + FALSE + } + } + ) if (!is.logical(data_ok)) { stop("Provided functions should return a logical object.") } @@ -179,9 +193,11 @@ determine_helper <- function(type, data_names, data) { old_names <- type$names new_names <- c( functions_names(type$names, data_names), - functions_data(type$names, data_names, data)) + functions_data(type$names, data_names, data) + ) new_names <- unlist(unique(new_names[!is.na(new_names)]), - use.names = FALSE) + use.names = FALSE + ) if (!length(new_names)) { return(NULL) # stop("No ", is(type), " meet the requirements") @@ -195,8 +211,10 @@ determine_helper <- function(type, data_names, data) { type$select <- type$names } - new_select <- c(functions_names(type$select, type$names), - functions_data(type$select, type$names, data)) + new_select <- c( + functions_names(type$select, type$names), + functions_data(type$select, type$names, data) + ) new_select <- unique(new_select[!is.na(new_select)]) if (!length(new_select)) { @@ -218,7 +236,7 @@ determine.datasets <- function(type, data, ...) { l <- vector("list", length(names(data))) # Somehow in some cases (I didn't explore much this was TRUE) - for (i in seq_along(l)){ + for (i in seq_along(l)) { data_name_env <- names(data)[i] out <- determine_helper(type, data_name_env, extract(data, data_name_env)) if (!is.null(out)) { @@ -281,7 +299,7 @@ determine.mae_colData <- function(type, data, ...) { } new_data <- colData(data) - for (i in seq_along(new_data)){ + for (i in seq_along(new_data)) { type <- determine_helper(type, colnames(new_data)[i], new_data[, i]) } if (length(dim(new_data)) != 2L) { @@ -372,15 +390,17 @@ eval_type_select <- function(type, data) { l <- vector("list", length(type$names)) names(l) <- type$names orig_select <- orig(type$select) - for (name in type$names){ + for (name in type$names) { out <- functions_data(orig_select, name, extract(data, name)) if (!is.null(out)) { l[[name]] <- unlist(out) } } - new_select <- c(functions_names(orig(type$select), type$names), - unlist(l, FALSE, FALSE)) + new_select <- c( + functions_names(orig(type$select), type$names), + unlist(l, FALSE, FALSE) + ) new_select <- unique(new_select) attr(new_select, "original") <- orig_select diff --git a/R/update_spec.R b/R/update_spec.R index 89616843..7c280db5 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -47,7 +47,6 @@ update_spec <- function(spec, type, value) { } update_s_spec <- function(spec, type, value) { - if (is.type(spec)) { l <- list(spec) names(l) <- is(spec) From b266116874678f1b4537bdd0086564fed3c8fa0a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:49:07 +0000 Subject: [PATCH 060/110] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/resolver.Rd | 2 -- 1 file changed, 2 deletions(-) diff --git a/man/resolver.Rd b/man/resolver.Rd index 170a6c86..14e2f520 100644 --- a/man/resolver.Rd +++ b/man/resolver.Rd @@ -8,8 +8,6 @@ resolver(spec, data) } \arguments{ \item{spec}{A object extraction specification.} - -\item{data}{A \code{qenv()}, or \code{teal.data::teal_data()} object.} } \value{ A transform but resolved From 8a2fcb6ff0ba55f2ad502b941d3d384e81226f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 7 Apr 2025 11:28:32 +0200 Subject: [PATCH 061/110] Instead of a list make us of ... --- R/merge_dataframes.R | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 514cbfd8..c5b2f32f 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -3,16 +3,17 @@ merge_module_ui <- function(id) { renderText(ns("a")) } -merge_module_srv <- function(id, data = data, input_list = input_list, ids, type) { - stopifnot(is.list(input_list)) - stopifnot(is.reactive(data)) +merge_module_srv <- function(id, ..., data, ids, type) { + # stopifnot(is.reactive(data)) stopifnot(is.character(id)) moduleServer(id, function(input, output, session) { out <- reactive({ + input_list = list(...) input_data <- lapply(input_list, extract_input, data = data) - merging(input_data, ids, type) + merging(input_data, ids = ids, type = type) }) output$out <- out + out }) } From b03e0d910edc8db32420e2fb16f625969857ab6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 7 Apr 2025 17:11:23 +0200 Subject: [PATCH 062/110] Solve most issues --- NAMESPACE | 1 + R/extract.R | 2 +- R/ops_transform.R | 6 +++++- R/resolver.R | 16 +++++++++++++--- R/types.R | 4 ++++ R/update_spec.R | 5 +++-- man/resolver.Rd | 2 +- tests/testthat/test-delayed.R | 1 - tests/testthat/test-ops_transform.R | 16 ++++++++-------- tests/testthat/test-resolver.R | 20 +++++++++++--------- tests/testthat/test-types.R | 10 +++++----- 11 files changed, 52 insertions(+), 31 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index cb2b8038..6f5acd46 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -105,3 +105,4 @@ export(variables) import(shiny) importFrom(dplyr,"%>%") importFrom(lifecycle,badge) +importFrom(methods,is) diff --git a/R/extract.R b/R/extract.R index 1624c01b..a49795f9 100644 --- a/R/extract.R +++ b/R/extract.R @@ -32,7 +32,7 @@ extract <- function(x, variable, ...) { # } #' @export -extract.default <- function(x, variable, drop = TRUE) { +extract.default <- function(x, variable, ..., drop = TRUE) { if (length(dim(x)) == 2L || length(variable) > 1L) { x[, variable, drop = drop] } else { diff --git a/R/ops_transform.R b/R/ops_transform.R index 4eb62408..540f2ee9 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -92,6 +92,10 @@ nd_type <- function(e1, e2) { out <- c(e1, e2) } else if (is.type(e1) && is.type(e2)) { out <- c(e1, e2) + } else if (or.transform(e1) && is.type(e2) ){ + out <- lapply(e1, nd_type, e2 = e2) + } else if (or.transform(e2) && is.type(e1) ){ + out <- lapply(e2, nd_type, e2 = e1) } else { stop("Maybe we should decide how to apply a type to a list of transformers...") } @@ -104,7 +108,7 @@ or_type <- function(e1, e2) { if (substitute) { out <- e1 e1[[is(e2)]] <- e2 - return(add_type(out, e1)) + return(nd_type(out, e1)) } list(e1, e2) } diff --git a/R/resolver.R b/R/resolver.R index 64b19763..53065a24 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -3,6 +3,7 @@ #' Given the specification of some data to extract find if they are available or not. #' The specification for selecting a variable shouldn't depend on the data of said variable. #' @param spec A object extraction specification. +#' @param data The qenv where the specification is evaluated. #' #' @returns A transform but resolved #' @export @@ -74,6 +75,7 @@ determine.default <- function(type, data, ..., spec) { } } rt + } #' @export @@ -109,7 +111,7 @@ functions_names <- function(spec_criteria, names) { for (fun in functions) { names_ok <- tryCatch(fun(names), - error = function(x){FALSE}, + error = function(x){x}, warning = function(x){ if (isTRUE(x) || isFALSE(x)){ x @@ -135,7 +137,7 @@ functions_data <- function(spec_criteria, names_data, data) { l <- lapply(functions, function(fun) { data_ok <- tryCatch(fun(data), - error = function(x){FALSE}, + error = function(x){x}, warning = function(x){ if (isTRUE(x) || isFALSE(x)){ x @@ -143,7 +145,7 @@ functions_data <- function(spec_criteria, names_data, data) { if (!is.logical(data_ok)) { stop("Provided functions should return a logical object.") } - if ((length(data_ok) == 1L && any(data_ok)) || all(data_ok)) { + if ((length(data_ok) == 1L && (any(data_ok)) || all(data_ok))) { return(names_data) } }) @@ -231,6 +233,10 @@ determine.datasets <- function(type, data, ...) { # Evaluate the selection based on all possible choices. type <- eval_type_select(type, data) + if (is.null(type$names)) { + stop("No datasets meet the specification.", call. = FALSE) + } + if (!is.delayed(type) && length(type$select) > 1L) { list(type = type, data = data[unorig(type$select)]) } else if (!is.delayed(type) && length(type$select) == 1L) { @@ -265,6 +271,10 @@ determine.variables <- function(type, data, ...) { # Check the selected values as they got appended. type <- eval_type_select(type, data) + if (is.null(type$names)) { + stop("No variables meet the specification.", call. = FALSE) + } + # Not possible to know what is happening if (is.delayed(type)) { return(list(type = type, data = NULL)) diff --git a/R/types.R b/R/types.R index 045c74a9..65855e5a 100644 --- a/R/types.R +++ b/R/types.R @@ -2,6 +2,10 @@ is.transform <- function(x) { inherits(x, "transform") } +or.transform <- function(x) { + is.list(x) && all(vapply(x, function(x){is.transform(x) || is.type(x)}, logical(1L))) +} + na_type <- function(type) { out <- NA_character_ class(out) <- c(type, "type") diff --git a/R/update_spec.R b/R/update_spec.R index 89616843..04cb5f9d 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -29,12 +29,12 @@ update_spec <- function(spec, type, value) { ) } - if (!is.transform(spec) || !is.list(spec) && !is.type(spec)) { + if (!((is.type(spec) || is.transform(spec)) || or.transform(spec))) { stop("Unexpected object used as specification") } if (is.null(names(spec))) { - updated_spec <- lapply(spec, update_s_spec, type, value) + updated_spec <- lapply(spec, update_s_spec, type = type, value = value) class(updated_spec) <- class(spec) return(updated_spec) } @@ -46,6 +46,7 @@ update_spec <- function(spec, type, value) { updated_spec } +#' @importFrom methods is update_s_spec <- function(spec, type, value) { if (is.type(spec)) { diff --git a/man/resolver.Rd b/man/resolver.Rd index 170a6c86..9a05f1c9 100644 --- a/man/resolver.Rd +++ b/man/resolver.Rd @@ -9,7 +9,7 @@ resolver(spec, data) \arguments{ \item{spec}{A object extraction specification.} -\item{data}{A \code{qenv()}, or \code{teal.data::teal_data()} object.} +\item{data}{The qenv where the specification is evaluated.} } \value{ A transform but resolved diff --git a/tests/testthat/test-delayed.R b/tests/testthat/test-delayed.R index 7a4eb07e..a6efe359 100644 --- a/tests/testthat/test-delayed.R +++ b/tests/testthat/test-delayed.R @@ -9,5 +9,4 @@ test_that("is.delayed works", { expect_false(is.delayed(1)) da <- datasets(is.data.frame) expect_true(is.delayed(da)) - expect_true(is.delayed(da$datasets)) }) diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R index 2a8fb0dc..e712ee25 100644 --- a/tests/testthat/test-ops_transform.R +++ b/tests/testthat/test-ops_transform.R @@ -4,26 +4,26 @@ basic_ops <- function(fun) { types <- type1 & type1 out <- list(names = "ABC", select = list(first)) class(out) <- c(fun, "type", "list") - expect_equal(types[[fun]], out, check.attributes = FALSE) + expect_equal(types, out, check.attributes = FALSE) type2 <- FUN("ABC2") types <- type1 & type2 out <- list(names = c("ABC", "ABC2"), select = list(first)) class(out) <- c("delayed", fun, "type", "list") - expect_equal(types[[fun]], out, check.attributes = FALSE) - expect_equal(types[[fun]]$names, c("ABC", "ABC2"), check.attributes = FALSE) + expect_equal(types, out, check.attributes = FALSE) + expect_equal(types$names, c("ABC", "ABC2"), check.attributes = FALSE) types2 <- types & type2 - expect_equal(types[[fun]]$names, c("ABC", "ABC2"), check.attributes = FALSE) - expect_s3_class(types[[fun]], class(out)) + expect_equal(types$names, c("ABC", "ABC2"), check.attributes = FALSE) + expect_s3_class(types, class(out)) type3 <- FUN("ABC2", select = all_choices) types <- type1 & type3 - expect_length(types[[fun]]$select, 2) + expect_length(types$select, 2) type2b <- FUN(first_choice) type2c <- FUN(last_choice) out <- type2b & type2c - expect_length(out[[fun]]$names, 2) + expect_length(out$names, 2) expect_error(FUN("ABC") & 1) out <- type1 & type2b - expect_true(is.list(out[[fun]]$names)) + expect_true(is.list(out$names)) } test_that("datasets & work", { diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index b00b722d..a8e4cd64 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -3,7 +3,7 @@ f <- function(x) { } test_that("resolver datasets works", { - df_head <- datasets("df", f) + df_head <- datasets("df") df_first <- datasets("df") matrices <- datasets(is.matrix) df_mean <- datasets("df", mean) @@ -16,7 +16,7 @@ test_that("resolver datasets works", { expect_no_error(resolver(df_head, td)) expect_no_error(resolver(df_first, td)) out <- resolver(matrices, td) - expect_length(out$datasets$select, 1L) # Because we use first + expect_length(out$select, 1L) # Because we use first expect_no_error(resolver(df_mean, td)) expect_error(resolver(median_mean, td)) }) @@ -104,17 +104,17 @@ test_that("names and variables are reported", { }) df_all_upper_variables <- d_df & v_all_upper expect_no_error(out <- resolver(df_all_upper_variables, td)) - expect_length(out$variables$names, 1) + expect_length(out$variables$names, 2L) expect_no_error(out <- resolver(datasets("df2") & v_all_upper, td)) - expect_length(out$variables$names, 2) + expect_length(out$variables$names, 2L) expect_no_error(out <- resolver(datasets(function(x) { is.data.frame(x) && all(colnames(x) == toupper(colnames(x))) }), td)) - expect_length(out$datasets$names, 1) + expect_length(out$names, 1L) expect_no_error(out <- resolver(datasets(is.data.frame) & datasets(function(x) { colnames(x) == toupper(colnames(x)) }), td)) - expect_length(out$datasets$names, 2) + expect_length(out$names, 2L) }) test_that("update_spec resolves correctly", { @@ -166,7 +166,7 @@ test_that("update_spec resolves correctly", { test_that("OR resolver invalidates subsequent specifications", { - td <- within(teal_data(), { + td <- within(teal.data::teal_data(), { df <- data.frame(A = 1:5, B = LETTERS[1:5]) m <- cbind(A = 1:5, B = 5:10) }) @@ -175,10 +175,11 @@ test_that("OR resolver invalidates subsequent specifications", { matrix_a <- datasets(is.matrix) & var_a df_or_m_var_a <- df_a | matrix_a out <- resolver(df_or_m_var_a, td) + expect_false(is.null(out)) }) test_that("OR update_spec filters specifications", { - td <- within(teal_data(), { + td <- within(teal.data::teal_data(), { df <- data.frame(A = 1:5, B = LETTERS[1:5]) m <- cbind(A = 1:5, B = 5:10) }) @@ -187,5 +188,6 @@ test_that("OR update_spec filters specifications", { matrix_a <- datasets(is.matrix) & var_a df_or_m_var_a <- df_a | matrix_a resolved <- resolver(df_or_m_var_a, td) - out <- update_spec(resolved, "datasets", "df") + # The second option is not possible to have it as df + expect_error(update_spec(resolved, "datasets", "df")) }) diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index 7c3dbdc8..3a64cee2 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -2,11 +2,11 @@ test_that("datasets", { expect_no_error(dataset0 <- datasets("df", "df")) out <- list(names = "df", select = "df") class(out) <- c("delayed", "datasets", "type", "list") - expect_equal(dataset0[["datasets"]], out, check.attributes = FALSE) + expect_equal(dataset0, out, check.attributes = FALSE) expect_no_error(dataset1 <- datasets("df")) - expect_true(is(dataset1$datasets$names, "vector")) + expect_true(is(dataset1$names, "vector")) expect_no_error(dataset2 <- datasets(is.matrix)) - expect_true(is(dataset2$datasets$names, "vector")) + expect_true(is(dataset2$names, "vector")) expect_no_error(dataset3 <- datasets(is.data.frame)) }) @@ -25,8 +25,8 @@ test_that("variables", { test_that("raw combine of types", { out <- c(datasets("df"), variables("df")) - expect_length(out, 3) - expect_error(c(datasets("df"), variables("df"), values("df"))) + expect_length(out, 2L) + expect_no_error(c(datasets("df"), variables("df"), values("df"))) }) test_that("values", { From a19ed318ae48be9ec75e8a058e005958f06d960c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:15:34 +0000 Subject: [PATCH 063/110] [skip style] [skip vbump] Restyle files --- R/merge_dataframes.R | 2 +- R/ops_transform.R | 4 ++-- R/resolver.R | 33 ++++++++++++++++++++++----------- R/types.R | 4 +++- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 56e29d61..4eeb87b8 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -8,7 +8,7 @@ merge_module_srv <- function(id, ..., data, ids, type) { stopifnot(is.character(id)) moduleServer(id, function(input, output, session) { out <- reactive({ - input_list = list(...) + input_list <- list(...) input_data <- lapply(input_list, extract_input, data = data) merging(input_data, ids = ids, type = type) }) diff --git a/R/ops_transform.R b/R/ops_transform.R index 540f2ee9..58ca87bb 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -92,9 +92,9 @@ nd_type <- function(e1, e2) { out <- c(e1, e2) } else if (is.type(e1) && is.type(e2)) { out <- c(e1, e2) - } else if (or.transform(e1) && is.type(e2) ){ + } else if (or.transform(e1) && is.type(e2)) { out <- lapply(e1, nd_type, e2 = e2) - } else if (or.transform(e2) && is.type(e1) ){ + } else if (or.transform(e2) && is.type(e1)) { out <- lapply(e2, nd_type, e2 = e1) } else { stop("Maybe we should decide how to apply a type to a list of transformers...") diff --git a/R/resolver.R b/R/resolver.R index 79063795..5b4eb38e 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -75,7 +75,6 @@ determine.default <- function(type, data, ..., spec) { } } rt - } #' @export @@ -111,11 +110,17 @@ functions_names <- function(spec_criteria, names) { for (fun in functions) { names_ok <- tryCatch(fun(names), - error = function(x){x}, - warning = function(x){ - if (isTRUE(x) || isFALSE(x)){ - x - } else {FALSE}} ) + error = function(x) { + x + }, + warning = function(x) { + if (isTRUE(x) || isFALSE(x)) { + x + } else { + FALSE + } + } + ) if (!is.logical(names_ok)) { stop("Provided functions should return a logical object.") } @@ -139,11 +144,17 @@ functions_data <- function(spec_criteria, names_data, data) { l <- lapply(functions, function(fun) { data_ok <- tryCatch(fun(data), - error = function(x){x}, - warning = function(x){ - if (isTRUE(x) || isFALSE(x)){ - x - } else {FALSE}}) + error = function(x) { + x + }, + warning = function(x) { + if (isTRUE(x) || isFALSE(x)) { + x + } else { + FALSE + } + } + ) if (!is.logical(data_ok)) { stop("Provided functions should return a logical object.") } diff --git a/R/types.R b/R/types.R index 65855e5a..1cc058ea 100644 --- a/R/types.R +++ b/R/types.R @@ -3,7 +3,9 @@ is.transform <- function(x) { } or.transform <- function(x) { - is.list(x) && all(vapply(x, function(x){is.transform(x) || is.type(x)}, logical(1L))) + is.list(x) && all(vapply(x, function(x) { + is.transform(x) || is.type(x) + }, logical(1L))) } na_type <- function(type) { From f146b4fb4df18b23eedaa0b1693bca11a9629a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 8 Apr 2025 17:09:46 +0200 Subject: [PATCH 064/110] Fix some error/warnings --- R/delayed.R | 9 +++++++++ R/extract.R | 7 +++++-- R/module_input.R | 2 +- R/resolver.R | 17 ++++++++--------- man/extract.Rd | 22 ++++++++++++++++++++++ man/is.delayed.Rd | 22 ++++++++++++++++++++++ man/update_spec.Rd | 2 +- tests/testthat/test-resolver.R | 19 ++++++++++++++++--- 8 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 man/extract.Rd create mode 100644 man/is.delayed.Rd diff --git a/R/delayed.R b/R/delayed.R index 8569aa18..f94cd585 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -6,6 +6,15 @@ delay <- function(x) { x } +#' Is the specification resolved? +#' +#' Check that the specification is resolved against a given data source. +#' @param x Object to be evaluated. +#' @returns A single logical value. +#' @examples +#' is.delayed(1) +#' is.delayed(variables("df", "df")) +#' is.delayed(variables("df")) # Unknown selection #' @export is.delayed <- function(x) { UseMethod("is.delayed") diff --git a/R/extract.R b/R/extract.R index a49795f9..d315ffe4 100644 --- a/R/extract.R +++ b/R/extract.R @@ -2,9 +2,12 @@ #' #' Required to resolve a specification into something usable (by comparing with the existing data). #' Required by merging data based on a resolved specification. +#' @param x Object from which a subset/element is required. +#' @param variable Name of the element to be extracted. +#' @param ... Other arguments passed to the specific method. #' @export -#' @noRd -#' @keywords internal +#' @examples +#' extract(iris, "Sepal.Length") extract <- function(x, variable, ...) { UseMethod("extract") } diff --git a/R/module_input.R b/R/module_input.R index f632d891..2cfad2ed 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -32,7 +32,7 @@ module_input_server <- function(id, spec, data) { react_updates <- reactive({ d <- data() if (!anyNA(spec) && is.delayed(spec)) { - spec <- teal.transform::resolver(spec, d) + spec <- resolver(spec, d) } for (i in seq_along(names(input))) { variable <- names(input)[i] diff --git a/R/resolver.R b/R/resolver.R index 79063795..e542893c 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -41,7 +41,11 @@ resolver <- function(spec, data) { stopifnot(is.list(spec) || is.transform(spec)) det <- determine(spec, data, spec = spec) - det$type + if (is.null(names(det))) { + return(lapply(det, `[[`, 1)) + } else { + det$type + } } #' A method that should take a type and resolve it. @@ -66,10 +70,10 @@ determine <- function(type, data, ...) { #' @export determine.default <- function(type, data, ..., spec) { # Used when the type is of class list. - if (!is.null(names(spec)) && is.delayed(spec)) { - rt <- determine(spec, data) + if (!is.null(names(type)) && is.delayed(type)) { + rt <- determine(type, data) } else { - rt <- lapply(spec, resolver, data = data) + rt <- lapply(type, determine, data = data, spec = spec) if (length(rt) == 1) { return(rt[[1]]) } @@ -81,11 +85,6 @@ determine.default <- function(type, data, ..., spec) { #' @export determine.transform <- function(type, data, ..., spec) { stopifnot(inherits(data, "qenv")) - # Recursion for other transforms in a list spec | spec - if (is.null(names(spec))) { - specs <- lapply(type, data, spec = spec) - return(specs) - } d <- data for (i in seq_along(type)) { diff --git a/man/extract.Rd b/man/extract.Rd new file mode 100644 index 00000000..9c2ab252 --- /dev/null +++ b/man/extract.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extract.R +\name{extract} +\alias{extract} +\title{Internal method to extract data from different objects} +\usage{ +extract(x, variable, ...) +} +\arguments{ +\item{x}{Object from which a subset/element is required.} + +\item{variable}{Name of the element to be extracted.} + +\item{...}{Other arguments passed to the specific method.} +} +\description{ +Required to resolve a specification into something usable (by comparing with the existing data). +Required by merging data based on a resolved specification. +} +\examples{ +extract(iris, "Sepal.Length") +} diff --git a/man/is.delayed.Rd b/man/is.delayed.Rd new file mode 100644 index 00000000..49f4290a --- /dev/null +++ b/man/is.delayed.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delayed.R +\name{is.delayed} +\alias{is.delayed} +\title{Is the specification resolved?} +\usage{ +is.delayed(x) +} +\arguments{ +\item{x}{Object to be evaluated.} +} +\value{ +A single logical value. +} +\description{ +Check that the specification is resolved against a given data source. +} +\examples{ +is.delayed(1) +is.delayed(variables("df", "df")) +is.delayed(variables("df")) # Unknown selection +} diff --git a/man/update_spec.Rd b/man/update_spec.Rd index 708e0909..a1db5f32 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -17,7 +17,7 @@ update_spec(spec, type, value) The specification with restored choices and selection if caused by the update. } \description{ -Once a selection is made update the specification for different valid selection. +Update the specification for different selection. } \examples{ td <- within(teal.data::teal_data(), { diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index a8e4cd64..d0c68701 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -160,12 +160,12 @@ test_that("update_spec resolves correctly", { expect_error(update_spec(res, "datasets", "error")) expect_error(update_spec(data_frames_factors, "datasets", "error")) - expect_no_error(update_spec(datasets(x = c("df", "df2")), "datasets", "df2")) + expect_error(update_spec(datasets(x = c("df", "df2")), "datasets", "df2")) expect_no_error(update_spec(datasets(x = c("df", "df2"), "df"), "datasets", "df2")) }) -test_that("OR resolver invalidates subsequent specifications", { +test_that("OR specifications resolves correctly", { td <- within(teal.data::teal_data(), { df <- data.frame(A = 1:5, B = LETTERS[1:5]) m <- cbind(A = 1:5, B = 5:10) @@ -175,7 +175,20 @@ test_that("OR resolver invalidates subsequent specifications", { matrix_a <- datasets(is.matrix) & var_a df_or_m_var_a <- df_a | matrix_a out <- resolver(df_or_m_var_a, td) - expect_false(is.null(out)) + expect_true(all(vapply(out, is.transform, logical(1L)))) +}) + +test_that("OR specifications fail correctly", { + td <- within(teal.data::teal_data(), { + df <- data.frame(A = 1:5, B = LETTERS[1:5]) + m <- cbind(A = 1:5, B = 5:10) + }) + var_a <- variables("A") + df_a <- datasets(is.data.frame) & var_a + matrix_a <- datasets(is.matrix) & var_a + df_or_m_var_a <- df_a | matrix_a + out <- resolver(df_or_m_var_a, td) + expect_error(update_spec(out, "variables", "B")) }) test_that("OR update_spec filters specifications", { From 58fcf91a02db57ea862f164648471ae51b389c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 8 Apr 2025 17:23:13 +0200 Subject: [PATCH 065/110] Apply styler --- R/merge_dataframes.R | 2 +- R/ops_transform.R | 4 ++-- R/resolver.R | 33 ++++++++++++++++++++++----------- R/types.R | 4 +++- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 56e29d61..4eeb87b8 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -8,7 +8,7 @@ merge_module_srv <- function(id, ..., data, ids, type) { stopifnot(is.character(id)) moduleServer(id, function(input, output, session) { out <- reactive({ - input_list = list(...) + input_list <- list(...) input_data <- lapply(input_list, extract_input, data = data) merging(input_data, ids = ids, type = type) }) diff --git a/R/ops_transform.R b/R/ops_transform.R index 540f2ee9..58ca87bb 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -92,9 +92,9 @@ nd_type <- function(e1, e2) { out <- c(e1, e2) } else if (is.type(e1) && is.type(e2)) { out <- c(e1, e2) - } else if (or.transform(e1) && is.type(e2) ){ + } else if (or.transform(e1) && is.type(e2)) { out <- lapply(e1, nd_type, e2 = e2) - } else if (or.transform(e2) && is.type(e1) ){ + } else if (or.transform(e2) && is.type(e1)) { out <- lapply(e2, nd_type, e2 = e1) } else { stop("Maybe we should decide how to apply a type to a list of transformers...") diff --git a/R/resolver.R b/R/resolver.R index e542893c..9cbc7fd2 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -79,7 +79,6 @@ determine.default <- function(type, data, ..., spec) { } } rt - } #' @export @@ -110,11 +109,17 @@ functions_names <- function(spec_criteria, names) { for (fun in functions) { names_ok <- tryCatch(fun(names), - error = function(x){x}, - warning = function(x){ - if (isTRUE(x) || isFALSE(x)){ - x - } else {FALSE}} ) + error = function(x) { + x + }, + warning = function(x) { + if (isTRUE(x) || isFALSE(x)) { + x + } else { + FALSE + } + } + ) if (!is.logical(names_ok)) { stop("Provided functions should return a logical object.") } @@ -138,11 +143,17 @@ functions_data <- function(spec_criteria, names_data, data) { l <- lapply(functions, function(fun) { data_ok <- tryCatch(fun(data), - error = function(x){x}, - warning = function(x){ - if (isTRUE(x) || isFALSE(x)){ - x - } else {FALSE}}) + error = function(x) { + x + }, + warning = function(x) { + if (isTRUE(x) || isFALSE(x)) { + x + } else { + FALSE + } + } + ) if (!is.logical(data_ok)) { stop("Provided functions should return a logical object.") } diff --git a/R/types.R b/R/types.R index 65855e5a..1cc058ea 100644 --- a/R/types.R +++ b/R/types.R @@ -3,7 +3,9 @@ is.transform <- function(x) { } or.transform <- function(x) { - is.list(x) && all(vapply(x, function(x){is.transform(x) || is.type(x)}, logical(1L))) + is.list(x) && all(vapply(x, function(x) { + is.transform(x) || is.type(x) + }, logical(1L))) } na_type <- function(type) { From 6aedd0afb5b542f63dfec25f0496071c712be1fc Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:26:59 +0000 Subject: [PATCH 066/110] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/update_spec.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/update_spec.Rd b/man/update_spec.Rd index a1db5f32..708e0909 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -17,7 +17,7 @@ update_spec(spec, type, value) The specification with restored choices and selection if caused by the update. } \description{ -Update the specification for different selection. +Once a selection is made update the specification for different valid selection. } \examples{ td <- within(teal.data::teal_data(), { From 4807d7d21b8ba666bdd43176d1c6c93ad4f97d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 9 Apr 2025 09:26:34 +0200 Subject: [PATCH 067/110] Add additional checks and case handling --- R/update_spec.R | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/R/update_spec.R b/R/update_spec.R index 44621634..05ae0248 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -1,6 +1,6 @@ #' Update a specification #' -#' Once a selection is made update the specification for different valid selection. +#' Update the specification for different selection. #' @param spec A resolved specification such as one created with datasets and variables. #' @param type Which type was updated? One of datasets, variables, values. #' @param value What is the new selection? One that is a valid value for the given type and specification. @@ -55,7 +55,9 @@ update_s_spec <- function(spec, type, value) { return(out[[is(spec)]]) } - + if (is.delayed(spec)) { + stop("Specification is not resolved (`!is.delayed(spec)`) can't update selections.") + } spec_types <- names(spec) type <- match.arg(type, spec_types) @@ -69,11 +71,16 @@ update_s_spec <- function(spec, type, value) { attr(spec[[type]][["select"]], "original") <- original_select } else if (!is.list(valid_names) && !all(value %in% valid_names)) { original_select <- orig(spec[[type]]$select) + valid_values <- intersect(value, valid_names) if (!length(valid_values)) { stop("No valid value provided.") } - spec[[type]][["select"]] <- valid_values + if (!length(valid_values)) { + spec[[type]][["select"]] <- original_select + } else { + spec[[type]][["select"]] <- valid_values + } attr(spec[[type]][["select"]], "original") <- original_select } else { stop("It seems the specification needs to be resolved first.") From 711f92d5c671cebc85fb0b5cc05d3fab28e1a03c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Apr 2025 07:30:30 +0000 Subject: [PATCH 068/110] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/update_spec.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/update_spec.Rd b/man/update_spec.Rd index 708e0909..a1db5f32 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -17,7 +17,7 @@ update_spec(spec, type, value) The specification with restored choices and selection if caused by the update. } \description{ -Once a selection is made update the specification for different valid selection. +Update the specification for different selection. } \examples{ td <- within(teal.data::teal_data(), { From 06c4b07ec99a101bfa638474eb5ff489d70705a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 9 Apr 2025 09:49:31 +0200 Subject: [PATCH 069/110] Comment code related to MAE --- NAMESPACE | 3 - R/resolver.R | 142 ++++++++++++++++++++++----------------------- man/update_spec.Rd | 2 +- 3 files changed, 72 insertions(+), 75 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 6f5acd46..80442543 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,9 +13,6 @@ S3method(data_extract_srv,FilteredData) S3method(data_extract_srv,list) S3method(determine,datasets) S3method(determine,default) -S3method(determine,mae_colData) -S3method(determine,mae_experiments) -S3method(determine,mae_sampleMap) S3method(determine,transform) S3method(determine,values) S3method(determine,variables) diff --git a/R/resolver.R b/R/resolver.R index 9cbc7fd2..7e620298 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -300,77 +300,77 @@ determine.variables <- function(type, data, ...) { list(type = type, data = data[, type$select]) } -#' @export -determine.mae_colData <- function(type, data, ...) { - if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { - stop("Requires 'MultiAssayExperiment' package.") - } - - new_data <- colData(data) - for (i in seq_along(new_data)) { - type <- determine_helper(type, colnames(new_data)[i], new_data[, i]) - } - if (length(dim(new_data)) != 2L) { - stop("Can't resolve variables from this object of class ", class(new_data)) - } - if (ncol(new_data) <= 0L) { - stop("Can't pull variable: No variable is available.") - } - type <- determine_helper(type, colnames(new_data), new_data) - - # Not possible to know what is happening - if (is.delayed(type)) { - return(list(type = type, data = NULL)) - } - - if (length(type$select) > 1) { - list(type = type, data = data[type$select]) - } else { - list(type = type, data = data[[type$select]]) - } -} - -#' @export -determine.mae_experiments <- function(type, data, ...) { - if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { - stop("Requires 'MultiAssayExperiment' package.") - } - new_data <- experiments(data) - type <- determine_helper(type, names(new_data), new_data) - - # Not possible to know what is happening - if (is.delayed(type)) { - } - - if (!is.delayed(type) && length(type$select) > 1) { - list(type = type, data = new_data[type$select]) - } else if (!is.delayed(type) && length(type$select) == 1) { - list(type = type, data = new_data[[type$select]]) - } else { - return(list(type = type, data = NULL)) - } -} - -#' @export -determine.mae_sampleMap <- function(type, data, ...) { - if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { - stop("Requires 'MultiAssayExperiment' package.") - } - - new_data <- sampleMap(data) - type <- determine_helper(type, names(new_data), new_data) - - # Not possible to know what is happening - if (is.delayed(type)) { - return(list(type = type, data = NULL)) - } - - if (length(type$select) > 1) { - list(type = type, data = data[type$select]) - } else { - list(type = type, data = data[[type$select]]) - } -} +# @export +# determine.mae_colData <- function(type, data, ...) { +# if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { +# stop("Requires 'MultiAssayExperiment' package.") +# } +# +# new_data <- colData(data) +# for (i in seq_along(new_data)) { +# type <- determine_helper(type, colnames(new_data)[i], new_data[, i]) +# } +# if (length(dim(new_data)) != 2L) { +# stop("Can't resolve variables from this object of class ", class(new_data)) +# } +# if (ncol(new_data) <= 0L) { +# stop("Can't pull variable: No variable is available.") +# } +# type <- determine_helper(type, colnames(new_data), new_data) +# +# # Not possible to know what is happening +# if (is.delayed(type)) { +# return(list(type = type, data = NULL)) +# } +# +# if (length(type$select) > 1) { +# list(type = type, data = data[type$select]) +# } else { +# list(type = type, data = data[[type$select]]) +# } +# } + +# @export +# determine.mae_experiments <- function(type, data, ...) { +# if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { +# stop("Requires 'MultiAssayExperiment' package.") +# } +# new_data <- experiments(data) +# type <- determine_helper(type, names(new_data), new_data) +# +# # Not possible to know what is happening +# if (is.delayed(type)) { +# } +# +# if (!is.delayed(type) && length(type$select) > 1) { +# list(type = type, data = new_data[type$select]) +# } else if (!is.delayed(type) && length(type$select) == 1) { +# list(type = type, data = new_data[[type$select]]) +# } else { +# return(list(type = type, data = NULL)) +# } +# } + +# @export +# determine.mae_sampleMap <- function(type, data, ...) { +# if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { +# stop("Requires 'MultiAssayExperiment' package.") +# } +# +# new_data <- sampleMap(data) +# type <- determine_helper(type, names(new_data), new_data) +# +# # Not possible to know what is happening +# if (is.delayed(type)) { +# return(list(type = type, data = NULL)) +# } +# +# if (length(type$select) > 1) { +# list(type = type, data = data[type$select]) +# } else { +# list(type = type, data = data[[type$select]]) +# } +# } #' @export determine.values <- function(type, data, ...) { diff --git a/man/update_spec.Rd b/man/update_spec.Rd index 708e0909..a1db5f32 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -17,7 +17,7 @@ update_spec(spec, type, value) The specification with restored choices and selection if caused by the update. } \description{ -Once a selection is made update the specification for different valid selection. +Update the specification for different selection. } \examples{ td <- within(teal.data::teal_data(), { From 0d032cd9065b92b7014573b5c1ff450b75bd7b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 9 Apr 2025 18:01:05 +0200 Subject: [PATCH 070/110] Minor improvements --- R/module_input.R | 7 +++++++ R/types.R | 5 +++++ R/update_spec.R | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/R/module_input.R b/R/module_input.R index 2cfad2ed..d532de7e 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -10,11 +10,17 @@ helper_input <- function(id, ) } +#' @export module_input_ui <- function(id, label, spec) { ns <- NS(id) input <- tagList( a(label), ) + + if (valid_transform(spec)) { + stop("Unexpected object used as specification.") + } + l <- lapply(spec, function(x) { helper_input(ns(is(x)), paste("Select", is(x), collapse = " "), @@ -24,6 +30,7 @@ module_input_ui <- function(id, label, spec) { input <- tagList(input, l) } +#' @export module_input_server <- function(id, spec, data) { stopifnot(is.transform(spec)) stopifnot(is.reactive(data)) diff --git a/R/types.R b/R/types.R index 1cc058ea..bce53884 100644 --- a/R/types.R +++ b/R/types.R @@ -2,6 +2,11 @@ is.transform <- function(x) { inherits(x, "transform") } + +valid_transform <- function(x) { + !((is.type(x) || is.transform(x)) || or.transform(x)) +} + or.transform <- function(x) { is.list(x) && all(vapply(x, function(x) { is.transform(x) || is.type(x) diff --git a/R/update_spec.R b/R/update_spec.R index 05ae0248..00e6efa8 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -29,7 +29,7 @@ update_spec <- function(spec, type, value) { ) } - if (!((is.type(spec) || is.transform(spec)) || or.transform(spec))) { + if (valid_transform(spec)) { stop("Unexpected object used as specification") } From c7f267566449a51026fee4432fcfe8ba54b262f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 9 Apr 2025 18:01:37 +0200 Subject: [PATCH 071/110] Merge from a list of inputs --- R/merge_dataframes.R | 72 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 4eeb87b8..1fb28f1c 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -3,12 +3,82 @@ merge_module_ui <- function(id) { renderText(ns("a")) } +consolidate_extraction <- function(...) { + if (...length() > 1) { + input_resolved <- as.list(...) + } else { + input_resolved <- ..1 + } + + datasets <- lapply(input_resolved, function(x){x$datasets}) + # Assume the data is a data.frame so no other specifications types are present. + variables <- lapply(input_resolved, function(x){x$variables}) + lapply(unique(datasets), + function(dataset, x, y) { + list("datasets" = dataset, "variables" = y[x == dataset]) + }, x = datasets, y = variables) +} + +add_ids <- function(input, data) { + + jk <- join_keys(data) + if (!length(jk)) { + return(input) + } + + datasets <- names(input) + l <- lapply(datasets, function(x, join_keys, i) { + c(i[[x]], unique(unlist(jk[[x]]))) + }, join_keys = jk, i = input) + + names(l) <- datasets +} + + +extract_ids <- function(input, data) { + jk <- join_keys(data) + # No join_keys => input + if (!length(jk)) { + input <- unlist(input) + tab <- table(input) + out <- names(tab)[tab > 1] + + if (length(out)) { + ei <- extract_input(input, data) + tab0 <- unlist(lapply(ei, colnames)) + tab <- table(tab0) + out <- names(tab)[tab > 1] + } + return(out) + } + + l <- lapply(datasets, function(x, join_keys) { + unique(unlist(jk[[x]])) + }, join_keys = jk) + out <- unique(unlist(l)) +} + merge_module_srv <- function(id, ..., data, ids, type) { # stopifnot(is.reactive(data)) stopifnot(is.character(id)) moduleServer(id, function(input, output, session) { out <- reactive({ - input_list <- list(...) + if (...length() == 1L && is.list(..1)) { + input_list <- ..1 + } else { + input_list <- list(...) + } + + input_list <- consolidate_extraction(input_list) + + # No merge is needed + if (length(input_list) == 1L) { + out <- extract_input(input_list, data) + output$out <- out + return(out) + } + # Add ids to merge by them if known + input_list <- add_ids(input_list, data) input_data <- lapply(input_list, extract_input, data = data) merging(input_data, ids = ids, type = type) }) From b733b34843477169e39cd318c9aa40a59a6c4fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 10 Apr 2025 14:46:57 +0200 Subject: [PATCH 072/110] Make it possible to consolidate input and extraction --- R/merge_dataframes.R | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 1fb28f1c..42c49a9c 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -1,3 +1,4 @@ + merge_module_ui <- function(id) { ns <- NS(id) renderText(ns("a")) @@ -5,7 +6,7 @@ merge_module_ui <- function(id) { consolidate_extraction <- function(...) { if (...length() > 1) { - input_resolved <- as.list(...) + input_resolved <- list(...) } else { input_resolved <- ..1 } @@ -15,7 +16,7 @@ consolidate_extraction <- function(...) { variables <- lapply(input_resolved, function(x){x$variables}) lapply(unique(datasets), function(dataset, x, y) { - list("datasets" = dataset, "variables" = y[x == dataset]) + list("datasets" = dataset, "variables" = unique(unlist(y[x == dataset]))) }, x = datasets, y = variables) } @@ -26,12 +27,14 @@ add_ids <- function(input, data) { return(input) } - datasets <- names(input) - l <- lapply(datasets, function(x, join_keys, i) { - c(i[[x]], unique(unlist(jk[[x]]))) - }, join_keys = jk, i = input) - - names(l) <- datasets + datasets <- lapply(input, function(x){x$datasets}) + for (i in seq_along(input)) { + x <- input[[i]] + # Avoid adding as id something already present. + ids <- setdiff(unique(unlist(jk[[x$datasets]])), x$variables) + input[[i]][["variables"]] <- c(x$variables, ids) + } + input } @@ -44,10 +47,7 @@ extract_ids <- function(input, data) { out <- names(tab)[tab > 1] if (length(out)) { - ei <- extract_input(input, data) - tab0 <- unlist(lapply(ei, colnames)) - tab <- table(tab0) - out <- names(tab)[tab > 1] + return(NULL) } return(out) } @@ -80,7 +80,9 @@ merge_module_srv <- function(id, ..., data, ids, type) { # Add ids to merge by them if known input_list <- add_ids(input_list, data) input_data <- lapply(input_list, extract_input, data = data) - merging(input_data, ids = ids, type = type) + # TODO: return an expression + # Evaluation should be addressed by eval_code(qenv, code = output) + merging(input_data, ids = extract_ids(input_list, data), type = type) }) output$out <- out out From 1134bbd4949da759f303932b87d197994c6dff2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 10 Apr 2025 17:29:28 +0200 Subject: [PATCH 073/110] Increase version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0150a808..72882ca9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: teal.transform Title: Functions for Extracting and Merging Data in the 'teal' Framework -Version: 0.6.0.9000 +Version: 0.6.0.9001 Date: 2025-02-12 Authors@R: c( person("Dawid", "Kaledkowski", , "dawid.kaledkowski@roche.com", role = c("aut", "cre")), From cc26a2553689c11d0ec993e2e21a1406ced14f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 24 Apr 2025 09:48:23 +0200 Subject: [PATCH 074/110] Merge data.frames within qenv --- NAMESPACE | 2 + R/extract.R | 32 ++++++ R/merge_dataframes.R | 233 +++++++++++++------------------------------ 3 files changed, 105 insertions(+), 162 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 80442543..bc4c5a81 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -87,6 +87,8 @@ export(mae_sampleMap) export(merge_datasets) export(merge_expression_module) export(merge_expression_srv) +export(module_input_server) +export(module_input_ui) export(no_selected_as_NULL) export(resolve_delayed) export(resolver) diff --git a/R/extract.R b/R/extract.R index d315ffe4..03012585 100644 --- a/R/extract.R +++ b/R/extract.R @@ -54,3 +54,35 @@ extract.default <- function(x, variable, ..., drop = TRUE) { # extract.qenv <- function(x, variable) { # x[[variable]] # } + +# Get code to be evaluated & displayed by modules +extract_srv <- function(id, input) { + stopifnot(is.null(input$datasets)) + stopifnot(is.null(input$variables)) + moduleServer( + id, + function(input, output, session) { + + obj <- extract(data(), input$datasets) + method <- paste0("extract.", class(obj)) + method <- dynGet(method, ifnotfound = "extract.default", inherits = TRUE) + if (identical(method, "extract.default")) { + b <- get("extract.default") + } else { + b <- get(method) + } + # Extract definition + extract_f_def <- call("<-", x = as.name("extract"), value = b) + q <- eval_code(data(), code = extract_f_def) + + # Extraction happening: + # FIXME assumes only to variables used + output <- call("<-", x = as.name(input$datasets), value = + substitute( + extract(obj, variables), + list(obj = as.name(input$datasets), + variables = input$variables))) + q <- eval_code(q, code = output) + }) +} + diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 42c49a9c..7b2f7ba1 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -1,9 +1,5 @@ - -merge_module_ui <- function(id) { - ns <- NS(id) - renderText(ns("a")) -} - +# Simplify multiple datasets & variables into the bare minimum necessary. +# This simplifies the number of extractions and merging required consolidate_extraction <- function(...) { if (...length() > 1) { input_resolved <- list(...) @@ -11,42 +7,45 @@ consolidate_extraction <- function(...) { input_resolved <- ..1 } - datasets <- lapply(input_resolved, function(x){x$datasets}) # Assume the data is a data.frame so no other specifications types are present. - variables <- lapply(input_resolved, function(x){x$variables}) + datasets <- lapply(input_resolved, `$`, name = datasets) + variables <- lapply(input_resolved, `$`, name = variables) lapply(unique(datasets), function(dataset, x, y) { - list("datasets" = dataset, "variables" = unique(unlist(y[x == dataset]))) + list("datasets" = dataset, + "variables" = unique(unlist(y[x == dataset]))) }, x = datasets, y = variables) } +# Function to add ids of data.frames to the output of modules to enable merging them. add_ids <- function(input, data) { jk <- join_keys(data) + # If no join keys they should be on the input if (!length(jk)) { return(input) } - datasets <- lapply(input, function(x){x$datasets}) + datasets <- lapply(input, `$`, name = datasets) for (i in seq_along(input)) { x <- input[[i]] - # Avoid adding as id something already present. + # Avoid adding as id something already present: No duplicating input. ids <- setdiff(unique(unlist(jk[[x$datasets]])), x$variables) input[[i]][["variables"]] <- c(x$variables, ids) } input } - +# Find common ids to enable merging. extract_ids <- function(input, data) { - jk <- join_keys(data) + jk <- teal.data::join_keys(data) # No join_keys => input if (!length(jk)) { input <- unlist(input) tab <- table(input) out <- names(tab)[tab > 1] - if (length(out)) { + if (!length(out)) { return(NULL) } return(out) @@ -58,180 +57,90 @@ extract_ids <- function(input, data) { out <- unique(unlist(l)) } -merge_module_srv <- function(id, ..., data, ids, type) { - # stopifnot(is.reactive(data)) - stopifnot(is.character(id)) - moduleServer(id, function(input, output, session) { - out <- reactive({ - if (...length() == 1L && is.list(..1)) { - input_list <- ..1 - } else { - input_list <- list(...) - } - - input_list <- consolidate_extraction(input_list) - - # No merge is needed - if (length(input_list) == 1L) { - out <- extract_input(input_list, data) - output$out <- out - return(out) - } - # Add ids to merge by them if known - input_list <- add_ids(input_list, data) - input_data <- lapply(input_list, extract_input, data = data) - # TODO: return an expression - # Evaluation should be addressed by eval_code(qenv, code = output) - merging(input_data, ids = extract_ids(input_list, data), type = type) - }) - output$out <- out - out - }) -} +merge_call_pair <- function(selections, by, data, + merge_function = "dplyr::full_join", + anl_name = "ANL") { + stopifnot(length(selections) == 2L) + datasets <- sapply(selections, function(x){x$datasets}) + by <- extract_ids(input = selections, data) + + if (grepl("::", merge_function, fixed = TRUE)) { + m <- strsplit(merge_function, split = "::", fixed = TRUE)[[1]] + data <- eval_code(data, call("library", m[1])) + merge_function <- m[2] + } -extract_input <- function(input, data) { - for (i in input) { - # Extract data recursively: only works on lists and square objects (no MAE or similar) - # To work on new classes implement an extract.class method - # Assumes order of extraction on the input: qenv > datasets > variables - # IF datasetes > variables > qenv order - data <- extract(data, i, drop = FALSE) + if (!missing(by) && length(by)) { + call_m <- call(merge_function, + x = as.name(datasets[1]), + y = as.name(datasets[2]), + by = by) + } else { + call_m <- call(merge_function, + x = as.name(datasets[1]), + y = as.name(datasets[2])) } - data + call_m } -# Allows merging arbitrary number of data.frames by ids and type +merge_call_multiple <- function(input, ids, merge_function, data, + anl_name = "ANL") { -merging <- function(..., ids, type) { - input_as_list <- is.list(..1) & ...length() == 1L - if (input_as_list) { - list_df <- ..1 - } else { - list_df <- list(...) - } - number_merges <- length(list_df) - 1L + datasets <- sapply(input, function(x){x$datasets}) + stopifnot(is.character(datasets) && length(datasets) >= 1L) + number_merges <- length(datasets) - 1L stopifnot( "Number of datasets is enough" = number_merges >= 1L, - "Number of arguments for type matches data" = length(type) == number_merges || length(type) == 1L + "Number of arguments for type matches data" = length(merge_function) == number_merges || length(merge_function) == 1L ) - if (!missing(ids)) { stopifnot("Number of arguments for ids matches data" = !(is.list(ids) && length(ids) == number_merges)) } - if (length(type) != number_merges) { - type <- rep(type, number_merges) + if (length(merge_function) != number_merges) { + merge_function <- rep(merge_function, number_merges) } if (!missing(ids) && length(ids) != number_merges) { ids <- rep(ids, number_merges) } - if (number_merges == 1L && !input_as_list && !missing(ids)) { - return(self_merging(..1, ..2, ids = ids, type = type)) - } else if (number_merges == 1L && !input_as_list && missing(ids)) { - return(self_merging(..1, ..2, type = type)) - } else if (number_merges == 1L && input_as_list && missing(ids)) { - return(self_merging(list_df[[1]], list_df[[2]], type = type)) - } else if (number_merges == 1L && input_as_list && !missing(ids)) { - return(self_merging(list_df[[1]], list_df[[2]], ids = ids, type = type)) + if (number_merges == 1L && missing(ids)) { + previous <- merge_call_pair(input, merge_function = merge_function, data = data) + final_call <- call("<-", x = as.name(anl_name), value = previous) + return(eval_code(data, final_call)) + } else if (number_merges == 1L && !missing(ids)) { + previous <- merge_call_pair(input, by = ids, merge_function = merge_function, data = data) + final_call <- call("<-", x = as.name(anl_name), value = previous) + return(eval_code(data, final_call)) } + + for (merge_i in seq_len(number_merges)) { - message(merge_i) if (merge_i == 1L) { - if (missing(ids)) { - ids <- intersect(colnames(list_df[[merge_i]]), colnames(list_df[[merge_i + 1L]])) - } else { + datasets_i <- seq_len(2) + if (!missing(ids)) { ids <- ids[[merge_i]] + previous <- merge_call_pair(input[datasets_i], + ids, + merge_function[merge_i], data = data) + } else { + previous <- merge_call_pair(input[datasets_i], + merge_function[merge_i], data = data) } - out <- self_merging(list_df[[merge_i]], list_df[[merge_i + 1L]], - ids, - type = type[[merge_i]] - ) } else { - if (missing(ids)) { - ids <- intersect(colnames(out, colnames(list_df[[merge_i + 1L]]))) + datasets_ids <- merge_i:(merge_i + 1L) + if (!missing(ids)) { + current <- merge_call_pair(input[datasets_ids], + type = merge_function[merge_i], data = data) } else { ids <- ids[[merge_i]] - } - out <- self_merging(out, list_df[[merge_i + 1L]], - ids, - type = type[[merge_i]] - ) - } - } - out -} - - -# self_merge(df1, df2) almost equal to self_merge(df2, df1): Only changes on the column order. -self_merging <- function(e1, e2, ids = intersect(colnames(e1), colnames(e2)), type) { - # Get the name of the variables to use as suffix. - # If we need the name at higher environments (ie: f(self_merging()) ) it could use rlang (probably) - name1 <- deparse(substitute(e1)) - name2 <- deparse(substitute(e2)) - suffix1 <- paste0(".", name1) - suffix2 <- paste0(".", name2) - ce1 <- colnames(e1) - ce2 <- colnames(e2) - type <- match.arg(type, c("inner", "left", "right", "full")) - - # Called by its side effects of adding the two variables the the current environment - switch(type, - inner = { - all.x <- FALSE - all.y <- FALSE - }, - full = { - all.x <- TRUE - all.y <- TRUE - }, - left = { - all.x <- TRUE - all.y <- FALSE - }, - right = { - all.x <- FALSE - all.y <- TRUE - }, - { - all.x <- FALSE - all.y <- FALSE - } - ) - - if (!is.null(names(ids))) { - name_ids <- names(ids) - } else { - name_ids <- ids - } - - if (!all(ids %in% name_ids) && !all(ids %in% ce2)) { - stop("Not all ids are in both objects") - } - # The default generic should find the right method, if not we : - # a) ask for the method to be implemented or - # b) implement it ourselves here to be used internally. - mm <- merge(e1, e2, - all.x = all.x, all.y = all.y, - by.x = name_ids, by.y = ids, - suffixes = c(".e1", ".e2") - ) - g <- grep("\\.[(e1)(e2)]", colnames(mm)) - if (length(g)) { - mix_columns <- setdiff(intersect(ce1, ce2), ids) - for (column in mix_columns) { - mc1 <- paste0(column, ".e1") - mc2 <- paste0(column, ".e2") - # Rename column and delete one if they are the same - if (identical(mm[, mc1], mm[, mc2])) { - mm[, mc2] <- NULL - colnames(mm)[colnames(mm) %in% mc1] <- column - } else { - # Rename to keep the suffic of the data names - colnames(mm)[colnames(mm) %in% mc1] <- paste0(column, suffix1) - colnames(mm)[colnames(mm) %in% mc2] <- paste0(column, suffix2) + current <- merge_call_pair(input[datasets_ids], + ids, + type = merge_function[merge_i], data = data) } } + previous <- call("%>%", as.name(previous), as.name(current)) } - mm + final_call <- call("<-", x = as.name(anl_name), value = previous) + eval_code(data, final_call) } From 1c2ddadc7e65483934d9c60b8b6b410b34c0e660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 24 Apr 2025 09:49:42 +0200 Subject: [PATCH 075/110] WIP: add ! operator --- R/ops_transform.R | 15 ++++++++++---- R/resolver.R | 32 +++++++++++++++++++++++------ R/types.R | 52 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/R/ops_transform.R b/R/ops_transform.R index 58ca87bb..8ff9da0b 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -18,10 +18,11 @@ Ops.transform <- function(e1, e2) { #' @export Ops.type <- function(e1, e2) { if (missing(e2)) { - # out <- switch(.Generic, - # "!" = Negate, - stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) - # return(out) + out <- switch(.Generic, + "!" = negate_type(e1), + stop("Method ", sQuote(.Generic), + " not implemented for this class ", .Class, ".", call. = FALSE)) + return(out) } out <- switch(.Generic, "!=" = NextMethod(), @@ -114,6 +115,12 @@ or_type <- function(e1, e2) { } +negate_type <- function(e1, e2) { + out <- list(except = e1$names) + class(out) <- class(e1) + out +} + # chooseOpsMethod.list <- function(x, y, mx, my, cl, reverse) TRUE #' @export chooseOpsMethod.transform <- function(x, y, mx, my, cl, reverse) { diff --git a/R/resolver.R b/R/resolver.R index 7e620298..058a48a7 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -171,15 +171,25 @@ determine_helper <- function(type, data_names, data) { stopifnot(!is.null(type)) orig_names <- type$names orig_select <- type$select + orig_exc <- type$except if (is.delayed(type) && all(is.character(type$names))) { - match <- intersect(data_names, type$names) - type$names <- match - if (length(match) == 0) { + new_names <- intersect(data_names, type$names) + + if (!is.null(type$except)) { + excludes <- c(functions_names(type$except, data_names), + functions_data(type$except, data_names, data)) + type$except <- excludes + original(type$except, "original") <- orig(orig_exc) + new_names <- setdiff(new_names, excludes) + } + + type$names <- new_names + if (length(new_names) == 0) { return(NULL) # stop("No selected ", is(type), " matching the conditions requested") - } else if (length(match) == 1L) { - type$select <- match + } else if (length(new_names) == 1L) { + type$select <- new_names } else { new_select <- functions_names(type$select, type$names) new_select <- unique(new_select[!is.na(new_select)]) @@ -190,7 +200,6 @@ determine_helper <- function(type, data_names, data) { type$select <- new_select } } else if (is.delayed(type)) { - old_names <- type$names new_names <- c( functions_names(type$names, data_names), functions_data(type$names, data_names, data) @@ -198,6 +207,17 @@ determine_helper <- function(type, data_names, data) { new_names <- unlist(unique(new_names[!is.na(new_names)]), use.names = FALSE ) + + if (!is.null(type$except)) { + excludes <- c(functions_names(type$except, data_names), + functions_data(type$except, data_names, data)) + + type$except <- excludes + original(type$except, "original") <- orig(orig_exc) + + new_names <- setdiff(new_names, excludes) + } + if (!length(new_names)) { return(NULL) # stop("No ", is(type), " meet the requirements") diff --git a/R/types.R b/R/types.R index bce53884..f58e8a45 100644 --- a/R/types.R +++ b/R/types.R @@ -202,10 +202,9 @@ c.type <- function(...) { vector <- vector("list", length(utypes)) names(vector) <- utypes for (t in utypes) { - new_type <- vector("list", length = 2) - names(new_type) <- c("names", "select") + new_type <- vector("list", length = 3) + names(new_type) <- c("names", "select", "except") for (i in seq_along(l)) { - names_l <- names(l[[i]]) if (!is(l[[i]], t)) { next } @@ -217,6 +216,10 @@ c.type <- function(...) { ), orig(l[[i]][["names"]])) new_type$select <- unique(c(old_select, l[[i]][["select"]])) attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][["select"]])) + + new_type$except <- c(new_type$except, l[[i]][["except"]]) + attr(new_type$except, "original") <- c(orig(l[[i]][["except"]]), orig(new_type$except)) + } orig_names <- unique(orig(new_type$names)) orig_select <- unique(orig(new_type$select)) @@ -251,12 +254,7 @@ print.type <- function(x, ...) { return(x) } - nam_list <- is.list(x$names) - if (nam_list) { - nam_functions <- vapply(x$names, is.function, logical(1L)) - } else { - nam_functions <- FALSE - } + nam_functions <- count_functions(x$names) msg_values <- character() nam_values <- length(x$names) - sum(nam_functions) @@ -272,12 +270,7 @@ print.type <- function(x, ...) { ) } - sel_list <- is.list(x$select) - if (sel_list) { - sel_functions <- vapply(x$select, is.function, logical(1L)) - } else { - sel_functions <- FALSE - } + sel_functions <- count_functions(x$select) msg_sel <- character() sel_values <- length(x$select) - sum(sel_functions) @@ -292,6 +285,33 @@ print.type <- function(x, ...) { collapse = "\n" ) } - cat(msg_values, msg_sel) + if (!is.null(x[["except"]])) { + exc_functions <- count_functions(x$except) + msg_exc <- character() + sel_values <- length(x$except) - sum(exc_functions) + if (any(exc_functions)) { + msg_exc <- paste0(msg_exc, sum(exc_functions), " functions to exclude.", + collapse = "\n" + ) + } + if (sel_values) { + msg_exc <- paste0(msg_exc, paste0(sQuote(x$except[!exc_functions]), collapse = ", "), + " excluded.", + collapse = "\n" + ) + } + } else { + msg_exc <- character() + } + + cat(msg_values, msg_sel, msg_exc) return(x) } + +count_functions <- function(x) { + if (is.list(x)) { + vapply(x, is.function, logical(1L)) + } else { + FALSE + } +} From d27750fc3e4cf437aec34ab8f4391639b60a4507 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 08:52:43 +0000 Subject: [PATCH 076/110] [skip style] [skip vbump] Restyle files --- R/extract.R | 20 ++++++++++------- R/merge_dataframes.R | 52 ++++++++++++++++++++++++++++---------------- R/ops_transform.R | 9 +++++--- R/resolver.R | 12 ++++++---- R/types.R | 7 +++--- 5 files changed, 62 insertions(+), 38 deletions(-) diff --git a/R/extract.R b/R/extract.R index 03012585..1ee7ecc1 100644 --- a/R/extract.R +++ b/R/extract.R @@ -62,7 +62,6 @@ extract_srv <- function(id, input) { moduleServer( id, function(input, output, session) { - obj <- extract(data(), input$datasets) method <- paste0("extract.", class(obj)) method <- dynGet(method, ifnotfound = "extract.default", inherits = TRUE) @@ -77,12 +76,17 @@ extract_srv <- function(id, input) { # Extraction happening: # FIXME assumes only to variables used - output <- call("<-", x = as.name(input$datasets), value = - substitute( - extract(obj, variables), - list(obj = as.name(input$datasets), - variables = input$variables))) + output <- call("<-", + x = as.name(input$datasets), value = + substitute( + extract(obj, variables), + list( + obj = as.name(input$datasets), + variables = input$variables + ) + ) + ) q <- eval_code(q, code = output) - }) + } + ) } - diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 7b2f7ba1..6be7d9a2 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -11,15 +11,18 @@ consolidate_extraction <- function(...) { datasets <- lapply(input_resolved, `$`, name = datasets) variables <- lapply(input_resolved, `$`, name = variables) lapply(unique(datasets), - function(dataset, x, y) { - list("datasets" = dataset, - "variables" = unique(unlist(y[x == dataset]))) - }, x = datasets, y = variables) + function(dataset, x, y) { + list( + "datasets" = dataset, + "variables" = unique(unlist(y[x == dataset])) + ) + }, + x = datasets, y = variables + ) } # Function to add ids of data.frames to the output of modules to enable merging them. add_ids <- function(input, data) { - jk <- join_keys(data) # If no join keys they should be on the input if (!length(jk)) { @@ -61,7 +64,9 @@ merge_call_pair <- function(selections, by, data, merge_function = "dplyr::full_join", anl_name = "ANL") { stopifnot(length(selections) == 2L) - datasets <- sapply(selections, function(x){x$datasets}) + datasets <- sapply(selections, function(x) { + x$datasets + }) by <- extract_ids(input = selections, data) if (grepl("::", merge_function, fixed = TRUE)) { @@ -72,21 +77,24 @@ merge_call_pair <- function(selections, by, data, if (!missing(by) && length(by)) { call_m <- call(merge_function, - x = as.name(datasets[1]), - y = as.name(datasets[2]), - by = by) + x = as.name(datasets[1]), + y = as.name(datasets[2]), + by = by + ) } else { call_m <- call(merge_function, - x = as.name(datasets[1]), - y = as.name(datasets[2])) + x = as.name(datasets[1]), + y = as.name(datasets[2]) + ) } call_m } merge_call_multiple <- function(input, ids, merge_function, data, anl_name = "ANL") { - - datasets <- sapply(input, function(x){x$datasets}) + datasets <- sapply(input, function(x) { + x$datasets + }) stopifnot(is.character(datasets) && length(datasets) >= 1L) number_merges <- length(datasets) - 1L stopifnot( @@ -121,22 +129,28 @@ merge_call_multiple <- function(input, ids, merge_function, data, if (!missing(ids)) { ids <- ids[[merge_i]] previous <- merge_call_pair(input[datasets_i], - ids, - merge_function[merge_i], data = data) + ids, + merge_function[merge_i], + data = data + ) } else { previous <- merge_call_pair(input[datasets_i], - merge_function[merge_i], data = data) + merge_function[merge_i], + data = data + ) } } else { datasets_ids <- merge_i:(merge_i + 1L) if (!missing(ids)) { current <- merge_call_pair(input[datasets_ids], - type = merge_function[merge_i], data = data) + type = merge_function[merge_i], data = data + ) } else { ids <- ids[[merge_i]] current <- merge_call_pair(input[datasets_ids], - ids, - type = merge_function[merge_i], data = data) + ids, + type = merge_function[merge_i], data = data + ) } } previous <- call("%>%", as.name(previous), as.name(current)) diff --git a/R/ops_transform.R b/R/ops_transform.R index 8ff9da0b..67d6dad0 100644 --- a/R/ops_transform.R +++ b/R/ops_transform.R @@ -19,9 +19,12 @@ Ops.transform <- function(e1, e2) { Ops.type <- function(e1, e2) { if (missing(e2)) { out <- switch(.Generic, - "!" = negate_type(e1), - stop("Method ", sQuote(.Generic), - " not implemented for this class ", .Class, ".", call. = FALSE)) + "!" = negate_type(e1), + stop("Method ", sQuote(.Generic), + " not implemented for this class ", .Class, ".", + call. = FALSE + ) + ) return(out) } out <- switch(.Generic, diff --git a/R/resolver.R b/R/resolver.R index 058a48a7..4d664836 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -177,8 +177,10 @@ determine_helper <- function(type, data_names, data) { new_names <- intersect(data_names, type$names) if (!is.null(type$except)) { - excludes <- c(functions_names(type$except, data_names), - functions_data(type$except, data_names, data)) + excludes <- c( + functions_names(type$except, data_names), + functions_data(type$except, data_names, data) + ) type$except <- excludes original(type$except, "original") <- orig(orig_exc) new_names <- setdiff(new_names, excludes) @@ -209,8 +211,10 @@ determine_helper <- function(type, data_names, data) { ) if (!is.null(type$except)) { - excludes <- c(functions_names(type$except, data_names), - functions_data(type$except, data_names, data)) + excludes <- c( + functions_names(type$except, data_names), + functions_data(type$except, data_names, data) + ) type$except <- excludes original(type$except, "original") <- orig(orig_exc) diff --git a/R/types.R b/R/types.R index f58e8a45..0cec0468 100644 --- a/R/types.R +++ b/R/types.R @@ -219,7 +219,6 @@ c.type <- function(...) { new_type$except <- c(new_type$except, l[[i]][["except"]]) attr(new_type$except, "original") <- c(orig(l[[i]][["except"]]), orig(new_type$except)) - } orig_names <- unique(orig(new_type$names)) orig_select <- unique(orig(new_type$select)) @@ -291,13 +290,13 @@ print.type <- function(x, ...) { sel_values <- length(x$except) - sum(exc_functions) if (any(exc_functions)) { msg_exc <- paste0(msg_exc, sum(exc_functions), " functions to exclude.", - collapse = "\n" + collapse = "\n" ) } if (sel_values) { msg_exc <- paste0(msg_exc, paste0(sQuote(x$except[!exc_functions]), collapse = ", "), - " excluded.", - collapse = "\n" + " excluded.", + collapse = "\n" ) } } else { From 810ce49a0aa815d709d7abc3eae0c0012572c502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 24 Apr 2025 12:36:40 +0200 Subject: [PATCH 077/110] Address some checks warnings --- DESCRIPTION | 1 + R/extract.R | 8 ++++---- R/merge_dataframes.R | 14 +++++++------- R/resolver.R | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 2908ce62..6608015d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -31,6 +31,7 @@ Imports: shinyjs, shinyvalidate (>= 0.1.3), stats, + teal.code (>= 0.6.0), teal.data (>= 0.7.0), teal.logger (>= 0.3.1), teal.widgets (>= 0.4.3), diff --git a/R/extract.R b/R/extract.R index 1ee7ecc1..162620f8 100644 --- a/R/extract.R +++ b/R/extract.R @@ -56,13 +56,13 @@ extract.default <- function(x, variable, ..., drop = TRUE) { # } # Get code to be evaluated & displayed by modules -extract_srv <- function(id, input) { +extract_srv <- function(id, input, data) { stopifnot(is.null(input$datasets)) stopifnot(is.null(input$variables)) moduleServer( id, function(input, output, session) { - obj <- extract(data(), input$datasets) + obj <- extract(data, input$datasets) method <- paste0("extract.", class(obj)) method <- dynGet(method, ifnotfound = "extract.default", inherits = TRUE) if (identical(method, "extract.default")) { @@ -72,7 +72,7 @@ extract_srv <- function(id, input) { } # Extract definition extract_f_def <- call("<-", x = as.name("extract"), value = b) - q <- eval_code(data(), code = extract_f_def) + q <- teal.code::eval_code(data, code = extract_f_def) # Extraction happening: # FIXME assumes only to variables used @@ -86,7 +86,7 @@ extract_srv <- function(id, input) { ) ) ) - q <- eval_code(q, code = output) + q <- teal.code::eval_code(q, code = output) } ) } diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 6be7d9a2..3ba12c80 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -23,7 +23,7 @@ consolidate_extraction <- function(...) { # Function to add ids of data.frames to the output of modules to enable merging them. add_ids <- function(input, data) { - jk <- join_keys(data) + jk <- teal.data::join_keys(data) # If no join keys they should be on the input if (!length(jk)) { return(input) @@ -71,7 +71,7 @@ merge_call_pair <- function(selections, by, data, if (grepl("::", merge_function, fixed = TRUE)) { m <- strsplit(merge_function, split = "::", fixed = TRUE)[[1]] - data <- eval_code(data, call("library", m[1])) + data <- teal.code::eval_code(data, call("library", m[1])) merge_function <- m[2] } @@ -114,11 +114,11 @@ merge_call_multiple <- function(input, ids, merge_function, data, if (number_merges == 1L && missing(ids)) { previous <- merge_call_pair(input, merge_function = merge_function, data = data) final_call <- call("<-", x = as.name(anl_name), value = previous) - return(eval_code(data, final_call)) + return(teal.code::eval_code(data, final_call)) } else if (number_merges == 1L && !missing(ids)) { previous <- merge_call_pair(input, by = ids, merge_function = merge_function, data = data) final_call <- call("<-", x = as.name(anl_name), value = previous) - return(eval_code(data, final_call)) + return(teal.code::eval_code(data, final_call)) } @@ -143,18 +143,18 @@ merge_call_multiple <- function(input, ids, merge_function, data, datasets_ids <- merge_i:(merge_i + 1L) if (!missing(ids)) { current <- merge_call_pair(input[datasets_ids], - type = merge_function[merge_i], data = data + merge_function = merge_function[merge_i], data = data ) } else { ids <- ids[[merge_i]] current <- merge_call_pair(input[datasets_ids], ids, - type = merge_function[merge_i], data = data + merge_function = merge_function[merge_i], data = data ) } } previous <- call("%>%", as.name(previous), as.name(current)) } final_call <- call("<-", x = as.name(anl_name), value = previous) - eval_code(data, final_call) + teal.code::eval_code(data, final_call) } diff --git a/R/resolver.R b/R/resolver.R index 4d664836..a85ea2e3 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -182,7 +182,7 @@ determine_helper <- function(type, data_names, data) { functions_data(type$except, data_names, data) ) type$except <- excludes - original(type$except, "original") <- orig(orig_exc) + attr(type$except, "original") <- orig(orig_exc) new_names <- setdiff(new_names, excludes) } @@ -217,7 +217,7 @@ determine_helper <- function(type, data_names, data) { ) type$except <- excludes - original(type$except, "original") <- orig(orig_exc) + attr(type$except, "original") <- orig(orig_exc) new_names <- setdiff(new_names, excludes) } From 8cea67418829ca3082966d8852efcad8cc857e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 24 Apr 2025 12:37:04 +0200 Subject: [PATCH 078/110] WIP: Add tests for ! operator --- tests/testthat/test-ops_transform.R | 1 + tests/testthat/test-resolver.R | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R index e712ee25..a015b709 100644 --- a/tests/testthat/test-ops_transform.R +++ b/tests/testthat/test-ops_transform.R @@ -24,6 +24,7 @@ basic_ops <- function(fun) { expect_error(FUN("ABC") & 1) out <- type1 & type2b expect_true(is.list(out$names)) + expect_no_error(type1 & !type2) } test_that("datasets & work", { diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index d0c68701..8b265eef 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -77,6 +77,16 @@ test_that("resolver values works", { expect_no_error(resolver(df & var_a & val_A, td)) }) +test_that("resolver works with excluded types", { + td <- within(teal.data::teal_data(), { + df <- data.frame(a = LETTERS[1:5], + b = factor(letters[1:5]), + c = factor(letters[1:5])) + }) + spec <- datasets("df") & variables(c("a", "b")) & !variables("b") + expect_no_error(resolver(spec, td)) +}) + test_that("names and variables are reported", { td <- within(teal.data::teal_data(), { df <- data.frame( From cf4c5b2fd7449dbba1e792d30311f9023a9f3dde Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 10:39:19 +0000 Subject: [PATCH 079/110] [skip style] [skip vbump] Restyle files --- tests/testthat/test-resolver.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 8b265eef..d061e442 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -79,9 +79,11 @@ test_that("resolver values works", { test_that("resolver works with excluded types", { td <- within(teal.data::teal_data(), { - df <- data.frame(a = LETTERS[1:5], - b = factor(letters[1:5]), - c = factor(letters[1:5])) + df <- data.frame( + a = LETTERS[1:5], + b = factor(letters[1:5]), + c = factor(letters[1:5]) + ) }) spec <- datasets("df") & variables(c("a", "b")) & !variables("b") expect_no_error(resolver(spec, td)) From 4a3c2c877a5eb0d30fcb9579fddd914add760435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= <185338939+llrs-roche@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:08:32 +0200 Subject: [PATCH 080/110] Use unique datasets before merging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dawid Kałędkowski Signed-off-by: Lluís Revilla <185338939+llrs-roche@users.noreply.github.com> --- R/merge_dataframes.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 3ba12c80..f5a3126a 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -92,9 +92,9 @@ merge_call_pair <- function(selections, by, data, merge_call_multiple <- function(input, ids, merge_function, data, anl_name = "ANL") { - datasets <- sapply(input, function(x) { + datasets <- unique(sapply(input, function(x) { x$datasets - }) + })) stopifnot(is.character(datasets) && length(datasets) >= 1L) number_merges <- length(datasets) - 1L stopifnot( From 59c6aff692fe04202f6ca4d652378595e156b2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 25 Apr 2025 14:47:26 +0200 Subject: [PATCH 081/110] Use consolidate_extraction to make sure merging is needed --- R/merge_dataframes.R | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index f5a3126a..46c5f4f9 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -8,8 +8,8 @@ consolidate_extraction <- function(...) { } # Assume the data is a data.frame so no other specifications types are present. - datasets <- lapply(input_resolved, `$`, name = datasets) - variables <- lapply(input_resolved, `$`, name = variables) + datasets <- lapply(input_resolved, function(x){x$datasets}) + variables <- lapply(input_resolved, function(x){x$variables}) lapply(unique(datasets), function(dataset, x, y) { list( @@ -29,7 +29,7 @@ add_ids <- function(input, data) { return(input) } - datasets <- lapply(input, `$`, name = datasets) + datasets <- lapply(input, function(x){x$datasets}) for (i in seq_along(input)) { x <- input[[i]] # Avoid adding as id something already present: No duplicating input. @@ -63,10 +63,13 @@ extract_ids <- function(input, data) { merge_call_pair <- function(selections, by, data, merge_function = "dplyr::full_join", anl_name = "ANL") { + + selections <- consolidate_extraction(selections) stopifnot(length(selections) == 2L) - datasets <- sapply(selections, function(x) { + datasets <- unique(sapply(selections, function(x) { x$datasets - }) + })) + stopifnot(length(datasets) >= 2) by <- extract_ids(input = selections, data) if (grepl("::", merge_function, fixed = TRUE)) { @@ -92,6 +95,8 @@ merge_call_pair <- function(selections, by, data, merge_call_multiple <- function(input, ids, merge_function, data, anl_name = "ANL") { + + input <- consolidate_extraction(input) datasets <- unique(sapply(input, function(x) { x$datasets })) From 11932a81797fafb65b8f61cf3fdddd884622d029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 25 Apr 2025 17:40:48 +0200 Subject: [PATCH 082/110] Verify it works without problems with just c() --- tests/testthat/test-types.R | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index 3a64cee2..c454419c 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -29,6 +29,13 @@ test_that("raw combine of types", { expect_no_error(c(datasets("df"), variables("df"), values("df"))) }) +test_that("combine types", { + expect_no_error(c( + datasets(is.data.frame, select = "df1"), + variables(is.numeric) + )) +}) + test_that("values", { expect_no_error(val0 <- values("a", "a")) expect_no_error(val1 <- values("a")) From 7b306fc7cb3bfde7ad5399652a0c71be8f1e0366 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 15:43:27 +0000 Subject: [PATCH 083/110] [skip style] [skip vbump] Restyle files --- R/merge_dataframes.R | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 46c5f4f9..a0b72bfc 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -8,8 +8,12 @@ consolidate_extraction <- function(...) { } # Assume the data is a data.frame so no other specifications types are present. - datasets <- lapply(input_resolved, function(x){x$datasets}) - variables <- lapply(input_resolved, function(x){x$variables}) + datasets <- lapply(input_resolved, function(x) { + x$datasets + }) + variables <- lapply(input_resolved, function(x) { + x$variables + }) lapply(unique(datasets), function(dataset, x, y) { list( @@ -29,7 +33,9 @@ add_ids <- function(input, data) { return(input) } - datasets <- lapply(input, function(x){x$datasets}) + datasets <- lapply(input, function(x) { + x$datasets + }) for (i in seq_along(input)) { x <- input[[i]] # Avoid adding as id something already present: No duplicating input. @@ -63,7 +69,6 @@ extract_ids <- function(input, data) { merge_call_pair <- function(selections, by, data, merge_function = "dplyr::full_join", anl_name = "ANL") { - selections <- consolidate_extraction(selections) stopifnot(length(selections) == 2L) datasets <- unique(sapply(selections, function(x) { @@ -95,7 +100,6 @@ merge_call_pair <- function(selections, by, data, merge_call_multiple <- function(input, ids, merge_function, data, anl_name = "ANL") { - input <- consolidate_extraction(input) datasets <- unique(sapply(input, function(x) { x$datasets From bec03ff98269c8f0c31d6be66e0826b7a3eb9ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 28 Apr 2025 12:28:59 +0200 Subject: [PATCH 084/110] Remove operators (and exclude) --- NAMESPACE | 3 - R/ops_transform.R | 134 ---------------------------- R/types.R | 15 +--- tests/testthat/test-delayed.R | 2 +- tests/testthat/test-ops_transform.R | 101 --------------------- tests/testthat/test-resolver.R | 63 +++++++------ tests/testthat/test-types.R | 6 +- 7 files changed, 39 insertions(+), 285 deletions(-) delete mode 100644 R/ops_transform.R delete mode 100644 tests/testthat/test-ops_transform.R diff --git a/NAMESPACE b/NAMESPACE index bc4c5a81..85bd463e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,11 +1,8 @@ # Generated by roxygen2: do not edit by hand -S3method(Ops,transform) -S3method(Ops,type) S3method(anyNA,type) S3method(c,transform) S3method(c,type) -S3method(chooseOpsMethod,transform) S3method(data_extract_multiple_srv,FilteredData) S3method(data_extract_multiple_srv,list) S3method(data_extract_multiple_srv,reactive) diff --git a/R/ops_transform.R b/R/ops_transform.R deleted file mode 100644 index 67d6dad0..00000000 --- a/R/ops_transform.R +++ /dev/null @@ -1,134 +0,0 @@ -#' @export -Ops.transform <- function(e1, e2) { - if (missing(e2)) { - # out <- switch(.Generic, - # "!" = Negate, - stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) - # return(out) - } - switch(.Generic, - "!=" = NextMethod(), - "==" = NextMethod(), - "|" = or_transform(e1, e2), - "&" = nd_transform(e1, e2), - stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) - ) -} - -#' @export -Ops.type <- function(e1, e2) { - if (missing(e2)) { - out <- switch(.Generic, - "!" = negate_type(e1), - stop("Method ", sQuote(.Generic), - " not implemented for this class ", .Class, ".", - call. = FALSE - ) - ) - return(out) - } - out <- switch(.Generic, - "!=" = NextMethod(), - # "==" = NextMethod(), - "|" = or_type(e1, e2), - "&" = nd_type(e1, e2), - stop("Method ", sQuote(.Generic), " not implemented for this class ", .Class, ".", call. = FALSE) - ) - out -} - -or_transform <- function(e1, e2) { - if (is.transform(e1) && is.type(e2) && !is.transform(e2)) { - opt2 <- e1 & e2 - out <- list(e1, opt2) - } else if (!is.transform(e1) && is.type(e1) && is.transform(e2)) { - opt2 <- e2 & e1 - out <- list(e2, opt2) - } else if (is.transform(e1) && is.transform(e2)) { - out <- list(e1, e2) - } else { - stop("Missing implementation method.") - } - # FIXME: Should we signal it is a transform or just a list of transform is enough? - # class(out) <- c("transform", "list") - out -} - -nd_transform <- function(e1, e2) { - if (is.transform(e1) && is.transform(e2)) { - types <- intersect(names(e1), names(e2)) - for (t in types) { - e1[[t]] <- unique(c(e1[[t]], e2[[t]])) - } - return(e1) - } - - if (is.type(e1) && is.transform(e2)) { - if (!is(e1) %in% names(e2)) { - e2[[is(e1)]] <- e1 - } else { - e2[[is(e1)]] <- c(e2[[is(e1)]], e1) - } - return(e2) - } else if (is.transform(e1) && is.type(e2)) { - if (!is(e2) %in% names(e1)) { - e1[[is(e2)]] <- e2 - } else { - e1[[is(e2)]] <- c(e1[[is(e2)]], e2) - } - out <- e1 - } else if (is.type(e1) && is.transform(e2)) { - out <- rev(c(e2, e1)) # To keep order in the list - } else { - stop("Method not implemented yet!") - } - out -} - -nd_type <- function(e1, e2) { - if (is.transform(e1) && !is.transform(e2)) { - out <- c(e1, list(e2)) - names(out)[length(out)] <- is(e2) - } else if (!is.transform(e1) && is.transform(e2)) { - out <- c(e2, list(e1)) - names(out)[length(out)] <- is(e1) - } else if (is.transform(e1) && is.transform(e2)) { - out <- c(e1, e2) - } else if (is.type(e1) && is.type(e2)) { - out <- c(e1, e2) - } else if (or.transform(e1) && is.type(e2)) { - out <- lapply(e1, nd_type, e2 = e2) - } else if (or.transform(e2) && is.type(e1)) { - out <- lapply(e2, nd_type, e2 = e1) - } else { - stop("Maybe we should decide how to apply a type to a list of transformers...") - } - class(out) <- unique(c("transform", class(out))) - out -} - -or_type <- function(e1, e2) { - substitute <- is(e2) %in% names(e1) - if (substitute) { - out <- e1 - e1[[is(e2)]] <- e2 - return(nd_type(out, e1)) - } - list(e1, e2) -} - - -negate_type <- function(e1, e2) { - out <- list(except = e1$names) - class(out) <- class(e1) - out -} - -# chooseOpsMethod.list <- function(x, y, mx, my, cl, reverse) TRUE -#' @export -chooseOpsMethod.transform <- function(x, y, mx, my, cl, reverse) { - # Apply one or other method - # !is.transform(x) - TRUE -} -# chooseOpsMethod.type <- function(x, y, mx, my, cl, reverse) TRUE diff --git a/R/types.R b/R/types.R index 0cec0468..6186fa41 100644 --- a/R/types.R +++ b/R/types.R @@ -7,12 +7,6 @@ valid_transform <- function(x) { !((is.type(x) || is.transform(x)) || or.transform(x)) } -or.transform <- function(x) { - is.list(x) && all(vapply(x, function(x) { - is.transform(x) || is.type(x) - }, logical(1L))) -} - na_type <- function(type) { out <- NA_character_ class(out) <- c(type, "type") @@ -175,6 +169,7 @@ c.transform <- function(...) { ), orig(l[[i]][[t]][["names"]])) new_type$select <- c(old_select, l[[i]][[t]][["select"]]) attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][[t]][["select"]])) + attr(new_type, "delayed") <- any(attr(new_type, "delayed"), attr(l[[i]], "delayed")) } orig_names <- unique(orig(new_type$names)) new_type$names <- unique(new_type$names) @@ -202,8 +197,8 @@ c.type <- function(...) { vector <- vector("list", length(utypes)) names(vector) <- utypes for (t in utypes) { - new_type <- vector("list", length = 3) - names(new_type) <- c("names", "select", "except") + new_type <- vector("list", length = 2) + names(new_type) <- c("names", "select") for (i in seq_along(l)) { if (!is(l[[i]], t)) { next @@ -216,9 +211,6 @@ c.type <- function(...) { ), orig(l[[i]][["names"]])) new_type$select <- unique(c(old_select, l[[i]][["select"]])) attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][["select"]])) - - new_type$except <- c(new_type$except, l[[i]][["except"]]) - attr(new_type$except, "original") <- c(orig(l[[i]][["except"]]), orig(new_type$except)) } orig_names <- unique(orig(new_type$names)) orig_select <- unique(orig(new_type$select)) @@ -233,6 +225,7 @@ c.type <- function(...) { attr(new_type$select, "original") <- orig_select class(new_type) <- c(t, "type", "list") + attr(new_type, "delayed") <- is.delayed(new_type) vector[[t]] <- new_type } if (length(vector) == 1) { diff --git a/tests/testthat/test-delayed.R b/tests/testthat/test-delayed.R index a6efe359..7b9a9beb 100644 --- a/tests/testthat/test-delayed.R +++ b/tests/testthat/test-delayed.R @@ -5,7 +5,7 @@ test_that("is.delayed works", { expect_false(is.delayed(datasets("a", "a"))) expect_true(is.delayed(v)) expect_false(is.delayed(variables("b", "b"))) - expect_true(is.delayed(d & v)) + expect_true(is.delayed(c(d, v))) expect_false(is.delayed(1)) da <- datasets(is.data.frame) expect_true(is.delayed(da)) diff --git a/tests/testthat/test-ops_transform.R b/tests/testthat/test-ops_transform.R deleted file mode 100644 index a015b709..00000000 --- a/tests/testthat/test-ops_transform.R +++ /dev/null @@ -1,101 +0,0 @@ -basic_ops <- function(fun) { - FUN <- match.fun(fun) - type1 <- FUN("ABC") - types <- type1 & type1 - out <- list(names = "ABC", select = list(first)) - class(out) <- c(fun, "type", "list") - expect_equal(types, out, check.attributes = FALSE) - type2 <- FUN("ABC2") - types <- type1 & type2 - out <- list(names = c("ABC", "ABC2"), select = list(first)) - class(out) <- c("delayed", fun, "type", "list") - expect_equal(types, out, check.attributes = FALSE) - expect_equal(types$names, c("ABC", "ABC2"), check.attributes = FALSE) - types2 <- types & type2 - expect_equal(types$names, c("ABC", "ABC2"), check.attributes = FALSE) - expect_s3_class(types, class(out)) - type3 <- FUN("ABC2", select = all_choices) - types <- type1 & type3 - expect_length(types$select, 2) - type2b <- FUN(first_choice) - type2c <- FUN(last_choice) - out <- type2b & type2c - expect_length(out$names, 2) - expect_error(FUN("ABC") & 1) - out <- type1 & type2b - expect_true(is.list(out$names)) - expect_no_error(type1 & !type2) -} - -test_that("datasets & work", { - basic_ops("datasets") -}) - - -test_that("variables & work", { - basic_ops("variables") -}) - -test_that("values & work", { - basic_ops("values") -}) - -test_that("&(datsets, variables) create a single transform", { - dataset1 <- datasets("ABC2") - var1 <- variables("abc") - vars <- dataset1 & var1 - vars2 <- var1 & dataset1 - expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) - expect_equal(vars$variables$names, "abc", check.attributes = FALSE) -}) - -test_that("&(datsets, number) errors", { - expect_error(datasets("abc") & 1) -}) - -test_that("datsets & values work", { - dataset1 <- datasets("ABC2") - val1 <- values("abc") - vars <- dataset1 & val1 - expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) - expect_equal(vars$values$names, "abc", check.attributes = FALSE) -}) - -test_that("&(datsets, number) errors", { - expect_error(variables("abc") & 1) -}) - -test_that("variables & values work", { - var1 <- variables("ABC2") - val1 <- values("abc") - vars <- var1 & val1 - expect_equal(vars$variables$names, "ABC2", check.attributes = FALSE) - expect_equal(vars$values$names, "abc", check.attributes = FALSE) -}) - -test_that("&(values, number) errors", { - expect_error(values("abc") & 1) -}) - -test_that("datasets & variables & values create a single specification", { - dataset1 <- datasets("ABC2") - var1 <- variables("ABC2") - val1 <- values("abc") - vars <- dataset1 & var1 & val1 - vars2 <- val1 & var1 & dataset1 - expect_equal(vars$datasets$names, "ABC2", check.attributes = FALSE) - expect_equal(vars$variables$names, "ABC2", check.attributes = FALSE) - expect_equal(vars$values$names, "abc", check.attributes = FALSE) -}) - -test_that("&(transform, number) errors", { - expect_error(datasets("ABC2") & variables("ABC2") & values("abc") & 1) - expect_error(datasets("ABC2") & values("abc") & 1) -}) - - -test_that("| combines two transformers", { - spec <- datasets("ABC") | datasets("abc") - expect_length(spec, 2) - expect_true(is.null(names(spec))) -}) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index d061e442..7cf7c6a4 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -39,21 +39,21 @@ test_that("resolver variables works", { m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) - expect_no_error(resolver(df & var_a, td)) - expect_no_error(resolver(df & factors, td)) - expect_error(resolver(df & factors_head, td)) - expect_error(resolver(df & var_matrices_head, td)) + expect_no_error(resolver(c(df, var_a), td)) + expect_no_error(resolver(c(df, factors), td)) + expect_error(resolver(c(df, factors_head), td)) + expect_error(resolver(c(df, var_matrices_head), td)) - expect_error(resolver(matrices & var_a, td)) # datasets selection overpasses variable choices. - expect_error(resolver(matrices & factors, td)) - expect_error(resolver(matrices & factors_head, td)) - expect_error(resolver(matrices & var_matrices_head, td)) + expect_error(resolver(c(matrices, var_a), td)) # datasets selection overpasses variable choices. + expect_error(resolver(c(matrices, factors), td)) + expect_error(resolver(c(matrices, factors_head), td)) + expect_error(resolver(c(matrices, var_matrices_head), td)) - expect_no_error(resolver(data_frames & var_a, td)) - expect_no_error(resolver(data_frames & factors, td)) - expect_error(resolver(data_frames & factors_head, td)) - expect_error(resolver(data_frames & var_matrices_head, td)) + expect_no_error(resolver(c(data_frames, var_a), td)) + expect_no_error(resolver(c(data_frames, factors), td)) + expect_error(resolver(c(data_frames, factors_head), td)) + expect_error(resolver(c(data_frames, var_matrices_head), td)) }) test_that("resolver values works", { @@ -74,7 +74,7 @@ test_that("resolver values works", { m <- cbind(b = 1:5, c = 10:14) m2 <- cbind(a = LETTERS[1:2], b = LETTERS[4:5]) }) - expect_no_error(resolver(df & var_a & val_A, td)) + expect_no_error(resolver(c(df, var_a, val_A), td)) }) test_that("resolver works with excluded types", { @@ -85,8 +85,8 @@ test_that("resolver works with excluded types", { c = factor(letters[1:5]) ) }) - spec <- datasets("df") & variables(c("a", "b")) & !variables("b") - expect_no_error(resolver(spec, td)) + # spec <- c(datasets("df"), variables(c("a", "b")), !variables("b")) + # expect_no_error(resolver(spec, td)) }) test_that("names and variables are reported", { @@ -103,9 +103,9 @@ test_that("names and variables are reported", { m <- matrix() }) d_df <- datasets("df") - df_upper_variables <- d_df & variables(function(x) { + df_upper_variables <- c(d_df, variables(function(x) { x == toupper(x) - }) + })) out <- resolver(df_upper_variables, td) # This should select A and Ab: # A because the name is all capital letters and @@ -114,17 +114,17 @@ test_that("names and variables are reported", { v_all_upper <- variables(function(x) { all(x == toupper(x)) }) - df_all_upper_variables <- d_df & v_all_upper + df_all_upper_variables <- c(d_df, v_all_upper) expect_no_error(out <- resolver(df_all_upper_variables, td)) expect_length(out$variables$names, 2L) - expect_no_error(out <- resolver(datasets("df2") & v_all_upper, td)) + expect_no_error(out <- resolver(c(datasets("df2"), v_all_upper), td)) expect_length(out$variables$names, 2L) expect_no_error(out <- resolver(datasets(function(x) { is.data.frame(x) && all(colnames(x) == toupper(colnames(x))) }), td)) expect_length(out$names, 1L) - expect_no_error(out <- resolver(datasets(is.data.frame) & datasets(function(x) { - colnames(x) == toupper(colnames(x)) + expect_no_error(out <- resolver(datasets(function(x) { + is.data.frame(x) || any(colnames(x) == toupper(colnames(x))) }), td)) expect_length(out$names, 2L) }) @@ -140,7 +140,7 @@ test_that("update_spec resolves correctly", { Ab = as.factor(letters[1:5]) ) }) - data_frames_factors <- datasets(is.data.frame) & variables(is.factor) + data_frames_factors <- c(datasets(is.data.frame), variables(is.factor)) expect_false(is.null(attr(data_frames_factors$datasets$names, "original"))) expect_false(is.null(attr(data_frames_factors$datasets$select, "original"))) expect_false(is.null(attr(data_frames_factors$variables$names, "original"))) @@ -176,16 +176,15 @@ test_that("update_spec resolves correctly", { expect_no_error(update_spec(datasets(x = c("df", "df2"), "df"), "datasets", "df2")) }) - test_that("OR specifications resolves correctly", { td <- within(teal.data::teal_data(), { df <- data.frame(A = 1:5, B = LETTERS[1:5]) m <- cbind(A = 1:5, B = 5:10) }) var_a <- variables("A") - df_a <- datasets(is.data.frame) & var_a - matrix_a <- datasets(is.matrix) & var_a - df_or_m_var_a <- df_a | matrix_a + df_a <- c(datasets(is.data.frame), var_a) + matrix_a <- c(datasets(is.matrix), var_a) + df_or_m_var_a <- list(df_a, matrix_a) out <- resolver(df_or_m_var_a, td) expect_true(all(vapply(out, is.transform, logical(1L)))) }) @@ -196,9 +195,9 @@ test_that("OR specifications fail correctly", { m <- cbind(A = 1:5, B = 5:10) }) var_a <- variables("A") - df_a <- datasets(is.data.frame) & var_a - matrix_a <- datasets(is.matrix) & var_a - df_or_m_var_a <- df_a | matrix_a + df_a <- c(datasets(is.data.frame), var_a) + matrix_a <- c(datasets(is.matrix), var_a) + df_or_m_var_a <- list(df_a, matrix_a) out <- resolver(df_or_m_var_a, td) expect_error(update_spec(out, "variables", "B")) }) @@ -209,9 +208,9 @@ test_that("OR update_spec filters specifications", { m <- cbind(A = 1:5, B = 5:10) }) var_a <- variables("A") - df_a <- datasets(is.data.frame) & var_a - matrix_a <- datasets(is.matrix) & var_a - df_or_m_var_a <- df_a | matrix_a + df_a <- c(datasets(is.data.frame), var_a) + matrix_a <- c(datasets(is.matrix), var_a) + df_or_m_var_a <- list(df_a, matrix_a) resolved <- resolver(df_or_m_var_a, td) # The second option is not possible to have it as df expect_error(update_spec(resolved, "datasets", "df")) diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index c454419c..39795174 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -24,9 +24,9 @@ test_that("variables", { }) test_that("raw combine of types", { - out <- c(datasets("df"), variables("df")) - expect_length(out, 2L) - expect_no_error(c(datasets("df"), variables("df"), values("df"))) + expect_equal(c(datasets("df")), datasets("df")) + expect_length(c(datasets("df"), variables("df")), 2L) + expect_length(c(datasets("df"), variables("df"), values("df")), 3L) }) test_that("combine types", { From 3c32eab8e584b91d4ce199e8a9c84a1a72d867ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 29 Apr 2025 08:09:44 +0200 Subject: [PATCH 085/110] Simplify --- R/merge_dataframes.R | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 46c5f4f9..6a3a15a0 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -66,9 +66,7 @@ merge_call_pair <- function(selections, by, data, selections <- consolidate_extraction(selections) stopifnot(length(selections) == 2L) - datasets <- unique(sapply(selections, function(x) { - x$datasets - })) + datasets <- unique(unlist(lapply(selections, `[[`, datasets), FALSE, FALSE)) stopifnot(length(datasets) >= 2) by <- extract_ids(input = selections, data) @@ -97,9 +95,7 @@ merge_call_multiple <- function(input, ids, merge_function, data, anl_name = "ANL") { input <- consolidate_extraction(input) - datasets <- unique(sapply(input, function(x) { - x$datasets - })) + datasets <- unique(unlist(lapply(input, `[[`, "datasets"), FALSE, FALSE)) stopifnot(is.character(datasets) && length(datasets) >= 1L) number_merges <- length(datasets) - 1L stopifnot( From 386232d9f06ba742bbb855bba4813eff1d53713d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 29 Apr 2025 10:10:00 +0200 Subject: [PATCH 086/110] Add tidyselect::eval_select --- R/selector.R | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 R/selector.R diff --git a/R/selector.R b/R/selector.R new file mode 100644 index 00000000..8c70318a --- /dev/null +++ b/R/selector.R @@ -0,0 +1,10 @@ +selector <- function(data, ...) { + if (is.environment(data)) { + data <- as.list(data) + } + if (is.null(names(data))) { + stop("Can't extract the data.") + } + pos <- tidyselect::eval_select(rlang::expr(c(...)), data) + pos +} From 64c42e2888ffbf028e3caced674118b4b77aa186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 30 Apr 2025 17:30:45 +0200 Subject: [PATCH 087/110] Add support for expressions in types --- R/selector.R | 5 +++- R/types.R | 66 +++++++--------------------------------------------- 2 files changed, 13 insertions(+), 58 deletions(-) diff --git a/R/selector.R b/R/selector.R index 8c70318a..55be6ed5 100644 --- a/R/selector.R +++ b/R/selector.R @@ -1,10 +1,13 @@ selector <- function(data, ...) { if (is.environment(data)) { data <- as.list(data) + } else if (length(dim(data)) == 2L) { + data <- as.data.frame(data) } + if (is.null(names(data))) { stop("Can't extract the data.") } - pos <- tidyselect::eval_select(rlang::expr(c(...)), data) + pos <- tidyselect::eval_select(expr = ..., data) pos } diff --git a/R/types.R b/R/types.R index 6186fa41..4267d334 100644 --- a/R/types.R +++ b/R/types.R @@ -45,16 +45,6 @@ check_input <- function(input) { } type_helper <- function(x, select, type) { - stopifnot( - "Invalid options" = check_input(x), - "Invalid selection" = check_input(type) - ) - if (is.function(x)) { - x <- list(x) - } - if (is.function(select)) { - select <- list(select) - } out <- list(names = x, select = select) class(out) <- c(type, "type", "list") attr(out$names, "original") <- x @@ -62,7 +52,6 @@ type_helper <- function(x, select, type) { delay(out) } - #' @rdname types #' @name Types #' @title Type specification @@ -78,67 +67,30 @@ type_helper <- function(x, select, type) { #' datasets("A") & variables(is.numeric) NULL - - #' @describeIn types Specify datasets. #' @export -datasets <- function(x, select = first) { - type_helper(x, select, type = "datasets") +datasets <- function(x, select = everything()) { + type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "datasets") } - #' @describeIn types Specify variables. #' @export -variables <- function(x, select = first) { - type_helper(x, select, type = "variables") +variables <- function(x, select = everything()) { + type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "variables") } -#' @describeIn types Specify variables of MultiAssayExperiment col Data. +#' @describeIn types Specify colData of SummarizedExperiment and derived classes. #' @export -mae_colData <- function(x, select = first) { - type_helper(x, select, type = "mae_colData") -} - -#' @describeIn types Specify variables of MultiAssayExperiment sampleMap. -#' @export -mae_sampleMap <- function(x, select = first) { - type_helper(x, select, type = "mae_sampleMap") -} - -#' @describeIn types Specify variables of MultiAssayExperiment experiments. -#' @export -mae_experiments <- function(x, select = first) { - type_helper(x, select, type = "mae_experiments") +colData <- function(x, select = everything()) { + type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "colData") } #' @describeIn types Specify values. #' @export -values <- function(x, select = first) { - type_helper(x, select, type = "values") +values <- function(x, select = everything()) { + type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "values") } -# #' @export -# c.type <- function(...) { -# -# if (is.na(..1)) { -# return(..2) -# } else if (is.na(..2)) { -# return(..1) -# } -# -# if (...length() > 2L) { -# stop("We can't combine this (yet)") -# } else if (all(class(..2) != class(..1))) { -# type_out <- ..1 -# type_out$child <- ..2 -# return(type_out) -# } -# out <- mapply(c, ..., SIMPLIFY = FALSE) -# out <- lapply(out, unique) -# class(out) <- c("transform", class(out)) -# delay(out) -# } - #' @export c.transform <- function(...) { l <- list(...) From 2a00dd22fd2df2a0207b8f6d204d6530d7f514dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 30 Apr 2025 17:31:59 +0200 Subject: [PATCH 088/110] Use the tidyselect approach (remove operators) --- R/resolver.R | 114 ++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 60 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index a85ea2e3..112c26fa 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -32,11 +32,11 @@ resolver <- function(spec, data) { # Adding some default specifications if they are missing if ("values" %in% names(spec) && !"variables" %in% names(spec)) { - spec <- variables(first) & spec + spec <- c(variables(first), spec) } if ("variables" %in% names(spec) && !"datasets" %in% names(spec)) { - spec <- datasets(first) & spec + spec <- c(datasets(first), spec) } stopifnot(is.list(spec) || is.transform(spec)) @@ -71,20 +71,20 @@ determine <- function(type, data, ...) { determine.default <- function(type, data, ..., spec) { # Used when the type is of class list. if (!is.null(names(type)) && is.delayed(type)) { - rt <- determine(type, data) - } else { - rt <- lapply(type, determine, data = data, spec = spec) - if (length(rt) == 1) { - return(rt[[1]]) - } + return(determine(type, data)) } - rt + d <- data + for (i in seq_along(type)) { + di <- determine(type[[i]], d, spec = spec) + type[[i]] <- di$type + d <- di$data + } + list(type = type, data = data) } #' @export determine.transform <- function(type, data, ..., spec) { stopifnot(inherits(data, "qenv")) - d <- data for (i in seq_along(type)) { di <- determine(type[[i]], d, spec = spec) @@ -242,8 +242,8 @@ determine_helper <- function(type, data_names, data) { new_select <- unique(new_select[!is.na(new_select)]) if (!length(new_select)) { - return(NULL) stop("No ", is(type), " meet the requirements to be selected") + return(NULL) } type$select <- new_select } @@ -254,32 +254,23 @@ determine_helper <- function(type, data_names, data) { #' @export determine.datasets <- function(type, data, ...) { - if (!inherits(data, "qenv")) { + if (is.null(data)) { + return(list(type = type, data = NULL)) + } else if (!inherits(data, "qenv")) { stop("Please use qenv() or teal_data() objects.") } - l <- vector("list", length(names(data))) - # Somehow in some cases (I didn't explore much this was TRUE) - for (i in seq_along(l)) { - data_name_env <- names(data)[i] - out <- determine_helper(type, data_name_env, extract(data, data_name_env)) - if (!is.null(out)) { - l[[i]] <- out - } - } - - # Merge together all the types - type <- do.call(c, l[lengths(l) > 1L]) - # Evaluate the selection based on all possible choices. - type <- eval_type_select(type, data) + # Assumes the object has colnames method (true for major object classes: DataFrame, tibble, Matrix, array) + # FIXME: What happens if colnames is null: array(dim = c(4, 2)) |> colnames() + type <- eval_type_names(type, data) - if (is.null(type$names)) { - stop("No datasets meet the specification.", call. = FALSE) + if (is.null(type$names) || !length(type$names)) { + stop("No ", class(type), " meet the specification.", call. = FALSE) } - if (!is.delayed(type) && length(type$select) > 1L) { - list(type = type, data = data[unorig(type$select)]) - } else if (!is.delayed(type) && length(type$select) == 1L) { + type <- eval_type_select(type, data[unorig(type$names)]) + + if (!is.delayed(type) && length(type$select) == 1L) { list(type = type, data = data[[unorig(type$select)]]) } else { list(type = type, data = NULL) @@ -288,8 +279,12 @@ determine.datasets <- function(type, data, ...) { #' @export determine.variables <- function(type, data, ...) { - if (length(dim(data)) != 2L) { - stop("Can't resolve variables from this object of class ", class(data)) + + if (is.null(data)) { + return(list(type = type, data = NULL)) + } else if (length(dim(data)) != 2L) { + stop("Can't resolve variables from this object of class ", + toString(sQuote(class(data)))) } if (ncol(data) <= 0L) { @@ -298,23 +293,14 @@ determine.variables <- function(type, data, ...) { # Assumes the object has colnames method (true for major object classes: DataFrame, tibble, Matrix, array) # FIXME: What happens if colnames is null: array(dim = c(4, 2)) |> colnames() - l <- vector("list", ncol(data)) - for (i in seq_len(ncol(data))) { - out <- determine_helper(type, colnames(data)[i], data[, i]) - if (!is.null(out)) { - l[[i]] <- out - } + type <- eval_type_names(type, data) + + if (is.null(type$names) || !length(type$names)) { + stop("No ", class(type), " meet the specification.", call. = FALSE) } - # Merge together all the types - type <- do.call(c, l[lengths(l) > 1]) - # Check the selected values as they got appended. type <- eval_type_select(type, data) - if (is.null(type$names)) { - stop("No variables meet the specification.", call. = FALSE) - } - # Not possible to know what is happening if (is.delayed(type)) { return(list(type = type, data = NULL)) @@ -417,25 +403,33 @@ unorig <- function(x) { x } - eval_type_select <- function(type, data) { - l <- vector("list", length(type$names)) - names(l) <- type$names + stopifnot(is.character(type$names)) + orig_select <- orig(type$select) - for (name in type$names) { - out <- functions_data(orig_select, name, extract(data, name)) - if (!is.null(out)) { - l[[name]] <- unlist(out) - } - } - new_select <- c( - functions_names(orig(type$select), type$names), - unlist(l, FALSE, FALSE) - ) + names <- seq_along(type$names) + names(names) <- type$names + select_data <- selector(data, type$select) - new_select <- unique(new_select) + # Keep only those that were already selected + new_select <- intersect(unique(names(c(select_data))), unorig(type$names)) attr(new_select, "original") <- orig_select + type$select <- new_select + + type +} + + +eval_type_names <- function(type, data) { + orig_names <- orig(type$names) + new_names <- selector(data, type$names) + + new_names <- unique(names(new_names)) + attr(new_names, "original") <- orig_names + + type$names <- new_names + type } From a78ecce88864befbf42d1844e71ffc5c8aabb520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 30 Apr 2025 17:34:54 +0200 Subject: [PATCH 089/110] Remove extra MAE methods --- NAMESPACE | 2 -- 1 file changed, 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 85bd463e..9e66c7b1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -79,8 +79,6 @@ export(last_choice) export(last_choices) export(list_extract_spec) export(mae_colData) -export(mae_experiments) -export(mae_sampleMap) export(merge_datasets) export(merge_expression_module) export(merge_expression_srv) From 6fa687ec2084dea0eeef4967d6c4b5a4b68660cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 6 May 2025 10:10:20 +0200 Subject: [PATCH 090/110] Fix issues --- .pre-commit-config.yaml | 2 +- DESCRIPTION | 1 + R/module_input.R | 4 +- R/resolver.R | 191 +++++++++------------------------ R/types.R | 55 ++++++---- R/update_spec.R | 10 +- inst/WORDLIST | 10 +- man/resolver.Rd | 10 +- man/types.Rd | 26 ++--- man/update_spec.Rd | 2 +- tests/testthat/test-delayed.R | 4 +- tests/testthat/test-resolver.R | 96 ++++++----------- tests/testthat/test-types.R | 29 +++-- 13 files changed, 164 insertions(+), 276 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 24e1e2c1..5949c0ee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ default_language_version: python: python3 repos: - repo: https://github.com/lorenzwalthert/precommit - rev: v0.4.3.9007 + rev: v0.4.3.9009 hooks: - id: style-files name: Style code with `styler` diff --git a/DESCRIPTION b/DESCRIPTION index 6608015d..89ae48f1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -22,6 +22,7 @@ Depends: R (>= 3.6) Imports: checkmate (>= 2.1.0), + cli (>= 3.6.4), dplyr (>= 1.1.0), lifecycle (>= 0.2.0), logger (>= 0.2.0), diff --git a/R/module_input.R b/R/module_input.R index d532de7e..a6931856 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -48,9 +48,7 @@ module_input_server <- function(id, spec, data) { # resolved <- !is.character(spec_v$names) && all(x %in% spec_v$names) && any(!x %in% spec_v$select) if (!is.null(x) && any(nzchar(x))) { - spec <- spec |> - update_spec(variable, x) |> - resolver(d) + spec <- resolver(update_spec(spec, variable, x), d) } else { spec <- resolver(spec, d) } diff --git a/R/resolver.R b/R/resolver.R index 112c26fa..2cd80544 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -9,18 +9,18 @@ #' @export #' #' @examples -#' dataset1 <- datasets(is.data.frame) -#' dataset2 <- datasets(is.matrix) -#' spec <- dataset1 & variables("a", "a") +#' dataset1 <- datasets(where(is.data.frame)) +#' dataset2 <- datasets(where(is.matrix)) +#' spec <- c(dataset1, variables("a", "a")) #' td <- within(teal.data::teal_data(), { #' df <- data.frame(a = as.factor(LETTERS[1:5]), b = letters[1:5]) #' df2 <- data.frame(a = LETTERS[1:5], b = 1:5) #' m <- matrix() #' }) -#' resolver(spec | dataset2, td) +#' resolver(list(spec, dataset2), td) #' resolver(dataset2, td) #' resolver(spec, td) -#' spec <- dataset1 & variables("a", is.factor) +#' spec <- c(dataset1, variables("a", where(is.character))) #' resolver(spec, td) resolver <- function(spec, data) { if (!inherits(data, "qenv")) { @@ -98,94 +98,16 @@ determine.transform <- function(type, data, ..., spec) { list(type = type, data = data) # It is the transform object resolved. } -functions_names <- function(spec_criteria, names) { - stopifnot(is.character(names) || is.factor(names) || is.null(names)) # Allows for NA characters - if (is.null(names)) { - return(NULL) - } - is_fc <- vapply(spec_criteria, is.function, logical(1L)) - functions <- spec_criteria[is_fc] - new_names <- vector("character") - - for (fun in functions) { - names_ok <- tryCatch(fun(names), - error = function(x) { - x - }, - warning = function(x) { - if (isTRUE(x) || isFALSE(x)) { - x - } else { - FALSE - } - } - ) - if (!is.logical(names_ok)) { - stop("Provided functions should return a logical object.") - } - if (any(names_ok)) { - new_names <- c(new_names, names[names_ok]) - } - } - old_names <- unique(unlist(spec_criteria[!is_fc], FALSE, FALSE)) - c(new_names, old_names) -} - -# Evaluate if the function applied to the data -# but we need to return the name of the data received -functions_data <- function(spec_criteria, names_data, data) { - stopifnot( - !is.null(data), - length(names_data) == 1L - ) # Must be something but not NULL - is_fc <- vapply(spec_criteria, is.function, logical(1L)) - functions <- spec_criteria[is_fc] - - l <- lapply(functions, function(fun) { - data_ok <- tryCatch(fun(data), - error = function(x) { - x - }, - warning = function(x) { - if (isTRUE(x) || isFALSE(x)) { - x - } else { - FALSE - } - } - ) - if (!is.logical(data_ok)) { - stop("Provided functions should return a logical object.") - } - if ((length(data_ok) == 1L && (any(data_ok)) || all(data_ok))) { - return(names_data) - } - }) - new_names <- unique(unlist(l, FALSE, FALSE)) - c(new_names, spec_criteria[!is_fc]) -} - # Checks that for the given type and data names and data it can be resolved # The workhorse of the resolver determine_helper <- function(type, data_names, data) { stopifnot(!is.null(type)) orig_names <- type$names orig_select <- type$select - orig_exc <- type$except if (is.delayed(type) && all(is.character(type$names))) { new_names <- intersect(data_names, type$names) - if (!is.null(type$except)) { - excludes <- c( - functions_names(type$except, data_names), - functions_data(type$except, data_names, data) - ) - type$except <- excludes - attr(type$except, "original") <- orig(orig_exc) - new_names <- setdiff(new_names, excludes) - } - type$names <- new_names if (length(new_names) == 0) { return(NULL) @@ -193,8 +115,7 @@ determine_helper <- function(type, data_names, data) { } else if (length(new_names) == 1L) { type$select <- new_names } else { - new_select <- functions_names(type$select, type$names) - new_select <- unique(new_select[!is.na(new_select)]) + new_select <- selector(data, type$names) if (!length(new_select)) { return(NULL) # stop("No ", is(type), " meet the requirements to be selected") @@ -202,51 +123,25 @@ determine_helper <- function(type, data_names, data) { type$select <- new_select } } else if (is.delayed(type)) { - new_names <- c( - functions_names(type$names, data_names), - functions_data(type$names, data_names, data) - ) - new_names <- unlist(unique(new_names[!is.na(new_names)]), - use.names = FALSE - ) - - if (!is.null(type$except)) { - excludes <- c( - functions_names(type$except, data_names), - functions_data(type$except, data_names, data) - ) - - type$except <- excludes - attr(type$except, "original") <- orig(orig_exc) - - new_names <- setdiff(new_names, excludes) - } - - if (!length(new_names)) { - return(NULL) - # stop("No ", is(type), " meet the requirements") - } - type$names <- new_names + new_names <- selector(data, type$select) + } - if (length(type$names) == 0) { - return(NULL) - # stop("No selected ", is(type), " matching the conditions requested") - } else if (length(type$names) == 1) { - type$select <- type$names - } - new_select <- c( - functions_names(type$select, type$names), - functions_data(type$select, type$names, data) - ) + if (!length(new_names)) { + return(NULL) + # stop("No ", is(type), " meet the requirements") + } + type$names <- new_names - new_select <- unique(new_select[!is.na(new_select)]) - if (!length(new_select)) { - stop("No ", is(type), " meet the requirements to be selected") - return(NULL) - } - type$select <- new_select + if (length(type$names) == 0) { + return(NULL) + # stop("No selected ", is(type), " matching the conditions requested") + } else if (length(type$names) == 1) { + type$select <- type$names } + + new_select <- selector(data, type$select) + type$select <- new_select attr(type$names, "original") <- orig(orig_names) attr(type$select, "original") <- orig(orig_select) resolved(type) @@ -261,11 +156,11 @@ determine.datasets <- function(type, data, ...) { } # Assumes the object has colnames method (true for major object classes: DataFrame, tibble, Matrix, array) - # FIXME: What happens if colnames is null: array(dim = c(4, 2)) |> colnames() + # FIXME: What happens if colnames is null: colnames(array(dim = c(4, 2))) type <- eval_type_names(type, data) if (is.null(type$names) || !length(type$names)) { - stop("No ", class(type), " meet the specification.", call. = FALSE) + stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) } type <- eval_type_select(type, data[unorig(type$names)]) @@ -273,30 +168,29 @@ determine.datasets <- function(type, data, ...) { if (!is.delayed(type) && length(type$select) == 1L) { list(type = type, data = data[[unorig(type$select)]]) } else { - list(type = type, data = NULL) + list(type = type, data = data[unorig(type$select)]) } } #' @export determine.variables <- function(type, data, ...) { - if (is.null(data)) { return(list(type = type, data = NULL)) } else if (length(dim(data)) != 2L) { - stop("Can't resolve variables from this object of class ", - toString(sQuote(class(data)))) + stop( + "Can't resolve variables from this object of class ", + toString(sQuote(class(data))) + ) } if (ncol(data) <= 0L) { stop("Can't pull variable: No variable is available.") } - # Assumes the object has colnames method (true for major object classes: DataFrame, tibble, Matrix, array) - # FIXME: What happens if colnames is null: array(dim = c(4, 2)) |> colnames() type <- eval_type_names(type, data) if (is.null(type$names) || !length(type$names)) { - stop("No ", class(type), " meet the specification.", call. = FALSE) + stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) } type <- eval_type_select(type, data) @@ -384,14 +278,25 @@ determine.variables <- function(type, data, ...) { #' @export determine.values <- function(type, data, ...) { - type <- determine_helper(type, names(data), data) + if (!is.numeric(data)) { + d <- data + names(d) <- data + } else { + d <- data + } + sel <- selector(d, type$names) + type$names <- data[sel] + + + sel2 <- selector(d[sel], type$select) + type$select <- data[sel][sel2] # Not possible to know what is happening if (is.delayed(type)) { return(list(type = type, data = NULL)) } - list(type = type, data = type$select) + list(type = type, data = data[sel]) } orig <- function(x) { @@ -407,6 +312,9 @@ eval_type_select <- function(type, data) { stopifnot(is.character(type$names)) orig_select <- orig(type$select) + if (length(orig_select) == 1L) { + orig_select <- orig_select[[1L]] + } names <- seq_along(type$names) names(names) <- type$names @@ -414,6 +322,9 @@ eval_type_select <- function(type, data) { # Keep only those that were already selected new_select <- intersect(unique(names(c(select_data))), unorig(type$names)) + if (is.null(new_select)) { + stop("No ", is(type), " selection is possible.") + } attr(new_select, "original") <- orig_select type$select <- new_select @@ -424,9 +335,13 @@ eval_type_select <- function(type, data) { eval_type_names <- function(type, data) { orig_names <- orig(type$names) + if (length(orig_names) == 1L) { + orig_names <- orig_names[[1L]] + } + new_names <- selector(data, type$names) - new_names <- unique(names(new_names)) + new_names <- unique(names(new_names)) attr(new_names, "original") <- orig_names type$names <- new_names diff --git a/R/types.R b/R/types.R index 4267d334..51c3884c 100644 --- a/R/types.R +++ b/R/types.R @@ -4,7 +4,7 @@ is.transform <- function(x) { valid_transform <- function(x) { - !((is.type(x) || is.transform(x)) || or.transform(x)) + !((is.type(x) || is.transform(x))) } na_type <- function(type) { @@ -37,13 +37,24 @@ first <- function(x) { return(FALSE) } -check_input <- function(input) { - is.character(input) || is.function(input) || - (is.list(input) && all(vapply(input, function(x) { - is.function(x) || is.character(x) - }, logical(1L)))) +first_var <- function(offset = 0L, vars = NULL) { + if (!rlang::is_integerish(offset, n = 1)) { + not <- class(offset) + cli::cli_abort("{.arg offset} must be a single integer, not {not}.") + } + vars <- vars %||% tidyselect::peek_vars(fn = "first_var") + n <- length(vars) + if (offset > n) { + cli::cli_abort("{.arg offset} ({offset}) must be smaller than the number of columns ({n}).") + } else if (n == 0) { + cli::cli_abort("Can't select last column when input is empty.") + } else { + 1L + } } +last_var <- tidyselect::last_col + type_helper <- function(x, select, type) { out <- list(names = x, select = select) class(out) <- c(type, "type", "list") @@ -62,32 +73,32 @@ type_helper <- function(x, select, type) { #' @returns An object of the same class as the function with two elements: names the content of x, and select. #' @examples #' datasets("A") -#' datasets("A") | datasets("B") -#' datasets(is.data.frame) -#' datasets("A") & variables(is.numeric) +#' c(datasets("A"), datasets("B")) +#' datasets(where(is.data.frame)) +#' c(datasets("A"), variables(where(is.numeric))) NULL #' @describeIn types Specify datasets. #' @export -datasets <- function(x, select = everything()) { +datasets <- function(x, select = 1) { type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "datasets") } #' @describeIn types Specify variables. #' @export -variables <- function(x, select = everything()) { +variables <- function(x, select = 1) { type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "variables") } -#' @describeIn types Specify colData of SummarizedExperiment and derived classes. +#' @describeIn types Specify colData. #' @export -colData <- function(x, select = everything()) { +mae_colData <- function(x, select = 1) { type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "colData") } #' @describeIn types Specify values. #' @export -values <- function(x, select = everything()) { +values <- function(x, select = 1) { type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "values") } @@ -166,14 +177,16 @@ c.type <- function(...) { } orig_names <- unique(orig(new_type$names)) orig_select <- unique(orig(new_type$select)) + new_type$names <- unique(new_type$names) + if (length(new_type$names) == 1) { + new_type$names <- new_type$names[[1]] + } attr(new_type$names, "original") <- orig_names - # From the possible names apply the original function - if (is.delayed(new_type)) { - new_type$select <- functions_names(orig(new_type$select), new_type$names) + if (length(new_type$select) == 1) { + new_type$select <- new_type$select[[1]] } - attr(new_type$select, "original") <- orig_select class(new_type) <- c(t, "type", "list") @@ -208,7 +221,7 @@ print.type <- function(x, ...) { ) } if (nam_values) { - msg_values <- paste0(msg_values, paste0(sQuote(x$names[!nam_functions]), collapse = ", "), + msg_values <- paste0(msg_values, paste0(rlang::as_label(x$names[!nam_functions]), collapse = ", "), " as possible choices.", collapse = "\n" ) @@ -224,7 +237,7 @@ print.type <- function(x, ...) { ) } if (sel_values) { - msg_sel <- paste0(msg_sel, paste0(sQuote(x$select[!sel_functions]), collapse = ", "), + msg_sel <- paste0(msg_sel, paste0(rlang::as_label(x$select[!sel_functions]), collapse = ", "), " selected.", collapse = "\n" ) @@ -239,7 +252,7 @@ print.type <- function(x, ...) { ) } if (sel_values) { - msg_exc <- paste0(msg_exc, paste0(sQuote(x$except[!exc_functions]), collapse = ", "), + msg_exc <- paste0(msg_exc, paste0(rlang::as_label(x$except[!exc_functions]), collapse = ", "), " excluded.", collapse = "\n" ) diff --git a/R/update_spec.R b/R/update_spec.R index 00e6efa8..4d637839 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -17,7 +17,7 @@ #' Ab = as.factor(letters[1:5]) #' ) #' }) -#' data_frames_factors <- datasets(is.data.frame) & variables(is.factor) +#' data_frames_factors <- c(datasets(where(is.data.frame)), variables(where(is.factor))) #' res <- resolver(data_frames_factors, td) #' update_spec(res, "datasets", "df_n") #' # update_spec(res, "datasets", "error") @@ -91,10 +91,10 @@ update_s_spec <- function(spec, type, value) { if (is.na(spec[[type_restart]])) { next } - fun <- match.fun(type_restart) - restored_transform <- fun( - x = orig(spec[[type_restart]]$names), - select = orig(spec[[type_restart]]$select) + restored_transform <- type_helper( + type = type_restart, + x = orig(spec[[type_restart]]$names)[[1]], + select = orig(spec[[type_restart]]$select)[[1]] ) spec[[type_restart]] <- restored_transform } diff --git a/inst/WORDLIST b/inst/WORDLIST index 69f70fc7..f6b2e6ca 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,10 +1,12 @@ CDISC -Forkers -Hoffmann -Shinylive -UI cloneable +colData +Forkers funder +Hoffmann preselected +qenv repo reproducibility +Shinylive +UI diff --git a/man/resolver.Rd b/man/resolver.Rd index 9a05f1c9..628952c4 100644 --- a/man/resolver.Rd +++ b/man/resolver.Rd @@ -19,17 +19,17 @@ Given the specification of some data to extract find if they are available or no The specification for selecting a variable shouldn't depend on the data of said variable. } \examples{ -dataset1 <- datasets(is.data.frame) -dataset2 <- datasets(is.matrix) -spec <- dataset1 & variables("a", "a") +dataset1 <- datasets(where(is.data.frame)) +dataset2 <- datasets(where(is.matrix)) +spec <- c(dataset1, variables("a", "a")) td <- within(teal.data::teal_data(), { df <- data.frame(a = as.factor(LETTERS[1:5]), b = letters[1:5]) df2 <- data.frame(a = LETTERS[1:5], b = 1:5) m <- matrix() }) -resolver(spec | dataset2, td) +resolver(list(spec, dataset2), td) resolver(dataset2, td) resolver(spec, td) -spec <- dataset1 & variables("a", is.factor) +spec <- c(dataset1, variables("a", where(is.character))) resolver(spec, td) } diff --git a/man/types.Rd b/man/types.Rd index 731ae80f..0295bf5d 100644 --- a/man/types.Rd +++ b/man/types.Rd @@ -5,22 +5,16 @@ \alias{datasets} \alias{variables} \alias{mae_colData} -\alias{mae_sampleMap} -\alias{mae_experiments} \alias{values} \title{Type specification} \usage{ -datasets(x, select = first) +datasets(x, select = 1) -variables(x, select = first) +variables(x, select = 1) -mae_colData(x, select = first) +mae_colData(x, select = 1) -mae_sampleMap(x, select = first) - -mae_experiments(x, select = first) - -values(x, select = first) +values(x, select = 1) } \arguments{ \item{x}{Character specifying the names or functions to select them. The functions will be applied on the data or the names.} @@ -39,18 +33,14 @@ Define how to select and extract data \item \code{variables()}: Specify variables. -\item \code{mae_colData()}: Specify variables of MultiAssayExperiment col Data. - -\item \code{mae_sampleMap()}: Specify variables of MultiAssayExperiment sampleMap. - -\item \code{mae_experiments()}: Specify variables of MultiAssayExperiment experiments. +\item \code{mae_colData()}: Specify colData. \item \code{values()}: Specify values. }} \examples{ datasets("A") -datasets("A") | datasets("B") -datasets(is.data.frame) -datasets("A") & variables(is.numeric) +c(datasets("A"), datasets("B")) +datasets(where(is.data.frame)) +c(datasets("A"), variables(where(is.numeric))) } diff --git a/man/update_spec.Rd b/man/update_spec.Rd index a1db5f32..eb750cb4 100644 --- a/man/update_spec.Rd +++ b/man/update_spec.Rd @@ -30,7 +30,7 @@ td <- within(teal.data::teal_data(), { Ab = as.factor(letters[1:5]) ) }) -data_frames_factors <- datasets(is.data.frame) & variables(is.factor) +data_frames_factors <- c(datasets(where(is.data.frame)), variables(where(is.factor))) res <- resolver(data_frames_factors, td) update_spec(res, "datasets", "df_n") # update_spec(res, "datasets", "error") diff --git a/tests/testthat/test-delayed.R b/tests/testthat/test-delayed.R index 7b9a9beb..17c2ab8f 100644 --- a/tests/testthat/test-delayed.R +++ b/tests/testthat/test-delayed.R @@ -2,9 +2,9 @@ test_that("is.delayed works", { d <- datasets("a") v <- variables("b") expect_true(is.delayed(d)) - expect_false(is.delayed(datasets("a", "a"))) + expect_true(is.delayed(datasets("a", "a"))) expect_true(is.delayed(v)) - expect_false(is.delayed(variables("b", "b"))) + expect_true(is.delayed(variables("b", "b"))) expect_true(is.delayed(c(d, v))) expect_false(is.delayed(1)) da <- datasets(is.data.frame) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 7cf7c6a4..e43b0370 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -5,9 +5,9 @@ f <- function(x) { test_that("resolver datasets works", { df_head <- datasets("df") df_first <- datasets("df") - matrices <- datasets(is.matrix) - df_mean <- datasets("df", mean) - median_mean <- datasets(median, mean) + matrices <- datasets(where(is.matrix)) + df_mean <- datasets("df", where(mean)) + median_mean <- datasets(where(median), where(mean)) td <- within(teal.data::teal_data(), { df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) m <- cbind(b = 1:5, c = 10:14) @@ -16,23 +16,23 @@ test_that("resolver datasets works", { expect_no_error(resolver(df_head, td)) expect_no_error(resolver(df_first, td)) out <- resolver(matrices, td) - expect_length(out$select, 1L) # Because we use first - expect_no_error(resolver(df_mean, td)) + expect_length(out$select, 2L) # Because we use everything + expect_error(expect_warning(resolver(df_mean, td))) expect_error(resolver(median_mean, td)) }) test_that("resolver variables works", { df <- datasets("df") - matrices <- datasets(is.matrix) - data_frames <- datasets(is.data.frame) + matrices <- datasets(where(is.matrix)) + data_frames <- datasets(where(is.data.frame)) var_a <- variables("a") - factors <- variables(is.factor) - factors_head <- variables(is.factor, function(x) { + factors <- variables(where(is.factor)) + factors_head <- variables(where(is.factor), where(function(x) { head(x, 1) - }) - var_matrices_head <- variables(is.matrix, function(x) { + })) + var_matrices_head <- variables(where(is.matrix), where(function(x) { head(x, 1) - }) + })) td <- within(teal.data::teal_data(), { df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) m <- cbind(b = 1:5, c = 10:14) @@ -58,16 +58,16 @@ test_that("resolver variables works", { test_that("resolver values works", { df <- datasets("df") - matrices <- datasets(is.matrix) - data_frames <- datasets(is.data.frame) + matrices <- datasets(where(is.matrix)) + data_frames <- datasets(where(is.data.frame)) var_a <- variables("a") factors <- variables(is.factor) - factors_head <- variables(is.factor, function(x) { + factors_head <- variables(where(is.factor), where(function(x) { head(x, 1) - }) - var_matrices_head <- variables(is.matrix, function(x) { + })) + var_matrices_head <- variables(where(is.matrix), where(function(x) { head(x, 1) - }) + })) val_A <- values("A") td <- within(teal.data::teal_data(), { df <- data.frame(a = LETTERS[1:5], b = factor(letters[1:5]), c = factor(letters[1:5])) @@ -103,29 +103,30 @@ test_that("names and variables are reported", { m <- matrix() }) d_df <- datasets("df") - df_upper_variables <- c(d_df, variables(function(x) { + upper_variables <- variables(where(function(x) { x == toupper(x) })) - out <- resolver(df_upper_variables, td) + df_upper_variables <- c(d_df, upper_variables) + expect_error(resolver(df_upper_variables, td)) # This should select A and Ab: # A because the name is all capital letters and # Ab values is all upper case. - expect_length(out$variables$names, 2) - v_all_upper <- variables(function(x) { + # expect_length(out$variables$names, 2) + v_all_upper <- variables(where(function(x) { all(x == toupper(x)) - }) + })) df_all_upper_variables <- c(d_df, v_all_upper) expect_no_error(out <- resolver(df_all_upper_variables, td)) - expect_length(out$variables$names, 2L) + expect_length(out$variables$names, 1L) expect_no_error(out <- resolver(c(datasets("df2"), v_all_upper), td)) expect_length(out$variables$names, 2L) expect_no_error(out <- resolver(datasets(function(x) { is.data.frame(x) && all(colnames(x) == toupper(colnames(x))) }), td)) expect_length(out$names, 1L) - expect_no_error(out <- resolver(datasets(function(x) { + expect_no_error(out <- resolver(datasets(where(function(x) { is.data.frame(x) || any(colnames(x) == toupper(colnames(x))) - }), td)) + })), td)) expect_length(out$names, 2L) }) @@ -140,40 +141,13 @@ test_that("update_spec resolves correctly", { Ab = as.factor(letters[1:5]) ) }) - data_frames_factors <- c(datasets(is.data.frame), variables(is.factor)) + data_frames_factors <- c(datasets(where(is.data.frame)), variables(where(is.factor))) expect_false(is.null(attr(data_frames_factors$datasets$names, "original"))) expect_false(is.null(attr(data_frames_factors$datasets$select, "original"))) expect_false(is.null(attr(data_frames_factors$variables$names, "original"))) expect_false(is.null(attr(data_frames_factors$variables$select, "original"))) - res <- resolver(data_frames_factors, td) - expect_false(is.null(attr(res$datasets$names, "original"))) - expect_false(is.null(attr(res$datasets$select, "original"))) - expect_false(is.null(attr(res$variables$names, "original"))) - expect_false(is.null(attr(res$variables$select, "original"))) - - res2 <- update_spec(res, "datasets", "df_n") - expect_false(is.null(attr(res2$datasets$names, "original"))) - expect_false(is.null(attr(res2$datasets$select, "original"))) - expect_false(is.null(attr(res2$variables$names, "original"))) - expect_false(is.null(attr(res2$variables$select, "original"))) - - expect_no_error(res3 <- resolver(res2, td)) - expect_false(is.null(attr(res3$datasets$names, "original"))) - expect_false(is.null(attr(res3$datasets$select, "original"))) - expect_equal(attr(res3$datasets$names, "original"), attr(data_frames_factors$datasets$names, "original")) - expect_equal(attr(res3$datasets$select, "original"), attr(data_frames_factors$datasets$select, "original")) - expect_equal(res3$datasets$select, "df_n", check.attributes = FALSE) - expect_equal(res3$variables$select, "Ab", check.attributes = FALSE) - expect_false(is.null(attr(res3$variables$names, "original"))) - expect_false(is.null(attr(res3$variables$select, "original"))) - expect_equal(attr(res3$variables$names, "original"), attr(data_frames_factors$variables$names, "original")) - expect_equal(attr(res3$variables$select, "original"), attr(data_frames_factors$variables$select, "original")) - - expect_error(update_spec(res, "datasets", "error")) - expect_error(update_spec(data_frames_factors, "datasets", "error")) - expect_error(update_spec(datasets(x = c("df", "df2")), "datasets", "df2")) - expect_no_error(update_spec(datasets(x = c("df", "df2"), "df"), "datasets", "df2")) + expect_error(resolver(data_frames_factors, td)) }) test_that("OR specifications resolves correctly", { @@ -182,8 +156,8 @@ test_that("OR specifications resolves correctly", { m <- cbind(A = 1:5, B = 5:10) }) var_a <- variables("A") - df_a <- c(datasets(is.data.frame), var_a) - matrix_a <- c(datasets(is.matrix), var_a) + df_a <- c(datasets(where(is.data.frame)), var_a) + matrix_a <- c(datasets(where(is.matrix)), var_a) df_or_m_var_a <- list(df_a, matrix_a) out <- resolver(df_or_m_var_a, td) expect_true(all(vapply(out, is.transform, logical(1L)))) @@ -195,8 +169,8 @@ test_that("OR specifications fail correctly", { m <- cbind(A = 1:5, B = 5:10) }) var_a <- variables("A") - df_a <- c(datasets(is.data.frame), var_a) - matrix_a <- c(datasets(is.matrix), var_a) + df_a <- c(datasets(where(is.data.frame)), var_a) + matrix_a <- c(datasets(where(is.matrix)), var_a) df_or_m_var_a <- list(df_a, matrix_a) out <- resolver(df_or_m_var_a, td) expect_error(update_spec(out, "variables", "B")) @@ -208,8 +182,8 @@ test_that("OR update_spec filters specifications", { m <- cbind(A = 1:5, B = 5:10) }) var_a <- variables("A") - df_a <- c(datasets(is.data.frame), var_a) - matrix_a <- c(datasets(is.matrix), var_a) + df_a <- c(datasets(where(is.data.frame)), var_a) + matrix_a <- c(datasets(where(is.matrix)), var_a) df_or_m_var_a <- list(df_a, matrix_a) resolved <- resolver(df_or_m_var_a, td) # The second option is not possible to have it as df diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index 39795174..2f99472c 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -1,26 +1,21 @@ test_that("datasets", { expect_no_error(dataset0 <- datasets("df", "df")) - out <- list(names = "df", select = "df") - class(out) <- c("delayed", "datasets", "type", "list") - expect_equal(dataset0, out, check.attributes = FALSE) expect_no_error(dataset1 <- datasets("df")) - expect_true(is(dataset1$names, "vector")) - expect_no_error(dataset2 <- datasets(is.matrix)) - expect_true(is(dataset2$names, "vector")) - expect_no_error(dataset3 <- datasets(is.data.frame)) + expect_no_error(dataset2 <- datasets(where(is.matrix))) + expect_no_error(dataset3 <- datasets(where(is.data.frame))) }) test_that("variables", { expect_no_error(var0 <- variables("a", "a")) expect_no_error(var1 <- variables("a")) - expect_no_error(var2 <- variables(is.factor)) + expect_no_error(var2 <- variables(where(is.factor))) # Allowed to specify whatever we like, it is not until resolution that this raises errors - expect_no_error(var3 <- variables(is.factor, function(x) { + expect_no_error(var3 <- variables(where(is.factor), where(function(x) { head(x, 1) - })) - expect_no_error(var4 <- variables(is.matrix, function(x) { + }))) + expect_no_error(var4 <- variables(where(is.matrix), where(function(x) { head(x, 1) - })) + }))) }) test_that("raw combine of types", { @@ -31,20 +26,20 @@ test_that("raw combine of types", { test_that("combine types", { expect_no_error(c( - datasets(is.data.frame, select = "df1"), - variables(is.numeric) + datasets(where(is.data.frame), select = "df1"), + variables(where(is.numeric)) )) }) test_that("values", { expect_no_error(val0 <- values("a", "a")) expect_no_error(val1 <- values("a")) - expect_no_error(val2 <- values(is.factor)) + expect_no_error(val2 <- values(where(is.factor))) # Allowed to specify whatever we like, it is not until resolution that this raises errors - expect_no_error(val3 <- values(is.factor, function(x) { + expect_no_error(val3 <- values(where(is.factor), function(x) { head(x, 1) })) - expect_no_error(val4 <- values(is.matrix, function(x) { + expect_no_error(val4 <- values(where(is.matrix), function(x) { head(x, 1) })) }) From 18df6fc6dec90d2552dc75ccde20087d5862a03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 6 May 2025 10:27:03 +0200 Subject: [PATCH 091/110] Fix remaining tests --- tests/testthat/test-resolver.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index e43b0370..f218d9a9 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -16,7 +16,7 @@ test_that("resolver datasets works", { expect_no_error(resolver(df_head, td)) expect_no_error(resolver(df_first, td)) out <- resolver(matrices, td) - expect_length(out$select, 2L) # Because we use everything + expect_length(out$select, 1L) # Because we use 1 expect_error(expect_warning(resolver(df_mean, td))) expect_error(resolver(median_mean, td)) }) @@ -45,7 +45,7 @@ test_that("resolver variables works", { expect_error(resolver(c(df, var_matrices_head), td)) - expect_error(resolver(c(matrices, var_a), td)) # datasets selection overpasses variable choices. + expect_no_error(resolver(c(matrices, var_a), td)) expect_error(resolver(c(matrices, factors), td)) expect_error(resolver(c(matrices, factors_head), td)) expect_error(resolver(c(matrices, var_matrices_head), td)) @@ -147,7 +147,7 @@ test_that("update_spec resolves correctly", { expect_false(is.null(attr(data_frames_factors$variables$names, "original"))) expect_false(is.null(attr(data_frames_factors$variables$select, "original"))) - expect_error(resolver(data_frames_factors, td)) + expect_no_error(resolver(data_frames_factors, td)) }) test_that("OR specifications resolves correctly", { From 5cadc4325bfe487df3dc92e4156f0fef4ed5e6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 6 May 2025 10:45:57 +0200 Subject: [PATCH 092/110] Fix bugs --- R/merge_dataframes.R | 16 ++++++++++------ R/update_spec.R | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 6a3a15a0..9e3b97f1 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -8,8 +8,12 @@ consolidate_extraction <- function(...) { } # Assume the data is a data.frame so no other specifications types are present. - datasets <- lapply(input_resolved, function(x){x$datasets}) - variables <- lapply(input_resolved, function(x){x$variables}) + datasets <- lapply(input_resolved, function(x) { + x$datasets + }) + variables <- lapply(input_resolved, function(x) { + x$variables + }) lapply(unique(datasets), function(dataset, x, y) { list( @@ -29,7 +33,9 @@ add_ids <- function(input, data) { return(input) } - datasets <- lapply(input, function(x){x$datasets}) + datasets <- lapply(input, function(x) { + x$datasets + }) for (i in seq_along(input)) { x <- input[[i]] # Avoid adding as id something already present: No duplicating input. @@ -63,10 +69,9 @@ extract_ids <- function(input, data) { merge_call_pair <- function(selections, by, data, merge_function = "dplyr::full_join", anl_name = "ANL") { - selections <- consolidate_extraction(selections) stopifnot(length(selections) == 2L) - datasets <- unique(unlist(lapply(selections, `[[`, datasets), FALSE, FALSE)) + datasets <- unique(unlist(lapply(selections, `[[`, "datasets"), FALSE, FALSE)) stopifnot(length(datasets) >= 2) by <- extract_ids(input = selections, data) @@ -93,7 +98,6 @@ merge_call_pair <- function(selections, by, data, merge_call_multiple <- function(input, ids, merge_function, data, anl_name = "ANL") { - input <- consolidate_extraction(input) datasets <- unique(unlist(lapply(input, `[[`, "datasets"), FALSE, FALSE)) stopifnot(is.character(datasets) && length(datasets) >= 1L) diff --git a/R/update_spec.R b/R/update_spec.R index 4d637839..b02e26e2 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -93,8 +93,8 @@ update_s_spec <- function(spec, type, value) { } restored_transform <- type_helper( type = type_restart, - x = orig(spec[[type_restart]]$names)[[1]], - select = orig(spec[[type_restart]]$select)[[1]] + x = orig(spec[[type_restart]]$names), + select = orig(spec[[type_restart]]$select) ) spec[[type_restart]] <- restored_transform } From 50576d654a0460a4c4c29caaf1382eaaf08322d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 6 May 2025 14:18:49 +0200 Subject: [PATCH 093/110] Add method for colData and avoid Infinite recursion --- NAMESPACE | 1 + R/resolver.R | 135 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 80 insertions(+), 56 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 9e66c7b1..5d596b3e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,7 @@ S3method(data_extract_multiple_srv,list) S3method(data_extract_multiple_srv,reactive) S3method(data_extract_srv,FilteredData) S3method(data_extract_srv,list) +S3method(determine,colData) S3method(determine,datasets) S3method(determine,default) S3method(determine,transform) diff --git a/R/resolver.R b/R/resolver.R index 2cd80544..765d883b 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -69,17 +69,40 @@ determine <- function(type, data, ...) { #' @export determine.default <- function(type, data, ..., spec) { - # Used when the type is of class list. - if (!is.null(names(type)) && is.delayed(type)) { - return(determine(type, data)) + type <- eval_type_names(type, data) + + if (is.null(type$names) || !length(type$names)) { + stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) } - d <- data - for (i in seq_along(type)) { - di <- determine(type[[i]], d, spec = spec) - type[[i]] <- di$type - d <- di$data + + type <- eval_type_select(type, data[unorig(type$names)]) + + if (!is.delayed(type) && length(type$select) == 1L) { + list(type = type, data = data[[unorig(type$select)]]) + } else { + list(type = type, data = data[unorig(type$select)]) + } +} + +#' @export +determine.colData <- function(type, data, ..., spec) { + if (!requireNamespace("SummarizedExperiment", quietly = TRUE)) { + stop("Requires SummarizedExperiment package from Bioconductor.") + } + data <- as.data.frame(colData(data)) + type <- eval_type_names(type, data) + + if (is.null(type$names) || !length(type$names)) { + stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) + } + + type <- eval_type_select(type, data[unorig(type$names)]) + + if (!is.delayed(type) && length(type$select) == 1L) { + list(type = type, data = data[[unorig(type$select)]]) + } else { + list(type = type, data = data[unorig(type$select)]) } - list(type = type, data = data) } #' @export @@ -100,52 +123,52 @@ determine.transform <- function(type, data, ..., spec) { # Checks that for the given type and data names and data it can be resolved # The workhorse of the resolver -determine_helper <- function(type, data_names, data) { - stopifnot(!is.null(type)) - orig_names <- type$names - orig_select <- type$select - - if (is.delayed(type) && all(is.character(type$names))) { - new_names <- intersect(data_names, type$names) - - type$names <- new_names - if (length(new_names) == 0) { - return(NULL) - # stop("No selected ", is(type), " matching the conditions requested") - } else if (length(new_names) == 1L) { - type$select <- new_names - } else { - new_select <- selector(data, type$names) - if (!length(new_select)) { - return(NULL) - # stop("No ", is(type), " meet the requirements to be selected") - } - type$select <- new_select - } - } else if (is.delayed(type)) { - new_names <- selector(data, type$select) - } - - - if (!length(new_names)) { - return(NULL) - # stop("No ", is(type), " meet the requirements") - } - type$names <- new_names - - if (length(type$names) == 0) { - return(NULL) - # stop("No selected ", is(type), " matching the conditions requested") - } else if (length(type$names) == 1) { - type$select <- type$names - } - - new_select <- selector(data, type$select) - type$select <- new_select - attr(type$names, "original") <- orig(orig_names) - attr(type$select, "original") <- orig(orig_select) - resolved(type) -} +# determine_helper <- function(type, data_names, data) { +# stopifnot(!is.null(type)) +# orig_names <- type$names +# orig_select <- type$select +# +# if (is.delayed(type) && all(is.character(type$names))) { +# new_names <- intersect(data_names, type$names) +# +# type$names <- new_names +# if (length(new_names) == 0) { +# return(NULL) +# # stop("No selected ", is(type), " matching the conditions requested") +# } else if (length(new_names) == 1L) { +# type$select <- new_names +# } else { +# new_select <- selector(data, type$names) +# if (!length(new_select)) { +# return(NULL) +# # stop("No ", is(type), " meet the requirements to be selected") +# } +# type$select <- new_select +# } +# } else if (is.delayed(type)) { +# new_names <- selector(data, type$select) +# } +# +# +# if (!length(new_names)) { +# return(NULL) +# # stop("No ", is(type), " meet the requirements") +# } +# type$names <- new_names +# +# if (length(type$names) == 0) { +# return(NULL) +# # stop("No selected ", is(type), " matching the conditions requested") +# } else if (length(type$names) == 1) { +# type$select <- type$names +# } +# +# new_select <- selector(data, type$select) +# type$select <- new_select +# attr(type$names, "original") <- orig(orig_names) +# attr(type$select, "original") <- orig(orig_select) +# resolved(type) +# } #' @export determine.datasets <- function(type, data, ...) { @@ -201,7 +224,7 @@ determine.variables <- function(type, data, ...) { } # This works for matrices and data.frames of length 1 or multiple # be aware of drop behavior on tibble vs data.frame - list(type = type, data = data[, type$select]) + list(type = type, data = data[, type$select, drop = FALSE]) } # @export From a031ad931c65f7f5f8401d23ed72abc17b20f75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Thu, 8 May 2025 11:13:50 +0200 Subject: [PATCH 094/110] If selection is empty it stops --- R/resolver.R | 9 +++++++-- tests/testthat/test-resolver.R | 9 ++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index 765d883b..4a8d4aad 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -69,6 +69,11 @@ determine <- function(type, data, ...) { #' @export determine.default <- function(type, data, ..., spec) { + if (is.list(type) && is.null(names(type))) { + l <- lapply(type, determine, data = data, spec = spec) + return(l) + } + type <- eval_type_names(type, data) if (is.null(type$names) || !length(type$names)) { @@ -345,8 +350,8 @@ eval_type_select <- function(type, data) { # Keep only those that were already selected new_select <- intersect(unique(names(c(select_data))), unorig(type$names)) - if (is.null(new_select)) { - stop("No ", is(type), " selection is possible.") + if (is.null(new_select) || !length(new_select)) { + stop("No ", is(type), " selection is possible.", call. = FALSE) } attr(new_select, "original") <- orig_select diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index f218d9a9..a9a59825 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -40,7 +40,7 @@ test_that("resolver variables works", { }) expect_no_error(resolver(c(df, var_a), td)) - expect_no_error(resolver(c(df, factors), td)) + expect_error(resolver(c(df, factors), td)) expect_error(resolver(c(df, factors_head), td)) expect_error(resolver(c(df, var_matrices_head), td)) @@ -51,7 +51,7 @@ test_that("resolver variables works", { expect_error(resolver(c(matrices, var_matrices_head), td)) expect_no_error(resolver(c(data_frames, var_a), td)) - expect_no_error(resolver(c(data_frames, factors), td)) + expect_error(resolver(c(data_frames, factors), td)) expect_error(resolver(c(data_frames, factors_head), td)) expect_error(resolver(c(data_frames, var_matrices_head), td)) }) @@ -116,8 +116,7 @@ test_that("names and variables are reported", { all(x == toupper(x)) })) df_all_upper_variables <- c(d_df, v_all_upper) - expect_no_error(out <- resolver(df_all_upper_variables, td)) - expect_length(out$variables$names, 1L) + expect_error(out <- resolver(df_all_upper_variables, td)) expect_no_error(out <- resolver(c(datasets("df2"), v_all_upper), td)) expect_length(out$variables$names, 2L) expect_no_error(out <- resolver(datasets(function(x) { @@ -147,7 +146,7 @@ test_that("update_spec resolves correctly", { expect_false(is.null(attr(data_frames_factors$variables$names, "original"))) expect_false(is.null(attr(data_frames_factors$variables$select, "original"))) - expect_no_error(resolver(data_frames_factors, td)) + expect_error(resolver(data_frames_factors, td)) }) test_that("OR specifications resolves correctly", { From 153524443cbaaa966da47c2ef039d157e29d69b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 09:28:44 +0200 Subject: [PATCH 095/110] Rename x argument to names and transform class to specification --- NAMESPACE | 2 +- R/delayed.R | 30 +++++++++++++++--------------- R/resolver.R | 2 +- R/types.R | 40 ++++++++++++++++++++-------------------- R/update_spec.R | 6 +++--- man/is.delayed.Rd | 4 ++-- man/resolver.Rd | 2 +- man/types.Rd | 10 +++++----- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 5d596b3e..0b3b27ac 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,7 +1,7 @@ # Generated by roxygen2: do not edit by hand S3method(anyNA,type) -S3method(c,transform) +S3method(c,specification) S3method(c,type) S3method(data_extract_multiple_srv,FilteredData) S3method(data_extract_multiple_srv,list) diff --git a/R/delayed.R b/R/delayed.R index f94cd585..62e8772d 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -9,20 +9,20 @@ delay <- function(x) { #' Is the specification resolved? #' #' Check that the specification is resolved against a given data source. -#' @param x Object to be evaluated. +#' @param specification Object to be evaluated. #' @returns A single logical value. #' @examples #' is.delayed(1) #' is.delayed(variables("df", "df")) #' is.delayed(variables("df")) # Unknown selection #' @export -is.delayed <- function(x) { +is.delayed <- function(specification) { UseMethod("is.delayed") } #' @export #' @method is.delayed default -is.delayed.default <- function(x) { +is.delayed.default <- function(specification) { # FIXME: A warning? FALSE } @@ -30,31 +30,31 @@ is.delayed.default <- function(x) { # Handling a list of transformers e1 | e2 #' @export #' @method is.delayed list -is.delayed.list <- function(x) { - any(vapply(x, is.delayed, logical(1L))) +is.delayed.list <- function(specification) { + any(vapply(specification, is.delayed, logical(1L))) } #' @export #' @method is.delayed transform -is.delayed.transform <- function(x) { - any(vapply(x, is.delayed, logical(1L))) +is.delayed.transform <- function(specification) { + any(vapply(specification, is.delayed, logical(1L))) } #' @export #' @method is.delayed type -is.delayed.type <- function(x) { - if (!is.na(x)) { - return(!all(is.character(x$names)) || !all(is.character(x$select))) +is.delayed.type <- function(specification) { + if (!is.na(specification)) { + return(!all(is.character(specification$names)) || !all(is.character(specification$select))) } FALSE } -resolved <- function(x, type = is(x)) { - s <- all(is.character(x$names)) && all(is.character(x$select)) +resolved <- function(specification, type = is(specification)) { + s <- all(is.character(specification$names)) && all(is.character(specification$select)) - if (!s && !all(x$select %in% x$names)) { + if (!s && !all(specification$select %in% specification$names)) { stop("Selected ", type, " not resolved.") } - attr(x, "delayed") <- NULL - x + attr(specification, "delayed") <- NULL + specification } diff --git a/R/resolver.R b/R/resolver.R index 4a8d4aad..a1226b4a 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -5,7 +5,7 @@ #' @param spec A object extraction specification. #' @param data The qenv where the specification is evaluated. #' -#' @returns A transform but resolved +#' @returns A specification but resolved: the names and selection is the name of the objects (if possible). #' @export #' #' @examples diff --git a/R/types.R b/R/types.R index 51c3884c..c55dfbb2 100644 --- a/R/types.R +++ b/R/types.R @@ -1,10 +1,10 @@ -is.transform <- function(x) { - inherits(x, "transform") +is.specification <- function(x) { + inherits(x, "specification") } -valid_transform <- function(x) { - !((is.type(x) || is.transform(x))) +valid_specification <- function(x) { + !((is.type(x) || is.specification(x))) } na_type <- function(type) { @@ -55,10 +55,10 @@ first_var <- function(offset = 0L, vars = NULL) { last_var <- tidyselect::last_col -type_helper <- function(x, select, type) { - out <- list(names = x, select = select) +type_helper <- function(names, select, type) { + out <- list(names = names, select = select) class(out) <- c(type, "type", "list") - attr(out$names, "original") <- x + attr(out$names, "original") <- names attr(out$select, "original") <- select delay(out) } @@ -68,7 +68,7 @@ type_helper <- function(x, select, type) { #' @title Type specification #' @description #' Define how to select and extract data -#' @param x Character specifying the names or functions to select them. The functions will be applied on the data or the names. +#' @param names Character specifying the names or functions to select them. The functions will be applied on the data or the names. #' @param select Character of `x` or functions to select on x (only on names or positional not on the data of the variable). #' @returns An object of the same class as the function with two elements: names the content of x, and select. #' @examples @@ -80,33 +80,33 @@ NULL #' @describeIn types Specify datasets. #' @export -datasets <- function(x, select = 1) { - type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "datasets") +datasets <- function(names, select = 1) { + type_helper(names = rlang::enquo(names), select = rlang::enquo(select), type = "datasets") } #' @describeIn types Specify variables. #' @export -variables <- function(x, select = 1) { - type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "variables") +variables <- function(names, select = 1) { + type_helper(names = rlang::enquo(names), select = rlang::enquo(select), type = "variables") } #' @describeIn types Specify colData. #' @export -mae_colData <- function(x, select = 1) { - type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "colData") +mae_colData <- function(names, select = 1) { + type_helper(names = rlang::enquo(names), select = rlang::enquo(select), type = "colData") } #' @describeIn types Specify values. #' @export -values <- function(x, select = 1) { - type_helper(x = rlang::enquo(x), select = rlang::enquo(select), type = "values") +values <- function(names, select = 1) { + type_helper(names = rlang::enquo(names), select = rlang::enquo(select), type = "values") } #' @export -c.transform <- function(...) { +c.specification <- function(...) { l <- list(...) types <- lapply(l, names) - typesc <- vapply(l, is.transform, logical(1L)) + typesc <- vapply(l, is.specification, logical(1L)) if (!all(typesc)) { stop("An object in position ", which(!typesc), " is not a specification.") } @@ -144,7 +144,7 @@ c.transform <- function(...) { class(new_type) <- c(t, "type", "list") vector[[t]] <- new_type } - class(vector) <- c("transform", "list") + class(vector) <- c("specification", "list") vector } @@ -196,7 +196,7 @@ c.type <- function(...) { if (length(vector) == 1) { return(vector[[1]]) } - class(vector) <- c("transform", "list") + class(vector) <- c("specification", "list") vector } diff --git a/R/update_spec.R b/R/update_spec.R index b02e26e2..b74d4674 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -29,7 +29,7 @@ update_spec <- function(spec, type, value) { ) } - if (valid_transform(spec)) { + if (valid_specification(spec)) { stop("Unexpected object used as specification") } @@ -91,12 +91,12 @@ update_s_spec <- function(spec, type, value) { if (is.na(spec[[type_restart]])) { next } - restored_transform <- type_helper( + restored_specification <- type_helper( type = type_restart, x = orig(spec[[type_restart]]$names), select = orig(spec[[type_restart]]$select) ) - spec[[type_restart]] <- restored_transform + spec[[type_restart]] <- restored_specification } spec } diff --git a/man/is.delayed.Rd b/man/is.delayed.Rd index 49f4290a..7a83ec6a 100644 --- a/man/is.delayed.Rd +++ b/man/is.delayed.Rd @@ -4,10 +4,10 @@ \alias{is.delayed} \title{Is the specification resolved?} \usage{ -is.delayed(x) +is.delayed(specification) } \arguments{ -\item{x}{Object to be evaluated.} +\item{specification}{Object to be evaluated.} } \value{ A single logical value. diff --git a/man/resolver.Rd b/man/resolver.Rd index 628952c4..108f2f18 100644 --- a/man/resolver.Rd +++ b/man/resolver.Rd @@ -12,7 +12,7 @@ resolver(spec, data) \item{data}{The qenv where the specification is evaluated.} } \value{ -A transform but resolved +A specification but resolved: the names and selection is the name of the objects (if possible). } \description{ Given the specification of some data to extract find if they are available or not. diff --git a/man/types.Rd b/man/types.Rd index 0295bf5d..10409985 100644 --- a/man/types.Rd +++ b/man/types.Rd @@ -8,16 +8,16 @@ \alias{values} \title{Type specification} \usage{ -datasets(x, select = 1) +datasets(names, select = 1) -variables(x, select = 1) +variables(names, select = 1) -mae_colData(x, select = 1) +mae_colData(names, select = 1) -values(x, select = 1) +values(names, select = 1) } \arguments{ -\item{x}{Character specifying the names or functions to select them. The functions will be applied on the data or the names.} +\item{names}{Character specifying the names or functions to select them. The functions will be applied on the data or the names.} \item{select}{Character of \code{x} or functions to select on x (only on names or positional not on the data of the variable).} } From 1385c1f5dd95cb635d98a24b44fc2204b99235f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 10:12:04 +0200 Subject: [PATCH 096/110] Revert testing to allow empty selection --- NAMESPACE | 2 +- R/module_input.R | 2 +- R/resolver.R | 14 +++----------- tests/testthat/test-resolver.R | 10 +++++----- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 0b3b27ac..a6695192 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -11,7 +11,7 @@ S3method(data_extract_srv,list) S3method(determine,colData) S3method(determine,datasets) S3method(determine,default) -S3method(determine,transform) +S3method(determine,specification) S3method(determine,values) S3method(determine,variables) S3method(extract,default) diff --git a/R/module_input.R b/R/module_input.R index a6931856..d60794e2 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -32,7 +32,7 @@ module_input_ui <- function(id, label, spec) { #' @export module_input_server <- function(id, spec, data) { - stopifnot(is.transform(spec)) + stopifnot(is.specification(spec)) stopifnot(is.reactive(data)) stopifnot(is.character(id)) moduleServer(id, function(input, output, session) { diff --git a/R/resolver.R b/R/resolver.R index a1226b4a..69ebcf2c 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -39,7 +39,7 @@ resolver <- function(spec, data) { spec <- c(datasets(first), spec) } - stopifnot(is.list(spec) || is.transform(spec)) + stopifnot(is.list(spec) || is.specification(spec)) det <- determine(spec, data, spec = spec) if (is.null(names(det))) { return(lapply(det, `[[`, 1)) @@ -58,7 +58,7 @@ resolver <- function(spec, data) { #' @keywords internal #' @export determine <- function(type, data, ...) { - stopifnot(is.type(type) || is.list(type) || is.transform(type)) + stopifnot(is.type(type) || is.list(type) || is.specification(type)) if (!is.delayed(type) && length(type$select) > 1L) { return(list(type = type, data = data[unorig(type$select)])) } else if (!is.delayed(type) && length(type$select) == 1L) { @@ -75,11 +75,6 @@ determine.default <- function(type, data, ..., spec) { } type <- eval_type_names(type, data) - - if (is.null(type$names) || !length(type$names)) { - stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) - } - type <- eval_type_select(type, data[unorig(type$names)]) if (!is.delayed(type) && length(type$select) == 1L) { @@ -111,7 +106,7 @@ determine.colData <- function(type, data, ..., spec) { } #' @export -determine.transform <- function(type, data, ..., spec) { +determine.specification <- function(type, data, ..., spec) { stopifnot(inherits(data, "qenv")) d <- data for (i in seq_along(type)) { @@ -350,9 +345,6 @@ eval_type_select <- function(type, data) { # Keep only those that were already selected new_select <- intersect(unique(names(c(select_data))), unorig(type$names)) - if (is.null(new_select) || !length(new_select)) { - stop("No ", is(type), " selection is possible.", call. = FALSE) - } attr(new_select, "original") <- orig_select type$select <- new_select diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index a9a59825..48c33130 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -40,7 +40,7 @@ test_that("resolver variables works", { }) expect_no_error(resolver(c(df, var_a), td)) - expect_error(resolver(c(df, factors), td)) + expect_no_error(resolver(c(df, factors), td)) expect_error(resolver(c(df, factors_head), td)) expect_error(resolver(c(df, var_matrices_head), td)) @@ -51,7 +51,7 @@ test_that("resolver variables works", { expect_error(resolver(c(matrices, var_matrices_head), td)) expect_no_error(resolver(c(data_frames, var_a), td)) - expect_error(resolver(c(data_frames, factors), td)) + expect_no_error(resolver(c(data_frames, factors), td)) expect_error(resolver(c(data_frames, factors_head), td)) expect_error(resolver(c(data_frames, var_matrices_head), td)) }) @@ -116,7 +116,7 @@ test_that("names and variables are reported", { all(x == toupper(x)) })) df_all_upper_variables <- c(d_df, v_all_upper) - expect_error(out <- resolver(df_all_upper_variables, td)) + expect_no_error(out <- resolver(df_all_upper_variables, td)) expect_no_error(out <- resolver(c(datasets("df2"), v_all_upper), td)) expect_length(out$variables$names, 2L) expect_no_error(out <- resolver(datasets(function(x) { @@ -146,7 +146,7 @@ test_that("update_spec resolves correctly", { expect_false(is.null(attr(data_frames_factors$variables$names, "original"))) expect_false(is.null(attr(data_frames_factors$variables$select, "original"))) - expect_error(resolver(data_frames_factors, td)) + expect_no_error(resolver(data_frames_factors, td)) }) test_that("OR specifications resolves correctly", { @@ -159,7 +159,7 @@ test_that("OR specifications resolves correctly", { matrix_a <- c(datasets(where(is.matrix)), var_a) df_or_m_var_a <- list(df_a, matrix_a) out <- resolver(df_or_m_var_a, td) - expect_true(all(vapply(out, is.transform, logical(1L)))) + expect_true(all(vapply(out, is.specification, logical(1L)))) }) test_that("OR specifications fail correctly", { From 85787b840f6c34d07d198299ff230bdd0ca8fa68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 12:22:58 +0200 Subject: [PATCH 097/110] Fix problems with renamings --- NAMESPACE | 3 ++- R/delayed.R | 4 ++-- R/module_input.R | 2 +- R/update_spec.R | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index a6695192..10c63de6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,11 +15,12 @@ S3method(determine,specification) S3method(determine,values) S3method(determine,variables) S3method(extract,default) +S3method(extract,teal_data) S3method(filter_spec_internal,default) S3method(filter_spec_internal,delayed_data) S3method(is.delayed,default) S3method(is.delayed,list) -S3method(is.delayed,transform) +S3method(is.delayed,specification) S3method(is.delayed,type) S3method(is.na,type) S3method(merge_expression_module,list) diff --git a/R/delayed.R b/R/delayed.R index 62e8772d..595838f7 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -35,8 +35,8 @@ is.delayed.list <- function(specification) { } #' @export -#' @method is.delayed transform -is.delayed.transform <- function(specification) { +#' @method is.delayed specification +is.delayed.specification <- function(specification) { any(vapply(specification, is.delayed, logical(1L))) } diff --git a/R/module_input.R b/R/module_input.R index d60794e2..00104c59 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -17,7 +17,7 @@ module_input_ui <- function(id, label, spec) { a(label), ) - if (valid_transform(spec)) { + if (valid_specification(spec)) { stop("Unexpected object used as specification.") } diff --git a/R/update_spec.R b/R/update_spec.R index b74d4674..a65c53f8 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -93,7 +93,7 @@ update_s_spec <- function(spec, type, value) { } restored_specification <- type_helper( type = type_restart, - x = orig(spec[[type_restart]]$names), + names = orig(spec[[type_restart]]$names), select = orig(spec[[type_restart]]$select) ) spec[[type_restart]] <- restored_specification From 3fafc92bf5acb99223e8b2d9816adc8f4c7f24ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 12:24:26 +0200 Subject: [PATCH 098/110] Extract before tidyselect on names --- R/extract.R | 32 +++++++++------------------- R/resolver.R | 39 ++++++++++++++++------------------ tests/testthat/test-resolver.R | 12 ----------- 3 files changed, 28 insertions(+), 55 deletions(-) diff --git a/R/extract.R b/R/extract.R index 162620f8..6279ec6f 100644 --- a/R/extract.R +++ b/R/extract.R @@ -12,30 +12,9 @@ extract <- function(x, variable, ...) { UseMethod("extract") } -# Cases handled by the default method -# @export -# extract.MultiAssayExperiment <- function(x, variable) { -# # if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { -# # stop("Required to have MultiAssayExperiment's package.") -# # } -# x[, variable, drop = TRUE] -# } -# -# @export -# extract.DataFrame <- function(x, variable) { -# # if (!requireNamespace("S4Vectors", quietly = TRUE)) { -# # stop("Required to have S4Vectors's package.") -# # } -# x[, variable, drop = TRUE] -# } -# -# @export -# extract.matrix <- function(x, variable) { -# x[, variable, drop = TRUE] -# } #' @export -extract.default <- function(x, variable, ..., drop = TRUE) { +extract.default <- function(x, variable, ..., drop = FALSE) { if (length(dim(x)) == 2L || length(variable) > 1L) { x[, variable, drop = drop] } else { @@ -43,6 +22,15 @@ extract.default <- function(x, variable, ..., drop = TRUE) { } } +#' @export +extract.teal_data <- function(x, variable, ...) { + if (length(variable) > 1L) { + x[variable] + } else { + x[[variable]] + } +} + # @export # @method extract data.frame # extract.data.frame <- function(x, variable) { diff --git a/R/resolver.R b/R/resolver.R index 69ebcf2c..ee8e091a 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -331,8 +331,25 @@ unorig <- function(x) { x } +eval_type_names <- function(type, data) { + orig_names <- orig(type$names) + if (length(orig_names) == 1L) { + orig_names <- orig_names[[1L]] + } + + new_names <- selector(data, type$names) + + new_names <- unique(names(new_names)) + attr(new_names, "original") <- orig_names + + type$names <- new_names + + type +} + eval_type_select <- function(type, data) { stopifnot(is.character(type$names)) + data <- extract(data, type$names) orig_select <- orig(type$select) if (length(orig_select) == 1L) { @@ -341,30 +358,10 @@ eval_type_select <- function(type, data) { names <- seq_along(type$names) names(names) <- type$names - select_data <- selector(data, type$select) + new_select <- names(selector(data, type$select)) - # Keep only those that were already selected - new_select <- intersect(unique(names(c(select_data))), unorig(type$names)) attr(new_select, "original") <- orig_select - type$select <- new_select type } - - -eval_type_names <- function(type, data) { - orig_names <- orig(type$names) - if (length(orig_names) == 1L) { - orig_names <- orig_names[[1L]] - } - - new_names <- selector(data, type$names) - - new_names <- unique(names(new_names)) - attr(new_names, "original") <- orig_names - - type$names <- new_names - - type -} diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 48c33130..126a6121 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -77,18 +77,6 @@ test_that("resolver values works", { expect_no_error(resolver(c(df, var_a, val_A), td)) }) -test_that("resolver works with excluded types", { - td <- within(teal.data::teal_data(), { - df <- data.frame( - a = LETTERS[1:5], - b = factor(letters[1:5]), - c = factor(letters[1:5]) - ) - }) - # spec <- c(datasets("df"), variables(c("a", "b")), !variables("b")) - # expect_no_error(resolver(spec, td)) -}) - test_that("names and variables are reported", { td <- within(teal.data::teal_data(), { df <- data.frame( From 5ccfe34c9ee7849eefa62bd798f50670e1713bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 13:20:49 +0200 Subject: [PATCH 099/110] Reuse extraction --- R/resolver.R | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index ee8e091a..3faa3a30 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -59,10 +59,8 @@ resolver <- function(spec, data) { #' @export determine <- function(type, data, ...) { stopifnot(is.type(type) || is.list(type) || is.specification(type)) - if (!is.delayed(type) && length(type$select) > 1L) { - return(list(type = type, data = data[unorig(type$select)])) - } else if (!is.delayed(type) && length(type$select) == 1L) { - return(list(type = type, data = data[[unorig(type$select)]])) + if (!is.delayed(type)) { + return(list(type = type, data = extract(data, unorig(type$select)))) } UseMethod("determine") } @@ -186,13 +184,9 @@ determine.datasets <- function(type, data, ...) { stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) } - type <- eval_type_select(type, data[unorig(type$names)]) + type <- eval_type_select(type, data) - if (!is.delayed(type) && length(type$select) == 1L) { - list(type = type, data = data[[unorig(type$select)]]) - } else { - list(type = type, data = data[unorig(type$select)]) - } + list(type = type, data = extract(data, unorig(type$select))) } #' @export @@ -224,7 +218,7 @@ determine.variables <- function(type, data, ...) { } # This works for matrices and data.frames of length 1 or multiple # be aware of drop behavior on tibble vs data.frame - list(type = type, data = data[, type$select, drop = FALSE]) + list(type = type, data = extract(data, unorig(type$select))) } # @export @@ -349,8 +343,12 @@ eval_type_names <- function(type, data) { eval_type_select <- function(type, data) { stopifnot(is.character(type$names)) - data <- extract(data, type$names) - + if (!is(data, "qenv")) { + data <- extract(data, type$names) + } else { + # Do not extract; selection would be from the data extracted not from the names. + data <- data[type$names] + } orig_select <- orig(type$select) if (length(orig_select) == 1L) { orig_select <- orig_select[[1L]] From 10509da325a39eca8ee0481480f5d13c2225270b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 13:22:35 +0200 Subject: [PATCH 100/110] Match expectations based on names --- R/selector.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/selector.R b/R/selector.R index 55be6ed5..069f8628 100644 --- a/R/selector.R +++ b/R/selector.R @@ -1,6 +1,7 @@ selector <- function(data, ...) { if (is.environment(data)) { - data <- as.list(data) + # To keep the "order" of the names in the extraction: avoids suprises + data <- as.list(data)[names(data)] } else if (length(dim(data)) == 2L) { data <- as.data.frame(data) } From 5b8b51d4fc58b61fc46523ac63c920228b363eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 13:45:42 +0200 Subject: [PATCH 101/110] Clean up of unused functions and arguments --- R/resolver.R | 10 +++++----- R/types.R | 27 --------------------------- 2 files changed, 5 insertions(+), 32 deletions(-) diff --git a/R/resolver.R b/R/resolver.R index 3faa3a30..d82b29e5 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -40,7 +40,7 @@ resolver <- function(spec, data) { } stopifnot(is.list(spec) || is.specification(spec)) - det <- determine(spec, data, spec = spec) + det <- determine(spec, data) if (is.null(names(det))) { return(lapply(det, `[[`, 1)) } else { @@ -66,7 +66,7 @@ determine <- function(type, data, ...) { } #' @export -determine.default <- function(type, data, ..., spec) { +determine.default <- function(type, data, ...) { if (is.list(type) && is.null(names(type))) { l <- lapply(type, determine, data = data, spec = spec) return(l) @@ -83,7 +83,7 @@ determine.default <- function(type, data, ..., spec) { } #' @export -determine.colData <- function(type, data, ..., spec) { +determine.colData <- function(type, data, ...) { if (!requireNamespace("SummarizedExperiment", quietly = TRUE)) { stop("Requires SummarizedExperiment package from Bioconductor.") } @@ -104,11 +104,11 @@ determine.colData <- function(type, data, ..., spec) { } #' @export -determine.specification <- function(type, data, ..., spec) { +determine.specification <- function(type, data, ...) { stopifnot(inherits(data, "qenv")) d <- data for (i in seq_along(type)) { - di <- determine(type[[i]], d, spec = spec) + di <- determine(type[[i]], d) # overwrite so that next type in line receives the corresponding data and specification if (is.null(di$type)) { next diff --git a/R/types.R b/R/types.R index c55dfbb2..349ce0a7 100644 --- a/R/types.R +++ b/R/types.R @@ -28,33 +28,6 @@ anyNA.type <- function(x, recursive = FALSE) { anyNA(unclass(x[c("names", "select")]), recursive) } -first <- function(x) { - if (length(x) > 0) { - false <- rep_len(FALSE, length.out = length(x)) - false[1] <- TRUE - return(false) - } - return(FALSE) -} - -first_var <- function(offset = 0L, vars = NULL) { - if (!rlang::is_integerish(offset, n = 1)) { - not <- class(offset) - cli::cli_abort("{.arg offset} must be a single integer, not {not}.") - } - vars <- vars %||% tidyselect::peek_vars(fn = "first_var") - n <- length(vars) - if (offset > n) { - cli::cli_abort("{.arg offset} ({offset}) must be smaller than the number of columns ({n}).") - } else if (n == 0) { - cli::cli_abort("Can't select last column when input is empty.") - } else { - 1L - } -} - -last_var <- tidyselect::last_col - type_helper <- function(names, select, type) { out <- list(names = names, select = select) class(out) <- c(type, "type", "list") From 29c65009c060909ece7192255c238eeddd26d9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 13:50:13 +0200 Subject: [PATCH 102/110] Restore test --- tests/testthat/test-resolver.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 126a6121..02f6c907 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -44,8 +44,7 @@ test_that("resolver variables works", { expect_error(resolver(c(df, factors_head), td)) expect_error(resolver(c(df, var_matrices_head), td)) - - expect_no_error(resolver(c(matrices, var_a), td)) + expect_error(resolver(c(matrices, var_a), td)) expect_error(resolver(c(matrices, factors), td)) expect_error(resolver(c(matrices, factors_head), td)) expect_error(resolver(c(matrices, var_matrices_head), td)) From 3ba1bf567511ed2e565ee07d576ad0f53d40f896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Fri, 9 May 2025 19:59:23 +0200 Subject: [PATCH 103/110] Renaming --- NAMESPACE | 1 + R/delayed.R | 6 +- R/module_input.R | 6 +- R/resolver.R | 221 ++++++++------------------------- R/types.R | 139 +++++++++------------ man/types.Rd | 13 +- tests/testthat/test-resolver.R | 31 +++-- 7 files changed, 147 insertions(+), 270 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 10c63de6..c84bac54 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -11,6 +11,7 @@ S3method(data_extract_srv,list) S3method(determine,colData) S3method(determine,datasets) S3method(determine,default) +S3method(determine,list) S3method(determine,specification) S3method(determine,values) S3method(determine,variables) diff --git a/R/delayed.R b/R/delayed.R index 595838f7..cb9c70eb 100644 --- a/R/delayed.R +++ b/R/delayed.R @@ -44,15 +44,15 @@ is.delayed.specification <- function(specification) { #' @method is.delayed type is.delayed.type <- function(specification) { if (!is.na(specification)) { - return(!all(is.character(specification$names)) || !all(is.character(specification$select))) + return(!all(is.character(specification$choices)) || !all(is.character(specification$selected))) } FALSE } resolved <- function(specification, type = is(specification)) { - s <- all(is.character(specification$names)) && all(is.character(specification$select)) + s <- all(is.character(specification$choices)) && all(is.character(specification$selected)) - if (!s && !all(specification$select %in% specification$names)) { + if (!s && !all(specification$selected %in% specification$choices)) { stop("Selected ", type, " not resolved.") } attr(specification, "delayed") <- NULL diff --git a/R/module_input.R b/R/module_input.R index 00104c59..0fd75dcb 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -69,8 +69,8 @@ module_input_server <- function(id, spec, data) { shiny::updateSelectInput( session, variable, - choices = unorig(spec[[variable]]$names), - selected = unorig(spec[[variable]]$select) + choices = unorig(spec[[variable]]$choices), + selected = unorig(spec[[variable]]$selected) ) # FIXME: set on gray the input # FIXME: Hide input field if any type on specification cannot be solved @@ -86,7 +86,7 @@ module_input_server <- function(id, spec, data) { names(selection) <- names(spec) for (i in seq_along(spec)) { variable <- names(spec)[i] - selection[[variable]] <- unorig(spec[[variable]]$select) + selection[[variable]] <- unorig(spec[[variable]]$selected) } selection }) diff --git a/R/resolver.R b/R/resolver.R index d82b29e5..f5e66cfd 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -30,16 +30,13 @@ resolver <- function(spec, data) { return(spec) } - # Adding some default specifications if they are missing - if ("values" %in% names(spec) && !"variables" %in% names(spec)) { - spec <- c(variables(first), spec) - } - - if ("variables" %in% names(spec) && !"datasets" %in% names(spec)) { - spec <- c(datasets(first), spec) + stopifnot(is.list(spec) || is.specification(spec)) + if (is.type(spec)) { + spec <- list(spec) + names(spec) <- is(spec[[1]]) + class(spec) <- c("specification", class(spec)) } - stopifnot(is.list(spec) || is.specification(spec)) det <- determine(spec, data) if (is.null(names(det))) { return(lapply(det, `[[`, 1)) @@ -60,26 +57,27 @@ resolver <- function(spec, data) { determine <- function(type, data, ...) { stopifnot(is.type(type) || is.list(type) || is.specification(type)) if (!is.delayed(type)) { - return(list(type = type, data = extract(data, unorig(type$select)))) + return(list(type = type, data = extract(data, unorig(type$selected)))) } UseMethod("determine") } #' @export determine.default <- function(type, data, ...) { + stop("There is not a specific method to pick choices.") +} + +#' @export +determine.list <- function(type, data, ...) { if (is.list(type) && is.null(names(type))) { l <- lapply(type, determine, data = data, spec = spec) return(l) } type <- eval_type_names(type, data) - type <- eval_type_select(type, data[unorig(type$names)]) + type <- eval_type_select(type, data) - if (!is.delayed(type) && length(type$select) == 1L) { - list(type = type, data = data[[unorig(type$select)]]) - } else { - list(type = type, data = data[unorig(type$select)]) - } + list(type = type, data = extract(data, unorig(type$selected))) } #' @export @@ -90,22 +88,28 @@ determine.colData <- function(type, data, ...) { data <- as.data.frame(colData(data)) type <- eval_type_names(type, data) - if (is.null(type$names) || !length(type$names)) { + if (is.null(type$choices) || !length(type$choices)) { stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) } - type <- eval_type_select(type, data[unorig(type$names)]) + type <- eval_type_select(type, data) - if (!is.delayed(type) && length(type$select) == 1L) { - list(type = type, data = data[[unorig(type$select)]]) - } else { - list(type = type, data = data[unorig(type$select)]) - } + list(type = type, data = extract(data, unorig(type$selected))) } #' @export determine.specification <- function(type, data, ...) { stopifnot(inherits(data, "qenv")) + + # Adding some default specifications if they are missing + if ("values" %in% names(type) && !"variables" %in% names(type)) { + type <- append(type, list(variables = variables()), length(type) - 1) + } + + if ("variables" %in% names(type) && !"datasets" %in% names(type)) { + type <- append(type, list(variables = datasets()), length(type) - 1) + } + d <- data for (i in seq_along(type)) { di <- determine(type[[i]], d) @@ -119,55 +123,6 @@ determine.specification <- function(type, data, ...) { list(type = type, data = data) # It is the transform object resolved. } -# Checks that for the given type and data names and data it can be resolved -# The workhorse of the resolver -# determine_helper <- function(type, data_names, data) { -# stopifnot(!is.null(type)) -# orig_names <- type$names -# orig_select <- type$select -# -# if (is.delayed(type) && all(is.character(type$names))) { -# new_names <- intersect(data_names, type$names) -# -# type$names <- new_names -# if (length(new_names) == 0) { -# return(NULL) -# # stop("No selected ", is(type), " matching the conditions requested") -# } else if (length(new_names) == 1L) { -# type$select <- new_names -# } else { -# new_select <- selector(data, type$names) -# if (!length(new_select)) { -# return(NULL) -# # stop("No ", is(type), " meet the requirements to be selected") -# } -# type$select <- new_select -# } -# } else if (is.delayed(type)) { -# new_names <- selector(data, type$select) -# } -# -# -# if (!length(new_names)) { -# return(NULL) -# # stop("No ", is(type), " meet the requirements") -# } -# type$names <- new_names -# -# if (length(type$names) == 0) { -# return(NULL) -# # stop("No selected ", is(type), " matching the conditions requested") -# } else if (length(type$names) == 1) { -# type$select <- type$names -# } -# -# new_select <- selector(data, type$select) -# type$select <- new_select -# attr(type$names, "original") <- orig(orig_names) -# attr(type$select, "original") <- orig(orig_select) -# resolved(type) -# } - #' @export determine.datasets <- function(type, data, ...) { if (is.null(data)) { @@ -180,13 +135,13 @@ determine.datasets <- function(type, data, ...) { # FIXME: What happens if colnames is null: colnames(array(dim = c(4, 2))) type <- eval_type_names(type, data) - if (is.null(type$names) || !length(type$names)) { + if (is.null(type$choices) || !length(type$choices)) { stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) } type <- eval_type_select(type, data) - list(type = type, data = extract(data, unorig(type$select))) + list(type = type, data = extract(data, unorig(type$selected))) } #' @export @@ -206,7 +161,7 @@ determine.variables <- function(type, data, ...) { type <- eval_type_names(type, data) - if (is.null(type$names) || !length(type$names)) { + if (is.null(type$choices) || !length(type$choices)) { stop("No ", toString(is(type)), " meet the specification.", call. = FALSE) } @@ -218,81 +173,9 @@ determine.variables <- function(type, data, ...) { } # This works for matrices and data.frames of length 1 or multiple # be aware of drop behavior on tibble vs data.frame - list(type = type, data = extract(data, unorig(type$select))) + list(type = type, data = extract(data, unorig(type$selected))) } -# @export -# determine.mae_colData <- function(type, data, ...) { -# if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { -# stop("Requires 'MultiAssayExperiment' package.") -# } -# -# new_data <- colData(data) -# for (i in seq_along(new_data)) { -# type <- determine_helper(type, colnames(new_data)[i], new_data[, i]) -# } -# if (length(dim(new_data)) != 2L) { -# stop("Can't resolve variables from this object of class ", class(new_data)) -# } -# if (ncol(new_data) <= 0L) { -# stop("Can't pull variable: No variable is available.") -# } -# type <- determine_helper(type, colnames(new_data), new_data) -# -# # Not possible to know what is happening -# if (is.delayed(type)) { -# return(list(type = type, data = NULL)) -# } -# -# if (length(type$select) > 1) { -# list(type = type, data = data[type$select]) -# } else { -# list(type = type, data = data[[type$select]]) -# } -# } - -# @export -# determine.mae_experiments <- function(type, data, ...) { -# if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { -# stop("Requires 'MultiAssayExperiment' package.") -# } -# new_data <- experiments(data) -# type <- determine_helper(type, names(new_data), new_data) -# -# # Not possible to know what is happening -# if (is.delayed(type)) { -# } -# -# if (!is.delayed(type) && length(type$select) > 1) { -# list(type = type, data = new_data[type$select]) -# } else if (!is.delayed(type) && length(type$select) == 1) { -# list(type = type, data = new_data[[type$select]]) -# } else { -# return(list(type = type, data = NULL)) -# } -# } - -# @export -# determine.mae_sampleMap <- function(type, data, ...) { -# if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { -# stop("Requires 'MultiAssayExperiment' package.") -# } -# -# new_data <- sampleMap(data) -# type <- determine_helper(type, names(new_data), new_data) -# -# # Not possible to know what is happening -# if (is.delayed(type)) { -# return(list(type = type, data = NULL)) -# } -# -# if (length(type$select) > 1) { -# list(type = type, data = data[type$select]) -# } else { -# list(type = type, data = data[[type$select]]) -# } -# } - #' @export determine.values <- function(type, data, ...) { if (!is.numeric(data)) { @@ -301,12 +184,12 @@ determine.values <- function(type, data, ...) { } else { d <- data } - sel <- selector(d, type$names) - type$names <- data[sel] + sel <- selector(d, type$choices) + type$choices <- data[sel] - sel2 <- selector(d[sel], type$select) - type$select <- data[sel][sel2] + sel2 <- selector(d[sel], type$selected) + type$selected <- data[sel][sel2] # Not possible to know what is happening if (is.delayed(type)) { @@ -326,40 +209,40 @@ unorig <- function(x) { } eval_type_names <- function(type, data) { - orig_names <- orig(type$names) - if (length(orig_names) == 1L) { - orig_names <- orig_names[[1L]] + orig_choices <- orig(type$choices) + if (length(orig_choices) == 1L) { + orig_choices <- orig_choices[[1L]] } - new_names <- selector(data, type$names) + new_choices <- selector(data, type$choices) - new_names <- unique(names(new_names)) - attr(new_names, "original") <- orig_names + new_choices <- unique(names(new_choices)) + attr(new_choices, "original") <- orig_choices - type$names <- new_names + type$choices <- new_choices type } eval_type_select <- function(type, data) { - stopifnot(is.character(type$names)) + stopifnot(is.character(type$choices)) if (!is(data, "qenv")) { - data <- extract(data, type$names) + data <- extract(data, type$choices) } else { # Do not extract; selection would be from the data extracted not from the names. - data <- data[type$names] + data <- data[type$choices] } - orig_select <- orig(type$select) - if (length(orig_select) == 1L) { - orig_select <- orig_select[[1L]] + orig_selected <- orig(type$selected) + if (length(orig_selected) == 1L) { + orig_selected <- orig_selected[[1L]] } - names <- seq_along(type$names) - names(names) <- type$names - new_select <- names(selector(data, type$select)) + choices <- seq_along(type$choices) + names(choices) <- type$choices + new_selected <- names(selector(data, type$selected)) - attr(new_select, "original") <- orig_select - type$select <- new_select + attr(new_selected, "original") <- orig_selected + type$selected <- new_selected type } diff --git a/R/types.R b/R/types.R index 349ce0a7..ba434533 100644 --- a/R/types.R +++ b/R/types.R @@ -20,19 +20,19 @@ is.type <- function(x) { #' @export #' @method is.na type is.na.type <- function(x) { - anyNA(unclass(x[c("names", "select")])) + anyNA(unclass(x[c("names", "selected")])) } #' @export anyNA.type <- function(x, recursive = FALSE) { - anyNA(unclass(x[c("names", "select")]), recursive) + anyNA(unclass(x[c("choices", "selected")]), recursive) } -type_helper <- function(names, select, type) { - out <- list(names = names, select = select) +type_helper <- function(choices, selected, type) { + out <- list(choices = choices, selected = selected) class(out) <- c(type, "type", "list") - attr(out$names, "original") <- names - attr(out$select, "original") <- select + attr(out$choices, "original") <- choices + attr(out$selected, "original") <- selected delay(out) } @@ -41,10 +41,11 @@ type_helper <- function(names, select, type) { #' @title Type specification #' @description #' Define how to select and extract data -#' @param names Character specifying the names or functions to select them. The functions will be applied on the data or the names. -#' @param select Character of `x` or functions to select on x (only on names or positional not on the data of the variable). +#' @param choices <[`tidy-select`][dplyr_tidy_select]> One unquoted expression to be used to pick the choices. +#' @param selected <[`tidy-select`][dplyr_tidy_select]> One unquoted expression to be used to pick from choices to be selected. #' @returns An object of the same class as the function with two elements: names the content of x, and select. #' @examples +#' datasets() #' datasets("A") #' c(datasets("A"), datasets("B")) #' datasets(where(is.data.frame)) @@ -53,26 +54,26 @@ NULL #' @describeIn types Specify datasets. #' @export -datasets <- function(names, select = 1) { - type_helper(names = rlang::enquo(names), select = rlang::enquo(select), type = "datasets") +datasets <- function(choices = everything(), select = 1) { + type_helper(rlang::enquo(choices), rlang::enquo(select), "datasets") } #' @describeIn types Specify variables. #' @export -variables <- function(names, select = 1) { - type_helper(names = rlang::enquo(names), select = rlang::enquo(select), type = "variables") +variables <- function(choices = everything(), select = 1) { + type_helper(rlang::enquo(choices), rlang::enquo(select), "variables") } #' @describeIn types Specify colData. #' @export -mae_colData <- function(names, select = 1) { - type_helper(names = rlang::enquo(names), select = rlang::enquo(select), type = "colData") +mae_colData <- function(choices = everything(), select = 1) { + type_helper(rlang::enquo(choices), rlang::enquo(select), "colData") } #' @describeIn types Specify values. #' @export -values <- function(names, select = 1) { - type_helper(names = rlang::enquo(names), select = rlang::enquo(select), type = "values") +values <- function(choices = everything(), select = 1) { + type_helper(rlang::enquo(choices), rlang::enquo(select), "values") } #' @export @@ -88,7 +89,7 @@ c.specification <- function(...) { names(vector) <- utypes for (t in utypes) { new_type <- vector("list", length = 2) - names(new_type) <- c("names", "select") + names(new_type) <- c("choices", "selected") class(new_type) <- c("type", "list") for (i in seq_along(l)) { if (!t %in% names(l[[i]])) { @@ -97,23 +98,23 @@ c.specification <- function(...) { # Slower but less code duplication: # new_type <- c(new_type, l[[i]][[t]]) # then we need class(new_type) <- c(t, "type", "list") outside the loop - old_names <- new_type$names - old_select <- new_type$select - new_type$names <- c(old_names, l[[i]][[t]][["names"]]) - attr(new_type$names, "original") <- c(orig( - old_names + old_choices <- new_type$choices + old_selected <- new_type$selected + new_type$choices <- c(old_choices, l[[i]][[t]][["choices"]]) + attr(new_type$choices, "original") <- c(orig( + old_choices ), orig(l[[i]][[t]][["names"]])) - new_type$select <- c(old_select, l[[i]][[t]][["select"]]) - attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][[t]][["select"]])) + new_type$selected <- c(old_selected, l[[i]][[t]][["selected"]]) + attr(new_type$selected, "original") <- c(orig(old_selected), orig(l[[i]][[t]][["selected"]])) attr(new_type, "delayed") <- any(attr(new_type, "delayed"), attr(l[[i]], "delayed")) } - orig_names <- unique(orig(new_type$names)) - new_type$names <- unique(new_type$names) - attr(new_type$names, "original") <- orig_names + orig_choices <- unique(orig(new_type$choices)) + new_type$choices <- unique(new_type$choices) + attr(new_type$choices, "original") <- orig_choices - orig_select <- unique(orig(new_type$select)) - new_type$select <- unique(new_type$select) - attr(new_type$select, "original") <- orig_select + orig_selected <- unique(orig(new_type$selected)) + new_type$selected <- unique(new_type$selected) + attr(new_type$selected, "original") <- orig_selected class(new_type) <- c(t, "type", "list") vector[[t]] <- new_type } @@ -134,33 +135,33 @@ c.type <- function(...) { names(vector) <- utypes for (t in utypes) { new_type <- vector("list", length = 2) - names(new_type) <- c("names", "select") + names(new_type) <- c("choices", "selected") for (i in seq_along(l)) { if (!is(l[[i]], t)) { next } - old_names <- new_type$names - old_select <- new_type$select - new_type$names <- c(old_names, l[[i]][["names"]]) - attr(new_type$names, "original") <- c(orig( - old_names - ), orig(l[[i]][["names"]])) - new_type$select <- unique(c(old_select, l[[i]][["select"]])) - attr(new_type$select, "original") <- c(orig(old_select), orig(l[[i]][["select"]])) + old_choices <- new_type$choices + old_selected <- new_type$selected + new_type$choices <- c(old_choices, l[[i]][["choices"]]) + attr(new_type$choices, "original") <- c(orig( + old_choices + ), orig(l[[i]][["choices"]])) + new_type$selected <- unique(c(old_selected, l[[i]][["selected"]])) + attr(new_type$selected, "original") <- c(orig(old_selected), orig(l[[i]][["selected"]])) } - orig_names <- unique(orig(new_type$names)) - orig_select <- unique(orig(new_type$select)) + orig_choices <- unique(orig(new_type$choices)) + orig_selected <- unique(orig(new_type$selected)) - new_type$names <- unique(new_type$names) - if (length(new_type$names) == 1) { - new_type$names <- new_type$names[[1]] + new_type$choices <- unique(new_type$choices) + if (length(new_type$choices) == 1) { + new_type$choices <- new_type$choices[[1]] } - attr(new_type$names, "original") <- orig_names + attr(new_type$choices, "original") <- orig_choices - if (length(new_type$select) == 1) { - new_type$select <- new_type$select[[1]] + if (length(new_type$selected) == 1) { + new_type$selected <- new_type$selected[[1]] } - attr(new_type$select, "original") <- orig_select + attr(new_type$selected, "original") <- orig_selected class(new_type) <- c(t, "type", "list") attr(new_type, "delayed") <- is.delayed(new_type) @@ -184,57 +185,39 @@ print.type <- function(x, ...) { return(x) } - nam_functions <- count_functions(x$names) + choices_fns <- count_functions(x$choices) msg_values <- character() - nam_values <- length(x$names) - sum(nam_functions) - if (any(nam_functions)) { - msg_values <- paste0(msg_values, sum(nam_functions), " functions for possible choices.", + choices_values <- length(x$choices) - sum(choices_fns) + if (any(choices_fns)) { + msg_values <- paste0(msg_values, sum(choices_fns), " functions for possible choices.", collapse = "\n" ) } - if (nam_values) { - msg_values <- paste0(msg_values, paste0(rlang::as_label(x$names[!nam_functions]), collapse = ", "), + if (choices_values) { + msg_values <- paste0(msg_values, paste0(rlang::as_label(x$choices[!choices_fns]), collapse = ", "), " as possible choices.", collapse = "\n" ) } - sel_functions <- count_functions(x$select) + selected_fns <- count_functions(x$selected) msg_sel <- character() - sel_values <- length(x$select) - sum(sel_functions) - if (any(sel_functions)) { - msg_sel <- paste0(msg_sel, sum(sel_functions), " functions to select.", + sel_values <- length(x$selected) - sum(selected_fns) + if (any(selected_fns)) { + msg_sel <- paste0(msg_sel, sum(selected_fns), " functions to select.", collapse = "\n" ) } if (sel_values) { - msg_sel <- paste0(msg_sel, paste0(rlang::as_label(x$select[!sel_functions]), collapse = ", "), + msg_sel <- paste0(msg_sel, paste0(rlang::as_label(x$selected[!selected_fns]), collapse = ", "), " selected.", collapse = "\n" ) } - if (!is.null(x[["except"]])) { - exc_functions <- count_functions(x$except) - msg_exc <- character() - sel_values <- length(x$except) - sum(exc_functions) - if (any(exc_functions)) { - msg_exc <- paste0(msg_exc, sum(exc_functions), " functions to exclude.", - collapse = "\n" - ) - } - if (sel_values) { - msg_exc <- paste0(msg_exc, paste0(rlang::as_label(x$except[!exc_functions]), collapse = ", "), - " excluded.", - collapse = "\n" - ) - } - } else { - msg_exc <- character() - } - cat(msg_values, msg_sel, msg_exc) + cat(msg_values, msg_sel) return(x) } diff --git a/man/types.Rd b/man/types.Rd index 10409985..d9c53b25 100644 --- a/man/types.Rd +++ b/man/types.Rd @@ -8,18 +8,18 @@ \alias{values} \title{Type specification} \usage{ -datasets(names, select = 1) +datasets(choices = everything(), select = 1) -variables(names, select = 1) +variables(choices = everything(), select = 1) -mae_colData(names, select = 1) +mae_colData(choices = everything(), select = 1) -values(names, select = 1) +values(choices = everything(), select = 1) } \arguments{ -\item{names}{Character specifying the names or functions to select them. The functions will be applied on the data or the names.} +\item{choices}{<\code{\link[=dplyr_tidy_select]{tidy-select}}> One unquoted expression to be used to pick the choices.} -\item{select}{Character of \code{x} or functions to select on x (only on names or positional not on the data of the variable).} +\item{selected}{<\code{\link[=dplyr_tidy_select]{tidy-select}}> One unquoted expression to be used to pick from choices to be selected.} } \value{ An object of the same class as the function with two elements: names the content of x, and select. @@ -39,6 +39,7 @@ Define how to select and extract data }} \examples{ +datasets() datasets("A") c(datasets("A"), datasets("B")) datasets(where(is.data.frame)) diff --git a/tests/testthat/test-resolver.R b/tests/testthat/test-resolver.R index 02f6c907..af07bdc5 100644 --- a/tests/testthat/test-resolver.R +++ b/tests/testthat/test-resolver.R @@ -16,7 +16,7 @@ test_that("resolver datasets works", { expect_no_error(resolver(df_head, td)) expect_no_error(resolver(df_first, td)) out <- resolver(matrices, td) - expect_length(out$select, 1L) # Because we use 1 + expect_length(out$datasets$selected, 1L) # Because we use 1 expect_error(expect_warning(resolver(df_mean, td))) expect_error(resolver(median_mean, td)) }) @@ -55,6 +55,15 @@ test_that("resolver variables works", { expect_error(resolver(c(data_frames, var_matrices_head), td)) }) +test_that("resolver with missing type works", { + td <- within(teal.data::teal_data(), { + i <- iris + }) + + r <- expect_no_error(resolver(variables(where(is.numeric)), td)) + expect_true(r$variables$selected == "i") +}) + test_that("resolver values works", { df <- datasets("df") matrices <- datasets(where(is.matrix)) @@ -98,22 +107,22 @@ test_that("names and variables are reported", { # This should select A and Ab: # A because the name is all capital letters and # Ab values is all upper case. - # expect_length(out$variables$names, 2) + # expect_length(out$variables$choices, 2) v_all_upper <- variables(where(function(x) { all(x == toupper(x)) })) df_all_upper_variables <- c(d_df, v_all_upper) expect_no_error(out <- resolver(df_all_upper_variables, td)) expect_no_error(out <- resolver(c(datasets("df2"), v_all_upper), td)) - expect_length(out$variables$names, 2L) - expect_no_error(out <- resolver(datasets(function(x) { + expect_length(out$variables$choices, 2L) + expect_no_error(out <- resolver(datasets(where(function(x) { is.data.frame(x) && all(colnames(x) == toupper(colnames(x))) - }), td)) - expect_length(out$names, 1L) + })), td)) + expect_length(out$datasets$choices, 1L) expect_no_error(out <- resolver(datasets(where(function(x) { is.data.frame(x) || any(colnames(x) == toupper(colnames(x))) })), td)) - expect_length(out$names, 2L) + expect_length(out$datasets$choices, 2L) }) test_that("update_spec resolves correctly", { @@ -128,10 +137,10 @@ test_that("update_spec resolves correctly", { ) }) data_frames_factors <- c(datasets(where(is.data.frame)), variables(where(is.factor))) - expect_false(is.null(attr(data_frames_factors$datasets$names, "original"))) - expect_false(is.null(attr(data_frames_factors$datasets$select, "original"))) - expect_false(is.null(attr(data_frames_factors$variables$names, "original"))) - expect_false(is.null(attr(data_frames_factors$variables$select, "original"))) + expect_false(is.null(attr(data_frames_factors$datasets$choices, "original"))) + expect_false(is.null(attr(data_frames_factors$datasets$selected, "original"))) + expect_false(is.null(attr(data_frames_factors$variables$choices, "original"))) + expect_false(is.null(attr(data_frames_factors$variables$selected, "original"))) expect_no_error(resolver(data_frames_factors, td)) }) From ced90532fba7606c740503aabdc6ffb1892b4550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 12 May 2025 09:36:20 +0200 Subject: [PATCH 104/110] Fix renaming issues --- NAMESPACE | 1 + R/resolver.R | 2 +- R/types.R | 21 +++++++++++---------- R/update_spec.R | 20 ++++++++++---------- man/types.Rd | 12 ++++++------ tests/testthat/test-types.R | 2 +- 6 files changed, 30 insertions(+), 28 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index c84bac54..bd169a15 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -103,3 +103,4 @@ import(shiny) importFrom(dplyr,"%>%") importFrom(lifecycle,badge) importFrom(methods,is) +importFrom(tidyselect,everything) diff --git a/R/resolver.R b/R/resolver.R index f5e66cfd..0f4ca488 100644 --- a/R/resolver.R +++ b/R/resolver.R @@ -70,7 +70,7 @@ determine.default <- function(type, data, ...) { #' @export determine.list <- function(type, data, ...) { if (is.list(type) && is.null(names(type))) { - l <- lapply(type, determine, data = data, spec = spec) + l <- lapply(type, determine, data = data) return(l) } diff --git a/R/types.R b/R/types.R index ba434533..d1f02fc6 100644 --- a/R/types.R +++ b/R/types.R @@ -41,8 +41,8 @@ type_helper <- function(choices, selected, type) { #' @title Type specification #' @description #' Define how to select and extract data -#' @param choices <[`tidy-select`][dplyr_tidy_select]> One unquoted expression to be used to pick the choices. -#' @param selected <[`tidy-select`][dplyr_tidy_select]> One unquoted expression to be used to pick from choices to be selected. +#' @param choices <[`tidy-select`][dplyr::dplyr_tidy_select]> One unquoted expression to be used to pick the choices. +#' @param selected <[`tidy-select`][dplyr::dplyr_tidy_select]> One unquoted expression to be used to pick from choices to be selected. #' @returns An object of the same class as the function with two elements: names the content of x, and select. #' @examples #' datasets() @@ -52,28 +52,29 @@ type_helper <- function(choices, selected, type) { #' c(datasets("A"), variables(where(is.numeric))) NULL +#' @importFrom tidyselect everything #' @describeIn types Specify datasets. #' @export -datasets <- function(choices = everything(), select = 1) { - type_helper(rlang::enquo(choices), rlang::enquo(select), "datasets") +datasets <- function(choices = tidyselect::everything(), selected = 1) { + type_helper(rlang::enquo(choices), rlang::enquo(selected), "datasets") } #' @describeIn types Specify variables. #' @export -variables <- function(choices = everything(), select = 1) { - type_helper(rlang::enquo(choices), rlang::enquo(select), "variables") +variables <- function(choices = tidyselect::everything(), selected = 1) { + type_helper(rlang::enquo(choices), rlang::enquo(selected), "variables") } #' @describeIn types Specify colData. #' @export -mae_colData <- function(choices = everything(), select = 1) { - type_helper(rlang::enquo(choices), rlang::enquo(select), "colData") +mae_colData <- function(choices = tidyselect::everything(), selected = 1) { + type_helper(rlang::enquo(choices), rlang::enquo(selected), "colData") } #' @describeIn types Specify values. #' @export -values <- function(choices = everything(), select = 1) { - type_helper(rlang::enquo(choices), rlang::enquo(select), "values") +values <- function(choices = tidyselect::everything(), selected = 1) { + type_helper(rlang::enquo(choices), rlang::enquo(selected), "values") } #' @export diff --git a/R/update_spec.R b/R/update_spec.R index a65c53f8..806a451b 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -63,25 +63,25 @@ update_s_spec <- function(spec, type, value) { type <- match.arg(type, spec_types) restart_types <- spec_types[seq_along(spec_types) > which(type == spec_types)] - valid_names <- spec[[type]]$names + valid_names <- spec[[type]]$choices if (!is.list(valid_names) && all(value %in% valid_names)) { original_select <- orig(spec[[type]]$select) - spec[[type]][["select"]] <- value - attr(spec[[type]][["select"]], "original") <- original_select + spec[[type]][["selected"]] <- value + attr(spec[[type]][["selected"]], "original") <- original_select } else if (!is.list(valid_names) && !all(value %in% valid_names)) { - original_select <- orig(spec[[type]]$select) + original_select <- orig(spec[[type]]$selected) valid_values <- intersect(value, valid_names) if (!length(valid_values)) { stop("No valid value provided.") } if (!length(valid_values)) { - spec[[type]][["select"]] <- original_select + spec[[type]][["selected"]] <- original_select } else { - spec[[type]][["select"]] <- valid_values + spec[[type]][["selected"]] <- valid_values } - attr(spec[[type]][["select"]], "original") <- original_select + attr(spec[[type]][["selected"]], "original") <- original_select } else { stop("It seems the specification needs to be resolved first.") } @@ -92,9 +92,9 @@ update_s_spec <- function(spec, type, value) { next } restored_specification <- type_helper( - type = type_restart, - names = orig(spec[[type_restart]]$names), - select = orig(spec[[type_restart]]$select) + orig(spec[[type_restart]]$choices), + orig(spec[[type_restart]]$selected), + type_restart ) spec[[type_restart]] <- restored_specification } diff --git a/man/types.Rd b/man/types.Rd index d9c53b25..2b47a81a 100644 --- a/man/types.Rd +++ b/man/types.Rd @@ -8,18 +8,18 @@ \alias{values} \title{Type specification} \usage{ -datasets(choices = everything(), select = 1) +datasets(choices = tidyselect::everything(), selected = 1) -variables(choices = everything(), select = 1) +variables(choices = tidyselect::everything(), selected = 1) -mae_colData(choices = everything(), select = 1) +mae_colData(choices = tidyselect::everything(), selected = 1) -values(choices = everything(), select = 1) +values(choices = tidyselect::everything(), selected = 1) } \arguments{ -\item{choices}{<\code{\link[=dplyr_tidy_select]{tidy-select}}> One unquoted expression to be used to pick the choices.} +\item{choices}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> One unquoted expression to be used to pick the choices.} -\item{selected}{<\code{\link[=dplyr_tidy_select]{tidy-select}}> One unquoted expression to be used to pick from choices to be selected.} +\item{selected}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> One unquoted expression to be used to pick from choices to be selected.} } \value{ An object of the same class as the function with two elements: names the content of x, and select. diff --git a/tests/testthat/test-types.R b/tests/testthat/test-types.R index 2f99472c..dfb4dc0c 100644 --- a/tests/testthat/test-types.R +++ b/tests/testthat/test-types.R @@ -26,7 +26,7 @@ test_that("raw combine of types", { test_that("combine types", { expect_no_error(c( - datasets(where(is.data.frame), select = "df1"), + datasets(where(is.data.frame), selected = "df1"), variables(where(is.numeric)) )) }) From f3e3fa3573e4b4dc268794546dc700ae580b7f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 12 May 2025 10:21:48 +0200 Subject: [PATCH 105/110] Remove unused package --- DESCRIPTION | 1 - 1 file changed, 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 89ae48f1..6608015d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -22,7 +22,6 @@ Depends: R (>= 3.6) Imports: checkmate (>= 2.1.0), - cli (>= 3.6.4), dplyr (>= 1.1.0), lifecycle (>= 0.2.0), logger (>= 0.2.0), From 55a8537943bf2ae1a4e3a28a4e1ca450c511e5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 12 May 2025 12:31:30 +0200 Subject: [PATCH 106/110] Renaming --- R/update_spec.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/update_spec.R b/R/update_spec.R index 806a451b..5fcd0245 100644 --- a/R/update_spec.R +++ b/R/update_spec.R @@ -66,7 +66,7 @@ update_s_spec <- function(spec, type, value) { valid_names <- spec[[type]]$choices if (!is.list(valid_names) && all(value %in% valid_names)) { - original_select <- orig(spec[[type]]$select) + original_select <- orig(spec[[type]]$selected) spec[[type]][["selected"]] <- value attr(spec[[type]][["selected"]], "original") <- original_select } else if (!is.list(valid_names) && !all(value %in% valid_names)) { From 6bf1af3388b1a8af6ad6b918277dffc529f84659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 12 May 2025 17:38:04 +0200 Subject: [PATCH 107/110] Select instead of merging --- R/merge_dataframes.R | 28 ++++++++++++++++++++++++---- R/module_input.R | 7 +++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 9e3b97f1..46b98768 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -102,8 +102,22 @@ merge_call_multiple <- function(input, ids, merge_function, data, datasets <- unique(unlist(lapply(input, `[[`, "datasets"), FALSE, FALSE)) stopifnot(is.character(datasets) && length(datasets) >= 1L) number_merges <- length(datasets) - 1L + + out <- vector("list", length = 2) + names(out) <- c("code", "specification") + + if (number_merges == 0L) { + dataset <- names(input) + variables <- input[[1]]$variables + final_call <- call( + "<-", as.name(anl_name), + call("dplyr::select", as.name(dataset), as.names(variables)) + ) + out$code <- teal.code::eval_code(data, final_call) + out$input <- input + return(out) + } stopifnot( - "Number of datasets is enough" = number_merges >= 1L, "Number of arguments for type matches data" = length(merge_function) == number_merges || length(merge_function) == 1L ) if (!missing(ids)) { @@ -119,11 +133,15 @@ merge_call_multiple <- function(input, ids, merge_function, data, if (number_merges == 1L && missing(ids)) { previous <- merge_call_pair(input, merge_function = merge_function, data = data) final_call <- call("<-", x = as.name(anl_name), value = previous) - return(teal.code::eval_code(data, final_call)) + out$code <- teal.code::eval_code(data, final_call) + out$input <- input + return(out) } else if (number_merges == 1L && !missing(ids)) { previous <- merge_call_pair(input, by = ids, merge_function = merge_function, data = data) final_call <- call("<-", x = as.name(anl_name), value = previous) - return(teal.code::eval_code(data, final_call)) + out$code <- teal.code::eval_code(data, final_call) + out$input <- input + return(out) } @@ -161,5 +179,7 @@ merge_call_multiple <- function(input, ids, merge_function, data, previous <- call("%>%", as.name(previous), as.name(current)) } final_call <- call("<-", x = as.name(anl_name), value = previous) - teal.code::eval_code(data, final_call) + out$code <- teal.code::eval_code(data, final_call) + out$input <- input + out } diff --git a/R/module_input.R b/R/module_input.R index 0fd75dcb..e4673565 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -33,11 +33,14 @@ module_input_ui <- function(id, label, spec) { #' @export module_input_server <- function(id, spec, data) { stopifnot(is.specification(spec)) - stopifnot(is.reactive(data)) stopifnot(is.character(id)) moduleServer(id, function(input, output, session) { react_updates <- reactive({ - d <- data() + if (is.reactive(data)) { + d <- data() + } else { + d <- data + } if (!anyNA(spec) && is.delayed(spec)) { spec <- resolver(spec, d) } From 744a2716026db92eb4f2b230f91578ecfab52d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Mon, 12 May 2025 17:42:00 +0200 Subject: [PATCH 108/110] Add function to provide the list of inputs --- R/merge_dataframes.R | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 46b98768..5be62380 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -183,3 +183,20 @@ merge_call_multiple <- function(input, ids, merge_function, data, out$input <- input out } + +merge_selector_srv <- function(id, available, data) { + moduleServer( + id, + function(input, output, session) { + req(input) + resolved_spec <- reactive({ + resolved_spec <- lapply(names(available), function(x) { + module_input_server(x, available[[x]], data)() + }) + names(resolved_spec) <- names(available) + resolved_spec + }) + resolved_spec() + } + ) +} From c773304d7e890bfeba04779a449f482ada04a235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Tue, 13 May 2025 14:07:19 +0200 Subject: [PATCH 109/110] Improve calls and merging --- R/merge_dataframes.R | 122 ++++++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/R/merge_dataframes.R b/R/merge_dataframes.R index 5be62380..f64870f9 100644 --- a/R/merge_dataframes.R +++ b/R/merge_dataframes.R @@ -66,61 +66,70 @@ extract_ids <- function(input, data) { out <- unique(unlist(l)) } -merge_call_pair <- function(selections, by, data, - merge_function = "dplyr::full_join", - anl_name = "ANL") { - selections <- consolidate_extraction(selections) +merge_call_pair <- function(input_res, by, data, + merge_function = "dplyr::full_join") { + selections <- consolidate_extraction(input_res) stopifnot(length(selections) == 2L) datasets <- unique(unlist(lapply(selections, `[[`, "datasets"), FALSE, FALSE)) stopifnot(length(datasets) >= 2) - by <- extract_ids(input = selections, data) + if (is.reactive(data)) { + data <- data() + } - if (grepl("::", merge_function, fixed = TRUE)) { - m <- strsplit(merge_function, split = "::", fixed = TRUE)[[1]] - data <- teal.code::eval_code(data, call("library", m[1])) - merge_function <- m[2] + if (is.null(by)) { + by <- extract_ids(input = selections, data) } + data <- add_library_call(merge_function, data) + if (!missing(by) && length(by)) { - call_m <- call(merge_function, - x = as.name(datasets[1]), - y = as.name(datasets[2]), - by = by - ) + call_m <- as.call(c( + rlang::parse_expr(merge_function), + list( + x = as.name(datasets[1]), + y = as.name(datasets[2]), + by = by + ) + )) } else { - call_m <- call(merge_function, - x = as.name(datasets[1]), - y = as.name(datasets[2]) - ) + call_m <- as.call(c( + rlang::parse_expr(merge_function), + list( + x = as.name(datasets[1]), + y = as.name(datasets[2]) + ) + )) } call_m } -merge_call_multiple <- function(input, ids, merge_function, data, - anl_name = "ANL") { - input <- consolidate_extraction(input) - datasets <- unique(unlist(lapply(input, `[[`, "datasets"), FALSE, FALSE)) +merge_call_multiple <- function(input_res, ids, data, merge_function = "dplyr::full_join", + anl = "ANL") { + selections <- consolidate_extraction(input_res) + datasets <- unique(unlist(lapply(selections, `[[`, "datasets"), FALSE, FALSE)) stopifnot(is.character(datasets) && length(datasets) >= 1L) number_merges <- length(datasets) - 1L - + if (is.reactive(data)) { + data <- data() + } out <- vector("list", length = 2) names(out) <- c("code", "specification") if (number_merges == 0L) { - dataset <- names(input) - variables <- input[[1]]$variables + dataset <- names(selections) + variables <- selections[[1]]$variables final_call <- call( - "<-", as.name(anl_name), + "<-", as.name(anl), call("dplyr::select", as.name(dataset), as.names(variables)) ) out$code <- teal.code::eval_code(data, final_call) - out$input <- input + out$input <- input_res return(out) } stopifnot( "Number of arguments for type matches data" = length(merge_function) == number_merges || length(merge_function) == 1L ) - if (!missing(ids)) { + if (!missing(ids) && !is.null(ids)) { stopifnot("Number of arguments for ids matches data" = !(is.list(ids) && length(ids) == number_merges)) } if (length(merge_function) != number_merges) { @@ -131,33 +140,33 @@ merge_call_multiple <- function(input, ids, merge_function, data, } if (number_merges == 1L && missing(ids)) { - previous <- merge_call_pair(input, merge_function = merge_function, data = data) - final_call <- call("<-", x = as.name(anl_name), value = previous) + data <- add_library_call(merge_function, data) + previous <- merge_call_pair(selections, merge_function = merge_function, data = data) + final_call <- call("<-", x = as.name(anl), value = previous) out$code <- teal.code::eval_code(data, final_call) - out$input <- input + out$input <- input_res return(out) } else if (number_merges == 1L && !missing(ids)) { - previous <- merge_call_pair(input, by = ids, merge_function = merge_function, data = data) - final_call <- call("<-", x = as.name(anl_name), value = previous) + data <- add_library_call(merge_function, data) + previous <- merge_call_pair(selections, by = ids, merge_function = merge_function, data = data) + final_call <- call("<-", x = as.name(anl), value = previous) out$code <- teal.code::eval_code(data, final_call) - out$input <- input + out$input <- input_res return(out) } - - for (merge_i in seq_len(number_merges)) { if (merge_i == 1L) { datasets_i <- seq_len(2) if (!missing(ids)) { ids <- ids[[merge_i]] - previous <- merge_call_pair(input[datasets_i], + previous <- merge_call_pair(selections[datasets_i], ids, merge_function[merge_i], data = data ) } else { - previous <- merge_call_pair(input[datasets_i], + previous <- merge_call_pair(selections[datasets_i], merge_function[merge_i], data = data ) @@ -165,12 +174,12 @@ merge_call_multiple <- function(input, ids, merge_function, data, } else { datasets_ids <- merge_i:(merge_i + 1L) if (!missing(ids)) { - current <- merge_call_pair(input[datasets_ids], + current <- merge_call_pair(selections[datasets_ids], merge_function = merge_function[merge_i], data = data ) } else { ids <- ids[[merge_i]] - current <- merge_call_pair(input[datasets_ids], + current <- merge_call_pair(selections[datasets_ids], ids, merge_function = merge_function[merge_i], data = data ) @@ -178,25 +187,44 @@ merge_call_multiple <- function(input, ids, merge_function, data, } previous <- call("%>%", as.name(previous), as.name(current)) } - final_call <- call("<-", x = as.name(anl_name), value = previous) + final_call <- call("<-", x = as.name(anl), value = previous) out$code <- teal.code::eval_code(data, final_call) - out$input <- input + out$input <- input_res out } -merge_selector_srv <- function(id, available, data) { +merge_type_srv <- function(id, inputs, data, merge_function = "dplyr::full_join", anl_name = "ANL") { + checkmate::assert_list(inputs, names = "named") + stopifnot(make.names(anl_name) == anl_name) + moduleServer( id, function(input, output, session) { req(input) resolved_spec <- reactive({ - resolved_spec <- lapply(names(available), function(x) { - module_input_server(x, available[[x]], data)() + resolved_spec <- lapply(names(inputs), function(x) { + # Return characters not reactives + module_input_server(x, inputs[[x]], data)() }) - names(resolved_spec) <- names(available) + # Keep input names + names(resolved_spec) <- names(inputs) resolved_spec }) - resolved_spec() + td <- merge_call_multiple(resolved_spec(), NULL, + data, + merge_function = merge_function, anl = anl_name + ) } ) } + +add_library_call <- function(merge_function, data) { + if (is.reactive(data)) { + data <- data() + } + if (grepl("::", merge_function, fixed = TRUE)) { + m <- strsplit(merge_function, split = "::", fixed = TRUE)[[1]] + data <- teal.code::eval_code(data, call("library", m[1])) + } + data +} From 063853436a85129a70f28dbedefdd88626c57ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Revilla?= Date: Wed, 14 May 2025 13:04:32 +0200 Subject: [PATCH 110/110] Fix unresolved updates --- R/module_input.R | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/R/module_input.R b/R/module_input.R index e4673565..65187c09 100644 --- a/R/module_input.R +++ b/R/module_input.R @@ -41,18 +41,23 @@ module_input_server <- function(id, spec, data) { } else { d <- data } + if (!anyNA(spec) && is.delayed(spec)) { spec <- resolver(spec, d) } + for (i in seq_along(names(input))) { variable <- names(input)[i] x <- input[[variable]] - spec_v <- spec[[variable]] - # resolved <- !is.character(spec_v$names) && all(x %in% spec_v$names) && any(!x %in% spec_v$select) - - if (!is.null(x) && any(nzchar(x))) { - spec <- resolver(update_spec(spec, variable, x), d) - } else { + update_is_empty <- !is.null(x) && all(!nzchar(x)) + if (update_is_empty) { + break + } + update_is_valid <- all(x %in% spec[[variable]][["choices"]]) + # Includes for adding or removing but not reordering + selection_is_new <- length(x) != length(spec[[variable]][["selected"]]) + if (update_is_valid && selection_is_new) { + spec <- update_spec(spec, variable, x) spec <- resolver(spec, d) } } @@ -60,15 +65,12 @@ module_input_server <- function(id, spec, data) { }) observe({ - req(react_updates()) - spec <- react_updates() + spec <- req(react_updates()) + req(!is.delayed(spec)) + # Relies on order of arguments for (i in seq_along(spec)) { variable <- names(spec)[i] - # Relies on order of arguments - if (is.delayed(spec[[variable]])) { - break - } shiny::updateSelectInput( session, variable, @@ -85,6 +87,7 @@ module_input_server <- function(id, spec, data) { react_selection <- reactive({ spec <- req(react_updates()) req(!is.delayed(spec)) + # FIXME: breaks with conditional specification: list(spec, spec) selection <- vector("list", length(spec)) names(selection) <- names(spec) for (i in seq_along(spec)) {