1 |
## Callable ==== |
|
2 |
#' |
|
3 |
#' @title A \code{Callable} class of objects |
|
4 |
#' |
|
5 |
#' @description Object that stores function name with its arguments. Methods to get call and run it. |
|
6 |
#' @keywords internal |
|
7 |
#' |
|
8 |
Callable <- R6::R6Class( # nolint |
|
9 |
"Callable", |
|
10 | ||
11 |
## __Public Methods ==== |
|
12 |
public = list( |
|
13 |
#' @description |
|
14 |
#' Create a new \code{CallableCode} object |
|
15 |
#' |
|
16 |
#' @param env (\code{environment})\cr |
|
17 |
#' environment where the call will be evaluated |
|
18 |
#' |
|
19 |
#' @return new \code{CallableCode} object |
|
20 |
initialize = function(env) { |
|
21 | 229x |
stopifnot(is.environment(env)) |
22 | 229x |
private$env <- env |
23 | 229x |
logger::log_trace("Callable initialized.") |
24 | 229x |
invisible(self) |
25 |
}, |
|
26 |
#' @description |
|
27 |
#' Assigns \code{x <- value} object to \code{env}. Assigned object can't |
|
28 |
#' be modified within local environment as it will be locked by using |
|
29 |
#' \code{lockBinding}. This also means that this object can't be reassigned |
|
30 |
#' which will throw an error. |
|
31 |
#' @param x (\code{character} value)\cr |
|
32 |
#' name of the variable in class environment |
|
33 |
#' @param value (\code{data.frame})\cr |
|
34 |
#' object to be assigned to \code{x} |
|
35 |
#' |
|
36 |
#' @return (\code{self}) invisibly for chaining. |
|
37 |
assign_to_env = function(x, value) { |
|
38 |
# assign variable once |
|
39 | 63x |
if (!exists(x, envir = private$env)) { |
40 | 54x |
assign(x, value, envir = private$env) |
41 | ||
42 |
# variable can't be modified |
|
43 | 54x |
lockBinding(sym = x, env = private$env) |
44 | 54x |
logger::log_trace("Callable$assign_to_env assigned '{ x }' to the environment.") |
45 |
} |
|
46 | ||
47 | 63x |
return(invisible(self)) |
48 |
}, |
|
49 |
#' @description |
|
50 |
#' Execute \code{Callable} function or code. |
|
51 |
#' |
|
52 |
#' @param return (\code{logical} value)\cr |
|
53 |
#' whether to return an object |
|
54 |
#' @param args (\code{NULL} or named \code{list})\cr |
|
55 |
#' supplied for callable functions only, these are dynamic arguments passed to function. |
|
56 |
#' Dynamic arguments are executed in this call and are not saved which means that |
|
57 |
#' \code{self$get_call()} won't include them later. |
|
58 |
#' @param try (\code{logical} value)\cr |
|
59 |
#' whether perform function evaluation inside \code{try} clause |
|
60 |
#' |
|
61 |
#' @return nothing or output from function depending on \code{return} |
|
62 |
#' argument. If \code{run} fails it will return object of class \code{simple-error error} |
|
63 |
#' when \code{try = TRUE} or will stop if \code{try = FALSE}. |
|
64 |
run = function(return = TRUE, args = NULL, try = FALSE) { |
|
65 | 150x |
checkmate::assert_flag(return) |
66 | 150x |
checkmate::assert_list(args, names = "unique", min.len = 0, null.ok = TRUE) |
67 | 150x |
checkmate::assert_flag(try) |
68 | ||
69 |
# args are "dynamic" are used only to evaluate this call |
|
70 |
# - args not saved to private$call persistently |
|
71 | 150x |
expr <- self$get_call(deparse = FALSE, args = args) |
72 | ||
73 | 150x |
res <- tryCatch( |
74 | 150x |
eval(expr, envir = private$env), |
75 | 150x |
error = function(e) e |
76 |
) |
|
77 | 150x |
private$check_run_output(res, try = try) |
78 | ||
79 | 145x |
logger::log_trace("Callable$run callable has been run.") |
80 | 145x |
if (return) { |
81 | 144x |
return(res) |
82 |
} else { |
|
83 | 1x |
return(invisible(NULL)) |
84 |
} |
|
85 |
}, |
|
86 |
#' @description |
|
87 |
#' Check if evaluation of the function has not failed. |
|
88 |
#' |
|
89 |
#' @return (\code{logical}) \code{TRUE} if evaluation of the function failed or \code{FALSE} |
|
90 |
#' if evaluation failed or function hasn't yet been called. |
|
91 |
is_failed = function() { |
|
92 | 151x |
return(private$failed) |
93 |
}, |
|
94 |
#' @description |
|
95 |
#' Get error message from last function execution |
|
96 |
#' |
|
97 |
#' @return (\code{character}) object with error message or \code{character(0)} if last |
|
98 |
#' function evaluation was successful. |
|
99 |
get_error_message = function() { |
|
100 | 3x |
return(private$error_msg) |
101 |
} |
|
102 |
), |
|
103 | ||
104 |
## __Private Fields ==== |
|
105 |
private = list( |
|
106 |
call = NULL, # a call object |
|
107 |
env = NULL, # environment where function is called |
|
108 |
failed = FALSE, |
|
109 |
error_msg = character(0), |
|
110 |
## __Private Methods ==== |
|
111 | ||
112 |
# The deep clone function deep clones the environment of the Callable so |
|
113 |
# that it is distinct for the copy |
|
114 |
deep_clone = function(name, value) { |
|
115 | 155x |
deep_clone_r6(name, value) |
116 |
}, |
|
117 |
# Checks output and handles error messages |
|
118 |
check_run_output = function(res, try) { |
|
119 | 150x |
if (inherits(res, "error")) { |
120 | 8x |
msg <- conditionMessage(res) |
121 | 8x |
is_locked <- grepl(pattern = "cannot change value of locked", x = msg) |
122 | ||
123 | 8x |
error_msg <- if (is_locked) { |
124 | 2x |
locked_var <- gsub("^.+\\'(.+)\\'$", "\\1", x = msg) |
125 | 2x |
sprintf( |
126 | 2x |
"Modification of the local variable '%1$s' is not allowed. %2$s '%1$s'", |
127 | 2x |
locked_var, |
128 | 2x |
"Please add proxy variable to CallableCode to obtain results depending on altered" |
129 |
) |
|
130 |
} else { |
|
131 | 6x |
msg |
132 |
} |
|
133 | ||
134 | 8x |
if (try) { |
135 | 3x |
private$failed <- TRUE |
136 | 3x |
private$error_msg <- error_msg |
137 | 3x |
logger::log_error("Callable$check_run_output { deparse1(error_msg) }.") |
138 |
} else { |
|
139 | 5x |
stop(error_msg, call. = FALSE) |
140 |
} |
|
141 |
} else { |
|
142 | 142x |
private$failed <- FALSE |
143 | 142x |
private$error_msg <- character(0) |
144 |
} |
|
145 |
} |
|
146 |
) |
|
147 |
) |
1 |
## MAETealDataset ==== |
|
2 |
#' |
|
3 |
#' @title R6 Class representing a `MultiAssayExperiment` object with its attributes |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("experimental")` |
|
6 |
#' Any `MultiAssayExperiment` object can be stored inside this `MAETealDataset`. |
|
7 |
#' Some attributes like colnames, dimension or column names for a specific type will |
|
8 |
#' be automatically derived. |
|
9 |
#' |
|
10 |
#' |
|
11 |
#' @param dataname (`character`)\cr |
|
12 |
#' A given name for the dataset it may not contain spaces |
|
13 |
#' @param x (`MultiAssayExperiment`)\cr |
|
14 |
#' @param keys optional, (`character`)\cr |
|
15 |
#' A vector of primary keys |
|
16 |
#' @param code (`character` or `CodeClass`)\cr |
|
17 |
#' A character string defining the code needed to produce the data set in `x`. |
|
18 |
#' `initialize()` and `recreate()` accept code as `CodeClass` |
|
19 |
#' which is also needed to preserve the code uniqueness and correct order. |
|
20 |
#' @param label (`character`)\cr |
|
21 |
#' Label to describe the dataset |
|
22 |
#' @param vars (named `list`)) \cr |
|
23 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
24 |
#' other constant value, this/these object(s) should be included as named |
|
25 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
26 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
27 |
#' It is recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
28 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
29 |
#' are included to this object as local `vars` and they cannot be modified |
|
30 |
#' within another dataset. |
|
31 |
#' @param metadata (named `list` or `NULL`) \cr |
|
32 |
#' Field containing metadata about the dataset. Each element of the list |
|
33 |
#' should be atomic and of length one. |
|
34 |
#' |
|
35 |
#' @seealso [`TealDataset`] |
|
36 |
#' |
|
37 |
MAETealDataset <- R6::R6Class( # nolint |
|
38 |
"MAETealDataset", |
|
39 |
inherit = TealDataset, |
|
40 |
## __Public Methods ==== |
|
41 |
public = list( |
|
42 |
#' @description |
|
43 |
#' Create a new object of `MAETealDataset` class |
|
44 |
#' |
|
45 |
initialize = function(dataname, |
|
46 |
x, |
|
47 |
keys = character(0), |
|
48 |
code = character(0), |
|
49 |
label = character(0), |
|
50 |
vars = list(), |
|
51 |
metadata = NULL) { |
|
52 | 18x |
if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { |
53 | ! |
stop("Cannot load MultiAssayExperiment - please install the package or restart your session.") |
54 |
} |
|
55 | 18x |
checkmate::assert_string(dataname) |
56 | 18x |
stopifnot(inherits(x, "MultiAssayExperiment")) |
57 | 18x |
checkmate::assert_character(keys, any.missing = FALSE) |
58 | 18x |
checkmate::assert( |
59 | 18x |
checkmate::check_character(code, max.len = 1, any.missing = FALSE), |
60 | 18x |
checkmate::check_class(code, "CodeClass") |
61 |
) |
|
62 | 18x |
checkmate::assert_character(label, max.len = 1, null.ok = TRUE, any.missing = FALSE) |
63 | 18x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
64 | ||
65 |
# validate metadata as a list of length one atomic |
|
66 | 18x |
validate_metadata(metadata) |
67 | ||
68 | 18x |
private$.raw_data <- x |
69 | 18x |
private$metadata <- metadata |
70 | 18x |
private$set_dataname(dataname) |
71 | 18x |
self$set_vars(vars) |
72 | 18x |
self$set_dataset_label(label) |
73 | 18x |
self$set_keys(keys) |
74 | ||
75 |
# needed if recreating dataset - we need to preserve code order and uniqueness |
|
76 | 18x |
private$code <- CodeClass$new() |
77 | 18x |
if (is.character(code)) { |
78 | 17x |
self$set_code(code) |
79 |
} else { |
|
80 | 1x |
private$code$append(code) |
81 |
} |
|
82 | ||
83 | 18x |
logger::log_trace("MAETealDataset$initialize initialized dataset: { deparse1(self$get_dataname()) }.") |
84 | ||
85 | 18x |
return(invisible(self)) |
86 |
}, |
|
87 |
# ___ check ==== |
|
88 |
#' @description |
|
89 |
#' Check to determine if the raw data is reproducible from the `get_code()` code. |
|
90 |
#' @return |
|
91 |
#' `TRUE` if the dataset generated from evaluating the |
|
92 |
#' `get_code()` code is identical to the raw data, else `FALSE`. |
|
93 |
check = function() { |
|
94 | 3x |
logger::log_trace( |
95 | 3x |
"TealDataset$check executing the code to reproduce dataset: { deparse1(self$get_dataname()) }..." |
96 |
) |
|
97 | 3x |
if (!checkmate::test_character(self$get_code(), len = 1, pattern = "\\w+")) { |
98 | 1x |
stop( |
99 | 1x |
sprintf( |
100 | 1x |
"Cannot check preprocessing code of '%s' - code is empty.", |
101 | 1x |
self$get_dataname() |
102 |
) |
|
103 |
) |
|
104 |
} |
|
105 | ||
106 | 2x |
new_set <- private$execute_code( |
107 | 2x |
code = self$get_code_class(), |
108 | 2x |
vars = private$vars |
109 |
) |
|
110 | 2x |
res_check <- tryCatch( |
111 |
{ |
|
112 | 2x |
identical(self$get_raw_data(), new_set) |
113 |
}, |
|
114 | 2x |
error = function(e) { |
115 | ! |
FALSE |
116 |
} |
|
117 |
) |
|
118 | 2x |
logger::log_trace("TealDataset$check { deparse1(self$get_dataname()) } reproducibility result: { res_check }.") |
119 | ||
120 | 2x |
return(res_check) |
121 |
}, |
|
122 |
#' @description |
|
123 |
#' Check if keys has been specified correctly for dataset. Set of `keys` |
|
124 |
#' should distinguish unique rows or be `character(0)`. |
|
125 |
#' |
|
126 |
#' @return `TRUE` if dataset has been already pulled, else `FALSE` |
|
127 |
check_keys = function(keys = private$.keys) { |
|
128 | 8x |
if (length(keys) > 0) { |
129 | 3x |
if (!all(keys %in% self$get_colnames())) { |
130 | 1x |
stop("Primary keys specifed for ", self$get_dataname(), " do not exist in the data.") |
131 |
} |
|
132 | ||
133 | 2x |
duplicates <- get_key_duplicates(as.data.frame(SummarizedExperiment::colData(self$get_raw_data())), keys) |
134 | 2x |
if (nrow(duplicates) > 0) { |
135 | 1x |
stop( |
136 | 1x |
"Duplicate primary key values found in the dataset '", self$get_dataname(), "' :\n", |
137 | 1x |
paste0(utils::capture.output(print(duplicates))[-c(1, 3)], collapse = "\n"), |
138 | 1x |
call. = FALSE |
139 |
) |
|
140 |
} |
|
141 |
} |
|
142 |
}, |
|
143 |
#' @description |
|
144 |
#' Derive the column names |
|
145 |
#' @return `character` vector. |
|
146 |
get_colnames = function() { |
|
147 | 8x |
colnames(SummarizedExperiment::colData(private$.raw_data)) |
148 |
}, |
|
149 |
#' @description |
|
150 |
#' Derive the column labels |
|
151 |
#' @return `character` vector. |
|
152 |
get_column_labels = function() { |
|
153 | ! |
vapply( |
154 | ! |
X = SummarizedExperiment::colData(private$.raw_data), |
155 | ! |
FUN.VALUE = character(1), |
156 | ! |
FUN = function(x) { |
157 | ! |
label <- attr(x, "label") |
158 | ! |
if (length(label) != 1) { |
159 | ! |
NA_character_ |
160 |
} else { |
|
161 | ! |
label |
162 |
} |
|
163 |
} |
|
164 |
) |
|
165 |
}, |
|
166 |
#' @description |
|
167 |
#' Get the number of columns of the data |
|
168 |
#' @return `numeric` vector |
|
169 |
get_ncol = function() { |
|
170 | ! |
ncol(SummarizedExperiment::colData(private$.raw_data)) |
171 |
}, |
|
172 |
#' @description |
|
173 |
#' Get the number of rows of the data |
|
174 |
#' @return `numeric` vector |
|
175 |
get_nrow = function() { |
|
176 | ! |
nrow(SummarizedExperiment::colData(private$.raw_data)) |
177 |
}, |
|
178 |
#' @description |
|
179 |
#' Derive the row names |
|
180 |
#' @return `character` vector. |
|
181 |
get_rownames = function() { |
|
182 | ! |
rownames(SummarizedExperiment::colData(private$.raw_data)) |
183 |
}, |
|
184 |
#' @description |
|
185 |
#' Prints this `MAETealDataset`. |
|
186 |
#' @param ... additional arguments to the printing method |
|
187 |
#' |
|
188 |
#' @return invisibly self |
|
189 |
print = function(...) { |
|
190 | ! |
cat(sprintf("A MAETealDataset object containing data of %d subjects.\n", self$get_nrow())) |
191 | ! |
print(MultiAssayExperiment::experiments(self$get_raw_data())) |
192 | ! |
invisible(self) |
193 |
} |
|
194 |
), |
|
195 |
## __Private Fields ==== |
|
196 |
private = list( |
|
197 |
.raw_data = NULL, |
|
198 |
get_class_colnames = function(class_type = "character") { |
|
199 | ! |
checkmate::assert_string(class_type) |
200 | ||
201 | ! |
return_cols <- private$.colnames[which(vapply( |
202 | ! |
lapply(SummarizedExperiment::colData(private$.raw_data), class), |
203 | ! |
function(x, target_class_name) any(x %in% target_class_name), |
204 | ! |
logical(1), |
205 | ! |
target_class_name = class_type |
206 |
))] |
|
207 | ||
208 | ! |
return(return_cols) |
209 |
}, |
|
210 | ||
211 |
# Evaluate script code to modify data or to reproduce data |
|
212 |
# |
|
213 |
# @param code (`CodeClass`) the object storing the code to execute |
|
214 |
# @param vars (named `list`) additional pre-requisite vars to execute code |
|
215 |
# @return (`environment`) which stores modified `x` |
|
216 |
execute_code = function(code, vars = list()) { |
|
217 | 2x |
stopifnot(inherits(code, "CodeClass")) |
218 | 2x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
219 | ||
220 | 2x |
execution_environment <- new.env(parent = parent.env(globalenv())) |
221 | ||
222 |
# set up environment for execution |
|
223 | 2x |
for (vars_idx in seq_along(vars)) { |
224 | ! |
var_name <- names(vars)[[vars_idx]] |
225 | ! |
var_value <- vars[[vars_idx]] |
226 | ! |
if (inherits(var_value, "TealDatasetConnector") || inherits(var_value, "TealDataset")) { |
227 | ! |
var_value <- get_raw_data(var_value) |
228 |
} |
|
229 | ! |
assign(envir = execution_environment, x = var_name, value = var_value) |
230 |
} |
|
231 | ||
232 |
# execute |
|
233 | 2x |
code$eval(envir = execution_environment) |
234 | ||
235 | 2x |
if (!inherits(execution_environment[[self$get_dataname()]], "MultiAssayExperiment")) { |
236 | ! |
out_msg <- sprintf( |
237 | ! |
"\n%s\n\n - Code from %s needs to return a MultiAssayExperiment assigned to an object of dataset name.", |
238 | ! |
self$get_code(), |
239 | ! |
self$get_dataname() |
240 |
) |
|
241 | ||
242 | ! |
rlang::with_options( |
243 | ! |
.expr = stop(out_msg, call. = FALSE), |
244 | ! |
warning.length = max(min(8170, nchar(out_msg) + 30), 100) |
245 |
) |
|
246 |
} |
|
247 | ||
248 | 2x |
new_set <- execution_environment[[self$get_dataname()]] |
249 | ||
250 | 2x |
return(new_set) |
251 |
} |
|
252 |
) |
|
253 |
) |
|
254 | ||
255 |
#' S3 method to construct an `MAETealDataset` object from `MultiAssayExperiment` |
|
256 |
#' |
|
257 |
#' @rdname dataset |
|
258 |
#' |
|
259 |
#' @examples |
|
260 |
#' # Simple example |
|
261 |
#' utils::data(miniACC, package = "MultiAssayExperiment") |
|
262 |
#' mae_d <- dataset( |
|
263 |
#' "MAE", |
|
264 |
#' miniACC, |
|
265 |
#' keys = c("STUDYID", "USUBJID"), |
|
266 |
#' metadata = list(type = "example") |
|
267 |
#' ) |
|
268 |
#' mae_d$get_dataname() |
|
269 |
#' mae_d$get_dataset_label() |
|
270 |
#' mae_d$get_metadata() |
|
271 |
#' mae_d$get_code() |
|
272 |
#' mae_d$get_raw_data() |
|
273 |
#' @export |
|
274 |
dataset.MultiAssayExperiment <- function(dataname, # nolint |
|
275 |
x, |
|
276 |
keys = character(0), |
|
277 |
label = data_label(x), |
|
278 |
code = character(0), |
|
279 |
vars = list(), |
|
280 |
metadata = NULL) { |
|
281 | 4x |
if (!requireNamespace("MultiAssayExperiment", quietly = TRUE)) { |
282 | ! |
stop("Cannot load MultiAssayExperiment - please install the package or restart your session.") |
283 |
} |
|
284 | 4x |
checkmate::assert_string(dataname) |
285 | 4x |
checkmate::assert( |
286 | 4x |
checkmate::check_character(code, max.len = 1, any.missing = FALSE), |
287 | 4x |
checkmate::check_class(code, "CodeClass") |
288 |
) |
|
289 | 4x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
290 | ||
291 | 4x |
MAETealDataset$new( |
292 | 4x |
dataname = dataname, |
293 | 4x |
x = x, |
294 | 4x |
keys = keys, |
295 | 4x |
code = code, |
296 | 4x |
label = label, |
297 | 4x |
vars = vars, |
298 | 4x |
metadata = metadata |
299 |
) |
|
300 |
} |
|
301 | ||
302 |
#' The constructor of `MAETealDataset` |
|
303 |
#' |
|
304 |
#' @description `r lifecycle::badge("deprecated")` |
|
305 |
#' |
|
306 |
#' @inheritParams dataset |
|
307 |
#' @param x (`MultiAssayExperiment`) |
|
308 |
#' |
|
309 |
#' @examples |
|
310 |
#' # Simple example |
|
311 |
#' utils::data(miniACC, package = "MultiAssayExperiment") |
|
312 |
#' mae_d <- dataset("MAE", miniACC) |
|
313 |
#' mae_d$get_dataname() |
|
314 |
#' mae_d$get_dataset_label() |
|
315 |
#' mae_d$get_code() |
|
316 |
#' mae_d$get_raw_data() |
|
317 |
#' @export |
|
318 |
mae_dataset <- function(dataname, |
|
319 |
x, |
|
320 |
label = data_label(x), |
|
321 |
code = character(0), |
|
322 |
vars = list()) { |
|
323 | ! |
lifecycle::deprecate_soft( |
324 | ! |
when = "0.10.1", |
325 | ! |
what = "teal.data::mae_dataset()", |
326 | ! |
with = "teal.data::dataset()" |
327 |
) |
|
328 | ||
329 | ! |
if (!inherits(x, "MultiAssayExperiment")) { |
330 | ! |
stop("Argument x must be a MultiAssayExperiment object") |
331 |
} |
|
332 | ||
333 | ! |
dataset( |
334 | ! |
dataname = dataname, |
335 | ! |
x = x, |
336 | ! |
code = code, |
337 | ! |
label = label, |
338 | ! |
vars = vars |
339 |
) |
|
340 |
} |
1 |
## JoinKeys ==== |
|
2 |
#' |
|
3 |
#' |
|
4 |
#' @title R6 Class to store relationships for joining datasets |
|
5 |
#' |
|
6 |
#' @description `r lifecycle::badge("stable")` |
|
7 |
#' This class stores symmetric links between pairs of key-values |
|
8 |
#' (e.g. column A of dataset X can be joined with column B of dataset Y). This relationship |
|
9 |
#' is more general than the SQL foreign key relationship which also imposes constraints on the values |
|
10 |
#' of these columns. |
|
11 |
#' @param dataset_1 (`character`) one dataset name |
|
12 |
#' @param dataset_2 (`character`) other dataset name |
|
13 |
#' |
|
14 |
#' @examples |
|
15 |
#' x <- teal.data:::JoinKeys$new() |
|
16 |
#' x$set( |
|
17 |
#' list( |
|
18 |
#' join_key("dataset_A", "dataset_B", c("col_1" = "col_a")), |
|
19 |
#' join_key("dataset_A", "dataset_C", c("col_2" = "col_x", "col_3" = "col_y")) |
|
20 |
#' ) |
|
21 |
#' ) |
|
22 |
#' x$get() |
|
23 |
#' x$mutate("dataset_A", "dataset_B", c("col1" = "col10")) |
|
24 |
#' x$get("dataset_A", "dataset_B") |
|
25 |
JoinKeys <- R6::R6Class( # nolint |
|
26 |
classname = "JoinKeys", |
|
27 |
## __Public Methods ==== |
|
28 |
public = list( |
|
29 |
#' @description |
|
30 |
#' Create a new object of `JoinKeys` |
|
31 |
#' @return empty (`JoinKeys`) |
|
32 |
initialize = function() { |
|
33 | 247x |
logger::log_trace("JoinKeys initialized.") |
34 | 247x |
return(invisible(self)) |
35 |
}, |
|
36 |
#' @description |
|
37 |
#' Split the current `JoinKeys` object into a named list of join keys objects with an element for each dataset |
|
38 |
#' @return (`list`) a list of `JoinKeys` object |
|
39 |
split = function() { |
|
40 | 6x |
list_of_list_of_join_key_set <- lapply( |
41 | 6x |
names(self$get()), |
42 | 6x |
function(dataset_1) { |
43 | 27x |
lapply( |
44 | 27x |
names(self$get()[[dataset_1]]), |
45 | 27x |
function(dataset_2) join_key(dataset_1, dataset_2, self$get()[[dataset_1]][[dataset_2]]) |
46 |
) |
|
47 |
} |
|
48 |
) |
|
49 | 6x |
res <- lapply( |
50 | 6x |
list_of_list_of_join_key_set, |
51 | 6x |
function(x) { |
52 | 27x |
y <- JoinKeys$new() |
53 | 27x |
y$set(x) |
54 |
} |
|
55 |
) |
|
56 | 6x |
names(res) <- names(self$get()) |
57 | ||
58 | 6x |
logger::log_trace("JoinKeys$split keys split.") |
59 | 6x |
return(res) |
60 |
}, |
|
61 |
#' @description |
|
62 |
#' Merging a list (or one) of `JoinKeys` objects into the current `JoinKeys` object |
|
63 |
#' @param x `list` of `JoinKeys` objects or single `JoinKeys` object |
|
64 |
#' @return (`self`) invisibly for chaining |
|
65 |
merge = function(x) { |
|
66 | 5x |
if (inherits(x, "JoinKeys")) x <- list(x) |
67 | 18x |
checkmate::assert_list(x, types = "JoinKeys", min.len = 1) |
68 | 13x |
for (jk in x) { |
69 | 25x |
for (dataset_1 in names(jk$get())) { |
70 | 87x |
for (dataset_2 in names(jk$get()[[dataset_1]])) { |
71 | 102x |
self$mutate(dataset_1, dataset_2, jk$get()[[dataset_1]][[dataset_2]]) |
72 |
} |
|
73 |
} |
|
74 |
} |
|
75 | 13x |
logger::log_trace("JoinKeys$merge keys merged.") |
76 | 13x |
return(invisible(self)) |
77 |
}, |
|
78 |
#' @description |
|
79 |
#' Get join keys between two datasets. |
|
80 |
#' @return (`character`) named character vector x with names(x) the |
|
81 |
#' columns of `dataset_1` and the values of `(x)` the corresponding join |
|
82 |
#' keys in `dataset_2` or `character(0)` if no relationship |
|
83 |
#' @details if one or both of `dataset_1` and `dataset_2` are missing then |
|
84 |
#' underlying keys structure is returned for further processing |
|
85 |
get = function(dataset_1, dataset_2) { |
|
86 | 857x |
if (missing(dataset_1) && missing(dataset_2)) { |
87 | 368x |
return(private$.keys) |
88 |
} |
|
89 | 489x |
if (missing(dataset_2)) { |
90 | 86x |
return(private$.keys[[dataset_1]]) |
91 |
} |
|
92 | 403x |
if (missing(dataset_1)) { |
93 | 1x |
return(private$.keys[[dataset_2]]) |
94 |
} |
|
95 | 402x |
if (is.null(private$.keys[[dataset_1]][[dataset_2]])) { |
96 | 152x |
return(character(0)) |
97 |
} |
|
98 | 250x |
return(private$.keys[[dataset_1]][[dataset_2]]) |
99 |
}, |
|
100 |
#' @description |
|
101 |
#' Change join_keys for a given pair of dataset names (or |
|
102 |
#' add join_keys for given pair if it does not exist) |
|
103 |
#' @param val (named `character`) column names used to join |
|
104 |
#' @return (`self`) invisibly for chaining |
|
105 |
mutate = function(dataset_1, dataset_2, val) { |
|
106 | 232x |
checkmate::assert_string(dataset_1) |
107 | 232x |
checkmate::assert_string(dataset_2) |
108 | 232x |
checkmate::assert_character(val, any.missing = FALSE) |
109 | ||
110 | 232x |
private$join_pair(join_key(dataset_1, dataset_2, val)) |
111 | ||
112 | 232x |
logger::log_trace( |
113 | 232x |
sprintf( |
114 | 232x |
"JoinKeys$mutate updated the keys between %s and %s to %s", |
115 | 232x |
dataset_1, |
116 | 232x |
dataset_2, |
117 | 232x |
paste(val, collapse = ", ") |
118 |
) |
|
119 |
) |
|
120 | 232x |
return(invisible(self)) |
121 |
}, |
|
122 |
#' @description |
|
123 |
#' Set up join keys basing on list of `JoinKeySet` objects. |
|
124 |
#' @param x `list` of `JoinKeySet` objects (which are created using the `join_key` function) |
|
125 |
#' or single `JoinKeySet` objects |
|
126 |
#' @details Note that join keys are symmetric although the relationship only needs |
|
127 |
#' to be specified once |
|
128 |
#' @return (`self`) invisibly for chaining |
|
129 |
set = function(x) { |
|
130 | 119x |
if (length(private$.keys) > 0) { |
131 | 1x |
stop("Keys already set, please use JoinKeys$mutate() to change them") |
132 |
} |
|
133 | 118x |
if (inherits(x, "JoinKeySet")) { |
134 | ! |
x <- list(x) |
135 |
} |
|
136 | ||
137 |
# check if any JoinKeySets share the same datasets but different values |
|
138 | 118x |
for (idx_1 in seq_along(x)) { |
139 | 213x |
for (idx_2 in seq_len(idx_1)) { |
140 | 363x |
private$check_compatible_keys(x[[idx_1]], x[[idx_2]]) |
141 |
} |
|
142 |
} |
|
143 | ||
144 | 110x |
checkmate::assert_list(x, types = "JoinKeySet", min.len = 1) |
145 | 110x |
lapply(x, private$join_pair) |
146 | ||
147 | 110x |
logger::log_trace("JoinKeys$set keys are set.") |
148 | 110x |
return(invisible(self)) |
149 |
}, |
|
150 |
#' @description |
|
151 |
#' Prints this `JoinKeys`. |
|
152 |
#' |
|
153 |
#' @param ... additional arguments to the printing method |
|
154 |
#' @return invisibly self |
|
155 |
print = function(...) { |
|
156 | 2x |
check_ellipsis(...) |
157 | 2x |
keys_list <- self$get() |
158 | 2x |
if (length(keys_list) > 0) { |
159 | 1x |
cat(sprintf( |
160 | 1x |
"A JoinKeys object containing foreign keys between %s datasets:\n", |
161 | 1x |
length(keys_list) |
162 |
)) |
|
163 | 1x |
print(keys_list) |
164 |
} else { |
|
165 | 1x |
cat("An empty JoinKeys object.") |
166 |
} |
|
167 | 2x |
invisible(self) |
168 |
}, |
|
169 |
#' @description |
|
170 |
#' Sets the parents of the datasets. |
|
171 |
#' |
|
172 |
#' @param named_list Named (`list`) of the parents datasets. |
|
173 |
#' |
|
174 |
#' @return (`self`) invisibly for chaining |
|
175 |
set_parents = function(named_list) { |
|
176 | 34x |
for (dataset in names(named_list)) { |
177 | 74x |
checkmate::assert( |
178 | 74x |
checkmate::check_null(self$get_parent(dataset)), |
179 | 74x |
checkmate::check_true( |
180 | 74x |
length(self$get_parent(dataset)) == 0 && |
181 | 74x |
length(named_list[[dataset]]) == 0 |
182 |
), |
|
183 | 74x |
checkmate::check_true(self$get_parent(dataset) == named_list[[dataset]]), |
184 | 74x |
"Please check the difference between provided datasets parents and provided join_keys parents." |
185 |
) |
|
186 | 73x |
if (is.null(self$get_parent(dataset))) { |
187 | 70x |
private$parents[[dataset]] <- named_list[[dataset]] |
188 |
} |
|
189 |
} |
|
190 | 33x |
invisible(self) |
191 |
}, |
|
192 |
#' @description |
|
193 |
#' Gets the parent of the desired dataset. |
|
194 |
#' |
|
195 |
#' @param dataname (`character`) name of the dataset. |
|
196 |
#' @return (`character`) the parent of the desired dataset |
|
197 |
get_parent = function(dataname) { |
|
198 | 241x |
if (missing(dataname)) { |
199 | 1x |
return(NULL) |
200 |
} |
|
201 | 240x |
private$parents[[dataname]] |
202 |
}, |
|
203 |
#' @description |
|
204 |
#' Gets the parents of the datasets. |
|
205 |
#' |
|
206 |
#' @return (`list`) A named list of the parents of all datasets |
|
207 |
get_parents = function() { |
|
208 | 53x |
private$parents |
209 |
}, |
|
210 |
#' @description |
|
211 |
#' Updates the keys of the datasets based on the parents. |
|
212 |
#' |
|
213 |
#' @return (`self`) invisibly for chaining |
|
214 |
update_keys_given_parents = function() { |
|
215 | 22x |
datanames <- names(self$get()) |
216 | 22x |
duplicate_pairs <- list() |
217 | 22x |
for (d1 in datanames) { |
218 | 46x |
d1_pk <- self$get(d1, d1) |
219 | 46x |
d1_parent <- self$get_parent(d1) |
220 | 46x |
for (d2 in datanames) { |
221 | 112x |
if (paste(d2, d1) %in% duplicate_pairs) { |
222 | 26x |
next |
223 |
} |
|
224 | 86x |
if (length(self$get(d1, d2)) == 0) { |
225 | 38x |
d2_parent <- self$get_parent(d2) |
226 | 38x |
d2_pk <- self$get(d2, d2) |
227 | ||
228 | 38x |
fk <- if (identical(d1, d2_parent)) { |
229 |
# first is parent of second -> parent keys -> first keys |
|
230 | 18x |
d1_pk |
231 | 38x |
} else if (identical(d1_parent, d2)) { |
232 |
# second is parent of first -> parent keys -> second keys |
|
233 | ! |
d2_pk |
234 | 38x |
} else if (identical(d1_parent, d2_parent) && length(d1_parent) > 0) { |
235 |
# both has the same parent -> parent keys |
|
236 | 10x |
self$get(d1_parent, d1_parent) |
237 |
} else { |
|
238 |
# cant find connection - leave empty |
|
239 | 10x |
next |
240 |
} |
|
241 | 28x |
self$mutate(d1, d2, fk) |
242 | 28x |
duplicate_pairs <- append(duplicate_pairs, paste(d1, d2)) |
243 |
} |
|
244 |
} |
|
245 |
} |
|
246 |
# check parent child relation |
|
247 | 22x |
private$check_parent_child() |
248 | ||
249 | 22x |
invisible(self) |
250 |
} |
|
251 |
), |
|
252 |
## __Private Fields ==== |
|
253 |
private = list( |
|
254 |
.keys = list(), |
|
255 |
parents = list(), |
|
256 |
join_pair = function(join_key) { |
|
257 | 430x |
dataset_1 <- join_key$dataset_1 |
258 | 430x |
dataset_2 <- join_key$dataset_2 |
259 | 430x |
keys <- join_key$keys |
260 | ||
261 | 430x |
if (is.null(private$.keys[[dataset_1]])) { |
262 | 228x |
private$.keys[[dataset_1]] <- list() |
263 |
} |
|
264 | 430x |
private$.keys[[dataset_1]][[dataset_2]] <- keys |
265 | ||
266 | 430x |
if (dataset_2 != dataset_1) { |
267 | 307x |
if (is.null(private$.keys[[dataset_2]])) { |
268 | 161x |
private$.keys[[dataset_2]] <- list() |
269 |
} |
|
270 | ||
271 | 307x |
if (length(keys) > 0) { |
272 | 302x |
keys <- setNames(names(keys), keys) |
273 |
} |
|
274 | 307x |
private$.keys[[dataset_2]][[dataset_1]] <- keys |
275 |
} |
|
276 |
}, |
|
277 |
# helper function to deterimine if two key sets contain incompatible keys |
|
278 |
# return TRUE if compatible, throw error otherwise |
|
279 |
check_compatible_keys = function(join_key_1, join_key_2) { |
|
280 | 363x |
error_message <- function(dataset_1, dataset_2) { |
281 | 7x |
stop( |
282 | 7x |
paste("cannot specify multiple different join keys between datasets:", dataset_1, "and", dataset_2) |
283 |
) |
|
284 |
} |
|
285 | ||
286 | ||
287 |
# if first datasets and the second datasets match and keys |
|
288 |
# must contain the same named elements |
|
289 | 363x |
if (join_key_1$dataset_1 == join_key_2$dataset_1 && join_key_1$dataset_2 == join_key_2$dataset_2) { |
290 | 212x |
if (!identical(sort(join_key_1$keys), sort(join_key_2$keys))) { |
291 | 3x |
error_message(join_key_1$dataset_1, join_key_1$dataset_2) |
292 |
} |
|
293 |
} |
|
294 | ||
295 |
# if first dataset of join_key_1 matches second dataset of join_key_2 |
|
296 |
# and the first dataset of join_key_2 must match second dataset of join_key_1 |
|
297 |
# and keys must contain the same elements but with names and values swapped |
|
298 | 359x |
if (join_key_1$dataset_1 == join_key_2$dataset_2 && join_key_1$dataset_2 == join_key_2$dataset_1) { |
299 |
# have to handle empty case differently as names(character(0)) is NULL |
|
300 | 39x |
if (length(join_key_1$keys) == 0 && length(join_key_2$keys) == 0) { |
301 | 2x |
return(TRUE) |
302 |
} |
|
303 | ||
304 | 37x |
if (xor(length(join_key_1$keys) == 0, length(join_key_2$keys) == 0) || |
305 | 37x |
!identical(sort(join_key_1$keys), sort(setNames(names(join_key_2$keys), join_key_2$keys)))) { |
306 | 4x |
error_message(join_key_1$dataset_1, join_key_1$dataset_2) |
307 |
} |
|
308 |
} |
|
309 | ||
310 |
# otherwise they are compatible |
|
311 | 353x |
return(TRUE) |
312 |
}, |
|
313 |
# checks the parent child relations are valid |
|
314 |
check_parent_child = function() { |
|
315 | 24x |
if (!is.null(self$get_parents())) { |
316 | 24x |
parents <- self$get_parents() |
317 | 24x |
for (idx1 in seq_along(parents)) { |
318 | 46x |
name_from <- names(parents)[[idx1]] |
319 | 46x |
for (idx2 in seq_along(parents[[idx1]])) { |
320 | 21x |
name_to <- parents[[idx1]][[idx2]] |
321 | 21x |
keys_from <- self$get(name_from, name_to) |
322 | 21x |
keys_to <- self$get(name_to, name_from) |
323 | 21x |
if (length(keys_from) == 0 && length(keys_to) == 0) { |
324 | 1x |
stop(sprintf("No join keys from %s to its parent (%s) and vice versa", name_from, name_to)) |
325 |
} |
|
326 | 20x |
if (length(keys_from) == 0) { |
327 | ! |
stop(sprintf("No join keys from %s to its parent (%s)", name_from, name_to)) |
328 |
} |
|
329 | 20x |
if (length(keys_to) == 0) { |
330 | ! |
stop(sprintf("No join keys from %s parent name (%s) to %s", name_from, name_to, name_from)) |
331 |
} |
|
332 |
} |
|
333 |
} |
|
334 |
} |
|
335 |
} |
|
336 |
) |
|
337 |
) |
|
338 | ||
339 |
# constructors ==== |
|
340 | ||
341 |
#' Create a `JoinKeys` out of a list of `JoinKeySet` objects |
|
342 |
#' |
|
343 |
#' @description `r lifecycle::badge("stable")` |
|
344 |
#' |
|
345 |
#' @param ... optional, a `JoinKeySet` objects created using the `join_key` function. |
|
346 |
#' @details Note that join keys are symmetric although the relationship only needs |
|
347 |
#' to be specified once. |
|
348 |
#' |
|
349 |
#' @return `JoinKeys` |
|
350 |
#' |
|
351 |
#' @export |
|
352 |
#' |
|
353 |
#' @examples |
|
354 |
#' join_keys() |
|
355 |
#' join_keys( |
|
356 |
#' join_key("dataset_A", "dataset_B", c("col_1" = "col_a")), |
|
357 |
#' join_key("dataset_A", "dataset_C", c("col_2" = "col_x", "col_3" = "col_y")) |
|
358 |
#' ) |
|
359 |
#' join_keys( |
|
360 |
#' join_key("dataset_A", "dataset_B", c("col_1" = "col_a")) |
|
361 |
#' ) |
|
362 |
join_keys <- function(...) { |
|
363 | 181x |
x <- list(...) |
364 | ||
365 | 181x |
res <- JoinKeys$new() |
366 | 181x |
if (length(x) > 0) { |
367 | 59x |
res$set(x) |
368 |
} |
|
369 | ||
370 | 173x |
res |
371 |
} |
|
372 | ||
373 |
# wrappers ==== |
|
374 |
#' Mutate `JoinKeys` with a new values |
|
375 |
#' |
|
376 |
#' @description `r lifecycle::badge("experimental")` |
|
377 |
#' Mutate `JoinKeys` with a new values |
|
378 |
#' |
|
379 |
#' @param x (`JoinKeys`) object to be modified |
|
380 |
#' @param dataset_1 (`character`) one dataset name |
|
381 |
#' @param dataset_2 (`character`) other dataset name |
|
382 |
#' @param val (named `character`) column names used to join |
|
383 |
#' |
|
384 |
#' @return modified `JoinKeys` object |
|
385 |
#' |
|
386 |
#' @export |
|
387 |
mutate_join_keys <- function(x, dataset_1, dataset_2, val) { |
|
388 | ! |
UseMethod("mutate_join_keys") |
389 |
} |
|
390 | ||
391 |
#' @rdname mutate_join_keys |
|
392 |
#' @export |
|
393 |
#' @examples |
|
394 |
#' # JoinKeys ---- |
|
395 |
#' |
|
396 |
#' x <- join_keys( |
|
397 |
#' join_key("dataset_A", "dataset_B", c("col_1" = "col_a")), |
|
398 |
#' join_key("dataset_A", "dataset_C", c("col_2" = "col_x", "col_3" = "col_y")) |
|
399 |
#' ) |
|
400 |
#' x$get("dataset_A", "dataset_B") |
|
401 |
#' |
|
402 |
#' mutate_join_keys(x, "dataset_A", "dataset_B", c("col_1" = "col_10")) |
|
403 |
#' x$get("dataset_A", "dataset_B") |
|
404 |
mutate_join_keys.JoinKeys <- function(x, dataset_1, dataset_2, val) { |
|
405 | ! |
x$mutate(dataset_1, dataset_2, val) |
406 |
} |
|
407 | ||
408 |
#' @rdname mutate_join_keys |
|
409 |
#' @export |
|
410 |
#' @examples |
|
411 |
#' # TealData ---- |
|
412 |
#' |
|
413 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
414 |
#' ADRS <- teal.data::example_cdisc_data("ADRS") |
|
415 |
#' |
|
416 |
#' x <- cdisc_data( |
|
417 |
#' cdisc_dataset("ADSL", ADSL), |
|
418 |
#' cdisc_dataset("ADRS", ADRS) |
|
419 |
#' ) |
|
420 |
#' x$get_join_keys()$get("ADSL", "ADRS") |
|
421 |
#' |
|
422 |
#' mutate_join_keys(x, "ADSL", "ADRS", c("COLUMN1" = "COLUMN2")) |
|
423 |
#' x$get_join_keys()$get("ADSL", "ADRS") |
|
424 |
mutate_join_keys.TealData <- function(x, dataset_1, dataset_2, val) { # nolint |
|
425 | ! |
x$mutate_join_keys(dataset_1, dataset_2, val) |
426 |
} |
|
427 | ||
428 | ||
429 |
#' Create a relationship between a pair of datasets |
|
430 |
#' |
|
431 |
#' @description `r lifecycle::badge("stable")` |
|
432 |
#' |
|
433 |
#' @inheritParams mutate_join_keys |
|
434 |
#' @param keys (optionally named `character`) where `names(keys)` are columns in `dataset_1` |
|
435 |
#' with relationship to columns of `dataset_2` given by the elements in `keys`. |
|
436 |
#' If `names(keys)` is `NULL` then the same column names are used for both `dataset_1` |
|
437 |
#' and `dataset_2`. |
|
438 |
#' |
|
439 |
#' @return object of class `JoinKeySet` to be passed into `join_keys` function. |
|
440 |
#' |
|
441 |
#' @seealso [join_keys()] |
|
442 |
#' |
|
443 |
#' @export |
|
444 |
join_key <- function(dataset_1, dataset_2, keys) { |
|
445 | 458x |
checkmate::assert_string(dataset_1) |
446 | 458x |
checkmate::assert_string(dataset_2) |
447 | 455x |
checkmate::assert_character(keys, any.missing = FALSE) |
448 | ||
449 | 453x |
if (length(keys) > 0) { |
450 | 409x |
if (is.null(names(keys))) { |
451 | 122x |
names(keys) <- keys |
452 |
} |
|
453 | ||
454 | 409x |
if (any(names(keys) == "")) { |
455 | 4x |
names(keys)[names(keys) == "" & keys != ""] <- keys[names(keys) == "" & keys != ""] |
456 |
} |
|
457 | ||
458 | 409x |
stopifnot(!is.null(names(keys))) |
459 | 409x |
stopifnot(!anyDuplicated(keys)) |
460 | 408x |
stopifnot(!anyDuplicated(names(keys))) |
461 |
} |
|
462 | ||
463 | 451x |
if (dataset_1 == dataset_2 && any(names(keys) != keys)) { |
464 | 1x |
stop("Keys within a dataset must match exactly: keys = c('A' = 'B') are not allowed") |
465 |
} |
|
466 | ||
467 | 450x |
structure( |
468 | 450x |
list( |
469 | 450x |
dataset_1 = dataset_1, |
470 | 450x |
dataset_2 = dataset_2, |
471 | 450x |
keys = keys |
472 |
), |
|
473 | 450x |
class = "JoinKeySet" |
474 |
) |
|
475 |
} |
1 |
#' Get Label Attributes of Variables in a \code{data.frame} |
|
2 |
#' |
|
3 |
#' Variable labels can be stored as a \code{label} attribute for each variable. |
|
4 |
#' This functions returns a named character vector with the variable labels |
|
5 |
#' (empty sting if not specified) |
|
6 |
#' |
|
7 |
#' @param x a \code{data.frame} object |
|
8 |
#' @param fill boolean in case the \code{label} attribute does not exist if |
|
9 |
#' \code{TRUE} the variable names is returned, otherwise \code{NA} |
|
10 |
#' |
|
11 |
#' @source This function was taken 1-1 from |
|
12 |
#' \href{https://cran.r-project.org/web/packages/formatters/index.html}{formatters} package, to reduce the complexity of |
|
13 |
#' the dependency tree. |
|
14 |
#' |
|
15 |
#' @seealso [col_relabel()] [`col_labels<-`] |
|
16 |
#' |
|
17 |
#' @return a named character vector with the variable labels, the names |
|
18 |
#' correspond to the variable names |
|
19 |
#' |
|
20 |
#' @export |
|
21 |
#' |
|
22 |
#' @examples |
|
23 |
#' x <- iris |
|
24 |
#' col_labels(x) |
|
25 |
#' col_labels(x) <- paste("label for", names(iris)) |
|
26 |
#' col_labels(x) |
|
27 |
col_labels <- function(x, fill = FALSE) { |
|
28 | 1x |
stopifnot(is.data.frame(x)) |
29 | 1x |
if (NCOL(x) == 0) { |
30 | ! |
return(character()) |
31 |
} |
|
32 | ||
33 | 1x |
y <- Map(function(col, colname) { |
34 | 4x |
label <- attr(col, "label") |
35 | ||
36 | 4x |
if (is.null(label)) { |
37 | 4x |
if (fill) { |
38 | ! |
colname |
39 |
} else { |
|
40 | 1x |
NA_character_ |
41 |
} |
|
42 |
} else { |
|
43 | ! |
if (!is.character(label) && !(length(label) == 1)) { |
44 | ! |
stop("label for variable ", colname, "is not a character string") |
45 |
} |
|
46 | ! |
as.vector(label) |
47 |
} |
|
48 | 1x |
}, x, colnames(x)) |
49 | ||
50 | 1x |
labels <- unlist(y, recursive = FALSE, use.names = TRUE) |
51 | ||
52 | 1x |
if (!is.character(labels)) { |
53 | ! |
stop("label extraction failed") |
54 |
} |
|
55 | ||
56 | 1x |
labels |
57 |
} |
|
58 | ||
59 |
#' Set Label Attributes of All Variables in a \code{data.frame} |
|
60 |
#' |
|
61 |
#' Variable labels can be stored as a \code{label} attribute for each variable. |
|
62 |
#' This functions sets all non-missing (non-NA) variable labels in a \code{data.frame} |
|
63 |
#' |
|
64 |
#' @inheritParams col_labels |
|
65 |
#' @param value new variable labels, \code{NA} removes the variable label |
|
66 |
#' |
|
67 |
#' @source This function was taken 1-1 from |
|
68 |
#' \href{https://cran.r-project.org/web/packages/formatters/index.html}{formatters} package, to reduce the complexity of |
|
69 |
#' the dependency tree. |
|
70 |
#' |
|
71 |
#' @seealso [col_labels()] [col_relabel()] |
|
72 |
#' |
|
73 |
#' @return modifies the variable labels of \code{x} |
|
74 |
#' |
|
75 |
#' @export |
|
76 |
#' |
|
77 |
#' @examples |
|
78 |
#' x <- iris |
|
79 |
#' col_labels(x) |
|
80 |
#' col_labels(x) <- paste("label for", names(iris)) |
|
81 |
#' col_labels(x) |
|
82 |
#' |
|
83 |
#' if (interactive()) { |
|
84 |
#' View(x) # in RStudio data viewer labels are displayed |
|
85 |
#' } |
|
86 |
`col_labels<-` <- function(x, value) { |
|
87 | 6x |
stopifnot( |
88 | 6x |
is.data.frame(x), |
89 | 6x |
is.character(value), |
90 | 6x |
ncol(x) == length(value) |
91 |
) |
|
92 | ||
93 | 6x |
theseq <- if (!is.null(names(value))) names(value) else seq_along(x) |
94 |
# across columns of x |
|
95 | 6x |
for (j in theseq) { |
96 | 26x |
attr(x[[j]], "label") <- if (!is.na(value[j])) { |
97 | 26x |
value[j] |
98 |
} else { |
|
99 | ! |
NULL |
100 |
} |
|
101 |
} |
|
102 | ||
103 | 6x |
x |
104 |
} |
|
105 | ||
106 |
#' Copy and Change Variable Labels of a \code{data.frame} |
|
107 |
#' |
|
108 |
#' Relabel a subset of the variables |
|
109 |
#' |
|
110 |
#' @inheritParams col_labels<- |
|
111 |
#' @param ... name-value pairs, where name corresponds to a variable name in |
|
112 |
#' \code{x} and the value to the new variable label |
|
113 |
#' |
|
114 |
#' @return a copy of \code{x} with changed labels according to \code{...} |
|
115 |
#' |
|
116 |
#' @source This function was taken 1-1 from |
|
117 |
#' \href{https://cran.r-project.org/web/packages/formatters/index.html}{formatters} package, to reduce the complexity of |
|
118 |
#' the dependency tree. |
|
119 |
#' |
|
120 |
#' @seealso [col_labels()] [`col_labels<-`] |
|
121 |
#' |
|
122 |
#' @export |
|
123 |
#' |
|
124 |
#' @examples |
|
125 |
#' x <- col_relabel(iris, Sepal.Length = "Sepal Length of iris flower") |
|
126 |
#' col_labels(x) |
|
127 |
#' |
|
128 |
col_relabel <- function(x, ...) { |
|
129 | ! |
stopifnot(is.data.frame(x)) |
130 | ! |
if (missing(...)) { |
131 | ! |
return(x) |
132 |
} |
|
133 | ! |
dots <- list(...) |
134 | ! |
varnames <- names(dots) |
135 | ! |
stopifnot(!is.null(varnames)) |
136 | ||
137 | ! |
map_varnames <- match(varnames, colnames(x)) |
138 | ||
139 | ! |
if (any(is.na(map_varnames))) { |
140 | ! |
stop("variables: ", paste(varnames[is.na(map_varnames)], collapse = ", "), " not found") |
141 |
} |
|
142 | ||
143 | ! |
if (any(vapply(dots, Negate(is.character), logical(1)))) { |
144 | ! |
stop("all variable labels must be of type character") |
145 |
} |
|
146 | ||
147 | ! |
for (i in seq_along(map_varnames)) { |
148 | ! |
attr(x[[map_varnames[[i]]]], "label") <- dots[[i]] |
149 |
} |
|
150 | ||
151 | ! |
x |
152 |
} |
1 |
#' Retrieve raw data |
|
2 |
#' |
|
3 |
#' @param x (`TealDataset`, `TealDatasetConnector`, `TealDataAbstract`)\cr |
|
4 |
#' object |
|
5 |
#' @param dataname (`character`)\cr |
|
6 |
#' Name of dataset to return raw data for. |
|
7 |
#' |
|
8 |
#' @description `r lifecycle::badge("stable")` |
|
9 |
#' |
|
10 |
#' @return `data.frame` with the raw data inserted into the R6 objects. In case of |
|
11 |
#' `TealDataAbstract`, `list` of `data.frame` can be returned |
|
12 |
#' if user doesn't specify `dataname` - (`get_raw_data` from all datasets). |
|
13 |
#' |
|
14 |
#' @export |
|
15 |
get_raw_data <- function(x, dataname = NULL) { |
|
16 | 214x |
checkmate::assert_string(dataname, null.ok = TRUE) |
17 | 213x |
UseMethod("get_raw_data") |
18 |
} |
|
19 | ||
20 |
#' @export |
|
21 |
#' @rdname get_raw_data |
|
22 |
#' @examples |
|
23 |
#' |
|
24 |
#' # TealDataset --------- |
|
25 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
26 |
#' |
|
27 |
#' x <- dataset(dataname = "ADSL", x = ADSL) |
|
28 |
#' get_raw_data(x) |
|
29 |
get_raw_data.TealDataset <- function(x, dataname = NULL) { |
|
30 | 192x |
if (!is.null(dataname)) { |
31 | 2x |
warning("'dataname' argument ignored - TealDataset can contain only one dataset.") |
32 |
} |
|
33 | 192x |
x$get_raw_data() |
34 |
} |
|
35 | ||
36 |
#' @export |
|
37 |
#' @rdname get_raw_data |
|
38 |
#' @examples |
|
39 |
#' |
|
40 |
#' # TealDatasetConnector --------- |
|
41 |
#' library(magrittr) |
|
42 |
#' pull_fun_adsl <- callable_function(teal.data::example_cdisc_data) %>% |
|
43 |
#' set_args(list(dataname = "ADSL")) |
|
44 |
#' dc <- dataset_connector("ADSL", pull_fun_adsl) |
|
45 |
#' load_dataset(dc) |
|
46 |
#' get_raw_data(dc) |
|
47 |
get_raw_data.TealDatasetConnector <- function(x, dataname = NULL) { # nolint |
|
48 | 17x |
if (!is.null(dataname)) { |
49 | 1x |
warning("'dataname' argument ignored - TealDatasetConnector can contain only one dataset.") |
50 |
} |
|
51 | 17x |
x$get_raw_data() |
52 |
} |
|
53 | ||
54 |
#' @rdname get_raw_data |
|
55 |
#' @export |
|
56 |
#' @examples |
|
57 |
#' |
|
58 |
#' # TealData ---------------- |
|
59 |
#' adsl <- cdisc_dataset( |
|
60 |
#' dataname = "ADSL", |
|
61 |
#' x = teal.data::example_cdisc_data("ADSL"), |
|
62 |
#' code = "library(teal.data)\nADSL <- teal.data::example_cdisc_data(\"ADSL\")" |
|
63 |
#' ) |
|
64 |
#' |
|
65 |
#' adtte <- cdisc_dataset( |
|
66 |
#' dataname = "ADTTE", |
|
67 |
#' x = teal.data::example_cdisc_data("ADTTE"), |
|
68 |
#' code = "library(teal.data)\nADTTE <- teal.data::example_cdisc_data(\"ADTTE\")" |
|
69 |
#' ) |
|
70 |
#' |
|
71 |
#' rd <- teal.data:::TealData$new(adsl, adtte) |
|
72 |
#' get_raw_data(rd) |
|
73 |
#' |
|
74 |
#' # TealDataConnector -------- |
|
75 |
#' library(magrittr) |
|
76 |
#' |
|
77 |
#' slice_cdisc_data <- function(dataname, n) { |
|
78 |
#' head(example_cdisc_data(dataname), n) |
|
79 |
#' } |
|
80 |
#' |
|
81 |
#' random_data_connector <- function(dataname) { |
|
82 |
#' fun_dataset_connector( |
|
83 |
#' dataname = dataname, |
|
84 |
#' fun = slice_cdisc_data, |
|
85 |
#' fun_args = list(dataname = dataname), |
|
86 |
#' ) |
|
87 |
#' } |
|
88 |
#' |
|
89 |
#' open_fun <- callable_function(library) |
|
90 |
#' open_fun$set_args(list(package = "teal.data")) |
|
91 |
#' |
|
92 |
#' con <- data_connection(open_fun = open_fun) |
|
93 |
#' con$set_open_server( |
|
94 |
#' function(id, connection) { |
|
95 |
#' moduleServer( |
|
96 |
#' id = id, |
|
97 |
#' module = function(input, output, session) { |
|
98 |
#' connection$open(try = TRUE) |
|
99 |
#' return(invisible(connection)) |
|
100 |
#' } |
|
101 |
#' ) |
|
102 |
#' } |
|
103 |
#' ) |
|
104 |
#' |
|
105 |
#' rdc <- relational_data_connector( |
|
106 |
#' connection = con, |
|
107 |
#' connectors = list(random_data_connector("ADSL"), random_data_connector("ADLB")) |
|
108 |
#' ) |
|
109 |
#' |
|
110 |
#' rdc$set_ui( |
|
111 |
#' function(id, connection, connectors) { |
|
112 |
#' ns <- NS(id) |
|
113 |
#' tagList( |
|
114 |
#' connection$get_open_ui(ns("open_connection")), |
|
115 |
#' numericInput(inputId = ns("n"), label = "Choose number of records", min = 0, value = 1), |
|
116 |
#' do.call( |
|
117 |
#' what = "tagList", |
|
118 |
#' args = lapply( |
|
119 |
#' connectors, |
|
120 |
#' function(connector) { |
|
121 |
#' div( |
|
122 |
#' connector$get_ui( |
|
123 |
#' id = ns(connector$get_dataname()) |
|
124 |
#' ), |
|
125 |
#' br() |
|
126 |
#' ) |
|
127 |
#' } |
|
128 |
#' ) |
|
129 |
#' ) |
|
130 |
#' ) |
|
131 |
#' } |
|
132 |
#' ) |
|
133 |
#' |
|
134 |
#' rdc$set_server( |
|
135 |
#' function(id, connection, connectors) { |
|
136 |
#' moduleServer( |
|
137 |
#' id = id, |
|
138 |
#' module = function(input, output, session) { |
|
139 |
#' # opens connection |
|
140 |
#' connection$get_open_server()(id = "open_connection", connection = connection) |
|
141 |
#' if (connection$is_opened()) { |
|
142 |
#' for (connector in connectors) { |
|
143 |
#' set_args(connector, args = list(n = input$n)) |
|
144 |
#' # pull each dataset |
|
145 |
#' connector$get_server()(id = connector$get_dataname()) |
|
146 |
#' if (connector$is_failed()) { |
|
147 |
#' break |
|
148 |
#' } |
|
149 |
#' } |
|
150 |
#' } |
|
151 |
#' } |
|
152 |
#' ) |
|
153 |
#' } |
|
154 |
#' ) |
|
155 |
#' |
|
156 |
#' \dontrun{ |
|
157 |
#' load_datasets(rdc) |
|
158 |
#' get_raw_data(rdc) |
|
159 |
#' } |
|
160 |
#' |
|
161 |
#' # TealData (with connectors) -------- |
|
162 |
#' drc <- cdisc_data(rdc) |
|
163 |
#' \dontrun{ |
|
164 |
#' get_raw_data(drc) |
|
165 |
#' } |
|
166 |
get_raw_data.TealDataAbstract <- function(x, dataname = NULL) { # nolint |
|
167 | 4x |
if (!is.null(dataname)) { |
168 | ! |
datasets_names <- x$get_datanames() |
169 | ! |
if (dataname %in% datasets_names) { |
170 | ! |
if (is_pulled(x$get_items(dataname))) { |
171 | ! |
get_raw_data( |
172 | ! |
get_dataset(x, dataname = dataname) |
173 |
) |
|
174 |
} else { |
|
175 | ! |
stop( |
176 | ! |
sprintf("'%s' has not been pulled yet\n - please use `load_dataset()` first.", dataname), |
177 | ! |
call. = FALSE |
178 |
) |
|
179 |
} |
|
180 |
} else { |
|
181 | ! |
stop("The dataname supplied does not exist.") |
182 |
} |
|
183 |
} else { |
|
184 | 4x |
lapply( |
185 | 4x |
get_datasets(x), |
186 | 4x |
get_raw_data |
187 |
) |
|
188 |
} |
|
189 |
} |
1 |
## `CodeClass` ==== |
|
2 |
#' |
|
3 |
#' @title Code Class |
|
4 |
#' @keywords internal |
|
5 |
#' |
|
6 |
#' @examples |
|
7 |
#' cc <- teal.data:::CodeClass$new() |
|
8 |
#' cc$set_code(c("foo <- function() {1}", "foo2 <- function() {2}")) |
|
9 |
#' cc$get_code() |
|
10 |
#' cc$get_code(deparse = FALSE) |
|
11 |
#' |
|
12 |
#' cc$set_code(c("DF <- data.frame(x = 1:10)", "DF$y <- 1"), "DF") |
|
13 |
#' cc$set_code("DF$a <- foo()", "DF") |
|
14 |
#' |
|
15 |
#' # dependent dataset |
|
16 |
#' cc$set_code(c("DF2 <- data.frame(x2 = 1:10)", "DF2$y2 <- DF$y"), "DF2", deps = "DF") |
|
17 |
#' |
|
18 |
#' cc$set_code("baz <- function() {2}") |
|
19 |
#' cc$set_code("DF2$a <- baz()", "DF2") |
|
20 |
#' |
|
21 |
#' cc$get_code() |
|
22 |
#' cc$get_code("DF") |
|
23 |
#' cc$get_code("DF2") |
|
24 |
#' |
|
25 |
#' |
|
26 |
#' x1 <- teal.data:::CodeClass$new() |
|
27 |
#' x1$set_code("DF <- data.frame(x = 1:10)", "DF") |
|
28 |
#' x1$get_code() |
|
29 |
#' |
|
30 |
#' x2 <- teal.data:::CodeClass$new() |
|
31 |
#' x2$set_code(c("DF2 <- data.frame(x2 = 1:10)", "DF2$x2 <- DF$x"), "DF2", deps = "DF") |
|
32 |
#' x2$get_code() |
|
33 |
#' |
|
34 |
#' x <- teal.data:::CodeClass$new() |
|
35 |
#' x$append(x1) |
|
36 |
#' x$append(x2) |
|
37 |
#' |
|
38 |
#' x$get_code() |
|
39 |
#' x$get_code("DF") |
|
40 |
#' x$get_code("DF2") |
|
41 |
#' x$get_code(c("DF", "DF2")) |
|
42 |
#' |
|
43 |
#' x3 <- teal.data:::CodeClass$new() |
|
44 |
#' x3$set_code("DF3 <- data.frame(x3 = 1:10) ", "DF3") |
|
45 |
#' x3$get_code() |
|
46 |
#' |
|
47 |
#' x$append(x3) |
|
48 |
#' x$get_code("DF3") |
|
49 |
#' |
|
50 |
#' # mutation simulation |
|
51 |
#' x$set_code("DF3$x <- foo(DF$x)", "DF3", deps = "DF") |
|
52 |
#' x$get_code("DF3") |
|
53 |
CodeClass <- R6::R6Class( # nolint |
|
54 |
"CodeClass", |
|
55 |
## __Public Methods ==== |
|
56 |
public = list( |
|
57 |
#' @description |
|
58 |
#' `CodeClass` constructor |
|
59 |
#' @param code (`character`) vector of code text to be set |
|
60 |
#' @param dataname optional, (`character`) vector of `datanames` to assign code to. If empty then the code |
|
61 |
#' is considered to be "global" |
|
62 |
#' @param deps optional, (`character`) vector of `datanames` that given code depends on |
|
63 |
#' @return object of class `CodeClass` |
|
64 |
initialize = function(code = character(0), dataname = character(0), deps = character(0)) { |
|
65 | 3518x |
if (length(code) > 0) { |
66 | 13x |
self$set_code(code, dataname, deps) |
67 |
} |
|
68 | 3518x |
logger::log_trace("CodeClass initialized.") |
69 | 3518x |
return(invisible(self)) |
70 |
}, |
|
71 |
#' @description |
|
72 |
#' Append `CodeClass` object to a given `CodeClass` object |
|
73 |
#' @param x (`CodeClass`) object to be appended |
|
74 |
#' @return changed `CodeClass` object |
|
75 |
append = function(x) { |
|
76 | 3022x |
stopifnot(inherits(x, "CodeClass")) |
77 | 3022x |
if (length(x$code) > 0) { |
78 | 1458x |
for (code_i in x$code) { |
79 | 2782x |
private$set_code_single(code_i) |
80 |
} |
|
81 | 1458x |
logger::log_trace("CodeClass$append CodeClass appended.") |
82 |
} |
|
83 | ||
84 | 3022x |
return(invisible(self)) |
85 |
}, |
|
86 |
#' @description |
|
87 |
#' Set code in form of character |
|
88 |
#' @param code (`character`) vector of code text to be set |
|
89 |
#' @param dataname optional, (`character`) vector of `datanames` to assign code to. If empty then the code |
|
90 |
#' is considered to be "global" |
|
91 |
#' @param deps optional, (`character`) vector of `datanames` that given code depends on |
|
92 |
#' |
|
93 |
#' @return changed `CodeClass` object |
|
94 |
set_code = function(code, dataname = character(0), deps = character(0)) { |
|
95 | 793x |
checkmate::assert_character(code, min.len = 1, any.missing = FALSE) |
96 | 793x |
checkmate::assert_character(dataname, any.missing = FALSE) |
97 | 793x |
stopifnot(!(dataname %in% deps)) |
98 | ||
99 | 793x |
code <- pretty_code_string(code) |
100 | ||
101 | 793x |
for (code_single in code) { |
102 | 822x |
private$set_code_single(code_single, dataname, deps) |
103 |
} |
|
104 | 793x |
logger::log_trace("CodeClass$set_code code set.") |
105 | 793x |
return(invisible(self)) |
106 |
}, |
|
107 |
#' @description |
|
108 |
#' Get the code for a given data names |
|
109 |
#' @param dataname optional, (`character`) vector of `datanames` for which the code is extracted. |
|
110 |
#' If `NULL` then get the code for all data names |
|
111 |
#' @param deparse optional, (`logical`) whether to return the deparsed form of a call |
|
112 |
#' @return `character` or `list` of calls |
|
113 |
get_code = function(dataname = NULL, deparse = TRUE) { |
|
114 | 273x |
checkmate::assert_character(dataname, min.len = 1, null.ok = TRUE, any.missing = FALSE) |
115 | 273x |
checkmate::assert_flag(deparse) |
116 | 273x |
if (is.null(dataname)) { |
117 | 234x |
private$get_code_all(deparse = deparse) |
118 |
} else { |
|
119 | 39x |
private$get_code_dataname(dataname = dataname, deparse = deparse) |
120 |
} |
|
121 |
}, |
|
122 |
#' @description |
|
123 |
#' Evaluates internal code within given environment |
|
124 |
#' @param envir (`environment`) environment in which code will be evaluated |
|
125 |
#' @return invisibly `NULL` |
|
126 |
eval = function(envir = new.env(parent = parent.env(.GlobalEnv))) { |
|
127 | 88x |
for (x in self$get_code(deparse = FALSE)) { |
128 | 121x |
out <- tryCatch( |
129 | 121x |
base::eval(x, envir = envir), |
130 | 121x |
error = function(e) e |
131 |
) |
|
132 | ||
133 | 121x |
if (inherits(out, "error")) { |
134 | 4x |
error_msg <- sprintf( |
135 | 4x |
"%s\n\nEvaluation of the code failed:\n %s", deparse1(x, collapse = "\n"), conditionMessage(out) |
136 |
) |
|
137 | ||
138 | 4x |
rlang::with_options( |
139 | 4x |
stop(error_msg, call. = FALSE), |
140 | 4x |
warning.length = max(min(8170, nchar(error_msg) + 30), 100) |
141 |
) |
|
142 |
} |
|
143 |
} |
|
144 | 84x |
logger::log_trace("CodeClass$eval successfuly evaluated the code.") |
145 | 84x |
return(invisible(NULL)) |
146 |
} |
|
147 |
), |
|
148 |
private = list( |
|
149 |
## __Private Fields ==== |
|
150 |
.code = list(), |
|
151 |
deps = list(), |
|
152 |
## __Private Methods ==== |
|
153 |
set_code_single = function(code, |
|
154 |
dataname = attr(code, "dataname"), |
|
155 |
deps = attr(code, "deps"), |
|
156 |
id = attr(code, "id")) { |
|
157 | ! |
if (is.null(dataname)) dataname <- character(0) |
158 | 418x |
if (is.null(deps)) deps <- character(0) |
159 | 822x |
if (is.null(id)) id <- digest::digest(c(private$.code, code)) |
160 |
# Line shouldn't be added when it contains the same code and the same `dataname` |
|
161 |
# as a line already present in an object of `CodeClass` |
|
162 |
if ( |
|
163 | 3604x |
!id %in% unlist(lapply(private$.code, "attr", "id")) || |
164 | 3604x |
all( |
165 | 3604x |
vapply(dataname, FUN.VALUE = logical(1), FUN = function(x) { |
166 | 206x |
!x %in% unlist(lapply(private$.code, "attr", "dataname")) |
167 |
}) |
|
168 |
) |
|
169 |
) { |
|
170 | 3399x |
attr(code, "dataname") <- dataname |
171 | 3399x |
attr(code, "deps") <- deps |
172 | 3399x |
attr(code, "id") <- id |
173 | ||
174 | 3399x |
private$.code <- base::append(private$.code, list(code)) |
175 |
} |
|
176 | 3604x |
return(invisible(NULL)) |
177 |
}, |
|
178 |
get_code_all = function(deparse) { |
|
179 | 234x |
private$get_code_idx(idx = seq_along(private$.code), deparse = deparse) |
180 |
}, |
|
181 |
get_code_dataname = function(dataname, deparse) { |
|
182 |
# the lines of code we need for the dataname |
|
183 | 39x |
res <- integer(0) |
184 |
# the set of datanames we want code for code for initially just dataname |
|
185 | 39x |
datanames <- dataname |
186 | ||
187 |
# loop backwards along code |
|
188 | 39x |
for (idx in rev(seq_along(private$.code))) { |
189 | 170x |
code_entry <- private$.code[[idx]] |
190 | ||
191 |
# line of code is one we want if it is not empty and |
|
192 |
# has any dataname attribute in the vector datanames or dataname starts with * or is global code and |
|
193 |
# already have some lines of code selected |
|
194 |
if ( |
|
195 |
( |
|
196 | 170x |
any(datanames %in% attr(code_entry, "dataname")) || |
197 | 170x |
any(grepl("^[*]", attr(code_entry, "dataname"))) || |
198 | 170x |
(length(res) > 0 && length(attr(code_entry, "dataname")) == 0) |
199 |
) && |
|
200 | 170x |
length(code_entry) > 0 |
201 |
) { |
|
202 |
# append to index of code we want |
|
203 | 92x |
res <- c(idx, res) |
204 | ||
205 |
# and update datasets we want for preceding code with additional datanames and deps |
|
206 | 92x |
datanames <- unique(c(datanames, attr(code_entry, "dataname"), attr(code_entry, "deps"))) |
207 |
} |
|
208 |
} |
|
209 | 39x |
private$get_code_idx(idx = res, deparse = deparse) |
210 |
}, |
|
211 |
get_code_idx = function(idx, deparse) { |
|
212 | 273x |
if (isFALSE(deparse)) { |
213 | 107x |
return(Filter( |
214 | 107x |
Negate(is.null), |
215 | 107x |
unname(unlist(lapply( |
216 | 107x |
private$.code[idx], |
217 | 107x |
function(x) sapply(x, function(i) text_to_call(i), simplify = FALSE) |
218 |
))) |
|
219 |
)) |
|
220 |
} else { |
|
221 | 166x |
return(paste0(unlist(private$.code[idx]), collapse = "\n")) |
222 |
} |
|
223 |
} |
|
224 |
), |
|
225 | ||
226 |
## __Active Fields ==== |
|
227 |
active = list( |
|
228 |
#' @field code (`list`) Derive the code of the dataset. |
|
229 |
code = function() { |
|
230 | 4541x |
private$.code |
231 |
} |
|
232 |
) |
|
233 |
) |
|
234 | ||
235 | ||
236 |
## Functions ==== |
|
237 | ||
238 |
# Convert named list to `CodeClass` utilizing both `TealDatasetConnector` and `TealDataset` |
|
239 |
list_to_code_class <- function(x) { |
|
240 | 1112x |
checkmate::assert_list(x, min.len = 0, names = "unique") |
241 | ||
242 | 1112x |
res <- CodeClass$new() |
243 | ||
244 | 1112x |
if (length(x) > 0) { |
245 | 163x |
for (var_idx in seq_along(x)) { |
246 | 179x |
var_value <- x[[var_idx]] |
247 | 179x |
var_name <- names(x)[[var_idx]] |
248 | 179x |
if (inherits(var_value, "TealDatasetConnector") || inherits(var_value, "TealDataset")) { |
249 | 172x |
res$append(var_value$get_code_class()) |
250 | 172x |
if (var_name != var_value$get_dataname()) { |
251 | 136x |
res$set_code( |
252 | 136x |
deparse1(call("<-", as.name(var_name), as.name(var_value$get_dataname())), collapse = "\n"), |
253 | 136x |
dataname = var_value$get_dataname() |
254 |
) |
|
255 |
} |
|
256 |
} else { |
|
257 | 7x |
var_code <- deparse1(call("<-", as.name(var_name), var_value), collapse = "\n") |
258 | 7x |
res$set_code(var_code, var_name) |
259 |
} |
|
260 |
} |
|
261 |
} |
|
262 | 1112x |
return(res) |
263 |
} |
|
264 | ||
265 |
#' Create call from string |
|
266 |
#' |
|
267 |
#' @param x (`character`) string containing the code. |
|
268 |
#' |
|
269 |
#' @return (`call`) object. |
|
270 |
#' @keywords internal |
|
271 |
text_to_call <- function(x) { |
|
272 | 169x |
parsed <- parse(text = x, keep.source = FALSE) |
273 | 169x |
if (length(parsed) == 0) { |
274 | 4x |
return(NULL) |
275 |
} else { |
|
276 | 165x |
return(as.list(as.call(parsed))[[1]]) |
277 |
} |
|
278 |
} |
|
279 | ||
280 |
#' Format a vector of code into a string |
|
281 |
#' |
|
282 |
#' @param code_vector (`character`) vector containing lines of |
|
283 |
#' code to format into a string. |
|
284 |
#' |
|
285 |
#' @return (`character`) string containing the formatted code. |
|
286 |
#' @keywords internal |
|
287 |
pretty_code_string <- function(code_vector) { |
|
288 |
# in order to remove bad formatting: text -> code -> text |
|
289 | 812x |
unlist(lapply( |
290 | 812x |
code_vector, |
291 | 812x |
function(code_single) { |
292 | 814x |
if (length(parse(text = code_single, keep.source = FALSE)) == 0) { |
293 |
# if string code cannot be passed into expression (e.g. code comment) then pass on the string |
|
294 | 11x |
code_single |
295 |
} else { |
|
296 | 803x |
vapply( |
297 | 803x |
as.list(as.call(parse(text = code_single, keep.source = FALSE))), |
298 | 803x |
deparse1, |
299 | 803x |
character(1), |
300 | 803x |
collapse = "\n" |
301 |
) |
|
302 |
} |
|
303 |
} |
|
304 |
)) |
|
305 |
} |
1 |
## CallablePythonCode ==== |
|
2 |
#' |
|
3 |
#' @title A `CallablePythonCode` class of objects |
|
4 |
#' @keywords internal |
|
5 |
#' |
|
6 |
CallablePythonCode <- R6::R6Class( # nolint |
|
7 | ||
8 |
## __Public Methods ==== |
|
9 |
classname = "CallablePythonCode", |
|
10 |
inherit = CallableFunction, |
|
11 |
public = list( |
|
12 |
#' @description |
|
13 |
#' Create a new `CallablePythonCode` object |
|
14 |
#' |
|
15 |
#' @param fun (`function`)\cr |
|
16 |
#' function to be evaluated in class. Function should be named |
|
17 |
#' @param env (\code{environment})\cr |
|
18 |
#' environment where the result of python code evaluation are stored |
|
19 |
#' @return new `CallablePythonCode` object |
|
20 |
initialize = function(fun, env = new.env(parent = parent.env(globalenv()))) { |
|
21 | ! |
if (!requireNamespace("reticulate", quietly = TRUE)) { |
22 | ! |
stop("Cannot load package 'reticulate' - please install the package.", call. = FALSE) |
23 |
} |
|
24 | ! |
if (utils::packageVersion("reticulate") < 1.22) { |
25 | ! |
stop("Please upgrade package 'reticulate', teal.data requires version >= 1.22") |
26 |
} |
|
27 | ||
28 | ! |
super$initialize(fun = fun, env = env) |
29 | ! |
logger::log_trace("CallablePythonCode initialized.") |
30 | ! |
return(invisible(self)) |
31 |
}, |
|
32 |
#' @description |
|
33 |
#' For scripts and code that contain multiple objects, save the name |
|
34 |
#' of the object that corresponds to the final dataset of interest. |
|
35 |
#' This is required for running python scripts with `reticulate`. |
|
36 |
#' |
|
37 |
#' @param x (`character`) the name of the object produced by the code |
|
38 |
#' or script. |
|
39 |
#' |
|
40 |
#' @return (`self`) invisibly for chaining. |
|
41 |
set_object = function(x) { |
|
42 | ! |
private$object <- x |
43 | ! |
private$refresh() |
44 | ! |
logger::log_trace("CallablePythonCode$set_object object set.") |
45 | ! |
return(invisible(self)) |
46 |
}, |
|
47 |
#' @description |
|
48 |
#' Execute `Callable` python code. |
|
49 |
#' |
|
50 |
#' @param args (`NULL` or named `list`)\cr |
|
51 |
#' supplied for callable functions only, these are dynamic arguments passed to |
|
52 |
#' `reticulate::py_run_string` or `reticulate::py_run_file`. Dynamic arguments |
|
53 |
#' are executed in this call and are not saved which means that `self$get_call()` |
|
54 |
#' won't include them later. |
|
55 |
#' @param try (`logical` value)\cr |
|
56 |
#' whether perform function evaluation inside `try` clause |
|
57 |
#' |
|
58 |
#' @return nothing or output from function depending on `return` |
|
59 |
#' argument. If `run` fails it will return object of class `simple-error` error |
|
60 |
#' when `try = TRUE` or will stop if `try = FALSE`. |
|
61 |
run = function(args = NULL, try = FALSE) { |
|
62 | ! |
rlang::with_options( |
63 | ! |
res <- super$run(args = args, try = try), |
64 | ! |
reticulate.engine.environment = private$env |
65 |
) |
|
66 | ! |
if (is.null(res)) { |
67 | ! |
stop("The specified python object returned NULL or does not exist in the python code") |
68 |
} |
|
69 | ! |
res |
70 |
} |
|
71 |
), |
|
72 | ||
73 |
## __Private Fields ==== |
|
74 |
private = list( |
|
75 |
object = NULL, |
|
76 | ||
77 |
## __Private Methods ==== |
|
78 |
# @description |
|
79 |
# Refresh call with function name and saved arguments |
|
80 |
# |
|
81 |
# @return nothing |
|
82 |
refresh = function() { |
|
83 |
# replaced str2lang found at: |
|
84 |
# https://rlang.r-lib.org/reference/call2.html |
|
85 | ! |
private$call <- as.call( |
86 | ! |
c(rlang::parse_expr(private$fun_name), private$args) |
87 |
) |
|
88 | ||
89 | ! |
private$call <- rlang::parse_expr( |
90 | ! |
sprintf("%s[[%s]]", deparse1(private$call, collapse = "\n"), deparse1(private$object, collapse = "\n")) |
91 |
) |
|
92 |
} |
|
93 |
) |
|
94 |
) |
|
95 |
## PythonCodeClass ==== |
|
96 |
#' |
|
97 |
#' @title A `CallablePythonCode` class of objects |
|
98 |
#' @description `r lifecycle::badge("experimental")` |
|
99 |
#' |
|
100 |
PythonCodeClass <- R6::R6Class( # nolint |
|
101 |
classname = "PythonCodeClass", |
|
102 |
inherit = CodeClass, |
|
103 | ||
104 |
## __Public Methods ==== |
|
105 |
public = list( |
|
106 |
#' @description |
|
107 |
#' Evaluates internal code within environment |
|
108 |
#' |
|
109 |
#' @param vars (named `list`) additional pre-requisite vars to execute code |
|
110 |
#' @param dataname (`character`) name of the data frame object to be returned |
|
111 |
#' @param envir (`environment`) environment in which code will be evaluated |
|
112 |
#' |
|
113 |
#' @return `data.frame` containing the mutated dataset |
|
114 |
eval = function(vars = list(), dataname = NULL, envir = new.env(parent = parent.env(.GlobalEnv))) { |
|
115 | ! |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
116 | ! |
execution_environment <- envir |
117 | ||
118 | ! |
for (vars_idx in seq_along(vars)) { |
119 | ! |
var_name <- names(vars)[[vars_idx]] |
120 | ! |
var_value <- vars[[vars_idx]] |
121 | ! |
if (inherits(var_value, "TealDatasetConnector") || inherits(var_value, "TealDataset")) { |
122 | ! |
var_value <- get_raw_data(var_value) |
123 |
} |
|
124 | ! |
assign(envir = execution_environment, x = var_name, value = var_value) |
125 |
} |
|
126 | ||
127 |
# execute |
|
128 | ! |
rlang::with_options( |
129 | ! |
super$eval(envir = execution_environment), |
130 | ! |
reticulate.engine.environment = execution_environment |
131 |
) |
|
132 | ||
133 |
# return early if only executing and not grabbing the dataset |
|
134 | ! |
if (is.null(dataname)) { |
135 | ! |
return(as.environment(as.list(execution_environment))) |
136 |
} |
|
137 | ||
138 | ! |
if (!is.data.frame(execution_environment[[dataname]])) { |
139 | ! |
out_msg <- sprintf( |
140 | ! |
"\n%s\n\n - Code from %s needs to return a data.frame assigned to an object of dataset name.", |
141 | ! |
self$get_code(), |
142 | ! |
self$get_dataname() |
143 |
) |
|
144 | ||
145 | ! |
rlang::with_options( |
146 | ! |
.expr = stop(out_msg, call. = FALSE), |
147 | ! |
warning.length = max(min(8170, nchar(out_msg) + 30), 100) |
148 |
) |
|
149 |
} |
|
150 | ||
151 | ! |
new_set <- execution_environment[[dataname]] |
152 | ! |
logger::log_trace("PythonCodeClass$eval successfuly evaluated the code.") |
153 | ||
154 | ! |
return(new_set) |
155 |
} |
|
156 |
) |
|
157 |
) |
|
158 | ||
159 |
#' Python Code |
|
160 |
#' |
|
161 |
#' `r lifecycle::badge("experimental")` |
|
162 |
#' Create a python code object directly from python code or a |
|
163 |
#' script containing python code. |
|
164 |
#' |
|
165 |
#' @details |
|
166 |
#' Used to mutate dataset connector objects with python code. See |
|
167 |
#' [`mutate_dataset`] or [`mutate_data`] for details. |
|
168 |
#' |
|
169 |
#' @param code (`character`)\cr |
|
170 |
#' Code to mutate the dataset. Must contain the `dataset$dataname`. |
|
171 |
#' @param script (`character`)\cr |
|
172 |
#' file that contains python Code that can be read using `reticulate::py_run_script`. |
|
173 |
#' |
|
174 |
#' @return (`PythonCodeClass`) object containing python code |
|
175 |
#' @export |
|
176 |
#' |
|
177 |
#' @examples |
|
178 |
#' \dontrun{ |
|
179 |
#' library(reticulate) |
|
180 |
#' library(magrittr) |
|
181 |
#' |
|
182 |
#' # mutate dataset object |
|
183 |
#' |
|
184 |
#' random_data_connector <- function(dataname) { |
|
185 |
#' fun_dataset_connector( |
|
186 |
#' dataname = dataname, |
|
187 |
#' fun = teal.data::example_cdisc_data, |
|
188 |
#' fun_args = list(dataname = dataname), |
|
189 |
#' ) |
|
190 |
#' } |
|
191 |
#' x <- random_data_connector(dataname = "ADSL") |
|
192 |
#' |
|
193 |
#' x %>% mutate_dataset(python_code("import pandas as pd |
|
194 |
#' r.ADSL = pd.DataFrame({'x': [1]})")) |
|
195 |
#' |
|
196 |
#' x$get_code() |
|
197 |
#' x$pull() |
|
198 |
#' x$get_raw_data() |
|
199 |
#' |
|
200 |
#' # mutate data object |
|
201 |
#' |
|
202 |
#' y <- 8 |
|
203 |
#' tc <- cdisc_data( |
|
204 |
#' random_data_connector(dataname = "ADSL"), |
|
205 |
#' random_data_connector(dataname = "ADLB") |
|
206 |
#' ) |
|
207 |
#' |
|
208 |
#' tc %>% mutate_data(python_code("import pandas as pd |
|
209 |
#' r.ADSL = pd.DataFrame({'STUDYID': [r.y], 'USUBJID': [r.y]})"), vars = list(y = y)) |
|
210 |
#' |
|
211 |
#' |
|
212 |
#' load_datasets(tc) # submit all |
|
213 |
#' ds <- tc$get_dataset("ADSL") |
|
214 |
#' ds$get_raw_data() |
|
215 |
#' } |
|
216 |
python_code <- function(code = character(0), script = character(0)) { |
|
217 | ! |
if (!xor(missing(code), missing(script))) stop("Exactly one of 'code' and 'script' is required") |
218 | ||
219 | ! |
if (length(script) > 0) { |
220 | ! |
code <- deparse(call("py_run_file", script)) |
221 |
} else { |
|
222 | ! |
code <- deparse(call("py_run_string", code)) |
223 |
} |
|
224 | ! |
py <- PythonCodeClass$new() |
225 | ! |
py$set_code(code) |
226 | ||
227 | ! |
return(py) |
228 |
} |
1 |
# TealDataConnector ------ |
|
2 |
#' |
|
3 |
#' |
|
4 |
#' @title Manage multiple and `TealDatasetConnector` of the same type. |
|
5 |
#' |
|
6 |
#' @description `r lifecycle::badge("stable")` |
|
7 |
#' Class manages `TealDatasetConnector` to specify additional dynamic arguments and to |
|
8 |
#' open/close connection. |
|
9 |
#' |
|
10 |
#' @param connection (`TealDataConnection`)\cr |
|
11 |
#' connection to data source |
|
12 |
#' @param connectors (`list` of `TealDatasetConnector` elements)\cr |
|
13 |
#' list with dataset connectors |
|
14 |
#' |
|
15 |
#' @examples |
|
16 |
#' library(magrittr) |
|
17 |
#' |
|
18 |
#' random_data_connector <- function(dataname) { |
|
19 |
#' fun_dataset_connector( |
|
20 |
#' dataname = dataname, |
|
21 |
#' fun = teal.data::example_cdisc_data, |
|
22 |
#' fun_args = list(dataname = dataname), |
|
23 |
#' ) |
|
24 |
#' } |
|
25 |
#' |
|
26 |
#' open_fun <- callable_function(library) |
|
27 |
#' open_fun$set_args(list(package = "teal.data")) |
|
28 |
#' |
|
29 |
#' con <- data_connection(open_fun = open_fun) |
|
30 |
#' con$set_open_server( |
|
31 |
#' function(id, connection) { |
|
32 |
#' moduleServer( |
|
33 |
#' id = id, |
|
34 |
#' module = function(input, output, session) { |
|
35 |
#' connection$open(try = TRUE) |
|
36 |
#' return(invisible(connection)) |
|
37 |
#' } |
|
38 |
#' ) |
|
39 |
#' } |
|
40 |
#' ) |
|
41 |
#' |
|
42 |
#' x <- teal.data:::TealDataConnector$new( |
|
43 |
#' connection = con, |
|
44 |
#' connectors = list( |
|
45 |
#' random_data_connector(dataname = "ADSL"), |
|
46 |
#' random_data_connector(dataname = "ADLB") |
|
47 |
#' ) |
|
48 |
#' ) |
|
49 |
#' |
|
50 |
#' x$set_ui( |
|
51 |
#' function(id, connection, connectors) { |
|
52 |
#' ns <- NS(id) |
|
53 |
#' tagList( |
|
54 |
#' connection$get_open_ui(ns("open_connection")), |
|
55 |
#' numericInput(inputId = ns("n"), label = "Choose number of records", min = 0, value = 1), |
|
56 |
#' do.call( |
|
57 |
#' what = "tagList", |
|
58 |
#' args = lapply( |
|
59 |
#' connectors, |
|
60 |
#' function(connector) { |
|
61 |
#' div( |
|
62 |
#' connector$get_ui( |
|
63 |
#' id = ns(connector$get_dataname()) |
|
64 |
#' ), |
|
65 |
#' br() |
|
66 |
#' ) |
|
67 |
#' } |
|
68 |
#' ) |
|
69 |
#' ) |
|
70 |
#' ) |
|
71 |
#' } |
|
72 |
#' ) |
|
73 |
#' |
|
74 |
#' x$set_server( |
|
75 |
#' function(id, connection, connectors) { |
|
76 |
#' moduleServer( |
|
77 |
#' id = id, |
|
78 |
#' module = function(input, output, session) { |
|
79 |
#' # opens connection |
|
80 |
#' connection$get_open_server()(id = "open_connection", connection = connection) |
|
81 |
#' if (connection$is_opened()) { |
|
82 |
#' for (connector in connectors) { |
|
83 |
#' set_args(connector, args = list(n = input$n)) |
|
84 |
#' # pull each dataset |
|
85 |
#' connector$get_server()(id = connector$get_dataname()) |
|
86 |
#' if (connector$is_failed()) { |
|
87 |
#' break |
|
88 |
#' } |
|
89 |
#' } |
|
90 |
#' } |
|
91 |
#' } |
|
92 |
#' ) |
|
93 |
#' } |
|
94 |
#' ) |
|
95 |
#' \dontrun{ |
|
96 |
#' x$launch() |
|
97 |
#' x$get_datasets() |
|
98 |
#' } |
|
99 |
TealDataConnector <- R6::R6Class( # nolint |
|
100 |
classname = "TealDataConnector", |
|
101 |
inherit = TealDataAbstract, |
|
102 | ||
103 |
## __Public Methods ==== |
|
104 |
public = list( |
|
105 |
#' @description |
|
106 |
#' Create a new `TealDataConnector` object |
|
107 |
initialize = function(connection, connectors) { |
|
108 | 18x |
checkmate::assert_list(connectors, types = "TealDatasetConnector", min.len = 1) |
109 | ||
110 | 18x |
connectors_names <- vapply(connectors, get_dataname, character(1)) |
111 | 18x |
connectors <- setNames(connectors, connectors_names) |
112 | ||
113 | 18x |
private$check_names(connectors_names) |
114 | ||
115 | 18x |
if (!missing(connection)) { |
116 | 18x |
stopifnot(inherits(connection, "TealDataConnection")) |
117 | 18x |
private$connection <- connection |
118 |
} |
|
119 | ||
120 | 18x |
private$datasets <- connectors |
121 | ||
122 | 18x |
private$pull_code <- CodeClass$new() |
123 | 18x |
private$mutate_code <- CodeClass$new() |
124 | ||
125 | 18x |
self$id <- sample.int(1e11, 1, useHash = TRUE) |
126 | ||
127 | ||
128 | 18x |
logger::log_trace( |
129 | 18x |
"TealDataConnector initialized with data: { paste(self$get_datanames(), collapse = ' ') }." |
130 |
) |
|
131 | 18x |
return(invisible(self)) |
132 |
}, |
|
133 |
#' @description |
|
134 |
#' Prints this `TealDataConnector`. |
|
135 |
#' |
|
136 |
#' @param ... additional arguments to the printing method |
|
137 |
#' @return invisibly self |
|
138 |
print = function(...) { |
|
139 | 1x |
check_ellipsis(...) |
140 | ||
141 | 1x |
cat(sprintf( |
142 | 1x |
"A currently %s %s object containing %d TealDataset/TealDatasetConnector object(s) as element(s).\n", |
143 | 1x |
ifelse(self$get_connection()$is_opened(), "opened", "not yet opened"), |
144 | 1x |
class(self)[1], |
145 | 1x |
length(private$datasets) |
146 |
)) |
|
147 | 1x |
cat(sprintf( |
148 | 1x |
"%d of which is/are loaded/pulled:\n", |
149 | 1x |
sum(vapply(private$datasets, function(x) x$is_pulled(), FUN.VALUE = logical(1))) |
150 |
)) |
|
151 | ||
152 | 1x |
for (i in seq_along(private$datasets)) { |
153 | 2x |
cat(sprintf("--> Element %d:\n", i)) |
154 | 2x |
print(private$datasets[[i]]) |
155 |
} |
|
156 | ||
157 | 1x |
invisible(self) |
158 |
}, |
|
159 | ||
160 |
# ___ getters ==== |
|
161 |
#' @description |
|
162 |
#' Get connection to data source |
|
163 |
#' |
|
164 |
#' @return connector's connection |
|
165 |
get_connection = function() { |
|
166 | 1x |
return(private$connection) |
167 |
}, |
|
168 |
#' @description |
|
169 |
#' Get internal `CodeClass` object |
|
170 |
#' |
|
171 |
#' @return `CodeClass` |
|
172 |
get_code_class = function() { |
|
173 | 30x |
all_code <- CodeClass$new() |
174 | ||
175 | 30x |
open_connection_code <- if (!is.null(private$connection)) { |
176 | 30x |
private$connection$get_open_call(deparse = TRUE) |
177 |
} else { |
|
178 | ! |
NULL |
179 |
} |
|
180 | ||
181 | 30x |
if (!is.null(open_connection_code)) { |
182 | 30x |
all_code$set_code(open_connection_code, dataname = "*open") |
183 |
} |
|
184 | 30x |
datasets_code_class <- private$get_datasets_code_class() |
185 | 30x |
all_code$append(datasets_code_class) |
186 | ||
187 | 30x |
close_connection_code <- if (!is.null(private$connection)) { |
188 | 30x |
private$connection$get_close_call(deparse = TRUE, silent = TRUE) |
189 |
} else { |
|
190 | ! |
NULL |
191 |
} |
|
192 | ||
193 | 30x |
if (!is.null(close_connection_code)) { |
194 | ! |
all_code$set_code(close_connection_code, dataname = "*close") |
195 |
} |
|
196 | ||
197 | 30x |
mutate_code_class <- private$get_mutate_code_class() |
198 | 30x |
all_code$append(mutate_code_class) |
199 | ||
200 | 30x |
return(all_code) |
201 |
}, |
|
202 |
#' @description get the server function |
|
203 |
#' |
|
204 |
#' @return the `server` function |
|
205 |
get_server = function() { |
|
206 | 2x |
if (is.null(private$server)) { |
207 | ! |
stop("No server function set yet. Please use set_server method first.") |
208 |
} |
|
209 | 2x |
function(id, connection = private$connection, connectors = private$datasets) { |
210 | ! |
rv <- reactiveVal(NULL) |
211 | ! |
moduleServer( |
212 | ! |
id = id, |
213 | ! |
module = function(input, output, session) { |
214 | ! |
private$server(id = "data_input", connection = connection, connectors = connectors) |
215 |
} |
|
216 |
) |
|
217 | ||
218 | ! |
if (self$is_pulled()) { |
219 | ! |
return(rv(TRUE)) |
220 |
} else { |
|
221 | ! |
return(rv(FALSE)) |
222 |
} |
|
223 |
} |
|
224 |
}, |
|
225 |
#' @description get the `preopen` server function |
|
226 |
#' |
|
227 |
#' @return the `server` function |
|
228 |
get_preopen_server = function() { |
|
229 | ! |
function(id, connection = private$connection) { |
230 | ! |
if (!is.null(private$preopen_server)) { |
231 | ! |
moduleServer( |
232 | ! |
id = id, |
233 | ! |
module = function(input, output, session) { |
234 | ! |
private$preopen_server(id = "data_input", connection = connection) |
235 |
} |
|
236 |
) |
|
237 |
} |
|
238 |
} |
|
239 |
}, |
|
240 |
#' @description |
|
241 |
#' Get Shiny module with inputs for all `TealDatasetConnector` objects |
|
242 |
#' |
|
243 |
#' @param id `character` shiny element id |
|
244 |
#' |
|
245 |
#' @return the `ui` function |
|
246 |
get_ui = function(id) { |
|
247 | 3x |
if (is.null(private$ui)) { |
248 | 1x |
stop("No UI set yet. Please use set_ui method first.") |
249 |
} |
|
250 | 2x |
x <- function(id, connection = private$connection, connectors = private$datasets) { |
251 | 2x |
ns <- NS(id) |
252 | 2x |
tags$div( |
253 | 2x |
h3("Data Connector for:", lapply(self$get_datanames(), code)), |
254 | 2x |
tags$div( |
255 | 2x |
id = ns("data_input"), |
256 | 2x |
private$ui(id = ns("data_input"), connection = connection, connectors = connectors) |
257 |
) |
|
258 |
) |
|
259 |
} |
|
260 | 2x |
x(id) |
261 |
}, |
|
262 | ||
263 |
# ___ setters ==== |
|
264 |
#' @description |
|
265 |
#' Set argument to the `pull_fun` |
|
266 |
#' |
|
267 |
#' @param args (named `list`)\cr |
|
268 |
#' arguments values as separate list elements named by argument name. These arguments |
|
269 |
#' are passed to each dataset. |
|
270 |
#' |
|
271 |
#' @return nothing |
|
272 |
set_pull_args = function(args) { |
|
273 | 1x |
lapply(private$datasets, function(x) set_args(x, args)) |
274 | 1x |
logger::log_trace("TealDataConnector$set_pull_args pull args set.") |
275 | 1x |
return(invisible(NULL)) |
276 |
}, |
|
277 |
#' @description |
|
278 |
#' Set connector UI function |
|
279 |
#' |
|
280 |
#' @param f (`function`)\cr |
|
281 |
#' shiny module as function. Inputs specified in this `ui` are passed to server module |
|
282 |
#' defined by `set_server` method. |
|
283 |
#' |
|
284 |
#' @return nothing |
|
285 |
set_ui = function(f) { |
|
286 | 4x |
stopifnot(inherits(f, "function")) |
287 | 4x |
stopifnot("id" %in% names(formals(f))) |
288 | 4x |
stopifnot(all(c("connection", "connectors") %in% names(formals(f))) || "..." %in% names(formals(f))) |
289 | 4x |
private$ui <- f |
290 | 4x |
logger::log_trace("TealDataConnector$set_ui ui set.") |
291 | 4x |
return(invisible(NULL)) |
292 |
}, |
|
293 |
#' @description |
|
294 |
#' Set connector server function |
|
295 |
#' |
|
296 |
#' This function will be called after submit button will be hit. There is no possibility to |
|
297 |
#' specify some dynamic `ui` as `server` function is executed after hitting submit |
|
298 |
#' button. |
|
299 |
#' |
|
300 |
#' @param f (`function`)\cr |
|
301 |
#' A shiny module server function that should load data from all connectors |
|
302 |
#' |
|
303 |
#' @return nothing |
|
304 |
set_server = function(f) { |
|
305 | 2x |
stopifnot(inherits(f, "function")) |
306 | 2x |
stopifnot(all(c("id", "connection", "connectors") %in% names(formals(f)))) |
307 | 2x |
private$server <- f |
308 | 2x |
logger::log_trace("TealDataConnector$set_server server set.") |
309 | 2x |
return(invisible(NULL)) |
310 |
}, |
|
311 |
#' @description |
|
312 |
#' Set connector pre-open server function |
|
313 |
#' |
|
314 |
#' This function will be called before submit button will be hit. |
|
315 |
#' |
|
316 |
#' @param f (`function`)\cr |
|
317 |
#' A shiny module server function |
|
318 |
#' |
|
319 |
#' @return nothing |
|
320 |
set_preopen_server = function(f) { |
|
321 | ! |
stopifnot(inherits(f, "function")) |
322 | ! |
stopifnot(all(c("id", "connection") %in% names(formals(f)))) |
323 | ! |
private$preopen_server <- f |
324 | ! |
logger::log_trace("TealDataConnector$set_preopen_server preopen_server set.") |
325 | ! |
return(invisible(NULL)) |
326 |
}, |
|
327 | ||
328 |
# ___ pull ==== |
|
329 |
#' @description |
|
330 |
#' Load data from each `TealDatasetConnector` |
|
331 |
#' |
|
332 |
#' @param con_args (`NULL` or named `list`)\cr |
|
333 |
#' additional dynamic arguments for connection function. `args` will be passed to each |
|
334 |
#' `TealDatasetConnector` object to evaluate `CallableFunction` assigned to |
|
335 |
#' this dataset. If `args` is null than default set of arguments will be used, otherwise |
|
336 |
#' call will be executed on these arguments only (arguments set before will be ignored). |
|
337 |
#' `pull` function doesn't update reproducible call, it's just evaluate function. |
|
338 |
#' |
|
339 |
#' @param args (`NULL` or named `list`)\cr |
|
340 |
#' additional dynamic arguments to pull dataset. `args` will be passed to each |
|
341 |
#' `TealDatasetConnector` object to evaluate `CallableFunction` assigned to |
|
342 |
#' this dataset. If `args` is null than default set of arguments will be used, otherwise |
|
343 |
#' call will be executed on these arguments only (arguments set before will be ignored). |
|
344 |
#' `pull` function doesn't update reproducible call, it's just evaluate function. |
|
345 |
#' |
|
346 |
#' @param try (`logical` value)\cr |
|
347 |
#' whether perform function evaluation inside `try` clause |
|
348 |
#' |
|
349 |
#' @return (`self`) invisibly for chaining. In order to get the data please use `get_datasets` method. |
|
350 |
pull = function(con_args = NULL, args = NULL, try = TRUE) { |
|
351 | 3x |
logger::log_trace("TealDataConnector$pull pulling data...") |
352 |
# open connection |
|
353 | 3x |
if (!is.null(private$connection)) { |
354 | 3x |
private$connection$open(args = con_args, try = try) |
355 | ||
356 | 3x |
conn <- private$connection$get_conn() |
357 | 3x |
for (connector in private$datasets) { |
358 | 4x |
connector$get_pull_callable()$assign_to_env("conn", conn) |
359 |
} |
|
360 |
} |
|
361 | ||
362 |
# load datasets |
|
363 | 3x |
for (dataset in private$datasets) { |
364 | 4x |
load_dataset(dataset, args = args) |
365 |
} |
|
366 | ||
367 |
# close connection |
|
368 | 3x |
if (!is.null(private$connection)) private$connection$close(silent = TRUE) |
369 | ||
370 | 3x |
logger::log_trace("TealDataConnector$pull data pulled.") |
371 | ||
372 | 3x |
return(invisible(self)) |
373 |
}, |
|
374 |
#' @description |
|
375 |
#' Run simple application that uses its `ui` and `server` fields to pull data from |
|
376 |
#' connection. |
|
377 |
#' |
|
378 |
#' Useful for debugging |
|
379 |
#' |
|
380 |
#' @return An object that represents the app |
|
381 |
launch = function() { |
|
382 |
# load TealDatasetConnector objects |
|
383 | ! |
if (self$is_pulled()) { |
384 | ! |
stop("All the datasets have already been pulled.") |
385 |
} |
|
386 | ||
387 | ! |
shinyApp( |
388 | ! |
ui = fluidPage( |
389 | ! |
include_js_files(), |
390 | ! |
theme = get_teal_bs_theme(), |
391 | ! |
fluidRow( |
392 | ! |
column( |
393 | ! |
width = 8, |
394 | ! |
offset = 2, |
395 | ! |
tags$div( |
396 | ! |
id = "data_inputs", |
397 | ! |
self$get_ui(id = "data_connector"), |
398 | ! |
actionButton("submit", "Submit"), |
399 | ! |
`data-proxy-click` = "submit" # handled by jscode in custom.js - hit enter to submit |
400 |
), |
|
401 | ! |
shinyjs::hidden( |
402 | ! |
tags$div( |
403 | ! |
id = "data_loaded", |
404 | ! |
div( |
405 | ! |
h3("Data successfully loaded."), |
406 | ! |
p("You can close this window and get back to R console.") |
407 |
) |
|
408 |
) |
|
409 |
) |
|
410 |
) |
|
411 |
) |
|
412 |
), |
|
413 | ! |
server = function(input, output, session) { |
414 | ! |
session$onSessionEnded(stopApp) |
415 | ! |
self$get_preopen_server()( |
416 | ! |
id = "data_connector", |
417 | ! |
connection = private$connection |
418 |
) |
|
419 | ! |
observeEvent(input$submit, { |
420 | ! |
rv <- reactiveVal(NULL) |
421 | ! |
rv( |
422 | ! |
self$get_server()( |
423 | ! |
id = "data_connector", |
424 | ! |
connection = private$connection, |
425 | ! |
connectors = private$datasets |
426 |
) |
|
427 |
) |
|
428 | ||
429 | ! |
observeEvent(rv(), { |
430 | ! |
if (self$is_pulled()) { |
431 | ! |
removeUI(sprintf("#%s", session$ns("data_inputs"))) |
432 | ! |
shinyjs::show("data_loaded") |
433 | ! |
stopApp() |
434 |
} |
|
435 |
}) |
|
436 |
}) |
|
437 |
} |
|
438 |
) |
|
439 |
}, |
|
440 | ||
441 |
# ___ mutate ==== |
|
442 |
#' @description |
|
443 |
#' Mutate data by code. |
|
444 |
#' |
|
445 |
#' @param ... parameters inherited from `TealDataAbstract`. |
|
446 |
#' |
|
447 |
#' @return Informational message to not use mutate_data() with `TealDataConnectors`. |
|
448 |
mutate = function(...) { |
|
449 | ! |
stop("TealDataConnectors do not support mutate_data(). |
450 | ! |
Please use mutate_data() with teal_data() or cdisc_data()") |
451 |
}, |
|
452 | ||
453 |
# ___ status ==== |
|
454 |
#' @description |
|
455 |
#' Check if pull or connection has not failed. |
|
456 |
#' |
|
457 |
#' @return `TRUE` if pull or connection failed, else `FALSE` |
|
458 |
is_failed = function() { |
|
459 | ! |
private$connection$is_failed() || |
460 | ! |
any(vapply(private$datasets, function(x) x$is_failed(), logical(1))) |
461 |
} |
|
462 |
), |
|
463 |
## __Private Fields ==== |
|
464 |
private = list( |
|
465 |
server = NULL, # shiny server function |
|
466 |
preopen_server = NULL, # shiny server function |
|
467 |
ui = NULL, # shiny ui function |
|
468 |
connection = NULL, # TealDataConnection |
|
469 | ||
470 |
## __Private Methods ==== |
|
471 |
# adds open/close connection code at beginning/end of the dataset code |
|
472 |
append_connection_code = function() { |
|
473 | ! |
lapply( |
474 | ! |
private$datasets, |
475 | ! |
function(connector) { |
476 | ! |
dataset <- get_dataset(connector) |
477 | ! |
try( |
478 | ! |
dataset$set_code(code = paste( |
479 | ! |
c( |
480 | ! |
if (!is.null(private$connection)) private$connection$get_open_call(deparse = TRUE), |
481 | ! |
get_code(dataset, deparse = TRUE, FUN.VALUE = character(1)), |
482 | ! |
if (!is.null(private$connection)) private$connection$get_close_call(deparse = TRUE, silent = TRUE) |
483 |
), |
|
484 | ! |
collapse = "\n" |
485 |
)) |
|
486 |
) |
|
487 |
} |
|
488 |
) |
|
489 |
} |
|
490 |
) |
|
491 |
) |
|
492 | ||
493 |
#' The constructor for `TealDataConnector` class. |
|
494 |
#' |
|
495 |
#' @description `r lifecycle::badge("stable")` |
|
496 |
#' @param connection (`TealDataConnection`)\cr |
|
497 |
#' connection to data source |
|
498 |
#' @param connectors (`list` of `TealDatasetConnector` elements)\cr |
|
499 |
#' list with dataset connectors |
|
500 |
#' |
|
501 |
#' @examples |
|
502 |
#' library(magrittr) |
|
503 |
#' random_data_connector <- function(dataname) { |
|
504 |
#' fun_dataset_connector( |
|
505 |
#' dataname = dataname, |
|
506 |
#' fun = teal.data::example_cdisc_data, |
|
507 |
#' fun_args = list(dataname = dataname), |
|
508 |
#' ) |
|
509 |
#' } |
|
510 |
#' |
|
511 |
#' open_fun <- callable_function(library) |
|
512 |
#' open_fun$set_args(list(package = "teal.data")) |
|
513 |
#' |
|
514 |
#' con <- data_connection(open_fun = open_fun) |
|
515 |
#' con$set_open_server( |
|
516 |
#' function(id, connection) { |
|
517 |
#' moduleServer( |
|
518 |
#' id = id, |
|
519 |
#' module = function(input, output, session) { |
|
520 |
#' connection$open(try = TRUE) |
|
521 |
#' return(invisible(connection)) |
|
522 |
#' } |
|
523 |
#' ) |
|
524 |
#' } |
|
525 |
#' ) |
|
526 |
#' |
|
527 |
#' x <- relational_data_connector( |
|
528 |
#' connection = con, |
|
529 |
#' connectors = list( |
|
530 |
#' random_data_connector(dataname = "ADSL"), |
|
531 |
#' random_data_connector(dataname = "ADLB") |
|
532 |
#' ) |
|
533 |
#' ) |
|
534 |
#' |
|
535 |
#' x$set_ui( |
|
536 |
#' function(id, connection, connectors) { |
|
537 |
#' ns <- NS(id) |
|
538 |
#' tagList( |
|
539 |
#' connection$get_open_ui(ns("open_connection")), |
|
540 |
#' numericInput(inputId = ns("n"), label = "Choose number of records", min = 0, value = 1), |
|
541 |
#' do.call( |
|
542 |
#' what = "tagList", |
|
543 |
#' args = lapply( |
|
544 |
#' connectors, |
|
545 |
#' function(connector) { |
|
546 |
#' div( |
|
547 |
#' connector$get_ui( |
|
548 |
#' id = ns(connector$get_dataname()) |
|
549 |
#' ), |
|
550 |
#' br() |
|
551 |
#' ) |
|
552 |
#' } |
|
553 |
#' ) |
|
554 |
#' ) |
|
555 |
#' ) |
|
556 |
#' } |
|
557 |
#' ) |
|
558 |
#' |
|
559 |
#' x$set_server( |
|
560 |
#' function(id, connection, connectors) { |
|
561 |
#' moduleServer( |
|
562 |
#' id = id, |
|
563 |
#' module = function(input, output, session) { |
|
564 |
#' # opens connection |
|
565 |
#' connection$get_open_server()(id = "open_connection", connection = connection) |
|
566 |
#' if (connection$is_opened()) { |
|
567 |
#' for (connector in connectors) { |
|
568 |
#' set_args(connector, args = list(n = input$n)) |
|
569 |
#' # pull each dataset |
|
570 |
#' connector$get_server()(id = connector$get_dataname()) |
|
571 |
#' if (connector$is_failed()) { |
|
572 |
#' break |
|
573 |
#' } |
|
574 |
#' } |
|
575 |
#' } |
|
576 |
#' } |
|
577 |
#' ) |
|
578 |
#' } |
|
579 |
#' ) |
|
580 |
#' \dontrun{ |
|
581 |
#' x$launch() |
|
582 |
#' x$get_datasets() |
|
583 |
#' } |
|
584 |
#' |
|
585 |
#' @return `TealDataConnector` object |
|
586 |
#' @export |
|
587 |
relational_data_connector <- function(connection, connectors) { |
|
588 | 2x |
stopifnot(inherits(connection, "TealDataConnection")) |
589 | 2x |
checkmate::assert_list(connectors, types = "TealDatasetConnector", min.len = 1) |
590 | 2x |
TealDataConnector$new(connection, connectors) |
591 |
} |
1 |
## CallableFunction ==== |
|
2 |
#' |
|
3 |
#' @title A \code{CallableFunction} class of objects |
|
4 |
#' |
|
5 |
#' @description Object that stores a function name together with its arguments. |
|
6 |
#' Methods are then available to get the function call and evaluate it. |
|
7 |
#' |
|
8 |
#' @keywords internal |
|
9 |
#' |
|
10 |
CallableFunction <- R6::R6Class( # nolint |
|
11 |
"CallableFunction", |
|
12 |
inherit = Callable, |
|
13 | ||
14 |
## __Public Methods ==== |
|
15 |
public = list( |
|
16 |
#' @description |
|
17 |
#' Create a new \code{CallableFunction} object |
|
18 |
#' |
|
19 |
#' @param fun (\code{function})\cr |
|
20 |
#' function to be evaluated in class. |
|
21 |
#' This is either a `function` object or its name as a string. |
|
22 |
#' @param env (\code{environment})\cr |
|
23 |
#' environment where function will be evaluated |
|
24 |
#' |
|
25 |
#' @return new \code{CallableFunction} object |
|
26 |
initialize = function(fun, env = new.env(parent = parent.env(globalenv()))) { |
|
27 | 210x |
super$initialize(env = env) |
28 | 210x |
if (missing(fun)) { |
29 | 1x |
stop("A valid function name must be provided.") |
30 |
} |
|
31 | 209x |
if (!(checkmate::test_string(fun) || is.function(fun) || is.call(fun) || is.symbol(fun))) { |
32 | 1x |
stop("CallableFunction can be specified as character, symbol, call or function") |
33 |
} |
|
34 | ||
35 | 207x |
fun_name <- private$get_callable_function(fun) |
36 | 202x |
private$fun_name <- deparse1(fun_name, collapse = "\n") |
37 | ||
38 | 202x |
private$refresh() |
39 | ||
40 | 202x |
logger::log_trace("CallableFunction initialized with function: { deparse1(private$fun_name) }.") |
41 | ||
42 | 202x |
return(invisible(self)) |
43 |
}, |
|
44 |
#' @description |
|
45 |
#' get the arguments a function gets called with |
|
46 |
#' |
|
47 |
#' @return arguments the function gets called with |
|
48 |
get_args = function() { |
|
49 | 3x |
return(private$args) |
50 |
}, |
|
51 |
#' @description |
|
52 |
#' Get function call with substituted arguments in \code{args}. |
|
53 |
#' These arguments will not be stored in the object. |
|
54 |
#' |
|
55 |
#' @param deparse (\code{logical} value)\cr |
|
56 |
#' whether to return a deparsed version of call |
|
57 |
#' @param args (\code{NULL} or named \code{list})\cr |
|
58 |
#' dynamic arguments to function |
|
59 |
#' |
|
60 |
#' @return \code{call} or \code{character} depending on \code{deparse} argument |
|
61 |
get_call = function(deparse = TRUE, args = NULL) { |
|
62 | 479x |
checkmate::assert_flag(deparse) |
63 | 479x |
checkmate::assert_list(args, names = "strict", min.len = 0, null.ok = TRUE) |
64 | ||
65 | 479x |
old_args <- private$args |
66 | 6x |
if (length(args) > 0) self$set_args(args) |
67 | ||
68 | 479x |
res <- if (deparse) { |
69 | 28x |
deparse1(private$call, collapse = "\n") |
70 |
} else { |
|
71 | 451x |
private$call |
72 |
} |
|
73 | ||
74 |
# set args back to default |
|
75 | 479x |
if (length(args) > 0) { |
76 | 6x |
lapply(names(args), self$set_arg_value, NULL) |
77 | 6x |
self$set_args(old_args) |
78 |
} |
|
79 | ||
80 | 479x |
return(res) |
81 |
}, |
|
82 |
#' @description |
|
83 |
#' Set up function arguments |
|
84 |
#' |
|
85 |
#' @param args (\code{NULL} or named \code{list})\cr |
|
86 |
#' function arguments to be stored persistently in the object. Setting \code{args} doesn't |
|
87 |
#' remove other \code{args}, only create new of modify previous of the same name. |
|
88 |
#' To clean arguments specify \code{args = NULL}. |
|
89 |
#' |
|
90 |
#' @return (`self`) invisibly for chaining. |
|
91 |
set_args = function(args) { |
|
92 |
# remove args if empty |
|
93 | 94x |
if (length(args) == 0) { |
94 | 6x |
private$args <- NULL |
95 | 6x |
private$refresh() |
96 | 6x |
return(invisible(self)) |
97 |
} |
|
98 | 88x |
checkmate::assert_list(args, min.len = 0, names = "unique") |
99 | ||
100 | 88x |
for (idx in seq_along(args)) { |
101 | 121x |
self$set_arg_value( |
102 | 121x |
name = names(args)[[idx]], |
103 | 121x |
value = args[[idx]] |
104 |
) |
|
105 |
} |
|
106 | 88x |
logger::log_trace( |
107 | 88x |
"CallableFunction$set_args args set for function: { deparse1(private$fun_name) }." |
108 |
) |
|
109 | ||
110 | 88x |
return(invisible(self)) |
111 |
}, |
|
112 |
#' @description |
|
113 |
#' Set up single function argument with value |
|
114 |
#' |
|
115 |
#' @param name (\code{character}) argument name |
|
116 |
#' @param value argument value |
|
117 |
#' |
|
118 |
#' @return (`self`) invisibly for chaining. |
|
119 |
set_arg_value = function(name, value) { |
|
120 | 130x |
checkmate::assert_string(name) |
121 | 130x |
arg_names <- names(formals(eval(str2lang(private$fun_name)))) |
122 | 130x |
stopifnot(name %in% arg_names || "..." %in% arg_names || is.null(arg_names)) |
123 | ||
124 | 130x |
if (length(private$args) == 0) { |
125 | 80x |
private$args <- list() |
126 |
} |
|
127 | 130x |
private$args[[name]] <- value |
128 | ||
129 | 130x |
private$refresh() |
130 | 130x |
logger::log_trace("CallableFunction$set_arg_value args values set for arg: { deparse1(name) }.") |
131 | ||
132 | 130x |
return(invisible(self)) |
133 |
} |
|
134 |
), |
|
135 | ||
136 |
## __Private Fields ==== |
|
137 |
private = list( |
|
138 |
fun_name = character(0), |
|
139 |
args = NULL, # named list with argument names and values |
|
140 |
## __Private Methods ==== |
|
141 |
# @description |
|
142 |
# Refresh call with function name and saved arguments |
|
143 |
# |
|
144 |
# @return nothing |
|
145 |
refresh = function() { |
|
146 | 338x |
if (!is.null(private$fun_name) || !identical(private$fun_name, character(0))) { |
147 | ||
148 |
# replaced str2lang found at: |
|
149 |
# https://rlang.r-lib.org/reference/call2.html |
|
150 | 338x |
private$call <- as.call( |
151 | 338x |
c(rlang::parse_expr(private$fun_name), private$args) |
152 |
) |
|
153 | ||
154 |
# exception for source(...)$value |
|
155 | 338x |
if (private$fun_name == "source") { |
156 | 9x |
private$call <- rlang::parse_expr( |
157 | 9x |
sprintf("%s$value", deparse1(private$call, collapse = "\n")) |
158 |
) |
|
159 | 329x |
} else if (private$fun_name %in% c("py_run_file", "py_run_string")) { |
160 | ! |
private$call <- rlang::parse_expr( |
161 | ! |
sprintf("%s[[%s]]", deparse1(private$call, collapse = "\n"), deparse1(private$object, collapse = "\n")) |
162 |
) |
|
163 |
} |
|
164 |
} |
|
165 |
}, |
|
166 |
# @description |
|
167 |
# Returns a call to a function |
|
168 |
# |
|
169 |
# Returns the call to the function as defined in the enclosing environment. |
|
170 |
# |
|
171 |
# @param callable \code{function, character, call, symbol} the function to return |
|
172 |
# |
|
173 |
# @return `call` the call to the function |
|
174 |
# |
|
175 |
get_callable_function = function(callable) { |
|
176 | 207x |
if (is.character(callable) && private$is_prefixed_function(callable)) { |
177 | 11x |
private$get_call_from_prefixed_function(callable) |
178 |
} else { |
|
179 | 196x |
private$get_call_from_symbol(callable) |
180 |
} |
|
181 |
}, |
|
182 |
# @param function_name (`character`) the function name prefixed with \code{::} |
|
183 |
# and the package name |
|
184 |
# @return `call` the call to the function passed to this method |
|
185 |
get_call_from_prefixed_function = function(function_name) { |
|
186 | 11x |
package_function_names <- strsplit(function_name, "::")[[1]] |
187 | 11x |
fun <- get(package_function_names[2], envir = getNamespace(package_function_names[1])) |
188 | 11x |
if (!is.function(fun)) { |
189 | 1x |
stop(sprintf("object '%s' of mode 'function' was not found", function_name)) |
190 |
} |
|
191 | 10x |
str2lang(function_name) |
192 |
}, |
|
193 |
# @param symbol (`function`, `symbol` or `character`) the item matching a function |
|
194 |
# @return `call` the call to the function passed to this method |
|
195 |
get_call_from_symbol = function(symbol) { |
|
196 | 196x |
fun <- match.fun(symbol) |
197 | 192x |
fun_environment <- environment(fun) |
198 | 192x |
if (isNamespace(fun_environment)) { |
199 | 69x |
fun_name <- get_binding_name(fun, fun_environment) |
200 | 69x |
namespace_name <- strsplit(rlang::env_name(fun_environment), ":")[[1]][2] |
201 | 69x |
if (namespace_name != "base") { |
202 | 8x |
fun_name <- paste(namespace_name, fun_name, sep = "::") |
203 |
} |
|
204 | 69x |
fun <- str2lang(fun_name) |
205 |
} |
|
206 | 192x |
fun |
207 |
}, |
|
208 |
# Checks whether a character vector is of this format |
|
209 |
# <package_name>::<function_name> |
|
210 |
# |
|
211 |
# @param function_name (`character`) the character vector |
|
212 |
# @return `logical` `TRUE` if \code{function_name} is of the specified |
|
213 |
# format; `FALSE` otherwise |
|
214 |
# |
|
215 |
is_prefixed_function = function(function_name) { |
|
216 | 18x |
grepl("^[[:ascii:]]+::[[:ascii:]]+$", function_name, perl = TRUE) |
217 |
} |
|
218 |
) |
|
219 |
) |
|
220 | ||
221 |
## Constructors ==== |
|
222 | ||
223 |
#' Create \code{CallableFunction} object |
|
224 |
#' |
|
225 |
#' @description `r lifecycle::badge("stable")` |
|
226 |
#' Create \code{\link{CallableFunction}} object to execute specific function and get reproducible |
|
227 |
#' call. |
|
228 |
#' |
|
229 |
#' @param fun (\code{function})\cr |
|
230 |
#' any R function, directly by name or \code{character} string. |
|
231 |
#' |
|
232 |
#' @return \code{CallableFunction} object |
|
233 |
#' |
|
234 |
#' @export |
|
235 |
#' |
|
236 |
#' @examples |
|
237 |
#' cf <- callable_function(fun = stats::median) |
|
238 |
#' cf$set_args(list(x = 1:10, na.rm = FALSE)) |
|
239 |
#' cf$run() |
|
240 |
#' cf$get_call() |
|
241 |
callable_function <- function(fun) { |
|
242 | 160x |
CallableFunction$new(fun) |
243 |
} |
|
244 | ||
245 |
#' Gets the name of the binding |
|
246 |
#' |
|
247 |
#' Gets the name of the object by finding its origin. |
|
248 |
#' Depending on type of object function uses different methods |
|
249 |
#' to obtain original location. If no `env` is specified then |
|
250 |
#' object is tracked by `substitute` along the `sys.frames`. |
|
251 |
#' If `env` is specified then search is limited to specified |
|
252 |
#' environment.\cr |
|
253 |
#' |
|
254 |
#' @note |
|
255 |
#' Raises an error if the object is not found in the environment. |
|
256 |
#' |
|
257 |
#' @param object (R object)\cr |
|
258 |
#' any R object |
|
259 |
#' @param envir (`environment`)\cr |
|
260 |
#' if origin of the object is known then should be provided for |
|
261 |
#' more precise search |
|
262 |
#' @return character |
|
263 |
#' @keywords internal |
|
264 |
#' |
|
265 |
get_binding_name <- function(object, envir) { |
|
266 | 70x |
bindings_names <- ls(envir) |
267 | 70x |
identical_binding_mask <- vapply( |
268 | 70x |
bindings_names, |
269 | 70x |
function(binding_name) identical(get(binding_name, envir), object), |
270 | 70x |
FUN.VALUE = logical(1), |
271 | 70x |
USE.NAMES = FALSE |
272 |
) |
|
273 | 70x |
if (length(bindings_names[identical_binding_mask]) == 0) { |
274 | 1x |
stop("Object not found in the environment") |
275 |
} |
|
276 | 69x |
bindings_names[identical_binding_mask] |
277 |
} |
1 |
#' Topological graph sort |
|
2 |
#' |
|
3 |
#' Graph is a list which for each node contains a vector of child nodes |
|
4 |
#' in the returned list, parents appear before their children. |
|
5 |
#' |
|
6 |
#' Implementation of `Kahn` algorithm with a modification to maintain the order of input elements. |
|
7 |
#' |
|
8 |
#' @param graph (named `list`) list with node vector elements |
|
9 |
#' @keywords internal |
|
10 |
#' |
|
11 |
#' @examples |
|
12 |
#' teal.data:::topological_sort(list(A = c(), B = c("A"), C = c("B"), D = c("A"))) |
|
13 |
#' teal.data:::topological_sort(list(D = c("A"), A = c(), B = c("A"), C = c("B"))) |
|
14 |
#' teal.data:::topological_sort(list(D = c("A"), B = c("A"), C = c("B"), A = c())) |
|
15 |
topological_sort <- function(graph) { |
|
16 |
# compute in-degrees |
|
17 | 29x |
in_degrees <- list() |
18 | 29x |
for (node in names(graph)) { |
19 | 55x |
in_degrees[[node]] <- 0 |
20 | 55x |
for (to_edge in graph[[node]]) { |
21 | 27x |
in_degrees[[to_edge]] <- 0 |
22 |
} |
|
23 |
} |
|
24 | ||
25 | 29x |
for (node in graph) { |
26 | 55x |
for (to_edge in node) { |
27 | 27x |
in_degrees[[to_edge]] <- in_degrees[[to_edge]] + 1 |
28 |
} |
|
29 |
} |
|
30 | ||
31 |
# sort |
|
32 | 29x |
visited <- 0 |
33 | 29x |
sorted <- list() |
34 | 29x |
zero_in <- list() |
35 | 29x |
for (node in names(in_degrees)) { |
36 | 39x |
if (in_degrees[[node]] == 0) zero_in <- append(zero_in, node) |
37 |
} |
|
38 | 29x |
zero_in <- rev(zero_in) |
39 | ||
40 | 29x |
while (length(zero_in) != 0) { |
41 | 57x |
visited <- visited + 1 |
42 | 57x |
sorted <- c(zero_in[[1]], sorted) |
43 | 57x |
for (edge_to in graph[[zero_in[[1]]]]) { |
44 | 26x |
in_degrees[[edge_to]] <- in_degrees[[edge_to]] - 1 |
45 | 26x |
if (in_degrees[[edge_to]] == 0) { |
46 | 18x |
zero_in <- append(zero_in, edge_to, 1) |
47 |
} |
|
48 |
} |
|
49 | 57x |
zero_in[[1]] <- NULL |
50 |
} |
|
51 | ||
52 | 29x |
if (visited != length(in_degrees)) { |
53 | 1x |
stop( |
54 | 1x |
"Graph is not a directed acyclic graph. Cycles involving nodes: ", |
55 | 1x |
paste0(setdiff(names(in_degrees), sorted), collapse = " ") |
56 |
) |
|
57 |
} else { |
|
58 | 28x |
return(sorted) |
59 |
} |
|
60 |
} |
|
61 | ||
62 |
#' Checks whether a graph is a `Directed Acyclic Graph (DAG)` |
|
63 |
#' |
|
64 |
#' @inheritParams topological_sort |
|
65 |
#' @return `logical(1)` `TRUE` if the graph is a `DAG`; `FALSE` otherwise |
|
66 |
#' @keywords internal |
|
67 |
is_dag <- function(graph) { |
|
68 | 29x |
inherits(try(topological_sort(graph), silent = TRUE), "try-error") |
69 |
} |
1 |
## TealDataConnection ==== |
|
2 |
#' @title A `TealDataConnection` class of objects |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' Objects of this class store the connection to a data source. |
|
6 |
#' It can be a database or server connection. |
|
7 |
#' |
|
8 |
#' @examples |
|
9 |
#' open_fun <- callable_function(data.frame) # define opening function |
|
10 |
#' open_fun$set_args(list(x = 1:5)) # define fixed arguments to opening function |
|
11 |
#' |
|
12 |
#' close_fun <- callable_function(sum) # define closing function |
|
13 |
#' close_fun$set_args(list(x = 1:5)) # define fixed arguments to closing function |
|
14 |
#' |
|
15 |
#' ping_fun <- callable_function(function() TRUE) |
|
16 |
#' |
|
17 |
#' x <- data_connection( # define connection |
|
18 |
#' ping_fun = ping_fun, # define ping function |
|
19 |
#' open_fun = open_fun, # define opening function |
|
20 |
#' close_fun = close_fun # define closing function |
|
21 |
#' ) |
|
22 |
#' |
|
23 |
#' x$set_open_args(args = list(y = letters[1:5])) # define additional arguments if necessary |
|
24 |
#' |
|
25 |
#' x$open() # call opening function |
|
26 |
#' x$get_open_call() # check reproducible R code |
|
27 |
#' |
|
28 |
#' # get data from connection via TealDataConnector$get_dataset() |
|
29 |
#' \dontrun{ |
|
30 |
#' x$open(args = list(x = 1:5, y = letters[1:5])) # able to call opening function with arguments |
|
31 |
#' x$close() # call closing function |
|
32 |
#' } |
|
33 |
#' |
|
34 |
TealDataConnection <- R6::R6Class( # nolint |
|
35 |
## __Public Methods ==== |
|
36 |
"TealDataConnection", |
|
37 |
public = list( |
|
38 |
#' @description |
|
39 |
#' Create a new `TealDataConnection` object |
|
40 |
#' |
|
41 |
#' @param open_fun (`CallableFunction`) function to open connection |
|
42 |
#' @param close_fun (`CallableFunction`) function to close connection |
|
43 |
#' @param ping_fun (`CallableFunction`) function to ping connection |
|
44 |
#' @param if_conn_obj optional, (`logical`) whether to store `conn` object returned from opening |
|
45 |
#' connection |
|
46 |
#' @return new `TealDataConnection` object |
|
47 |
initialize = function(open_fun = NULL, close_fun = NULL, ping_fun = NULL, if_conn_obj = FALSE) { |
|
48 | 29x |
checkmate::assert_flag(if_conn_obj) |
49 | 29x |
if (!is.null(open_fun)) { |
50 | 21x |
stopifnot(inherits(open_fun, "Callable")) |
51 | 21x |
private$set_open_fun(open_fun) |
52 |
} |
|
53 | 29x |
if (!is.null(close_fun)) { |
54 | 3x |
stopifnot(inherits(close_fun, "Callable")) |
55 | 3x |
private$set_close_fun(close_fun) |
56 |
} |
|
57 | 29x |
if (!is.null(ping_fun)) { |
58 | ! |
stopifnot(inherits(ping_fun, "Callable")) |
59 | ! |
private$set_ping_fun(ping_fun) |
60 |
} |
|
61 | 29x |
private$if_conn_obj <- if_conn_obj |
62 | ||
63 | 29x |
private$open_ui <- function(id) { |
64 | ! |
NULL |
65 |
} |
|
66 | 29x |
private$ping_ui <- function(id) { |
67 | ! |
NULL |
68 |
} |
|
69 | 29x |
private$close_ui <- function(id) { |
70 | ! |
NULL |
71 |
} |
|
72 | ||
73 | 29x |
logger::log_trace( |
74 | 29x |
sprintf( |
75 | 29x |
"TealDataConnection initialized with:%s%s%s%s.", |
76 | 29x |
if (!is.null(open_fun)) " open_fun" else "", |
77 | 29x |
if (!is.null(close_fun)) " close_fun" else "", |
78 | 29x |
if (!is.null(ping_fun)) " ping_fun" else "", |
79 | 29x |
if (if_conn_obj) " conn" else "" |
80 |
) |
|
81 |
) |
|
82 | 29x |
invisible(self) |
83 |
}, |
|
84 |
#' @description |
|
85 |
#' Finalize method closing the connection. |
|
86 |
#' |
|
87 |
#' @return NULL |
|
88 |
finalize = function() { |
|
89 | 29x |
self$close(silent = TRUE, try = TRUE) |
90 | 29x |
NULL |
91 |
}, |
|
92 |
#' @description |
|
93 |
#' If connection is opened |
|
94 |
#' |
|
95 |
#' If open connection has been successfully evaluated |
|
96 |
#' |
|
97 |
#' @return (`logical`) if connection is open |
|
98 |
is_opened = function() { |
|
99 | 4x |
return(private$opened) |
100 |
}, |
|
101 |
#' @description |
|
102 |
#' Check if connection has not failed. |
|
103 |
#' |
|
104 |
#' @return (`logical`) `TRUE` if connection failed, else `FALSE` |
|
105 |
is_failed = function() { |
|
106 | ! |
self$is_open_failed() || self$is_close_failed() |
107 |
}, |
|
108 |
#' @description |
|
109 |
#' Run simple application that uses its `ui` and `server` fields to open the |
|
110 |
#' connection. |
|
111 |
#' |
|
112 |
#' Useful for debugging |
|
113 |
#' |
|
114 |
#' @return An object that represents the app |
|
115 |
launch = function() { |
|
116 | ! |
shinyApp( |
117 | ! |
ui = fluidPage( |
118 | ! |
include_js_files(), |
119 | ! |
theme = get_teal_bs_theme(), |
120 | ! |
fluidRow( |
121 | ! |
column( |
122 | ! |
width = 8, |
123 | ! |
offset = 2, |
124 | ! |
tags$div( |
125 | ! |
id = "connection_inputs", |
126 | ! |
self$get_open_ui(id = "data_connection"), |
127 | ! |
actionButton("submit", "Submit"), |
128 | ! |
`data-proxy-click` = "submit" # handled by jscode in custom.js - hit enter to submit |
129 |
), |
|
130 | ! |
shinyjs::hidden( |
131 | ! |
tags$div( |
132 | ! |
id = "connection_set", |
133 | ! |
div( |
134 | ! |
h3("Connection successfully set."), |
135 | ! |
p("You can close this window and get back to R console.") |
136 |
) |
|
137 |
) |
|
138 |
) |
|
139 |
) |
|
140 |
) |
|
141 |
), |
|
142 | ! |
server = function(input, output, session) { |
143 | ! |
session$onSessionEnded(stopApp) |
144 | ! |
preopen_server <- self$get_preopen_server() |
145 | ! |
if (!is.null(preopen_server)) { |
146 | ! |
preopen_server(id = "data_connection", connection = self) |
147 |
} |
|
148 | ! |
observeEvent(input$submit, { |
149 | ! |
rv <- reactiveVal(NULL) |
150 | ! |
open_server <- self$get_open_server() |
151 | ! |
if (!is.null(open_server)) { |
152 | ! |
rv(open_server(id = "data_connection", connection = self)) |
153 |
} |
|
154 | ! |
observeEvent(rv(), { |
155 | ! |
if (self$is_opened()) { |
156 | ! |
removeUI(sprintf("#%s", session$ns("connection_inputs"))) |
157 | ! |
shinyjs::show("connection_set") |
158 | ! |
stopApp() |
159 |
} |
|
160 |
}) |
|
161 |
}) |
|
162 |
} |
|
163 |
) |
|
164 |
}, |
|
165 |
# ___ open connection ----- |
|
166 |
#' @description |
|
167 |
#' Open the connection. |
|
168 |
#' |
|
169 |
#' Note that if the connection is already opened then it does nothing. |
|
170 |
#' |
|
171 |
#' @param args (`NULL` or named `list`) additional arguments not set up previously |
|
172 |
#' @param silent (`logical`) whether convert all "missing function" errors to messages |
|
173 |
#' @param try (`logical`) whether perform function evaluation inside `try` clause |
|
174 |
#' |
|
175 |
#' @return returns `self` if successful or if connection has been already |
|
176 |
#' opened. If `open_fun` fails, app returns an error in form of |
|
177 |
#' `shinyjs::alert` (if `try = TRUE`) or breaks the app (if `try = FALSE`) |
|
178 |
#' |
|
179 |
open = function(args = NULL, silent = FALSE, try = FALSE) { |
|
180 | 6x |
logger::log_trace("TealDataConnection$open opening the connection...") |
181 | 6x |
checkmate::assert_list(args, min.len = 0, names = "unique", null.ok = TRUE) |
182 | 6x |
if (isFALSE(private$check_open_fun(silent = silent))) { |
183 | ! |
return() |
184 |
} |
|
185 | 6x |
if (isTRUE(private$opened) && isTRUE(private$ping())) { |
186 | ! |
private$opened <- TRUE |
187 | ! |
logger::log_trace("TealDataConnection$open connection already opened - skipped.") |
188 | ! |
return(invisible(self)) |
189 |
} else { |
|
190 | 6x |
open_res <- private$open_fun$run(args = args, try = try) |
191 | 6x |
if (!self$is_open_failed()) { |
192 | 6x |
private$opened <- TRUE |
193 | 6x |
if (private$if_conn_obj && !is.null(open_res)) { |
194 | ! |
private$conn <- open_res |
195 | ||
196 | ! |
if (!is.null(private$close_fun)) { |
197 | ! |
private$close_fun$assign_to_env("conn", private$conn) |
198 |
} |
|
199 | ! |
if (!is.null(private$ping_fun)) { |
200 | ! |
private$ping_fun$assign_to_env("conn", private$conn) |
201 |
} |
|
202 |
} |
|
203 | 6x |
logger::log_trace("TealDataConnection$open connection opened.") |
204 |
} else { |
|
205 | ! |
private$opened <- FALSE |
206 | ! |
private$conn <- NULL |
207 | ! |
logger::log_error("TealDataConnection$open connection failed to open.") |
208 |
} |
|
209 | ||
210 | 6x |
return(invisible(self)) |
211 |
} |
|
212 |
}, |
|
213 | ||
214 |
#' @description |
|
215 |
#' Get internal connection object |
|
216 |
#' |
|
217 |
#' @return `connection` object |
|
218 |
get_conn = function() { |
|
219 | 3x |
return(private$conn) |
220 |
}, |
|
221 |
#' @description |
|
222 |
#' Get executed open connection call |
|
223 |
#' |
|
224 |
#' @param deparse (`logical`) whether return deparsed form of a call |
|
225 |
#' @param args (`NULL` or named `list`) additional arguments not set up previously |
|
226 |
#' @param silent (`logical`) whether convert all "missing function" errors to messages |
|
227 |
#' |
|
228 |
#' @return optionally deparsed `call` object |
|
229 |
get_open_call = function(deparse = TRUE, args = NULL, silent = FALSE) { |
|
230 | 34x |
checkmate::assert_flag(deparse) |
231 | 34x |
checkmate::assert_list(args, min.len = 0, names = "unique", null.ok = TRUE) |
232 | 34x |
if (isFALSE(private$check_open_fun(silent = silent))) { |
233 | ! |
return() |
234 |
} |
|
235 | 34x |
open_call <- private$open_fun$get_call(deparse = FALSE, args = args) |
236 | ||
237 | 34x |
if (private$if_conn_obj) { |
238 | ! |
open_call <- call("<-", as.name("conn"), open_call) |
239 |
} |
|
240 | ||
241 | 34x |
if (isTRUE(deparse)) { |
242 | 32x |
deparse1(open_call, collapse = "\n") |
243 |
} else { |
|
244 | 2x |
open_call |
245 |
} |
|
246 |
}, |
|
247 |
#' @description |
|
248 |
#' Get error message from last connection |
|
249 |
#' |
|
250 |
#' @return (`character`)\cr |
|
251 |
#' text of the error message or `character(0)` if last |
|
252 |
#' connection was successful. |
|
253 |
get_open_error_message = function() { |
|
254 | ! |
return(private$open_fun$get_error_message()) |
255 |
}, |
|
256 |
#' @description |
|
257 |
#' Get shiny server module prior opening connection. |
|
258 |
#' |
|
259 |
#' @return (`function`) shiny server prior opening connection. |
|
260 |
get_preopen_server = function() { |
|
261 | ! |
return(private$preopen_server) |
262 |
}, |
|
263 |
#' @description |
|
264 |
#' Get shiny server module to open connection. |
|
265 |
#' |
|
266 |
#' @return (`function`) shiny server to open connection. |
|
267 |
get_open_server = function() { |
|
268 | ! |
return(private$open_server) |
269 |
}, |
|
270 |
#' @description |
|
271 |
#' Get Shiny module with inputs to open connection |
|
272 |
#' |
|
273 |
#' @param id `character` shiny element id |
|
274 |
#' |
|
275 |
#' @return (`function`) shiny UI to set arguments to open connection function. |
|
276 |
get_open_ui = function(id) { |
|
277 | ! |
return(private$open_ui(id)) |
278 |
}, |
|
279 |
#' @description |
|
280 |
#' Check if open connection has not failed. |
|
281 |
#' |
|
282 |
#' @return (`logical`) `TRUE` if open connection failed, else `FALSE` |
|
283 |
is_open_failed = function() { |
|
284 | 6x |
if (!is.null(private$open_fun)) { |
285 | 6x |
private$open_fun$is_failed() |
286 |
} else { |
|
287 | ! |
FALSE |
288 |
} |
|
289 |
}, |
|
290 |
#' @description |
|
291 |
#' Set open connection function argument |
|
292 |
#' |
|
293 |
#' @param args (`NULL` or named `list`) with values where list names are argument names |
|
294 |
#' @param silent (`logical`) whether convert all "missing function" errors to messages |
|
295 |
#' |
|
296 |
#' @return (`self`) invisibly for chaining. |
|
297 |
set_open_args = function(args, silent = FALSE) { |
|
298 | 2x |
checkmate::assert_list(args, min.len = 0, names = "unique", null.ok = TRUE) |
299 | 2x |
if (isFALSE(private$check_open_fun(silent = silent))) { |
300 | ! |
return() |
301 |
} |
|
302 | 2x |
private$open_fun$set_args(args) |
303 | 2x |
logger::log_trace("TealDataConnection$set_open_args open args set.") |
304 | ||
305 | 2x |
return(invisible(self)) |
306 |
}, |
|
307 |
#' @description |
|
308 |
#' Set pre-open connection server function |
|
309 |
#' |
|
310 |
#' This function will be called before submit button will be hit. |
|
311 |
#' |
|
312 |
#' @param preopen_module (`function`)\cr |
|
313 |
#' A shiny module server function |
|
314 |
#' |
|
315 |
#' @return (`self`) invisibly for chaining. |
|
316 |
set_preopen_server = function(preopen_module) { |
|
317 | 2x |
stopifnot(inherits(preopen_module, "function")) |
318 | 2x |
module_name <- "open_conn" |
319 | 2x |
if (all(names(formals(preopen_module)) %in% c("input", "output", "session", "connection"))) { |
320 | 1x |
private$preopen_server <- function(input, output, session, connection) { |
321 | ! |
callModule(preopen_module, id = module_name, connection = connection) |
322 |
} |
|
323 | 1x |
} else if (all(names(formals(preopen_module)) %in% c("id", "connection"))) { |
324 | 1x |
private$preopen_server <- function(id, connection) { |
325 | ! |
moduleServer( |
326 | ! |
id = id, |
327 | ! |
module = function(input, output, session) { |
328 | ! |
preopen_module(id = module_name, connection = connection) |
329 |
} |
|
330 |
) |
|
331 |
} |
|
332 |
} else { |
|
333 | ! |
stop(paste( |
334 | ! |
"set_preopen_server accepts only a valid shiny module", |
335 | ! |
"definition with a single additional parameter 'connection'." |
336 |
)) |
|
337 |
} |
|
338 | 2x |
logger::log_trace("TealDataConnection$set_preopen_server preopen_server set.") |
339 | ||
340 | 2x |
invisible(self) |
341 |
}, |
|
342 |
#' @description |
|
343 |
#' Set open connection server function |
|
344 |
#' |
|
345 |
#' This function will be called after submit button will be hit. There is no possibility to |
|
346 |
#' specify some dynamic `ui` as `server` function is executed after hitting submit |
|
347 |
#' button. |
|
348 |
#' |
|
349 |
#' @param open_module (`function`)\cr |
|
350 |
#' A shiny module server function that should load data from all connectors |
|
351 |
#' |
|
352 |
#' @return (`self`) invisibly for chaining. |
|
353 |
set_open_server = function(open_module) { |
|
354 | 2x |
stopifnot(inherits(open_module, "function")) |
355 | 2x |
module_name <- "open_conn" |
356 | 2x |
if (all(names(formals(open_module)) %in% c("input", "output", "session", "connection"))) { |
357 | 1x |
private$open_server <- function(input, output, session, connection) { |
358 | ! |
withProgress(message = "Opening connection", value = 1, { |
359 | ! |
callModule(open_module, id = module_name, connection = connection) |
360 |
}) |
|
361 |
} |
|
362 | 1x |
} else if (all(names(formals(open_module)) %in% c("id", "connection"))) { |
363 | 1x |
private$open_server <- function(id, connection) { |
364 | ! |
moduleServer( |
365 | ! |
id = id, |
366 | ! |
module = function(input, output, session) { |
367 | ! |
withProgress(message = "Opening connection", value = 1, { |
368 | ! |
open_module(id = module_name, connection = connection) |
369 |
}) |
|
370 |
} |
|
371 |
) |
|
372 |
} |
|
373 |
} else { |
|
374 | ! |
stop(paste( |
375 | ! |
"set_open_server accepts only a valid shiny module", |
376 | ! |
"definition with a single additional parameter 'connection'." |
377 |
)) |
|
378 |
} |
|
379 | 2x |
logger::log_trace("TealDataConnection$set_open_server open_server set.") |
380 | ||
381 | 2x |
invisible(self) |
382 |
}, |
|
383 |
#' @description |
|
384 |
#' Set open connection UI function |
|
385 |
#' |
|
386 |
#' @param open_module (`function`)\cr |
|
387 |
#' shiny module as function. Inputs specified in this `ui` are passed to server module |
|
388 |
#' defined by `set_open_server` method. |
|
389 |
#' |
|
390 |
#' @return (`self`) invisibly for chaining. |
|
391 |
set_open_ui = function(open_module) { |
|
392 | ! |
stopifnot(inherits(open_module, "function")) |
393 | ! |
stopifnot(identical(names(formals(open_module)), "id")) |
394 | ||
395 | ! |
private$open_ui <- function(id) { |
396 | ! |
ns <- NS(id) |
397 | ! |
tags$div( |
398 | ! |
tags$div( |
399 | ! |
id = ns("open_conn"), |
400 | ! |
open_module(id = ns("open_conn")) |
401 |
) |
|
402 |
) |
|
403 |
} |
|
404 | ! |
logger::log_trace("TealDataConnection$set_open_ui open_ui set.") |
405 | ||
406 | ! |
invisible(self) |
407 |
}, |
|
408 |
# ___ close connection ------- |
|
409 |
#' @description |
|
410 |
#' Close the connection. |
|
411 |
#' |
|
412 |
#' @param silent (`logical`) whether convert all "missing function" errors to messages |
|
413 |
#' @param try (`logical`) whether perform function evaluation inside `try` clause |
|
414 |
#' |
|
415 |
#' @return returns (`self`) if successful. For unsuccessful evaluation it |
|
416 |
#' depends on `try` argument: if `try = TRUE` then returns |
|
417 |
#' `error`, for `try = FALSE` otherwise |
|
418 |
close = function(silent = FALSE, try = FALSE) { |
|
419 | 33x |
logger::log_trace("TealDataConnection$close closing the connection...") |
420 | 33x |
if (isFALSE(private$check_close_fun(silent = silent))) { |
421 | 29x |
return() |
422 |
} |
|
423 | 4x |
close_res <- private$close_fun$run(try = try) |
424 | 4x |
if (inherits(close_res, "error")) { |
425 | ! |
logger::log_error("TealDataConnection$close failed to close the connection.") |
426 | ! |
return(close_res) |
427 |
} else { |
|
428 | 4x |
private$opened <- FALSE |
429 | 4x |
private$conn <- NULL |
430 | 4x |
logger::log_trace("TealDataConnection$close connection closed.") |
431 | 4x |
return(invisible(NULL)) |
432 |
} |
|
433 |
}, |
|
434 |
#' @description |
|
435 |
#' Get executed close connection call |
|
436 |
#' |
|
437 |
#' @param deparse (`logical`) whether return deparsed form of a call |
|
438 |
#' @param silent (`logical`) whether convert all "missing function" errors to messages |
|
439 |
#' |
|
440 |
#' @return optionally deparsed `call` object |
|
441 |
get_close_call = function(deparse = TRUE, silent = FALSE) { |
|
442 | 30x |
checkmate::assert_flag(deparse) |
443 | 30x |
if (isFALSE(private$check_close_fun(silent = silent))) { |
444 | 30x |
return() |
445 |
} |
|
446 | ! |
private$close_fun$get_call(deparse = deparse) |
447 |
}, |
|
448 |
#' @description |
|
449 |
#' Get error message from last connection |
|
450 |
#' |
|
451 |
#' @return (`character`)\cr |
|
452 |
#' text of the error message or `character(0)` if last |
|
453 |
#' connection was successful. |
|
454 |
get_close_error_message = function() { |
|
455 | ! |
return(private$close_fun$get_error_message()) |
456 |
}, |
|
457 |
#' @description |
|
458 |
#' Get shiny server module to close connection. |
|
459 |
#' |
|
460 |
#' @return the `server function` to close connection. |
|
461 |
get_close_server = function() { |
|
462 | ! |
return(private$close_server) |
463 |
}, |
|
464 |
#' @description |
|
465 |
#' Check if close connection has not failed. |
|
466 |
#' |
|
467 |
#' @return (`logical`) `TRUE` if close connection failed, else `FALSE` |
|
468 |
is_close_failed = function() { |
|
469 | ! |
if (!is.null(private$close_fun)) { |
470 | ! |
private$close_fun$is_failed() |
471 |
} else { |
|
472 | ! |
FALSE |
473 |
} |
|
474 |
}, |
|
475 | ||
476 |
#' @description |
|
477 |
#' Set close connection function argument |
|
478 |
#' |
|
479 |
#' @param args (named `list`) with values where list names are argument names |
|
480 |
#' @param silent (`logical`) whether convert all "missing function" errors to messages |
|
481 |
#' |
|
482 |
#' @return (`self`) invisibly for chaining. |
|
483 |
set_close_args = function(args, silent = FALSE) { |
|
484 | ! |
checkmate::assert_list(args, min.len = 0, names = "unique", null.ok = TRUE) |
485 | ! |
if (isFalse(private$check_close_fun(silent = silent))) { |
486 | ! |
return() |
487 |
} |
|
488 | ! |
private$close_fun$set_args(args) |
489 | ! |
logger::log_trace("TealDataConnection$set_close_args close_args set") |
490 | ||
491 | ! |
return(invisible(self)) |
492 |
}, |
|
493 | ||
494 |
#' @description |
|
495 |
#' Set close connection UI function |
|
496 |
#' |
|
497 |
#' @param close_module (`function`)\cr |
|
498 |
#' shiny module as function. Inputs specified in this `ui` are passed to server module |
|
499 |
#' defined by `set_close_server` method. |
|
500 |
#' |
|
501 |
#' @return (`self`) invisibly for chaining. |
|
502 |
set_close_ui = function(close_module) { |
|
503 | ! |
stopifnot(inherits(close_module, "function")) |
504 | ! |
stopifnot(identical(names(formals(close_module)), "id")) |
505 | ||
506 | ! |
private$close_ui <- function(id) { |
507 | ! |
ns <- NS(id) |
508 | ! |
tags$div( |
509 | ! |
tags$div( |
510 | ! |
id = ns("close_conn"), |
511 | ! |
close_module(id = ns("close_conn")) |
512 |
) |
|
513 |
) |
|
514 |
} |
|
515 | ! |
logger::log_trace("TealDataConnection$close_ui close_ui set.") |
516 | ||
517 | ! |
return(invisible(self)) |
518 |
}, |
|
519 | ||
520 |
#' @description |
|
521 |
#' Set close-connection server function |
|
522 |
#' |
|
523 |
#' This function will be called after submit button will be hit. There is no possibility to |
|
524 |
#' specify some dynamic `ui` as `server` function is executed after hitting submit |
|
525 |
#' button. |
|
526 |
#' |
|
527 |
#' @param close_module (`function`)\cr |
|
528 |
#' A shiny module server function that should load data from all connectors |
|
529 |
#' |
|
530 |
#' @return (`self`) invisibly for chaining. |
|
531 |
set_close_server = function(close_module) { |
|
532 | 2x |
stopifnot(inherits(close_module, "function")) |
533 | 2x |
if (all(names(formals(close_module)) %in% c("input", "output", "session", "connection"))) { |
534 | 1x |
function(input, output, session, connection) { |
535 | ! |
connection$close(try = TRUE) |
536 | ||
537 | ! |
if (connection$is_close_failed()) { |
538 | ! |
shinyjs::alert( |
539 | ! |
paste( |
540 | ! |
"Error closing connection\nError message: ", |
541 | ! |
connection$get_close_error_message() |
542 |
) |
|
543 |
) |
|
544 |
} |
|
545 | ! |
invisible(connection) |
546 |
} |
|
547 | 1x |
} else if (all(names(formals(close_module)) %in% c("id", "connection"))) { |
548 | 1x |
function(id, connection) { |
549 | ! |
moduleServer( |
550 | ! |
id, |
551 | ! |
function(input, output, session) { |
552 | ! |
connection$close(try = TRUE) |
553 | ||
554 | ! |
if (connection$is_close_failed()) { |
555 | ! |
shinyjs::alert( |
556 | ! |
paste( |
557 | ! |
"Error closing connection\nError message: ", |
558 | ! |
connection$get_close_error_message() |
559 |
) |
|
560 |
) |
|
561 |
} |
|
562 | ! |
invisible(connection) |
563 |
} |
|
564 |
) |
|
565 |
} |
|
566 |
} else { |
|
567 | ! |
stop(paste( |
568 | ! |
"set_close_server accepts only a valid shiny module", |
569 | ! |
"definition with a single additional parameter 'connection'." |
570 |
)) |
|
571 |
} |
|
572 | 2x |
logger::log_trace("TealDataConnection$set_close_server close_server set.") |
573 | ||
574 | 2x |
invisible(self) |
575 |
} |
|
576 |
), |
|
577 |
## __Private Fields ==== |
|
578 |
private = list( |
|
579 |
# callableFunctions |
|
580 |
open_fun = NULL, |
|
581 |
close_fun = NULL, |
|
582 |
ping_fun = NULL, |
|
583 | ||
584 |
# connection object |
|
585 |
if_conn_obj = FALSE, |
|
586 |
conn = NULL, |
|
587 | ||
588 |
# shiny elements |
|
589 |
open_ui = NULL, |
|
590 |
close_ui = NULL, |
|
591 |
ping_ui = NULL, |
|
592 |
preopen_server = NULL, |
|
593 |
open_server = NULL, |
|
594 |
close_server = NULL, |
|
595 |
ping_server = NULL, |
|
596 |
opened = FALSE, |
|
597 | ||
598 |
## __Private Methods ==== |
|
599 |
# need to have a custom deep_clone because one of the key fields are reference-type object |
|
600 |
# in particular: open_fun is a R6 object that wouldn't be cloned using default clone(deep = T) |
|
601 |
deep_clone = function(name, value) { |
|
602 | ! |
deep_clone_r6(name, value) |
603 |
}, |
|
604 |
check_open_fun = function(silent = FALSE) { |
|
605 | 42x |
checkmate::assert_flag(silent) |
606 | ||
607 | 42x |
if (is.null(private$open_fun)) { |
608 | ! |
msg <- "Open connection function not set" |
609 | ! |
if (silent) { |
610 | ! |
return(FALSE) |
611 |
} else { |
|
612 | ! |
stop(msg) |
613 |
} |
|
614 |
} else { |
|
615 | 42x |
return(TRUE) |
616 |
} |
|
617 |
}, |
|
618 |
check_close_fun = function(silent = FALSE) { |
|
619 | 63x |
checkmate::assert_flag(silent) |
620 | ||
621 | 63x |
if (is.null(private$close_fun)) { |
622 | 59x |
msg <- "Close connection function not set" |
623 | 59x |
if (silent) { |
624 | 59x |
return(FALSE) |
625 |
} else { |
|
626 | ! |
stop(msg) |
627 |
} |
|
628 |
} else { |
|
629 | 4x |
return(TRUE) |
630 |
} |
|
631 |
}, |
|
632 |
# @description |
|
633 |
# Set close connection function |
|
634 |
# |
|
635 |
# @param fun (`Callable`) function to close connection |
|
636 |
# |
|
637 |
# @return (`self`) invisibly for chaining. |
|
638 |
set_close_fun = function(fun) { |
|
639 | 3x |
stopifnot(inherits(fun, "Callable")) |
640 | 3x |
private$close_fun <- fun |
641 | 3x |
return(invisible(self)) |
642 |
}, |
|
643 |
# @description |
|
644 |
# Set open connection function |
|
645 |
# |
|
646 |
# @param fun (`Callable`) function to open connection |
|
647 |
# |
|
648 |
# @return (`self`) invisibly for chaining. |
|
649 |
set_open_fun = function(fun) { |
|
650 | 21x |
stopifnot(inherits(fun, "Callable")) |
651 | 21x |
private$open_fun <- fun |
652 | 21x |
return(invisible(self)) |
653 |
}, |
|
654 |
# @description |
|
655 |
# Set a ping function |
|
656 |
# |
|
657 |
# @param fun (`Callable`) function to ping connection |
|
658 |
# |
|
659 |
# @return (`self`) invisibly for chaining. |
|
660 |
set_ping_fun = function(fun) { |
|
661 | ! |
stopifnot(inherits(fun, "Callable")) |
662 | ! |
private$ping_fun <- fun |
663 | ! |
return(invisible(self)) |
664 |
}, |
|
665 |
# @description |
|
666 |
# Ping the connection. |
|
667 |
# |
|
668 |
# @return (`logical`) |
|
669 |
ping = function() { |
|
670 | 1x |
logger::log_trace("TealDataConnection$ping pinging the connection...") |
671 | 1x |
if (!is.null(private$ping_fun)) { |
672 | ! |
ping_res <- isTRUE(private$ping_fun$run()) |
673 | ! |
logger::log_trace("TealDataConnection$ping ping result: { ping_res }.") |
674 | ! |
return(ping_res) |
675 |
} else { |
|
676 | 1x |
return(invisible(NULL)) |
677 |
} |
|
678 |
} |
|
679 |
) |
|
680 |
) |
|
681 | ||
682 |
#' The constructor for `TealDataConnection` class. |
|
683 |
#' |
|
684 |
#' @description `r lifecycle::badge("stable")` |
|
685 |
#' |
|
686 |
#' @param open_fun (`CallableFunction`) function to open connection |
|
687 |
#' @param close_fun (`CallableFunction`) function to close connection |
|
688 |
#' @param ping_fun (`CallableFunction`) function to ping connection |
|
689 |
#' @param if_conn_obj optional, (`logical`) whether to store `conn` object returned from opening |
|
690 |
#' |
|
691 |
#' @examples |
|
692 |
#' open_fun <- callable_function(data.frame) # define opening function |
|
693 |
#' open_fun$set_args(list(x = 1:5)) # define fixed arguments to opening function |
|
694 |
#' |
|
695 |
#' close_fun <- callable_function(sum) # define closing function |
|
696 |
#' close_fun$set_args(list(x = 1:5)) # define fixed arguments to closing function |
|
697 |
#' |
|
698 |
#' ping_fun <- callable_function(function() TRUE) |
|
699 |
#' |
|
700 |
#' x <- data_connection( # define connection |
|
701 |
#' ping_fun = ping_fun, # define ping function |
|
702 |
#' open_fun = open_fun, # define opening function |
|
703 |
#' close_fun = close_fun # define closing function |
|
704 |
#' ) |
|
705 |
#' |
|
706 |
#' x$set_open_args(args = list(y = letters[1:5])) # define additional arguments if necessary |
|
707 |
#' |
|
708 |
#' x$open() # call opening function |
|
709 |
#' x$get_open_call() # check reproducible R code |
|
710 |
#' |
|
711 |
#' # get data from connection via TealDataConnector$get_dataset() |
|
712 |
#' \dontrun{ |
|
713 |
#' x$open(args = list(x = 1:5, y = letters[1:5])) # able to call opening function with arguments |
|
714 |
#' x$close() # call closing function |
|
715 |
#' } |
|
716 |
#' |
|
717 |
#' @return `TealDataConnection` object |
|
718 |
#' @export |
|
719 |
data_connection <- function(open_fun = NULL, close_fun = NULL, ping_fun = NULL, if_conn_obj = FALSE) { |
|
720 | 6x |
TealDataConnection$new( |
721 | 6x |
open_fun = open_fun, close_fun = close_fun, ping_fun = ping_fun, if_conn_obj = if_conn_obj |
722 |
) |
|
723 |
} |
1 |
## TealDatasetConnector ==== |
|
2 |
#' |
|
3 |
#' |
|
4 |
#' @title A `TealDatasetConnector` class of objects |
|
5 |
#' |
|
6 |
#' @description `r lifecycle::badge("stable")` |
|
7 |
#' Objects of this class store the connection function to fetch a single dataset. |
|
8 |
#' Note that for some specific connection types, |
|
9 |
#' an object of class `TealDataConnection` must be provided. |
|
10 |
#' Data can be pulled via the `pull` method and accessed directly |
|
11 |
#' through the `dataset` active binding. |
|
12 |
#' Pulled data inherits from the class [`TealDataset`] |
|
13 |
#' |
|
14 |
#' @param dataname (`character`)\cr |
|
15 |
#' A given name for the dataset it may not contain spaces |
|
16 |
#' |
|
17 |
#' @param pull_callable (`CallableFunction`)\cr |
|
18 |
#' function with necessary arguments set to fetch data from connection. |
|
19 |
#' |
|
20 |
#' @param keys optional, (`character`)\cr |
|
21 |
#' vector of dataset primary keys column names |
|
22 |
#' |
|
23 |
#' @param label (`character`)\cr |
|
24 |
#' Label to describe the dataset. |
|
25 |
#' |
|
26 |
#' @param code (`character`)\cr |
|
27 |
#' A character string defining code to modify `raw_data` from this dataset. To modify |
|
28 |
#' current dataset code should contain at least one assignment to object defined in `dataname` |
|
29 |
#' argument. For example if `dataname = ADSL` example code should contain |
|
30 |
#' `ADSL <- <some R code>`. Can't be used simultaneously with `script` |
|
31 |
#' |
|
32 |
#' @param script (`character`)\cr |
|
33 |
#' Alternatively to `code` - location of the file containing modification code. |
|
34 |
#' Can't be used simultaneously with `script`. |
|
35 |
#' |
|
36 |
#' @param vars (named `list`)) \cr |
|
37 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
38 |
#' other constant value, this/these object(s) should be included as named |
|
39 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
40 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
41 |
#' It's recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
42 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
43 |
#' are included to this object as local `vars` and they cannot be modified |
|
44 |
#' within another dataset. |
|
45 |
#' |
|
46 |
#' @param metadata (named `list`, `NULL` or `CallableFunction`) \cr |
|
47 |
#' Field containing either the metadata about the dataset (each element of the list |
|
48 |
#' should be atomic and length one) or a `CallableFuntion` to pull the metadata |
|
49 |
#' from a connection. This should return a `list` or an object which can be |
|
50 |
#' converted to a list with `as.list`. |
|
51 |
TealDatasetConnector <- R6::R6Class( # nolint |
|
52 | ||
53 |
## __Public Methods ==== |
|
54 |
classname = "TealDatasetConnector", |
|
55 |
public = list( |
|
56 |
#' @description |
|
57 |
#' Create a new `TealDatasetConnector` object. Set the pulling function |
|
58 |
#' `CallableFunction` which returns a `data.frame` or `MultiAssayExperiment`, |
|
59 |
#' e.g. by reading from a function or creating it on the fly. |
|
60 |
initialize = function(dataname, |
|
61 |
pull_callable, |
|
62 |
keys = character(0), |
|
63 |
label = character(0), |
|
64 |
code = character(0), |
|
65 |
vars = list(), |
|
66 |
metadata = NULL) { |
|
67 | 181x |
private$set_pull_callable(pull_callable) |
68 | 181x |
private$set_var_r6(vars) |
69 | 181x |
private$set_pull_vars(vars) |
70 | ||
71 | 181x |
private$set_dataname(dataname) |
72 | 181x |
private$set_metadata(metadata) |
73 | ||
74 | 181x |
self$set_dataset_label(label) |
75 | 181x |
self$set_keys(keys) |
76 | ||
77 | 181x |
if (length(code) > 0) { |
78 |
# just needs a dummy TealDataset object to store mutate code, hence col = 1 |
|
79 | 1x |
private$dataset <- TealDataset$new(dataname = self$get_dataname(), x = data.frame(col = 1)) |
80 | 1x |
private$dataset$mutate(code = code, vars = vars, force_delay = TRUE) |
81 |
} |
|
82 | ||
83 | 181x |
logger::log_trace("TealDatasetConnector initialized for dataset: { deparse1(self$get_dataname()) }.") |
84 | ||
85 | 181x |
return(invisible(self)) |
86 |
}, |
|
87 |
#' @description |
|
88 |
#' Prints this `TealDatasetConnector`. |
|
89 |
#' |
|
90 |
#' @param ... additional arguments to the printing method |
|
91 |
#' @return invisibly self |
|
92 |
print = function(...) { |
|
93 | 6x |
check_ellipsis(...) |
94 | ||
95 | 6x |
cat(sprintf( |
96 | 6x |
"A %s object, named %s, containing a TealDataset object that has %sbeen loaded/pulled%s\n", |
97 | 6x |
class(self)[1], |
98 | 6x |
self$get_dataname(), |
99 | 6x |
ifelse(self$is_pulled(), "", "not "), |
100 | 6x |
ifelse(self$is_pulled(), ":", "") |
101 |
)) |
|
102 | 6x |
if (self$is_pulled()) { |
103 | 2x |
print(self$get_dataset()) |
104 |
} |
|
105 | ||
106 | 6x |
invisible(self) |
107 |
}, |
|
108 | ||
109 |
# ___ getters ==== |
|
110 |
#' @description |
|
111 |
#' Get `dataname` of dataset |
|
112 |
#' |
|
113 |
#' @return `dataname` of the dataset |
|
114 |
get_dataname = function() { |
|
115 | 509x |
return(private$dataname) |
116 |
}, |
|
117 |
#' @description |
|
118 |
#' Get `dataname` of dataset |
|
119 |
#' |
|
120 |
#' @return `character` `dataname` of the dataset |
|
121 |
get_datanames = function() { |
|
122 | 11x |
return(private$dataname) |
123 |
}, |
|
124 |
#' @description |
|
125 |
#' Get label of dataset |
|
126 |
#' |
|
127 |
#' @return `character` dataset label |
|
128 |
get_dataset_label = function() { |
|
129 | 132x |
return(private$dataset_label) |
130 |
}, |
|
131 |
#' @description |
|
132 |
#' Get primary keys of dataset |
|
133 |
#' @return `character` vector with dataset primary keys |
|
134 |
get_keys = function() { |
|
135 | 152x |
return(private$keys) |
136 |
}, |
|
137 |
#' @description |
|
138 |
#' Get code to get data |
|
139 |
#' |
|
140 |
#' @param deparse (`logical`)\cr |
|
141 |
#' whether return deparsed form of a call |
|
142 |
#' |
|
143 |
#' @return optionally deparsed `call` object |
|
144 |
get_code = function(deparse = TRUE) { |
|
145 | 44x |
checkmate::assert_flag(deparse) |
146 | 44x |
return(self$get_code_class()$get_code(deparse = deparse)) |
147 |
}, |
|
148 |
#' @description |
|
149 |
#' Get internal `CodeClass` object |
|
150 |
#' |
|
151 |
#' @return `CodeClass` |
|
152 |
get_code_class = function() { |
|
153 | 186x |
code_class <- CodeClass$new() |
154 | 186x |
pull_code_class <- private$get_pull_code_class() |
155 | 186x |
code_class$append(pull_code_class) |
156 | ||
157 | 186x |
if (!is.null(private$dataset)) { |
158 | 70x |
executed_code_in_dataset <- private$dataset$get_code_class() |
159 | 70x |
code_class$append(executed_code_in_dataset) |
160 |
} |
|
161 | ||
162 | 186x |
return(code_class) |
163 |
}, |
|
164 |
#' @description |
|
165 |
#' |
|
166 |
#' Derive the arguments this connector will pull with |
|
167 |
#' @return `list` of pull function fixed arguments |
|
168 |
get_pull_args = function() { |
|
169 | ! |
private$pull_callable$get_args() |
170 |
}, |
|
171 |
#' @description |
|
172 |
#' Get dataset |
|
173 |
#' |
|
174 |
#' @return dataset (`TealDataset`) |
|
175 |
get_dataset = function() { |
|
176 | 120x |
if (!self$is_pulled()) { |
177 | 21x |
stop( |
178 | 21x |
sprintf("'%s' has not been pulled yet\n - please use `load_dataset()` first.", self$get_dataname()), |
179 | 21x |
call. = FALSE |
180 |
) |
|
181 |
} |
|
182 | 99x |
private$dataset$get_dataset() |
183 | 99x |
return(private$dataset) |
184 |
}, |
|
185 |
#' @description |
|
186 |
#' Get error message from last pull |
|
187 |
#' |
|
188 |
#' @return `character` object with error message or `character(0)` if last |
|
189 |
#' pull was successful. |
|
190 |
get_error_message = function() { |
|
191 | 1x |
return(private$pull_callable$get_error_message()) |
192 |
}, |
|
193 |
#' @description |
|
194 |
#' Get pull function |
|
195 |
#' |
|
196 |
#' @return `CallableFunction` |
|
197 |
get_pull_callable = function() { |
|
198 | 28x |
return(private$pull_callable) |
199 |
}, |
|
200 |
#' @description |
|
201 |
#' Get raw data from dataset |
|
202 |
#' |
|
203 |
#' @return `data.frame` or `MultiAssayExperiment` data |
|
204 |
get_raw_data = function() { |
|
205 | 60x |
dataset <- self$get_dataset() |
206 | 58x |
return(dataset$get_raw_data()) |
207 |
}, |
|
208 |
#' @description |
|
209 |
#' Get the list of dependencies that are `TealDataset` or `TealDatasetConnector` objects |
|
210 |
#' |
|
211 |
#' @return `list` |
|
212 |
get_var_r6 = function() { |
|
213 | 47x |
return(private$var_r6) |
214 |
}, |
|
215 | ||
216 |
# ___ setters ==== |
|
217 |
#' @description |
|
218 |
#' Reassign `vars` in this object to keep references up to date after deep clone. |
|
219 |
#' Update is done based on the objects passed in `datasets` argument. Reassignment |
|
220 |
#' refers only to the provided `datasets`, other `vars` remains the same. |
|
221 |
#' @param datasets (`named list` of `TealDataset(s)` or `TealDatasetConnector(s)`)\cr |
|
222 |
#' objects with valid pointers. |
|
223 |
#' @return NULL invisible |
|
224 |
reassign_datasets_vars = function(datasets) { |
|
225 | 7x |
logger::log_trace( |
226 | 7x |
"TealDatasetConnector$reassign_datasets_vars reassigning vars in dataset: { self$get_dataname() }." |
227 |
) |
|
228 | 7x |
checkmate::assert_list(datasets, min.len = 0, names = "unique") |
229 | ||
230 | 7x |
common_var_r6 <- intersect(names(datasets), names(private$var_r6)) |
231 | 7x |
private$var_r6[common_var_r6] <- datasets[common_var_r6] |
232 | ||
233 | 7x |
common_vars <- intersect(names(datasets), names(private$pull_vars)) |
234 | 7x |
private$pull_vars[common_vars] <- datasets[common_vars] |
235 | ||
236 | 7x |
if (!is.null(private$dataset)) { |
237 | ! |
private$dataset$reassign_datasets_vars(datasets) |
238 |
} |
|
239 | 7x |
logger::log_trace( |
240 | 7x |
"TealDatasetConnector$reassign_datasets_vars reassigned vars in dataset: { self$get_dataname() }." |
241 |
) |
|
242 | ||
243 | 7x |
invisible(NULL) |
244 |
}, |
|
245 |
#' @description |
|
246 |
#' Set label of the `dataset` object |
|
247 |
#' |
|
248 |
#' @return (`self`) invisibly for chaining |
|
249 |
set_dataset_label = function(label) { |
|
250 | 181x |
if (is.null(label)) { |
251 | ! |
label <- character(0) |
252 |
} |
|
253 | 181x |
checkmate::assert_character(label, max.len = 1, any.missing = FALSE) |
254 | 181x |
private$dataset_label <- label |
255 | 181x |
if (self$is_pulled()) { |
256 | ! |
private$dataset$set_dataset_label(label) |
257 |
} |
|
258 | 181x |
logger::log_trace( |
259 | 181x |
"TealDatasetConnector$set_dataset_label label set for dataset: { deparse1(self$get_dataname()) }." |
260 |
) |
|
261 | ||
262 | 181x |
return(invisible(self)) |
263 |
}, |
|
264 |
#' @description |
|
265 |
#' Set new keys |
|
266 |
#' @return (`self`) invisibly for chaining. |
|
267 |
set_keys = function(keys) { |
|
268 | 181x |
checkmate::assert_character(keys, any.missing = FALSE) |
269 | 181x |
if (isTRUE(self$is_pulled())) { |
270 | ! |
set_keys(private$dataset, keys) |
271 |
} |
|
272 | 181x |
private$keys <- keys |
273 | 181x |
logger::log_trace("TealDatasetConnector$set_keys keys set for dataset: { deparse1(self$get_dataname()) }.") |
274 | ||
275 | 181x |
return(invisible(self)) |
276 |
}, |
|
277 | ||
278 |
# ___ pull ==== |
|
279 |
#' @description |
|
280 |
#' Pull the data (and metadata if it is a `Callable`) |
|
281 |
#' |
|
282 |
#' Read or create data using `pull_callable` specified in the constructor. |
|
283 |
#' |
|
284 |
#' @param args (`NULL` or named `list`)\cr |
|
285 |
#' additional dynamic arguments for pull function. `args` can be omitted if `pull_callable` |
|
286 |
#' from constructor already contains all necessary arguments to pull data. One can try |
|
287 |
#' to execute `pull_callable` directly by `x$pull_callable$run()` or to get code using |
|
288 |
#' `x$pull_callable$get_code()`. `args` specified in pull are used temporary to get data but |
|
289 |
#' not saved in code. |
|
290 |
#' @param try (`logical` value)\cr |
|
291 |
#' whether perform function evaluation inside `try` clause |
|
292 |
#' |
|
293 |
#' @return (`self`) if successful. |
|
294 |
pull = function(args = NULL, try = FALSE) { |
|
295 | 118x |
logger::log_trace("TealDatasetConnector$pull pulling dataset: {self$get_dataname() }.") |
296 | 118x |
data <- private$pull_internal(args = args, try = try) |
297 | 116x |
if (!self$is_failed()) { |
298 |
# The first time object is pulled, private$dataset may be NULL if mutate method was never called |
|
299 | 115x |
has_dataset <- !is.null(private$dataset) |
300 | 115x |
if (has_dataset) { |
301 | 13x |
code_in_dataset <- private$dataset$get_code_class(nodeps = TRUE) |
302 | 13x |
vars_in_dataset <- private$dataset$get_vars() |
303 |
} |
|
304 | ||
305 | 115x |
pulled_metadata <- private$pull_metadata_internal() |
306 | 115x |
private$dataset <- dataset( |
307 | 115x |
dataname = self$get_dataname(), |
308 | 115x |
x = data, |
309 | 115x |
keys = character(0), # keys need to be set after mutate |
310 | 115x |
label = self$get_dataset_label(), |
311 | 115x |
code = private$get_pull_code_class(), |
312 | 115x |
metadata = pulled_metadata |
313 |
) |
|
314 | ||
315 | 115x |
if (has_dataset) { |
316 | 13x |
private$dataset$mutate( |
317 | 13x |
code = code_in_dataset, |
318 | 13x |
vars = vars_in_dataset |
319 |
) |
|
320 |
} |
|
321 | 115x |
set_keys(private$dataset, self$get_keys()) |
322 | 115x |
private$is_pulled_flag <- TRUE |
323 | 115x |
logger::log_trace("TealDatasetConnector$pull pulled dataset: {self$get_dataname() }.") |
324 |
} else { |
|
325 | 1x |
logger::log_error("TealDatasetConnector$pull failed to pull dataset: {self$get_dataname() }.") |
326 |
} |
|
327 | ||
328 | 116x |
return(invisible(self)) |
329 |
}, |
|
330 |
#' @description |
|
331 |
#' Set arguments to the pulling function |
|
332 |
#' |
|
333 |
#' @param args (`NULL` or named `list`) dynamic arguments to function |
|
334 |
#' |
|
335 |
#' @return (`self`) invisibly for chaining |
|
336 |
set_args = function(args) { |
|
337 | 1x |
set_args(private$pull_callable, args) |
338 | 1x |
logger::log_trace("TealDatasetConnector$set_args pull args set for dataset: {self$get_dataname() }.") |
339 | 1x |
return(invisible(self)) |
340 |
}, |
|
341 | ||
342 |
# ___ mutate ==== |
|
343 |
#' @description |
|
344 |
#' Dispatcher for either eager or delayed mutate methods |
|
345 |
#' |
|
346 |
#' Either code or script must be provided, but not both. |
|
347 |
#' |
|
348 |
#' @return (`self`) invisibly for chaining. |
|
349 |
mutate = function(code, vars = list()) { |
|
350 | 46x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
351 | ||
352 | 46x |
if (is.null(private$dataset)) { |
353 |
# just needs a dummy TealDataset object to store mutate code, hence col = 1 |
|
354 | 11x |
private$dataset <- TealDataset$new(dataname = self$get_dataname(), x = data.frame(col = 1)) |
355 |
} |
|
356 | 46x |
private$dataset$mutate(code = code, vars = vars, force_delay = !self$is_pulled()) |
357 |
# should be called at the end so that failure in TealDataset object will prevent it. |
|
358 | 45x |
private$set_var_r6(vars) |
359 | 45x |
logger::log_trace( |
360 | 45x |
sprintf( |
361 | 45x |
"TealDatasetConnector$mutate mutated dataset '%s' using the code (%s lines) and vars (%s).", |
362 | 45x |
self$get_dataname(), |
363 | 45x |
length(parse(text = if (inherits(code, "CodeClass")) code$get_code() else code)), |
364 | 45x |
paste(names(vars), collapse = ", ") |
365 |
) |
|
366 |
) |
|
367 | ||
368 | ||
369 | 45x |
return(invisible(self)) |
370 |
}, |
|
371 | ||
372 |
# ___ status ==== |
|
373 |
#' @description |
|
374 |
#' Check if pull has not failed. |
|
375 |
#' |
|
376 |
#' @return `TRUE` if pull failed, else `FALSE` |
|
377 |
is_failed = function() { |
|
378 | 143x |
return(private$pull_callable$is_failed()) |
379 |
}, |
|
380 |
#' @description |
|
381 |
#' Check if dataset has already been pulled. |
|
382 |
#' |
|
383 |
#' @return `TRUE` if connector has been already pulled, else `FALSE` |
|
384 |
is_pulled = function() { |
|
385 | 678x |
private$is_pulled_flag |
386 |
}, |
|
387 |
#' @description |
|
388 |
#' Check if dataset has mutations that are delayed |
|
389 |
#' |
|
390 |
#' @return `logical` |
|
391 |
is_mutate_delayed = function() { |
|
392 | 58x |
if (is.null(private$dataset)) { |
393 | 2x |
FALSE |
394 |
} else { |
|
395 | 56x |
private$dataset$is_mutate_delayed() |
396 |
} |
|
397 |
}, |
|
398 | ||
399 |
# ___ check ==== |
|
400 |
#' @description |
|
401 |
#' Check to determine if the raw data is reproducible from the |
|
402 |
#' `get_code()` code. |
|
403 |
#' @return |
|
404 |
#' `TRUE` always for all connectors to avoid evaluating the same code multiple times. |
|
405 |
check = function() { |
|
406 | 10x |
return(TRUE) |
407 |
}, |
|
408 |
# ___ shiny ==== |
|
409 |
#' @description |
|
410 |
#' Sets the shiny UI according to the given inputs. |
|
411 |
#' Inputs must provide only scalar (length of 1) variables. |
|
412 |
#' @param inputs (`function`) A shiny module UI function with single argument `ns`. |
|
413 |
#' This function needs to return a list of shiny inputs with their `inputId` wrapped |
|
414 |
#' in function `ns`. The `inputId` must match exactly the argument name to be set. |
|
415 |
#' See example. |
|
416 |
#' Nested lists are not allowed. |
|
417 |
#' @return (`self`) invisibly for chaining. |
|
418 |
#' @examples |
|
419 |
#' ds <- dataset_connector("xyz", pull_callable = callable_function(data.frame)) |
|
420 |
#' ds$set_ui_input( |
|
421 |
#' function(ns) { |
|
422 |
#' list(sliderInput(ns("colA"), "Select value for colA", min = 0, max = 10, value = 3), |
|
423 |
#' sliderInput(ns("colB"), "Select value for colB", min = 0, max = 10, value = 7)) |
|
424 |
#' } |
|
425 |
#' ) |
|
426 |
#' \dontrun{ |
|
427 |
#' ds$launch() |
|
428 |
#' } |
|
429 |
set_ui_input = function(inputs = NULL) { |
|
430 | 3x |
stopifnot(is.null(inputs) || is.function(inputs)) |
431 | 3x |
if (is.function(inputs)) { |
432 | 3x |
if (!identical(names(formals(inputs)), "ns")) { |
433 | ! |
stop("'inputs' must be a function of a single argument called 'ns'") |
434 |
} |
|
435 |
} |
|
436 | 3x |
private$ui_input <- inputs |
437 | 3x |
logger::log_trace( |
438 | 3x |
"TealDatasetConnector$set_ui_input ui_input set for dataset: { deparse1(self$get_dataname()) }." |
439 |
) |
|
440 | 3x |
return(invisible(self)) |
441 |
}, |
|
442 |
#' @description |
|
443 |
#' Get shiny `ui` function |
|
444 |
#' @param id (`character`) namespace id |
|
445 |
#' @return shiny UI in given namespace id |
|
446 |
get_ui = function(id) { |
|
447 | 2x |
checkmate::assert_string(id) |
448 | 2x |
if (!is.null(private$ui)) { |
449 | 2x |
private$ui(id) |
450 |
} |
|
451 |
}, |
|
452 |
#' @description |
|
453 |
#' Get shiny server function |
|
454 |
#' @return shiny server function |
|
455 |
get_server = function() { |
|
456 | ! |
return(private$server) |
457 |
}, |
|
458 |
#' @description |
|
459 |
#' Launches a shiny app. |
|
460 |
#' @return Shiny app |
|
461 |
#' @examples |
|
462 |
#' ds <- dataset_connector("xyz", pull_callable = callable_function(data.frame)) |
|
463 |
#' ds$set_ui_input( |
|
464 |
#' function(ns) { |
|
465 |
#' list(sliderInput(ns("colA"), "Select value for colA", min = 0, max = 10, value = 3), |
|
466 |
#' sliderInput(ns("colB"), "Select value for colB", min = 0, max = 10, value = 7)) |
|
467 |
#' } |
|
468 |
#' ) |
|
469 |
#' \dontrun{ |
|
470 |
#' ds$launch() |
|
471 |
#' } |
|
472 |
launch = function() { |
|
473 | ! |
if (is.null(private$server)) { |
474 | ! |
stop("No arguments set yet. Please use set_ui_input method first.") |
475 |
} |
|
476 | ! |
shinyApp( |
477 | ! |
ui = fluidPage( |
478 | ! |
theme = get_teal_bs_theme(), |
479 | ! |
self$get_ui(id = "main_app"), |
480 | ! |
shinyjs::useShinyjs(), |
481 | ! |
br(), |
482 | ! |
actionButton("pull", "Get data"), |
483 | ! |
br(), |
484 | ! |
tableOutput("result") |
485 |
), |
|
486 | ! |
server = function(input, output, session) { |
487 | ! |
session$onSessionEnded(stopApp) |
488 | ! |
observeEvent(input$pull, { |
489 | ! |
self$get_server()(id = "main_app") |
490 | ! |
if (self$is_pulled()) { |
491 | ! |
output$result <- renderTable(head(self$get_raw_data())) |
492 |
} |
|
493 |
}) |
|
494 |
} |
|
495 |
) |
|
496 |
} |
|
497 |
), |
|
498 |
## __Private Fields ==== |
|
499 |
private = list( |
|
500 |
dataset = NULL, # TealDataset |
|
501 |
pull_callable = NULL, # Callable |
|
502 |
pull_vars = list(), # named list |
|
503 |
dataname = character(0), |
|
504 |
dataset_label = character(0), |
|
505 |
metadata = NULL, # Callable or list |
|
506 |
keys = NULL, |
|
507 |
var_r6 = list(), |
|
508 |
ui_input = NULL, # NULL or list |
|
509 |
is_pulled_flag = FALSE, |
|
510 | ||
511 |
## __Private Methods ==== |
|
512 |
ui = function(id) { |
|
513 | 2x |
ns <- NS(id) |
514 |
# add namespace to input ids |
|
515 | 2x |
ui <- if (!is.null(private$ui_input)) { |
516 | 1x |
do.call(private$ui_input, list(ns = ns)) |
517 |
} else { |
|
518 | 1x |
NULL |
519 |
} |
|
520 |
# check ui inputs |
|
521 | 2x |
if (!is.null(ui)) { |
522 | 1x |
checkmate::assert_list(ui, types = "shiny.tag") |
523 | 1x |
attr_class <- vapply(lapply(ui, "[[", i = "attribs"), "[[", character(1), i = "class") |
524 | 1x |
if (!all(grepl("shiny-input-container", attr_class))) { |
525 | ! |
stop("All elements must be shiny inputs") |
526 |
} |
|
527 |
} |
|
528 |
# create ui |
|
529 | 2x |
if (!is.null(ui)) { |
530 | 1x |
tags$div( |
531 | 1x |
tags$div( |
532 | 1x |
id = ns("inputs"), |
533 | 1x |
h4("TealDataset Connector for ", code(self$get_dataname())), |
534 | 1x |
ui |
535 |
) |
|
536 |
) |
|
537 |
} |
|
538 |
}, |
|
539 |
server = function(id, data_args = NULL) { |
|
540 | ! |
moduleServer( |
541 | ! |
id = id, |
542 | ! |
function(input, output, session) { |
543 | ! |
withProgress(value = 1, message = paste("Pulling", self$get_dataname()), { |
544 |
# set args to save them - args set will be returned in the call |
|
545 | ! |
dataset_args <- if (!is.null(private$ui_input)) { |
546 | ! |
reactiveValuesToList(input) |
547 |
} else { |
|
548 | ! |
NULL |
549 |
} |
|
550 | ! |
if (length(dataset_args) > 0) { |
551 | ! |
self$set_args(args = dataset_args) |
552 |
} |
|
553 | ||
554 | ! |
self$pull(args = data_args, try = TRUE) |
555 | ||
556 |
# print error if any |
|
557 |
# error doesn't break an app |
|
558 | ! |
if (self$is_failed()) { |
559 | ! |
shinyjs::alert( |
560 | ! |
sprintf( |
561 | ! |
"Error pulling %s:\nError message: %s", |
562 | ! |
self$get_dataname(), |
563 | ! |
self$get_error_message() |
564 |
) |
|
565 |
) |
|
566 |
} |
|
567 |
}) |
|
568 |
} |
|
569 |
) |
|
570 | ! |
return(invisible(self)) |
571 |
}, |
|
572 | ||
573 |
# need to have a custom deep_clone because one of the key fields are reference-type object |
|
574 |
# in particular: dataset is a R6 object that wouldn't be cloned using default clone(deep = T) |
|
575 |
deep_clone = function(name, value) { |
|
576 | 208x |
deep_clone_r6(name, value) |
577 |
}, |
|
578 |
get_pull_code_class = function(args = NULL) { |
|
579 | 301x |
res <- CodeClass$new() |
580 | 301x |
res$append(list_to_code_class(private$pull_vars)) |
581 | 301x |
code <- if (inherits(private$pull_callable, "CallableCode")) { |
582 | 21x |
tmp <- private$pull_callable$get_call(deparse = FALSE) |
583 | 21x |
tmp[[length(tmp)]] <- substitute(a <- b, list(a = as.name(private$dataname), b = tmp[[length(tmp)]])) |
584 | 21x |
paste0(vapply(tmp, deparse1, character(1), collapse = "\n"), collapse = "\n") |
585 |
} else { |
|
586 | 280x |
deparse1(substitute( |
587 | 280x |
a <- b, |
588 | 280x |
list( |
589 | 280x |
a = as.name(private$dataname), |
590 | 280x |
b = private$pull_callable$get_call(deparse = FALSE, args = args) |
591 |
) |
|
592 | 280x |
), collapse = "\n") |
593 |
} |
|
594 | ||
595 | 301x |
res$set_code(code = code, dataname = private$dataname, deps = names(private$pull_vars)) |
596 | 301x |
return(res) |
597 |
}, |
|
598 |
set_pull_callable = function(pull_callable) { |
|
599 | 181x |
stopifnot(inherits(pull_callable, "Callable")) |
600 | 181x |
private$pull_callable <- pull_callable |
601 | 181x |
return(invisible(self)) |
602 |
}, |
|
603 |
set_metadata = function(metadata) { |
|
604 | 181x |
if (inherits(metadata, "Callable")) { |
605 | 4x |
private$metadata <- metadata |
606 |
} else { |
|
607 | 177x |
validate_metadata(metadata) |
608 | 177x |
private$metadata <- metadata |
609 |
} |
|
610 | 181x |
return(invisible(self)) |
611 |
}, |
|
612 |
set_pull_vars = function(pull_vars) { |
|
613 | 181x |
checkmate::assert_list(pull_vars, min.len = 0, names = "unique") |
614 | 181x |
private$pull_vars <- pull_vars |
615 | 181x |
return(invisible(self)) |
616 |
}, |
|
617 |
pull_metadata_internal = function() { |
|
618 | 115x |
if (!checkmate::test_class(private$metadata, "Callable")) { |
619 | 112x |
return(private$metadata) |
620 |
} |
|
621 | ||
622 | 3x |
logger::log_trace("TealDatasetConnector$pull pulling metadata for dataset: {self$get_dataname() }.") |
623 | 3x |
pulled_metadata <- private$metadata$run(try = TRUE) |
624 | ||
625 | 3x |
if (checkmate::test_class(pulled_metadata, c("simpleError", "error"))) { |
626 | 1x |
logger::log_warn("TealDatasetConnector$pull pulling metadata failed for dataset: {self$get_dataname() }.") |
627 | 1x |
return(NULL) |
628 |
} |
|
629 | ||
630 |
# metadata pulled, now lets make sure it is valid |
|
631 | 2x |
tryCatch( |
632 |
{ |
|
633 | 2x |
pulled_metadata <- as.list(pulled_metadata) |
634 | 2x |
validate_metadata(pulled_metadata) |
635 | 1x |
logger::log_trace("TealDatasetConnector$pull pulled metadata for dataset: {self$get_dataname() }.") |
636 | 1x |
return(pulled_metadata) |
637 |
}, |
|
638 | 2x |
error = function(e) { |
639 | 1x |
logger::log_warn("TealDatasetConnector$pull invalid metadata for dataset: {self$get_dataname() }.") |
640 | 1x |
return(NULL) |
641 |
} |
|
642 |
) |
|
643 |
}, |
|
644 |
pull_internal = function(args = NULL, try = FALSE) { |
|
645 |
# include objects CallableFunction environment |
|
646 | 118x |
if (length(private$pull_vars) > 0) { |
647 | 57x |
for (var_idx in seq_along(private$pull_vars)) { |
648 | 57x |
var_name <- names(private$pull_vars)[[var_idx]] |
649 | 57x |
var_value <- private$pull_vars[[var_idx]] |
650 | ||
651 |
# assignment is done in pull_callable only once |
|
652 |
# because x is locked within local environment |
|
653 |
# this means that re-assignment is not possible and will be silently skipped |
|
654 |
# During the app loading, assign is called only once. |
|
655 | 57x |
private$pull_callable$assign_to_env( |
656 | 57x |
x = var_name, |
657 | 57x |
value = if (inherits(var_value, "TealDatasetConnector") || inherits(var_value, "TealDataset")) { |
658 | 48x |
get_raw_data(var_value) |
659 |
} else { |
|
660 | ! |
var_value |
661 |
} |
|
662 |
) |
|
663 |
} |
|
664 |
} |
|
665 |
# eval CallableFunction with dynamic args |
|
666 | 118x |
tryCatch( |
667 | 118x |
expr = private$pull_callable$run(args = args, try = try), |
668 | 118x |
error = function(e) { |
669 | 2x |
if (grepl("object 'conn' not found", e$message)) { |
670 | ! |
output_message <- "This dataset connector requires connection object (conn) to be provided." |
671 |
} else { |
|
672 | 2x |
output_message <- paste("Could not pull dataset, the following error message was returned:", e$message) |
673 |
} |
|
674 | 2x |
stop(output_message, call. = FALSE) |
675 |
} |
|
676 |
) |
|
677 |
}, |
|
678 |
set_failure = function(res) { |
|
679 | ! |
if (inherits(res, "error")) { |
680 | ! |
private$failed <- TRUE |
681 | ! |
private$failure_msg <- conditionMessage(res) |
682 |
} else { |
|
683 | ! |
private$failed <- FALSE |
684 | ! |
private$failure_msg <- NULL |
685 |
} |
|
686 | ! |
return(NULL) |
687 |
}, |
|
688 |
set_var_r6 = function(vars) { |
|
689 | 226x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
690 | 226x |
for (varname in names(vars)) { |
691 | 91x |
var <- vars[[varname]] |
692 | ||
693 | 91x |
if (inherits(var, "TealDatasetConnector") || inherits(var, "TealDataset")) { |
694 | 80x |
var_deps <- var$get_var_r6() |
695 | 80x |
var_deps[[varname]] <- var |
696 | 80x |
for (var_dep_name in names(var_deps)) { |
697 | 85x |
var_dep <- var_deps[[var_dep_name]] |
698 | 85x |
if (identical(self, var_dep)) { |
699 | ! |
stop("Circular dependencies detected") |
700 |
} |
|
701 | 85x |
private$var_r6[[var_dep_name]] <- var_dep |
702 |
} |
|
703 |
} |
|
704 |
} |
|
705 | 226x |
return(invisible(self)) |
706 |
}, |
|
707 |
set_dataname = function(dataname) { |
|
708 | 181x |
checkmate::assert_string(dataname) |
709 | 181x |
stopifnot(!grepl("\\s", dataname)) |
710 | 181x |
private$dataname <- dataname |
711 | 181x |
return(invisible(self)) |
712 |
}, |
|
713 |
set_ui = function(ui_args = NULL) { |
|
714 | ! |
private$ui <- function(id) { |
715 | ! |
ns <- NS(id) |
716 |
# add namespace to input ids |
|
717 | ! |
ui <- if (!is.null(ui_args)) { |
718 | ! |
do.call(ui_args, list(ns = ns)) |
719 |
} else { |
|
720 | ! |
NULL |
721 |
} |
|
722 |
# check ui inputs |
|
723 | ! |
if (!is.null(ui)) { |
724 | ! |
checkmate::assert_list(ui, types = "shiny.tag") |
725 | ! |
attr_class <- vapply(lapply(ui, "[[", i = "attribs"), "[[", character(1), i = "class") |
726 | ! |
if (!all(grepl("shiny-input-container", attr_class))) { |
727 | ! |
stop("All elements must be shiny inputs") |
728 |
} |
|
729 |
} |
|
730 |
# create ui |
|
731 | ! |
if (!is.null(ui)) { |
732 | ! |
tags$div( |
733 | ! |
tags$div( |
734 | ! |
id = ns("inputs"), |
735 | ! |
h4("TealDataset Connector for ", code(self$get_dataname())), |
736 | ! |
ui |
737 |
) |
|
738 |
) |
|
739 |
} |
|
740 |
} |
|
741 | ! |
return(invisible(self)) |
742 |
} |
|
743 |
) |
|
744 |
) |
1 |
#' Create a new `TealDatasetConnector` object |
|
2 |
#' |
|
3 |
#' `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' Create `TealDatasetConnector` from [callable_function]. |
|
6 |
#' |
|
7 |
#' @param dataname (`character`)\cr |
|
8 |
#' A given name for the dataset it may not contain spaces |
|
9 |
#' |
|
10 |
#' @param pull_callable (`CallableFunction`)\cr |
|
11 |
#' function with necessary arguments set to fetch data from connection. |
|
12 |
#' |
|
13 |
#' @param keys optional, (`character`)\cr |
|
14 |
#' vector of dataset primary keys column names |
|
15 |
#' |
|
16 |
#' @param label (`character`)\cr |
|
17 |
#' Label to describe the dataset. |
|
18 |
#' |
|
19 |
#' @param code (`character`)\cr |
|
20 |
#' A character string defining code to modify `raw_data` from this dataset. To modify |
|
21 |
#' current dataset code should contain at least one assignment to object defined in `dataname` |
|
22 |
#' argument. For example if `dataname = ADSL` example code should contain |
|
23 |
#' `ADSL <- <some R code>`. Can't be used simultaneously with `script` |
|
24 |
#' |
|
25 |
#' @param script (`character`)\cr |
|
26 |
#' Alternatively to `code` - location of the file containing modification code. |
|
27 |
#' Can't be used simultaneously with `script`. |
|
28 |
#' |
|
29 |
#' @param vars (named `list`)) \cr |
|
30 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
31 |
#' other constant value, this/these object(s) should be included as named |
|
32 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
33 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
34 |
#' It's recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
35 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
36 |
#' are included to this object as local `vars` and they cannot be modified |
|
37 |
#' within another dataset. |
|
38 |
#' |
|
39 |
#' @param metadata (named `list`, `NULL` or `CallableFunction`) \cr |
|
40 |
#' Field containing either the metadata about the dataset (each element of the list |
|
41 |
#' should be atomic and length one) or a `CallableFuntion` to pull the metadata |
|
42 |
#' from a connection. This should return a `list` or an object which can be |
|
43 |
#' converted to a list with `as.list`. |
|
44 |
#' @return new `TealDatasetConnector` object |
|
45 |
#' |
|
46 |
#' @examples |
|
47 |
#' library(MultiAssayExperiment) |
|
48 |
#' # data.frame example |
|
49 |
#' pull_fun2 <- callable_function(data.frame) |
|
50 |
#' pull_fun2$set_args(args = list(a = c(1, 2, 3))) |
|
51 |
#' dataset_connector("test", pull_fun2) |
|
52 |
#' |
|
53 |
#' # MultiAssayExperiment example |
|
54 |
#' pull_fun <- callable_function( |
|
55 |
#' function() { |
|
56 |
#' library("MultiAssayExperiment") |
|
57 |
#' data("miniACC") |
|
58 |
#' return(miniACC) |
|
59 |
#' } |
|
60 |
#' ) |
|
61 |
#' dataset_connector( |
|
62 |
#' "miniacc", |
|
63 |
#' pull_fun, |
|
64 |
#' code = 'library("MultiAssayExperiment"); data("miniACC"); return(miniACC)' |
|
65 |
#' ) |
|
66 |
#' @export |
|
67 |
dataset_connector <- function(dataname, |
|
68 |
pull_callable, |
|
69 |
keys = character(0), |
|
70 |
label = character(0), |
|
71 |
code = character(0), |
|
72 |
script = character(0), |
|
73 |
vars = list(), |
|
74 |
metadata = NULL) { |
|
75 | 112x |
checkmate::assert_string(dataname) |
76 | 111x |
stopifnot(inherits(pull_callable, "Callable")) |
77 | 111x |
checkmate::assert_character(keys, any.missing = FALSE) |
78 | 111x |
checkmate::assert_character(code, any.missing = FALSE) |
79 | 111x |
checkmate::assert_character(label, any.missing = FALSE) |
80 | ||
81 | 111x |
if (!checkmate::test_class(metadata, "Callable", null.ok = TRUE)) { |
82 | 14x |
validate_metadata(metadata) |
83 |
} |
|
84 | ||
85 | 111x |
x <- TealDatasetConnector$new( |
86 | 111x |
dataname = dataname, |
87 | 111x |
pull_callable = pull_callable, |
88 | 111x |
keys = keys, |
89 | 111x |
code = code_from_script(code, script), |
90 | 111x |
label = label, |
91 | 111x |
vars = vars, |
92 | 111x |
metadata = metadata |
93 |
) |
|
94 | ||
95 | 111x |
return(x) |
96 |
} |
|
97 | ||
98 |
#' Create a new `CDISCTealDatasetConnector` object |
|
99 |
#' |
|
100 |
#' `r lifecycle::badge("stable")` |
|
101 |
#' |
|
102 |
#' Create `CDISCTealDatasetConnector` from [callable_function]. |
|
103 |
#' |
|
104 |
#' @inheritParams dataset_connector |
|
105 |
#' @inheritParams cdisc_dataset |
|
106 |
#' |
|
107 |
#' @return new `CDISCTealDatasetConnector` object |
|
108 |
#' |
|
109 |
#' @export |
|
110 |
cdisc_dataset_connector <- function(dataname, |
|
111 |
pull_callable, |
|
112 |
keys, |
|
113 |
parent = `if`(identical(dataname, "ADSL"), character(0), "ADSL"), |
|
114 |
label = character(0), |
|
115 |
code = character(0), |
|
116 |
script = character(0), |
|
117 |
vars = list(), |
|
118 |
metadata = NULL) { |
|
119 | 31x |
checkmate::assert_string(dataname) |
120 | 31x |
stopifnot(inherits(pull_callable, "Callable")) |
121 | 31x |
checkmate::assert_character(keys, any.missing = FALSE) |
122 | 31x |
checkmate::assert_character(parent, max.len = 1, any.missing = FALSE) |
123 | 31x |
checkmate::assert_character(code, max.len = 1, any.missing = FALSE) |
124 | 31x |
checkmate::assert_character(label, max.len = 1, any.missing = FALSE) |
125 | ||
126 | 31x |
if (!checkmate::test_class(metadata, "Callable", null.ok = TRUE)) { |
127 | 11x |
validate_metadata(metadata) |
128 |
} |
|
129 | ||
130 | 31x |
x <- CDISCTealDatasetConnector$new( |
131 | 31x |
dataname = dataname, |
132 | 31x |
pull_callable = pull_callable, |
133 | 31x |
keys = keys, |
134 | 31x |
parent = parent, |
135 | 31x |
code = code_from_script(code, script), |
136 | 31x |
label = label, |
137 | 31x |
vars = vars, |
138 | 31x |
metadata = metadata |
139 |
) |
|
140 | ||
141 | 31x |
return(x) |
142 |
} |
|
143 | ||
144 | ||
145 |
#' Load `TealDatasetConnector` object from a file |
|
146 |
#' |
|
147 |
#' `r lifecycle::badge("stable")` |
|
148 |
#' |
|
149 |
#' Please note that the script has to end with a call creating desired object. The error will |
|
150 |
#' be raised otherwise. |
|
151 |
#' |
|
152 |
#' @inheritParams dataset_file |
|
153 |
#' |
|
154 |
#' @return `TealDatasetConnector` object |
|
155 |
#' |
|
156 |
#' @rdname dataset_connector_file |
|
157 |
#' |
|
158 |
#' @export |
|
159 |
#' |
|
160 |
#' @examples |
|
161 |
#' # simple example |
|
162 |
#' library(magrittr) |
|
163 |
#' file_example <- tempfile(fileext = ".R") |
|
164 |
#' writeLines( |
|
165 |
#' text = c( |
|
166 |
#' "library(teal.data) |
|
167 |
#' |
|
168 |
#' pull_callable <- callable_function(teal.data::example_cdisc_data) %>% |
|
169 |
#' set_args(list(dataname = \"ADSL\")) |
|
170 |
#' dataset_connector(\"ADSL\", pull_callable, get_cdisc_keys(\"ADSL\"))" |
|
171 |
#' ), |
|
172 |
#' con = file_example |
|
173 |
#' ) |
|
174 |
#' x <- dataset_connector_file(file_example) |
|
175 |
#' get_code(x) |
|
176 |
dataset_connector_file <- function(path) { # nolint |
|
177 | ! |
object <- object_file(path, "TealDatasetConnector") |
178 | ! |
return(object) |
179 |
} |
|
180 | ||
181 |
#' Load `CDISCTealDatasetConnector` object from a file |
|
182 |
#' |
|
183 |
#' `r lifecycle::badge("stable")` |
|
184 |
#' |
|
185 |
#' Please note that the script has to end with a call creating desired object. The error will |
|
186 |
#' be raised otherwise. |
|
187 |
#' |
|
188 |
#' @inheritParams dataset_connector_file |
|
189 |
#' |
|
190 |
#' @return `CDISCTealDatasetConnector` object |
|
191 |
#' |
|
192 |
#' @rdname dataset_connector_file |
|
193 |
#' |
|
194 |
#' @export |
|
195 |
#' |
|
196 |
#' @examples |
|
197 |
#' # simple example |
|
198 |
#' library(magrittr) |
|
199 |
#' file_example <- tempfile(fileext = ".R") |
|
200 |
#' writeLines( |
|
201 |
#' text = c( |
|
202 |
#' "library(teal.data) |
|
203 |
#' |
|
204 |
#' pull_callable <- callable_function(teal.data::example_cdisc_data) %>% |
|
205 |
#' set_args(list(dataname = \"ADSL\")) |
|
206 |
#' cdisc_dataset_connector(\"ADSL\", pull_callable, get_cdisc_keys(\"ADSL\"))" |
|
207 |
#' ), |
|
208 |
#' con = file_example |
|
209 |
#' ) |
|
210 |
#' x <- cdisc_dataset_connector_file(file_example) |
|
211 |
#' get_code(x) |
|
212 |
cdisc_dataset_connector_file <- function(path) { # nolint |
|
213 | ! |
object <- object_file(path, "CDISCTealDatasetConnector") |
214 | ! |
return(object) |
215 |
} |
|
216 | ||
217 |
# RDS ==== |
|
218 |
#' `RDS` `TealDatasetConnector` |
|
219 |
#' |
|
220 |
#' `r lifecycle::badge("stable")` |
|
221 |
#' |
|
222 |
#' Create a `TealDatasetConnector` from `RDS` file. |
|
223 |
#' |
|
224 |
#' @inheritParams dataset_connector |
|
225 |
#' @inheritParams fun_dataset_connector |
|
226 |
#' @param file (`character`)\cr |
|
227 |
#' path to (`.rds` or `.R`) that contains `data.frame` object or |
|
228 |
#' code to `source` |
|
229 |
#' |
|
230 |
#' @param ... (`optional`)\cr |
|
231 |
#' additional arguments applied to [base::readRDS()] function |
|
232 |
#' |
|
233 |
#' @export |
|
234 |
#' |
|
235 |
#' @rdname rds_dataset_connector |
|
236 |
#' |
|
237 |
#' @examples |
|
238 |
#' \dontrun{ |
|
239 |
#' x <- rds_dataset_connector( |
|
240 |
#' dataname = "ADSL", |
|
241 |
#' file = "path/to/file.RDS" |
|
242 |
#' ) |
|
243 |
#' x$get_code() |
|
244 |
#' } |
|
245 |
rds_dataset_connector <- function(dataname, |
|
246 |
file, |
|
247 |
keys = character(0), |
|
248 |
label = character(0), |
|
249 |
code = character(0), |
|
250 |
script = character(0), |
|
251 |
metadata = list(type = "rds", file = file), |
|
252 |
...) { |
|
253 | 4x |
dot_args <- list(...) |
254 | 4x |
checkmate::assert_list(dot_args, min.len = 0, names = "unique") |
255 | 4x |
checkmate::assert_string(file) |
256 | 4x |
if (!file.exists(file)) { |
257 | 1x |
stop("File ", file, " does not exist.", call. = FALSE) |
258 |
} |
|
259 | ||
260 | 3x |
x_fun <- callable_function(readRDS) # nolint |
261 | 3x |
args <- c(list(file = file), dot_args) |
262 | 3x |
x_fun$set_args(args) |
263 | ||
264 | 3x |
x <- dataset_connector( |
265 | 3x |
dataname = dataname, |
266 | 3x |
pull_callable = x_fun, |
267 | 3x |
keys = keys, |
268 | 3x |
label = label, |
269 | 3x |
code = code_from_script(code, script), |
270 | 3x |
metadata = metadata |
271 |
) |
|
272 | ||
273 | 3x |
return(x) |
274 |
} |
|
275 | ||
276 |
#' `RDS` `CDSICTealDatasetConnector` |
|
277 |
#' |
|
278 |
#' `r lifecycle::badge("stable")` |
|
279 |
#' |
|
280 |
#' Create a `CDSICTealDatasetConnector` from `RDS` file with keys automatically |
|
281 |
#' assigned by `dataname` |
|
282 |
#' |
|
283 |
#' @inheritParams rds_dataset_connector |
|
284 |
#' @inheritParams cdisc_dataset_connector |
|
285 |
#' |
|
286 |
#' @rdname rds_dataset_connector |
|
287 |
#' |
|
288 |
#' @export |
|
289 |
rds_cdisc_dataset_connector <- function(dataname, |
|
290 |
file, |
|
291 |
keys = get_cdisc_keys(dataname), |
|
292 |
parent = `if`(identical(dataname, "ADSL"), character(0L), "ADSL"), |
|
293 |
label = character(0), |
|
294 |
code = character(0), |
|
295 |
script = character(0), |
|
296 |
metadata = list(type = "rds", file = file), |
|
297 |
...) { |
|
298 | 2x |
x <- rds_dataset_connector( |
299 | 2x |
dataname = dataname, |
300 | 2x |
file = file, |
301 | 2x |
keys = keys, |
302 | 2x |
code = code_from_script(code, script), |
303 | 2x |
label = label, |
304 | 2x |
metadata = metadata, |
305 |
... |
|
306 |
) |
|
307 | ||
308 | 1x |
res <- as_cdisc( |
309 | 1x |
x, |
310 | 1x |
parent = parent |
311 |
) |
|
312 | ||
313 | 1x |
return(res) |
314 |
} |
|
315 | ||
316 | ||
317 |
# SCRIPT ==== |
|
318 |
#' Script `TealDatasetConnector` |
|
319 |
#' |
|
320 |
#' `r lifecycle::badge("stable")` |
|
321 |
#' |
|
322 |
#' Create a `TealDatasetConnector` from `.R` file. |
|
323 |
#' |
|
324 |
#' @inheritParams dataset_connector |
|
325 |
#' @inheritParams fun_dataset_connector |
|
326 |
#' @param file (`character`)\cr |
|
327 |
#' file location containing code to be evaluated in connector. Object obtained in the last |
|
328 |
#' call from file will be returned to the connector - same as `source(file = file)$value` |
|
329 |
#' |
|
330 |
#' @export |
|
331 |
#' |
|
332 |
#' @rdname script_dataset_connector |
|
333 |
#' |
|
334 |
#' @examples |
|
335 |
#' \dontrun{ |
|
336 |
#' x <- script_dataset_connector( |
|
337 |
#' dataname = "ADSL", |
|
338 |
#' file = "path/to/script.R", |
|
339 |
#' keys = get_cdisc_keys("ADSL") |
|
340 |
#' ) |
|
341 |
#' x$get_code() |
|
342 |
#' } |
|
343 |
script_dataset_connector <- function(dataname, |
|
344 |
file, |
|
345 |
keys = character(0), |
|
346 |
label = character(0), |
|
347 |
code = character(0), |
|
348 |
script = character(0), |
|
349 |
metadata = NULL, |
|
350 |
...) { |
|
351 | 4x |
vars <- list(...) |
352 | 4x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
353 | 4x |
checkmate::assert_string(file) |
354 | 4x |
if (!file.exists(file)) { |
355 | 1x |
stop("File ", file, " does not exist.", call. = FALSE) |
356 |
} |
|
357 | ||
358 | 3x |
x_fun <- callable_function(source) # nolint |
359 | 3x |
x_fun$set_args(list(file = file, local = TRUE)) |
360 | ||
361 | 3x |
x <- dataset_connector( |
362 | 3x |
dataname = dataname, |
363 | 3x |
pull_callable = x_fun, |
364 | 3x |
keys = keys, |
365 | 3x |
label = label, |
366 | 3x |
code = code_from_script(code, script), |
367 | 3x |
vars = vars, |
368 | 3x |
metadata = metadata |
369 |
) |
|
370 | ||
371 | 3x |
return(x) |
372 |
} |
|
373 | ||
374 |
#' Script `CDISCTealDatasetConnector` |
|
375 |
#' |
|
376 |
#' `r lifecycle::badge("stable")` |
|
377 |
#' |
|
378 |
#' Create a `CDISCTealDatasetConnector` from `script` file with keys assigned |
|
379 |
#' automatically by `dataname`. |
|
380 |
#' |
|
381 |
#' @inheritParams script_dataset_connector |
|
382 |
#' @inheritParams cdisc_dataset_connector |
|
383 |
#' |
|
384 |
#' @rdname script_dataset_connector |
|
385 |
#' |
|
386 |
#' @export |
|
387 |
script_cdisc_dataset_connector <- function(dataname, |
|
388 |
file, |
|
389 |
keys = get_cdisc_keys(dataname), |
|
390 |
parent = `if`(identical(dataname, "ADSL"), character(0L), "ADSL"), |
|
391 |
label = character(0), |
|
392 |
code = character(0), |
|
393 |
script = character(0), |
|
394 |
metadata = NULL, |
|
395 |
...) { |
|
396 | 1x |
x <- script_dataset_connector( |
397 | 1x |
dataname = dataname, |
398 | 1x |
file = file, |
399 | 1x |
keys = keys, |
400 | 1x |
code = code_from_script(code, script), |
401 | 1x |
script = script, |
402 | 1x |
label = label, |
403 | 1x |
metadata = metadata, |
404 |
... |
|
405 |
) |
|
406 | ||
407 | 1x |
res <- as_cdisc( |
408 | 1x |
x, |
409 | 1x |
parent = parent |
410 |
) |
|
411 | ||
412 | 1x |
return(res) |
413 |
} |
|
414 | ||
415 | ||
416 |
# CODE ==== |
|
417 |
#' Code `TealDatasetConnector` |
|
418 |
#' |
|
419 |
#' `r lifecycle::badge("stable")` |
|
420 |
#' |
|
421 |
#' Create a `TealDatasetConnector` from a string of code. |
|
422 |
#' |
|
423 |
#' @inheritParams dataset_connector |
|
424 |
#' @inheritParams fun_dataset_connector |
|
425 |
#' |
|
426 |
#' @param code (`character`)\cr |
|
427 |
#' String containing the code to produce the object. |
|
428 |
#' The code must end in a call to the object. |
|
429 |
#' @param mutate_code (`character`)\cr |
|
430 |
#' String containing the code used to mutate the object |
|
431 |
#' after it is produced. |
|
432 |
#' @param mutate_script (`character`)\cr |
|
433 |
#' Alternatively to `mutate_code` - location of the file containing modification code. |
|
434 |
#' Can't be used simultaneously with `mutate_script`. |
|
435 |
#' |
|
436 |
#' @export |
|
437 |
#' |
|
438 |
#' @rdname code_dataset_connector |
|
439 |
#' |
|
440 |
#' @examples |
|
441 |
#' x <- code_dataset_connector( |
|
442 |
#' dataname = "ADSL", |
|
443 |
#' keys = get_cdisc_keys("ADSL"), |
|
444 |
#' code = "ADSL <- teal.data::example_cdisc_data(\"ADSL\"); ADSL" |
|
445 |
#' ) |
|
446 |
#' |
|
447 |
#' x$get_code() |
|
448 |
#' |
|
449 |
#' mutate_dataset(x, code = "ADSL$new_variable <- 1") |
|
450 |
#' x$get_code() |
|
451 |
#' |
|
452 |
#' file_example <- tempfile(fileext = ".R") |
|
453 |
#' writeLines( |
|
454 |
#' text = c( |
|
455 |
#' "seed <- 1; ADSL <- radsl(cached = TRUE, seed = seed)\nADSL" |
|
456 |
#' ), |
|
457 |
#' con = file_example |
|
458 |
#' ) |
|
459 |
#' |
|
460 |
#' y <- code_dataset_connector( |
|
461 |
#' dataname = "ADSL", |
|
462 |
#' keys = get_cdisc_keys("ADSL"), |
|
463 |
#' code = paste0(readLines(file_example), collapse = "\n") |
|
464 |
#' ) |
|
465 |
code_dataset_connector <- function(dataname, |
|
466 |
code, |
|
467 |
keys = character(0), |
|
468 |
label = character(0), |
|
469 |
mutate_code = character(0), |
|
470 |
mutate_script = character(0), |
|
471 |
metadata = NULL, |
|
472 |
...) { |
|
473 | 6x |
vars <- list(...) |
474 | 6x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
475 | 6x |
checkmate::assert_string(code) |
476 | 6x |
checkmate::assert_character(label, max.len = 1, any.missing = FALSE) |
477 | ||
478 | 6x |
call <- callable_code(code = code) |
479 | ||
480 | 6x |
x <- dataset_connector( |
481 | 6x |
dataname = dataname, |
482 | 6x |
pull_callable = call, |
483 | 6x |
keys = keys, |
484 | 6x |
label = label, |
485 | 6x |
code = code_from_script(mutate_code, mutate_script), |
486 | 6x |
vars = vars, |
487 | 6x |
metadata = metadata |
488 |
) |
|
489 | ||
490 | 6x |
return(x) |
491 |
} |
|
492 | ||
493 |
#' Code `CDISCTealDatasetConnector` |
|
494 |
#' |
|
495 |
#' `r lifecycle::badge("stable")` |
|
496 |
#' |
|
497 |
#' Create a `CDISCTealDatasetConnector` from a string of code with keys |
|
498 |
#' assigned automatically by `dataname`. |
|
499 |
#' |
|
500 |
#' @inheritParams code_dataset_connector |
|
501 |
#' @inheritParams cdisc_dataset_connector |
|
502 |
#' |
|
503 |
#' @rdname code_dataset_connector |
|
504 |
#' |
|
505 |
#' @export |
|
506 |
code_cdisc_dataset_connector <- function(dataname, |
|
507 |
code, |
|
508 |
keys = get_cdisc_keys(dataname), |
|
509 |
parent = `if`(identical(dataname, "ADSL"), character(0L), "ADSL"), |
|
510 |
label = character(0), |
|
511 |
mutate_code = character(0), |
|
512 |
metadata = NULL, |
|
513 |
...) { |
|
514 | 1x |
x <- code_dataset_connector( |
515 | 1x |
dataname = dataname, |
516 | 1x |
code = code, |
517 | 1x |
keys = keys, |
518 | 1x |
mutate_code = mutate_code, |
519 | 1x |
label = label, |
520 | 1x |
metadata = metadata, |
521 |
... |
|
522 |
) |
|
523 | ||
524 | 1x |
res <- as_cdisc( |
525 | 1x |
x, |
526 | 1x |
parent = parent |
527 |
) |
|
528 | ||
529 | 1x |
return(res) |
530 |
} |
|
531 | ||
532 |
# CSV ==== |
|
533 |
#' `csv` `TealDatasetConnector` |
|
534 |
#' |
|
535 |
#' `r lifecycle::badge("stable")` |
|
536 |
#' |
|
537 |
#' Create a `TealDatasetConnector` from `csv` (or general delimited file). |
|
538 |
#' |
|
539 |
#' |
|
540 |
#' @inheritParams dataset_connector |
|
541 |
#' @inheritParams fun_dataset_connector |
|
542 |
#' |
|
543 |
#' @param file (`character`)\cr |
|
544 |
#' path to (`.csv)` (or general delimited) file that contains `data.frame` object |
|
545 |
#' |
|
546 |
#' @param ... (`optional`)\cr |
|
547 |
#' additional arguments applied to pull function (`readr::read_delim`) by default |
|
548 |
#' `delim = ","`. |
|
549 |
#' |
|
550 |
#' @export |
|
551 |
#' |
|
552 |
#' @rdname csv_dataset_connector |
|
553 |
#' |
|
554 |
#' @examples |
|
555 |
#' \dontrun{ |
|
556 |
#' x <- csv_dataset_connector( |
|
557 |
#' dataname = "ADSL", |
|
558 |
#' file = "path/to/file.csv", |
|
559 |
#' delim = ",", |
|
560 |
#' col_types = quote(readr::cols(AGE = "i")) |
|
561 |
#' ) |
|
562 |
#' x$get_code() |
|
563 |
#' } |
|
564 |
csv_dataset_connector <- function(dataname, |
|
565 |
file, |
|
566 |
keys = character(0), |
|
567 |
label = character(0), |
|
568 |
code = character(0), |
|
569 |
script = character(0), |
|
570 |
metadata = list(type = "csv", file = file), |
|
571 |
...) { |
|
572 | 13x |
dot_args <- list(...) |
573 | 13x |
checkmate::assert_list(dot_args, min.len = 0, names = "unique") |
574 | ||
575 | 13x |
check_pkg_quietly( |
576 | 13x |
"readr", |
577 | 13x |
"library readr is required to use csv connectors please install it." |
578 |
) |
|
579 | ||
580 |
# add default delim as "," |
|
581 | 13x |
if (!"delim" %in% names(dot_args)) { |
582 | 6x |
dot_args$delim <- "," |
583 |
} |
|
584 | ||
585 | 13x |
checkmate::assert_string(file) |
586 | 10x |
if (!file.exists(file)) { |
587 | 1x |
stop("File ", file, " does not exist.", call. = FALSE) |
588 |
} |
|
589 | ||
590 | 9x |
x_fun <- callable_function("readr::read_delim") # using read_delim as preserves dates (read.csv does not) |
591 | 9x |
args <- c(list(file = file), dot_args) |
592 | 9x |
x_fun$set_args(args) |
593 | ||
594 | 9x |
x <- dataset_connector( |
595 | 9x |
dataname = dataname, |
596 | 9x |
pull_callable = x_fun, |
597 | 9x |
keys = keys, |
598 | 9x |
label = label, |
599 | 9x |
code = code_from_script(code, script), |
600 | 9x |
metadata = metadata |
601 |
) |
|
602 | ||
603 | 9x |
return(x) |
604 |
} |
|
605 | ||
606 |
#' `csv` `CDISCTealDatasetConnector` |
|
607 |
#' |
|
608 |
#' `r lifecycle::badge("stable")` |
|
609 |
#' |
|
610 |
#' Create a `CDISCTealDatasetConnector` from `csv` (or general delimited) file |
|
611 |
#' with keys and parent name assigned automatically by `dataname`. |
|
612 |
#' |
|
613 |
#' @inheritParams csv_dataset_connector |
|
614 |
#' @inheritParams cdisc_dataset_connector |
|
615 |
#' |
|
616 |
#' @rdname csv_dataset_connector |
|
617 |
#' |
|
618 |
#' @export |
|
619 |
csv_cdisc_dataset_connector <- function(dataname, |
|
620 |
file, |
|
621 |
keys = get_cdisc_keys(dataname), |
|
622 |
parent = `if`(identical(dataname, "ADSL"), character(0L), "ADSL"), |
|
623 |
label = character(0), |
|
624 |
code = character(0), |
|
625 |
script = character(0), |
|
626 |
metadata = list(type = "csv", file = file), |
|
627 |
...) { |
|
628 | 9x |
x <- csv_dataset_connector( |
629 | 9x |
dataname = dataname, |
630 | 9x |
file = file, |
631 | 9x |
keys = keys, |
632 | 9x |
code = code_from_script(code, script), |
633 | 9x |
label = label, |
634 | 9x |
metadata = metadata, |
635 |
... |
|
636 |
) |
|
637 | ||
638 | 9x |
res <- as_cdisc( |
639 | 9x |
x, |
640 | 9x |
parent = parent |
641 |
) |
|
642 | ||
643 | 9x |
return(res) |
644 |
} |
|
645 | ||
646 |
# FUN ==== |
|
647 |
#' Function Dataset Connector |
|
648 |
#' |
|
649 |
#' `r lifecycle::badge("stable")` |
|
650 |
#' |
|
651 |
#' Create a `TealDatasetConnector` from `function` and its arguments. |
|
652 |
#' |
|
653 |
#' @inheritParams dataset_connector |
|
654 |
#' |
|
655 |
#' @param fun (`function`)\cr |
|
656 |
#' a custom function to obtain dataset. |
|
657 |
#' @param fun_args (`list`)\cr |
|
658 |
#' additional arguments for (`func`). |
|
659 |
#' @param func_name (`name`)\cr |
|
660 |
#' for internal purposes, please keep it default |
|
661 |
#' @param ... Additional arguments applied to pull function. |
|
662 |
#' In case when this object code depends on the `raw_data` from the other |
|
663 |
#' `TealDataset`, `TealDatasetConnector` object(s) or other constant value, |
|
664 |
#' this/these object(s) should be included. Please note that `vars` |
|
665 |
#' are included to this object as local `vars` and they cannot be modified |
|
666 |
#' within another dataset. |
|
667 |
#' @export |
|
668 |
#' |
|
669 |
#' @rdname fun_dataset_connector |
|
670 |
#' |
|
671 |
#' @examples |
|
672 |
#' my_data <- function(...) { |
|
673 |
#' data.frame( |
|
674 |
#' ID = paste0("ABC_", seq_len(10)), |
|
675 |
#' var1 = rnorm(n = 10), |
|
676 |
#' var2 = rnorm(n = 10), |
|
677 |
#' var3 = rnorm(n = 10) |
|
678 |
#' ) |
|
679 |
#' } |
|
680 |
#' y <- fun_dataset_connector( |
|
681 |
#' dataname = "XYZ", |
|
682 |
#' fun = my_data |
|
683 |
#' ) |
|
684 |
#' |
|
685 |
#' y$get_code() |
|
686 |
#' |
|
687 |
#' y$pull() |
|
688 |
#' |
|
689 |
#' get_raw_data(y) |
|
690 |
fun_dataset_connector <- function(dataname, |
|
691 |
fun, |
|
692 |
fun_args = NULL, |
|
693 |
keys = character(0), |
|
694 |
label = character(0), |
|
695 |
code = character(0), |
|
696 |
script = character(0), |
|
697 |
func_name = substitute(fun), |
|
698 |
metadata = NULL, |
|
699 |
...) { |
|
700 | 7x |
vars <- list(...) |
701 | 7x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
702 | ||
703 | 7x |
stopifnot(is.function(fun)) |
704 | ||
705 | 7x |
stopifnot(is.list(fun_args) || is.null(fun_args)) |
706 | ||
707 | 7x |
cal <- if (!is.symbol(func_name)) as.call(func_name) else NULL |
708 | ||
709 | 7x |
is_pak <- FALSE |
710 | 7x |
is_locked <- TRUE |
711 | 7x |
if ((!is.null(cal)) && identical(cal[[1]], as.symbol("::"))) { |
712 | 5x |
pak <- cal[[2]] |
713 | 5x |
pak_char <- as.character(pak) # nolint |
714 | 5x |
library(pak_char, character.only = TRUE) |
715 | 5x |
func_name <- cal[[3]] |
716 | 5x |
is_pak <- TRUE |
717 | 5x |
is_locked <- TRUE |
718 |
} else { |
|
719 | 2x |
is_locked <- environmentIsLocked(environment(fun)) |
720 |
} |
|
721 | ||
722 | 7x |
func_char <- as.character(func_name) |
723 | ||
724 | 7x |
ee <- new.env(parent = parent.env(globalenv())) |
725 | ||
726 | 7x |
ee$library <- function(...) { |
727 | ! |
mc <- match.call() |
728 | ! |
mc[[1]] <- quote(base::library) |
729 | ! |
eval(mc, envir = globalenv()) |
730 | ! |
this_env <- parent.frame() |
731 | ! |
if (!identical(this_env, globalenv())) { |
732 | ! |
parent.env(this_env) <- parent.env(globalenv()) |
733 |
} |
|
734 |
} |
|
735 | ||
736 | ||
737 | 7x |
if (!is_pak && !is_locked) { |
738 | 2x |
eval(bquote(.(func_name) <- get(.(func_char), .(environment(fun)))), envir = ee) |
739 | 2x |
eval(bquote(.(func_name) <- rlang::set_env(.(func_name), .(ee))), envir = ee) |
740 |
} |
|
741 | ||
742 | 7x |
x_fun <- CallableFunction$new(fun, env = ee) |
743 | 7x |
x_fun$set_args(fun_args) |
744 | ||
745 | 7x |
vars[[func_char]] <- ee[[func_char]] |
746 | ||
747 | 7x |
x <- dataset_connector( |
748 | 7x |
dataname = dataname, |
749 | 7x |
pull_callable = x_fun, |
750 | 7x |
keys = keys, |
751 | 7x |
code = code_from_script(code, script), |
752 | 7x |
label = label, |
753 | 7x |
vars = vars, |
754 | 7x |
metadata = metadata |
755 |
) |
|
756 | ||
757 | 7x |
return(x) |
758 |
} |
|
759 | ||
760 |
#' Function `CDISCTealDatasetConnector` |
|
761 |
#' |
|
762 |
#' `r lifecycle::badge("stable")` |
|
763 |
#' |
|
764 |
#' Create a `CDISCTealDatasetConnector` from `function` and its arguments |
|
765 |
#' with keys and parent name assigned automatically by `dataname`. |
|
766 |
#' |
|
767 |
#' @inheritParams fun_dataset_connector |
|
768 |
#' @inheritParams cdisc_dataset_connector |
|
769 |
#' |
|
770 |
#' @rdname fun_dataset_connector |
|
771 |
#' |
|
772 |
#' @export |
|
773 |
fun_cdisc_dataset_connector <- function(dataname, |
|
774 |
fun, |
|
775 |
fun_args = NULL, |
|
776 |
keys = get_cdisc_keys(dataname), |
|
777 |
parent = `if`(identical(dataname, "ADSL"), character(0L), "ADSL"), |
|
778 |
label = character(0), |
|
779 |
code = character(0), |
|
780 |
script = character(0), |
|
781 |
func_name = substitute(fun), |
|
782 |
metadata = NULL, |
|
783 |
...) { |
|
784 | 4x |
x <- fun_dataset_connector( |
785 | 4x |
dataname = dataname, |
786 | 4x |
fun = fun, |
787 | 4x |
fun_args = fun_args, |
788 | 4x |
func_name = func_name, |
789 | 4x |
keys = keys, |
790 | 4x |
label = label, |
791 | 4x |
code = code, |
792 | 4x |
script = script, |
793 | 4x |
metadata = metadata, |
794 |
... |
|
795 |
) |
|
796 | ||
797 | 4x |
res <- as_cdisc( |
798 | 4x |
x, |
799 | 4x |
parent = parent |
800 |
) |
|
801 | ||
802 | 4x |
return(res) |
803 |
} |
|
804 | ||
805 | ||
806 |
# PYTHON ==== |
|
807 |
#' `Python` `TealDatasetConnector` |
|
808 |
#' |
|
809 |
#' `r lifecycle::badge("experimental")` |
|
810 |
#' Create a `TealDatasetConnector` from `.py` file or through python code supplied directly. |
|
811 |
#' |
|
812 |
#' @details |
|
813 |
#' Note that in addition to the `reticulate` package, support for python requires an |
|
814 |
#' existing python installation. By default, `reticulate` will attempt to use the |
|
815 |
#' location `Sys.which("python")`, however the path to the python installation can be |
|
816 |
#' supplied directly via `reticulate::use_python`. |
|
817 |
#' |
|
818 |
#' The `teal` API for delayed data requires the python code or script to return a |
|
819 |
#' data.frame object. For this, the `pandas` package is required. This can be installed |
|
820 |
#' using `reticulate::py_install("pandas")`. |
|
821 |
#' |
|
822 |
#' Please see the package documentation for more details. |
|
823 |
#' |
|
824 |
#' @inheritParams dataset_connector |
|
825 |
#' @inheritParams code_dataset_connector |
|
826 |
#' @param file (`character`)\cr |
|
827 |
#' Path to the file location containing the python script used to generate the object. |
|
828 |
#' @param code (`character`)\cr |
|
829 |
#' string containing the python code to be run using `reticulate`. Carefully consider |
|
830 |
#' indentation to follow proper python syntax. |
|
831 |
#' @param object (`character`)\cr |
|
832 |
#' name of the object from the python script that is assigned to the dataset to be used. |
|
833 |
#' |
|
834 |
#' @note |
|
835 |
#' Raises an error when passed `code` and `file` are passed at the same time. |
|
836 |
#' |
|
837 |
#' When using `code`, keep in mind that when using `reticulate` with delayed data, python |
|
838 |
#' functions do not have access to other objects in the `code` and must be self contained. |
|
839 |
#' In the following example, the function `makedata()` doesn't have access to variable `x`: |
|
840 |
#' |
|
841 |
#' \preformatted{import pandas as pd |
|
842 |
#' |
|
843 |
#' x = 1 |
|
844 |
#' def makedata(): |
|
845 |
#' return pd.DataFrame({'x': [x, 2], 'y': [3, 4]}) |
|
846 |
#' |
|
847 |
#' data = makedata()} |
|
848 |
#' |
|
849 |
#' When using custom functions, the function environment must be entirely self contained: |
|
850 |
#' |
|
851 |
#' \preformatted{def makedata(): |
|
852 |
#' import pandas as pd |
|
853 |
#' x = 1 |
|
854 |
#' return pd.DataFrame({'x': [x, 2], 'y': [3, 4]}) |
|
855 |
#' |
|
856 |
#' data = makedata() |
|
857 |
#' } |
|
858 |
#' |
|
859 |
#' **Additional `reticulate` considerations:** |
|
860 |
#' 1. Note that when using pull `vars`, `R` objects referenced in the python |
|
861 |
#' code or script have to be prefixed with `r.`. |
|
862 |
#' 2. `reticulate` isn't able to convert `POSIXct` objects. Please take extra |
|
863 |
#' care when working with `datetime` variables. |
|
864 |
#' |
|
865 |
#' Please read the official documentation for the `reticulate` package for additional |
|
866 |
#' features and current limitations. |
|
867 |
#' |
|
868 |
#' @export |
|
869 |
#' |
|
870 |
#' @rdname python_dataset_connector |
|
871 |
#' |
|
872 |
#' @examples |
|
873 |
#' \dontrun{ |
|
874 |
#' library(reticulate) |
|
875 |
#' |
|
876 |
#' # supply python code directly in R |
|
877 |
#' |
|
878 |
#' x <- python_dataset_connector( |
|
879 |
#' "ADSL", |
|
880 |
#' code = "import pandas as pd |
|
881 |
#' data = pd.DataFrame({'STUDYID': [1, 2], 'USUBJID': [3, 4]})", |
|
882 |
#' object = "data" |
|
883 |
#' ) |
|
884 |
#' |
|
885 |
#' x$pull() |
|
886 |
#' x$get_raw_data() |
|
887 |
#' |
|
888 |
#' # supply an external python script |
|
889 |
#' |
|
890 |
#' python_file <- tempfile(fileext = ".py") |
|
891 |
#' writeLines( |
|
892 |
#' text = "import pandas as pd |
|
893 |
#' data = pd.DataFrame({'STUDYID': [1, 2], 'USUBJID': [3, 4]})", |
|
894 |
#' con = python_file |
|
895 |
#' ) |
|
896 |
#' |
|
897 |
#' x <- python_dataset_connector( |
|
898 |
#' "ADSL", |
|
899 |
#' file = python_file, |
|
900 |
#' object = "data", |
|
901 |
#' ) |
|
902 |
#' |
|
903 |
#' x$pull() |
|
904 |
#' x$get_raw_data() |
|
905 |
#' |
|
906 |
#' # supply pull `vars` from R |
|
907 |
#' |
|
908 |
#' y <- 8 |
|
909 |
#' x <- python_dataset_connector( |
|
910 |
#' "ADSL", |
|
911 |
#' code = "import pandas as pd |
|
912 |
#' data = pd.DataFrame({'STUDYID': [r.y], 'USUBJID': [r.y]})", |
|
913 |
#' object = "data", |
|
914 |
#' vars = list(y = y) |
|
915 |
#' ) |
|
916 |
#' |
|
917 |
#' x$pull() |
|
918 |
#' x$get_raw_data() |
|
919 |
#' } |
|
920 |
python_dataset_connector <- function(dataname, |
|
921 |
file, |
|
922 |
code, |
|
923 |
object = dataname, |
|
924 |
keys = character(0), |
|
925 |
label = character(0), |
|
926 |
mutate_code = character(0), |
|
927 |
mutate_script = character(0), |
|
928 |
vars = list(), |
|
929 |
metadata = NULL) { |
|
930 | ! |
if (!requireNamespace("reticulate", quietly = TRUE)) { |
931 | ! |
stop("Cannot load package 'reticulate' - please install the package.", call. = FALSE) |
932 |
} |
|
933 | ! |
if (utils::packageVersion("reticulate") < 1.22) { |
934 | ! |
stop("Please upgrade package 'reticulate', teal.data requires version >= 1.22") |
935 |
} |
|
936 | ||
937 | ! |
checkmate::assert_string(object) |
938 | ! |
if (!xor(missing(code), missing(file))) stop("Exactly one of 'code' and 'script' is required") |
939 | ||
940 | ! |
if (!missing(file)) { |
941 | ! |
checkmate::assert_string(file) |
942 | ! |
checkmate::assert_file_exists(file, extension = "py") |
943 | ! |
x_fun <- CallablePythonCode$new("py_run_file") # nolint |
944 | ! |
x_fun$set_args(list(file = file, local = TRUE)) |
945 |
} else { |
|
946 | ! |
checkmate::assert_string(code) |
947 | ! |
x_fun <- CallablePythonCode$new("py_run_string") # nolint |
948 | ! |
x_fun$set_args(list(code = code, local = TRUE)) |
949 |
} |
|
950 | ||
951 | ! |
x_fun$set_object(object) |
952 | ||
953 | ! |
x <- dataset_connector( |
954 | ! |
dataname = dataname, |
955 | ! |
pull_callable = x_fun, |
956 | ! |
keys = keys, |
957 | ! |
label = label, |
958 | ! |
code = code_from_script(mutate_code, mutate_script), |
959 | ! |
vars = vars, |
960 | ! |
metadata = metadata |
961 |
) |
|
962 | ||
963 | ! |
return(x) |
964 |
} |
|
965 | ||
966 |
#' `Python` `CDISCTealDatasetConnector` |
|
967 |
#' |
|
968 |
#' `r lifecycle::badge("experimental")` |
|
969 |
#' Create a `CDISCTealDatasetConnector` from `.py` file or through python code supplied directly. |
|
970 |
#' |
|
971 |
#' @inheritParams python_dataset_connector |
|
972 |
#' @inheritParams cdisc_dataset_connector |
|
973 |
#' |
|
974 |
#' @export |
|
975 |
#' |
|
976 |
#' @rdname python_dataset_connector |
|
977 |
python_cdisc_dataset_connector <- function(dataname, |
|
978 |
file, |
|
979 |
code, |
|
980 |
object = dataname, |
|
981 |
keys = get_cdisc_keys(dataname), |
|
982 |
parent = `if`(identical(dataname, "ADSL"), character(0L), "ADSL"), |
|
983 |
mutate_code = character(0), |
|
984 |
mutate_script = character(0), |
|
985 |
label = character(0), |
|
986 |
vars = list(), |
|
987 |
metadata = NULL) { |
|
988 | ! |
x <- python_dataset_connector( |
989 | ! |
dataname = dataname, |
990 | ! |
file = file, |
991 | ! |
code = code, |
992 | ! |
object = object, |
993 | ! |
keys = keys, |
994 | ! |
mutate_code = mutate_code, |
995 | ! |
mutate_script = mutate_script, |
996 | ! |
label = label, |
997 | ! |
vars = vars, |
998 | ! |
metadata = metadata |
999 |
) |
|
1000 | ||
1001 | ! |
res <- as_cdisc( |
1002 | ! |
x, |
1003 | ! |
parent = parent |
1004 |
) |
|
1005 | ||
1006 | ! |
return(res) |
1007 |
} |
1 |
#' Get dataset from `TealDatasetConnector` |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' Get dataset from `TealDatasetConnector` |
|
6 |
#' @param x (`TealDatasetConnector` or `TealDatasetConnector` or `TealDataAbstract`) |
|
7 |
#' @param dataname (`character`) a name of dataset to be retrieved |
|
8 |
#' @details See `help(TealDataConnector)` and `help(TealData)` for more complex examples. |
|
9 |
#' @return (`TealDataset`) |
|
10 |
#' @export |
|
11 |
get_dataset <- function(x, dataname) { |
|
12 | 131x |
UseMethod("get_dataset") |
13 |
} |
|
14 | ||
15 |
#' @rdname get_dataset |
|
16 |
#' @export |
|
17 |
#' @examples |
|
18 |
#' |
|
19 |
#' # TealDatasetConnector -------- |
|
20 |
#' library(magrittr) |
|
21 |
#' |
|
22 |
#' pull_fun_adae <- callable_function(teal.data::example_cdisc_data) %>% |
|
23 |
#' set_args(list(dataname = "ADAE")) |
|
24 |
#' |
|
25 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
26 |
#' |
|
27 |
#' dc <- dataset_connector( |
|
28 |
#' dataname = "ADAE", pull_callable = pull_fun_adae, |
|
29 |
#' keys = get_cdisc_keys("ADSL") |
|
30 |
#' ) |
|
31 |
#' |
|
32 |
#' \dontrun{ |
|
33 |
#' load_dataset(dc) |
|
34 |
#' get_dataset(dc) |
|
35 |
#' } |
|
36 |
#' |
|
37 |
get_dataset.TealDatasetConnector <- function(x, dataname = NULL) { # nolint |
|
38 | 47x |
if (!is.null(dataname)) { |
39 | ! |
warning("'dataname' argument ignored - TealDatasetConnector can contain only one dataset.") |
40 |
} |
|
41 | 47x |
return(x$get_dataset()) |
42 |
} |
|
43 | ||
44 |
#' @rdname get_dataset |
|
45 |
#' @export |
|
46 |
#' @examples |
|
47 |
#' |
|
48 |
#' # TealDataset -------- |
|
49 |
#' ADSL <- example_cdisc_data("ADSL") |
|
50 |
#' x <- dataset("ADSL", ADSL) |
|
51 |
#' |
|
52 |
#' get_dataset(x) |
|
53 |
get_dataset.TealDataset <- function(x, dataname = NULL) { # nolint |
|
54 | 84x |
if (!is.null(dataname)) { |
55 | ! |
warning("'dataname' argument ignored - TealDataset can contain only one dataset.") |
56 |
} |
|
57 | 84x |
return(x$get_dataset()) |
58 |
} |
|
59 | ||
60 |
#' @rdname get_dataset |
|
61 |
#' @export |
|
62 |
#' @examples |
|
63 |
#' |
|
64 |
#' # TealData (not containing connectors) -------- |
|
65 |
#' adsl <- cdisc_dataset( |
|
66 |
#' dataname = "ADSL", |
|
67 |
#' x = example_cdisc_data("ADSL"), |
|
68 |
#' code = "library(teal.data)\nADSL <- example_cdisc_data(\"ADSL\")" |
|
69 |
#' ) |
|
70 |
#' |
|
71 |
#' adae <- cdisc_dataset( |
|
72 |
#' dataname = "ADAE", |
|
73 |
#' x = example_cdisc_data("ADAE"), |
|
74 |
#' code = "library(teal.data)\nADAE <- example_cdisc_data(\"ADAE\")" |
|
75 |
#' ) |
|
76 |
#' |
|
77 |
#' rd <- teal.data:::TealData$new(adsl, adae) |
|
78 |
#' get_dataset(rd, "ADSL") |
|
79 |
get_dataset.TealDataAbstract <- function(x, dataname = NULL) { |
|
80 | ! |
if (is.null(dataname)) { |
81 | ! |
stop(paste( |
82 | ! |
"To get single dataset from data class one must specify the name of the dataset.", |
83 | ! |
"To get all datasets please use get_datasets()" |
84 |
)) |
|
85 |
} |
|
86 | ! |
return(x$get_dataset(dataname = dataname)) |
87 |
} |
1 |
#' S3 generic for creating an information summary about the duplicate key values in a dataset |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' @details The information summary provides row numbers and number of duplicates |
|
6 |
#' for each duplicated key value. |
|
7 |
#' |
|
8 |
#' @param dataset `TealDataset` or `data.frame` a dataset, which will be tested |
|
9 |
#' @param keys `character` vector of variable names in `dataset` consisting the key |
|
10 |
#' or `keys` object, which does have a `primary` element with a vector of variable |
|
11 |
#' names in `dataset` consisting the key. Optional, default: NULL |
|
12 |
#' |
|
13 |
#' @return a `tibble` with variables consisting the key and `row_no` and `duplicates_count` columns |
|
14 |
#' |
|
15 |
#' @note Raises an exception when this function cannot determine the primary key columns of the tested object. |
|
16 |
#' |
|
17 |
#' @examples |
|
18 |
#' |
|
19 |
#' adsl <- teal.data::example_cdisc_data("ADSL") |
|
20 |
#' # create a TealDataset with default keys |
|
21 |
#' rel_adsl <- cdisc_dataset("ADSL", adsl) |
|
22 |
#' get_key_duplicates(rel_adsl) |
|
23 |
#' |
|
24 |
#' df <- as.data.frame( |
|
25 |
#' list(a = c("a", "a", "b", "b", "c"), b = c(1, 2, 3, 3, 4), c = c(1, 2, 3, 4, 5)) |
|
26 |
#' ) |
|
27 |
#' res <- get_key_duplicates(df, keys = c("a", "b")) # duplicated keys are in rows 3 and 4 |
|
28 |
#' print(res) # prints a tibble |
|
29 |
#' \dontrun{ |
|
30 |
#' get_key_duplicates(df) # raises an exception, because keys are missing with no default |
|
31 |
#' } |
|
32 |
#' |
|
33 |
#' @export |
|
34 |
get_key_duplicates <- function(dataset, keys = NULL) { |
|
35 | 46x |
UseMethod("get_key_duplicates", dataset) |
36 |
} |
|
37 | ||
38 |
#' @rdname get_key_duplicates |
|
39 |
#' @export |
|
40 |
get_key_duplicates.TealDataset <- function(dataset, keys = NULL) { # nolint |
|
41 | ! |
df <- get_raw_data(dataset) |
42 | ! |
if (is.null(keys)) { |
43 | ! |
keys_ds <- get_keys(dataset) |
44 | ! |
keys <- if (is.null(keys_ds)) character(0) else keys_ds |
45 |
} |
|
46 | ||
47 | ! |
get_key_duplicates_util(df, keys) |
48 |
} |
|
49 | ||
50 |
#' @rdname get_key_duplicates |
|
51 |
#' @export |
|
52 |
get_key_duplicates.data.frame <- function(dataset, keys = NULL) { # nolint |
|
53 | 46x |
if (is.null(keys)) { |
54 | ! |
attr_key <- attr(dataset, "primary_key") |
55 | ! |
keys <- if (is.null(attr_key)) character(0) else attr |
56 |
} |
|
57 | 46x |
get_key_duplicates_util(dataset, keys) |
58 |
} |
|
59 | ||
60 |
#' Creates a duplicate keys information summary. |
|
61 |
#' |
|
62 |
#' @details |
|
63 |
#' Accepts a list of variable names - `keys`, which are treated as the |
|
64 |
#' key to the `data.frame` argument. An instance of duplicated key is |
|
65 |
#' defined as two rows, which have the same values in columns defined by `keys`. |
|
66 |
#' Per each key value with duplicates returns a row in a `tibble`. The return table |
|
67 |
#' has columns corresponding to the variable names passed in `keys` and |
|
68 |
#' two additional columns: `rows` and `n`, which provide |
|
69 |
#' information about row numbers of the original dataframe, which contain duplicated keys |
|
70 |
#' and total duplicates counts. |
|
71 |
#' |
|
72 |
#' @param dataframe dataframe |
|
73 |
#' @param keys `character` vector of variable names consisting the key to the `data.frame` |
|
74 |
#' |
|
75 |
#' @return `data.frame` with a duplicate keys information summary |
|
76 |
#' |
|
77 |
#' @keywords internal |
|
78 |
#' |
|
79 |
#' @examples |
|
80 |
#' df <- data.frame( |
|
81 |
#' a = c("a", "a", "b", "b", "c"), |
|
82 |
#' b = c(1, 2, 3, 3, 4), |
|
83 |
#' c = c(1, 2, 3, 4, 5) |
|
84 |
#' ) |
|
85 |
#' res <- teal.data:::get_key_duplicates_util(df, keys = c("a", "b")) |
|
86 |
#' print(res) # duplicated keys are in rows 3 and 4 |
|
87 |
#' @seealso [get_key_duplicates] |
|
88 |
get_key_duplicates_util <- function(dataframe, keys) { |
|
89 | 53x |
checkmate::assert_data_frame(dataframe) |
90 | 52x |
checkmate::assert_character(keys) |
91 | 50x |
stopifnot( |
92 | 50x |
all( |
93 | 50x |
vapply(keys, FUN.VALUE = logical(1), FUN = function(key) key %in% colnames(dataframe)) |
94 |
) |
|
95 |
) |
|
96 | ||
97 |
# The goal is to print values of duplicated primary keys with number of duplicates and row numbers |
|
98 | 49x |
duplicates <- dataframe[, keys, drop = FALSE] |
99 | 49x |
duplicates$dups <- duplicated(duplicates, fromLast = FALSE) | duplicated(duplicates, fromLast = TRUE) |
100 | 49x |
duplicates$row_number <- seq_len(nrow(duplicates)) |
101 | 49x |
duplicates <- duplicates[duplicates$dups, ] |
102 | 49x |
duplicates$dups <- NULL |
103 | ||
104 | 49x |
if (nrow(duplicates) == 0) { |
105 | 45x |
duplicates$rows <- character(0) |
106 | 45x |
duplicates$row_number <- NULL |
107 | 45x |
duplicates$n <- integer(0) |
108 | 45x |
return(duplicates) |
109 |
} |
|
110 | ||
111 | 4x |
groups <- split(duplicates, duplicates[, keys, drop = FALSE], drop = TRUE) |
112 | 4x |
summary_list <- lapply(groups, function(group) { |
113 | 6x |
ans <- group[1, keys, drop = FALSE] |
114 | 6x |
ans$rows <- paste(group[, "row_number"], collapse = ",") |
115 | 6x |
ans$n <- nrow(group) |
116 | 6x |
ans |
117 |
}) |
|
118 | 4x |
summary <- do.call(rbind, summary_list) |
119 | 4x |
rownames(summary) <- NULL |
120 | 4x |
summary |
121 |
} |
1 |
#' Get code |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Reads code from specified files or an R6 object. |
|
5 |
#' |
|
6 |
#' \itemize{ |
|
7 |
#' \item{if reading from R6: }{get the R code stored inside the object.} |
|
8 |
#' \item{if reading from files: }{ |
|
9 |
#' Includes code from source if reading from files. Method reads code without |
|
10 |
#' } |
|
11 |
#' } |
|
12 |
#' `library()` or `require()` calls. Function created for teal app, but can be used with any file. |
|
13 |
#' Get code from certain files and for specific datasets |
|
14 |
#' |
|
15 |
#' Reads code from specified files and specific code chunks. |
|
16 |
#' |
|
17 |
#' Code chunks are described with: |
|
18 |
#' |
|
19 |
#' \itemize{ |
|
20 |
#' \item{to open chunk }{`#code>` or `#code ADSL>` or `#code ADSL ADTTE>`} |
|
21 |
#' \item{to close chunk }{`#<code` or `#<ADSL code` or `#<ADSL ADTTE code`} |
|
22 |
#' } |
|
23 |
#' |
|
24 |
#' @param x ([`TealDatasetConnector`] or [`TealDataset`]). If of class `character` will be treated as file to read. |
|
25 |
#' @param exclude_comments (`logical`) whether exclude commented-out lines of code. Lines to be excluded |
|
26 |
#' should be ended with `# nocode`. For multiple line exclusions one should enclose ignored block of code with |
|
27 |
#' `# nocode>` and `# <nocode` |
|
28 |
#' @param read_sources (`logical`) whether to replace `source("path")` with code lines from sourced file. |
|
29 |
#' If `read_sources = TRUE` changing working directory inside preprocessing is not allowed. |
|
30 |
#' @param deparse (`logical`) whether return deparsed form of a call |
|
31 |
#' @param files_path (`character`) (optional) vector of files path to be read for preprocessing. Code from |
|
32 |
#' multiple files is joined together. |
|
33 |
#' @param dataname (`character`) Name of dataset to return code for. |
|
34 |
#' @param ... not used, only for support of S3 |
|
35 |
#' @export |
|
36 |
#' @return (`character`) code of import and preparation of data for teal application. |
|
37 |
get_code <- function(x, ...) { |
|
38 | 59x |
UseMethod("get_code") |
39 |
} |
|
40 | ||
41 | ||
42 |
# Getting code from R6 ==== |
|
43 | ||
44 |
#' @export |
|
45 |
#' @rdname get_code |
|
46 |
get_code.TealDatasetConnector <- function(x, deparse = TRUE, ...) { |
|
47 | 5x |
check_ellipsis(...) |
48 | 5x |
x$get_code(deparse = deparse) |
49 |
} |
|
50 | ||
51 |
#' @export |
|
52 |
#' @rdname get_code |
|
53 |
get_code.TealDataset <- function(x, deparse = TRUE, ...) { |
|
54 | 12x |
check_ellipsis(...) |
55 | 12x |
x$get_code(deparse = deparse) |
56 |
} |
|
57 | ||
58 | ||
59 |
#' @rdname get_code |
|
60 |
#' @export |
|
61 |
#' @examples |
|
62 |
#' x1 <- dataset( |
|
63 |
#' x = data.frame(x = c(1, 2), y = c("a", "b"), stringsAsFactors = FALSE), |
|
64 |
#' keys = "y", |
|
65 |
#' dataname = "XY", |
|
66 |
#' code = "XY <- data.frame(x = c(1, 2), y = c('aa', 'bb'), stringsAsFactors = FALSE)", |
|
67 |
#' label = character(0) |
|
68 |
#' ) |
|
69 |
#' |
|
70 |
#' x2 <- dataset( |
|
71 |
#' x = data.frame(x = c(1, 2), y = c("a", "b"), stringsAsFactors = FALSE), |
|
72 |
#' keys = "y", |
|
73 |
#' dataname = "XYZ", |
|
74 |
#' code = "XYZ <- data.frame(x = c(1, 2), y = c('aa', 'bb'), stringsAsFactors = FALSE)", |
|
75 |
#' label = character(0) |
|
76 |
#' ) |
|
77 |
#' |
|
78 |
#' rd <- teal_data(x1, x2) |
|
79 |
#' |
|
80 |
#' get_code(rd) |
|
81 |
#' get_code(rd, "XY") |
|
82 |
#' get_code(rd, "XYZ") |
|
83 |
get_code.TealDataAbstract <- function(x, dataname = character(0), deparse = TRUE, ...) { # nolint |
|
84 | 7x |
check_ellipsis(...) |
85 | 7x |
if (length(dataname) > 0) { |
86 | 4x |
if (any(!(dataname %in% x$get_datanames()))) { |
87 | ! |
stop("The dataname provided does not exist") |
88 |
} |
|
89 | 4x |
x$get_code(dataname = dataname, deparse = deparse) |
90 |
} else { |
|
91 | 3x |
x$get_code(deparse = deparse) |
92 |
} |
|
93 |
} |
|
94 | ||
95 |
# Getting code from files ==== |
|
96 | ||
97 |
#' @rdname get_code |
|
98 |
#' @export |
|
99 |
get_code.default <- function(x, |
|
100 |
exclude_comments = TRUE, |
|
101 |
read_sources = TRUE, |
|
102 |
deparse = FALSE, |
|
103 |
files_path = NULL, |
|
104 |
dataname = NULL, |
|
105 |
...) { |
|
106 | 35x |
if (!is.null(files_path)) { |
107 | 31x |
x <- files_path |
108 |
} |
|
109 | ||
110 | 35x |
check_ellipsis(...) |
111 | 35x |
checkmate::assert_character(x, min.len = 1, any.missing = FALSE) |
112 | 35x |
checkmate::assert_flag(exclude_comments) |
113 | 32x |
checkmate::assert_flag(read_sources) |
114 | ||
115 | 30x |
if (!methods::hasArg(dataname)) { |
116 | 11x |
l_lines <- lapply(x, function(file_path) { |
117 | 11x |
code_exclude( |
118 | 11x |
enclosed_with( |
119 | 11x |
get_code_single(file_path, read_sources = read_sources) |
120 |
), |
|
121 | 11x |
lines, |
122 | 11x |
exclude_comments = exclude_comments |
123 |
) |
|
124 |
}) |
|
125 |
} else { |
|
126 | 19x |
l_lines <- lapply(x, function(file_path) { |
127 | 19x |
code_exclude( |
128 | 19x |
enclosed_with_dataname( |
129 | 19x |
get_code_single(file_path, read_sources = read_sources), |
130 | 19x |
dataname = dataname |
131 |
), |
|
132 | 19x |
lines, |
133 | 19x |
exclude_comments = exclude_comments |
134 |
) |
|
135 |
}) |
|
136 |
} |
|
137 | ||
138 | 27x |
lines <- unlist(l_lines) |
139 | 27x |
if (deparse) { |
140 | ! |
return(paste( |
141 | ! |
vapply(lines, FUN = deparse1, collapse = "\n", FUN.VALUE = character(1)), |
142 | ! |
collapse = "\n" |
143 |
)) |
|
144 |
} else { |
|
145 | 27x |
return(paste(lines, collapse = "\n")) |
146 |
} |
|
147 |
} |
|
148 | ||
149 | ||
150 | ||
151 |
# * Sub functions for getting code from files ==== |
|
152 | ||
153 |
#' Get code |
|
154 |
#' |
|
155 |
#' Get code from specified file. |
|
156 |
#' @param file_path (`character`) path or URL address of the file to be parsed |
|
157 |
#' @param if_url (`logical`) (optional) TRUE when URL address is provided |
|
158 |
#' @inheritParams get_code |
|
159 |
#' |
|
160 |
#' @return lines (`character`) of preprocessing code |
|
161 |
#' @keywords internal |
|
162 |
get_code_single <- function(file_path, read_sources, if_url = grepl("^http[s]", file_path)) { |
|
163 | 84x |
checkmate::assert_string(file_path) |
164 | 84x |
if (!if_url) { |
165 | 84x |
if (!file.exists(file_path)) { |
166 | 2x |
stop( |
167 | 2x |
"Reading preprocessing code from ", file_path, " file failed. ", |
168 | 2x |
"Please double check if you saved your script." |
169 |
) |
|
170 |
} |
|
171 |
} |
|
172 | 82x |
checkmate::assert_flag(read_sources) |
173 | 82x |
checkmate::assert_flag(if_url) |
174 | ||
175 | 82x |
lines <- readLines(file_path) |
176 | 82x |
if (read_sources) { |
177 | 80x |
lines <- include_source_code(lines = lines, dir = `if`(if_url, NULL, dirname(file_path))) |
178 |
} |
|
179 | ||
180 | 82x |
lines |
181 |
} |
|
182 | ||
183 |
#' Get code enclosed within |
|
184 |
#' |
|
185 |
#' Extracts lines from code which are enclosed within regexp starts_at and stops_at |
|
186 |
#' @param lines (`character`) of preprocessing code. |
|
187 |
#' @return (`character`) subset of lines which start and end with preprocessing |
|
188 |
#' start and stop tags. |
|
189 |
#' @keywords internal |
|
190 |
enclosed_with <- function(lines) { |
|
191 | 11x |
checkmate::assert_character(lines, min.len = 1, any.missing = FALSE) |
192 | ||
193 |
# set beginning of preprocessing |
|
194 | 9x |
idx_start <- grep("#\\s*code>", lines) |
195 | 9x |
line_starts <- if (length(idx_start) > 1) { |
196 | ! |
warning("More than one preproc start found - using the first one.") |
197 | ! |
idx_start[1] + 1 |
198 | 9x |
} else if (length(idx_start) == 1) { |
199 | 7x |
idx_start + 1 |
200 |
} else { |
|
201 | 2x |
1L |
202 |
} |
|
203 | ||
204 |
# set stop of preprocessing |
|
205 | 9x |
idx_stop <- grep("#\\s*<code", lines) |
206 | 9x |
line_stops <- if (length(idx_stop) > 1) { |
207 | ! |
warning("More than one preproc stops found - using the last one.") |
208 | ! |
utils::tail(idx_stop, 1) - 1 |
209 | 9x |
} else if (length(idx_stop) == 1) { |
210 | 7x |
idx_stop - 1 |
211 |
} else { |
|
212 | 2x |
length(lines) |
213 |
} |
|
214 | ||
215 | 9x |
line_numbers <- seq(line_starts, line_stops) |
216 | ||
217 | 9x |
lines[line_numbers] |
218 |
} |
|
219 | ||
220 |
#' Get code enclosed within |
|
221 |
#' |
|
222 |
#' Extracts lines from code which are enclosed within regexp starts_at and stops_at |
|
223 |
#' @inheritParams enclosed_with |
|
224 |
#' @param dataname (`character`) metadata for returned lines |
|
225 |
#' @return (`list`) list of lines and their numbers from certain chunks of code at the specific file. |
|
226 |
#' @keywords internal |
|
227 |
enclosed_with_dataname <- function(lines, dataname = NULL) { |
|
228 | 21x |
checkmate::assert_character(lines, min.len = 1, any.missing = FALSE) |
229 | 21x |
if (!checkmate::test_character(dataname, min.len = 1, any.missing = FALSE)) { |
230 | 4x |
dataname <- "" |
231 |
} |
|
232 | 21x |
dataname <- trimws(dataname) |
233 | 21x |
any_chunk <- any(grepl("#\\s*<?\\s*code", lines)) |
234 | ||
235 | 21x |
if (any_chunk) { |
236 | 17x |
any_start <- any(grepl(sprintf("#\\s*code[\\sa-zA-Z_]*%s[\\sa-zA-Z_]*>", dataname), lines, perl = TRUE)) |
237 | 17x |
any_stop <- any(grepl(sprintf("#\\s*<[\\sa-zA-Z_]*%s[\\sa-zA-Z_]*(?<![a-zA-Z])code", dataname), lines, perl = TRUE)) |
238 | ||
239 | 17x |
if (!(any_start && any_stop)) { |
240 | 1x |
stop(sprintf("File doesn't contain code marked for this %1$s.\n |
241 | 1x |
Please use # code %1$s> to indicate which lines should be extracted.", dataname)) |
242 |
} |
|
243 |
} |
|
244 | ||
245 |
# set beginning of preprocessing |
|
246 | 20x |
idx_start <- grep(sprintf("#\\s*code(?:[\\sa-zA-Z_]*%s[\\sa-zA-Z_]*|[\\s]*)>", dataname), lines, perl = TRUE) |
247 | 20x |
line_starts <- if (length(idx_start) >= 1) { |
248 | 16x |
idx_start + 1 |
249 |
} else { |
|
250 | 4x |
1L |
251 |
} |
|
252 | ||
253 |
# set stop of preprocessing |
|
254 | 20x |
idx_stop <- grep( |
255 | 20x |
sprintf("#\\s*<(?:[\\sa-zA-Z_]*%s[\\sa-zA-Z_]*|[\\s]*)(?<![a-zA-Z])code", dataname), |
256 | 20x |
lines, |
257 | 20x |
perl = TRUE |
258 |
) |
|
259 | 20x |
line_stops <- if (length(idx_stop) >= 1) { |
260 | 16x |
idx_stop - 1 |
261 |
} else { |
|
262 | 4x |
length(lines) |
263 |
} |
|
264 | ||
265 | 20x |
if (length(line_starts) != length(line_stops) || any(line_starts > line_stops)) { |
266 | ! |
stop("Number of #code> has to be the same as #<code") |
267 |
} |
|
268 | ||
269 | ||
270 | 20x |
ll <- data.frame(line_starts, line_stops) |
271 | ||
272 | 20x |
line_numbers <- apply(ll, 1, function(x) seq(x[1], x[2])) |
273 | ||
274 | 20x |
lines_taken <- as.integer(unlist(line_numbers)) |
275 | ||
276 | 20x |
res_lines <- lines[lines_taken] |
277 | ||
278 | 20x |
return(res_lines) |
279 |
} |
|
280 | ||
281 |
#' Exclude from code |
|
282 |
#' |
|
283 |
#' Excludes lines from code. It is possible to exclude one line ended by `# nocode` |
|
284 |
#' @inheritParams enclosed_with |
|
285 |
#' @inheritParams get_code |
|
286 |
#' @inheritParams get_code_single |
|
287 |
#' @keywords internal |
|
288 |
code_exclude <- function(lines, exclude_comments, file_path) { |
|
289 | 32x |
checkmate::assert_character(lines, min.len = 1, any.missing = FALSE) |
290 | 29x |
checkmate::assert_flag(exclude_comments) |
291 | ||
292 | 29x |
nocode_single <- grep("^.+#[[:space:]]*nocode", lines) |
293 | 29x |
nocode_start <- grep("[[:space:]]*#[[:space:]]*nocode[[:space:]]*>+", lines) |
294 | 29x |
nocode_stop <- grep("[[:space:]]*#[[:space:]]*<+[[:space:]]*nocode[[:space:]]*", lines) |
295 | ||
296 | 29x |
if (length(nocode_start) != length(nocode_stop)) { |
297 | ! |
stop(paste("Unequal number of no-code starts and stops in ", file_path)) # nolint |
298 |
} |
|
299 | ||
300 | 29x |
nocode_multi <- NULL |
301 | 29x |
if (length(nocode_start) > 0) { |
302 | 10x |
nocode_multi <- unlist(Map(seq, from = nocode_start, to = nocode_stop)) |
303 |
} |
|
304 | ||
305 | 29x |
nocode <- c(nocode_single, nocode_multi) |
306 | ||
307 | 29x |
if (length(nocode) > 0) { |
308 | 19x |
lines <- lines[-nocode] |
309 |
} |
|
310 | ||
311 | 29x |
if (exclude_comments) { |
312 | 10x |
lines <- grep("^\\s*#.+$", x = lines, invert = TRUE, value = TRUE) |
313 | 10x |
lines <- gsub("(^\\s*#.+$)|(#[^\'\"]*$)", "", x = lines, perl = TRUE) |
314 |
} |
|
315 | ||
316 | 29x |
lines |
317 |
} |
|
318 | ||
319 |
#' Finds lines of code with source call |
|
320 |
#' |
|
321 |
#' Finds lines in preprocessing code where `source()` call is located |
|
322 |
#' @inheritParams enclosed_with |
|
323 |
#' @keywords internal |
|
324 |
find_source_code <- function(lines) { |
|
325 | 80x |
checkmate::assert_character(lines, min.len = 1, any.missing = FALSE) |
326 | 80x |
idx <- grep("^[^#]*source\\([\'\"]([A-Za-z0-9_/.]).*\\.R[\'\"].*\\).*$", lines) |
327 | ||
328 | 80x |
if (length(idx) == 0) { |
329 | 45x |
return(idx) |
330 |
} |
|
331 | ||
332 | 35x |
if (any(grepl("source\\([^)]*chdir\\s*=\\s*T(RUE)*", x = lines[idx]))) { |
333 | ! |
stop("Preprocessing doesn't handle source(chdir = TRUE)") |
334 |
} |
|
335 | ||
336 | 35x |
if (any(grepl("source\\(.+;\\s*source\\(", x = lines[idx]))) { |
337 | ! |
stop("Preprocessing doesn't handle multiple sources in one line\n") |
338 |
} |
|
339 | ||
340 | 35x |
idx |
341 |
} |
|
342 | ||
343 |
#' Includes source in preprocessing code lines |
|
344 |
#' |
|
345 |
#' @inheritParams enclosed_with |
|
346 |
#' @param dir of the file where source is called from. |
|
347 |
#' @return lines of code with source text included |
|
348 |
#' @keywords internal |
|
349 |
include_source_code <- function(lines, dir = NULL) { |
|
350 | 80x |
checkmate::assert_character(lines, min.len = 1, any.missing = FALSE) |
351 | 80x |
stopifnot(is.null(dir) || dir.exists(dir)) |
352 | ||
353 | ||
354 | 80x |
idx <- find_source_code(lines) |
355 | ||
356 | 80x |
if (length(idx) == 0) { |
357 | 45x |
return(lines) |
358 |
} |
|
359 | ||
360 | 35x |
sources_path <- unname(vapply( |
361 | 35x |
lines[idx], |
362 | 35x |
function(x) { |
363 | 52x |
res <- gsub("source\\(.*[\"\']([A-Za-z0-9_/.])", "\\1", strsplit(x, ",")[[1]][1]) |
364 | 52x |
res <- gsub("[\'\"]", "", res) |
365 | 52x |
res <- gsub(")", "", res) |
366 | 52x |
res |
367 |
}, |
|
368 | 35x |
character(1) |
369 |
)) |
|
370 | ||
371 | 35x |
if (length(sources_path) != length(idx)) { |
372 | ! |
stop("Couldn't detect R file name from source() call.") |
373 |
} |
|
374 | ||
375 | 35x |
sources_code <- lapply(sources_path, function(s) { |
376 | 52x |
if (grepl("^http[s]", s)) { |
377 |
# url detected - do nothing |
|
378 |
} else { |
|
379 | 52x |
s <- ifelse(grepl("^(/)|^([\\])|^([A-Za-z]:)", s), s, file.path(dir, s)) |
380 | 52x |
if (!all(file.exists(s))) { |
381 | ! |
msg <- paste0( |
382 | ! |
"File(s) provided in the source() calls don't exist: \n", |
383 | ! |
paste(s[!file.exists(s)], collapse = "\n") |
384 |
) |
|
385 | ! |
stop(msg) |
386 |
} |
|
387 | ||
388 | 52x |
s <- normalizePath(s) |
389 |
} |
|
390 | ||
391 | 52x |
get_code_single(file_path = s, read_sources = TRUE) |
392 |
}) |
|
393 | ||
394 | 35x |
lines[idx] <- sources_code |
395 | 35x |
lines <- unlist(lines) |
396 | ||
397 | 35x |
lines |
398 |
} |
1 |
#' Get code from script |
|
2 |
#' |
|
3 |
#' Get code from script. Switches between `code` and `script` arguments |
|
4 |
#' to return non-empty one to pass it further to constructors. |
|
5 |
#' |
|
6 |
#' @param code (`character`)\cr |
|
7 |
#' an R code to be evaluated or a `PythonCodeClass` created using [python_code]. |
|
8 |
#' @inheritParams dataset_connector |
|
9 |
#' @return code (`character`) |
|
10 |
#' @keywords internal |
|
11 |
code_from_script <- function(code, script, dataname = NULL) { |
|
12 | 248x |
checkmate::assert( |
13 | 248x |
checkmate::check_character(code, max.len = 1, any.missing = FALSE), |
14 | 248x |
checkmate::check_class(code, "PythonCodeClass") |
15 |
) |
|
16 | 246x |
checkmate::assert_character(script, max.len = 1, any.missing = FALSE) |
17 | 246x |
if (length(code) == 0 && length(script) == 0) { |
18 | 181x |
return(character(0)) |
19 |
} |
|
20 | ||
21 | 65x |
if (checkmate::test_string(code) && checkmate::test_string(script)) { |
22 | ! |
stop("Function doesn't accept 'code' and 'script' at the same time. |
23 | ! |
Please specify either 'code' or 'script'", call. = FALSE) |
24 |
} |
|
25 | ||
26 | 65x |
if (checkmate::test_string(script)) { |
27 | ! |
code <- read_script(file = script, dataname = dataname) |
28 |
} |
|
29 | ||
30 | 65x |
code |
31 |
} |
|
32 | ||
33 |
#' Read .R file into character |
|
34 |
#' |
|
35 |
#' @description `r lifecycle::badge("stable")` |
|
36 |
#' Comments will be excluded |
|
37 |
#' |
|
38 |
#' @param file (`character`) File to be parsed into code |
|
39 |
#' @param dataname (`character`) dataset name to subset code from chunks |
|
40 |
#' @return (`character`) vector with the code |
|
41 |
#' |
|
42 |
#' @export |
|
43 |
#' @examples |
|
44 |
#' file_example <- tempfile() |
|
45 |
#' writeLines(c("x <- 2", "#second line comment", "x <- x + 2"), file_example) |
|
46 |
#' |
|
47 |
#' read_script(file_example) |
|
48 |
read_script <- function(file, dataname = NULL) { |
|
49 | 2x |
checkmate::assert_string(file) |
50 | 2x |
checkmate::assert_file_exists(file) |
51 | 2x |
paste( |
52 | 2x |
code_exclude( |
53 | 2x |
enclosed_with_dataname( |
54 | 2x |
get_code_single(file, read_sources = TRUE), |
55 | 2x |
dataname = dataname |
56 |
), |
|
57 | 2x |
exclude_comments = TRUE |
58 |
), |
|
59 | 2x |
collapse = "\n" |
60 |
) |
|
61 |
} |
|
62 | ||
63 |
#' Function to get a file out of a package |
|
64 |
#' |
|
65 |
#' @param pkg (`character`)\cr |
|
66 |
#' The name of the package the file should be received from. |
|
67 |
#' @param file_name (`character`)\cr |
|
68 |
#' The name of the file to be received or path to it starting from |
|
69 |
#' the base package path. |
|
70 |
#' @return The path to the file |
|
71 |
#' @keywords internal |
|
72 |
#' @examples |
|
73 |
#' teal.data:::get_package_file("teal.data", "WORDLIST") |
|
74 |
#' teal.data:::get_package_file("teal.data", "cdisc_datasets/cdisc_datasets.yaml") |
|
75 |
get_package_file <- function(pkg = NULL, file_name = NULL) { |
|
76 | ! |
checkmate::assert_string(pkg) |
77 | ! |
checkmate::assert_string(file_name) |
78 | ! |
base_file <- system.file(file_name, package = pkg) |
79 | ||
80 | ! |
if (file.exists(base_file)) { |
81 | ! |
return(base_file) |
82 |
} else { |
|
83 | ! |
stop(paste("There is no such file:", file_name, "or package:", pkg)) |
84 |
} |
|
85 |
} |
|
86 | ||
87 |
# Function to be used while trying to load the object of specific class from the script. |
|
88 |
object_file <- function(path, class) { |
|
89 | 6x |
checkmate::assert_string(path) |
90 | 6x |
checkmate::assert_file_exists(path) |
91 | 6x |
checkmate::assert_string(class) |
92 | ||
93 | 6x |
lines <- paste0(readLines(path), collapse = "\n") |
94 | 6x |
object <- eval(parse(text = lines, keep.source = FALSE)) |
95 | ||
96 | 6x |
if (!inherits(object, class)) { |
97 | 1x |
stop("The object returned from the file is not of ", class, " class.") |
98 |
} |
|
99 | 5x |
return(object) |
100 |
} |
|
101 | ||
102 |
#' Check if package can be loaded |
|
103 |
#' |
|
104 |
#' @param pckg `character` package name. |
|
105 |
#' @param msg `character` error message to display if package is not available. |
|
106 |
#' |
|
107 |
#' @return Error or invisible NULL. |
|
108 |
#' @keywords internal |
|
109 |
check_pkg_quietly <- function(pckg, msg) { |
|
110 | 14x |
checkmate::assert_string(pckg) |
111 | 14x |
checkmate::assert_string(msg) |
112 | 14x |
if (!pckg %in% rownames(utils::installed.packages())) { |
113 | 1x |
stop(msg) |
114 |
} |
|
115 | ||
116 | 13x |
invisible(NULL) |
117 |
} |
|
118 | ||
119 | ||
120 |
#' validate metadata as a list of length one atomic entries (or NULL) |
|
121 |
#' @param metadata `object` to be checked |
|
122 |
#' @return `NULL` or throw error |
|
123 |
#' @examples |
|
124 |
#' |
|
125 |
#' validate_metadata(NULL) |
|
126 |
#' validate_metadata(list(A = TRUE, B = 10, C = "test")) |
|
127 |
#' \dontrun{ |
|
128 |
#' validate_metadata(list(a = 1:10)) |
|
129 |
#' } |
|
130 |
#' |
|
131 |
#' @export |
|
132 |
validate_metadata <- function(metadata) { |
|
133 | 719x |
checkmate::assert_list(metadata, any.missing = FALSE, names = "named", null.ok = TRUE) |
134 | 713x |
lapply(names(metadata), function(name) { |
135 | 156x |
checkmate::assert_atomic(metadata[[name]], len = 1, .var.name = name) |
136 |
}) |
|
137 | 709x |
return(NULL) |
138 |
} |
|
139 | ||
140 |
#' Resolve the expected bootstrap theme |
|
141 |
#' @keywords internal |
|
142 |
get_teal_bs_theme <- function() { |
|
143 | 8x |
bs_theme <- getOption("teal.bs_theme") |
144 | 8x |
if (is.null(bs_theme)) { |
145 | 5x |
NULL |
146 | 3x |
} else if (!inherits(bs_theme, "bs_theme")) { |
147 | 2x |
warning("teal.bs_theme has to be of a bslib::bs_theme class, the default shiny bootstrap is used.") |
148 | 2x |
NULL |
149 |
} else { |
|
150 | 1x |
bs_theme |
151 |
} |
|
152 |
} |
1 |
## TealDataAbstract ==== |
|
2 |
#' @title `TealDataAbstract` class |
|
3 |
#' |
|
4 |
#' @description |
|
5 |
#' Abstract class containing code for handling set of datasets. |
|
6 |
#' @keywords internal |
|
7 |
TealDataAbstract <- R6::R6Class( # nolint |
|
8 |
classname = "TealDataAbstract", |
|
9 |
## __Public Methods ==== |
|
10 |
public = list( |
|
11 |
#' @description |
|
12 |
#' Cannot create a `TealDataAbstract` object |
|
13 |
#' |
|
14 |
#' @return throws error |
|
15 |
initialize = function() { |
|
16 | 1x |
stop("Pure virtual method") |
17 |
}, |
|
18 |
#' @description |
|
19 |
#' Check if the object raw data is reproducible from the `get_code()` code. |
|
20 |
#' @return |
|
21 |
#' `NULL` if check step has been disabled |
|
22 |
#' `TRUE` if all the datasets generated from evaluating the |
|
23 |
#' `get_code()` code are identical to the raw data, else `FALSE`. |
|
24 |
check = function() { |
|
25 |
# code can be put only to the mutate with empty code in datasets |
|
26 | 57x |
res <- if (isFALSE(private$.check)) { |
27 | 44x |
NULL |
28 |
} else { |
|
29 | 13x |
if (length(private$pull_code$code) > 0) { |
30 | 1x |
private$check_combined_code() |
31 |
} else { |
|
32 | 12x |
all(vapply( |
33 | 12x |
private$datasets, |
34 | 12x |
function(x) { |
35 | 27x |
check_res <- x$check() |
36 |
# NULL is still ok |
|
37 | 26x |
is.null(check_res) || isTRUE(check_res) |
38 |
}, |
|
39 | 12x |
logical(1) |
40 |
)) |
|
41 |
} |
|
42 |
} |
|
43 | 56x |
private$check_result <- res |
44 | 56x |
logger::log_trace("TealDataAbstract$check executed the code to reproduce the data - result: { res }.") |
45 | 56x |
res |
46 |
}, |
|
47 |
#' @description |
|
48 |
#' Execute `check()` and raise an error if it's not reproducible. |
|
49 |
#' @return error if code is not reproducible else invisibly nothing |
|
50 |
check_reproducibility = function() { |
|
51 | 47x |
self$check() |
52 | 47x |
if (isFALSE(self$get_check_result())) { |
53 | 2x |
stop("Reproducibility check failed.") |
54 |
} |
|
55 | 45x |
logger::log_trace("TealDataAbstract$check_reproducibility reproducibility check passed.") |
56 | 45x |
return(invisible(NULL)) |
57 |
}, |
|
58 |
#' @description |
|
59 |
#' Execute mutate code. Using `mutate_data(set).TealDataAbstract` |
|
60 |
#' does not cause instant execution, the `mutate_code` is |
|
61 |
#' delayed and can be evaluated using this method. |
|
62 |
execute_mutate = function() { |
|
63 | 2x |
logger::log_trace("TealDataAbstract$execute_mutate evaluating mutate code...") |
64 |
# this will be pulled already! - not needed? |
|
65 | 2x |
if (length(private$mutate_code$code) == 0) { |
66 | 1x |
res <- unlist(lapply( |
67 | 1x |
private$datasets, |
68 | 1x |
function(x) { |
69 | 2x |
if (is_pulled(x)) { |
70 | 2x |
get_datasets(x) |
71 |
} else { |
|
72 | ! |
NULL |
73 |
} |
|
74 |
} |
|
75 |
)) |
|
76 |
# exit early if mutate isn't required |
|
77 | 1x |
logger::log_trace("TealDataAbstract$execute_mutate no code to evaluate.") |
78 | 1x |
if (!is.null(res)) { |
79 | 1x |
res <- stats::setNames(res, vapply(res, get_dataname, character(1))) |
80 |
} |
|
81 | 1x |
return(res) |
82 |
} |
|
83 | ||
84 | 1x |
if (inherits(private$mutate_code, "PythonCodeClass")) { |
85 | ! |
items <- lapply(self$get_items(), get_raw_data) |
86 | ! |
datasets <- stats::setNames(items, vapply(self$get_items(), get_dataname, character(1))) |
87 | ||
88 | ! |
new_env <- private$mutate_code$eval(vars = c(datasets, private$mutate_vars)) |
89 |
} else { |
|
90 |
# have to evaluate post-processing code (i.e. private$mutate_code) before returning dataset |
|
91 | 1x |
new_env <- new.env(parent = parent.env(globalenv())) |
92 | 1x |
for (dataset in self$get_items()) { |
93 | 2x |
assign(get_dataname(dataset), get_raw_data(dataset), envir = new_env) |
94 |
} |
|
95 | ||
96 | 1x |
for (var_idx in seq_along(private$mutate_vars)) { |
97 | ! |
mutate_var <- private$mutate_vars[[var_idx]] |
98 | ! |
assign( |
99 | ! |
x = names(private$mutate_vars)[[var_idx]], |
100 | ! |
value = `if`( |
101 | ! |
inherits(mutate_var, "TealDataset") || inherits(mutate_var, "TealDatasetConnector"), |
102 | ! |
get_raw_data(mutate_var), |
103 | ! |
mutate_var |
104 |
), |
|
105 | ! |
envir = new_env |
106 |
) |
|
107 |
} |
|
108 | ||
109 | 1x |
private$mutate_code$eval(envir = new_env) |
110 |
} |
|
111 | ||
112 | 1x |
lapply( |
113 | 1x |
self$get_datasets(), |
114 | 1x |
function(x) { |
115 | 2x |
x$recreate( |
116 | 2x |
x = get(get_dataname(x), new_env) |
117 |
) |
|
118 |
} |
|
119 |
) |
|
120 | 1x |
logger::log_trace("TealDataAbstract$execute_mutate evaluated mutate code.") |
121 | 1x |
return(invisible(NULL)) |
122 |
}, |
|
123 |
#' @description |
|
124 |
#' Get result of reproducibility check |
|
125 |
#' @return `NULL` if check has not been called yet, `TRUE` / `FALSE` otherwise |
|
126 |
get_check_result = function() { |
|
127 | 49x |
private$check_result |
128 |
}, |
|
129 |
#' @description |
|
130 |
#' Get code for all datasets. |
|
131 |
#' @param dataname (`character`) `dataname` or `NULL` for all datasets |
|
132 |
#' @param deparse (`logical`) whether to return the deparsed form of a call |
|
133 |
#' @return (`character`) vector of code to generate datasets. |
|
134 |
get_code = function(dataname = NULL, deparse = TRUE) { |
|
135 | 47x |
checkmate::assert_character(dataname, min.len = 1, null.ok = TRUE, any.missing = FALSE) |
136 | 46x |
checkmate::assert_flag(deparse) |
137 | ||
138 | 45x |
return(self$get_code_class()$get_code(dataname = dataname, deparse = deparse)) |
139 |
}, |
|
140 |
#' @description |
|
141 |
#' Get internal `CodeClass` object |
|
142 |
#' @param only_pull (`logical` value)\cr |
|
143 |
#' if `TRUE` only code to pull datasets will be returned without the mutate code. |
|
144 |
#' |
|
145 |
#' @return `CodeClass` |
|
146 |
get_code_class = function(only_pull = FALSE) { |
|
147 | 46x |
all_code_class <- CodeClass$new() |
148 | ||
149 | 46x |
pull_code_class <- private$get_pull_code_class() |
150 | 46x |
all_code_class$append(pull_code_class) |
151 | ||
152 | 46x |
datasets_code_class <- private$get_datasets_code_class() |
153 | 46x |
all_code_class$append(datasets_code_class) |
154 | ||
155 | 46x |
if (isFALSE(only_pull)) { |
156 | 41x |
mutate_code_class <- private$get_mutate_code_class() |
157 | 41x |
all_code_class$append(mutate_code_class) |
158 |
} |
|
159 | ||
160 | 46x |
return(all_code_class) |
161 |
}, |
|
162 |
#' @description |
|
163 |
#' Get names of the datasets. |
|
164 |
#' |
|
165 |
#' @return `character` vector with names of all datasets. |
|
166 |
get_datanames = function() { |
|
167 | 209x |
datasets_names <- unname(unlist(lapply(private$datasets, get_dataname))) |
168 | ||
169 | 209x |
return(datasets_names) |
170 |
}, |
|
171 |
#' @description |
|
172 |
#' Get `TealDataset` object. |
|
173 |
#' |
|
174 |
#' @param dataname (`character` value)\cr |
|
175 |
#' name of dataset to be returned. If `NULL`, all datasets are returned. |
|
176 |
#' |
|
177 |
#' @return `TealDataset`. |
|
178 |
get_dataset = function(dataname = NULL) { |
|
179 | 5x |
checkmate::assert_string(dataname, null.ok = TRUE) |
180 | ||
181 | 4x |
if (length(dataname) == 1) { |
182 | 3x |
if (!(dataname %in% self$get_datanames())) { |
183 | 1x |
stop(paste("dataset", dataname, "not found")) |
184 |
} |
|
185 | ||
186 | 2x |
res <- self$get_datasets()[[dataname]] |
187 | 2x |
return(res) |
188 |
} else { |
|
189 | 1x |
return(self$get_datasets()) |
190 |
} |
|
191 |
}, |
|
192 |
#' @description |
|
193 |
#' Get `list` of `TealDataset` objects. |
|
194 |
#' |
|
195 |
#' @return `list` of `TealDataset`. |
|
196 |
get_datasets = function() { |
|
197 | 62x |
if (!self$is_pulled()) { |
198 | 2x |
stop( |
199 | 2x |
"Not all datasets have been pulled yet.\n", |
200 | 2x |
"- Please use `load_datasets()` to retrieve complete results." |
201 |
) |
|
202 |
} |
|
203 | 60x |
unlist(lapply(self$get_items(), get_dataset)) |
204 |
}, |
|
205 |
#' @description |
|
206 |
#' Get all datasets and all dataset connectors |
|
207 |
#' |
|
208 |
#' @param dataname (`character` value)\cr |
|
209 |
#' name of dataset connector to be returned. If `NULL`, all connectors are returned. |
|
210 |
#' @return `list` with all datasets and all connectors |
|
211 |
get_items = function(dataname = NULL) { |
|
212 | 30x |
checkmate::assert_string(dataname, null.ok = TRUE) |
213 | ||
214 | 30x |
if (length(dataname) == 1) { |
215 | ! |
if (!(dataname %in% self$get_datanames())) { |
216 | ! |
stop(paste("dataset", dataname, "not found")) |
217 |
} |
|
218 | ! |
return(private$datasets[[dataname]]) |
219 |
} else { |
|
220 | 30x |
return(private$datasets) |
221 |
} |
|
222 |
}, |
|
223 |
#' @description |
|
224 |
#' Has this data been or will this data be subjected to a reproducibility check |
|
225 |
#' @return `logical` |
|
226 |
get_check = function() { |
|
227 | 3x |
private$.check |
228 |
}, |
|
229 |
#' @field id String used to create unique GUI elements |
|
230 |
id = NULL, |
|
231 |
#' @description |
|
232 |
#' Check if dataset has already been pulled. |
|
233 |
#' |
|
234 |
#' @return `TRUE` if dataset has been already pulled, else `FALSE` |
|
235 |
is_pulled = function() { |
|
236 | 128x |
all(vapply(private$datasets, is_pulled, logical(1))) |
237 |
}, |
|
238 |
#' @description |
|
239 |
#' Mutate data by code. Code used in this mutation is not linked to particular |
|
240 |
#' but refers to all datasets. |
|
241 |
#' Consequence of this is that when using `get_code(<dataset>)` this |
|
242 |
#' part of the code will be returned for each specified dataset. This method |
|
243 |
#' should be used only if particular call involve changing multiple datasets. |
|
244 |
#' Otherwise please use `mutate_dataset`. |
|
245 |
#' Execution of `mutate_code` is delayed after datasets are pulled |
|
246 |
#' (`isTRUE(is_pulled)`). |
|
247 |
#' |
|
248 |
#' @param code (`character`) Code to mutate the dataset. Must contain the |
|
249 |
#' `dataset$dataname` |
|
250 |
#' @param vars (named `list`)) \cr |
|
251 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
252 |
#' other constant value, this/these object(s) should be included as named |
|
253 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
254 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
255 |
#' It's recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
256 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
257 |
#' are included to this object as local `vars` and they cannot be modified |
|
258 |
#' within another dataset. |
|
259 |
#' |
|
260 |
#' @return self invisibly for chaining |
|
261 |
mutate = function(code, vars = list()) { |
|
262 | 8x |
private$set_mutate_vars(vars) |
263 | 8x |
private$set_mutate_code( |
264 | 8x |
code = code, |
265 | 8x |
deps = names(vars) |
266 |
) |
|
267 | 8x |
private$check_result <- NULL |
268 | 8x |
logger::log_trace( |
269 | 8x |
sprintf( |
270 | 8x |
"TealDataAbstract$mutate code (%s lines) and vars (%s) set.", |
271 | 8x |
length(parse(text = code, keep.source = FALSE)), |
272 | 8x |
paste(names(vars), collapse = ", ") |
273 |
) |
|
274 |
) |
|
275 | 8x |
return(invisible(self)) |
276 |
}, |
|
277 |
#' @description |
|
278 |
#' Mutate dataset by code. |
|
279 |
#' Execution of `mutate_code` is delayed after datasets are pulled |
|
280 |
#' (`isTRUE(is_pulled)`). |
|
281 |
#' |
|
282 |
#' @param dataname (`character`) `Dataname` to be mutated |
|
283 |
#' @param code (`character`) Code to mutate the dataset. Must contain the |
|
284 |
#' `dataset$dataname` |
|
285 |
#' @param vars (named `list`)) \cr |
|
286 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
287 |
#' other constant value, this/these object(s) should be included as named |
|
288 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
289 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
290 |
#' It's recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
291 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
292 |
#' are included to this object as local `vars` and they cannot be modified |
|
293 |
#' within another dataset. |
|
294 |
#' |
|
295 |
#' @return self invisibly for chaining |
|
296 |
mutate_dataset = function(dataname, code, vars = list()) { |
|
297 | 7x |
checkmate::assert_character(dataname, min.len = 1, any.missing = FALSE) |
298 | 6x |
stopifnot(all(dataname %in% self$get_datanames())) |
299 | ||
300 | 5x |
private$set_mutate_vars(vars = vars) |
301 | 5x |
private$set_mutate_code( |
302 | 5x |
code = code, |
303 | 5x |
dataname = dataname, |
304 | 5x |
deps = names(vars) |
305 |
) |
|
306 | ||
307 | 5x |
private$check_result <- NULL |
308 | 5x |
logger::log_trace( |
309 | 5x |
sprintf( |
310 | 5x |
"TealDataAbstract$mutate code (%s lines) and vars (%s) set for dataset: %s.", |
311 | 5x |
length(parse(text = code, keep.source = FALSE)), |
312 | 5x |
paste(names(vars), collapse = ", "), |
313 | 5x |
dataname |
314 |
) |
|
315 |
) |
|
316 | ||
317 | 5x |
return(invisible(self)) |
318 |
}, |
|
319 |
#' @description |
|
320 |
#' Set reproducibility check |
|
321 |
#' |
|
322 |
#' @param check (`logical`) whether to perform reproducibility check. |
|
323 |
#' |
|
324 |
#' @return (`self`) invisibly for chaining. |
|
325 |
set_check = function(check = FALSE) { |
|
326 | 132x |
checkmate::assert_flag(check) |
327 | 131x |
private$.check <- check |
328 | 131x |
logger::log_trace("TealDataAbstract$set_check check set to: { check }.") |
329 | 131x |
return(invisible(self)) |
330 |
}, |
|
331 |
#' @description |
|
332 |
#' Set pull code |
|
333 |
#' |
|
334 |
#' @param code (`character` value)\cr |
|
335 |
#' code to reproduce `data` in `TealDataset` objects. Can't be set if any dataset |
|
336 |
#' has `code` set already. |
|
337 |
#' |
|
338 |
#' @return (`self`) invisibly for chaining. |
|
339 |
set_pull_code = function(code) { |
|
340 | 7x |
checkmate::assert_string(code) |
341 | 6x |
is_code_set <- vapply( |
342 | 6x |
self$get_items(), |
343 | 6x |
function(item) { |
344 | 11x |
get_code(item, deparse = TRUE) != "" |
345 |
}, |
|
346 | 6x |
logical(1) |
347 |
) |
|
348 | ||
349 | 6x |
is_dataset <- vapply( |
350 | 6x |
self$get_items(), |
351 | 6x |
function(item) { |
352 | 11x |
inherits(item, "TealDataset") |
353 |
}, |
|
354 | 6x |
logical(1) |
355 |
) |
|
356 | ||
357 | 6x |
if (any(is_code_set & is_dataset)) { |
358 | 2x |
stop( |
359 | 2x |
"'code' argument should be specified only in the 'cdisc_data' or in 'cdisc_dataset' but not in both", |
360 | 2x |
call. = FALSE |
361 |
) |
|
362 |
} |
|
363 | ||
364 | 4x |
if (all(!is_dataset)) { |
365 | 1x |
stop( |
366 | 1x |
"Connectors are reproducible by default and setting 'code' argument might break it", |
367 | 1x |
call. = FALSE |
368 |
) |
|
369 |
} |
|
370 | ||
371 | 3x |
private$pull_code <- private$pull_code$set_code( |
372 | 3x |
code = code, |
373 | 3x |
dataname = self$get_datanames() |
374 |
) |
|
375 | 3x |
logger::log_trace("TealDataAbstract$set_pull_code pull code set.") |
376 | ||
377 | 3x |
return(invisible(self)) |
378 |
}, |
|
379 | ||
380 |
#' @description |
|
381 |
#' Reassign `vars` in `TealDataset` and `TealDatasetConnector` objects |
|
382 |
#' to keep the valid reference after deep cloning |
|
383 |
#' For example if `TealDatasetConnector` has a dependency on some `TealDataset`, this |
|
384 |
#' `TealDataset` is reassigned inside of `TealDatasetConnector`. |
|
385 |
reassign_datasets_vars = function() { |
|
386 | 3x |
for (dataset in self$get_items()) { |
387 | 6x |
dataset$reassign_datasets_vars( |
388 | 6x |
datasets = self$get_items() |
389 |
) |
|
390 |
} |
|
391 | 3x |
logger::log_trace("TealDataAbstract$reassign_datasets_vars reassigned vars.") |
392 | 3x |
invisible(NULL) |
393 |
} |
|
394 |
), |
|
395 | ||
396 |
## __Private Fields ==== |
|
397 |
private = list( |
|
398 |
datasets = NULL, |
|
399 |
.check = FALSE, |
|
400 |
check_result = NULL, # TRUE / FALSE after calling check() |
|
401 |
mutate_code = NULL, # CodeClass after initialization |
|
402 |
mutate_vars = list(), # named list with vars used to mutate object |
|
403 |
pull_code = NULL, # CodeClass - code to reproduce loading of TealDataset(s) only |
|
404 | ||
405 |
## __Private Methods ==== |
|
406 |
# need to have a custom deep_clone because one of the key fields are reference-type object |
|
407 |
# in particular: datasets is a list of R6 objects that wouldn't be cloned using default clone(deep = T) |
|
408 |
deep_clone = function(name, value) { |
|
409 | 222x |
deep_clone_r6(name, value) |
410 |
}, |
|
411 |
check_combined_code = function() { |
|
412 | 4x |
execution_environment <- new.env(parent = parent.env(globalenv())) |
413 | 4x |
self$get_code_class(only_pull = TRUE)$eval(envir = execution_environment) |
414 | 4x |
res <- all(vapply( |
415 | 4x |
Filter(is_pulled, self$get_items()), |
416 | 4x |
function(dataset) { |
417 | 8x |
data <- get_raw_data(dataset) |
418 | 8x |
data_from_code <- get(get_dataname(dataset), execution_environment) |
419 | 7x |
identical(data, data_from_code) |
420 |
}, |
|
421 | 4x |
logical(1) |
422 |
)) |
|
423 | 3x |
logger::log_trace("TealDataAbstract$check_combined_code reproducibility result of the combined code: { res }.") |
424 | 3x |
res |
425 |
}, |
|
426 |
get_datasets_code_class = function() { |
|
427 | 78x |
res <- CodeClass$new() |
428 | 78x |
if (is.null(private$datasets)) { |
429 | ! |
return(res) |
430 |
} |
|
431 | 78x |
for (dataset in private$datasets) { |
432 | 164x |
res$append(dataset$get_code_class()) |
433 |
} |
|
434 | 78x |
return(res) |
435 |
}, |
|
436 |
get_mutate_code_class = function() { |
|
437 | 71x |
res <- CodeClass$new() |
438 | 71x |
res$append(list_to_code_class(private$mutate_vars)) |
439 | 71x |
res$append(private$mutate_code) |
440 | 71x |
return(res) |
441 |
}, |
|
442 |
get_pull_code_class = function() { |
|
443 | 48x |
res <- CodeClass$new() |
444 | 48x |
res$append(private$pull_code) |
445 | 48x |
return(res) |
446 |
}, |
|
447 |
set_mutate_code = function(code, dataname = self$get_datanames(), deps = names(private$mutate_vars)) { |
|
448 | 16x |
checkmate::assert( |
449 | 16x |
checkmate::check_character(code, max.len = 1, any.missing = FALSE), |
450 | 16x |
checkmate::check_class(code, "PythonCodeClass") |
451 |
) |
|
452 | ||
453 | 14x |
if (inherits(code, "PythonCodeClass")) { |
454 | ! |
r <- PythonCodeClass$new() |
455 | ! |
r$append(private$mutate_code) |
456 | ! |
private$mutate_code <- r |
457 | ||
458 | ! |
code <- code$get_code() |
459 |
} |
|
460 | ||
461 | 14x |
if (length(code) > 0 && code != "") { |
462 | 14x |
private$mutate_code$set_code(code = code, dataname = dataname, deps = deps) |
463 |
} |
|
464 | ||
465 | 14x |
return(invisible(self)) |
466 |
}, |
|
467 |
set_mutate_vars = function(vars) { |
|
468 | 17x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
469 | 15x |
if (length(vars) > 0) { |
470 | 2x |
private$mutate_vars <- c( |
471 | 2x |
private$mutate_vars, |
472 | 2x |
vars[!names(vars) %in% names(private$mutate_vars)] |
473 |
) |
|
474 |
} |
|
475 | ||
476 | 15x |
return(invisible(self)) |
477 |
}, |
|
478 |
check_names = function(x) { |
|
479 | 149x |
if (any(vapply(x, identical, logical(1), y = ""))) { |
480 | ! |
stop("Cannot extract some dataset names") |
481 |
} |
|
482 | 149x |
if (any(duplicated(x))) { |
483 | 1x |
stop("TealDatasets names should be unique") |
484 |
} |
|
485 | 148x |
if (any(x %in% self$get_datanames())) { |
486 | ! |
stop("Some datanames already exists") |
487 |
} |
|
488 | 148x |
return(TRUE) |
489 |
} |
|
490 |
) |
|
491 |
) |
1 |
#' Helper function to deep copy `R6` object |
|
2 |
#' |
|
3 |
#' When cloning an R6 object the private function |
|
4 |
#' `deep_clone` is automatically used. To ensure a complete |
|
5 |
#' clone the private function should call this function |
|
6 |
#' |
|
7 |
#' @param name (`character`) argument passed by `deep_clone` function. |
|
8 |
#' @param value (any `R` object) argument passed by `deep_clone` function. |
|
9 |
#' @keywords internal |
|
10 |
deep_clone_r6 <- function(name, value) { |
|
11 | 1629x |
if (checkmate::test_list(value, types = "R6")) { |
12 | 86x |
lapply(value, function(x) x$clone(deep = TRUE)) |
13 | 1543x |
} else if (R6::is.R6(value)) { |
14 | 31x |
value$clone(deep = TRUE) |
15 | 1512x |
} else if (is.environment(value)) { |
16 | 5x |
new_env <- as.environment(as.list(value, all.names = TRUE)) |
17 | 5x |
parent.env(new_env) <- parent.env(value) |
18 | 5x |
new_env |
19 |
} else { |
|
20 | 1507x |
value |
21 |
} |
|
22 |
} |
1 |
# CDISCTealDataConnector ------ |
|
2 |
#' |
|
3 |
#' @title Manage multiple and `TealDatasetConnector` of the same type. |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("stable")` |
|
6 |
#' Class manages `TealDatasetConnector` to specify additional dynamic arguments and to |
|
7 |
#' open/close connection. |
|
8 |
#' |
|
9 |
#' @param connection (`TealDataConnection`)\cr |
|
10 |
#' connection to data source |
|
11 |
#' @param connectors (`list` of `TealDatasetConnector` elements)\cr |
|
12 |
#' list with dataset connectors |
|
13 |
#' |
|
14 |
CDISCTealDataConnector <- R6::R6Class( # nolint |
|
15 |
classname = "CDISCTealDataConnector", |
|
16 |
inherit = TealDataConnector, |
|
17 | ||
18 |
## __Public Methods ==== |
|
19 |
public = list( |
|
20 |
#' @description |
|
21 |
#' Create a new `CDISCTealDataConnector` object |
|
22 |
initialize = function(connection, connectors) { |
|
23 | 8x |
super$initialize(connection = connection, connectors = connectors) |
24 | ||
25 | 8x |
new_parent <- list() |
26 | 8x |
for (x in connectors) { |
27 | 12x |
x_dataname <- x$get_dataname() |
28 | 12x |
new_parent[[x_dataname]] <- if (inherits(x, "CDISCTealDatasetConnector")) { |
29 | 12x |
x$get_parent() |
30 |
} else { |
|
31 | ! |
character(0L) |
32 |
} |
|
33 |
} |
|
34 | ||
35 | 8x |
if (is_dag(new_parent)) { |
36 | ! |
stop("Cycle detected in a parent and child dataset graph.") |
37 |
} |
|
38 | ||
39 | 8x |
private$parent <- new_parent |
40 | 8x |
logger::log_trace( |
41 | 8x |
"CDISCTealDataConnector initialized with data: { paste(self$get_datanames(), collapse = ' ') }" |
42 |
) |
|
43 | 8x |
return(invisible(self)) |
44 |
}, |
|
45 |
#' @description |
|
46 |
#' Get all datasets parent names |
|
47 |
#' @return (named `list`) with dataset name and its corresponding parent dataset name |
|
48 |
get_parent = function() { |
|
49 | ! |
private$parent |
50 |
} |
|
51 |
), |
|
52 | ||
53 |
## __Private Fields ==== |
|
54 |
private = list( |
|
55 |
parent = list() # list with dataset names and its parent dataset names |
|
56 |
) |
|
57 |
) |
|
58 | ||
59 |
#' The constructor of `CDISCTealDataConnector` objects. |
|
60 |
#' |
|
61 |
#' @description `r lifecycle::badge("stable")` |
|
62 |
#' |
|
63 |
#' @param connection (`TealDataConnection`)\cr |
|
64 |
#' connection to data source |
|
65 |
#' @param connectors (`list` of `TealDatasetConnector` elements)\cr |
|
66 |
#' list with dataset connectors |
|
67 |
#' |
|
68 |
#' @examples |
|
69 |
#' adsl_cf <- callable_function( |
|
70 |
#' function() as.data.frame(as.list(setNames(nm = get_cdisc_keys("ADSL")))) |
|
71 |
#' ) |
|
72 |
#' adae_cf <- callable_function( |
|
73 |
#' function() as.data.frame(as.list(setNames(nm = get_cdisc_keys("ADAE")))) |
|
74 |
#' ) |
|
75 |
#' adsl <- cdisc_dataset_connector( |
|
76 |
#' "ADSL", adsl_cf, |
|
77 |
#' keys = get_cdisc_keys("ADSL"), parent = character(0) |
|
78 |
#' ) |
|
79 |
#' adae <- cdisc_dataset_connector( |
|
80 |
#' "ADAE", adae_cf, |
|
81 |
#' keys = get_cdisc_keys("ADAE"), parent = "ADSL" |
|
82 |
#' ) |
|
83 |
#' data <- cdisc_data_connector( |
|
84 |
#' connection = data_connection(open_fun = callable_function(function() "open function")), |
|
85 |
#' connectors = list(adsl, adae) |
|
86 |
#' ) |
|
87 |
#' @return `CDISCTealDataConnector` object |
|
88 |
#' @export |
|
89 |
cdisc_data_connector <- function(connection, connectors) { |
|
90 | 9x |
stopifnot(inherits(connection, "TealDataConnection")) |
91 | 7x |
checkmate::assert_list(connectors, types = "TealDatasetConnector", min.len = 1) |
92 | 5x |
CDISCTealDataConnector$new(connection, connectors) |
93 |
} |
1 |
#' Load data from connection |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Load data from connection. Function used on [`TealDatasetConnector`] and |
|
5 |
#' [`TealDataset`] to obtain data from connection. |
|
6 |
#' |
|
7 |
#' @param x (`TealDatasetConnector` or `TealDataset`) |
|
8 |
#' @param args (`NULL` or named `list`)\cr |
|
9 |
#' additional dynamic arguments passed to function which loads the data. |
|
10 |
#' @param try (`logical`) whether perform function evaluation inside `try` clause |
|
11 |
#' @param conn Optional (`TealDataConnection`) object required to pull the data. |
|
12 |
#' @param ... not used, only for support of S3 |
|
13 |
#' |
|
14 |
#' @return `x` with loaded `dataset` object |
|
15 |
#' @export |
|
16 |
load_dataset <- function(x, ...) { |
|
17 | 121x |
UseMethod("load_dataset") |
18 |
} |
|
19 | ||
20 |
#' @rdname load_dataset |
|
21 |
#' @examples |
|
22 |
#' |
|
23 |
#' # TealDataset -------- |
|
24 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
25 |
#' ADSL_dataset <- dataset("ADSL", x = ADSL) |
|
26 |
#' |
|
27 |
#' load_dataset(ADSL_dataset) |
|
28 |
#' @export |
|
29 |
load_dataset.TealDataset <- function(x, ...) { # nolint |
|
30 | 45x |
check_ellipsis(...) |
31 | 45x |
return(invisible(x$get_dataset())) |
32 |
} |
|
33 | ||
34 |
#' @rdname load_dataset |
|
35 |
#' @examples |
|
36 |
#' |
|
37 |
#' # TealDatasetConnector -------- |
|
38 |
#' |
|
39 |
#' random_data_connector <- function(dataname) { |
|
40 |
#' fun_dataset_connector( |
|
41 |
#' dataname = dataname, |
|
42 |
#' fun = teal.data::example_cdisc_data, |
|
43 |
#' fun_args = list(dataname = dataname), |
|
44 |
#' ) |
|
45 |
#' } |
|
46 |
#' |
|
47 |
#' adsl <- random_data_connector(dataname = "ADSL") |
|
48 |
#' load_dataset(adsl) |
|
49 |
#' get_dataset(adsl) |
|
50 |
#' |
|
51 |
#' adae <- random_data_connector(dataname = "ADAE") |
|
52 |
#' load_dataset(adae) |
|
53 |
#' get_dataset(adae) |
|
54 |
#' @export |
|
55 |
load_dataset.TealDatasetConnector <- function(x, args = NULL, try = FALSE, conn = NULL, ...) { # nolint |
|
56 | 76x |
check_ellipsis(...) |
57 | 76x |
if (!is.null(conn)) { |
58 | ! |
stopifnot(inherits(conn, "TealDataConnection")) |
59 | ||
60 | ! |
conn$open() |
61 | ! |
conn_obj <- conn$get_conn() |
62 | ||
63 | ! |
x$get_pull_callable()$assign_to_env("conn", conn_obj) |
64 |
} |
|
65 | ||
66 | 76x |
x$pull(args = args, try = try) |
67 | ||
68 | 76x |
return(invisible(x)) |
69 |
} |
|
70 | ||
71 |
#' Load datasets |
|
72 |
#' |
|
73 |
#' @description `r lifecycle::badge("stable")` |
|
74 |
#' |
|
75 |
#' @param x ([`TealData`], [`TealDataset`] or [`TealDatasetConnector`]) |
|
76 |
#' @param args (`NULL` or named `list`)\cr |
|
77 |
#' additional dynamic arguments passed to function which loads the data. Applicable only on [`TealDatasetConnector`]) |
|
78 |
#' @param try (`logical`)\cr |
|
79 |
#' whether perform function evaluation inside `try` clause. Applicable only on [`TealDatasetConnector`]) |
|
80 |
#' @param ... (not used)\cr |
|
81 |
#' only for support of S3 |
|
82 |
#' |
|
83 |
#' @export |
|
84 |
#' @return If executed in the interactive session shiny app is opened to load the data. If executed in |
|
85 |
#' shiny application - it returns shiny server module. |
|
86 |
load_datasets <- function(x, ...) { |
|
87 | ! |
UseMethod("load_datasets") |
88 |
} |
|
89 | ||
90 |
#' @rdname load_datasets |
|
91 |
#' @examples |
|
92 |
#' |
|
93 |
#' # TealDataset ------ |
|
94 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
95 |
#' x <- dataset("ADSL", x = ADSL) |
|
96 |
#' |
|
97 |
#' load_datasets(x) |
|
98 |
#' @export |
|
99 |
load_datasets.TealDataset <- function(x, ...) { # nolint |
|
100 | ! |
check_ellipsis(...) |
101 | ! |
return(invisible(x$get_dataset())) |
102 |
} |
|
103 | ||
104 |
#' @rdname load_datasets |
|
105 |
#' @examples |
|
106 |
#' |
|
107 |
#' # TealDatasetConnector ------ |
|
108 |
#' random_data_connector <- function(dataname) { |
|
109 |
#' fun_dataset_connector( |
|
110 |
#' dataname = dataname, |
|
111 |
#' fun = teal.data::example_cdisc_data, |
|
112 |
#' fun_args = list(dataname = dataname), |
|
113 |
#' ) |
|
114 |
#' } |
|
115 |
#' |
|
116 |
#' adsl <- random_data_connector(dataname = "ADSL") |
|
117 |
#' load_datasets(adsl) |
|
118 |
#' get_dataset(adsl) |
|
119 |
#' |
|
120 |
#' adae <- random_data_connector(dataname = "ADAE") |
|
121 |
#' load_datasets(adae) |
|
122 |
#' get_dataset(adae) |
|
123 |
#' @export |
|
124 |
load_datasets.TealDatasetConnector <- function(x, args = NULL, try = FALSE, ...) { # nolint |
|
125 | ! |
check_ellipsis(...) |
126 | ! |
x$pull(args = args, try = try) |
127 | ! |
return(invisible(x)) |
128 |
} |
|
129 | ||
130 | ||
131 |
#' @rdname load_datasets |
|
132 |
#' @export |
|
133 |
#' @examples |
|
134 |
#' |
|
135 |
#' # TealDataConnector -------- |
|
136 |
#' random_data_connector <- function(dataname) { |
|
137 |
#' fun_dataset_connector( |
|
138 |
#' dataname = dataname, |
|
139 |
#' fun = teal.data::example_cdisc_data, |
|
140 |
#' fun_args = list(dataname = dataname), |
|
141 |
#' ) |
|
142 |
#' } |
|
143 |
#' |
|
144 |
#' adsl <- random_data_connector(dataname = "ADSL") |
|
145 |
#' adrs <- random_data_connector(dataname = "ADRS") |
|
146 |
#' |
|
147 |
#' rdc <- cdisc_data(adsl, adrs) |
|
148 |
#' \dontrun{ |
|
149 |
#' load_datasets(rdc) |
|
150 |
#' } |
|
151 |
load_datasets.TealDataConnector <- function(x, ...) { # nolint |
|
152 | ! |
check_ellipsis(...) |
153 | ! |
if (interactive()) { |
154 | ! |
x$launch() |
155 |
} else { |
|
156 | ! |
return(invisible(x)) |
157 |
} |
|
158 |
} |
|
159 | ||
160 |
#' @rdname load_datasets |
|
161 |
#' @export |
|
162 |
#' @examples |
|
163 |
#' |
|
164 |
#' # TealData -------- |
|
165 |
#' random_data_connector <- function(dataname) { |
|
166 |
#' fun_dataset_connector( |
|
167 |
#' dataname = dataname, |
|
168 |
#' fun = teal.data::example_cdisc_data, |
|
169 |
#' fun_args = list(dataname = dataname), |
|
170 |
#' ) |
|
171 |
#' } |
|
172 |
#' |
|
173 |
#' adsl <- random_data_connector(dataname = "ADSL") |
|
174 |
#' adlb <- random_data_connector(dataname = "ADLB") |
|
175 |
#' adrs <- random_data_connector(dataname = "ADRS") |
|
176 |
#' |
|
177 |
#' tc <- cdisc_data(adsl, adlb, adrs) |
|
178 |
#' \dontrun{ |
|
179 |
#' load_datasets(tc) |
|
180 |
#' } |
|
181 |
load_datasets.TealData <- function(x, ...) { # nolint |
|
182 | ! |
check_ellipsis(...) |
183 | ! |
if (interactive()) { |
184 | ! |
x$launch() |
185 |
} else { |
|
186 | ! |
return(invisible(x)) |
187 |
} |
|
188 |
} |
1 |
## TealData ==== |
|
2 |
#' @title Manage multiple `TealDataConnector`, `TealDatasetConnector` and `TealDataset` objects. |
|
3 |
#' |
|
4 |
#' @description `r lifecycle::badge("experimental")` |
|
5 |
#' Class manages `TealDataConnector`, `TealDatasetConnector` and |
|
6 |
#' `TealDataset` objects and aggregate them in one collection. |
|
7 |
#' Class also decides whether to launch app before initialize teal application. |
|
8 |
#' |
|
9 |
#' @param ... (`TealDataConnector`, `TealDataset`, `TealDatasetConnector`)\cr |
|
10 |
#' objects |
|
11 |
#' @param join_keys (`JoinKeys`) or a single (`JoinKeySet`)\cr |
|
12 |
#' (optional) object with dataset column relationships used for joining. |
|
13 |
#' If empty then an empty `JoinKeys` object is passed by default. |
|
14 |
#' @param check (`logical`) reproducibility check - whether evaluated preprocessing code gives the same objects |
|
15 |
#' as provided in arguments. Check is run only if flag is true and preprocessing code is not empty. |
|
16 |
#' |
|
17 |
#' @examples |
|
18 |
#' adsl_cf <- callable_function(teal.data::example_cdisc_data)$set_args(list(dataname = "ADSL")) |
|
19 |
#' adlb_cf <- callable_function(teal.data::example_cdisc_data)$set_args(list(dataname = "ADLB")) |
|
20 |
#' adrs_cf <- callable_function(teal.data::example_cdisc_data)$set_args(list(dataname = "ADRS")) |
|
21 |
#' adtte_cf <- callable_function(teal.data::example_cdisc_data)$set_args(list(dataname = "ADTTE")) |
|
22 |
#' x1 <- cdisc_dataset_connector("ADSL", adsl_cf, keys = get_cdisc_keys("ADSL")) |
|
23 |
#' x2 <- cdisc_dataset_connector("ADRS", adrs_cf, keys = get_cdisc_keys("ADRS")) |
|
24 |
#' x3 <- cdisc_dataset( |
|
25 |
#' dataname = "ADAE", |
|
26 |
#' x = teal.data::example_cdisc_data("ADAE"), |
|
27 |
#' code = "library(teal.data)\nADAE <- teal.data::example_cdisc_data(\"ADAE\")" |
|
28 |
#' ) |
|
29 |
#' x4 <- cdisc_dataset_connector("ADTTE", adtte_cf, keys = get_cdisc_keys("ADTTE")) |
|
30 |
#' tc <- teal.data:::TealData$new(x1, x2, x3, x4) |
|
31 |
#' tc$get_datanames() |
|
32 |
#' \dontrun{ |
|
33 |
#' tc$launch() |
|
34 |
#' get_datasets(tc) # equivalent to tc$get_datasets() |
|
35 |
#' tc$get_dataset("ADAE") |
|
36 |
#' tc$check() |
|
37 |
#' } |
|
38 |
#' |
|
39 |
#' x <- cdisc_dataset( |
|
40 |
#' dataname = "ADSL", |
|
41 |
#' x = teal.data::example_cdisc_data("ADSL"), |
|
42 |
#' code = "library(teal.data)\nADSL <- teal.data::example_cdisc_data(\"ADSL\")" |
|
43 |
#' ) |
|
44 |
#' |
|
45 |
#' x2 <- cdisc_dataset_connector("ADTTE", adtte_cf, keys = get_cdisc_keys("ADTTE")) |
|
46 |
#' tc <- teal.data:::TealData$new(x, x2) |
|
47 |
#' \dontrun{ |
|
48 |
#' # This errors as we have not pulled the data |
|
49 |
#' # tc$get_datasets() |
|
50 |
#' # pull the data and then we can get the datasets |
|
51 |
#' tc$launch() |
|
52 |
#' tc$get_datasets() |
|
53 |
#' get_raw_data(tc) |
|
54 |
#' } |
|
55 |
#' |
|
56 |
TealData <- R6::R6Class( # nolint |
|
57 |
classname = "TealData", |
|
58 |
inherit = TealDataAbstract, |
|
59 |
## __Public Methods ==== |
|
60 |
public = list( |
|
61 |
#' @description |
|
62 |
#' Create a new object of `TealData` class |
|
63 |
initialize = function(..., check = FALSE, join_keys = teal.data::join_keys()) { |
|
64 | 133x |
checkmate::assert_class(join_keys, "JoinKeys") |
65 | ||
66 | 133x |
dot_args <- list(...) |
67 | 133x |
is_teal_data <- checkmate::test_list( |
68 | 133x |
dot_args, |
69 | 133x |
types = c("TealDataConnector", "TealDataset", "TealDatasetConnector") |
70 |
) |
|
71 | 133x |
if (!all(is_teal_data)) { |
72 | 2x |
stop("All elements should be of TealDataset(Connector) or TealDataConnector class") |
73 |
} |
|
74 | ||
75 | 131x |
datanames <- unlist(lapply(dot_args, get_dataname)) |
76 | 131x |
private$check_names(datanames) |
77 | ||
78 | 130x |
private$datasets <- dot_args |
79 | ||
80 | 130x |
self$set_check(check) |
81 | ||
82 | 130x |
private$pull_code <- CodeClass$new() |
83 | 130x |
private$mutate_code <- CodeClass$new() |
84 | ||
85 | 130x |
private$join_keys <- join_keys |
86 | ||
87 | 130x |
self$id <- sample.int(1e11, 1, useHash = TRUE) |
88 | ||
89 | 130x |
logger::log_trace( |
90 | 130x |
"TealData initialized with data: { paste(self$get_datanames(), collapse = ' ') }." |
91 |
) |
|
92 | 130x |
return(invisible(self)) |
93 |
}, |
|
94 |
#' @description |
|
95 |
#' Creates a copy of the object with keeping valid references |
|
96 |
#' between `TealDataset` and `TealDatasetConnector` objects |
|
97 |
#' @param deep (`logical(1)`)\cr |
|
98 |
#' argument passed to `clone` method. If `TRUE` deep copy is made |
|
99 |
#' @return self invisible |
|
100 |
copy = function(deep = FALSE) { |
|
101 | 2x |
new_self <- self$clone(deep = deep) |
102 | 2x |
new_self$reassign_datasets_vars() |
103 | 2x |
logger::log_trace("TealData$copy{if (deep) ' deep-' else ' '}copied self.") |
104 | 2x |
invisible(new_self) |
105 |
}, |
|
106 |
#' @description |
|
107 |
#' Prints this `TealData`. |
|
108 |
#' |
|
109 |
#' @param ... additional arguments to the printing method |
|
110 |
#' @return invisibly self |
|
111 |
print = function(...) { |
|
112 | 1x |
check_ellipsis(...) |
113 | ||
114 | 1x |
cat(sprintf( |
115 | 1x |
"A %s object containing %d TealDataset/TealDatasetConnector object(s) as element(s):\n", |
116 | 1x |
class(self)[1], |
117 | 1x |
length(private$datasets) |
118 |
)) |
|
119 | ||
120 | 1x |
for (i in seq_along(private$datasets)) { |
121 | 2x |
cat(sprintf("--> Element %d:\n", i)) |
122 | 2x |
print(private$datasets[[i]]) |
123 |
} |
|
124 | ||
125 | 1x |
invisible(self) |
126 |
}, |
|
127 |
# ___ getters ==== |
|
128 |
#' @description |
|
129 |
#' Get data connectors. |
|
130 |
#' |
|
131 |
#' @return (`list`) with all `TealDatasetConnector` or `TealDataConnector` objects. |
|
132 |
get_connectors = function() { |
|
133 | 5x |
return(Filter( |
134 | 5x |
function(x) { |
135 | 9x |
inherits(x, "TealDatasetConnector") || inherits(x, "TealDataConnector") |
136 |
}, |
|
137 | 5x |
private$datasets |
138 |
)) |
|
139 |
}, |
|
140 |
#' @description |
|
141 |
#' Get all datasets and all dataset connectors |
|
142 |
#' |
|
143 |
#' @param dataname (`character` value)\cr |
|
144 |
#' name of dataset connector to be returned. If `NULL`, all connectors are returned. |
|
145 |
#' |
|
146 |
#' @return `list` with all datasets and all connectors |
|
147 |
get_items = function(dataname = NULL) { |
|
148 | 105x |
checkmate::assert_string(dataname, null.ok = TRUE) |
149 | ||
150 | 105x |
get_sets <- function(x) { |
151 | 195x |
if (inherits(x, "TealDataConnector")) { |
152 | 12x |
x$get_items() |
153 |
} else { |
|
154 | 183x |
x |
155 |
} |
|
156 |
} |
|
157 | ||
158 | 105x |
sets <- unlist(lapply(private$datasets, get_sets)) |
159 | 105x |
names(sets) <- vapply(sets, get_dataname, character(1)) |
160 | ||
161 | 104x |
if (checkmate::test_string(dataname)) { |
162 | 5x |
if (!(dataname %in% self$get_datanames())) { |
163 | 2x |
stop(paste("dataset", dataname, "not found")) |
164 |
} |
|
165 | 3x |
return(sets[[dataname]]) |
166 |
} else { |
|
167 | 99x |
return(sets) |
168 |
} |
|
169 |
}, |
|
170 | ||
171 |
#' @description |
|
172 |
#' Get join keys between two datasets. |
|
173 |
#' |
|
174 |
#' @param dataset_1 (`character`) name of first dataset. |
|
175 |
#' @param dataset_2 (`character`) name of second dataset. |
|
176 |
#' @return (`character`) named character vector x with names(x) the |
|
177 |
#' columns of `dataset_1` and the values of `(x)` the corresponding join |
|
178 |
#' keys in `dataset_2` or `character(0)` if no relationship |
|
179 |
get_join_keys = function(dataset_1, dataset_2) { |
|
180 | 179x |
if (missing(dataset_1) && missing(dataset_2)) { |
181 | 16x |
private$join_keys |
182 |
} else { |
|
183 | 163x |
private$join_keys$get(dataset_1, dataset_2) |
184 |
} |
|
185 |
}, |
|
186 | ||
187 |
#' @description |
|
188 |
#' returns the parents list of the datasets. |
|
189 |
#' |
|
190 |
#' @return named (`list`) of the parents of all datasets. |
|
191 |
get_parents = function() { |
|
192 | 1x |
private$join_keys$get_parents() |
193 |
}, |
|
194 | ||
195 |
# ___ shiny ==== |
|
196 | ||
197 |
#' @description |
|
198 |
#' |
|
199 |
#' Get a shiny-module UI to render the necessary app to |
|
200 |
#' derive `TealDataConnector` object's data |
|
201 |
#' |
|
202 |
#' @param id (`character`) item ID for the shiny module |
|
203 |
#' @return the `shiny` `ui` function |
|
204 |
get_ui = function(id) { |
|
205 | 4x |
if (is.null(private$ui)) { |
206 | ! |
div(id = id, "Data Loaded") |
207 |
} else { |
|
208 | 4x |
private$ui(id) |
209 |
} |
|
210 |
}, |
|
211 |
#' @description |
|
212 |
#' |
|
213 |
#' Get a shiny-module server to render the necessary app to |
|
214 |
#' derive `TealDataConnector` object's data |
|
215 |
#' |
|
216 |
#' @return `shiny` server module. |
|
217 |
get_server = function() { |
|
218 | ! |
if (is.null(private$server)) { |
219 | ! |
return( |
220 | ! |
function(id) { |
221 | ! |
moduleServer( |
222 | ! |
id = id, |
223 | ! |
module = function(input, output, session) { |
224 | ! |
reactive(self) |
225 |
} |
|
226 |
) |
|
227 |
} |
|
228 |
) |
|
229 |
} else { |
|
230 | ! |
function(id) { |
231 | ! |
moduleServer( |
232 | ! |
id = id, |
233 | ! |
module = private$server |
234 |
) |
|
235 |
} |
|
236 |
} |
|
237 |
}, |
|
238 |
#' @description |
|
239 |
#' |
|
240 |
#' Launch an app that allows to run the user-interfaces of all |
|
241 |
#' `TealDataConnector` and `TealDatasetConnector` modules |
|
242 |
#' |
|
243 |
#' This piece is mainly used for debugging. |
|
244 |
launch = function() { |
|
245 |
# if no data connectors can append any dataset connectors |
|
246 |
# and not load an app |
|
247 | ! |
if (self$is_pulled()) { |
248 | ! |
stop("All the datasets have already been pulled.") |
249 |
} |
|
250 | ||
251 |
# otherwise load TealDataConnector and |
|
252 |
# TealDatasetConnector with shiny app |
|
253 | ! |
shinyApp( |
254 | ! |
ui = fluidPage( |
255 | ! |
theme = get_teal_bs_theme(), |
256 | ! |
fluidRow( |
257 | ! |
column( |
258 | ! |
width = 8, |
259 | ! |
offset = 2, |
260 | ! |
self$get_ui(id = "main_app"), |
261 | ! |
shinyjs::hidden( |
262 | ! |
tags$div( |
263 | ! |
id = "data_loaded", |
264 | ! |
div( |
265 | ! |
h3("Data successfully loaded."), |
266 | ! |
p("You can close this window and get back to R console.") |
267 |
) |
|
268 |
) |
|
269 |
), |
|
270 | ! |
include_js_files(), |
271 | ! |
br() |
272 |
) |
|
273 |
) |
|
274 |
), |
|
275 | ! |
server = function(input, output, session) { |
276 | ! |
session$onSessionEnded(stopApp) |
277 | ! |
dat <- self$get_server()(id = "main_app") |
278 | ||
279 | ! |
observeEvent(dat(), { |
280 | ! |
if (self$is_pulled()) { |
281 | ! |
shinyjs::show("data_loaded") |
282 | ! |
stopApp() |
283 |
} |
|
284 |
}) |
|
285 | ! |
NULL |
286 |
} |
|
287 |
) |
|
288 |
}, |
|
289 | ||
290 |
# ___ mutate ==== |
|
291 |
#' @description |
|
292 |
#' Change join_keys for a given pair of dataset names |
|
293 |
#' @param dataset_1,dataset_2 (`character`) datasets for which join_keys are to be returned |
|
294 |
#' @param val (named `character`) column names used to join |
|
295 |
#' @return (`self`) invisibly for chaining |
|
296 |
mutate_join_keys = function(dataset_1, dataset_2, val) { |
|
297 | 3x |
private$join_keys$mutate(dataset_1, dataset_2, val) |
298 |
}, |
|
299 | ||
300 |
# ___ check ==== |
|
301 |
#' @description |
|
302 |
#' Check there is consistency between the datasets and join_keys |
|
303 |
#' @return raise and error or invisible `TRUE` |
|
304 |
check_metadata = function() { |
|
305 | 52x |
if (isFALSE(self$is_pulled())) { |
306 |
# all the checks below required data to be already pulled |
|
307 | 4x |
return(invisible(TRUE)) |
308 |
} |
|
309 | ||
310 | 48x |
for (dataset in self$get_datasets()) { |
311 | 82x |
dataname <- get_dataname(dataset) |
312 | 82x |
dataset_colnames <- dataset$get_colnames() |
313 | ||
314 |
# expected columns in this dataset from JoinKeys specification |
|
315 | 82x |
join_key_cols <- unique(unlist(lapply(self$get_join_keys(dataname), names))) |
316 | 82x |
if (!is.null(join_key_cols) && !all(join_key_cols %in% dataset_colnames)) { |
317 | 3x |
stop( |
318 | 3x |
paste( |
319 | 3x |
"The join key specification requires dataset", |
320 | 3x |
dataname, |
321 | 3x |
"to contain the following columns:", |
322 | 3x |
paste(join_key_cols, collapse = ", ") |
323 |
) |
|
324 |
) |
|
325 |
} |
|
326 | ||
327 |
# check if primary keys in dataset |
|
328 | 79x |
primary_key_cols <- self$get_join_keys(dataname, dataname) |
329 | 79x |
if (!is.null(primary_key_cols) && !all(primary_key_cols %in% dataset_colnames)) { |
330 | ! |
stop( |
331 | ! |
paste( |
332 | ! |
"The primary keys specification requires dataset", |
333 | ! |
dataname, |
334 | ! |
"to contain the following columns:", |
335 | ! |
paste(primary_key_cols, collapse = ", ") |
336 |
) |
|
337 |
) |
|
338 |
} |
|
339 | 79x |
dataset$check_keys() |
340 |
} |
|
341 | ||
342 | 43x |
logger::log_trace("TealData$check_metadata metadata check passed.") |
343 | ||
344 | 43x |
return(invisible(TRUE)) |
345 |
} |
|
346 |
), |
|
347 | ||
348 |
## __Private Fields ==== |
|
349 |
private = list( |
|
350 |
join_keys = NULL, |
|
351 |
ui = function(id) { |
|
352 | 4x |
ns <- NS(id) |
353 | ||
354 |
# connectors ui(s) + submit button |
|
355 | 4x |
fluidPage( |
356 | 4x |
include_js_files(), |
357 | 4x |
theme = get_teal_bs_theme(), |
358 | 4x |
shinyjs::hidden( |
359 | 4x |
column( |
360 | 4x |
id = ns("delayed_data"), |
361 | 4x |
width = 8, |
362 | 4x |
offset = 2, |
363 | 4x |
div( |
364 | 4x |
tagList( |
365 | 4x |
lapply( |
366 | 4x |
private$datasets, |
367 | 4x |
function(x) { |
368 | 6x |
div( |
369 | 6x |
if (inherits(x, "TealDataConnector")) { |
370 | ! |
ui <- x$get_ui(id = ns(x$id)) |
371 | ! |
if (is.null(ui)) { |
372 | ! |
ui <- div( |
373 | ! |
h4("TealDataset Connector for: ", lapply(x$get_datanames(), code)), |
374 | ! |
p(icon("check"), "Ready to Load") |
375 |
) |
|
376 |
} |
|
377 | ! |
ui |
378 | 6x |
} else if (inherits(x, "TealDatasetConnector")) { |
379 | ! |
ui <- x$get_ui(id = ns(paste0(x$get_datanames(), collapse = "_"))) |
380 | ! |
if (is.null(ui)) { |
381 | ! |
ui <- div( |
382 | ! |
h4("TealDataset Connector for: ", code(x$get_dataname())), |
383 | ! |
p(icon("check"), "Ready to Load") |
384 |
) |
|
385 |
} |
|
386 | ! |
ui |
387 |
} else { |
|
388 | 6x |
div(h4("Data(set) for: ", lapply(x$get_datanames(), code)), p(icon("check"), "Loaded")) |
389 |
}, |
|
390 | 6x |
br() |
391 |
) |
|
392 |
} |
|
393 |
), |
|
394 | 4x |
actionButton(inputId = ns("submit"), label = "Submit all") |
395 |
), |
|
396 | 4x |
`data-proxy-click` = ns("submit") # handled by jscode in custom.js - hit enter to submit |
397 |
) |
|
398 |
) |
|
399 |
) |
|
400 |
) |
|
401 |
}, |
|
402 |
server = function(input, output, session) { |
|
403 | ! |
logger::log_trace("TealData$server initializing...") |
404 | ||
405 | ! |
shinyjs::show("delayed_data") |
406 | ! |
for (dc in self$get_connectors()) { |
407 | ! |
if (inherits(dc, "TealDataConnector")) { |
408 | ! |
dc$get_preopen_server()(id = dc$id) |
409 |
} |
|
410 |
} |
|
411 | ! |
rv <- reactiveVal(NULL) |
412 | ! |
observeEvent(input$submit, { |
413 | ! |
logger::log_trace("TealData$server@1 submit button clicked.") |
414 |
# load data from all connectors |
|
415 | ! |
for (dc in self$get_connectors()) { |
416 | ! |
if (inherits(dc, "TealDataConnector")) { |
417 | ! |
dc$get_server()( |
418 | ! |
id = dc$id, |
419 | ! |
connection = dc$get_connection(), |
420 | ! |
connectors = dc$get_items() |
421 |
) |
|
422 | ! |
} else if (inherits(dc, "TealDatasetConnector")) { |
423 | ! |
dc$get_server()(id = dc$get_dataname()) |
424 |
} |
|
425 | ! |
if (dc$is_failed()) { |
426 | ! |
break |
427 |
} |
|
428 |
} |
|
429 | ||
430 | ! |
if (self$is_pulled()) { |
431 | ! |
logger::log_trace("TealData$server@1 data is pulled.") |
432 | ! |
withProgress(value = 1, message = "Checking data reproducibility", { |
433 |
# We check first and then mutate. |
|
434 |
# mutate_code is reproducible by default we assume that we don't |
|
435 |
# have to check the result of the re-evaluation of the code |
|
436 | ! |
self$check_reproducibility() |
437 |
}) |
|
438 | ||
439 | ! |
withProgress(value = 1, message = "Executing processing code", { |
440 | ! |
self$execute_mutate() |
441 | ! |
self$check_metadata() |
442 |
}) |
|
443 | ! |
logger::log_info("Data ready to pass to the application.") |
444 | ! |
shinyjs::hide("delayed_data") |
445 | ! |
rv(self) |
446 |
} |
|
447 |
}) |
|
448 | ! |
return(rv) |
449 |
} |
|
450 |
) |
|
451 |
) |
1 |
#' Get dataset label attribute |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' @param data \code{data.frame} from which attribute is extracted |
|
6 |
#' |
|
7 |
#' @return (\code{character}) label or \code{NULL} if it is missing |
|
8 |
#' |
|
9 |
#' @export |
|
10 |
#' |
|
11 |
#' @examples |
|
12 |
#' data_label(example_cdisc_data("ADSL")) |
|
13 |
data_label <- function(data) { |
|
14 | 179x |
attr(data, "label") |
15 |
} |
|
16 | ||
17 |
#' Set dataset label attribute |
|
18 |
#' |
|
19 |
#' @description `r lifecycle::badge("stable")` |
|
20 |
#' |
|
21 |
#' @param x \code{data.frame} for which attribute is set |
|
22 |
#' @param value (\code{character}) label |
|
23 |
#' |
|
24 |
#' @return modified \code{x} object |
|
25 |
#' |
|
26 |
#' @export |
|
27 |
#' |
|
28 |
#' @examples |
|
29 |
#' x <- teal.data::example_cdisc_data("ADSL") |
|
30 |
#' data_label(x) <- "My custom label" |
|
31 |
#' data_label(x) |
|
32 |
`data_label<-` <- function(x, value) { # nolint |
|
33 | ! |
stopifnot(is.data.frame(x)) |
34 | ! |
checkmate::assert_string(value) |
35 | ||
36 | ! |
attr(x, "label") <- value |
37 | ! |
x |
38 |
} |
|
39 | ||
40 |
#' Function that returns the default keys for a `CDISC` dataset by name |
|
41 |
#' |
|
42 |
#' @description `r lifecycle::badge("stable")` |
|
43 |
#' |
|
44 |
#' @param dataname name of the `CDISC` dataset |
|
45 |
#' |
|
46 |
#' @return \code{keys} object |
|
47 |
#' |
|
48 |
#' @export |
|
49 |
#' |
|
50 |
#' @examples |
|
51 |
#' get_cdisc_keys("ADSL") |
|
52 |
get_cdisc_keys <- function(dataname) { |
|
53 | 306x |
checkmate::assert_string(dataname) |
54 | ||
55 | 306x |
if (!(dataname %in% names(default_cdisc_keys))) { |
56 | ! |
stop(paste(sprintf("get_cdisc_keys does not support datasets called %s", dataname), |
57 | ! |
" Please specify the keys directly, for example:", |
58 | ! |
sprintf( |
59 | ! |
" cdisc_dataset(dataname = \"%s\", keys = c(\"STUDYID\", \"USUBJID\", ...), parent = \"ADSL\", ...)", |
60 | ! |
dataname |
61 |
), |
|
62 | ! |
sep = "\n" |
63 |
)) |
|
64 |
} else { |
|
65 | 306x |
cdisc_keys <- default_cdisc_keys[[dataname]]$primary |
66 | ||
67 | 306x |
return(cdisc_keys) |
68 |
} |
|
69 |
} |
|
70 | ||
71 |
#' Extracts dataset and variable labels from a dataset. |
|
72 |
#' |
|
73 |
#' @description `r lifecycle::badge("stable")` |
|
74 |
#' |
|
75 |
#' @param data (`data.frame`) table to extract the labels from |
|
76 |
#' @param fill (`logical(1)`) if `TRUE`, the function will return variable names for columns with non-existent labels; |
|
77 |
#' otherwise will return `NA` for them |
|
78 |
#' |
|
79 |
#' @return `list` with two keys: `dataset_labels` and `column_labels` |
|
80 |
#' |
|
81 |
#' @export |
|
82 |
#' |
|
83 |
#' @examples |
|
84 |
#' iris_with_labels <- iris |
|
85 |
#' attr(iris_with_labels, "label") <- "Custom iris dataset with labels" |
|
86 |
#' attr(iris_with_labels["Sepal.Length"], "label") <- c(`Sepal.Length` = "Sepal Length") |
|
87 |
#' get_labels(iris_with_labels, fill = TRUE) |
|
88 |
#' get_labels(iris_with_labels, fill = FALSE) |
|
89 |
get_labels <- function(data, fill = TRUE) { |
|
90 | 8x |
stopifnot(is.data.frame(data)) |
91 | 8x |
checkmate::assert_flag(fill) |
92 | ||
93 | 8x |
column_labels <- Map(function(col, colname) { |
94 | 27x |
label <- attr(col, "label") |
95 | 27x |
if (is.null(label)) { |
96 | 25x |
if (fill) { |
97 | 20x |
colname |
98 |
} else { |
|
99 | 8x |
NA_character_ |
100 |
} |
|
101 |
} else { |
|
102 | 2x |
if (!checkmate::test_string(label, na.ok = TRUE)) { |
103 | ! |
stop("label for variable ", colname, " is not a character string") |
104 |
} |
|
105 | 2x |
as.vector(label) # because label might be a named vector |
106 |
} |
|
107 | 8x |
}, data, colnames(data)) |
108 | 8x |
column_labels <- unlist(column_labels, recursive = FALSE, use.names = TRUE) |
109 | ||
110 | 8x |
list("dataset_label" = data_label(data), "column_labels" = column_labels) |
111 |
} |
1 |
## TealDataset ==== |
|
2 |
#' |
|
3 |
#' |
|
4 |
#' @title R6 Class representing a dataset with its attributes |
|
5 |
#' |
|
6 |
#' @description `r lifecycle::badge("stable")` |
|
7 |
#' Any `data.frame` object can be stored inside this object. |
|
8 |
#' Some attributes like colnames, dimension or column names for a specific type will |
|
9 |
#' be automatically derived. |
|
10 |
#' |
|
11 |
#' @param dataname (`character`)\cr |
|
12 |
#' A given name for the dataset it may not contain spaces |
|
13 |
#' @param x (`data.frame`)\cr |
|
14 |
#' @param keys optional, (`character`)\cr |
|
15 |
#' Vector with primary keys |
|
16 |
#' @param code (`character`)\cr |
|
17 |
#' A character string defining the code needed to produce the data set in `x`. |
|
18 |
#' `initialize()` and `recreate()` accept code as `CodeClass` |
|
19 |
#' which is also needed to preserve the code uniqueness and correct order. |
|
20 |
#' @param label (`character`)\cr |
|
21 |
#' Label to describe the dataset |
|
22 |
#' @param vars (named `list`)) \cr |
|
23 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
24 |
#' other constant value, this/these object(s) should be included as named |
|
25 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
26 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
27 |
#' It is recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
28 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
29 |
#' are included to this object as local `vars` and they cannot be modified |
|
30 |
#' within another dataset. |
|
31 |
#' @param metadata (named `list` or `NULL`) \cr |
|
32 |
#' Field containing metadata about the dataset. Each element of the list |
|
33 |
#' should be atomic and of length one. |
|
34 |
#' |
|
35 |
#' @seealso [`MAETealDataset`] |
|
36 |
#' |
|
37 |
TealDataset <- R6::R6Class( # nolint |
|
38 |
"TealDataset", |
|
39 | ||
40 |
## __Public Methods ==== |
|
41 |
public = list( |
|
42 |
#' @description |
|
43 |
#' Create a new object of `TealDataset` class |
|
44 |
initialize = function(dataname, |
|
45 |
x, |
|
46 |
keys = character(0), |
|
47 |
code = character(0), |
|
48 |
label = character(0), |
|
49 |
vars = list(), |
|
50 |
metadata = NULL) { |
|
51 | 490x |
checkmate::assert_string(dataname) |
52 | 490x |
checkmate::assert_data_frame(x) |
53 | 490x |
checkmate::assert_character(keys, any.missing = FALSE) |
54 | 490x |
checkmate::assert( |
55 | 490x |
checkmate::check_character(code, max.len = 1, any.missing = FALSE), |
56 | 490x |
checkmate::check_class(code, "CodeClass") |
57 |
) |
|
58 |
# label might be NULL also because of taking label attribute from data.frame - missing attr is NULL |
|
59 | 490x |
checkmate::assert_character(label, max.len = 1, null.ok = TRUE, any.missing = FALSE) |
60 | 490x |
checkmate::assert_list(vars, names = "named") |
61 | ||
62 | 490x |
validate_metadata(metadata) |
63 | ||
64 | 487x |
private$.raw_data <- x |
65 | 487x |
private$metadata <- metadata |
66 | ||
67 | 487x |
private$set_dataname(dataname) |
68 | 487x |
self$set_vars(vars) |
69 | 487x |
self$set_dataset_label(label) |
70 | 487x |
self$set_keys(keys) |
71 | ||
72 |
# needed if recreating dataset - we need to preserve code order and uniqueness |
|
73 | 487x |
private$code <- CodeClass$new() |
74 | 487x |
if (is.character(code)) { |
75 | 281x |
self$set_code(code) |
76 |
} else { |
|
77 | 206x |
private$code$append(code) |
78 |
} |
|
79 | ||
80 | 487x |
logger::log_trace("TealDataset initialized for dataset: { deparse1(self$get_dataname()) }.") |
81 | 487x |
return(invisible(self)) |
82 |
}, |
|
83 | ||
84 |
#' @description |
|
85 |
#' Recreate this `TealDataset` with its current attributes. |
|
86 |
#' |
|
87 |
#' @return a new object of the `TealDataset` class |
|
88 |
recreate = function(dataname = self$get_dataname(), |
|
89 |
x = self$get_raw_data(), |
|
90 |
keys = self$get_keys(), |
|
91 |
code = private$code, |
|
92 |
label = self$get_dataset_label(), |
|
93 |
vars = list(), |
|
94 |
metadata = self$get_metadata()) { |
|
95 | 53x |
res <- self$initialize( |
96 | 53x |
dataname = dataname, |
97 | 53x |
x = x, |
98 | 53x |
keys = keys, |
99 | 53x |
code = code, |
100 | 53x |
label = label, |
101 | 53x |
vars = vars, |
102 | 53x |
metadata = metadata |
103 |
) |
|
104 | 53x |
logger::log_trace("TealDataset$recreate recreated dataset: { deparse1(self$get_dataname()) }.") |
105 | 53x |
return(res) |
106 |
}, |
|
107 |
#' @description |
|
108 |
#' Prints this `TealDataset`. |
|
109 |
#' |
|
110 |
#' @param ... additional arguments to the printing method |
|
111 |
#' @return invisibly self |
|
112 |
print = function(...) { |
|
113 | 8x |
check_ellipsis(...) |
114 | 8x |
cat(sprintf( |
115 | 8x |
"A %s object containing the following data.frame (%s rows and %s columns):\n", |
116 | 8x |
class(self)[1], |
117 | 8x |
self$get_nrow(), |
118 | 8x |
self$get_ncol() |
119 |
)) |
|
120 | 8x |
print(head(as.data.frame(self$get_raw_data()))) |
121 | 8x |
if (self$get_nrow() > 6) { |
122 | 1x |
cat("...\n") |
123 |
} |
|
124 | 8x |
invisible(self) |
125 |
}, |
|
126 |
# ___ getters ==== |
|
127 |
#' @description |
|
128 |
#' Performs any delayed mutate calls before returning self. |
|
129 |
#' |
|
130 |
#' @return dataset (`TealDataset`) |
|
131 |
get_dataset = function() { |
|
132 | 228x |
if (self$is_mutate_delayed() && !private$is_any_dependency_delayed()) { |
133 | 2x |
private$mutate_eager() |
134 |
} |
|
135 | 228x |
return(self) |
136 |
}, |
|
137 |
#' @description |
|
138 |
#' Get all dataset attributes |
|
139 |
#' @return (named `list`) with dataset attributes |
|
140 |
get_attrs = function() { |
|
141 | ! |
x <- append( |
142 | ! |
attributes(self$get_raw_data()), |
143 | ! |
list( |
144 | ! |
column_labels = self$get_column_labels(), |
145 | ! |
row_labels = self$get_row_labels(), |
146 | ! |
dataname = self$get_dataname(), |
147 | ! |
dataset_label = self$get_dataset_label(), |
148 | ! |
keys = self$get_keys() |
149 |
) |
|
150 |
) |
|
151 | ! |
return(x) |
152 |
}, |
|
153 |
#' @description |
|
154 |
#' Derive the raw data frame inside this object |
|
155 |
#' @return `data.frame` |
|
156 |
get_raw_data = function() { |
|
157 | 356x |
private$.raw_data |
158 |
}, |
|
159 |
#' @description |
|
160 |
#' Derive the names of all `numeric` columns |
|
161 |
#' @return `character` vector. |
|
162 |
get_numeric_colnames = function() { |
|
163 | 1x |
private$get_class_colnames("numeric") |
164 |
}, |
|
165 |
#' @description |
|
166 |
#' Derive the names of all `character` columns |
|
167 |
#' @return `character` vector. |
|
168 |
get_character_colnames = function() { |
|
169 | 1x |
private$get_class_colnames("character") |
170 |
}, |
|
171 |
#' @description |
|
172 |
#' Derive the names of all `factor` columns |
|
173 |
#' @return `character` vector. |
|
174 |
get_factor_colnames = function() { |
|
175 | 1x |
private$get_class_colnames("factor") |
176 |
}, |
|
177 |
#' @description |
|
178 |
#' Derive the column names |
|
179 |
#' @return `character` vector. |
|
180 |
get_colnames = function() { |
|
181 | 128x |
colnames(private$.raw_data) |
182 |
}, |
|
183 |
#' @description |
|
184 |
#' Derive the column labels |
|
185 |
#' @return `character` vector. |
|
186 |
get_column_labels = function() { |
|
187 | 1x |
col_labels(private$.raw_data, fill = FALSE) |
188 |
}, |
|
189 |
#' @description |
|
190 |
#' Get the number of columns of the data |
|
191 |
#' @return `numeric` vector |
|
192 |
get_ncol = function() { |
|
193 | 9x |
ncol(private$.raw_data) |
194 |
}, |
|
195 |
#' @description |
|
196 |
#' Get the number of rows of the data |
|
197 |
#' @return `numeric` vector |
|
198 |
get_nrow = function() { |
|
199 | 17x |
nrow(private$.raw_data) |
200 |
}, |
|
201 |
#' @description |
|
202 |
#' Derive the row names |
|
203 |
#' @return `character` vector. |
|
204 |
get_rownames = function() { |
|
205 | 2x |
rownames(private$.raw_data) |
206 |
}, |
|
207 |
#' @description |
|
208 |
#' Derive the row labels |
|
209 |
#' @return `character` vector. |
|
210 |
get_row_labels = function() { |
|
211 | 1x |
c() |
212 |
}, |
|
213 |
#' @description |
|
214 |
#' Derive the `name` which was formerly called `dataname` |
|
215 |
#' @return `character` name of the dataset |
|
216 |
get_dataname = function() { |
|
217 | 1229x |
private$dataname |
218 |
}, |
|
219 |
#' @description |
|
220 |
#' Derive the `dataname` |
|
221 |
#' @return `character` name of the dataset |
|
222 |
get_datanames = function() { |
|
223 | 159x |
private$dataname |
224 |
}, |
|
225 |
#' @description |
|
226 |
#' Derive the `label` which was former called `datalabel` |
|
227 |
#' @return `character` label of the dataset |
|
228 |
get_dataset_label = function() { |
|
229 | 93x |
private$dataset_label |
230 |
}, |
|
231 |
#' @description |
|
232 |
#' Get primary keys of dataset |
|
233 |
#' @return (`character` vector) with dataset primary keys |
|
234 |
get_keys = function() { |
|
235 | 208x |
private$.keys |
236 |
}, |
|
237 |
#' @description |
|
238 |
#' Get metadata of dataset |
|
239 |
#' @return (named `list`) |
|
240 |
get_metadata = function() { |
|
241 | 100x |
private$metadata |
242 |
}, |
|
243 |
#' @description |
|
244 |
#' Get the list of dependencies that are `TealDataset` or `TealDatasetConnector` objects |
|
245 |
#' |
|
246 |
#' @return `list` |
|
247 |
get_var_r6 = function() { |
|
248 | 105x |
return(private$var_r6) |
249 |
}, |
|
250 |
# ___ setters ==== |
|
251 |
#' @description |
|
252 |
#' Overwrites `TealDataset` or `TealDatasetConnector` dependencies of this `TealDataset` with |
|
253 |
#' those found in `datasets`. Reassignment |
|
254 |
#' refers only to the provided `datasets`, other `vars` remains the same. |
|
255 |
#' @details |
|
256 |
#' Reassign `vars` in this object to keep references up to date after deep clone. |
|
257 |
#' Update is done based on the objects passed in `datasets` argument. |
|
258 |
#' Overwrites dependencies with names matching the names of the objects passed |
|
259 |
#' in `datasets`. |
|
260 |
#' @param datasets (`named list` of `TealDataset(s)` or `TealDatasetConnector(s)`)\cr |
|
261 |
#' objects with valid pointers. |
|
262 |
#' @return NULL invisible |
|
263 |
#' @examples |
|
264 |
#' test_dataset <- teal.data:::TealDataset$new( |
|
265 |
#' dataname = "iris", |
|
266 |
#' x = iris, |
|
267 |
#' vars = list(dep = teal.data:::TealDataset$new("iris2", iris)) |
|
268 |
#' ) |
|
269 |
#' test_dataset$reassign_datasets_vars( |
|
270 |
#' list(iris2 = teal.data:::TealDataset$new("iris2", head(iris))) |
|
271 |
#' ) |
|
272 |
#' |
|
273 |
reassign_datasets_vars = function(datasets) { |
|
274 | 7x |
checkmate::assert_list(datasets, min.len = 0, names = "unique") |
275 | ||
276 | 7x |
common_var_r6 <- intersect(names(datasets), names(private$var_r6)) |
277 | 7x |
private$var_r6[common_var_r6] <- datasets[common_var_r6] |
278 | ||
279 | 7x |
common_vars <- intersect(names(datasets), names(private$vars)) |
280 | 7x |
private$vars[common_vars] <- datasets[common_vars] |
281 | ||
282 | 7x |
common_mutate_vars <- intersect(names(datasets), names(private$mutate_vars)) |
283 | 7x |
private$mutate_vars[common_mutate_vars] <- datasets[common_mutate_vars] |
284 | ||
285 | 7x |
logger::log_trace( |
286 | 7x |
"TealDataset$reassign_datasets_vars reassigned vars for dataset: { deparse1(self$get_dataname()) }." |
287 |
) |
|
288 | 7x |
invisible(NULL) |
289 |
}, |
|
290 |
#' @description |
|
291 |
#' Set the label for the dataset |
|
292 |
#' @return (`self`) invisibly for chaining |
|
293 |
set_dataset_label = function(label) { |
|
294 | 506x |
if (is.null(label)) { |
295 | 166x |
label <- character(0) |
296 |
} |
|
297 | 506x |
checkmate::assert_character(label, max.len = 1, any.missing = FALSE) |
298 | 506x |
private$dataset_label <- label |
299 | ||
300 | 506x |
logger::log_trace( |
301 | 506x |
"TealDataset$set_dataset_label dataset_label set for dataset: { deparse1(self$get_dataname()) }." |
302 |
) |
|
303 | 506x |
return(invisible(self)) |
304 |
}, |
|
305 |
#' @description |
|
306 |
#' Set new keys |
|
307 |
#' @return (`self`) invisibly for chaining. |
|
308 |
set_keys = function(keys) { |
|
309 | 625x |
checkmate::assert_character(keys, any.missing = FALSE) |
310 | 625x |
private$.keys <- keys |
311 | 625x |
logger::log_trace(sprintf( |
312 | 625x |
"TealDataset$set_keys set the keys %s for dataset: %s", |
313 | 625x |
paste(keys, collapse = ", "), |
314 | 625x |
self$get_dataname() |
315 |
)) |
|
316 | 625x |
return(invisible(self)) |
317 |
}, |
|
318 | ||
319 |
#' @description |
|
320 |
#' Adds variables which code depends on |
|
321 |
#' |
|
322 |
#' @param vars (`named list`) contains any R object which code depends on |
|
323 |
#' @return (`self`) invisibly for chaining |
|
324 |
set_vars = function(vars) { |
|
325 | 576x |
private$set_vars_internal(vars, is_mutate_vars = FALSE) |
326 | 572x |
logger::log_trace("TealDataset$set_vars vars set for dataset: { deparse1(self$get_dataname()) }.") |
327 | ||
328 | 572x |
return(invisible(NULL)) |
329 |
}, |
|
330 |
#' @description |
|
331 |
#' Sets reproducible code |
|
332 |
#' |
|
333 |
#' @return (`self`) invisibly for chaining |
|
334 |
set_code = function(code) { |
|
335 | 300x |
checkmate::assert_character(code, max.len = 1, any.missing = FALSE) |
336 | 300x |
if (length(code) > 0 && code != "") { |
337 | 120x |
private$code$set_code( |
338 | 120x |
code = code, |
339 | 120x |
dataname = self$get_datanames(), |
340 | 120x |
deps = names(private$vars) |
341 |
) |
|
342 |
} |
|
343 | 300x |
logger::log_trace("TealDataset$set_code code set for dataset: { deparse1(self$get_dataname()) }.") |
344 | 300x |
return(invisible(NULL)) |
345 |
}, |
|
346 | ||
347 |
# ___ get_code ==== |
|
348 |
#' @description |
|
349 |
#' Get code to get data |
|
350 |
#' |
|
351 |
#' @param deparse (`logical`) whether return deparsed form of a call |
|
352 |
#' |
|
353 |
#' @return optionally deparsed `call` object |
|
354 |
get_code = function(deparse = TRUE) { |
|
355 | 60x |
checkmate::assert_flag(deparse) |
356 | 60x |
res <- self$get_code_class()$get_code(deparse = deparse) |
357 | 60x |
return(res) |
358 |
}, |
|
359 |
#' @description |
|
360 |
#' Get internal `CodeClass` object |
|
361 |
#' @param nodeps (`logical(1)`) whether `CodeClass` should not contain the code |
|
362 |
#' of the dependent `vars` |
|
363 |
#' the `mutate` |
|
364 |
#' @return `CodeClass` |
|
365 |
get_code_class = function(nodeps = FALSE) { |
|
366 | 383x |
res <- CodeClass$new() |
367 |
# precise order matters |
|
368 | 383x |
if (!nodeps) { |
369 | 370x |
res$append(list_to_code_class(private$vars)) |
370 | 370x |
res$append(list_to_code_class(private$mutate_vars)) |
371 |
} |
|
372 | 383x |
res$append(private$code) |
373 | 383x |
res$append(private$mutate_list_to_code_class()) |
374 | ||
375 | 383x |
return(res) |
376 |
}, |
|
377 |
#' @description |
|
378 |
#' Get internal `CodeClass` object |
|
379 |
#' |
|
380 |
#' @return `CodeClass` |
|
381 |
get_mutate_code_class = function() { |
|
382 | ! |
res <- CodeClass$new() |
383 | ! |
res$append(list_to_code_class(private$mutate_vars)) |
384 | ! |
res$append(private$mutate_list_to_code_class()) |
385 | ||
386 | ! |
return(res) |
387 |
}, |
|
388 |
#' @description |
|
389 |
#' Get internal `vars` object |
|
390 |
#' |
|
391 |
#' @return `list` |
|
392 |
get_vars = function() { |
|
393 | 17x |
return(c( |
394 | 17x |
private$vars, |
395 | 17x |
private$mutate_vars[!names(private$mutate_vars) %in% names(private$vars)] |
396 |
)) |
|
397 |
}, |
|
398 |
#' @description |
|
399 |
#' Get internal `mutate_vars` object |
|
400 |
#' |
|
401 |
#' @return `list` |
|
402 |
get_mutate_vars = function() { |
|
403 | 2x |
return(private$mutate_vars) |
404 |
}, |
|
405 | ||
406 |
#' @description |
|
407 |
#' Whether mutate code has delayed evaluation. |
|
408 |
#' @return `logical` |
|
409 |
is_mutate_delayed = function() { |
|
410 | 348x |
return(length(private$mutate_code) > 0) |
411 |
}, |
|
412 | ||
413 |
# ___ mutate ==== |
|
414 |
#' @description |
|
415 |
#' Mutate dataset by code |
|
416 |
#' |
|
417 |
#' @param code (`CodeClass`) or (`character`) R expressions to be executed |
|
418 |
#' @param vars a named list of R objects that `code` depends on to execute |
|
419 |
#' @param force_delay (`logical`) used by the containing `TealDatasetConnector` object |
|
420 |
#' |
|
421 |
#' Either code or script must be provided, but not both. |
|
422 |
#' |
|
423 |
#' @return (`self`) invisibly for chaining |
|
424 |
mutate = function(code, vars = list(), force_delay = FALSE) { |
|
425 | 98x |
logger::log_trace( |
426 | 98x |
sprintf( |
427 | 98x |
"TealDatasetConnector$mutate mutating dataset '%s' using the code (%s lines) and vars (%s).", |
428 | 98x |
self$get_dataname(), |
429 | 98x |
length(parse(text = if (inherits(code, "CodeClass")) code$get_code() else code, keep.source = FALSE)), |
430 | 98x |
paste(names(vars), collapse = ", ") |
431 |
) |
|
432 |
) |
|
433 | ||
434 | 98x |
checkmate::assert_flag(force_delay) |
435 | 98x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
436 | 98x |
checkmate::assert( |
437 | 98x |
checkmate::check_string(code), |
438 | 98x |
checkmate::check_class(code, "CodeClass") |
439 |
) |
|
440 | ||
441 | 97x |
if (inherits(code, "PythonCodeClass")) { |
442 | ! |
self$set_vars(vars) |
443 | ! |
self$set_code(code$get_code()) |
444 | ! |
new_df <- code$eval(dataname = self$get_dataname()) |
445 | ||
446 |
# dataset is recreated by replacing data by mutated object |
|
447 |
# mutation code is added to the code which replicates the data |
|
448 | ! |
self$recreate( |
449 | ! |
x = new_df, |
450 | ! |
vars = list() |
451 |
) |
|
452 |
} else { |
|
453 | 97x |
private$mutate_delayed(code, vars) |
454 | 93x |
if (!(private$is_any_dependency_delayed(vars) || force_delay)) { |
455 | 58x |
private$mutate_eager() |
456 |
} |
|
457 |
} |
|
458 | 88x |
logger::log_trace( |
459 | 88x |
sprintf( |
460 | 88x |
"TealDataset$mutate mutated dataset '%s' using the code (%s lines) and vars (%s).", |
461 | 88x |
self$get_dataname(), |
462 | 88x |
length(parse(text = if (inherits(code, "CodeClass")) code$get_code() else code, keep.source = FALSE)), |
463 | 88x |
paste(names(vars), collapse = ", ") |
464 |
) |
|
465 |
) |
|
466 | ||
467 | 88x |
return(invisible(self)) |
468 |
}, |
|
469 | ||
470 |
# ___ check ==== |
|
471 |
#' @description |
|
472 |
#' Check to determine if the raw data is reproducible from the `get_code()` code. |
|
473 |
#' @return |
|
474 |
#' `TRUE` if the dataset generated from evaluating the |
|
475 |
#' `get_code()` code is identical to the raw data, else `FALSE`. |
|
476 |
check = function() { |
|
477 | 23x |
logger::log_trace( |
478 | 23x |
"TealDataset$check executing the code to reproduce dataset: { deparse1(self$get_dataname()) }..." |
479 |
) |
|
480 | 23x |
if (!checkmate::test_character(self$get_code(), len = 1, pattern = "\\w+")) { |
481 | 2x |
stop( |
482 | 2x |
sprintf( |
483 | 2x |
"Cannot check preprocessing code of '%s' - code is empty.", |
484 | 2x |
self$get_dataname() |
485 |
) |
|
486 |
) |
|
487 |
} |
|
488 | ||
489 | 21x |
new_set <- private$execute_code( |
490 | 21x |
code = self$get_code_class(), |
491 | 21x |
vars = c( |
492 | 21x |
list(), # list() in the beginning to ensure c.list |
493 | 21x |
private$vars, |
494 | 21x |
setNames(list(self), self$get_dataname()) |
495 |
) |
|
496 |
) |
|
497 | ||
498 | 21x |
res_check <- tryCatch( |
499 |
{ |
|
500 | 21x |
identical(self$get_raw_data(), new_set) |
501 |
}, |
|
502 | 21x |
error = function(e) { |
503 | ! |
FALSE |
504 |
} |
|
505 |
) |
|
506 | 21x |
logger::log_trace("TealDataset$check { deparse1(self$get_dataname()) } reproducibility result: { res_check }.") |
507 | ||
508 | 21x |
return(res_check) |
509 |
}, |
|
510 |
#' @description |
|
511 |
#' Check if keys has been specified correctly for dataset. Set of `keys` |
|
512 |
#' should distinguish unique rows or be `character(0)`. |
|
513 |
#' |
|
514 |
#' @return `TRUE` if dataset has been already pulled, else `FALSE` |
|
515 |
check_keys = function(keys = private$.keys) { |
|
516 | 78x |
if (length(keys) > 0) { |
517 | 46x |
if (!all(keys %in% self$get_colnames())) { |
518 | 2x |
stop("Primary keys specifed for ", self$get_dataname(), " do not exist in the data.") |
519 |
} |
|
520 | ||
521 | 44x |
duplicates <- get_key_duplicates(self$get_raw_data(), keys) |
522 | 44x |
if (nrow(duplicates) > 0) { |
523 | 1x |
stop( |
524 | 1x |
"Duplicate primary key values found in the dataset '", self$get_dataname(), "' :\n", |
525 | 1x |
paste0(utils::capture.output(print(duplicates))[-c(1, 3)], collapse = "\n"), |
526 | 1x |
call. = FALSE |
527 |
) |
|
528 |
} |
|
529 |
} |
|
530 | 75x |
logger::log_trace("TealDataset$check_keys keys checking passed for dataset: { deparse1(self$get_dataname()) }.") |
531 |
}, |
|
532 |
#' @description |
|
533 |
#' Check if dataset has already been pulled. |
|
534 |
#' |
|
535 |
#' @return `TRUE` if dataset has been already pulled, else `FALSE` |
|
536 |
is_pulled = function() { |
|
537 | 174x |
return(TRUE) |
538 |
} |
|
539 |
), |
|
540 |
## __Private Fields ==== |
|
541 |
private = list( |
|
542 |
.raw_data = data.frame(), |
|
543 |
metadata = NULL, |
|
544 |
dataname = character(0), |
|
545 |
code = NULL, # CodeClass after initialization |
|
546 |
vars = list(), |
|
547 |
var_r6 = list(), |
|
548 |
dataset_label = character(0), |
|
549 |
.keys = character(0), |
|
550 |
mutate_code = list(), |
|
551 |
mutate_vars = list(), |
|
552 | ||
553 |
## __Private Methods ==== |
|
554 |
mutate_delayed = function(code, vars) { |
|
555 | 97x |
private$set_vars_internal(vars, is_mutate_vars = TRUE) |
556 | 93x |
private$mutate_code[[length(private$mutate_code) + 1]] <- list(code = code, deps = names(vars)) |
557 | 93x |
logger::log_trace( |
558 | 93x |
sprintf( |
559 | 93x |
"TealDatasetConnector$mutate_delayed set the code (%s lines) and vars (%s) for dataset: %s.", |
560 | 93x |
length(parse(text = if (inherits(code, "CodeClass")) code$get_code() else code, keep.source = FALSE)), |
561 | 93x |
paste(names(vars), collapse = ", "), |
562 | 93x |
self$get_dataname() |
563 |
) |
|
564 |
) |
|
565 | 93x |
return(invisible(self)) |
566 |
}, |
|
567 |
mutate_eager = function() { |
|
568 | 60x |
logger::log_trace( |
569 | 60x |
"TealDatasetConnector$mutate_eager executing mutate code for dataset: { deparse1(self$get_dataname()) }..." |
570 |
) |
|
571 | 60x |
new_df <- private$execute_code( |
572 | 60x |
code = private$mutate_list_to_code_class(), |
573 | 60x |
vars = c( |
574 | 60x |
list(), # list() in the beginning to ensure c.list |
575 | 60x |
private$vars, |
576 |
# if they have the same name, then they are guaranteed to be identical objects. |
|
577 | 60x |
private$mutate_vars[!names(private$mutate_vars) %in% names(private$vars)], |
578 | 60x |
setNames(list(self), self$get_dataname()) |
579 |
) |
|
580 |
) |
|
581 | ||
582 |
# code set after successful evaluation |
|
583 |
# otherwise code != dataset |
|
584 |
# private$code$append(private$mutate_code) # nolint |
|
585 | 55x |
private$append_mutate_code() |
586 | 55x |
self$set_vars(private$mutate_vars) |
587 | 55x |
private$mutate_code <- list() |
588 | 55x |
private$mutate_vars <- list() |
589 | ||
590 |
# dataset is recreated by replacing data by mutated object |
|
591 |
# mutation code is added to the code which replicates the data |
|
592 |
# because new_code contains also code of the |
|
593 | 55x |
new_self <- self$recreate( |
594 | 55x |
x = new_df, |
595 | 55x |
vars = list() |
596 |
) |
|
597 | ||
598 | 55x |
logger::log_trace( |
599 | 55x |
"TealDatasetConnector$mutate_eager executed mutate code for dataset: { deparse1(self$get_dataname()) }." |
600 |
) |
|
601 | ||
602 | 55x |
new_self |
603 |
}, |
|
604 | ||
605 |
# need to have a custom deep_clone because one of the key fields are reference-type object |
|
606 |
# in particular: code is a R6 object that wouldn't be cloned using default clone(deep = T) |
|
607 |
deep_clone = function(name, value) { |
|
608 | 1044x |
deep_clone_r6(name, value) |
609 |
}, |
|
610 |
get_class_colnames = function(class_type = "character") { |
|
611 | 3x |
checkmate::assert_string(class_type) |
612 | 3x |
return_cols <- self$get_colnames()[which(vapply( |
613 | 3x |
lapply(self$get_raw_data(), class), |
614 | 3x |
function(x, target_class_name) any(x %in% target_class_name), |
615 | 3x |
logical(1), |
616 | 3x |
target_class_name = class_type |
617 |
))] |
|
618 | ||
619 | 3x |
return(return_cols) |
620 |
}, |
|
621 |
mutate_list_to_code_class = function() { |
|
622 | 443x |
res <- CodeClass$new() |
623 | 443x |
for (mutate_code in private$mutate_code) { |
624 | 121x |
if (inherits(mutate_code$code, "CodeClass")) { |
625 | 14x |
res$append(mutate_code$code) |
626 |
} else { |
|
627 | 107x |
res$set_code( |
628 | 107x |
code = mutate_code$code, |
629 | 107x |
dataname = private$dataname, |
630 | 107x |
deps = mutate_code$deps |
631 |
) |
|
632 |
} |
|
633 |
} |
|
634 | 443x |
return(res) |
635 |
}, |
|
636 |
append_mutate_code = function() { |
|
637 | 55x |
for (mutate_code in private$mutate_code) { |
638 | 57x |
if (inherits(mutate_code$code, "CodeClass")) { |
639 | 11x |
private$code$append(mutate_code$code) |
640 |
} else { |
|
641 | 46x |
private$code$set_code( |
642 | 46x |
code = mutate_code$code, |
643 | 46x |
dataname = private$dataname, |
644 | 46x |
deps = mutate_code$deps |
645 |
) |
|
646 |
} |
|
647 |
} |
|
648 |
}, |
|
649 |
is_any_dependency_delayed = function(vars = list()) { |
|
650 | 101x |
any(vapply( |
651 | 101x |
c(list(), private$var_r6, vars), |
652 | 101x |
FUN.VALUE = logical(1), |
653 | 101x |
FUN = function(var) { |
654 | 130x |
if (inherits(var, "TealDatasetConnector")) { |
655 | 68x |
!var$is_pulled() || var$is_mutate_delayed() |
656 | 62x |
} else if (inherits(var, "TealDataset")) { |
657 | 50x |
var$is_mutate_delayed() |
658 |
} else { |
|
659 | 12x |
FALSE |
660 |
} |
|
661 |
} |
|
662 |
)) |
|
663 |
}, |
|
664 | ||
665 |
# Set variables which code depends on |
|
666 |
# @param vars (`named list`) contains any R object which code depends on |
|
667 |
# @param is_mutate_vars (`logical(1)`) whether this var is used in mutate code |
|
668 |
set_vars_internal = function(vars, is_mutate_vars = FALSE) { |
|
669 | 673x |
checkmate::assert_flag(is_mutate_vars) |
670 | 673x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
671 | ||
672 | 673x |
total_vars <- c(list(), private$vars, private$mutate_vars) |
673 | ||
674 | 673x |
if (length(vars) > 0) { |
675 |
# not allowing overriding variable names |
|
676 | 89x |
over_rides <- names(vars)[vapply( |
677 | 89x |
names(vars), |
678 | 89x |
FUN.VALUE = logical(1), |
679 | 89x |
FUN = function(var_name) { |
680 | 92x |
var_name %in% names(total_vars) && |
681 | 92x |
!identical(total_vars[[var_name]], vars[[var_name]]) |
682 |
} |
|
683 |
)] |
|
684 | 89x |
if (length(over_rides) > 0) { |
685 | 2x |
stop(paste("Variable name(s) already used:", paste(over_rides, collapse = ", "))) |
686 |
} |
|
687 | 87x |
if (is_mutate_vars) { |
688 | 44x |
private$mutate_vars <- c( |
689 | 44x |
private$mutate_vars[!names(private$mutate_vars) %in% names(vars)], |
690 | 44x |
vars |
691 |
) |
|
692 |
} else { |
|
693 | 43x |
private$vars <- c( |
694 | 43x |
private$vars[!names(private$vars) %in% names(vars)], |
695 | 43x |
vars |
696 |
) |
|
697 |
} |
|
698 |
} |
|
699 |
# only adding dependencies if checks passed |
|
700 | 671x |
private$set_var_r6(vars) |
701 | 665x |
return(invisible(NULL)) |
702 |
}, |
|
703 | ||
704 |
# Evaluate script code to modify data or to reproduce data |
|
705 |
# |
|
706 |
# Evaluate script code to modify data or to reproduce data |
|
707 |
# @param vars (named `list`) additional pre-requisite vars to execute code |
|
708 |
# @return (`environment`) which stores modified `x` |
|
709 |
execute_code = function(code, vars = list()) { |
|
710 | 81x |
stopifnot(inherits(code, "CodeClass")) |
711 | 81x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
712 | ||
713 | 81x |
execution_environment <- new.env(parent = parent.env(globalenv())) |
714 | ||
715 |
# set up environment for execution |
|
716 | 81x |
for (vars_idx in seq_along(vars)) { |
717 | 126x |
var_name <- names(vars)[[vars_idx]] |
718 | 126x |
var_value <- vars[[vars_idx]] |
719 | 126x |
if (inherits(var_value, "TealDatasetConnector") || inherits(var_value, "TealDataset")) { |
720 | 106x |
var_value <- get_raw_data(var_value) |
721 |
} |
|
722 | 126x |
assign(envir = execution_environment, x = var_name, value = var_value) |
723 |
} |
|
724 | ||
725 |
# execute |
|
726 | 81x |
code$eval(envir = execution_environment) |
727 | ||
728 | 77x |
if (!is.data.frame(execution_environment[[self$get_dataname()]])) { |
729 | 1x |
out_msg <- sprintf( |
730 | 1x |
"\n%s\n\n - Code from %s need to return a data.frame assigned to an object of dataset name.", |
731 | 1x |
self$get_code(), |
732 | 1x |
self$get_dataname() |
733 |
) |
|
734 | ||
735 | 1x |
rlang::with_options( |
736 | 1x |
.expr = stop(out_msg, call. = FALSE), |
737 | 1x |
warning.length = max(min(8170, nchar(out_msg) + 30), 100) |
738 |
) |
|
739 |
} |
|
740 | ||
741 | 76x |
new_set <- execution_environment[[self$get_dataname()]] |
742 | ||
743 | 76x |
return(new_set) |
744 |
}, |
|
745 | ||
746 |
# Set the name for the dataset |
|
747 |
# @param `dataname` (`character`) the new name |
|
748 |
# @return self invisibly for chaining |
|
749 |
set_dataname = function(dataname) { |
|
750 | 505x |
check_simple_name(dataname) |
751 | 505x |
private$dataname <- dataname |
752 | 505x |
return(invisible(self)) |
753 |
}, |
|
754 |
set_var_r6 = function(vars) { |
|
755 | 671x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
756 | 671x |
for (varname in names(vars)) { |
757 | 90x |
var <- vars[[varname]] |
758 | ||
759 | 90x |
if (inherits(var, "TealDatasetConnector") || inherits(var, "TealDataset")) { |
760 | 64x |
var_deps <- var$get_var_r6() |
761 | 64x |
var_deps[[varname]] <- var |
762 | 64x |
for (var_dep_name in names(var_deps)) { |
763 | 82x |
var_dep <- var_deps[[var_dep_name]] |
764 | 82x |
if (identical(self, var_dep)) { |
765 | 6x |
stop("Circular dependencies detected") |
766 |
} |
|
767 | 76x |
private$var_r6[[var_dep_name]] <- var_dep |
768 |
} |
|
769 |
} |
|
770 |
} |
|
771 | 665x |
return(invisible(self)) |
772 |
} |
|
773 |
), |
|
774 |
## __Active Fields ==== |
|
775 |
active = list( |
|
776 |
#' @field raw_data The data.frame behind this R6 class |
|
777 |
raw_data = function() { |
|
778 | 37x |
private$.raw_data |
779 |
}, |
|
780 |
#' @field data The data.frame behind this R6 class |
|
781 |
data = function() { |
|
782 | 40x |
private$.raw_data |
783 |
}, |
|
784 |
#' @field var_names The column names of the data |
|
785 |
var_names = function() { |
|
786 | 37x |
colnames(private$.raw_data) |
787 |
} |
|
788 |
) |
|
789 |
) |
|
790 | ||
791 |
## Constructors ==== |
|
792 | ||
793 |
#' Constructor for [`TealDataset`] class |
|
794 |
#' |
|
795 |
#' @description `r lifecycle::badge("stable")` |
|
796 |
#' |
|
797 |
#' @param dataname (`character`) a given name for the dataset, it cannot contain spaces |
|
798 |
#' |
|
799 |
#' @param x (`data.frame` or `MultiAssayExperiment`) object from which the dataset will be created |
|
800 |
#' |
|
801 |
#' @param keys optional, (`character`) vector with primary keys |
|
802 |
#' |
|
803 |
#' @param code (`character`) a character string defining the code needed to |
|
804 |
#' produce the data set in `x` |
|
805 |
#' |
|
806 |
#' @param label (`character`) label to describe the dataset |
|
807 |
#' |
|
808 |
#' @param vars (named `list`) in case when this object code depends on other `TealDataset` |
|
809 |
#' object(s) or other constant value, this/these object(s) should be included as named |
|
810 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
811 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
812 |
#' It's recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
813 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
814 |
#' are included to this object as local `vars` and they cannot be modified |
|
815 |
#' within another dataset. |
|
816 |
#' |
|
817 |
#' @param metadata (named `list` or `NULL`) field containing metadata about the dataset. |
|
818 |
#' Each element of the list should be atomic and length one. |
|
819 |
#' |
|
820 |
#' @return [`TealDataset`] object |
|
821 |
#' |
|
822 |
#' @rdname dataset |
|
823 |
#' |
|
824 |
#' @export |
|
825 |
#' |
|
826 |
#' @examples |
|
827 |
#' # Simple example |
|
828 |
#' dataset("iris", iris) |
|
829 |
#' |
|
830 |
#' # Example with more arguments |
|
831 |
#' \dontrun{ |
|
832 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
833 |
#' ADSL_dataset <- dataset(dataname = "ADSL", x = ADSL) |
|
834 |
#' |
|
835 |
#' ADSL_dataset$get_dataname() |
|
836 |
#' |
|
837 |
#' ADSL_dataset <- dataset( |
|
838 |
#' dataname = "ADSL", |
|
839 |
#' x = ADSL, |
|
840 |
#' label = "AdAM subject-level dataset", |
|
841 |
#' code = "ADSL <- teal.data::example_cdisc_data(\"ADSL\")" |
|
842 |
#' ) |
|
843 |
#' ADSL_dataset$get_metadata() |
|
844 |
#' ADSL_dataset$get_dataset_label() |
|
845 |
#' ADSL_dataset$get_code() |
|
846 |
#' } |
|
847 |
dataset <- function(dataname, |
|
848 |
x, |
|
849 |
keys = character(0), |
|
850 |
label = data_label(x), |
|
851 |
code = character(0), |
|
852 |
vars = list(), |
|
853 |
metadata = NULL) { |
|
854 | 258x |
UseMethod("dataset", x) |
855 |
} |
|
856 | ||
857 |
#' @rdname dataset |
|
858 |
#' @export |
|
859 |
dataset.data.frame <- function(dataname, |
|
860 |
x, |
|
861 |
keys = character(0), |
|
862 |
label = data_label(x), |
|
863 |
code = character(0), |
|
864 |
vars = list(), |
|
865 |
metadata = NULL) { |
|
866 | 254x |
checkmate::assert_string(dataname) |
867 | 254x |
checkmate::assert_data_frame(x) |
868 | 254x |
checkmate::assert( |
869 | 254x |
checkmate::check_character(code, max.len = 1, any.missing = FALSE), |
870 | 254x |
checkmate::check_class(code, "CodeClass") |
871 |
) |
|
872 | 254x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
873 | ||
874 | 254x |
TealDataset$new( |
875 | 254x |
dataname = dataname, |
876 | 254x |
x = x, |
877 | 254x |
keys = keys, |
878 | 254x |
code = code, |
879 | 254x |
label = label, |
880 | 254x |
vars = vars, |
881 | 254x |
metadata = metadata |
882 |
) |
|
883 |
} |
|
884 | ||
885 |
#' Load `TealDataset` object from a file |
|
886 |
#' |
|
887 |
#' @description `r lifecycle::badge("experimental")` |
|
888 |
#' Please note that the script has to end with a call creating desired object. The error will be raised otherwise. |
|
889 |
#' |
|
890 |
#' @param path (`character`) string giving the pathname of the file to read from. |
|
891 |
#' @param code (`character`) reproducible code to re-create object |
|
892 |
#' |
|
893 |
#' @return `TealDataset` object |
|
894 |
#' |
|
895 |
#' @export |
|
896 |
#' |
|
897 |
#' @examples |
|
898 |
#' # simple example |
|
899 |
#' file_example <- tempfile(fileext = ".R") |
|
900 |
#' writeLines( |
|
901 |
#' text = c( |
|
902 |
#' "library(teal.data) |
|
903 |
#' dataset(dataname = \"iris\", |
|
904 |
#' x = iris, |
|
905 |
#' code = \"iris\")" |
|
906 |
#' ), |
|
907 |
#' con = file_example |
|
908 |
#' ) |
|
909 |
#' x <- dataset_file(file_example, code = character(0)) |
|
910 |
#' get_code(x) |
|
911 |
#' |
|
912 |
#' # custom code |
|
913 |
#' file_example <- tempfile(fileext = ".R") |
|
914 |
#' writeLines( |
|
915 |
#' text = c( |
|
916 |
#' "library(teal.data) |
|
917 |
#' |
|
918 |
#' # code> |
|
919 |
#' x <- iris |
|
920 |
#' x$a1 <- 1 |
|
921 |
#' x$a2 <- 2 |
|
922 |
#' |
|
923 |
#' # <code |
|
924 |
#' dataset(dataname = \"iris_mod\", x = x)" |
|
925 |
#' ), |
|
926 |
#' con = file_example |
|
927 |
#' ) |
|
928 |
#' x <- dataset_file(file_example) |
|
929 |
#' get_code(x) |
|
930 |
dataset_file <- function(path, code = get_code(path)) { |
|
931 | 2x |
object <- object_file(path, "TealDataset") |
932 | 1x |
object$set_code(code) |
933 | 1x |
return(object) |
934 |
} |
1 |
#' S3 generic for `to_relational_data` function. |
|
2 |
#' |
|
3 |
#' This function takes an object and converts into a `TealData` object, the primary data |
|
4 |
#' object for use in teal applications. |
|
5 |
#' |
|
6 |
#' @param data `TealDataset`, `TealDatasetConnector`, `data.frame`, `MultiAssayExperiment`, `list` |
|
7 |
#' or `function` returning a named list. |
|
8 |
#' |
|
9 |
#' @details Passing a `TealData` into this function leaves the object unchanged. |
|
10 |
#' |
|
11 |
#' @return `TealData` object |
|
12 |
#' |
|
13 |
#' @examples |
|
14 |
#' |
|
15 |
#' to_relational_data(head(iris)) |
|
16 |
#' to_relational_data(dataset("IRIS", head(iris))) |
|
17 |
#' to_relational_data(list(iris = head(iris), mtcars = head(mtcars))) |
|
18 |
#' |
|
19 |
#' d_connector <- dataset_connector("iris", callable_function(function() head(iris))) |
|
20 |
#' d_connector$pull() |
|
21 |
#' to_relational_data(d_connector) |
|
22 |
#' |
|
23 |
#' @keywords internal |
|
24 |
#' @export |
|
25 |
to_relational_data <- function(data) { |
|
26 | 18x |
UseMethod("to_relational_data") |
27 |
} |
|
28 | ||
29 |
#' @keywords internal |
|
30 |
#' @export |
|
31 |
to_relational_data.data.frame <- function(data) { # nolint |
|
32 | 2x |
dataname <- deparse(substitute(data, parent.frame()), width.cutoff = 500L) |
33 | ||
34 | 2x |
if (grepl("\\)$", dataname) && inherits(data, "data.frame")) { |
35 | ! |
stop("Single data.frame shouldn't be provided as a result of a function call. Please name |
36 | ! |
the object first or use a named list.") |
37 |
} |
|
38 | ||
39 | 2x |
if (dataname %in% names(default_cdisc_keys)) { |
40 | ! |
cdisc_data(cdisc_dataset(dataname, data)) |
41 |
} else { |
|
42 | 2x |
teal_data(dataset(dataname, data)) |
43 |
} |
|
44 |
} |
|
45 | ||
46 |
#' @keywords internal |
|
47 |
#' @export |
|
48 |
to_relational_data.TealDataset <- function(data) { |
|
49 | 4x |
dataname <- get_dataname(data) |
50 | ||
51 | 4x |
if (dataname %in% names(default_cdisc_keys)) { |
52 | 2x |
cdisc_data(data) |
53 |
} else { |
|
54 | 2x |
teal_data(data) |
55 |
} |
|
56 |
} |
|
57 | ||
58 |
#' @keywords internal |
|
59 |
#' @export |
|
60 |
to_relational_data.TealDatasetConnector <- function(data) { # nolint |
|
61 | 1x |
to_relational_data.TealDataset(data) |
62 |
} |
|
63 | ||
64 |
#' @keywords internal |
|
65 |
#' @export |
|
66 |
to_relational_data.list <- function(data) { |
|
67 | 11x |
checkmate::assert_list( |
68 | 11x |
data, |
69 | 11x |
types = c("dataset", "data.frame", "MultiAssayExperiment", "TealDataset", "TealDatasetConnector") |
70 |
) |
|
71 | ||
72 | 11x |
call <- substitute(data, parent.frame()) |
73 | 11x |
list_names <- names(data) |
74 | 11x |
parsed_names <- as.character(call)[-1] |
75 | ||
76 |
if ( |
|
77 |
( |
|
78 | 11x |
length(list_names) == 0 && |
79 | 11x |
length(parsed_names) == 0 && |
80 | 11x |
any(sapply(data, inherits, c("dataset", "data.frame", "MultiAssayExperiment"))) |
81 |
) || |
|
82 | 11x |
(any(list_names == "") && length(parsed_names) == 0) || |
83 | 11x |
(any(is.na(list_names))) |
84 |
) { |
|
85 | 3x |
stop("Unnamed lists shouldn't be provided as input for data. Please use a named list.") |
86 |
} |
|
87 | ||
88 | 8x |
datasets_list <- lapply( |
89 | 8x |
seq_along(data), |
90 | 8x |
function(idx) { |
91 | 15x |
if (is.data.frame(data[[idx]]) || inherits(data[[idx]], "MultiAssayExperiment")) { |
92 | 12x |
dataname <- if (length(list_names) == 0 || list_names[[idx]] == "") { |
93 | 3x |
parsed_names[[idx]] |
94 |
} else { |
|
95 | 9x |
list_names[[idx]] |
96 |
} |
|
97 | ||
98 | 12x |
if (dataname %in% names(default_cdisc_keys)) { |
99 | ! |
cdisc_dataset(dataname, data[[idx]]) |
100 |
} else { |
|
101 | 12x |
dataset(dataname, data[[idx]]) |
102 |
} |
|
103 | 3x |
} else if (inherits(data[[idx]], "TealDataset") || inherits(data[[idx]], "TealDatasetConnector")) { |
104 | 3x |
data[[idx]] |
105 |
} else { |
|
106 | ! |
stop("Unknown class to create TealDataset from.") |
107 |
} |
|
108 |
} |
|
109 |
) |
|
110 | ||
111 | 8x |
if (any(sapply(datasets_list, function(x) inherits(x, "CDISCTealDataset")))) { |
112 | ! |
do.call("cdisc_data", args = datasets_list) |
113 |
} else { |
|
114 | 8x |
do.call("teal_data", args = datasets_list) |
115 |
} |
|
116 |
} |
|
117 | ||
118 |
#' @keywords internal |
|
119 |
#' @export |
|
120 |
to_relational_data.MultiAssayExperiment <- function(data) { # nolint |
|
121 | 1x |
teal_data(dataset("MAE", data)) |
122 |
} |
|
123 | ||
124 |
#' @keywords internal |
|
125 |
#' @export |
|
126 |
to_relational_data.TealData <- function(data) { # nolint |
|
127 | ! |
data |
128 |
} |
1 |
#' Get dataset primary keys |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Get dataset primary keys |
|
5 |
#' |
|
6 |
#' @param x an object of `TealDataset` or `TealDatasetConnector` class |
|
7 |
#' @param dataname (`character`) name of dataset to return keys for |
|
8 |
#' @param ... not used, only for support of S3 |
|
9 |
#' |
|
10 |
#' @return (`character`) vector of column names |
|
11 |
#' |
|
12 |
#' @export |
|
13 |
get_keys <- function(x, ...) { |
|
14 | 92x |
UseMethod("get_keys") |
15 |
} |
|
16 | ||
17 |
#' @rdname get_keys |
|
18 |
#' @export |
|
19 |
#' @examples |
|
20 |
#' # TealDataset -------- |
|
21 |
#' |
|
22 |
#' get_keys( |
|
23 |
#' dataset( |
|
24 |
#' "ADSL", |
|
25 |
#' teal.data::example_cdisc_data("ADSL"), |
|
26 |
#' keys = get_cdisc_keys("ADSL"), |
|
27 |
#' code = "ADSL <- teal.data::example_cdisc_data(\"ADSL\")" |
|
28 |
#' ) |
|
29 |
#' ) |
|
30 |
get_keys.TealDataset <- function(x, ...) { |
|
31 | 72x |
check_ellipsis(...) |
32 | 72x |
x$get_keys() |
33 |
} |
|
34 | ||
35 |
#' @rdname get_keys |
|
36 |
#' @export |
|
37 |
#' @examples |
|
38 |
#' # TealDatasetConnector -------- |
|
39 |
#' library(magrittr) |
|
40 |
#' pull_fun_adsl <- callable_function(teal.data::example_cdisc_data) %>% |
|
41 |
#' set_args(list(dataname = "ADAE")) |
|
42 |
#' get_keys( |
|
43 |
#' dataset_connector( |
|
44 |
#' "ADSL", |
|
45 |
#' pull_fun_adsl, |
|
46 |
#' keys = get_cdisc_keys("ADSL"), |
|
47 |
#' ) |
|
48 |
#' ) |
|
49 |
get_keys.TealDatasetConnector <- function(x, ...) { |
|
50 | 20x |
check_ellipsis(...) |
51 | 20x |
x$get_keys() |
52 |
} |
|
53 | ||
54 |
#' @rdname get_keys |
|
55 |
#' @export |
|
56 |
#' @examples |
|
57 |
#' # TealData -------- |
|
58 |
#' |
|
59 |
#' get_keys( |
|
60 |
#' teal_data( |
|
61 |
#' dataset("x", data.frame(x1 = 1:10, y1 = 11:20), keys = "x1"), |
|
62 |
#' dataset("y", data.frame(x2 = 1:10, y2 = 11:20), keys = "x2") |
|
63 |
#' ), |
|
64 |
#' "x" |
|
65 |
#' ) |
|
66 |
get_keys.TealDataAbstract <- function(x, dataname, ...) { |
|
67 | ! |
check_ellipsis(...) |
68 | ! |
get_keys(x$get_items(dataname)) |
69 |
} |
|
70 | ||
71 | ||
72 | ||
73 |
#' Set dataset primary keys |
|
74 |
#' |
|
75 |
#' @description `r lifecycle::badge("stable")` |
|
76 |
#' Set dataset primary keys |
|
77 |
#' |
|
78 |
#' @param x an object of `TealDataset` or `TealDatasetConnector` class |
|
79 |
#' @param keys optional, (`character`) vector with primary keys |
|
80 |
#' @param dataname (`character`) name of dataset for which set the keys |
|
81 |
#' @param ... not used, only for support of S3 |
|
82 |
#' |
|
83 |
#' @return (`character`) vector of column names |
|
84 |
#' |
|
85 |
#' @export |
|
86 |
set_keys <- function(x, ...) { |
|
87 | 119x |
UseMethod("set_keys") |
88 |
} |
|
89 | ||
90 |
#' @rdname set_keys |
|
91 |
#' @export |
|
92 |
#' @examples |
|
93 |
#' # TealDataset -------- |
|
94 |
#' |
|
95 |
#' set_keys( |
|
96 |
#' dataset( |
|
97 |
#' "DF", |
|
98 |
#' data.frame(ID = 1:10, x = runif(10)) |
|
99 |
#' ), |
|
100 |
#' keys = c("ID") |
|
101 |
#' ) |
|
102 |
set_keys.TealDataset <- function(x, keys, ...) { |
|
103 | 119x |
check_ellipsis(...) |
104 | 119x |
x$set_keys(keys) |
105 |
} |
|
106 | ||
107 |
#' @rdname set_keys |
|
108 |
#' @export |
|
109 |
#' @examples |
|
110 |
#' # TealDatasetConnector -------- |
|
111 |
#' |
|
112 |
#' pull_fun <- callable_function( |
|
113 |
#' function() { |
|
114 |
#' data.frame(ID = 1:10, x = runif(10)) |
|
115 |
#' } |
|
116 |
#' ) |
|
117 |
#' set_keys( |
|
118 |
#' dataset_connector( |
|
119 |
#' "DF", |
|
120 |
#' pull_fun |
|
121 |
#' ), |
|
122 |
#' keys = c("ID") |
|
123 |
#' ) |
|
124 |
set_keys.TealDatasetConnector <- function(x, keys, ...) { |
|
125 | ! |
check_ellipsis(...) |
126 | ! |
x$set_keys(keys) |
127 |
} |
|
128 | ||
129 |
#' @rdname set_keys |
|
130 |
#' @export |
|
131 |
#' @examples |
|
132 |
#' # TealData -------- |
|
133 |
#' |
|
134 |
#' set_keys( |
|
135 |
#' teal_data( |
|
136 |
#' dataset("x", data.frame(x1 = 1:10, y1 = 11:20), keys = "x1"), |
|
137 |
#' dataset("y", data.frame(x2 = 1:10, y2 = 11:20), keys = "x2") |
|
138 |
#' ), |
|
139 |
#' "x", |
|
140 |
#' c("x1", "y1") |
|
141 |
#' ) |
|
142 |
set_keys.TealDataAbstract <- function(x, dataname, keys, ...) { |
|
143 | ! |
check_ellipsis(...) |
144 | ! |
set_keys(x$get_items(dataname), keys = keys) |
145 | ! |
return(invisible(x)) |
146 |
} |
1 |
#' Ensure the ellipsis, ..., in method arguments are empty |
|
2 |
#' |
|
3 |
#' Ellipsis, ..., are needed as part of method arguments to allow for its arguments to be different from its generic's |
|
4 |
#' arguments and for this to pass check(). Hence, ..., should always be empty. This function will check for this |
|
5 |
#' condition. |
|
6 |
#' |
|
7 |
#' @param ... it should literally just be ... |
|
8 |
#' @param stop TRUE to raise an error; FALSE will output warning message |
|
9 |
#' @param allowed_args character vector naming arguments that are allowed in the \code{...}. |
|
10 |
#' to allow for unnamed arguments, let "" be one of the elements in this character vector. |
|
11 |
#' |
|
12 |
#' @return \code{NULL} if ... is empty |
|
13 |
#' |
|
14 |
#' @keywords internal |
|
15 |
#' |
|
16 |
#' @examples |
|
17 |
#' method.class <- function(a, b, c, ...) { |
|
18 |
#' check_ellipsis(...) |
|
19 |
#' } |
|
20 |
#' method.class <- function(a, b, c, ...) { |
|
21 |
#' check_ellipsis(..., allowed_args = c("y", "z")) |
|
22 |
#' } |
|
23 |
check_ellipsis <- function(..., stop = FALSE, allowed_args = character(0)) { |
|
24 | 491x |
if (!missing(...)) { |
25 | 20x |
checkmate::assert_flag(stop) |
26 | 20x |
checkmate::assert_character(allowed_args, min.len = 0, null.ok = TRUE, any.missing = FALSE) |
27 | 20x |
args <- list(...) |
28 | 20x |
arg_names <- names(args) |
29 | 20x |
if (is.null(arg_names)) { |
30 | 5x |
arg_names <- rep("", length(args)) |
31 |
} |
|
32 | 20x |
extra_args <- arg_names[!is.element(arg_names, allowed_args)] |
33 | 20x |
if (length(extra_args) == 0) { |
34 | 4x |
return(invisible(NULL)) |
35 |
} |
|
36 | 16x |
message <- paste(length(extra_args), "total unused argument(s).") |
37 | ||
38 | 16x |
named_extra_args <- extra_args[!vapply(extra_args, identical, logical(1), "")] |
39 | 16x |
if (length(named_extra_args) > 0) { |
40 | 12x |
message <- paste0( |
41 | 12x |
message, |
42 |
" ", |
|
43 | 12x |
length(named_extra_args), |
44 | 12x |
" with name(s): ", |
45 | 12x |
paste(named_extra_args, collapse = ", "), |
46 |
"." |
|
47 |
) |
|
48 |
} |
|
49 | 16x |
if (stop) { |
50 | 8x |
stop(message) |
51 |
} else { |
|
52 | 8x |
warning(message) |
53 |
} |
|
54 |
} |
|
55 |
} |
|
56 | ||
57 |
#' Whether the variable name is good to use within Show R Code |
|
58 |
#' |
|
59 |
#' Spaces are problematic because the variables must be escaped |
|
60 |
#' with backticks. |
|
61 |
#' Also, they should not start with a number as R may silently make |
|
62 |
#' it valid by changing it. |
|
63 |
#' Therefore, we only allow alphanumeric characters with underscores. |
|
64 |
#' The first character of the `name` must be an alphabetic character |
|
65 |
#' and can be followed by alphanumeric characters. |
|
66 |
#' |
|
67 |
#' @md |
|
68 |
#' |
|
69 |
#' @param name `character, single or vector` name to check |
|
70 |
#' @keywords internal |
|
71 |
#' |
|
72 |
#' @examples |
|
73 |
#' teal.data:::check_simple_name("aas2df") |
|
74 |
#' teal.data:::check_simple_name("ADSL") |
|
75 |
#' teal.data:::check_simple_name("ADSLmodified") |
|
76 |
#' teal.data:::check_simple_name("ADSL_2") |
|
77 |
#' teal.data:::check_simple_name("a1") |
|
78 |
#' # the following fail |
|
79 |
#' \dontrun{ |
|
80 |
#' teal.data:::check_simple_name("1a") |
|
81 |
#' teal.data:::check_simple_name("ADSL.modified") |
|
82 |
#' teal.data:::check_simple_name("ADSL_modified") |
|
83 |
#' teal.data:::check_simple_name("a1...") |
|
84 |
#' } |
|
85 |
check_simple_name <- function(name) { |
|
86 | 521x |
checkmate::assert_character(name, min.len = 1, any.missing = FALSE) |
87 | 521x |
if (!grepl("^[[:alpha:]][a-zA-Z0-9_]*$", name, perl = TRUE)) { |
88 | 5x |
stop( |
89 | 5x |
"name '", |
90 | 5x |
name, |
91 | 5x |
"' must only contain alphanumeric characters (with underscores)", |
92 | 5x |
" and the first character must be an alphabetic character" |
93 |
) |
|
94 |
} |
|
95 |
} |
1 |
#' Get a [`TealDataset`] objects. |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' @param x ([`TealData`])\cr |
|
6 |
#' object containing datasets. |
|
7 |
#' @export |
|
8 |
#' @return `list` or `TealDataset` objects |
|
9 |
get_datasets <- function(x) { |
|
10 | 9x |
UseMethod("get_datasets") |
11 |
} |
|
12 | ||
13 |
#' @rdname get_datasets |
|
14 |
#' @export |
|
15 |
#' @examples |
|
16 |
#' |
|
17 |
#' library(magrittr) |
|
18 |
#' |
|
19 |
#' # TealData -------- |
|
20 |
#' adsl <- cdisc_dataset( |
|
21 |
#' dataname = "ADSL", |
|
22 |
#' x = teal.data::example_cdisc_data("ADSL"), , |
|
23 |
#' code = "library(teal.data)\nADSL <- teal.data::example_cdisc_data(\"ADSL\")" |
|
24 |
#' ) |
|
25 |
#' |
|
26 |
#' adae <- cdisc_dataset( |
|
27 |
#' dataname = "ADAE", |
|
28 |
#' x = teal.data::example_cdisc_data("ADAE"), |
|
29 |
#' code = "library(teal.data)\nADAE <- teal.data::example_cdisc_data(\"ADAE\")" |
|
30 |
#' ) |
|
31 |
#' |
|
32 |
#' rd <- cdisc_data(adsl, adae) |
|
33 |
#' get_datasets(rd) |
|
34 |
#' |
|
35 |
#' # TealDataConnector -------- |
|
36 |
#' random_data_connector <- function(dataname) { |
|
37 |
#' fun_dataset_connector( |
|
38 |
#' dataname = dataname, |
|
39 |
#' fun = teal.data::example_cdisc_data, |
|
40 |
#' fun_args = list(dataname = dataname), |
|
41 |
#' ) |
|
42 |
#' } |
|
43 |
#' |
|
44 |
#' adsl_cf <- callable_function(teal.data::example_cdisc_data) %>% |
|
45 |
#' set_args(list(dataname = "ADSL")) |
|
46 |
#' adsl <- cdisc_dataset_connector( |
|
47 |
#' dataname = "ADSL", |
|
48 |
#' pull_callable = adsl_cf, |
|
49 |
#' keys = get_cdisc_keys("ADSL") |
|
50 |
#' ) |
|
51 |
#' adlb_cf <- callable_function(teal.data::example_cdisc_data) %>% |
|
52 |
#' set_args(list(dataname = "ADLB")) |
|
53 |
#' adlb <- cdisc_dataset_connector( |
|
54 |
#' dataname = "ADLB", |
|
55 |
#' pull_callable = adlb_cf, |
|
56 |
#' keys = get_cdisc_keys("ADLB") |
|
57 |
#' ) |
|
58 |
#' |
|
59 |
#' rdc <- relational_data_connector( |
|
60 |
#' connection = data_connection(), |
|
61 |
#' connectors = list(adsl, adlb) |
|
62 |
#' ) |
|
63 |
#' |
|
64 |
#' rdc$set_ui(function(id, connection, connectors) p("Example UI")) |
|
65 |
#' rdc$set_server( |
|
66 |
#' function(id, connection, connectors) { |
|
67 |
#' moduleServer( |
|
68 |
#' id = id, |
|
69 |
#' module = function(input, output, session) { |
|
70 |
#' # Note this is simplified as we are not opening a real connection here |
|
71 |
#' for (connector in connectors) { |
|
72 |
#' set_args(connector, args = list(name = input$name)) |
|
73 |
#' # pull each dataset |
|
74 |
#' connector$get_server()(id = connector$get_dataname()) |
|
75 |
#' if (connector$is_failed()) { |
|
76 |
#' break |
|
77 |
#' } |
|
78 |
#' } |
|
79 |
#' } |
|
80 |
#' ) |
|
81 |
#' } |
|
82 |
#' ) |
|
83 |
#' \dontrun{ |
|
84 |
#' load_datasets(rdc) |
|
85 |
#' get_datasets(rdc) |
|
86 |
#' } |
|
87 |
#' |
|
88 |
#' # TealData -------- |
|
89 |
#' \dontrun{ |
|
90 |
#' drc <- cdisc_data(rdc, adae) |
|
91 |
#' get_datasets(drc) |
|
92 |
#' } |
|
93 |
get_datasets.TealDataAbstract <- function(x) { # nolint |
|
94 | 7x |
res <- x$get_datasets() |
95 | 6x |
if (length(res) == 0) { |
96 | ! |
return(invisible(NULL)) |
97 |
} |
|
98 | 6x |
res |
99 |
} |
|
100 | ||
101 |
#' @rdname get_datasets |
|
102 |
#' @export |
|
103 |
#' @examples |
|
104 |
#' |
|
105 |
#' # TealDatasetConnector -------- |
|
106 |
#' adsl_cf <- callable_function(teal.data::example_cdisc_data) %>% |
|
107 |
#' set_args(list(dataname = "ADSL")) |
|
108 |
#' rdc <- cdisc_dataset_connector("ADSL", adsl_cf, keys = get_cdisc_keys("ADSL")) |
|
109 |
#' \dontrun{ |
|
110 |
#' load_datasets(rdc) |
|
111 |
#' get_datasets(rdc) |
|
112 |
#' } |
|
113 |
get_datasets.TealDatasetConnector <- function(x) { # nolint |
|
114 | 1x |
res <- x$get_dataset() |
115 | 1x |
if (length(res) == 0) { |
116 | ! |
return(invisible(NULL)) |
117 |
} |
|
118 | 1x |
res |
119 |
} |
|
120 | ||
121 |
#' @rdname get_datasets |
|
122 |
#' @export |
|
123 |
#' @examples |
|
124 |
#' |
|
125 |
#' # TealDataset -------- |
|
126 |
#' adsl <- cdisc_dataset( |
|
127 |
#' dataname = "ADSL", |
|
128 |
#' x = teal.data::example_cdisc_data("ADSL"), |
|
129 |
#' code = "library(teal.data)\nADSL <- example_cdisc_data(\"ADSL\")" |
|
130 |
#' ) |
|
131 |
#' |
|
132 |
#' get_datasets(adsl) |
|
133 |
get_datasets.TealDataset <- function(x) { |
|
134 | 1x |
x |
135 |
} |
1 |
#' Mutate dataset by code |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("experimental")` |
|
4 |
#' |
|
5 |
#' @param x (`TealDataset`)\cr |
|
6 |
#' object. |
|
7 |
#' @param dataname (`character`)\cr |
|
8 |
#' `Dataname` to be mutated. |
|
9 |
#' @param code (`character`)\cr |
|
10 |
#' Code to mutate the dataset. Must contain the `dataset$dataname`. Or can also be an object |
|
11 |
#' of class `PythonCodeClass` returned by [`python_code`]. |
|
12 |
#' @param script (`character`)\cr |
|
13 |
#' file that contains R Code that can be read using [`read_script`]. |
|
14 |
#' Preferred before `code` argument. |
|
15 |
#' @param vars (named `list`)) \cr |
|
16 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
17 |
#' other constant value, this/these object(s) should be included as named |
|
18 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
19 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
20 |
#' It's recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
21 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
22 |
#' are included to this object as local `vars` and they cannot be modified |
|
23 |
#' within another dataset. |
|
24 |
#' @param ... not used, only for support of S3 |
|
25 |
#' |
|
26 |
#' @return modified `x` object |
|
27 |
#' |
|
28 |
#' @export |
|
29 |
mutate_dataset <- function(x, ...) { |
|
30 | 64x |
UseMethod("mutate_dataset") |
31 |
} |
|
32 | ||
33 |
#' @rdname mutate_dataset |
|
34 |
#' @examples |
|
35 |
#' library(magrittr) |
|
36 |
#' |
|
37 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
38 |
#' |
|
39 |
#' ADSL_dataset <- dataset( |
|
40 |
#' dataname = "ADSL", |
|
41 |
#' x = ADSL, |
|
42 |
#' label = "AdAM subject-level dataset", |
|
43 |
#' code = "ADSL <- teal.data::example_cdisc_data(\"ADSL\")" |
|
44 |
#' ) |
|
45 |
#' ADSL_mutated <- ADSL_dataset %>% |
|
46 |
#' mutate_dataset(code = "ADSL$new_variable <- 1") |
|
47 |
#' |
|
48 |
#' ADSL_mutated$get_raw_data()$new_variable[1] |
|
49 |
#' |
|
50 |
#' # Use an R script to mutate the data |
|
51 |
#' file_example <- tempfile(fileext = ".R") |
|
52 |
#' writeLines( |
|
53 |
#' text = c( |
|
54 |
#' "ADSL <- ADSL %>% |
|
55 |
#' dplyr::mutate(new_variable = new_variable * 2)" |
|
56 |
#' ), |
|
57 |
#' con = file_example |
|
58 |
#' ) |
|
59 |
#' |
|
60 |
#' ADSL_mutated <- ADSL_mutated %>% |
|
61 |
#' mutate_dataset(script = file_example) |
|
62 |
#' |
|
63 |
#' ADSL_mutated$get_raw_data()$new_variable[1] |
|
64 |
#' |
|
65 |
#' ADSL_mutated <- ADSL_mutated %>% |
|
66 |
#' mutate_dataset(code = read_script(file_example)) |
|
67 |
#' |
|
68 |
#' ADSL_mutated$get_raw_data()$new_variable[1] |
|
69 |
#' @export |
|
70 |
mutate_dataset.TealDataset <- function(x, |
|
71 |
code = character(0), |
|
72 |
script = character(0), |
|
73 |
vars = list(), |
|
74 |
...) { |
|
75 | 31x |
check_ellipsis(...) |
76 | 31x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
77 | ||
78 | 31x |
code <- code_from_script(code, script) |
79 | 29x |
x$mutate(code = code, vars = vars, ...) |
80 |
} |
|
81 | ||
82 | ||
83 |
#' @rdname mutate_dataset |
|
84 |
#' @export |
|
85 |
mutate_dataset.TealDatasetConnector <- function(x, # nolint |
|
86 |
code = character(0), |
|
87 |
script = character(0), |
|
88 |
vars = list(), |
|
89 |
...) { |
|
90 | 29x |
check_ellipsis(...) |
91 | 29x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
92 | 29x |
code <- code_from_script(code, script) |
93 | 29x |
x$mutate(code = code, vars = vars, ...) |
94 |
} |
|
95 | ||
96 | ||
97 |
#' @rdname mutate_dataset |
|
98 |
#' @export |
|
99 |
mutate_dataset.TealDataAbstract <- function(x, |
|
100 |
dataname, |
|
101 |
code = character(0), |
|
102 |
script = character(0), |
|
103 |
vars = list(), |
|
104 |
...) { |
|
105 | 4x |
check_ellipsis(...) |
106 | 4x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
107 | ||
108 | 4x |
code <- code_from_script(code, script) |
109 | 4x |
x$mutate_dataset(dataname = dataname, code = code, vars = vars) |
110 |
} |
|
111 | ||
112 | ||
113 | ||
114 |
#' Mutate data by code |
|
115 |
#' |
|
116 |
#' @description `r lifecycle::badge("experimental")` |
|
117 |
#' Code used in this mutation is not linked to particular |
|
118 |
#' but refers to all datasets. |
|
119 |
#' Consequence of this is that when using `get_code(<dataset>)` this |
|
120 |
#' part of the code will be returned for each dataset specified. This method |
|
121 |
#' should be used only if particular call involve changing multiple datasets. |
|
122 |
#' Otherwise please use `mutate_dataset`. |
|
123 |
#' Execution of the code is delayed after datasets are pulled |
|
124 |
#' (`isTRUE(is_pulled)`). |
|
125 |
#' |
|
126 |
#' @param x (`TealDataAbstract`)\cr |
|
127 |
#' object. |
|
128 |
#' @inheritParams mutate_dataset |
|
129 |
#' |
|
130 |
#' @return modified `x` object |
|
131 |
#' |
|
132 |
#' @export |
|
133 |
mutate_data <- function(x, |
|
134 |
code = character(0), |
|
135 |
script = character(0), |
|
136 |
vars = list()) { |
|
137 | 3x |
UseMethod("mutate_data") |
138 |
} |
|
139 | ||
140 |
#' @rdname mutate_data |
|
141 |
#' @export |
|
142 |
mutate_data.TealDataAbstract <- function(x, |
|
143 |
code = character(0), |
|
144 |
script = character(0), |
|
145 |
vars = list()) { |
|
146 | 3x |
checkmate::assert_list(vars, min.len = 0, names = "unique") |
147 | ||
148 | 3x |
code <- code_from_script(code, script) |
149 | 3x |
x$mutate(code = code, vars = vars) |
150 | 3x |
return(invisible(x)) |
151 |
} |
1 |
## CallableCode ==== |
|
2 |
#' |
|
3 |
#' @title A \code{CallableCode} class of objects |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("stable")` |
|
6 |
#' Object that stores code to reproduce an object. It includes methods to |
|
7 |
#' get or run the code and return the object. |
|
8 |
#' |
|
9 |
CallableCode <- R6::R6Class( # nolint |
|
10 |
"CallableCode", |
|
11 |
inherit = Callable, |
|
12 | ||
13 |
## __Public Methods ==== |
|
14 |
public = list( |
|
15 |
#' @description |
|
16 |
#' Create a new \code{CallableCode} object |
|
17 |
#' |
|
18 |
#' @param code (\code{character})\cr |
|
19 |
#' a string containing R code to reproduce the desired object. |
|
20 |
#' @param env (\code{environment})\cr |
|
21 |
#' environment where function will be evaluated |
|
22 |
#' |
|
23 |
#' @return new \code{CallableCode} object |
|
24 |
initialize = function(code, env = new.env(parent = parent.env(globalenv()))) { |
|
25 | 19x |
if (!checkmate::test_string(code)) { |
26 | ! |
stop("A string of length one containing the code needed to produce the object must be provided.") |
27 |
} |
|
28 | ||
29 |
# reposition all library calls in the code so that they are |
|
30 |
# visible in the new env |
|
31 | 19x |
env$library <- function(...) { |
32 | 4x |
mc <- match.call() |
33 | 4x |
mc[[1]] <- quote(base::library) |
34 | 4x |
eval(mc, envir = globalenv()) |
35 | 4x |
this_env <- parent.frame() |
36 | ||
37 | 4x |
if (!identical(this_env, globalenv())) { |
38 | 4x |
parent.env(this_env) <- parent.env(globalenv()) |
39 |
} |
|
40 |
} |
|
41 | ||
42 | 19x |
super$initialize(env = env) |
43 | ||
44 | 19x |
private$code <- code |
45 | 19x |
private$call <- private$get_callable_code(code) |
46 | 15x |
logger::log_trace("CallableCode initialized.") |
47 | ||
48 | 15x |
return(invisible(self)) |
49 |
}, |
|
50 |
#' @description |
|
51 |
#' Get sequence of calls from the code supplied to produce the object. |
|
52 |
#' |
|
53 |
#' @param deparse (\code{logical} value)\cr |
|
54 |
#' whether to return a deparsed version of call |
|
55 |
#' @param args (\code{NULL})\cr |
|
56 |
#' available to be consistent with \code{CallableFunction} but are not used to |
|
57 |
#' retrieve the call. |
|
58 |
#' |
|
59 |
#' @return \code{list} of \code{calls} or \code{character} depending on \code{deparse} argument |
|
60 |
get_call = function(deparse = TRUE, args = NULL) { |
|
61 | 38x |
checkmate::assert_flag(deparse) |
62 | 38x |
if (!is.null(args)) { |
63 | ! |
stop("'args' are not used to retrieve the call.") |
64 |
} |
|
65 | ||
66 | 38x |
res <- if (deparse) { |
67 | 4x |
paste0(vapply(private$call, deparse1, character(1)), collapse = "\n") |
68 |
} else { |
|
69 | 34x |
private$call |
70 |
} |
|
71 | ||
72 | 38x |
return(res) |
73 |
} |
|
74 |
), |
|
75 | ||
76 |
## __Private Fields ==== |
|
77 |
private = list( |
|
78 |
code = NULL, |
|
79 |
## __Private Methods ==== |
|
80 |
# @description |
|
81 |
# Determines whether code is valid and callable. If not then stores error message. |
|
82 |
# |
|
83 |
# @param code \code{character} string to check |
|
84 |
# |
|
85 |
# @return \code{expression} parsed from the supplied code |
|
86 |
# |
|
87 |
get_callable_code = function(code) { |
|
88 | 19x |
expr <- tryCatch( |
89 | 19x |
str2expression(code), |
90 | 19x |
error = function(e) { |
91 | 3x |
private$error_msg <- e$message |
92 | 3x |
private$failed <- TRUE |
93 |
} |
|
94 |
) |
|
95 | 19x |
if (length(expr) >= 1 && !private$failed) { |
96 | 15x |
return(expr) |
97 |
} else { |
|
98 | 4x |
stop(paste("Code supplied is not valid:", private$error_msg)) |
99 |
} |
|
100 |
} |
|
101 |
) |
|
102 |
) |
|
103 | ||
104 |
## Constructors ==== |
|
105 | ||
106 |
#' Create \code{\link{CallableCode}} object |
|
107 |
#' |
|
108 |
#' @description `r lifecycle::badge("stable")` |
|
109 |
#' |
|
110 |
#' Create \link{CallableCode} object to execute specific code and get reproducible call. |
|
111 |
#' |
|
112 |
#' @param code (\code{character})\cr |
|
113 |
#' a string containing R code to reproduce the desired object. Please be aware |
|
114 |
#' that objects assigned to temporary environment are locked which means |
|
115 |
#' that they can't be modified. |
|
116 |
#' |
|
117 |
#' @return \code{CallableCode} object |
|
118 |
#' |
|
119 |
#' @export |
|
120 |
#' |
|
121 |
#' @examples |
|
122 |
#' cf <- callable_code(code = "mtcars") |
|
123 |
#' cf$run() |
|
124 |
#' cf$get_call() |
|
125 |
callable_code <- function(code) { |
|
126 | 19x |
CallableCode$new(code) |
127 |
} |
1 |
#' S3 method for getting a `dataname(s)` of |
|
2 |
#' (`TealDataAbstract`, (`TealDatasetConnector` or |
|
3 |
#' `TealDataset`) R6 object |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("stable")` |
|
6 |
#' |
|
7 |
#' @param x (`TealDataAbstract`, `TealDatasetConnector` or |
|
8 |
#' `TealDataset`) object |
|
9 |
#' |
|
10 |
#' @return `dataname` (`character`) A given name for the dataset(s) |
|
11 |
#' it may not contain spaces |
|
12 |
#' @export |
|
13 |
get_dataname <- function(x) { |
|
14 | 731x |
UseMethod("get_dataname") |
15 |
} |
|
16 | ||
17 |
#' @rdname get_dataname |
|
18 |
#' @export |
|
19 |
get_dataname.TealDataAbstract <- function(x) { # nolint |
|
20 | 15x |
return(x$get_datanames()) |
21 |
} |
|
22 | ||
23 |
#' @rdname get_dataname |
|
24 |
#' @export |
|
25 |
get_dataname.TealDatasetConnector <- function(x) { # nolint |
|
26 | 213x |
return(x$get_dataname()) |
27 |
} |
|
28 | ||
29 | ||
30 |
#' @rdname get_dataname |
|
31 |
#' @export |
|
32 |
get_dataname.TealDataset <- function(x) { # nolint |
|
33 | 503x |
return(x$get_dataname()) |
34 |
} |
1 |
#' Is pulled |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' S3 method to determine if dataset is pulled (loaded). |
|
5 |
#' |
|
6 |
#' @param x ([`TealDatasetConnector`], [`TealDataset`] or [`TealDataAbstract`]) |
|
7 |
#' |
|
8 |
#' @return (`logical`) `TRUE` if connector has been already pulled, else `FALSE`. |
|
9 |
#' @export |
|
10 |
is_pulled <- function(x) { |
|
11 | 245x |
UseMethod("is_pulled") |
12 |
} |
|
13 | ||
14 |
#' @rdname is_pulled |
|
15 |
#' @export |
|
16 |
#' |
|
17 |
#' @examples |
|
18 |
#' # TealDatasetConnector -------- |
|
19 |
#' library(magrittr) |
|
20 |
#' pull_fun_adsl <- callable_function(teal.data::example_cdisc_data) %>% |
|
21 |
#' set_args(list(dataname = "ADSL")) |
|
22 |
#' x <- dataset_connector("ADSL", pull_fun_adsl) |
|
23 |
#' |
|
24 |
#' is_pulled(x) |
|
25 |
#' |
|
26 |
#' load_dataset(x) |
|
27 |
#' is_pulled(x) |
|
28 |
is_pulled.TealDatasetConnector <- function(x) { |
|
29 | 59x |
return(x$is_pulled()) |
30 |
} |
|
31 | ||
32 |
#' @rdname is_pulled |
|
33 |
#' @export |
|
34 |
#' |
|
35 |
#' @examples |
|
36 |
#' # TealDataset -------- |
|
37 |
#' x <- dataset( |
|
38 |
#' dataname = "XY", |
|
39 |
#' x = data.frame(x = c(1, 2), y = c("a", "b"), stringsAsFactors = FALSE), |
|
40 |
#' keys = "y", |
|
41 |
#' code = "XY <- data.frame(x = c(1, 2), y = c('aa', 'bb'), |
|
42 |
#' stringsAsFactors = FALSE)" |
|
43 |
#' ) |
|
44 |
#' |
|
45 |
#' is_pulled(x) |
|
46 |
is_pulled.TealDataset <- function(x) { |
|
47 | 173x |
return(x$is_pulled()) |
48 |
} |
|
49 | ||
50 |
#' @rdname is_pulled |
|
51 |
#' @export |
|
52 |
#' |
|
53 |
#' @examples |
|
54 |
#' |
|
55 |
#' library(magrittr) |
|
56 |
#' |
|
57 |
#' # TealData -------- |
|
58 |
#' x1 <- dataset( |
|
59 |
#' x = teal.data::example_cdisc_data("ADSL"), |
|
60 |
#' dataname = "ADSL", |
|
61 |
#' keys = get_cdisc_keys("ADSL"), |
|
62 |
#' code = "ADSL <- teal.data::example_cdisc_data(\"ADSL\")", |
|
63 |
#' label = "ADTTE dataset" |
|
64 |
#' ) |
|
65 |
#' |
|
66 |
#' x2 <- dataset( |
|
67 |
#' x = teal.data::example_cdisc_data("ADTTE"), |
|
68 |
#' dataname = "ADTTE", |
|
69 |
#' keys = get_cdisc_keys("ADTTE"), |
|
70 |
#' code = "ADTTE <- teal.data::example_cdisc_data(\"ADTTE\")", |
|
71 |
#' label = "ADTTE dataset" |
|
72 |
#' ) |
|
73 |
#' |
|
74 |
#' rd <- teal_data(x1, x2) |
|
75 |
#' is_pulled(rd) |
|
76 |
#' |
|
77 |
#' # TealDataConnector -------- |
|
78 |
#' adsl_cf <- callable_function(teal.data::example_cdisc_data) %>% |
|
79 |
#' set_args(list(dataname = "ADSL")) |
|
80 |
#' adsl <- cdisc_dataset_connector( |
|
81 |
#' dataname = "ADSL", |
|
82 |
#' pull_callable = adsl_cf, |
|
83 |
#' keys = get_cdisc_keys("ADSL") |
|
84 |
#' ) |
|
85 |
#' |
|
86 |
#' new_cf <- callable_function(function(x) { |
|
87 |
#' x$NEW <- 1:nrow(x) |
|
88 |
#' x |
|
89 |
#' }) |
|
90 |
#' new_cf$set_args(list(x = as.name("x"))) |
|
91 |
#' new <- cdisc_dataset_connector( |
|
92 |
#' dataname = "NEW", |
|
93 |
#' pull_callable = new_cf, |
|
94 |
#' keys = get_cdisc_keys("ADSL"), |
|
95 |
#' vars = list(x = adsl) |
|
96 |
#' ) |
|
97 |
#' |
|
98 |
#' rdc <- cdisc_data(adsl, new) |
|
99 |
#' |
|
100 |
#' is_pulled(rdc) |
|
101 |
#' \dontrun{ |
|
102 |
#' load_datasets(rdc) |
|
103 |
#' is_pulled(rdc) |
|
104 |
#' } |
|
105 |
is_pulled.TealDataAbstract <- function(x) { # nolint |
|
106 | 13x |
return(x$is_pulled()) |
107 |
} |
1 |
#' Set arguments of a `CallableFunction` |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Set arguments of a `CallableFunction` |
|
5 |
#' |
|
6 |
#' @param x `CallableFunction` or `TealDatasetConnector`) |
|
7 |
#' @param args (`NULL` or named `list`) dynamic arguments to function |
|
8 |
#' |
|
9 |
#' @return nothing |
|
10 |
#' @rdname set_args |
|
11 |
#' @export |
|
12 |
set_args <- function(x, args) { |
|
13 | 14x |
UseMethod("set_args") |
14 |
} |
|
15 | ||
16 |
#' @rdname set_args |
|
17 |
#' @export |
|
18 |
#' @examples |
|
19 |
#' ## Using CallableFunction |
|
20 |
#' fun <- callable_function(example_cdisc_data) |
|
21 |
#' set_args(fun, list(dataname = "ADSL")) |
|
22 |
set_args.CallableFunction <- function(x, args) { |
|
23 | 13x |
x$set_args(args) |
24 | 13x |
return(invisible(x)) |
25 |
} |
|
26 | ||
27 |
#' @rdname set_args |
|
28 |
#' @export |
|
29 |
#' @examples |
|
30 |
#' ## Using CallableCode |
|
31 |
#' code <- callable_code("example_cdisc_data()") |
|
32 |
#' set_args(code, list(df = "adsl")) |
|
33 |
set_args.CallableCode <- function(x, args) { |
|
34 | ! |
warning( |
35 | ! |
"'CallableCode' is unchangable. Ignoring arguments set by 'set_args'", |
36 | ! |
call. = FALSE |
37 |
) |
|
38 | ! |
return(invisible(x)) |
39 |
} |
|
40 | ||
41 |
#' @rdname set_args |
|
42 |
#' @export |
|
43 |
#' @examples |
|
44 |
#' ## Using TealDatasetConnector |
|
45 |
#' ds <- dataset_connector("x", pull_callable = callable_function(data.frame)) |
|
46 |
#' set_args(ds, list(x = 1:5, y = letters[1:5])) |
|
47 |
set_args.TealDatasetConnector <- function(x, args) { |
|
48 | 1x |
x$set_args(args) |
49 | 1x |
return(invisible(x)) |
50 |
} |
1 |
#' S3 method for getting a label of |
|
2 |
#' (`TealDatasetConnector` or `TealDataset`) R6 object |
|
3 |
#' |
|
4 |
#' @description `r lifecycle::badge("stable")` |
|
5 |
#' |
|
6 |
#' @param x (`TealDatasetConnector` or `TealDataset`) R6 object |
|
7 |
#' |
|
8 |
#' @return label (`character`) Label to describe the dataset |
|
9 |
#' @export |
|
10 |
get_dataset_label <- function(x) { |
|
11 | 48x |
UseMethod("get_dataset_label") |
12 |
} |
|
13 | ||
14 |
#' @rdname get_dataset_label |
|
15 |
#' @export |
|
16 |
#' @examples |
|
17 |
#' fun <- callable_function(data.frame) |
|
18 |
#' fun$set_args(list(c1 = seq_len(10))) |
|
19 |
#' |
|
20 |
#' x <- dataset_connector( |
|
21 |
#' pull_callable = fun, |
|
22 |
#' dataname = "ADSL", |
|
23 |
#' label = "My custom label" |
|
24 |
#' ) |
|
25 |
#' get_dataset_label(x) |
|
26 |
get_dataset_label.TealDatasetConnector <- function(x) { # nolint |
|
27 | 17x |
return(x$get_dataset_label()) |
28 |
} |
|
29 | ||
30 |
#' @rdname get_dataset_label |
|
31 |
#' @export |
|
32 |
#' @examples |
|
33 |
#' ADSL <- example_cdisc_data("ADSL") |
|
34 |
#' ADSL_dataset <- dataset(dataname = "ADSL", x = ADSL, label = "My custom label") |
|
35 |
#' get_dataset_label(ADSL_dataset) |
|
36 |
get_dataset_label.TealDataset <- function(x) { |
|
37 | 31x |
return(x$get_dataset_label()) |
38 |
} |
1 |
#' This function returns a dummy dataset for testing examples and should only be used within `teal.data`. |
|
2 |
#' |
|
3 |
#' It is not meant to retrieve the SCDA dataset, and the dataset itself is not maintained here. |
|
4 |
#' |
|
5 |
#' This function creates a copy of the SCDA data for testing purposes. |
|
6 |
#' |
|
7 |
#' CDISC data includes `ADSL`, `ADAE`, `ADLB`, `ADCM`, `ADEX`, `ADRS`, `ADTR` and `ADTTE`. |
|
8 |
#' |
|
9 |
#' @param dataname name of the `CDISC` dataset |
|
10 |
#' |
|
11 |
#' @return `cdisc_data` |
|
12 |
#' |
|
13 |
#' @export |
|
14 |
example_cdisc_data <- function(dataname) { |
|
15 |
# Define the available datasets |
|
16 | 33x |
datasets <- c("ADSL", "ADAE", "ADLB", "ADCM", "ADEX", "ADRS", "ADTR", "ADTTE", "ADVS") |
17 | ||
18 |
# Check if the provided dataname is valid |
|
19 | 33x |
if (dataname %in% datasets) { |
20 | 33x |
dataset <- get(paste0("r", dataname)) |
21 | 33x |
return(dataset) |
22 |
} else { |
|
23 | ! |
stop("Invalid dataname. Please provide one of the following: ", paste(datasets, collapse = ", ")) |
24 |
} |
|
25 |
} |
1 |
#' Data input for teal app |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Function takes datasets and creates `TealData` object which can be used in `teal` applications. |
|
5 |
#' |
|
6 |
#' @note This function does not automatically assign keys to `TealDataset` |
|
7 |
#' and `TealDatasetConnector` objects passed to it. If the keys are needed |
|
8 |
#' they should be assigned before calling `cdisc_data`. See example: |
|
9 |
#' ``` |
|
10 |
#' test_dataset <- dataset("ADAE", teal.data::example_cdisc_data("ADAE")) # does not have keys |
|
11 |
#' test_adsl <- cdisc_dataset("ADSL", teal.data::example_cdisc_data("ADSL")) |
|
12 |
#' test_data <- cdisc_data(test_dataset, test_adsl) |
|
13 |
#' get_keys(test_data, "ADAE") # returns character(0) |
|
14 |
#' |
|
15 |
#' test_dataset <- cdisc_dataset("ADAE", teal.data::example_cdisc_data("ADAE")) |
|
16 |
#' test_data <- cdisc_data(test_dataset, test_adsl) |
|
17 |
#' get_keys(test_data, "ADAE") # returns [1] "STUDYID" "USUBJID" "ASTDTM" "AETERM" "AESEQ" |
|
18 |
#' ``` |
|
19 |
#' @inheritParams teal_data |
|
20 |
#' @param ... (`TealDataConnector`, `TealDataset` or |
|
21 |
#' `TealDatasetConnector`) elements to include. |
|
22 |
#' @param join_keys (`JoinKeys`) or a single (`JoinKeySet`)\cr |
|
23 |
#' (optional) object with datasets column names used for joining. |
|
24 |
#' If empty then it would be automatically derived basing on intersection of datasets primary keys |
|
25 |
#' |
|
26 |
#' @return a `TealData` object |
|
27 |
#' |
|
28 |
#' @details This function checks if there were keys added to all data sets |
|
29 |
#' |
|
30 |
#' @export |
|
31 |
#' |
|
32 |
#' @examples |
|
33 |
#' |
|
34 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
35 |
#' ADTTE <- teal.data::example_cdisc_data("ADTTE") |
|
36 |
#' |
|
37 |
#' # basic example |
|
38 |
#' cdisc_data( |
|
39 |
#' cdisc_dataset("ADSL", ADSL), |
|
40 |
#' cdisc_dataset("ADTTE", ADTTE), |
|
41 |
#' code = "ADSL <- teal.data::example_cdisc_data(\"ADSL\") |
|
42 |
#' ADTTE <- teal.data::example_cdisc_data(\"ADTTE\")", |
|
43 |
#' check = TRUE |
|
44 |
#' ) |
|
45 |
#' |
|
46 |
#' # Example with keys |
|
47 |
#' cdisc_data( |
|
48 |
#' cdisc_dataset("ADSL", ADSL, |
|
49 |
#' keys = c("STUDYID", "USUBJID") |
|
50 |
#' ), |
|
51 |
#' cdisc_dataset("ADTTE", ADTTE, |
|
52 |
#' keys = c("STUDYID", "USUBJID", "PARAMCD"), |
|
53 |
#' parent = "ADSL" |
|
54 |
#' ), |
|
55 |
#' join_keys = join_keys( |
|
56 |
#' join_key( |
|
57 |
#' "ADSL", |
|
58 |
#' "ADTTE", |
|
59 |
#' c("STUDYID" = "STUDYID", "USUBJID" = "USUBJID") |
|
60 |
#' ) |
|
61 |
#' ), |
|
62 |
#' code = "ADSL <- teal.data::example_cdisc_data(\"ADSL\") |
|
63 |
#' ADTTE <- teal.data::example_cdisc_data(\"ADTTE\")", |
|
64 |
#' check = TRUE |
|
65 |
#' ) |
|
66 |
cdisc_data <- function(..., |
|
67 |
join_keys = teal.data::join_keys(), |
|
68 |
code = "", |
|
69 |
check = FALSE) { |
|
70 | 21x |
data_objects <- list(...) |
71 | 21x |
checkmate::assert_list( |
72 | 21x |
data_objects, |
73 | 21x |
types = c("TealDataset", "TealDatasetConnector", "TealDataConnector") |
74 |
) |
|
75 | 21x |
if (inherits(join_keys, "JoinKeySet")) { |
76 | ! |
join_keys <- teal.data::join_keys(join_keys) |
77 |
} |
|
78 | ||
79 | 21x |
update_join_keys_to_primary(data_objects, join_keys) |
80 | ||
81 | 21x |
retrieve_parents <- function(x) { |
82 | 43x |
tryCatch( |
83 | 43x |
x$get_parent(), |
84 | 43x |
error = function(cond) rep(character(0), length(x$get_datanames())) |
85 |
) |
|
86 |
} |
|
87 | ||
88 | 21x |
new_parents_fun <- function(data_objects) { |
89 | 25x |
lapply(data_objects, function(x) { |
90 | 47x |
if (inherits(x, "TealDataConnector")) { |
91 | 4x |
unlist(new_parents_fun(x$get_items()), recursive = FALSE) |
92 |
} else { |
|
93 | 43x |
list(retrieve_parents(x)) |
94 |
} |
|
95 |
}) |
|
96 |
} |
|
97 | ||
98 | 21x |
new_parents <- unlist(new_parents_fun(data_objects), recursive = FALSE) |
99 | ||
100 | 21x |
names(new_parents) <- unlist(lapply(data_objects, function(x) { |
101 | 42x |
if (inherits(x, "TealDataConnector")) { |
102 | 4x |
lapply(x$get_items(), function(z) z$get_dataname()) |
103 |
} else { |
|
104 | 38x |
x$get_datanames() |
105 |
} |
|
106 |
})) |
|
107 | ||
108 | 21x |
if (is_dag(new_parents)) { |
109 | 1x |
stop("Cycle detected in a parent and child dataset graph.") |
110 |
} |
|
111 | 20x |
join_keys$set_parents(new_parents) |
112 | 20x |
join_keys$update_keys_given_parents() |
113 | ||
114 | 20x |
x <- TealData$new(..., check = check, join_keys = join_keys) |
115 | ||
116 | 20x |
if (length(code) > 0 && !identical(code, "")) { |
117 | 2x |
x$set_pull_code(code = code) |
118 |
} |
|
119 | ||
120 | 19x |
x$check_reproducibility() |
121 | 18x |
x$check_metadata() |
122 | 18x |
return(x) |
123 |
} |
|
124 | ||
125 |
#' Load `TealData` object from a file |
|
126 |
#' |
|
127 |
#' @description `r lifecycle::badge("deprecated")` |
|
128 |
#' |
|
129 |
#' @inheritParams teal_data_file |
|
130 |
#' |
|
131 |
#' @return `TealData` object |
|
132 |
#' |
|
133 |
#' @export |
|
134 |
#' |
|
135 |
#' @examples |
|
136 |
#' file_example <- tempfile(fileext = ".R") |
|
137 |
#' writeLines( |
|
138 |
#' text = c( |
|
139 |
#' "# code> |
|
140 |
#' ADSL <- teal.data::example_cdisc_data('ADSL') |
|
141 |
#' ADTTE <- teal.data::example_cdisc_data('ADTTE') |
|
142 |
#' |
|
143 |
#' cdisc_data( |
|
144 |
#' cdisc_dataset(\"ADSL\", ADSL), cdisc_dataset(\"ADTTE\", ADTTE), |
|
145 |
#' code = \"ADSL <- teal.data::example_cdisc_data('ADSL') |
|
146 |
#' ADTTE <- teal.data::example_cdisc_data('ADTTE')\", |
|
147 |
#' check = FALSE |
|
148 |
#' ) |
|
149 |
#' # <code" |
|
150 |
#' ), |
|
151 |
#' con = file_example |
|
152 |
#' ) |
|
153 |
#' |
|
154 |
#' cdisc_data_file(file_example) |
|
155 |
cdisc_data_file <- function(path, code = get_code(path)) { |
|
156 | 2x |
lifecycle::deprecate_warn(when = "0.1.3", what = "cdisc_data_file()", with = "teal_data_file()") |
157 | 2x |
object <- object_file(path, "TealData") |
158 | 2x |
object$mutate(code) |
159 | 2x |
return(object) |
160 |
} |
1 |
#' Teal data |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Universal function to pass data to teal application |
|
5 |
#' |
|
6 |
#' @param ... (`TealDataConnector`, `TealDataset`, `TealDatasetConnector`)\cr |
|
7 |
#' objects |
|
8 |
#' @param join_keys (`JoinKeys`) or a single (`JoinKeySet`)\cr |
|
9 |
#' (optional) object with dataset column relationships used for joining. |
|
10 |
#' If empty then no joins between pairs of objects |
|
11 |
#' @param code (`character`) code to reproduce the datasets. |
|
12 |
#' @param check (`logical`) reproducibility check - whether to perform a check that the pre-processing |
|
13 |
#' code included in the object definitions actually produces those objects. |
|
14 |
#' If `check` is true and preprocessing code is empty an error will be thrown. |
|
15 |
#' |
|
16 |
#' @return (`TealData`) |
|
17 |
#' |
|
18 |
#' @export |
|
19 |
#' |
|
20 |
#' @examples |
|
21 |
#' x1 <- dataset( |
|
22 |
#' "x1", |
|
23 |
#' iris, |
|
24 |
#' code = "x1 <- iris" |
|
25 |
#' ) |
|
26 |
#' |
|
27 |
#' x2 <- dataset( |
|
28 |
#' "x2", |
|
29 |
#' mtcars, |
|
30 |
#' code = "x2 <- mtcars" |
|
31 |
#' ) |
|
32 |
#' |
|
33 |
#' teal_data(x1, x2) |
|
34 |
teal_data <- function(..., |
|
35 |
join_keys = teal.data::join_keys(), |
|
36 |
code = "", |
|
37 |
check = FALSE) { |
|
38 | 28x |
data_objects <- list(...) |
39 | 28x |
checkmate::assert_list( |
40 | 28x |
data_objects, |
41 | 28x |
types = c("TealDataset", "TealDatasetConnector", "TealDataConnector") |
42 |
) |
|
43 | 26x |
if (inherits(join_keys, "JoinKeySet")) { |
44 | ! |
join_keys <- teal.data::join_keys(join_keys) |
45 |
} |
|
46 | ||
47 | 26x |
update_join_keys_to_primary(data_objects, join_keys) |
48 | ||
49 | 26x |
x <- TealData$new(..., check = check, join_keys = join_keys) |
50 | ||
51 | 26x |
if (length(code) > 0 && !identical(code, "")) { |
52 | ! |
x$set_pull_code(code = code) |
53 |
} |
|
54 | ||
55 | 26x |
x$check_reproducibility() |
56 | 26x |
x$check_metadata() |
57 | ||
58 | 25x |
return(x) |
59 |
} |
|
60 | ||
61 | ||
62 |
#' Load `TealData` object from a file |
|
63 |
#' |
|
64 |
#' @description `r lifecycle::badge("experimental")` |
|
65 |
#' Please note that the script has to end with a call creating desired object. The error will be raised otherwise. |
|
66 |
#' |
|
67 |
#' @param path A (`connection`) or a (`character`)\cr |
|
68 |
#' string giving the pathname of the file or URL to read from. "" indicates the connection `stdin`. |
|
69 |
#' @param code (`character`)\cr |
|
70 |
#' reproducible code to re-create object |
|
71 |
#' |
|
72 |
#' @return `TealData` object |
|
73 |
#' |
|
74 |
#' |
|
75 |
#' @export |
|
76 |
#' |
|
77 |
#' @examples |
|
78 |
#' # simple example |
|
79 |
#' file_example <- tempfile(fileext = ".R") |
|
80 |
#' writeLines( |
|
81 |
#' text = c( |
|
82 |
#' "library(teal.data) |
|
83 |
#' |
|
84 |
#' x1 <- dataset(dataname = \"IRIS\", |
|
85 |
#' x = iris, |
|
86 |
#' code = \"IRIS <- iris\") |
|
87 |
#' |
|
88 |
#' x2 <- dataset(dataname = \"MTCARS\", |
|
89 |
#' x = mtcars, |
|
90 |
#' code = \"MTCARS <- mtcars\") |
|
91 |
#' |
|
92 |
#' teal_data(x1, x2)" |
|
93 |
#' ), |
|
94 |
#' con = file_example |
|
95 |
#' ) |
|
96 |
#' teal_data_file(file_example, code = character(0)) |
|
97 |
teal_data_file <- function(path, code = get_code(path)) { |
|
98 | 2x |
object <- object_file(path, "TealData") |
99 | 2x |
object$mutate(code) |
100 | 2x |
return(object) |
101 |
} |
|
102 | ||
103 |
#' Add primary keys as join_keys to a dataset self |
|
104 |
#' |
|
105 |
#' @param data_objects (`list`) of `TealDataset`, `TealDatasetConnector` or `TealDataConnector` objects |
|
106 |
#' @param join_keys (`JoinKeys`) object |
|
107 |
#' |
|
108 |
#' @keywords internal |
|
109 |
update_join_keys_to_primary <- function(data_objects, join_keys) { |
|
110 | 55x |
lapply(data_objects, function(obj) { |
111 | 97x |
if (inherits(obj, "TealDataConnector")) { |
112 | 5x |
update_join_keys_to_primary(obj$get_items(), join_keys) |
113 |
} else { |
|
114 | 92x |
dataname <- obj$get_dataname() |
115 | 92x |
if (length(join_keys$get(dataname, dataname)) == 0) { |
116 | 91x |
join_keys$mutate( |
117 | 91x |
dataname, |
118 | 91x |
dataname, |
119 | 91x |
obj$get_keys() |
120 |
) |
|
121 |
} |
|
122 |
} |
|
123 |
}) |
|
124 |
} |
1 |
## CDISCTealDataset ==== |
|
2 |
#' |
|
3 |
#' @title R6 Class representing a dataset with parent attribute |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("stable")` |
|
6 |
#' Any `data.frame` object can be stored inside this object. |
|
7 |
#' |
|
8 |
#' The difference compared to `TealDataset` class is a parent field that |
|
9 |
#' indicates name of the parent dataset. Note that the parent field might |
|
10 |
#' be empty (i.e. `character(0)`). |
|
11 |
#' |
|
12 |
#' @param dataname (`character`)\cr |
|
13 |
#' A given name for the dataset it may not contain spaces |
|
14 |
#' |
|
15 |
#' @param x (`data.frame`)\cr |
|
16 |
#' |
|
17 |
#' @param keys (`character`)\cr |
|
18 |
#' vector with primary keys |
|
19 |
#' |
|
20 |
#' @param parent optional, (`character`) \cr |
|
21 |
#' parent dataset name |
|
22 |
#' |
|
23 |
#' @param code (`character`)\cr |
|
24 |
#' A character string defining the code needed to produce the data set in `x` |
|
25 |
#' |
|
26 |
#' @param label (`character`)\cr |
|
27 |
#' Label to describe the dataset |
|
28 |
#' |
|
29 |
#' @param vars (named `list`)) \cr |
|
30 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
31 |
#' other constant value, this/these object(s) should be included as named |
|
32 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
33 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
34 |
#' It is recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
35 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
36 |
#' are included to this object as local `vars` and they cannot be modified |
|
37 |
#' within another dataset. |
|
38 |
#' |
|
39 |
#' @param metadata (named `list` or `NULL`) \cr |
|
40 |
#' Field containing metadata about the dataset. Each element of the list |
|
41 |
#' should be atomic and length one. |
|
42 |
#' |
|
43 |
#' @examples |
|
44 |
#' x <- cdisc_dataset( |
|
45 |
#' dataname = "XYZ", |
|
46 |
#' x = data.frame(x = c(1, 2), y = c("a", "b"), stringsAsFactors = FALSE), |
|
47 |
#' keys = "y", |
|
48 |
#' parent = "ABC", |
|
49 |
#' code = "XYZ <- data.frame(x = c(1, 2), y = c('aa', 'bb'), |
|
50 |
#' stringsAsFactors = FALSE)", |
|
51 |
#' metadata = list(type = "example") |
|
52 |
#' ) |
|
53 |
#' |
|
54 |
#' x$ncol |
|
55 |
#' x$get_code() |
|
56 |
#' x$get_dataname() |
|
57 |
#' x$get_keys() |
|
58 |
#' x$get_parent() |
|
59 |
CDISCTealDataset <- R6::R6Class( # nolint |
|
60 |
"CDISCTealDataset", |
|
61 |
inherit = TealDataset, |
|
62 |
## __Public Methods ==== |
|
63 |
public = list( |
|
64 |
#' @description |
|
65 |
#' Create a new object of `CDISCTealDataset` class |
|
66 |
initialize = function(dataname, x, keys, parent, code = character(0), |
|
67 |
label = character(0), vars = list(), metadata = NULL) { |
|
68 | 81x |
checkmate::assert_character(parent, max.len = 1, any.missing = FALSE) |
69 | 80x |
super$initialize( |
70 | 80x |
dataname = dataname, x = x, keys = keys, code = code, |
71 | 80x |
label = label, vars = vars, metadata = metadata |
72 |
) |
|
73 | ||
74 | 80x |
self$set_parent(parent) |
75 | 80x |
logger::log_trace("CDISCTealDataset initialized for dataset: { deparse1(self$get_dataname()) }.") |
76 | 80x |
return(invisible(self)) |
77 |
}, |
|
78 |
#' @description |
|
79 |
#' Recreate a dataset with its current attributes |
|
80 |
#' This is useful way to have access to class initialize method basing on class object |
|
81 |
#' |
|
82 |
#' @return a new object of `CDISCTealDataset` class |
|
83 |
recreate = function(dataname = self$get_dataname(), |
|
84 |
x = self$get_raw_data(), |
|
85 |
keys = self$get_keys(), |
|
86 |
parent = self$get_parent(), |
|
87 |
code = private$code, |
|
88 |
label = self$get_dataset_label(), |
|
89 |
vars = list(), |
|
90 |
metadata = self$get_metadata()) { |
|
91 | 8x |
res <- self$initialize( |
92 | 8x |
dataname = dataname, |
93 | 8x |
x = x, |
94 | 8x |
keys = keys, |
95 | 8x |
parent = parent, |
96 | 8x |
code = code, |
97 | 8x |
label = label, |
98 | 8x |
vars = vars, |
99 | 8x |
metadata = metadata |
100 |
) |
|
101 | 8x |
logger::log_trace("CDISCTealDataset$recreate recreated dataset: { deparse1(self$get_dataname()) }.") |
102 | 8x |
return(res) |
103 |
}, |
|
104 |
#' @description |
|
105 |
#' Get all dataset attributes |
|
106 |
#' @return (named `list`) with dataset attributes |
|
107 |
get_attrs = function() { |
|
108 | ! |
x <- super$get_attrs() |
109 | ! |
x <- append( |
110 | ! |
x, |
111 | ! |
list( |
112 | ! |
parent = self$get_parent() |
113 |
) |
|
114 |
) |
|
115 | ! |
return(x) |
116 |
}, |
|
117 |
#' @description |
|
118 |
#' Get parent dataset name |
|
119 |
#' @return (`character`) indicating parent `dataname` |
|
120 |
get_parent = function() { |
|
121 | 38x |
return(private$parent) |
122 |
}, |
|
123 |
#' @description |
|
124 |
#' Set parent dataset name |
|
125 |
#' @param parent (`character`) indicating parent `dataname` |
|
126 |
#' @return (`self`) invisibly for chaining |
|
127 |
set_parent = function(parent) { |
|
128 | 81x |
checkmate::assert_character(parent, max.len = 1, any.missing = FALSE) |
129 | 81x |
private$parent <- parent |
130 | ||
131 | 81x |
logger::log_trace("CDISCTealDataset$set_parent parent set for dataset: { deparse1(self$get_dataname()) }.") |
132 | 81x |
return(invisible(self)) |
133 |
} |
|
134 |
), |
|
135 |
## __Private Fields ==== |
|
136 |
private = list( |
|
137 |
parent = character(0) |
|
138 |
) |
|
139 |
) |
|
140 | ||
141 |
# constructors ==== |
|
142 |
#' Create a new object of `CDISCTealDataset` class |
|
143 |
#' |
|
144 |
#' @description `r lifecycle::badge("stable")` |
|
145 |
#' Function that creates `CDISCTealDataset` object |
|
146 |
#' |
|
147 |
#' @inheritParams dataset |
|
148 |
#' @param parent (`character`, optional) parent dataset name |
|
149 |
#' |
|
150 |
#' @return (`CDISCTealDataset`) a dataset with connected metadata |
|
151 |
#' |
|
152 |
#' @export |
|
153 |
#' |
|
154 |
#' @examples |
|
155 |
#' ADSL <- example_cdisc_data("ADSL") |
|
156 |
#' |
|
157 |
#' cdisc_dataset("ADSL", ADSL, metadata = list(type = "teal.data")) |
|
158 |
cdisc_dataset <- function(dataname, |
|
159 |
x, |
|
160 |
keys = get_cdisc_keys(dataname), |
|
161 |
parent = `if`(identical(dataname, "ADSL"), character(0), "ADSL"), |
|
162 |
label = data_label(x), |
|
163 |
code = character(0), |
|
164 |
vars = list(), |
|
165 |
metadata = NULL) { |
|
166 | 66x |
CDISCTealDataset$new( |
167 | 66x |
dataname = dataname, |
168 | 66x |
x = x, |
169 | 66x |
keys = keys, |
170 | 66x |
parent = parent, |
171 | 66x |
label = label, |
172 | 66x |
code = code, |
173 | 66x |
vars = vars, |
174 | 66x |
metadata = metadata |
175 |
) |
|
176 |
} |
|
177 | ||
178 |
#' Load `CDISCTealDataset` object from a file |
|
179 |
#' |
|
180 |
#' @description `r lifecycle::badge("experimental")` |
|
181 |
#' Please note that the script has to end with a call creating desired object. The error will be raised otherwise. |
|
182 |
#' |
|
183 |
#' @inheritParams dataset_file |
|
184 |
#' |
|
185 |
#' @return (`CDISCTealDataset`) object |
|
186 |
#' |
|
187 |
#' @export |
|
188 |
#' |
|
189 |
#' @examples |
|
190 |
#' # simple example |
|
191 |
#' file_example <- tempfile(fileext = ".R") |
|
192 |
#' writeLines( |
|
193 |
#' text = c( |
|
194 |
#' "library(teal.data) |
|
195 |
#' cdisc_dataset(dataname = \"ADSL\", |
|
196 |
#' x = teal.data::example_cdisc_data(\"ADSL\"), |
|
197 |
#' code = \"ADSL <- teal.data::example_cdisc_data('ADSL')\")" |
|
198 |
#' ), |
|
199 |
#' con = file_example |
|
200 |
#' ) |
|
201 |
#' x <- cdisc_dataset_file(file_example, code = character(0)) |
|
202 |
#' get_code(x) |
|
203 |
cdisc_dataset_file <- function(path, code = get_code(path)) { |
|
204 | ! |
object <- object_file(path, "CDISCTealDataset") |
205 | ! |
object$set_code(code) |
206 | ! |
return(object) |
207 |
} |
1 |
## CDISCTealDatasetConnector ==== |
|
2 |
#' |
|
3 |
#' @title A `CDISCTealDatasetConnector` class of objects |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("stable")` |
|
6 |
#' Objects of this class store the connection function to fetch a single dataset. |
|
7 |
#' |
|
8 |
#' The difference compared to `TealDatasetConnector` is a parent field that |
|
9 |
#' indicates name of the parent dataset. Note that the parent field might |
|
10 |
#' be empty (i.e. `character(0)`). |
|
11 |
#' |
|
12 |
#' @param dataname (`character`)\cr |
|
13 |
#' A given name for the dataset it may not contain spaces |
|
14 |
#' |
|
15 |
#' @param pull_callable (`CallableFunction`)\cr |
|
16 |
#' function with necessary arguments set to fetch data from connection. |
|
17 |
#' |
|
18 |
#' @param keys (`character`)\cr |
|
19 |
#' vector of dataset primary keys column names |
|
20 |
#' |
|
21 |
#' @param parent optional, (`character`) \cr |
|
22 |
#' parent dataset name |
|
23 |
#' |
|
24 |
#' @param label (`character`)\cr |
|
25 |
#' Label to describe the dataset. |
|
26 |
#' |
|
27 |
#' @param code (`character`)\cr |
|
28 |
#' A character string defining code to modify `raw_data` from this dataset. To modify |
|
29 |
#' current dataset code should contain at least one assignment to object defined in `dataname` |
|
30 |
#' argument. For example if `dataname = ADSL` example code should contain |
|
31 |
#' `ADSL <- <some R code>`. Can't be used simultaneously with `script` |
|
32 |
#' |
|
33 |
#' @param script (`character`)\cr |
|
34 |
#' Alternatively to `code` - location of the file containing modification code. |
|
35 |
#' Can't be used simultaneously with `script`. |
|
36 |
#' |
|
37 |
#' @param vars (named `list`)) \cr |
|
38 |
#' In case when this object code depends on other `TealDataset` object(s) or |
|
39 |
#' other constant value, this/these object(s) should be included as named |
|
40 |
#' element(s) of the list. For example if this object code needs `ADSL` |
|
41 |
#' object we should specify `vars = list(ADSL = <adsl object>)`. |
|
42 |
#' It's recommended to include `TealDataset` or `TealDatasetConnector` objects to |
|
43 |
#' the `vars` list to preserve reproducibility. Please note that `vars` |
|
44 |
#' are included to this object as local `vars` and they cannot be modified |
|
45 |
#' within another dataset. |
|
46 |
#' |
|
47 |
#' @param metadata (named `list`, `NULL` or `CallableFunction`) \cr |
|
48 |
#' Field containing either the metadata about the dataset (each element of the list |
|
49 |
#' should be atomic and length one) or a `CallableFuntion` to pull the metadata |
|
50 |
#' from a connection. This should return a `list` or an object which can be |
|
51 |
#' converted to a list with `as.list`. |
|
52 |
CDISCTealDatasetConnector <- R6::R6Class( # nolint |
|
53 |
classname = "CDISCTealDatasetConnector", |
|
54 |
inherit = TealDatasetConnector, |
|
55 | ||
56 |
## __Public Methods ==== |
|
57 |
public = list( |
|
58 |
#' @description |
|
59 |
#' Create a new `TealDatasetConnector` object. Set the pulling function |
|
60 |
#' `CallableFunction` which returns a `data.frame`, e.g. by reading |
|
61 |
#' from a function or creating it on the fly. |
|
62 |
initialize = function(dataname, |
|
63 |
pull_callable, |
|
64 |
keys, parent, |
|
65 |
code = character(0), |
|
66 |
label = character(0), |
|
67 |
vars = list(), |
|
68 |
metadata = NULL) { |
|
69 | 37x |
super$initialize( |
70 | 37x |
dataname = dataname, |
71 | 37x |
pull_callable = pull_callable, |
72 | 37x |
keys = keys, |
73 | 37x |
code = code, |
74 | 37x |
label = label, |
75 | 37x |
vars = vars, |
76 | 37x |
metadata = metadata |
77 |
) |
|
78 | 37x |
private$set_parent(parent) |
79 | 37x |
logger::log_trace("CDISCTealDatasetConnector initialized for dataset: { deparse1(self$get_dataname()) }") |
80 | ||
81 | 37x |
return(invisible(self)) |
82 |
}, |
|
83 |
#' @description |
|
84 |
#' Get parent dataset name |
|
85 |
#' @return (`character`) indicating parent `dataname` |
|
86 |
get_parent = function() { |
|
87 | 49x |
private$parent |
88 |
}, |
|
89 | ||
90 |
#' @description |
|
91 |
#' Pull the data |
|
92 |
#' |
|
93 |
#' Read or create the data using `pull_callable` specified in the constructor. |
|
94 |
#' |
|
95 |
#' @param args (`NULL` or named `list`)\cr |
|
96 |
#' additional dynamic arguments for pull function. `args` can be omitted if `pull_callable` |
|
97 |
#' from constructor already contains all necessary arguments to pull data. One can try |
|
98 |
#' to execute `pull_callable` directly by `x$pull_callable$run()` or to get code using |
|
99 |
#' `x$pull_callable$get_code()`. `args` specified in pull are used temporary to get data but |
|
100 |
#' not saved in code. |
|
101 |
#' @param try (`logical` value)\cr |
|
102 |
#' whether perform function evaluation inside `try` clause |
|
103 |
#' |
|
104 |
#' @return `self` invisibly for chaining. |
|
105 |
pull = function(args = NULL, try = FALSE) { |
|
106 | 28x |
logger::log_trace("CDISCTealDatasetConnector$pull pulling dataset: { deparse1(self$get_dataname()) }.") |
107 | 28x |
super$pull(args = args, try = try) |
108 | ||
109 | 27x |
if (!self$is_failed()) { |
110 | 27x |
private$dataset <- as_cdisc( |
111 | 27x |
private$dataset, |
112 | 27x |
parent = self$get_parent() |
113 |
) |
|
114 | 27x |
logger::log_trace("CDISCTealDatasetConnector$pull pulled dataset: { deparse1(self$get_dataname()) }.") |
115 |
} else { |
|
116 | ! |
logger::log_error("CDISCTealDatasetConnector$pull failed to pull dataset: { deparse1(self$get_dataname()) }.") |
117 |
} |
|
118 | 27x |
return(invisible(self)) |
119 |
} |
|
120 |
), |
|
121 | ||
122 |
## __Private Fields ==== |
|
123 |
private = list( |
|
124 |
parent = character(0), |
|
125 | ||
126 |
## __Private Methods ==== |
|
127 |
set_parent = function(parent) { |
|
128 | 37x |
checkmate::assert_character(parent, max.len = 1, any.missing = FALSE) |
129 | 37x |
private$parent <- parent |
130 | 37x |
return(invisible(self)) |
131 |
} |
|
132 |
) |
|
133 |
) |
1 |
#' Convert a `TealDataset(Connector)` object to a `CDISCTealDataset(Connector)` object |
|
2 |
#' |
|
3 |
#' Convert a `TealDataset(Connector)` object to a `CDISCTealDataset(Connector)` object |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("stable")` |
|
6 |
#' |
|
7 |
#' @note If passed a `CDISC`-flavored object it returns the unmodified object. |
|
8 |
#' |
|
9 |
#' @param x an object of `TealDataset` or `TealDatasetConnector` class |
|
10 |
#' @inheritParams cdisc_dataset |
|
11 |
#' |
|
12 |
#' @return (`CDISCTealDataset` or `CDISCTealDatasetConnector`) object |
|
13 |
#' |
|
14 |
#' @export |
|
15 |
as_cdisc <- function(x, parent = `if`(identical(get_dataname(x), "ADSL"), character(0), "ADSL")) { |
|
16 | 50x |
if (any(class(x) %in% c("CDISCTealDataset", "CDISCTealDatasetConnector"))) { |
17 | 2x |
x |
18 |
} else { |
|
19 | 48x |
UseMethod("as_cdisc") |
20 |
} |
|
21 |
} |
|
22 | ||
23 |
#' @rdname as_cdisc |
|
24 |
#' @export |
|
25 |
#' @examples |
|
26 |
#' # TealDataset -------- |
|
27 |
#' |
|
28 |
#' as_cdisc( |
|
29 |
#' dataset( |
|
30 |
#' "ADSL", |
|
31 |
#' teal.data::example_cdisc_data("ADSL"), |
|
32 |
#' keys = get_cdisc_keys("ADSL"), |
|
33 |
#' code = "ADSL <- teal.data::example_cdisc_data(\"ADSL\")" |
|
34 |
#' ) |
|
35 |
#' ) |
|
36 |
#' as_cdisc( |
|
37 |
#' dataset( |
|
38 |
#' "ADAE", |
|
39 |
#' teal.data::example_cdisc_data("ADAE"), |
|
40 |
#' keys = get_cdisc_keys("ADAE"), |
|
41 |
#' code = "ADAE <- teal.data::example_cdisc_data(\"ADAE\")" |
|
42 |
#' ), |
|
43 |
#' parent = "ADSL" |
|
44 |
#' ) |
|
45 |
as_cdisc.TealDataset <- function(x, parent = `if`(identical(get_dataname(x), "ADSL"), character(0), "ADSL")) { |
|
46 | 31x |
if (length(get_keys(x)) > 0 || !(get_dataname(x) %in% names(default_cdisc_keys))) { |
47 | 30x |
cdisc_dataset( |
48 | 30x |
dataname = get_dataname(x), |
49 | 30x |
x = get_raw_data(x), |
50 | 30x |
keys = get_keys(x), |
51 | 30x |
parent = parent, |
52 | 30x |
label = get_dataset_label(x), |
53 | 30x |
code = x$get_code_class(), |
54 | 30x |
metadata = x$get_metadata() |
55 |
) |
|
56 |
} else { |
|
57 | 1x |
cdisc_dataset( |
58 | 1x |
dataname = get_dataname(x), |
59 | 1x |
x = get_raw_data(x), |
60 | 1x |
parent = parent, |
61 | 1x |
label = get_dataset_label(x), |
62 | 1x |
code = x$get_code_class(), |
63 | 1x |
metadata = x$get_metadata() |
64 |
) |
|
65 |
} |
|
66 |
} |
|
67 | ||
68 |
#' @rdname as_cdisc |
|
69 |
#' @export |
|
70 |
#' @examples |
|
71 |
#' # TealDatasetConnector -------- |
|
72 |
#' library(magrittr) |
|
73 |
#' pull_fun_adsl <- callable_function(teal.data::example_cdisc_data) %>% |
|
74 |
#' set_args(list(dataname = "ADSL")) |
|
75 |
#' as_cdisc( |
|
76 |
#' dataset_connector( |
|
77 |
#' "ADSL", |
|
78 |
#' pull_fun_adsl, |
|
79 |
#' keys = get_cdisc_keys("ADSL") |
|
80 |
#' ) |
|
81 |
#' ) |
|
82 |
#' |
|
83 |
#' pull_fun_adae <- callable_function(teal.data::example_cdisc_data) %>% |
|
84 |
#' set_args(list(dataname = "ADAE")) |
|
85 |
#' as_cdisc( |
|
86 |
#' dataset_connector( |
|
87 |
#' "ADAE", |
|
88 |
#' pull_fun_adae, |
|
89 |
#' keys = get_cdisc_keys("ADAE") |
|
90 |
#' ), |
|
91 |
#' parent = "ADSL" |
|
92 |
#' ) |
|
93 |
as_cdisc.TealDatasetConnector <- function(x, parent = `if`(identical(get_dataname(x), "ADSL"), character(0), "ADSL")) { |
|
94 | 17x |
ds <- tryCatch( |
95 | 17x |
expr = get_dataset(x), |
96 | 17x |
error = function(e) NULL |
97 |
) |
|
98 | 17x |
if (!is.null(ds)) { |
99 | ! |
warning( |
100 | ! |
"Pulled 'dataset' from 'x' will not be passed to CDISCTealDatasetConnector. |
101 | ! |
Avoid pulling before conversion." |
102 |
) |
|
103 |
} |
|
104 | ||
105 | 17x |
cdisc_dataset_connector( |
106 | 17x |
dataname = get_dataname(x), |
107 | 17x |
pull_callable = x$get_pull_callable(), |
108 | 17x |
keys = get_keys(x), |
109 | 17x |
parent = parent, |
110 | 17x |
label = get_dataset_label(x), |
111 | 17x |
vars = x$.__enclos_env__$private$pull_vars, |
112 | 17x |
metadata = x$.__enclos_env__$private$metadata |
113 |
) |
|
114 |
} |
1 |
#' Include `JS` files from `/inst/js/` package directory to application header |
|
2 |
#' |
|
3 |
#' `system.file` should not be used to access files in other packages, it does |
|
4 |
#' not work with `devtools`. Therefore, we redefine this method in each package |
|
5 |
#' as needed. Thus, we do not export this method |
|
6 |
#' |
|
7 |
#' @param pattern (`character`) pattern of files to be included, passed to `system.file` |
|
8 |
#' @param except (`character`) vector of basename filenames to be excluded |
|
9 |
#' |
|
10 |
#' @return HTML code that includes `JS` files |
|
11 |
#' @keywords internal |
|
12 |
include_js_files <- function(pattern = NULL, except = NULL) { |
|
13 | 4x |
checkmate::assert_character(except, min.len = 1, any.missing = FALSE, null.ok = TRUE) |
14 | 4x |
js_files <- list.files( |
15 | 4x |
system.file("js", package = "teal.data", mustWork = TRUE), |
16 | 4x |
pattern = pattern, full.names = TRUE |
17 |
) |
|
18 | 4x |
js_files <- js_files[!(basename(js_files) %in% except)] # no-op if except is NULL |
19 | 4x |
if (length(js_files) == 0) { |
20 | ! |
return(NULL) |
21 |
} |
|
22 | 4x |
return(singleton(lapply(js_files, includeScript))) |
23 |
} |
1 |
.onLoad <- function(libname, pkgname) { # nolint |
|
2 |
# expose default CDISC dataset names |
|
3 |
# copy from excel file |
|
4 | ! |
default_cdisc_keys <- yaml::yaml.load_file( |
5 | ! |
get_package_file("teal.data", "cdisc_datasets/cdisc_datasets.yaml") |
6 | ! |
) # nolint |
7 | ! |
assign("default_cdisc_keys", default_cdisc_keys, envir = parent.env(environment())) |
8 | ||
9 |
# Set up the teal logger instance |
|
10 | ! |
teal.logger::register_logger("teal.data") |
11 | ||
12 | ! |
invisible() |
13 |
} |
1 |
#' Get dataset attributes |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Get dataset attributes in form of named list. |
|
5 |
#' |
|
6 |
#' @param x an object of (`TealDataset`) class |
|
7 |
#' |
|
8 |
#' @return named `list` of object attributes |
|
9 |
#' |
|
10 |
#' @export |
|
11 |
get_attrs <- function(x) { |
|
12 | ! |
UseMethod("get_attrs") |
13 |
} |
|
14 | ||
15 | ||
16 |
#' @rdname get_attrs |
|
17 |
#' @export |
|
18 |
#' @examples |
|
19 |
#' # TealDataset -------- |
|
20 |
#' |
|
21 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
22 |
#' |
|
23 |
#' x1 <- dataset("ADSL", x = ADSL, label = "custom label") |
|
24 |
#' get_attrs(x1) |
|
25 |
#' |
|
26 |
#' x2 <- dataset( |
|
27 |
#' "ADSL", |
|
28 |
#' x = ADSL, |
|
29 |
#' keys = get_cdisc_keys("ADSL"), |
|
30 |
#' label = "custom label" |
|
31 |
#' ) |
|
32 |
#' get_attrs(x2) |
|
33 |
#' |
|
34 |
#' # CDISCTealDataset -------- |
|
35 |
#' |
|
36 |
#' ADSL <- teal.data::example_cdisc_data("ADSL") |
|
37 |
#' x3 <- cdisc_dataset( |
|
38 |
#' "ADSL", |
|
39 |
#' x = ADSL, |
|
40 |
#' keys = get_cdisc_keys("ADSL"), |
|
41 |
#' label = "custom label" |
|
42 |
#' ) |
|
43 |
#' get_attrs(x3) |
|
44 |
get_attrs.TealDataset <- function(x) { |
|
45 | ! |
return(x$get_attrs()) |
46 |
} |