1 |
no_select_keyword <- "-- no selection --" |
|
2 | ||
3 |
#' Choices Selected |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("stable")` |
|
6 |
#' Construct a single list containing available choices, the default selected value, and |
|
7 |
#' additional settings such as to order the choices with the selected elements appearing first |
|
8 |
#' or whether to block the user from making selections. Can be used in `ui` input elements |
|
9 |
#' such as [teal.widgets::optionalSelectInput()] |
|
10 |
#' |
|
11 |
#' @param choices (`character`) vector of possible choices or `delayed_data` object\cr |
|
12 |
#' See [variable_choices()] and [value_choices()]. |
|
13 |
#' @param selected (`character`) vector of preselected options, (`all_choices`) object |
|
14 |
#' or (`delayed_data`) object. If `delayed_data` object then `choices` must also be |
|
15 |
#' a `delayed_data` object. If not supplied it will default to the first element of |
|
16 |
#' `choices` if `choices` is a vector, or `NULL` if `choices` is a `delayed_data` object. |
|
17 |
#' @param keep_order (`logical`)\cr |
|
18 |
#' In case of `FALSE` the selected variables will be on top of the drop-down field. |
|
19 |
#' @param fixed optional, (`logical`)\cr |
|
20 |
#' Whether to block user to select choices |
|
21 |
#' |
|
22 |
#' @details |
|
23 |
#' |
|
24 |
#' Please note that the order of selected will always follow the order of choices. The `keep_order` |
|
25 |
#' argument is set to false which will run the following code inside: |
|
26 |
#' |
|
27 |
#' `choices <- c(selected, setdiff(choices, selected))` |
|
28 |
#' |
|
29 |
#' in case you want to keep your specific order of choices, set `keep_order` to `TRUE`. |
|
30 |
#' |
|
31 |
#' @return Object of class `choices_selected` and of type list which contains the specified |
|
32 |
#' `choices`, `selected`, `keep_order` and `fixed`. |
|
33 |
#' |
|
34 |
#' @export |
|
35 |
#' |
|
36 |
#' @examples |
|
37 |
#' |
|
38 |
#' library(shiny) |
|
39 |
#' |
|
40 |
#' # all_choices example - semantically the same objects |
|
41 |
#' choices_selected(choices = letters, selected = all_choices()) |
|
42 |
#' choices_selected(choices = letters, selected = letters) |
|
43 |
#' |
|
44 |
#' choices_selected( |
|
45 |
#' choices = stats::setNames(LETTERS[1:5], paste("Letter", LETTERS[1:5])), |
|
46 |
#' selected = "C" |
|
47 |
#' ) |
|
48 |
#' |
|
49 |
#' ADSL <- teal.transform::rADSL |
|
50 |
#' choices_selected(variable_choices(ADSL), "SEX") |
|
51 |
#' |
|
52 |
#' # How to select nothing |
|
53 |
#' # use an empty character |
|
54 |
#' choices_selected( |
|
55 |
#' choices = c("", "A", "B", "C"), |
|
56 |
#' selected = "" |
|
57 |
#' ) |
|
58 |
#' |
|
59 |
#' # How to allow the user to select nothing |
|
60 |
#' # use an empty character |
|
61 |
#' choices_selected( |
|
62 |
#' choices = c("A", "", "B", "C"), |
|
63 |
#' selected = "A" |
|
64 |
#' ) |
|
65 |
#' |
|
66 |
#' |
|
67 |
#' # How to make Nothing the Xth choice |
|
68 |
#' # just use keep_order |
|
69 |
#' choices_selected( |
|
70 |
#' choices = c("A", "", "B", "C"), |
|
71 |
#' selected = "A", |
|
72 |
#' keep_order = TRUE |
|
73 |
#' ) |
|
74 |
#' |
|
75 |
#' |
|
76 |
#' # How to give labels to selections |
|
77 |
#' # by adding names - choices will be replaced by "name" in UI, not in code |
|
78 |
#' choices_selected( |
|
79 |
#' choices = c("name for A" = "A", "Name for nothing" = "", "name for b" = "B", "name for C" = "C"), |
|
80 |
#' selected = "A" |
|
81 |
#' ) |
|
82 |
#' |
|
83 |
#' # by using choices_labeled |
|
84 |
#' # labels will be shown behind the choice |
|
85 |
#' choices_selected( |
|
86 |
#' choices = choices_labeled( |
|
87 |
#' c("A", "", "B", "C"), |
|
88 |
#' c("name for A", "nothing", "name for B", "name for C") |
|
89 |
#' ), |
|
90 |
#' selected = "A" |
|
91 |
#' ) |
|
92 |
#' |
|
93 |
#' # Passing a `delayed_data` object to `selected` |
|
94 |
#' choices_selected( |
|
95 |
#' choices = variable_choices("ADSL"), |
|
96 |
#' selected = variable_choices("ADSL", subset = c("STUDYID")) |
|
97 |
#' ) |
|
98 |
#' |
|
99 |
#' # functional form (subsetting for factor variables only) of choices_selected |
|
100 |
#' # with delayed data loading |
|
101 |
#' choices_selected(variable_choices("ADSL", subset = function(data) { |
|
102 |
#' idx <- vapply(data, is.factor, logical(1)) |
|
103 |
#' return(names(data)[idx]) |
|
104 |
#' })) |
|
105 |
#' |
|
106 |
#' cs <- choices_selected( |
|
107 |
#' choices = c("A", "B", "C"), |
|
108 |
#' selected = "A" |
|
109 |
#' ) |
|
110 |
#' |
|
111 |
#' ui <- fluidPage( |
|
112 |
#' teal.widgets::optionalSelectInput( |
|
113 |
#' inputId = "id", |
|
114 |
#' choices = cs$choices, |
|
115 |
#' selected = cs$selected |
|
116 |
#' ) |
|
117 |
#' ) |
|
118 |
#' \dontrun{ |
|
119 |
#' shinyApp(ui, server = function(input, output, session) {}) |
|
120 |
#' } |
|
121 |
choices_selected <- function(choices, |
|
122 |
selected = if (inherits(choices, "delayed_data")) NULL else choices[1], |
|
123 |
keep_order = FALSE, |
|
124 |
fixed = FALSE) { |
|
125 | 34x |
stopifnot(is.atomic(choices) || inherits(choices, "delayed_data")) |
126 | 34x |
stopifnot(is.atomic(selected) || inherits(selected, "delayed_data") || inherits(selected, "all_choices")) |
127 | 34x |
checkmate::assert_flag(keep_order) |
128 | 34x |
checkmate::assert_flag(fixed) |
129 | ||
130 | 1x |
if (inherits(selected, "all_choices")) selected <- choices |
131 | ||
132 | 34x |
if (inherits(selected, "delayed_data") && !inherits(choices, "delayed_data")) { |
133 | 1x |
stop("If 'selected' is of class 'delayed_data', so must be 'choices'.") |
134 |
} |
|
135 | ||
136 | 33x |
if (inherits(choices, "delayed_data")) { |
137 | 12x |
out <- structure( |
138 | 12x |
list(choices = choices, selected = selected, keep_order = keep_order, fixed = fixed), |
139 | 12x |
class = c("delayed_choices_selected", "delayed_data", "choices_selected") |
140 |
) |
|
141 | 12x |
return(out) |
142 |
} |
|
143 | ||
144 | 21x |
if (!is.null(choices) && no_select_keyword %in% choices) { |
145 | 1x |
stop(paste(no_select_keyword, "is not a valid choice as it is used as a keyword")) |
146 |
} |
|
147 | ||
148 |
# remove duplicates |
|
149 | 20x |
choices <- vector_remove_dups(choices) |
150 | 20x |
selected <- vector_remove_dups(selected) |
151 | ||
152 | 20x |
if (!all(selected %in% choices)) { |
153 | 3x |
stop(paste( |
154 | 3x |
paste( |
155 | 3x |
selected[which(!selected %in% choices)], |
156 | 3x |
collapse = ", " |
157 |
), |
|
158 | 3x |
"'selected' but not in 'choices'" |
159 |
)) |
|
160 |
} |
|
161 | ||
162 | 17x |
if (!keep_order && length(choices) > 0) { |
163 | 17x |
choices_in_selected <- which(choices %in% selected) |
164 | 17x |
choices <- vector_reorder( |
165 | 17x |
choices, |
166 | 17x |
c(choices_in_selected, setdiff(seq_along(choices), choices_in_selected)) |
167 |
) |
|
168 |
} |
|
169 | ||
170 | 17x |
structure( |
171 | 17x |
list( |
172 | 17x |
choices = choices, |
173 | 17x |
selected = selected, |
174 | 17x |
fixed = fixed |
175 |
), |
|
176 | 17x |
class = "choices_selected" |
177 |
) |
|
178 |
} |
|
179 | ||
180 |
#' Check if an object is a choices_selected class. |
|
181 |
#' |
|
182 |
#' @description `r lifecycle::badge("stable")` |
|
183 |
#' |
|
184 |
#' @param x object to check |
|
185 |
#' @rdname choices_selected |
|
186 |
#' @export |
|
187 |
is.choices_selected <- function(x) { # nolint |
|
188 | 24x |
inherits(x, "choices_selected") |
189 |
} |
|
190 | ||
191 |
#' Add empty choice to choices selected |
|
192 |
#' |
|
193 |
#' @description `r lifecycle::badge("stable")` |
|
194 |
#' |
|
195 |
#' @param x (\code{choices_selected}) output |
|
196 |
#' @param multiple (\code{logical}) whether multiple selections are allowed or not |
|
197 |
#' |
|
198 |
#' @export |
|
199 |
add_no_selected_choices <- function(x, multiple = FALSE) { |
|
200 | ! |
if (is.null(x)) { |
201 | ! |
choices_selected(NULL) |
202 |
} else { |
|
203 | ! |
stopifnot(is.choices_selected(x)) |
204 | ||
205 | ! |
if (!multiple) { |
206 | ! |
x$choices <- c(no_select_keyword, x$choices) |
207 | ! |
if (is.null(x$selected)) x$selected <- no_select_keyword |
208 |
} |
|
209 | ||
210 | ! |
x |
211 |
} |
|
212 |
} |
|
213 | ||
214 |
#' Check select choices for no choice made |
|
215 |
#' |
|
216 |
#' @description `r lifecycle::badge("stable")` |
|
217 |
#' |
|
218 |
#' @param x (\code{character}) Word that shall be checked for |
|
219 |
#' NULL, empty, "--no-selection" |
|
220 |
#' |
|
221 |
#' @return the word or NULL |
|
222 |
#' |
|
223 |
#' @export |
|
224 |
no_selected_as_NULL <- function(x) { # nolint |
|
225 | ! |
if (is.null(x) || identical(x, no_select_keyword) || x == "") { |
226 | ! |
NULL |
227 |
} else { |
|
228 | ! |
x |
229 |
} |
|
230 |
} |
|
231 | ||
232 |
## Non-exported utils functions ---- |
|
233 |
## Modify vectors and keep attributes |
|
234 |
vector_reorder <- function(vec, idx) { |
|
235 | 17x |
stopifnot(is.atomic(vec)) |
236 | 17x |
checkmate::assert_integer(idx, min.len = 1, lower = 1, any.missing = FALSE) |
237 | 17x |
stopifnot(length(vec) == length(idx)) |
238 | ||
239 | 17x |
vec_attrs <- attributes(vec) |
240 | ||
241 | 17x |
vec <- vec[idx] |
242 | ||
243 | 17x |
for (vec_attrs_idx in seq_along(vec_attrs)) { |
244 | 48x |
if (length(vec_attrs[[vec_attrs_idx]]) == length(vec)) { |
245 | 47x |
vec_attrs[[vec_attrs_idx]] <- vec_attrs[[vec_attrs_idx]][idx] |
246 |
} |
|
247 |
} |
|
248 | ||
249 | 17x |
attributes(vec) <- vec_attrs |
250 | ||
251 | 17x |
return(vec) |
252 |
} |
|
253 | ||
254 |
vector_pop <- function(vec, idx) { |
|
255 | 1x |
stopifnot(is.atomic(vec)) |
256 | 1x |
checkmate::assert_integer(idx, lower = 1, any.missing = FALSE) |
257 | ||
258 | 1x |
if (length(idx) == 0) { |
259 | ! |
return(vec) |
260 |
} |
|
261 | ||
262 | 1x |
vec_attrs <- attributes(vec) |
263 | 1x |
names_vec_attrs <- names(vec_attrs) |
264 | ||
265 | 1x |
for (vec_attrs_idx in seq_along(vec_attrs)) { |
266 | 4x |
if (length(vec_attrs[[vec_attrs_idx]]) == length(vec) && names_vec_attrs[vec_attrs_idx] != "class") { |
267 | 3x |
vec_attrs[[vec_attrs_idx]] <- vec_attrs[[vec_attrs_idx]][-idx] |
268 |
} |
|
269 |
} |
|
270 | ||
271 | 1x |
vec <- vec[-idx] |
272 | 1x |
attributes(vec) <- vec_attrs |
273 | 1x |
return(vec) |
274 |
} |
|
275 | ||
276 |
vector_remove_dups <- function(vec) { |
|
277 | 40x |
stopifnot(is.atomic(vec)) |
278 | ||
279 | 40x |
idx <- which(duplicated(vec)) |
280 | ||
281 | 40x |
if (length(idx) == 0) { |
282 | 35x |
return(vec) |
283 | 5x |
} else if (is.null(attributes(vec))) { |
284 | 2x |
return(unique(vec)) |
285 | 3x |
} else if (identical(names(attributes(vec)), "names")) { |
286 | 2x |
return(vec[-idx]) |
287 |
} else { |
|
288 | 1x |
return(vector_pop(vec, idx)) |
289 |
} |
|
290 |
} |
1 |
#' Set "`<choice>:<label>`" type of Names |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' This is often useful for [choices_selected] as it marks up the drop-down boxes |
|
5 |
#' for [shiny::selectInput()]. |
|
6 |
#' |
|
7 |
#' @param choices A character / factor / numeric / logical vector. |
|
8 |
#' @param labels character vector containing labels to be applied to `choices`. If `NA` then |
|
9 |
#' "Label Missing" will be used. |
|
10 |
#' @param subset a vector that is a subset of `choices`. This is useful if |
|
11 |
#' only a few variables need to be named. If this argument is used, the returned vector will |
|
12 |
#' match its order. |
|
13 |
#' @param types Character vector containing the types of the columns to be used for applying the appropriate |
|
14 |
#' icons to the [choices_selected] drop down box. (e.g. "numeric") |
|
15 |
#' @details If either `choices` or `labels` are factors, they are coerced to character. |
|
16 |
#' Duplicated elements from `choices` get removed. |
|
17 |
#' |
|
18 |
#' @return a named character vector |
|
19 |
#' |
|
20 |
#' @export |
|
21 |
#' |
|
22 |
#' @examples |
|
23 |
#' library(shiny) |
|
24 |
#' |
|
25 |
#' ADSL <- teal.transform::rADSL |
|
26 |
#' ADTTE <- teal.transform::rADTTE |
|
27 |
#' choices1 <- choices_labeled(names(ADSL), teal.data::col_labels(ADSL, fill = FALSE)) |
|
28 |
#' choices2 <- choices_labeled(ADTTE$PARAMCD, ADTTE$PARAM) |
|
29 |
#' # if only a subset of variables are needed, use subset argument |
|
30 |
#' choices3 <- choices_labeled( |
|
31 |
#' names(ADSL), |
|
32 |
#' teal.data::col_labels(ADSL, fill = FALSE), |
|
33 |
#' subset = c("ARMCD", "ARM") |
|
34 |
#' ) |
|
35 |
#' \dontrun{ |
|
36 |
#' shinyApp( |
|
37 |
#' ui = fluidPage( |
|
38 |
#' selectInput("c1", |
|
39 |
#' label = "Choices from ADSL", |
|
40 |
#' choices = choices1, |
|
41 |
#' selected = choices1[1] |
|
42 |
#' ), |
|
43 |
#' selectInput("c2", |
|
44 |
#' label = "Choices from ADTTE", |
|
45 |
#' choices = choices2, |
|
46 |
#' selected = choices2[1] |
|
47 |
#' ), |
|
48 |
#' selectInput("c3", |
|
49 |
#' label = "Arm choices from ADSL", |
|
50 |
#' choices = choices3, |
|
51 |
#' selected = choices3[1] |
|
52 |
#' ) |
|
53 |
#' ), |
|
54 |
#' server = function(input, output) {} |
|
55 |
#' ) |
|
56 |
#' } |
|
57 |
choices_labeled <- function(choices, labels, subset = NULL, types = NULL) { |
|
58 | 308x |
if (is.factor(choices)) { |
59 | ! |
choices <- as.character(choices) |
60 |
} |
|
61 | ||
62 | 308x |
checkmate::assert_atomic(choices, min.len = 1, any.missing = FALSE) |
63 | ||
64 | 308x |
if (is.factor(labels)) { |
65 | ! |
labels <- as.character(labels) |
66 |
} |
|
67 | ||
68 | 308x |
checkmate::assert_character(labels[!is.na(labels)], any.missing = FALSE) |
69 | 308x |
if (length(choices) != length(labels)) { |
70 | ! |
stop("length of choices must be the same as labels") |
71 |
} |
|
72 | 308x |
checkmate::assert_subset(subset, choices, empty.ok = TRUE) |
73 | 308x |
checkmate::assert_character(types, len = length(choices), null.ok = TRUE) |
74 | ||
75 | 308x |
if (!is.null(subset)) { |
76 | 281x |
if (!all(subset %in% choices)) { |
77 | ! |
stop("all of subset variables must be in choices") |
78 |
} |
|
79 | 281x |
labels <- labels[choices %in% subset] |
80 | 281x |
types <- types[choices %in% subset] |
81 | 281x |
choices <- choices[choices %in% subset] |
82 |
} |
|
83 | ||
84 | 308x |
is_dupl <- duplicated(choices) |
85 | 308x |
choices <- choices[!is_dupl] |
86 | 308x |
labels <- labels[!is_dupl] |
87 | 308x |
types <- types[!is_dupl] |
88 | 308x |
labels[is.na(labels)] <- "Label Missing" |
89 | 308x |
raw_labels <- labels |
90 | 308x |
combined_labels <- if (length(choices) > 0) { |
91 | 308x |
paste0(choices, ": ", labels) |
92 |
} else { |
|
93 | ! |
character(0) |
94 |
} |
|
95 | ||
96 | 308x |
if (!is.null(subset)) { |
97 | 281x |
ord <- match(subset, choices) |
98 | 281x |
choices <- choices[ord] |
99 | 281x |
raw_labels <- raw_labels[ord] |
100 | 281x |
combined_labels <- combined_labels[ord] |
101 | 281x |
types <- types[ord] |
102 |
} |
|
103 | 308x |
choices <- structure( |
104 | 308x |
choices, |
105 | 308x |
names = combined_labels, |
106 | 308x |
raw_labels = raw_labels, |
107 | 308x |
combined_labels = combined_labels, |
108 | 308x |
class = c("choices_labeled", "character"), |
109 | 308x |
types = types |
110 |
) |
|
111 | ||
112 | 308x |
return(choices) |
113 |
} |
|
114 | ||
115 | ||
116 |
#' Wrapper on [choices_labeled] to label variables basing on existing labels in data |
|
117 |
#' |
|
118 |
#' @description `r lifecycle::badge("stable")` |
|
119 |
#' |
|
120 |
#' @param data (`data.frame`, `character`, `TealDataset`, `TealDatasetConnector`) |
|
121 |
#' If `data.frame`, then data to extract labels from |
|
122 |
#' If `character`, then name of the dataset to extract data from once available |
|
123 |
#' If `TealDataset` or `TealDatasetConnector`, then raw data to extract labels from. |
|
124 |
#' @param subset (`character` or `function`) |
|
125 |
#' If `character`, then a vector of column names. |
|
126 |
#' If `function`, then this function is used to determine the possible columns (e.g. all factor columns). |
|
127 |
#' In this case, the function must take only single argument "data" and return a character vector. |
|
128 |
#' See examples for more details. |
|
129 |
#' @param key (`character`) vector with names of the variables, which are part of the primary key |
|
130 |
#' of the `data` argument. This is an optional argument, which allows to identify variables |
|
131 |
#' associated with the primary key and display the appropriate icon for them in the |
|
132 |
#' [teal.widgets::optionalSelectInput()] widget. |
|
133 |
#' @param fill (`logical(1)`) if `TRUE`, the function will return variable names for columns with non-existent labels; |
|
134 |
#' otherwise will return `NA` for them |
|
135 |
#' |
|
136 |
#' @return named character vector with additional attributes or `delayed_data` object |
|
137 |
#' |
|
138 |
#' @rdname variable_choices |
|
139 |
#' |
|
140 |
#' @export |
|
141 |
#' |
|
142 |
#' @examples |
|
143 |
#' ADRS <- teal.transform::rADRS |
|
144 |
#' variable_choices(ADRS) |
|
145 |
#' variable_choices(ADRS, subset = c("PARAM", "PARAMCD")) |
|
146 |
#' variable_choices(ADRS, subset = c("", "PARAM", "PARAMCD")) |
|
147 |
#' variable_choices(ADRS, subset = c("", "PARAM", "PARAMCD"), key = teal.data::get_cdisc_keys("ADRS")) |
|
148 |
#' |
|
149 |
#' # delayed version |
|
150 |
#' variable_choices("ADRS", subset = c("USUBJID", "STUDYID")) |
|
151 |
#' |
|
152 |
#' # also works with [teal.data::TealDataset] and [teal.data::TealDatasetConnector] |
|
153 |
#' ADRS_dataset <- teal.data::dataset("ADRS", ADRS, key = teal.data::get_cdisc_keys("ADRS")) |
|
154 |
#' variable_choices(ADRS_dataset) |
|
155 |
#' |
|
156 |
#' ADRS_conn <- teal.data::dataset_connector( |
|
157 |
#' "ADRS", |
|
158 |
#' pull_callable = teal.data::callable_code("radrs(cached = TRUE)"), |
|
159 |
#' key = teal.data::get_cdisc_keys("ADRS") |
|
160 |
#' ) |
|
161 |
#' variable_choices(ADRS_conn) |
|
162 |
#' |
|
163 |
#' # functional subset (with delayed data) - return only factor variables |
|
164 |
#' variable_choices("ADRS", subset = function(data) { |
|
165 |
#' idx <- vapply(data, is.factor, logical(1)) |
|
166 |
#' return(names(data)[idx]) |
|
167 |
#' }) |
|
168 |
variable_choices <- function(data, subset = NULL, fill = FALSE, key = NULL) { |
|
169 | 299x |
checkmate::assert( |
170 | 299x |
checkmate::check_character(subset, null.ok = TRUE, any.missing = FALSE), |
171 | 299x |
checkmate::check_function(subset) |
172 |
) |
|
173 | 299x |
checkmate::assert_flag(fill) |
174 | 299x |
checkmate::assert_character(key, null.ok = TRUE, any.missing = FALSE) |
175 | ||
176 | 299x |
UseMethod("variable_choices") |
177 |
} |
|
178 | ||
179 |
#' @rdname variable_choices |
|
180 |
#' @export |
|
181 |
variable_choices.character <- function(data, subset = NULL, fill = FALSE, key = NULL) { |
|
182 | 102x |
structure(list(data = data, subset = subset, key = key), |
183 | 102x |
class = c("delayed_variable_choices", "delayed_data", "choices_labeled") |
184 |
) |
|
185 |
} |
|
186 | ||
187 |
#' @rdname variable_choices |
|
188 |
#' @export |
|
189 |
variable_choices.data.frame <- function(data, subset = NULL, fill = TRUE, key = NULL) { # nolint |
|
190 | ||
191 | 193x |
checkmate::assert( |
192 | 193x |
checkmate::check_character(subset, null.ok = TRUE), |
193 | 193x |
checkmate::check_function(subset, null.ok = TRUE) |
194 |
) |
|
195 | ||
196 | 193x |
if (is.function(subset)) { |
197 | 20x |
subset <- resolve_delayed_expr(subset, ds = data, is_value_choices = FALSE) |
198 |
} |
|
199 | ||
200 | 193x |
checkmate::assert_subset(subset, c("", names(data)), empty.ok = TRUE) |
201 | ||
202 | 193x |
if (length(subset) == 0) { |
203 | 21x |
subset <- names(data) |
204 |
} |
|
205 | ||
206 | 193x |
key <- intersect(subset, key) |
207 | ||
208 | 193x |
var_types <- stats::setNames(teal.slice:::variable_types(data = data), names(data)) |
209 | 193x |
if (length(key) != 0) { |
210 | 59x |
var_types[key] <- "primary_key" |
211 |
} |
|
212 | ||
213 | 193x |
if (any(duplicated(subset))) { |
214 | ! |
warning( |
215 | ! |
"removed duplicated entries in subset:", |
216 | ! |
paste(unique(subset[duplicated(subset)]), collapse = ", ") |
217 |
) |
|
218 | ! |
subset <- unique(subset) |
219 |
} |
|
220 | ||
221 | 193x |
res <- if ("" %in% subset) { |
222 | ! |
choices_labeled( |
223 | ! |
choices = c("", names(data)), |
224 | ! |
labels = c("", unname(teal.data::col_labels(data, fill = fill))), |
225 | ! |
subset = subset, |
226 | ! |
types = c("", var_types) |
227 |
) |
|
228 |
} else { |
|
229 | 193x |
choices_labeled( |
230 | 193x |
choices = names(data), |
231 | 193x |
labels = unname(teal.data::col_labels(data, fill = fill)), |
232 | 193x |
subset = subset, |
233 | 193x |
types = var_types |
234 |
) |
|
235 |
} |
|
236 | ||
237 | 193x |
return(res) |
238 |
} |
|
239 | ||
240 |
#' @rdname variable_choices |
|
241 |
#' @export |
|
242 |
variable_choices.TealDataset <- function(data, subset = NULL, fill = FALSE, key = teal.data::get_keys(data)) { |
|
243 | 4x |
variable_choices( |
244 | 4x |
data = teal.data::get_raw_data(data), |
245 | 4x |
subset = subset, |
246 | 4x |
fill = fill, |
247 | 4x |
key = key |
248 |
) |
|
249 |
} |
|
250 | ||
251 |
#' @rdname variable_choices |
|
252 |
#' @export |
|
253 |
variable_choices.TealDatasetConnector <- function(data, # nolint |
|
254 |
subset = NULL, |
|
255 |
fill = FALSE, |
|
256 |
key = teal.data::get_keys(data)) { |
|
257 | ! |
if (teal.data::is_pulled(data)) { |
258 | ! |
variable_choices( |
259 | ! |
data = teal.data::get_raw_data(data), |
260 | ! |
subset = subset, |
261 | ! |
fill = fill, |
262 | ! |
key = key |
263 |
) |
|
264 |
} else { |
|
265 | ! |
variable_choices( |
266 | ! |
data = teal.data::get_dataname(data), |
267 | ! |
subset = subset, |
268 | ! |
fill = fill, |
269 | ! |
key = key |
270 |
) |
|
271 |
} |
|
272 |
} |
|
273 | ||
274 | ||
275 |
#' Wrapper on [choices_labeled] to label variable values basing on other variable values |
|
276 |
#' |
|
277 |
#' @description `r lifecycle::badge("stable")` |
|
278 |
#' |
|
279 |
#' @param data (`data.frame`, `character`, `TealDataset`, `TealDatasetConnector`) |
|
280 |
#' If `data.frame`, then data to extract labels from |
|
281 |
#' If `character`, then name of the dataset to extract data from once available |
|
282 |
#' If `TealDataset` or `TealDatasetConnector`, then raw data to extract labels from. |
|
283 |
#' @param var_choices (`character` or `NULL`) vector with choices column names |
|
284 |
#' @param var_label (`character`) vector with labels column names |
|
285 |
#' @param subset (`character` or `function`) |
|
286 |
#' If `character`, vector with values to subset. |
|
287 |
#' If `function`, then this function is used to determine the possible columns (e.g. all factor columns). |
|
288 |
#' In this case, the function must take only single argument "data" and return a character vector. |
|
289 |
#' See examples for more details. |
|
290 |
#' @param sep (`character`) separator used in case of multiple column names |
|
291 |
#' |
|
292 |
#' @return named character vector or `delayed_data` object |
|
293 |
#' |
|
294 |
#' @rdname value_choices |
|
295 |
#' |
|
296 |
#' @export |
|
297 |
#' |
|
298 |
#' @examples |
|
299 |
#' ADRS <- teal.transform::rADRS |
|
300 |
#' value_choices(ADRS, "PARAMCD", "PARAM", subset = c("BESRSPI", "INVET")) |
|
301 |
#' value_choices(ADRS, c("PARAMCD", "ARMCD"), c("PARAM", "ARM")) |
|
302 |
#' value_choices(ADRS, c("PARAMCD", "ARMCD"), c("PARAM", "ARM"), |
|
303 |
#' subset = c("BESRSPI - ARM A", "INVET - ARM A", "OVRINV - ARM A") |
|
304 |
#' ) |
|
305 |
#' value_choices(ADRS, c("PARAMCD", "ARMCD"), c("PARAM", "ARM"), sep = " --- ") |
|
306 |
#' |
|
307 |
#' # delayed version |
|
308 |
#' value_choices("ADRS", c("PARAMCD", "ARMCD"), c("PARAM", "ARM")) |
|
309 |
#' |
|
310 |
#' # functional subset |
|
311 |
#' value_choices(ADRS, "PARAMCD", "PARAM", subset = function(data) { |
|
312 |
#' return(levels(data$PARAMCD)[1:2]) |
|
313 |
#' }) |
|
314 |
value_choices <- function(data, |
|
315 |
var_choices, |
|
316 |
var_label = NULL, |
|
317 |
subset = NULL, |
|
318 |
sep = " - ") { |
|
319 | 176x |
checkmate::assert_character(var_choices, any.missing = FALSE) |
320 | 176x |
checkmate::assert_character(var_label, len = length(var_choices), null.ok = TRUE, any.missing = FALSE) |
321 | 176x |
checkmate::assert( |
322 | 176x |
checkmate::check_vector(subset, null.ok = TRUE), |
323 | 176x |
checkmate::check_function(subset) |
324 |
) |
|
325 | 176x |
checkmate::assert_string(sep) |
326 | 176x |
UseMethod("value_choices") |
327 |
} |
|
328 | ||
329 |
#' @rdname value_choices |
|
330 |
#' @export |
|
331 |
value_choices.character <- function(data, |
|
332 |
var_choices, |
|
333 |
var_label = NULL, |
|
334 |
subset = NULL, |
|
335 |
sep = " - ") { |
|
336 | 58x |
out <- structure( |
337 | 58x |
list( |
338 | 58x |
data = data, |
339 | 58x |
var_choices = var_choices, |
340 | 58x |
var_label = var_label, |
341 | 58x |
subset = subset, |
342 | 58x |
sep = sep |
343 |
), |
|
344 | 58x |
class = c("delayed_value_choices", "delayed_data", "choices_labeled") |
345 |
) |
|
346 | 58x |
return(out) |
347 |
} |
|
348 | ||
349 |
#' @rdname value_choices |
|
350 |
#' @export |
|
351 |
value_choices.data.frame <- function(data, # nolint |
|
352 |
var_choices, |
|
353 |
var_label = NULL, |
|
354 |
subset = NULL, |
|
355 |
sep = " - ") { |
|
356 | 117x |
checkmate::assert_subset(var_choices, names(data)) |
357 | 116x |
checkmate::assert_subset(var_label, names(data), empty.ok = TRUE) |
358 | ||
359 | 115x |
df_choices <- data[var_choices] |
360 | 115x |
df_label <- data[var_label] |
361 | ||
362 | 115x |
for (i in seq_along(var_choices)) { |
363 | 126x |
if ("NA" %in% c(df_choices[[i]], levels(df_choices[[i]])) && any(is.na(df_choices[[i]]))) { |
364 | 6x |
warning(paste0( |
365 | 6x |
"Missing values and the string value of 'NA' both exist in the column of ", var_choices[i], |
366 | 6x |
" either as value(s) or level(s). ", |
367 | 6x |
"This will cause the missing values to be grouped with the actual string 'NA' values in the UI widget." |
368 |
)) |
|
369 |
} |
|
370 |
} |
|
371 | ||
372 | 115x |
choices <- if ( |
373 | 115x |
length(var_choices) > 1 || |
374 | 115x |
is.character(df_choices[[1]]) || |
375 | 115x |
is.factor(df_choices[[1]]) || |
376 | 115x |
inherits(df_choices[[1]], c("Date", "POSIXct", "POSIXlt", "POSIXt")) |
377 |
) { |
|
378 | 113x |
df_choices <- dplyr::mutate_if( |
379 | 113x |
df_choices, |
380 | 113x |
.predicate = function(col) inherits(col, c("POSIXct", "POSIXlt", "POSIXt")), |
381 | 113x |
.fun = function(col) { |
382 | ! |
if (is.null(attr(col, "tzone")) || all(attr(col, "tzone") == "")) { |
383 | ! |
format(trunc(col), "%Y-%m-%d %H:%M:%S") |
384 |
} else { |
|
385 | ! |
format(trunc(col), "%Y-%m-%d %H:%M:%S %Z") |
386 |
} |
|
387 |
} |
|
388 |
) |
|
389 | 113x |
apply(df_choices, 1, paste, collapse = sep) |
390 |
} else { |
|
391 | 2x |
df_choices[[var_choices]] |
392 |
} |
|
393 | 115x |
labels <- apply(df_label, 1, paste, collapse = sep) |
394 | 115x |
df <- unique(data.frame(choices, labels, stringsAsFactors = FALSE)) # unique combo of choices x labels |
395 | ||
396 | 115x |
if (is.function(subset)) { |
397 | 20x |
subset <- resolve_delayed_expr(subset, ds = data, is_value_choices = TRUE) |
398 |
} |
|
399 | 115x |
res <- choices_labeled( |
400 | 115x |
choices = df$choices, |
401 | 115x |
labels = df$labels, |
402 | 115x |
subset = subset |
403 |
) |
|
404 | 115x |
attr(res, "sep") <- sep |
405 | 115x |
attr(res, "var_choices") <- var_choices |
406 | 115x |
attr(res, "var_label") <- var_label |
407 | 115x |
return(res) |
408 |
} |
|
409 | ||
410 |
#' @rdname value_choices |
|
411 |
#' @export |
|
412 |
value_choices.TealDataset <- function(data, |
|
413 |
var_choices, |
|
414 |
var_label = NULL, |
|
415 |
subset = NULL, |
|
416 |
sep = " - ") { |
|
417 | 1x |
value_choices( |
418 | 1x |
data = teal.data::get_raw_data(data), |
419 | 1x |
var_choices = var_choices, |
420 | 1x |
var_label = var_label, |
421 | 1x |
subset = subset, |
422 | 1x |
sep = sep |
423 |
) |
|
424 |
} |
|
425 | ||
426 |
#' @rdname value_choices |
|
427 |
#' @export |
|
428 |
value_choices.TealDatasetConnector <- function(data, # nolint |
|
429 |
var_choices, |
|
430 |
var_label = NULL, |
|
431 |
subset = NULL, |
|
432 |
sep = " - ") { |
|
433 | ! |
if (teal.data::is_pulled(data)) { |
434 | ! |
value_choices( |
435 | ! |
data = teal.data::get_raw_data(data), |
436 | ! |
var_choices = var_choices, |
437 | ! |
var_label = var_label, |
438 | ! |
subset = subset, |
439 | ! |
sep = sep |
440 |
) |
|
441 |
} else { |
|
442 | ! |
value_choices( |
443 | ! |
data = teal.data::get_dataname(data), |
444 | ! |
var_choices = var_choices, |
445 | ! |
var_label = var_label, |
446 | ! |
subset = subset, |
447 | ! |
sep = sep |
448 |
) |
|
449 |
} |
|
450 |
} |
|
451 |
#' Print choices_labeled object |
|
452 |
#' @description `r lifecycle::badge("stable")` |
|
453 |
#' @rdname choices_labeled |
|
454 |
#' @param x an object used to select a method. |
|
455 |
#' @param ... further arguments passed to or from other methods. |
|
456 |
#' @export |
|
457 |
print.choices_labeled <- function(x, ...) { |
|
458 | ! |
cat( |
459 | ! |
sprintf("number of choices: %s \n", length(x)), |
460 | ! |
names(x), |
461 |
"", |
|
462 | ! |
sep = "\n" |
463 |
) |
|
464 | ||
465 | ! |
return(invisible(x)) |
466 |
} |
1 |
#' Merge expression module |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("experimental")` |
|
4 |
#' @details This function is a convenient wrapper to combine `data_extract_multiple_srv()` and |
|
5 |
#' `merge_expression_srv()` when no additional processing is required. |
|
6 |
#' Compare the example below with that found in [merge_expression_srv()]. |
|
7 |
#' |
|
8 |
#' @inheritParams shiny::moduleServer |
|
9 |
#' @param datasets (named `list` of `reactive` or non-`reactive` `data.frame`)\cr |
|
10 |
#' object containing data as a list of `data.frame`. When passing a list of non-reactive `data.frame` objects, they are |
|
11 |
#' converted to reactive `data.frame` objects internally. |
|
12 |
#' @param join_keys (`JoinKeys`)\cr |
|
13 |
#' of variables used as join keys for each of the datasets in `datasets`. |
|
14 |
#' This will be used to extract the `keys` of every dataset. |
|
15 |
#' @param data_extract (named `list` of `data_extract_spec`)\cr |
|
16 |
#' @param merge_function (`character(1)`)\cr |
|
17 |
#' A character string of a function that |
|
18 |
#' accepts the arguments `x`, `y` and `by` to perform the merging of datasets. |
|
19 |
#' @param anl_name (`character(1)`)\cr |
|
20 |
#' Name of the analysis dataset. |
|
21 |
#' |
|
22 |
#' @return reactive expression with output from [merge_expression_srv()]. |
|
23 |
#' |
|
24 |
#' @seealso [merge_expression_srv()] |
|
25 |
#' |
|
26 |
#' @examples |
|
27 |
#' library(shiny) |
|
28 |
#' ADSL <- data.frame( |
|
29 |
#' STUDYID = "A", |
|
30 |
#' USUBJID = LETTERS[1:10], |
|
31 |
#' SEX = rep(c("F", "M"), 5), |
|
32 |
#' AGE = rpois(10, 30), |
|
33 |
#' BMRKR1 = rlnorm(10) |
|
34 |
#' ) |
|
35 |
#' ADLB <- expand.grid( |
|
36 |
#' STUDYID = "A", |
|
37 |
#' USUBJID = LETTERS[1:10], |
|
38 |
#' PARAMCD = c("ALT", "CRP", "IGA"), |
|
39 |
#' AVISIT = c("SCREENING", "BASELINE", "WEEK 1 DAY 8", "WEEK 2 DAY 15") |
|
40 |
#' ) |
|
41 |
#' ADLB$AVAL <- rlnorm(120) |
|
42 |
#' ADLB$CHG <- rnorm(120) |
|
43 |
#' |
|
44 |
#' data_list <- list( |
|
45 |
#' ADSL = ADSL, |
|
46 |
#' ADLB = ADLB |
|
47 |
#' ) |
|
48 |
#' |
|
49 |
#' join_keys <- teal.data::join_keys( |
|
50 |
#' teal.data::join_key("ADSL", "ADSL", c("STUDYID", "USUBJID")), |
|
51 |
#' teal.data::join_key("ADSL", "ADLB", c("STUDYID", "USUBJID")), |
|
52 |
#' teal.data::join_key("ADLB", "ADLB", c("STUDYID", "USUBJID", "PARAMCD", "AVISIT")) |
|
53 |
#' ) |
|
54 |
#' |
|
55 |
#' |
|
56 |
#' adsl_extract <- data_extract_spec( |
|
57 |
#' dataname = "ADSL", |
|
58 |
#' select = select_spec( |
|
59 |
#' label = "Select variable:", |
|
60 |
#' choices = c("AGE", "BMRKR1"), |
|
61 |
#' selected = "AGE", |
|
62 |
#' multiple = TRUE, |
|
63 |
#' fixed = FALSE |
|
64 |
#' ) |
|
65 |
#' ) |
|
66 |
#' adlb_extract <- data_extract_spec( |
|
67 |
#' dataname = "ADLB", |
|
68 |
#' filter = filter_spec(vars = "PARAMCD", choices = c("ALT", "CRP", "IGA"), selected = "ALT"), |
|
69 |
#' select = select_spec( |
|
70 |
#' label = "Select variable:", |
|
71 |
#' choices = c("AVAL", "CHG"), |
|
72 |
#' selected = "AVAL", |
|
73 |
#' multiple = TRUE, |
|
74 |
#' fixed = FALSE |
|
75 |
#' ) |
|
76 |
#' ) |
|
77 |
#' app <- shinyApp( |
|
78 |
#' ui = fluidPage( |
|
79 |
#' teal.widgets::standard_layout( |
|
80 |
#' output = div( |
|
81 |
#' verbatimTextOutput("expr"), |
|
82 |
#' dataTableOutput("data") |
|
83 |
#' ), |
|
84 |
#' encoding = tagList( |
|
85 |
#' data_extract_ui("adsl_var", label = "ADSL selection", adsl_extract), |
|
86 |
#' data_extract_ui("adlb_var", label = "ADLB selection", adlb_extract) |
|
87 |
#' ) |
|
88 |
#' ) |
|
89 |
#' ), |
|
90 |
#' server = function(input, output, session) { |
|
91 |
#' data_q <- teal.code::new_qenv() |
|
92 |
#' |
|
93 |
#' data_q <- teal.code::eval_code( |
|
94 |
#' data_q, |
|
95 |
#' "ADSL <- data.frame( |
|
96 |
#' STUDYID = 'A', |
|
97 |
#' USUBJID = LETTERS[1:10], |
|
98 |
#' SEX = rep(c('F', 'M'), 5), |
|
99 |
#' AGE = rpois(10, 30), |
|
100 |
#' BMRKR1 = rlnorm(10) |
|
101 |
#' )" |
|
102 |
#' ) |
|
103 |
#' |
|
104 |
#' data_q <- teal.code::eval_code( |
|
105 |
#' data_q, |
|
106 |
#' "ADLB <- expand.grid( |
|
107 |
#' STUDYID = 'A', |
|
108 |
#' USUBJID = LETTERS[1:10], |
|
109 |
#' PARAMCD = c('ALT', 'CRP', 'IGA'), |
|
110 |
#' AVISIT = c('SCREENING', 'BASELINE', 'WEEK 1 DAY 8', 'WEEK 2 DAY 15'), |
|
111 |
#' AVAL = rlnorm(120), |
|
112 |
#' CHG = rlnorm(120) |
|
113 |
#' )" |
|
114 |
#' ) |
|
115 |
#' |
|
116 |
#' merged_data <- merge_expression_module( |
|
117 |
#' data_extract = list(adsl_var = adsl_extract, adlb_var = adlb_extract), |
|
118 |
#' datasets = data_list, |
|
119 |
#' join_keys = join_keys, |
|
120 |
#' merge_function = "dplyr::left_join" |
|
121 |
#' ) |
|
122 |
#' |
|
123 |
#' code_merge <- reactive({ |
|
124 |
#' for (exp in merged_data()$expr) data_q <- teal.code::eval_code(data_q, exp) |
|
125 |
#' data_q |
|
126 |
#' }) |
|
127 |
#' |
|
128 |
#' output$expr <- renderText(paste(merged_data()$expr, collapse = "\n")) |
|
129 |
#' output$data <- renderDataTable(code_merge()[["ANL"]]) |
|
130 |
#' } |
|
131 |
#' ) |
|
132 |
#' \dontrun{ |
|
133 |
#' runApp(app) |
|
134 |
#' } |
|
135 |
#' @export |
|
136 |
merge_expression_module <- function(datasets, |
|
137 |
join_keys = NULL, |
|
138 |
data_extract, |
|
139 |
merge_function = "dplyr::full_join", |
|
140 |
anl_name = "ANL", |
|
141 |
id = "merge_id") { |
|
142 | 5x |
logger::log_trace("merge_expression_module called with: { paste(names(datasets), collapse = ', ') } datasets.") |
143 | 5x |
checkmate::assert_list(data_extract, names = "named", types = c("list", "data_extract_spec", "NULL")) |
144 | 3x |
lapply(data_extract, function(x) { |
145 | 6x |
if (is.list(x) && !inherits(x, "data_extract_spec")) { |
146 | ! |
checkmate::assert_list(x, "data_extract_spec") |
147 |
} |
|
148 |
}) |
|
149 | ||
150 | 3x |
selector_list <- data_extract_multiple_srv(data_extract, datasets, join_keys) |
151 | ||
152 | 3x |
merge_expression_srv( |
153 | 3x |
id = id, |
154 | 3x |
selector_list = selector_list, |
155 | 3x |
datasets = datasets, |
156 | 3x |
join_keys = join_keys, |
157 | 3x |
merge_function = merge_function, |
158 | 3x |
anl_name = anl_name |
159 |
) |
|
160 |
} |
|
161 | ||
162 | ||
163 |
#' Data merge module server |
|
164 |
#' |
|
165 |
#' @description `r lifecycle::badge("experimental")` |
|
166 |
#' @details When additional processing of the `data_extract` list input is required, `merge_expression_srv()` can be |
|
167 |
#' combined with `data_extract_multiple_srv()` or `data_extract_srv()` to influence the `selector_list` input. |
|
168 |
#' Compare the example below with that found in [merge_expression_module()]. |
|
169 |
#' |
|
170 |
#' @inheritParams shiny::moduleServer |
|
171 |
#' @param datasets (named `list` of `reactive` or non-`reactive` `data.frame`)\cr |
|
172 |
#' object containing data as a list of `data.frame`. When passing a list of non-reactive `data.frame` objects, they are |
|
173 |
#' converted to reactive `data.frame` objects internally. |
|
174 |
#' @param join_keys (`JoinKeys`)\cr |
|
175 |
#' of variables used as join keys for each of the datasets in `datasets`. |
|
176 |
#' This will be used to extract the `keys` of every dataset. |
|
177 |
#' @param selector_list (`reactive`)\cr |
|
178 |
#' output from [data_extract_multiple_srv()] or a reactive named list of outputs from [data_extract_srv()]. |
|
179 |
#' When using a reactive named list, the names must be identical to the shiny ids of the respective |
|
180 |
#' [data_extract_ui()]. |
|
181 |
#' @param merge_function (`character(1)` or `reactive`)\cr |
|
182 |
#' A character string of a function that accepts the arguments |
|
183 |
#' `x`, `y` and `by` to perform the merging of datasets. |
|
184 |
#' @param anl_name (`character(1)`)\cr |
|
185 |
#' Name of the analysis dataset. |
|
186 |
#' |
|
187 |
#' @return reactive expression with output from [merge_datasets()]. |
|
188 |
#' |
|
189 |
#' @seealso [merge_expression_srv()] |
|
190 |
#' |
|
191 |
#' @export |
|
192 |
#' |
|
193 |
#' @examples |
|
194 |
#' library(shiny) |
|
195 |
#' |
|
196 |
#' ADSL <- data.frame( |
|
197 |
#' STUDYID = "A", |
|
198 |
#' USUBJID = LETTERS[1:10], |
|
199 |
#' SEX = rep(c("F", "M"), 5), |
|
200 |
#' AGE = rpois(10, 30), |
|
201 |
#' BMRKR1 = rlnorm(10) |
|
202 |
#' ) |
|
203 |
#' |
|
204 |
#' ADLB <- expand.grid( |
|
205 |
#' STUDYID = "A", |
|
206 |
#' USUBJID = LETTERS[1:10], |
|
207 |
#' PARAMCD = c("ALT", "CRP", "IGA"), |
|
208 |
#' AVISIT = c("SCREENING", "BASELINE", "WEEK 1 DAY 8", "WEEK 2 DAY 15") |
|
209 |
#' ) |
|
210 |
#' ADLB$AVAL <- rlnorm(120) |
|
211 |
#' ADLB$CHG <- rlnorm(120) |
|
212 |
#' |
|
213 |
#' data_list <- list( |
|
214 |
#' ADSL = ADSL, |
|
215 |
#' ADLB = ADLB |
|
216 |
#' ) |
|
217 |
#' |
|
218 |
#' join_keys <- teal.data::join_keys( |
|
219 |
#' teal.data::join_key("ADSL", "ADSL", c("STUDYID", "USUBJID")), |
|
220 |
#' teal.data::join_key("ADSL", "ADLB", c("STUDYID", "USUBJID")), |
|
221 |
#' teal.data::join_key("ADLB", "ADLB", c("STUDYID", "USUBJID", "PARAMCD", "AVISIT")) |
|
222 |
#' ) |
|
223 |
#' |
|
224 |
#' adsl_extract <- data_extract_spec( |
|
225 |
#' dataname = "ADSL", |
|
226 |
#' select = select_spec( |
|
227 |
#' label = "Select variable:", |
|
228 |
#' choices = c("AGE", "BMRKR1"), |
|
229 |
#' selected = "AGE", |
|
230 |
#' multiple = TRUE, |
|
231 |
#' fixed = FALSE |
|
232 |
#' ) |
|
233 |
#' ) |
|
234 |
#' adlb_extract <- data_extract_spec( |
|
235 |
#' dataname = "ADLB", |
|
236 |
#' filter = filter_spec(vars = "PARAMCD", choices = c("ALT", "CRP", "IGA"), selected = "ALT"), |
|
237 |
#' select = select_spec( |
|
238 |
#' label = "Select variable:", |
|
239 |
#' choices = c("AVAL", "CHG"), |
|
240 |
#' selected = "AVAL", |
|
241 |
#' multiple = TRUE, |
|
242 |
#' fixed = FALSE |
|
243 |
#' ) |
|
244 |
#' ) |
|
245 |
#' |
|
246 |
#' app <- shinyApp( |
|
247 |
#' ui = fluidPage( |
|
248 |
#' teal.widgets::standard_layout( |
|
249 |
#' output = div( |
|
250 |
#' verbatimTextOutput("expr"), |
|
251 |
#' dataTableOutput("data") |
|
252 |
#' ), |
|
253 |
#' encoding = tagList( |
|
254 |
#' data_extract_ui("adsl_var", label = "ADSL selection", adsl_extract), |
|
255 |
#' data_extract_ui("adlb_var", label = "ADLB selection", adlb_extract) |
|
256 |
#' ) |
|
257 |
#' ) |
|
258 |
#' ), |
|
259 |
#' server = function(input, output, session) { |
|
260 |
#' data_q <- teal.code::new_qenv() |
|
261 |
#' |
|
262 |
#' data_q <- teal.code::eval_code( |
|
263 |
#' data_q, |
|
264 |
#' "ADSL <- data.frame( |
|
265 |
#' STUDYID = 'A', |
|
266 |
#' USUBJID = LETTERS[1:10], |
|
267 |
#' SEX = rep(c('F', 'M'), 5), |
|
268 |
#' AGE = rpois(10, 30), |
|
269 |
#' BMRKR1 = rlnorm(10) |
|
270 |
#' )" |
|
271 |
#' ) |
|
272 |
#' |
|
273 |
#' data_q <- teal.code::eval_code( |
|
274 |
#' data_q, |
|
275 |
#' "ADLB <- expand.grid( |
|
276 |
#' STUDYID = 'A', |
|
277 |
#' USUBJID = LETTERS[1:10], |
|
278 |
#' PARAMCD = c('ALT', 'CRP', 'IGA'), |
|
279 |
#' AVISIT = c('SCREENING', 'BASELINE', 'WEEK 1 DAY 8', 'WEEK 2 DAY 15'), |
|
280 |
#' AVAL = rlnorm(120), |
|
281 |
#' CHG = rlnorm(120) |
|
282 |
#' )" |
|
283 |
#' ) |
|
284 |
#' |
|
285 |
#' selector_list <- data_extract_multiple_srv( |
|
286 |
#' list(adsl_var = adsl_extract, adlb_var = adlb_extract), |
|
287 |
#' datasets = data_list |
|
288 |
#' ) |
|
289 |
#' merged_data <- merge_expression_srv( |
|
290 |
#' selector_list = selector_list, |
|
291 |
#' datasets = data_list, |
|
292 |
#' join_keys = join_keys, |
|
293 |
#' merge_function = "dplyr::left_join" |
|
294 |
#' ) |
|
295 |
#' |
|
296 |
#' code_merge <- reactive({ |
|
297 |
#' for (exp in merged_data()$expr) data_q <- teal.code::eval_code(data_q, exp) |
|
298 |
#' data_q |
|
299 |
#' }) |
|
300 |
#' |
|
301 |
#' output$expr <- renderText(paste(merged_data()$expr, collapse = "\n")) |
|
302 |
#' output$data <- renderDataTable(code_merge()[["ANL"]]) |
|
303 |
#' } |
|
304 |
#' ) |
|
305 |
#' \dontrun{ |
|
306 |
#' runApp(app) |
|
307 |
#' } |
|
308 |
merge_expression_srv <- function(id = "merge_id", |
|
309 |
selector_list, |
|
310 |
datasets, |
|
311 |
join_keys, |
|
312 |
merge_function = "dplyr::full_join", |
|
313 |
anl_name = "ANL") { |
|
314 | 23x |
checkmate::assert_string(anl_name) |
315 | 22x |
stopifnot(make.names(anl_name) == anl_name) |
316 | 19x |
checkmate::assert_class(selector_list, "reactive") |
317 | 17x |
checkmate::assert_list(datasets, types = c("reactive", "data.frame"), names = "named") |
318 | 15x |
checkmate::assert_class(join_keys, "JoinKeys") |
319 | ||
320 | 14x |
moduleServer( |
321 | 14x |
id, |
322 | 14x |
function(input, output, session) { |
323 | 14x |
logger::log_trace( |
324 | 14x |
"merge_expression_srv initialized with: { paste(names(datasets), collapse = ', ') } datasets." |
325 |
) |
|
326 | ||
327 |
# convert to list of reactives |
|
328 | 14x |
datasets <- sapply(X = datasets, simplify = FALSE, FUN = function(x) { |
329 | 2x |
if (is.reactive(x)) x else reactive(x) |
330 |
}) |
|
331 | ||
332 | 14x |
reactive({ |
333 | 7x |
checkmate::assert_list(selector_list(), names = "named", types = "reactive") |
334 | 5x |
merge_fun_name <- if (inherits(merge_function, "reactive")) merge_function() else merge_function |
335 | 5x |
check_merge_function(merge_fun_name) |
336 | ||
337 |
# function to filter out selectors which are NULL or only have validator |
|
338 | 5x |
f <- function(x) { |
339 | 7x |
is.null(x) || (length(names(x)) == 1 && names(x) == "iv") |
340 |
} |
|
341 | ||
342 | 5x |
ds <- Filter(Negate(f), lapply(selector_list(), function(x) x())) |
343 | 5x |
validate(need(length(ds) > 0, "At least one dataset needs to be selected")) |
344 | 5x |
merge_datasets( |
345 | 5x |
selector_list = ds, |
346 | 5x |
datasets = datasets, |
347 | 5x |
join_keys = join_keys, |
348 | 5x |
merge_function = merge_fun_name, |
349 | 5x |
anl_name = anl_name |
350 |
) |
|
351 |
}) |
|
352 |
} |
|
353 |
) |
|
354 |
} |
1 |
check_data_extract_spec <- function(data_extract_spec) { |
|
2 | 22x |
if (is.null(data_extract_spec)) { |
3 | ! |
return() |
4 |
} |
|
5 | 22x |
checkmate::assert_list(data_extract_spec, types = "data_extract_spec") |
6 |
} |
|
7 | ||
8 |
id_for_dataset <- function(dataname) { |
|
9 | 46x |
paste0("dataset_", dataname, "_singleextract") |
10 |
} |
|
11 | ||
12 |
#' |
|
13 |
#' Creates a panel that displays (with filter and column selection) |
|
14 |
#' conditionally on `input[ns("dataset")] == dataname`. |
|
15 |
#' |
|
16 |
#' @param ns (`function`) the shiny namespace function |
|
17 |
#' @param single_data_extract_spec (`data_extract_spec`) the specification |
|
18 |
#' for extraction of data during the application initialization. |
|
19 |
#' Generated by [data_extract_spec()]. |
|
20 |
#' |
|
21 |
#' @return (`shiny.tag`) the Shiny tag with the HTML code for the panel |
|
22 |
#' @keywords internal |
|
23 |
#' @examples |
|
24 |
#' teal.transform:::cond_data_extract_single_ui( |
|
25 |
#' shiny::NS("TEST"), |
|
26 |
#' data_extract_spec(dataname = "test") |
|
27 |
#' ) |
|
28 |
cond_data_extract_single_ui <- function(ns, single_data_extract_spec) { |
|
29 | 2x |
dataname <- single_data_extract_spec$dataname |
30 | 2x |
conditionalPanel( |
31 | 2x |
condition = paste0("input['", ns("dataset"), "'] == '", dataname, "'"), |
32 | 2x |
data_extract_single_ui( |
33 | 2x |
id = ns(id_for_dataset(dataname)), |
34 | 2x |
single_data_extract_spec = single_data_extract_spec |
35 |
) |
|
36 |
) |
|
37 |
} |
|
38 | ||
39 |
#' teal data extraction module user-interface |
|
40 |
#' |
|
41 |
#' @description `r lifecycle::badge("experimental")` |
|
42 |
#' This functionality should be used in the encoding panel of your `teal` app. It will |
|
43 |
#' allow app-developers to specify a [data_extract_spec] object. This object should be used |
|
44 |
#' to teal module variables being filtered data from `CDISC` datasets. You can use this function in the same way as any |
|
45 |
#' [shiny module](https://shiny.rstudio.com/articles/modules.html) UI. The corresponding server module |
|
46 |
#' can be found in [data_extract_srv()]. |
|
47 |
#' |
|
48 |
#' @param id (`character`) shiny input unique identifier |
|
49 |
#' @param label (`character`) Label above the data extract input |
|
50 |
#' @param data_extract_spec (`list` of `data_extract_spec`) |
|
51 |
#' This is the outcome of listing [data_extract_spec] |
|
52 |
#' constructor calls. |
|
53 |
#' @param is_single_dataset (`logical`) FALSE to display the dataset widget |
|
54 |
#' |
|
55 |
#' @return shiny [shiny::selectInput]`s` that allow to define how to extract data from |
|
56 |
#' a specific dataset. The input elements will be returned inside a [shiny::div] container. |
|
57 |
#' |
|
58 |
#' There are three inputs that will be rendered |
|
59 |
#' \enumerate{ |
|
60 |
#' \item{Dataset select}{ Optional. If more than one [data_extract_spec] is handed over |
|
61 |
#' to the function, a shiny [shiny::selectInput] will be rendered. Else just the name |
|
62 |
#' of the dataset is given. |
|
63 |
#' } |
|
64 |
#' \item{Filter Panel }{Optional. If the [data_extract_spec] contains a |
|
65 |
#' filter element a shiny [shiny::selectInput] will be rendered with the options to |
|
66 |
#' filter the dataset. |
|
67 |
#' } |
|
68 |
#' \item{Select panel }{A shiny [shiny::selectInput] to select columns from the dataset to |
|
69 |
#' go into the analysis. |
|
70 |
#' } |
|
71 |
#' } |
|
72 |
#' |
|
73 |
#' The output can be analyzed using `data_extract_srv(...)`. |
|
74 |
#' |
|
75 |
#' @examples |
|
76 |
#' library(shiny) |
|
77 |
#' |
|
78 |
#' adtte_filters <- filter_spec( |
|
79 |
#' vars = c("PARAMCD", "CNSR"), |
|
80 |
#' sep = "-", |
|
81 |
#' choices = c("OS-1" = "OS-1", "OS-0" = "OS-0", "PFS-1" = "PFS-1"), |
|
82 |
#' selected = "OS-1", |
|
83 |
#' multiple = FALSE, |
|
84 |
#' label = "Choose endpoint and Censor" |
|
85 |
#' ) |
|
86 |
#' |
|
87 |
#' response_spec <- data_extract_spec( |
|
88 |
#' dataname = "ADTTE", |
|
89 |
#' filter = adtte_filters, |
|
90 |
#' select = select_spec( |
|
91 |
#' choices = c("AVAL", "BMRKR1", "AGE"), |
|
92 |
#' selected = c("AVAL", "BMRKR1"), |
|
93 |
#' multiple = TRUE, |
|
94 |
#' fixed = FALSE, |
|
95 |
#' label = "Column" |
|
96 |
#' ) |
|
97 |
#' ) |
|
98 |
#' # Call to use inside your teal module UI function |
|
99 |
#' teal.widgets::standard_layout( |
|
100 |
#' output = tableOutput("table"), |
|
101 |
#' encoding = div( |
|
102 |
#' data_extract_ui( |
|
103 |
#' id = "regressor", |
|
104 |
#' label = "Regressor Variable", |
|
105 |
#' data_extract_spec = response_spec |
|
106 |
#' ) |
|
107 |
#' ) |
|
108 |
#' ) |
|
109 |
#' |
|
110 |
#' @export |
|
111 |
data_extract_ui <- function(id, label, data_extract_spec, is_single_dataset = FALSE) { |
|
112 | 2x |
ns <- NS(id) |
113 | ||
114 | 2x |
if (inherits(data_extract_spec, "data_extract_spec")) { |
115 | ! |
data_extract_spec <- list(data_extract_spec) |
116 |
} |
|
117 | 2x |
check_data_extract_spec(data_extract_spec) |
118 | ||
119 | 2x |
if (is.null(data_extract_spec)) { |
120 | ! |
return(helpText(sprintf("Data extraction with label '%s' is NULL. Please contact the app author.", label))) |
121 |
} |
|
122 | 2x |
stopifnot( |
123 | 2x |
`more than one dataset in data_extract_spec but is_single_dataset parameter is set to TRUE` = |
124 | 2x |
!is_single_dataset || length(data_extract_spec) == 1 |
125 |
) |
|
126 | ||
127 | 1x |
dataset_names <- vapply( |
128 | 1x |
data_extract_spec, |
129 | 1x |
function(x) x$dataname, |
130 | 1x |
character(1), |
131 | 1x |
USE.NAMES = FALSE |
132 |
) |
|
133 | ||
134 | 1x |
stopifnot(`list contains data_extract_spec objects with the same dataset` = all(!duplicated(dataset_names))) |
135 | ||
136 | 1x |
dataset_input <- if (is_single_dataset) { |
137 | ! |
NULL |
138 |
} else { |
|
139 | 1x |
if (length(dataset_names) == 1) { |
140 | ! |
if ((is.null(data_extract_spec[[1]]$filter)) && |
141 |
( |
|
142 | ! |
!is.null(data_extract_spec[[1]]$select$fixed) && |
143 | ! |
data_extract_spec[[1]]$select$fixed == TRUE |
144 |
)) { |
|
145 | ! |
NULL |
146 |
} else { |
|
147 | ! |
helpText("Dataset:", tags$code(dataset_names)) |
148 |
} |
|
149 |
} else { |
|
150 | 1x |
teal.widgets::optionalSelectInput( |
151 | 1x |
inputId = ns("dataset"), |
152 | 1x |
label = "Dataset", |
153 | 1x |
choices = dataset_names, |
154 | 1x |
selected = dataset_names[1], |
155 | 1x |
multiple = FALSE |
156 |
) |
|
157 |
} |
|
158 |
} |
|
159 | 1x |
tagList( |
160 | 1x |
include_css_files(pattern = "data_extract"), |
161 | 1x |
div( |
162 | 1x |
class = "data-extract", |
163 | 1x |
tags$label(label), |
164 | 1x |
dataset_input, |
165 | 1x |
if (length(dataset_names) == 1) { |
166 | ! |
data_extract_single_ui( |
167 | ! |
id = ns(id_for_dataset(dataset_names)), |
168 | ! |
single_data_extract_spec = data_extract_spec[[1]] |
169 |
) |
|
170 |
} else { |
|
171 | 1x |
do.call( |
172 | 1x |
div, |
173 | 1x |
unname(lapply( |
174 | 1x |
data_extract_spec, |
175 | 1x |
function(x) { |
176 | 2x |
cond_data_extract_single_ui(ns, x) |
177 |
} |
|
178 |
)) |
|
179 |
) |
|
180 |
} |
|
181 |
) |
|
182 |
) |
|
183 |
} |
|
184 | ||
185 |
#' Function to check data_extract_specs |
|
186 |
#' |
|
187 |
#' Checks if `dataname` argument exists as a dataset. |
|
188 |
#' Checks if selected or filter columns exist within the datasets. Throws a `shiny` |
|
189 |
#' validation error if the above requirements are not met. |
|
190 |
#' |
|
191 |
#' @param datasets (`FilteredData`) the object created using the `teal` API |
|
192 |
#' @param data_extract (`list`) the output of the `data_extract` module |
|
193 |
#' |
|
194 |
#' @return `NULL` |
|
195 |
#' @keywords internal |
|
196 |
check_data_extract_spec_react <- function(datasets, data_extract) { |
|
197 | ! |
if (!all(unlist(lapply(data_extract, `[[`, "dataname")) %in% datasets$datanames())) { |
198 | ! |
shiny::validate( |
199 | ! |
"Error in data_extract_spec setup:\ |
200 | ! |
Data extract spec contains datasets that were not handed over to the teal app." |
201 |
) |
|
202 |
} |
|
203 | ||
204 | ! |
column_return <- unlist(lapply( |
205 | ! |
data_extract, |
206 | ! |
function(data_extract_spec) { |
207 | ! |
columns_filter <- if (is.null(data_extract_spec$filter)) { |
208 | ! |
NULL |
209 |
} else { |
|
210 | ! |
unique(unlist(lapply( |
211 | ! |
data_extract_spec$filter, |
212 | ! |
function(x) { |
213 | ! |
if (inherits(x, "filter_spec")) { |
214 | ! |
x$vars_choices |
215 |
} else { |
|
216 | ! |
stop("Unsupported object class") |
217 |
} |
|
218 |
} |
|
219 |
))) |
|
220 |
} |
|
221 | ||
222 | ! |
columns_ds <- unique(c( |
223 | ! |
data_extract_spec$select$choices, |
224 | ! |
columns_filter |
225 |
)) |
|
226 | ||
227 | ! |
if (!all(columns_ds %in% names(datasets$get_data(data_extract_spec$dataname, filtered = FALSE)))) { |
228 | ! |
non_columns <- columns_ds[!columns_ds %in% names( |
229 | ! |
datasets$get_data(data_extract_spec$dataname, filtered = FALSE) |
230 |
)] |
|
231 | ! |
paste0( |
232 | ! |
"Error in data_extract_spec setup: ", |
233 | ! |
"Column '", |
234 | ! |
non_columns, |
235 | ! |
"' is not inside dataset '", |
236 | ! |
data_extract_spec$dataname, "'." |
237 |
) |
|
238 |
} |
|
239 |
} |
|
240 |
)) |
|
241 | ||
242 | ! |
if (!is.null(column_return)) shiny::validate(unlist(column_return)) |
243 | ! |
NULL |
244 |
} |
|
245 | ||
246 |
#' Extraction of the selector(s) details |
|
247 |
#' |
|
248 |
#' @description `r lifecycle::badge("stable")` |
|
249 |
#' Extracting details of the selection(s) in [data_extract_ui] elements. |
|
250 |
#' |
|
251 |
#' @inheritParams shiny::moduleServer |
|
252 |
#' @param datasets (`FilteredData` or `list` of `reactive` or non-`reactive` `data.frame`)\cr |
|
253 |
#' object containing data either in the form of [teal.slice::FilteredData] or as a list of `data.frame`. |
|
254 |
#' When passing a list of non-reactive `data.frame` objects, they are converted to reactive `data.frame`s internally. |
|
255 |
#' When passing a list of reactive or non-reactive `data.frame` objects, the argument `join_keys` is required also. |
|
256 |
#' @param data_extract_spec (`data_extract_spec` or a list of `data_extract_spec`)\cr |
|
257 |
#' A list of data filter and select information constructed by [data_extract_spec]. |
|
258 |
#' @param ... |
|
259 |
#' an additional argument `join_keys` is required when `datasets` is a list of `data.frame`. |
|
260 |
#' It shall contain the keys per dataset in `datasets`. |
|
261 |
#' |
|
262 |
#' @return |
|
263 |
#' A reactive `list` containing following fields: |
|
264 |
#' |
|
265 |
#' \itemize{ |
|
266 |
#' \item{`filters`: }{A list with the information on the filters that are applied to the data set.} |
|
267 |
#' \item{`select`: }{The variables that are selected from the dataset.} |
|
268 |
#' \item{`always_selected`: }{The column names from the data set that should always be selected.} |
|
269 |
#' \item{`reshape`: }{Whether reshape long to wide should be applied or not.} |
|
270 |
#' \item{`dataname`: }{The name of the data set.} |
|
271 |
#' \item{`internal_id`: }{The `id` of the corresponding shiny input element.} |
|
272 |
#' \item{`keys`: }{The names of the columns that can be used to merge the data set.} |
|
273 |
#' \item{`iv`:}{A `shinyvalidate::InputValidator` containing `validator` for this `data_extract`} |
|
274 |
#' } |
|
275 |
#' |
|
276 |
#' @references [data_extract_srv] |
|
277 |
#' |
|
278 |
#' @export |
|
279 |
#' |
|
280 |
#' @examples |
|
281 |
#' |
|
282 |
#' library(shiny) |
|
283 |
#' library(shinyvalidate) |
|
284 |
#' |
|
285 |
#' ADSL <- data.frame( |
|
286 |
#' STUDYID = "A", |
|
287 |
#' USUBJID = LETTERS[1:10], |
|
288 |
#' SEX = rep(c("F", "M"), 5), |
|
289 |
#' AGE = rpois(10, 30), |
|
290 |
#' BMRKR1 = rlnorm(10) |
|
291 |
#' ) |
|
292 |
#' |
|
293 |
#' adsl_extract <- data_extract_spec( |
|
294 |
#' dataname = "ADSL", |
|
295 |
#' filter = filter_spec(vars = "SEX", choices = c("F", "M"), selected = "F"), |
|
296 |
#' select = select_spec( |
|
297 |
#' label = "Select variable:", |
|
298 |
#' choices = variable_choices(ADSL, c("AGE", "BMRKR1")), |
|
299 |
#' selected = "AGE", |
|
300 |
#' multiple = TRUE, |
|
301 |
#' fixed = FALSE |
|
302 |
#' ) |
|
303 |
#' ) |
|
304 |
#' |
|
305 |
#' # Using reactive list of data.frames |
|
306 |
#' data_list <- list(ADSL = reactive(ADSL)) |
|
307 |
#' |
|
308 |
#' join_keys <- teal.data::join_keys(teal.data::join_key("ADSL", "ADSL", c("STUDYID", "USUBJID"))) |
|
309 |
#' |
|
310 |
#' app <- shinyApp( |
|
311 |
#' ui = fluidPage( |
|
312 |
#' teal.widgets::standard_layout( |
|
313 |
#' output = verbatimTextOutput("out1"), |
|
314 |
#' encoding = tagList( |
|
315 |
#' data_extract_ui( |
|
316 |
#' id = "adsl_var", |
|
317 |
#' label = "ADSL selection", |
|
318 |
#' data_extract_spec = adsl_extract |
|
319 |
#' ) |
|
320 |
#' ) |
|
321 |
#' ) |
|
322 |
#' ), |
|
323 |
#' server = function(input, output, session) { |
|
324 |
#' adsl_reactive_input <- data_extract_srv( |
|
325 |
#' id = "adsl_var", |
|
326 |
#' datasets = data_list, |
|
327 |
#' data_extract_spec = adsl_extract, |
|
328 |
#' join_keys = join_keys, |
|
329 |
#' select_validation_rule = sv_required("Please select a variable.") |
|
330 |
#' ) |
|
331 |
#' |
|
332 |
#' iv_r <- reactive({ |
|
333 |
#' iv <- InputValidator$new() |
|
334 |
#' iv$add_validator(adsl_reactive_input()$iv) |
|
335 |
#' iv$enable() |
|
336 |
#' iv |
|
337 |
#' }) |
|
338 |
#' |
|
339 |
#' output$out1 <- renderPrint({ |
|
340 |
#' if (iv_r()$is_valid()) { |
|
341 |
#' cat(format_data_extract(adsl_reactive_input())) |
|
342 |
#' } else { |
|
343 |
#' "Please fix errors in your selection" |
|
344 |
#' } |
|
345 |
#' }) |
|
346 |
#' } |
|
347 |
#' ) |
|
348 |
#' if (interactive()) { |
|
349 |
#' runApp(app) |
|
350 |
#' } |
|
351 |
#' |
|
352 |
#' # Using FilteredData - Note this method will be deprecated |
|
353 |
#' datasets <- teal.slice::init_filtered_data( |
|
354 |
#' list(ADSL = list(dataset = ADSL)), |
|
355 |
#' join_keys = teal.data::join_keys( |
|
356 |
#' teal.data::join_key("ADSL", "ADSL", c("USUBJID", "STUDYID")) |
|
357 |
#' ) |
|
358 |
#' ) |
|
359 |
#' |
|
360 |
#' app <- shinyApp( |
|
361 |
#' ui = fluidPage( |
|
362 |
#' teal.widgets::standard_layout( |
|
363 |
#' output = verbatimTextOutput("out1"), |
|
364 |
#' encoding = tagList( |
|
365 |
#' data_extract_ui( |
|
366 |
#' id = "adsl_var", |
|
367 |
#' label = "ADSL selection", |
|
368 |
#' data_extract_spec = adsl_extract |
|
369 |
#' ) |
|
370 |
#' ) |
|
371 |
#' ) |
|
372 |
#' ), |
|
373 |
#' server = function(input, output, session) { |
|
374 |
#' adsl_reactive_input <- data_extract_srv( |
|
375 |
#' id = "adsl_var", |
|
376 |
#' datasets = datasets, |
|
377 |
#' data_extract_spec = adsl_extract |
|
378 |
#' ) |
|
379 |
#' |
|
380 |
#' output$out1 <- renderPrint(adsl_reactive_input()) |
|
381 |
#' } |
|
382 |
#' ) |
|
383 |
#' if (interactive()) { |
|
384 |
#' runApp(app) |
|
385 |
#' } |
|
386 |
data_extract_srv <- function(id, datasets, data_extract_spec, ...) { |
|
387 | 35x |
checkmate::assert_multi_class(datasets, c("FilteredData", "list")) |
388 | 33x |
checkmate::assert( |
389 | 33x |
checkmate::check_class(data_extract_spec, "data_extract_spec"), |
390 | 33x |
checkmate::check_list(data_extract_spec, "data_extract_spec") |
391 |
) |
|
392 | 30x |
UseMethod("data_extract_srv", datasets) |
393 |
} |
|
394 | ||
395 |
#' @rdname data_extract_srv |
|
396 |
#' @export |
|
397 |
data_extract_srv.FilteredData <- function(id, datasets, data_extract_spec, ...) { |
|
398 | 4x |
checkmate::assert_class(datasets, "FilteredData") |
399 | 4x |
moduleServer( |
400 | 4x |
id, |
401 | 4x |
function(input, output, session) { |
402 | 4x |
logger::log_trace( |
403 | 4x |
"data_extract_srv.FilteredData initialized with datasets: { paste(datasets$datanames(), collapse = ', ') }." |
404 |
) |
|
405 | ||
406 | 4x |
data_list <- sapply(X = datasets$datanames(), simplify = FALSE, FUN = function(x) { |
407 | 5x |
reactive(datasets$get_data(dataname = x, filtered = TRUE)) |
408 |
}) |
|
409 | ||
410 | 4x |
join_keys <- datasets$get_join_keys() |
411 | ||
412 | 4x |
filter_and_select_reactive <- data_extract_srv( |
413 | 4x |
id = NULL, |
414 | 4x |
datasets = data_list, |
415 | 4x |
data_extract_spec = data_extract_spec, |
416 | 4x |
join_keys = join_keys |
417 |
) |
|
418 | 4x |
filter_and_select_reactive |
419 |
} |
|
420 |
) |
|
421 |
} |
|
422 | ||
423 |
#' @rdname data_extract_srv |
|
424 |
#' @param join_keys (`JoinKeys` or `NULL`) of keys per dataset in `datasets` |
|
425 |
#' @param select_validation_rule (`NULL` or `function`) |
|
426 |
#' Should there be any `shinyvalidate` input validation of the select parts of the `data_extract_ui`. |
|
427 |
#' You can use a validation function directly (i.e. `select_validation_rule = shinyvalidate::sv_required()`) |
|
428 |
#' or for more fine-grained control use a function: |
|
429 |
#' `select_validation_rule = ~ if (length(.) > 2) "Error"`. |
|
430 |
#' If `NULL` then no validation will be added. See example for more details. |
|
431 |
#' @param filter_validation_rule (`NULL` or `function`) Same as |
|
432 |
#' `select_validation_rule` but for the filter (values) part of the `data_extract_ui`. |
|
433 |
#' @param dataset_validation_rule (`NULL` or `function`) Same as |
|
434 |
#' `select_validation_rule` but for the choose dataset part of the `data_extract_ui` |
|
435 |
#' @export |
|
436 |
data_extract_srv.list <- function(id, datasets, data_extract_spec, join_keys = NULL, |
|
437 |
select_validation_rule = NULL, |
|
438 |
filter_validation_rule = NULL, |
|
439 |
dataset_validation_rule = if (is.null(select_validation_rule) && is.null(filter_validation_rule)) { # nolint |
|
440 | 11x |
NULL |
441 |
} else { |
|
442 | 4x |
shinyvalidate::sv_required("Please select a dataset") |
443 |
}, |
|
444 |
...) { |
|
445 | 26x |
checkmate::assert_list(datasets, types = c("reactive", "data.frame"), names = "named") |
446 | 26x |
checkmate::assert_class(join_keys, "JoinKeys", null.ok = TRUE) |
447 | 25x |
checkmate::assert_multi_class(select_validation_rule, classes = c("function", "formula"), null.ok = TRUE) |
448 | 22x |
checkmate::assert_multi_class(filter_validation_rule, classes = c("function", "formula"), null.ok = TRUE) |
449 | 21x |
checkmate::assert_multi_class(dataset_validation_rule, classes = c("function", "formula"), null.ok = TRUE) |
450 | ||
451 | 20x |
moduleServer( |
452 | 20x |
id, |
453 | 20x |
function(input, output, session) { |
454 | 20x |
logger::log_trace( |
455 | 20x |
"data_extract_srv.list initialized with datasets: { paste(names(datasets), collapse = ', ') }." |
456 |
) |
|
457 | ||
458 |
# get keys out of join_keys |
|
459 | 20x |
if (!is.null(join_keys)) { |
460 | 16x |
keys <- sapply(names(datasets), simplify = FALSE, function(x) join_keys$get(x, x)) |
461 |
} else { |
|
462 | 4x |
keys <- sapply(names(datasets), simplify = FALSE, function(x) character(0)) |
463 |
} |
|
464 | ||
465 |
# convert to list of reactives |
|
466 | 20x |
datasets <- sapply(X = datasets, simplify = FALSE, FUN = function(x) { |
467 | 5x |
if (is.reactive(x)) x else reactive(x) |
468 |
}) |
|
469 | ||
470 | 20x |
if (inherits(data_extract_spec, "data_extract_spec")) { |
471 | 18x |
data_extract_spec <- list(data_extract_spec) |
472 |
} |
|
473 | ||
474 | 20x |
for (idx in seq_along(data_extract_spec)) { |
475 | 22x |
if (inherits(data_extract_spec[[idx]]$filter, "filter_spec")) { |
476 | ! |
data_extract_spec[[idx]]$filter <- list(data_extract_spec[[idx]]$filter) |
477 |
} |
|
478 |
} |
|
479 | ||
480 | 20x |
if (is.null(data_extract_spec)) { |
481 | ! |
return(reactive(NULL)) |
482 |
} |
|
483 | 20x |
check_data_extract_spec(data_extract_spec = data_extract_spec) |
484 | ||
485 |
# Each dataset needs its own shinyvalidate to make sure only the |
|
486 |
# currently visible d-e-s's validation is used |
|
487 | 20x |
iv <- lapply(data_extract_spec, function(x) { |
488 | 22x |
iv_dataset <- shinyvalidate::InputValidator$new() |
489 | 22x |
if (!is.null(dataset_validation_rule) && length(data_extract_spec) > 1) { |
490 | 2x |
iv_dataset$add_rule("dataset", dataset_validation_rule) |
491 |
} |
|
492 | 22x |
iv_dataset |
493 |
}) |
|
494 | 20x |
names(iv) <- lapply(data_extract_spec, `[[`, "dataname") |
495 | ||
496 |
# also need a final iv for the case where no dataset is selected |
|
497 | 20x |
iv[["blank_dataset_case"]] <- shinyvalidate::InputValidator$new() |
498 | 20x |
if (!is.null(dataset_validation_rule) && length(data_extract_spec) > 1) { |
499 | 1x |
iv[["blank_dataset_case"]]$add_rule("dataset", dataset_validation_rule) |
500 |
} |
|
501 | ||
502 | 20x |
filter_and_select <- lapply(data_extract_spec, function(x) { |
503 | 22x |
data_extract_single_srv( |
504 | 22x |
id = id_for_dataset(x$dataname), |
505 | 22x |
datasets = datasets, |
506 | 22x |
single_data_extract_spec = x |
507 |
) |
|
508 | ||
509 | 22x |
data_extract_read_srv( |
510 | 22x |
id = id_for_dataset(x$dataname), |
511 | 22x |
datasets = datasets, |
512 | 22x |
single_data_extract_spec = x, |
513 | 22x |
iv = iv[[x$dataname]], |
514 | 22x |
select_validation_rule = select_validation_rule, |
515 | 22x |
filter_validation_rule = filter_validation_rule |
516 |
) |
|
517 |
}) |
|
518 | 20x |
names(filter_and_select) <- sapply(data_extract_spec, function(x) x$dataname) |
519 | ||
520 | 20x |
dataname <- reactive({ |
521 |
# For fixed data sets, ignore input_value |
|
522 | 16x |
if (is.null(input$dataset) && length(data_extract_spec) < 2) { |
523 | 12x |
data_extract_spec[[1]]$dataname |
524 |
# For data set selectors, return NULL if NULL |
|
525 |
} else { |
|
526 | 4x |
input$dataset |
527 |
} |
|
528 |
}) |
|
529 | ||
530 | 20x |
filter_and_select_reactive <- reactive({ |
531 | 30x |
if (is.null(dataname())) { |
532 | 1x |
list(iv = iv[["blank_dataset_case"]]) |
533 |
} else { |
|
534 | 29x |
append( |
535 | 29x |
filter_and_select[[dataname()]](), |
536 | 29x |
list( |
537 | 29x |
dataname = dataname(), |
538 | 29x |
internal_id = gsub("^.*-(.+)$", "\\1", session$ns(NULL)), # parent module id |
539 | 29x |
keys = keys[[dataname()]] |
540 |
) |
|
541 |
) |
|
542 |
} |
|
543 |
}) |
|
544 | 20x |
filter_and_select_reactive |
545 |
} |
|
546 |
) |
|
547 |
} |
|
548 | ||
549 |
#' Creates a named list of `data_extract_srv` output |
|
550 |
#' |
|
551 |
#' @description `r lifecycle::badge("experimental")` |
|
552 |
#' `data_extract_multiple_srv` loops over the list of `data_extract` given and |
|
553 |
#' runs `data_extract_srv` for each one returning a list of reactive objects. |
|
554 |
#' This was suitable as input for (deprecated) [data_merge_srv()]. |
|
555 |
#' |
|
556 |
#' @inheritParams data_extract_srv |
|
557 |
#' @param data_extract (named `list` of `data_extract_spec` objects) the list `data_extract_spec` objects. |
|
558 |
#' The names of the elements in the list need to correspond to the `ids` passed to `data_extract_ui`. |
|
559 |
#' See example for details. |
|
560 |
#' |
|
561 |
#' @return reactive named list containing outputs from [data_extract_srv()]. Output list |
|
562 |
#' names are the same as `data_extract` input argument. |
|
563 |
#' |
|
564 |
#' @export |
|
565 |
#' |
|
566 |
#' @examples |
|
567 |
#' library(shiny) |
|
568 |
#' library(shinyvalidate) |
|
569 |
#' library(shinyjs) |
|
570 |
#' library(teal.widgets) |
|
571 |
#' |
|
572 |
#' iris_select <- data_extract_spec( |
|
573 |
#' dataname = "iris", |
|
574 |
#' select = select_spec( |
|
575 |
#' label = "Select variable:", |
|
576 |
#' choices = variable_choices(iris, colnames(iris)), |
|
577 |
#' selected = "Sepal.Length", |
|
578 |
#' multiple = TRUE, |
|
579 |
#' fixed = FALSE |
|
580 |
#' ) |
|
581 |
#' ) |
|
582 |
#' |
|
583 |
#' iris_filter <- data_extract_spec( |
|
584 |
#' dataname = "iris", |
|
585 |
#' filter = filter_spec( |
|
586 |
#' vars = "Species", |
|
587 |
#' choices = c("setosa", "versicolor", "virginica"), |
|
588 |
#' selected = "setosa", |
|
589 |
#' multiple = TRUE |
|
590 |
#' ) |
|
591 |
#' ) |
|
592 |
#' |
|
593 |
#' data_list <- list(iris = reactive(iris)) |
|
594 |
#' |
|
595 |
#' app <- shinyApp( |
|
596 |
#' ui = fluidPage( |
|
597 |
#' useShinyjs(), |
|
598 |
#' standard_layout( |
|
599 |
#' output = verbatimTextOutput("out1"), |
|
600 |
#' encoding = tagList( |
|
601 |
#' data_extract_ui( |
|
602 |
#' id = "x_var", |
|
603 |
#' label = "Please select an X column", |
|
604 |
#' data_extract_spec = iris_select |
|
605 |
#' ), |
|
606 |
#' data_extract_ui( |
|
607 |
#' id = "species_var", |
|
608 |
#' label = "Please select 2 Species", |
|
609 |
#' data_extract_spec = iris_filter |
|
610 |
#' ) |
|
611 |
#' ) |
|
612 |
#' ) |
|
613 |
#' ), |
|
614 |
#' server = function(input, output, session) { |
|
615 |
#' exactly_2_validation <- function(msg) { |
|
616 |
#' ~ if (length(.) != 2) msg |
|
617 |
#' } |
|
618 |
#' |
|
619 |
#' |
|
620 |
#' selector_list <- data_extract_multiple_srv( |
|
621 |
#' list(x_var = iris_select, species_var = iris_filter), |
|
622 |
#' datasets = data_list, |
|
623 |
#' select_validation_rule = list( |
|
624 |
#' x_var = sv_required("Please select an X column") |
|
625 |
#' ), |
|
626 |
#' filter_validation_rule = list( |
|
627 |
#' species_var = compose_rules( |
|
628 |
#' sv_required("Exactly 2 Species must be chosen"), |
|
629 |
#' exactly_2_validation("Exactly 2 Species must be chosen") |
|
630 |
#' ) |
|
631 |
#' ) |
|
632 |
#' ) |
|
633 |
#' iv_r <- reactive({ |
|
634 |
#' iv <- InputValidator$new() |
|
635 |
#' compose_and_enable_validators( |
|
636 |
#' iv, |
|
637 |
#' selector_list, |
|
638 |
#' validator_names = NULL |
|
639 |
#' ) |
|
640 |
#' }) |
|
641 |
#' |
|
642 |
#' output$out1 <- renderPrint({ |
|
643 |
#' if (iv_r()$is_valid()) { |
|
644 |
#' ans <- lapply(selector_list(), function(x) { |
|
645 |
#' cat(format_data_extract(x()), "\n\n") |
|
646 |
#' }) |
|
647 |
#' } else { |
|
648 |
#' "Please fix errors in your selection" |
|
649 |
#' } |
|
650 |
#' }) |
|
651 |
#' } |
|
652 |
#' ) |
|
653 |
#' if (interactive()) { |
|
654 |
#' runApp(app) |
|
655 |
#' } |
|
656 |
data_extract_multiple_srv <- function(data_extract, datasets, ...) { |
|
657 | 18x |
checkmate::assert_list(data_extract, names = "named") |
658 | 17x |
lapply(data_extract, function(x) { |
659 | 20x |
if (is.list(x) && !inherits(x, "data_extract_spec")) { |
660 | ! |
checkmate::assert_list(x, "data_extract_spec") |
661 |
} |
|
662 |
}) |
|
663 | 17x |
checkmate::assert_multi_class(datasets, classes = c("FilteredData", "list")) |
664 | 16x |
UseMethod("data_extract_multiple_srv", datasets) |
665 |
} |
|
666 | ||
667 |
#' @rdname data_extract_multiple_srv |
|
668 |
#' @export |
|
669 |
data_extract_multiple_srv.FilteredData <- function(data_extract, datasets, ...) { |
|
670 | 1x |
checkmate::assert_class(datasets, classes = "FilteredData") |
671 | 1x |
logger::log_trace( |
672 | 1x |
"data_extract_multiple_srv.filteredData initialized with dataset: { paste(datasets$datanames(), collapse = ', ') }." |
673 |
) |
|
674 | ||
675 | 1x |
data_list <- sapply(X = datasets$datanames(), simplify = FALSE, FUN = function(x) { |
676 | 1x |
reactive(datasets$get_data(dataname = x, filtered = TRUE)) |
677 |
}) |
|
678 | ||
679 | 1x |
join_keys <- datasets$get_join_keys() |
680 | 1x |
data_extract_multiple_srv(data_extract = data_extract, datasets = data_list, join_keys = join_keys) |
681 |
} |
|
682 | ||
683 |
#' @rdname data_extract_multiple_srv |
|
684 |
#' @param join_keys (`JoinKeys` or `NULL`) of join keys per dataset in `datasets`. |
|
685 |
#' @param select_validation_rule (`NULL`, `function` or `named list` of `function`) |
|
686 |
#' Should there be any `shinyvalidate` input validation of the select parts of the `data_extract_ui` |
|
687 |
#' If all `data_extract` require the same validation function then this can be used directly ( |
|
688 |
#' i.e. `select_validation_rule = shinyvalidate::sv_required()`). For more fine-grained control use a list: |
|
689 |
#' `select_validation_rule = list(extract_1 = sv_required(), extract2 = ~ if (length(.) > 2) "Error")`. |
|
690 |
#' If `NULL` then no validation will be added. See example for more details. |
|
691 |
#' @param filter_validation_rule (`NULL`, `function` or `named list` of `function`) Same as |
|
692 |
#' `select_validation_rule` but for the filter (values) part of the `data_extract_ui`. |
|
693 |
#' @param dataset_validation_rule (`NULL`, `function` or `named list` of `function`) Same as |
|
694 |
#' `select_validation_rule` but for the choose dataset part of the `data_extract_ui` |
|
695 |
#' @export |
|
696 |
data_extract_multiple_srv.list <- function(data_extract, datasets, join_keys = NULL, |
|
697 |
select_validation_rule = NULL, |
|
698 |
filter_validation_rule = NULL, |
|
699 |
dataset_validation_rule = if (is.null(select_validation_rule) && is.null(filter_validation_rule)) { # nolint |
|
700 | 13x |
NULL |
701 |
} else { |
|
702 | 1x |
shinyvalidate::sv_required("Please select a dataset") |
703 |
}, ...) { |
|
704 | 15x |
checkmate::assert_list(datasets, types = c("reactive", "data.frame"), names = "named") |
705 | 15x |
checkmate::assert_class(join_keys, "JoinKeys", null.ok = TRUE) |
706 | 14x |
checkmate::assert( |
707 | 14x |
checkmate::check_multi_class(select_validation_rule, class = c("function", "formula"), null.ok = TRUE), |
708 | 14x |
checkmate::check_list(select_validation_rule, types = c("function", "formula", "NULL"), null.ok = TRUE) |
709 |
) |
|
710 | 14x |
checkmate::assert( |
711 | 14x |
checkmate::check_multi_class(filter_validation_rule, class = c("function", "formula"), null.ok = TRUE), |
712 | 14x |
checkmate::check_list(filter_validation_rule, types = c("function", "formula", "NULL"), null.ok = TRUE) |
713 |
) |
|
714 | 14x |
checkmate::assert( |
715 | 14x |
checkmate::check_multi_class(dataset_validation_rule, class = c("function", "formula"), null.ok = TRUE), |
716 | 14x |
checkmate::check_list(dataset_validation_rule, types = c("function", "formula", "NULL"), null.ok = TRUE) |
717 |
) |
|
718 | ||
719 | 14x |
logger::log_trace( |
720 | 14x |
"data_extract_multiple_srv.list initialized with dataset: { paste(names(datasets), collapse = ', ') }." |
721 |
) |
|
722 | ||
723 | 14x |
data_extract <- Filter(Negate(is.null), data_extract) |
724 | ||
725 | 14x |
if (is.function(select_validation_rule)) { |
726 | ! |
select_validation_rule <- sapply( |
727 | ! |
names(data_extract), |
728 | ! |
simplify = FALSE, |
729 | ! |
USE.NAMES = TRUE, |
730 | ! |
function(x) select_validation_rule |
731 |
) |
|
732 |
} |
|
733 | ||
734 | 14x |
if (is.function(dataset_validation_rule)) { |
735 | 1x |
dataset_validation_rule <- sapply( |
736 | 1x |
names(data_extract), |
737 | 1x |
simplify = FALSE, |
738 | 1x |
USE.NAMES = TRUE, |
739 | 1x |
function(x) dataset_validation_rule |
740 |
) |
|
741 |
} |
|
742 | ||
743 | 14x |
reactive({ |
744 | 4x |
sapply( |
745 | 4x |
X = names(data_extract), |
746 | 4x |
simplify = FALSE, |
747 | 4x |
USE.NAMES = TRUE, |
748 | 4x |
function(x) { |
749 | 5x |
data_extract_srv( |
750 | 5x |
id = x, |
751 | 5x |
data_extract_spec = data_extract[[x]], |
752 | 5x |
datasets = datasets, |
753 | 5x |
join_keys = join_keys, |
754 | 5x |
select_validation_rule = select_validation_rule[[x]], |
755 | 5x |
filter_validation_rule = filter_validation_rule[[x]], |
756 | 5x |
dataset_validation_rule = dataset_validation_rule[[x]] |
757 |
) |
|
758 |
} |
|
759 |
) |
|
760 |
}) |
|
761 |
} |
1 |
#' Resolve delayed inputs by evaluating the code within the provided datasets |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' @param x Object of class `delayed_data` to resolve. |
|
6 |
#' @param datasets A named list of type `data.frame` to use for evaluation. |
|
7 |
#' @param keys A named list of type `character` to be used as the keys for each dataset. The names of this |
|
8 |
#' list must be exactly the same as for datasets. |
|
9 |
#' |
|
10 |
#' @return Resolved object. |
|
11 |
#' |
|
12 |
#' @examples |
|
13 |
#' ADSL <- teal.transform::rADSL |
|
14 |
#' attr(ADSL, "keys") <- teal.data::get_cdisc_keys("ADSL") |
|
15 |
#' data_list <- list(ADSL = shiny::reactive(ADSL)) |
|
16 |
#' keys <- list(ADSL = attr(ADSL, "keys")) |
|
17 |
#' shiny::isolate({ |
|
18 |
#' # value_choices example |
|
19 |
#' v1 <- value_choices("ADSL", "SEX", "SEX") |
|
20 |
#' v1 |
|
21 |
#' teal.transform:::resolve(v1, data_list, keys) |
|
22 |
#' |
|
23 |
#' # variable_choices example |
|
24 |
#' v2 <- variable_choices("ADSL", c("BMRKR1", "BMRKR2")) |
|
25 |
#' v2 |
|
26 |
#' teal.transform:::resolve(v2, data_list, keys) |
|
27 |
#' |
|
28 |
#' # data_extract_spec example |
|
29 |
#' adsl_filter <- filter_spec( |
|
30 |
#' vars = variable_choices("ADSL", "SEX"), |
|
31 |
#' sep = "-", |
|
32 |
#' choices = value_choices("ADSL", "SEX", "SEX"), |
|
33 |
#' selected = "F", |
|
34 |
#' multiple = FALSE, |
|
35 |
#' label = "Choose endpoint and Censor" |
|
36 |
#' ) |
|
37 |
#' |
|
38 |
#' adsl_select <- select_spec( |
|
39 |
#' label = "Select variable:", |
|
40 |
#' choices = variable_choices("ADSL", c("BMRKR1", "BMRKR2")), |
|
41 |
#' selected = "BMRKR1", |
|
42 |
#' multiple = FALSE, |
|
43 |
#' fixed = FALSE |
|
44 |
#' ) |
|
45 |
#' |
|
46 |
#' adsl_de <- data_extract_spec( |
|
47 |
#' dataname = "ADSL", |
|
48 |
#' select = adsl_select, |
|
49 |
#' filter = adsl_filter |
|
50 |
#' ) |
|
51 |
#' |
|
52 |
#' teal.transform:::resolve(adsl_filter, data_list, keys) |
|
53 |
#' teal.transform:::resolve(adsl_select, data_list, keys) |
|
54 |
#' teal.transform:::resolve(adsl_de, data_list, keys) |
|
55 |
#' |
|
56 |
#' # nested list (arm_ref_comp) |
|
57 |
#' arm_ref_comp <- list( |
|
58 |
#' ARMCD = list( |
|
59 |
#' ref = variable_choices("ADSL"), |
|
60 |
#' comp = variable_choices("ADSL") |
|
61 |
#' ) |
|
62 |
#' ) |
|
63 |
#' |
|
64 |
#' teal.transform:::resolve(arm_ref_comp, data_list, keys) |
|
65 |
#' }) |
|
66 |
resolve <- function(x, datasets, keys = NULL) { |
|
67 | 313x |
checkmate::assert_list(datasets, type = "reactive", min.len = 1, names = "named") |
68 | 310x |
checkmate::assert_list(keys, "character", names = "named", null.ok = TRUE) |
69 | 308x |
checkmate::assert( |
70 | 308x |
.var.name = "keys", |
71 | 308x |
checkmate::check_names(names(keys), subset.of = names(datasets)), |
72 | 308x |
checkmate::check_null(keys) |
73 |
) |
|
74 | ||
75 | 307x |
UseMethod("resolve") |
76 |
} |
|
77 | ||
78 |
#' @export |
|
79 |
resolve.delayed_variable_choices <- function(x, datasets, keys) { # nolint |
|
80 | 112x |
if (is.null(x$key)) { |
81 | 110x |
x$key <- `if`(is.null(keys), character(), keys[[x$data]]) |
82 |
} |
|
83 | 112x |
x$data <- datasets[[x$data]]() |
84 | 112x |
if (inherits(x$subset, "function")) { |
85 | 38x |
x$subset <- resolve_delayed_expr(x$subset, ds = x$data, is_value_choices = FALSE) |
86 |
} |
|
87 | 112x |
return(do.call("variable_choices", x)) |
88 |
} |
|
89 | ||
90 |
#' @export |
|
91 |
resolve.delayed_value_choices <- function(x, datasets, keys) { # nolint |
|
92 | 56x |
x$data <- datasets[[x$data]]() |
93 | 56x |
if (is.function(x$subset)) { |
94 | 28x |
x$subset <- resolve_delayed_expr(x$subset, ds = x$data, is_value_choices = TRUE) |
95 |
} |
|
96 | 56x |
return(do.call("value_choices", x)) |
97 |
} |
|
98 | ||
99 |
#' @export |
|
100 |
resolve.delayed_choices_selected <- function(x, datasets, keys) { # nolint |
|
101 | 6x |
if (inherits(x$selected, "delayed_data")) { |
102 | 6x |
x$selected <- resolve(x$selected, datasets = datasets, keys) |
103 |
} |
|
104 | 6x |
x$choices <- resolve(x$choices, datasets = datasets, keys) |
105 | ||
106 | 6x |
if (!all(x$selected %in% x$choices)) { |
107 | 2x |
logger::log_warn(paste( |
108 | 2x |
"Removing", |
109 | 2x |
paste(x$selected[which(!x$selected %in% x$choices)]), |
110 | 2x |
"from 'selected' as not in 'choices' when resolving delayed choices_selected" |
111 |
)) |
|
112 | 2x |
x$selected <- x$selected[which(x$selected %in% x$choices)] |
113 |
} |
|
114 | ||
115 | 6x |
return(do.call("choices_selected", x)) |
116 |
} |
|
117 | ||
118 |
#' @export |
|
119 |
resolve.delayed_select_spec <- function(x, datasets, keys) { # nolint |
|
120 | 36x |
x$choices <- resolve(x$choices, datasets = datasets, keys) |
121 | 36x |
if (inherits(x$selected, "delayed_data")) { |
122 | 8x |
x$selected <- resolve(x$selected, datasets = datasets, keys) |
123 |
} |
|
124 | 36x |
return(do.call("select_spec", x)) |
125 |
} |
|
126 | ||
127 |
#' @export |
|
128 |
resolve.delayed_filter_spec <- function(x, datasets, keys) { # nolint |
|
129 | 38x |
if (inherits(x$vars_choices, "delayed_data")) { |
130 | 22x |
x$vars_choices <- resolve(x$vars_choices, datasets = datasets, keys) |
131 |
} |
|
132 | 38x |
if (inherits(x$vars_selected, "delayed_data")) { |
133 | 18x |
x$vars_selected <- resolve(x$vars_selected, datasets = datasets, keys) |
134 |
} |
|
135 | 38x |
if (inherits(x$choices, "delayed_data")) { |
136 | 34x |
x$choices <- resolve(x$choices, datasets = datasets, keys) |
137 |
} |
|
138 | 38x |
if (inherits(x$selected, "delayed_data")) { |
139 | 8x |
x$selected <- resolve(x$selected, datasets = datasets, keys) |
140 |
} |
|
141 | ||
142 | 38x |
return(do.call("filter_spec_internal", x[intersect(names(x), methods::formalArgs(filter_spec_internal))])) |
143 |
} |
|
144 | ||
145 |
#' @export |
|
146 |
resolve.delayed_data_extract_spec <- function(x, datasets, keys) { # nolint |
|
147 | 34x |
x$select <- `if`( |
148 | 34x |
inherits(x$select, "delayed_data"), |
149 | 34x |
resolve(x$select, datasets = datasets, keys), |
150 | 34x |
x$select |
151 |
) |
|
152 | ||
153 | 34x |
if (any(vapply(x$filter, inherits, logical(1L), "delayed_data"))) { |
154 | 24x |
idx <- vapply(x$filter, inherits, logical(1), "delayed_data") |
155 | 24x |
x$filter[idx] <- lapply(x$filter[idx], resolve, datasets = datasets, keys = keys) |
156 |
} |
|
157 | ||
158 | 34x |
return(do.call("data_extract_spec", x)) |
159 |
} |
|
160 | ||
161 |
#' @export |
|
162 |
resolve.list <- function(x, datasets, keys) { # nolint |
|
163 |
# If specified explicitly, return it unchanged. Otherwise if delayed, resolve. |
|
164 | 17x |
res <- lapply(x, resolve, datasets = datasets, keys = keys) |
165 | 16x |
return(res) |
166 |
} |
|
167 | ||
168 |
#' @export |
|
169 |
resolve.default <- function(x, datasets, keys) { |
|
170 | 8x |
return(x) |
171 |
} |
|
172 | ||
173 |
#' Resolve expression after delayed data are loaded |
|
174 |
#' |
|
175 |
#' |
|
176 |
#' @param x (`function`) Function that is applied on dataset. |
|
177 |
#' It must take only a single argument "data" and return character vector with columns / values. |
|
178 |
#' @param ds (`data.frame`) `TealDataset` on which the function is applied to. |
|
179 |
#' @param is_value_choices (`logical`) Determines which check of the returned value will be applied. |
|
180 |
#' |
|
181 |
#' @return Character vector - result of calling function `x` on dataset `ds`. |
|
182 |
#' @keywords internal |
|
183 |
#' |
|
184 |
#' @examples |
|
185 |
#' \dontrun{ |
|
186 |
#' # get only possible factor variables from mtcars dataset |
|
187 |
#' resolve_delayed_expr( |
|
188 |
#' function(data) { |
|
189 |
#' idx <- vapply(data, function(x) is.numeric(x) && length(unique(x)) <= 6, logical(1)) |
|
190 |
#' colnames(data)[idx] |
|
191 |
#' }, |
|
192 |
#' ds = mtcars, |
|
193 |
#' is_value_choices = FALSE |
|
194 |
#' ) |
|
195 |
#' } |
|
196 |
resolve_delayed_expr <- function(x, ds, is_value_choices) { |
|
197 | 124x |
checkmate::assert_function(x, args = "data", nargs = 1) |
198 | ||
199 |
# evaluate function |
|
200 | 118x |
res <- do.call(x, list(data = ds)) |
201 | ||
202 |
# check returned value |
|
203 | 118x |
if (is_value_choices) { |
204 | 52x |
if (!is.atomic(res) || anyDuplicated(res)) { |
205 | 2x |
stop(paste( |
206 | 2x |
"The following function must return a vector with unique values", |
207 | 2x |
"from the respective columns of the dataset.\n\n", |
208 | 2x |
deparse1(bquote(.(x)), collapse = "\n") |
209 |
)) |
|
210 |
} |
|
211 |
} else { |
|
212 | 66x |
if (!checkmate::test_character(res, any.missing = FALSE) || length(res) > ncol(ds) || anyDuplicated(res)) { |
213 | 6x |
stop(paste( |
214 | 6x |
"The following function must return a character vector with unique", |
215 | 6x |
"names from the available columns of the dataset:\n\n", |
216 | 6x |
deparse1(bquote(.(x)), collapse = "\n") |
217 |
)) |
|
218 |
} |
|
219 |
} |
|
220 | ||
221 | 110x |
return(res) |
222 |
} |
|
223 | ||
224 |
#' @keywords internal |
|
225 |
#' @export |
|
226 |
print.delayed_variable_choices <- function(x, indent = 0L, ...) { |
|
227 | ! |
cat(indent_msg(indent, paste("variable_choices with delayed data:", x$data))) |
228 | ! |
cat("\n") |
229 | ! |
print_delayed_list(x, indent) |
230 | ! |
return(invisible(NULL)) |
231 |
} |
|
232 | ||
233 |
#' @keywords internal |
|
234 |
#' @export |
|
235 |
print.delayed_value_choices <- function(x, indent = 0L, ...) { |
|
236 | ! |
cat(indent_msg(indent, paste("value_choices with delayed data: ", x$data))) |
237 | ! |
cat("\n") |
238 | ! |
print_delayed_list(x, indent) |
239 | ! |
return(invisible(NULL)) |
240 |
} |
|
241 | ||
242 |
#' @keywords internal |
|
243 |
#' @export |
|
244 |
print.delayed_choices_selected <- function(x, indent = 0L, ...) { |
|
245 | ! |
cat(indent_msg(indent, paste("choices_selected with delayed data: ", x$choices$data))) |
246 | ! |
cat("\n") |
247 | ! |
print_delayed_list(x, indent) |
248 | ! |
return(invisible(NULL)) |
249 |
} |
|
250 | ||
251 |
#' @keywords internal |
|
252 |
#' @export |
|
253 |
print.delayed_select_spec <- function(x, indent = 0L, ...) { |
|
254 | ! |
cat(indent_msg(indent, paste("select_spec with delayed data:", x$choices$data))) |
255 | ! |
cat("\n") |
256 | ! |
print_delayed_list(x, indent) |
257 | ! |
return(invisible(NULL)) |
258 |
} |
|
259 | ||
260 |
#' @keywords internal |
|
261 |
#' @export |
|
262 |
print.filter_spec <- function(x, indent = 0L, ...) { |
|
263 | ! |
cat(indent_msg(indent, "filter_spec with delayed data:")) |
264 | ! |
cat("\n") |
265 | ! |
print_delayed_list(x, indent) |
266 | ! |
return(invisible(NULL)) |
267 |
} |
|
268 | ||
269 |
#' @keywords internal |
|
270 |
#' @export |
|
271 |
print.delayed_filter_spec <- function(x, indent = 0L, ...) { |
|
272 | ! |
cat(indent_msg(indent, "filter_spec with delayed data:")) |
273 | ! |
cat("\n") |
274 | ! |
print_delayed_list(x, indent) |
275 | ! |
return(invisible(NULL)) |
276 |
} |
|
277 | ||
278 |
#' @keywords internal |
|
279 |
#' @export |
|
280 |
print.delayed_data_extract_spec <- function(x, indent = 0L, ...) { |
|
281 | ! |
cat(paste("data_extract_spec with delayed data:", x$dataname)) |
282 | ! |
cat("\n\n") |
283 | ! |
print_delayed_list(x) |
284 | ! |
return(invisible(NULL)) |
285 |
} |
|
286 | ||
287 |
indent_msg <- function(n, msg) { |
|
288 | ! |
checkmate::assert_integer(n, len = 1, lower = 0, any.missing = FALSE) |
289 | ! |
checkmate::assert_character(msg, min.len = 1, any.missing = FALSE) |
290 | ! |
indent <- paste(rep(" ", n), collapse = "") |
291 | ! |
return(paste0(indent, msg)) |
292 |
} |
|
293 | ||
294 |
print_delayed_list <- function(obj, n = 0L) { |
|
295 | ! |
checkmate::assert_integer(n, len = 1, lower = 0, any.missing = FALSE) |
296 | ! |
stopifnot(is.list(obj)) |
297 | ||
298 | ! |
for (idx in seq_along(obj)) { |
299 | ! |
cat(indent_msg(n, ifelse(is.null(names(obj)[[idx]]), paste0("[[", idx, "]]"), paste("$", names(obj)[[idx]])))) |
300 | ! |
cat("\n") |
301 | ! |
if (inherits(obj[[idx]], "delayed_data")) { |
302 | ! |
print(obj[[idx]], n + 1L) |
303 | ! |
} else if (is.list(obj[[idx]])) { |
304 | ! |
print_delayed_list(obj[[idx]], n + 1L) |
305 |
} else { |
|
306 | ! |
cat(indent_msg(n, paste(utils::capture.output(print(obj[[idx]])), collapse = "\n"))) |
307 | ! |
cat("\n") |
308 |
} |
|
309 |
} |
|
310 | ! |
return(invisible(NULL)) |
311 |
} |
1 |
#' Returns a reactive list with values read from the inputs of `data_extract_spec` |
|
2 |
#' |
|
3 |
#' @details Reads the UI inputs of a single `data_extract_spec` object in |
|
4 |
#' a running `teal` application. Returns a reactive list of reactive values |
|
5 |
#' read from the input. The returned list has keys corresponding to the UI |
|
6 |
#' inputs: `select`, `filters`, `always_selected`, `reshape`. |
|
7 |
#' |
|
8 |
#' @inheritParams data_extract_single_srv |
|
9 |
#' @return `shiny::reactive` the reactive list with reactive values read from the UI |
|
10 |
#' @keywords internal |
|
11 |
#' |
|
12 |
data_extract_read_srv <- function(id, datasets, single_data_extract_spec, iv, select_validation_rule = NULL, |
|
13 |
filter_validation_rule = NULL) { |
|
14 | 22x |
checkmate::assert_class(single_data_extract_spec, "data_extract_spec") |
15 | 22x |
checkmate::assert_list(datasets, types = "reactive", names = "named") |
16 | 22x |
moduleServer( |
17 | 22x |
id, |
18 | 22x |
function(input, output, session) { |
19 | 22x |
logger::log_trace( |
20 | 22x |
"data_extract_read_srv initialized with: { single_data_extract_spec$dataname } dataset." |
21 |
) |
|
22 | 22x |
filter_idx <- seq_along(single_data_extract_spec$filter) |
23 | 22x |
extract_n_process_inputs <- function(idx) { |
24 | 10x |
x <- single_data_extract_spec$filter[[idx]] |
25 | 10x |
input_col <- input[[paste0("filter", idx, ns.sep, "col")]] |
26 | 10x |
input_vals <- input[[paste0("filter", idx, ns.sep, "vals")]] |
27 |
# convert to numeric for class consistency because everything coming from input is character, e.g. "1" |
|
28 | 10x |
if (length(input_col) == 1L && is.numeric(datasets[[x$dataname]]()[[input_col]])) { |
29 | ! |
input_vals <- as.numeric(input_vals) |
30 |
} |
|
31 | 10x |
for (col in input_col) { |
32 |
# replace NA with NA_character_ for class consistency |
|
33 | ! |
if (any(vapply(input_vals, identical, logical(1), "NA")) && |
34 | ! |
anyNA(datasets[[x$dataname]]()[col]) && |
35 | ! |
!any(vapply(unique(datasets[[x$dataname]]()[col]), identical, logical(1), "NA"))) { |
36 | ! |
input_vals[vapply(input_vals, identical, logical(1), "NA")] <- NA_character_ |
37 |
} |
|
38 |
} |
|
39 | ||
40 | 10x |
selected <- split_by_sep(input_vals, x$sep) |
41 | ||
42 | 10x |
dn <- single_data_extract_spec$dataname |
43 | 10x |
cols <- `if`(length(input_col) > 0, paste(input_col, collapse = ", "), "NULL") |
44 | 10x |
sel <- `if`(length(selected) > 0, paste(selected, collapse = ", "), "NULL") |
45 | 10x |
logger::log_trace("data_extract_read_srv@1 dataname: { dn }; filter vars: { cols }; filter values: { sel }") |
46 | ||
47 | 10x |
list( |
48 | 10x |
columns = input_col, |
49 | 10x |
selected = selected, |
50 | 10x |
multiple = x$multiple, |
51 | 10x |
drop_keys = x$drop_keys |
52 |
) |
|
53 |
} |
|
54 | ||
55 | 22x |
r_filter <- eventReactive( |
56 | 22x |
ignoreNULL = FALSE, |
57 | 22x |
eventExpr = { |
58 | 19x |
lapply( |
59 | 19x |
filter_idx, |
60 | 19x |
function(idx) { |
61 | 10x |
input[[paste0("filter", idx, ns.sep, "vals")]] |
62 |
} |
|
63 |
) |
|
64 |
}, |
|
65 | 22x |
valueExpr = { |
66 | 19x |
res <- if (length(single_data_extract_spec$filter) >= 1) { |
67 | 10x |
lapply(filter_idx, FUN = extract_n_process_inputs) |
68 |
} |
|
69 | 19x |
res |
70 |
} |
|
71 |
) |
|
72 | ||
73 | 22x |
if (!is.null(select_validation_rule)) { |
74 | 5x |
iv$add_rule("select", select_validation_rule) |
75 |
} |
|
76 | ||
77 | 22x |
if (!is.null(filter_validation_rule)) { |
78 | 2x |
for (idx in filter_idx) { |
79 | 2x |
iv$add_rule( |
80 | 2x |
paste0("filter", idx, ns.sep, "vals"), |
81 | 2x |
filter_validation_rule |
82 |
) |
|
83 |
} |
|
84 |
} |
|
85 | ||
86 | 22x |
tracked_input <- Queue$new() |
87 | 22x |
r_select <- eventReactive( |
88 | 22x |
ignoreNULL = FALSE, |
89 | 22x |
eventExpr = { |
90 | 29x |
input$select |
91 |
# Note that r_select reactivity is triggered by filter vals and not filter col. |
|
92 |
# This is intended since filter col updates filter vals which is then updating both r_filter and r_select. |
|
93 |
# If it depends on filter col then there will be two reactivity cycles: |
|
94 |
# (1) filter-col -> r_select -> read -> ... (2) filter-col -> filter-val -> r_filter -> read -> ... |
|
95 | 29x |
lapply( |
96 | 29x |
filter_idx, |
97 | 29x |
function(idx) { |
98 | 12x |
input[[paste0("filter", idx, shiny::ns.sep, "vals")]] |
99 |
} |
|
100 |
) |
|
101 |
}, |
|
102 | 22x |
valueExpr = { |
103 | 29x |
if (isTRUE(single_data_extract_spec$select$ordered)) { |
104 | 3x |
shinyjs::runjs( |
105 | 3x |
sprintf( |
106 | 3x |
'$("#%s").parent().find("span.caret").removeClass("caret").addClass("fas fa-exchange-alt")', |
107 | 3x |
session$ns("select") |
108 |
) |
|
109 |
) |
|
110 | 3x |
tracked_input$remove(setdiff(tracked_input$get(), input$select)) |
111 | 3x |
tracked_input$push(setdiff(input$select, tracked_input$get())) |
112 | 3x |
res <- tracked_input$get() |
113 | 3x |
res <- if (is.null(res)) character(0) else res |
114 |
} else { |
|
115 | 26x |
res <- if (is.null(input$select)) { |
116 | 15x |
if (is.null(single_data_extract_spec$select)) { |
117 | 4x |
as.character(unlist(lapply( |
118 | 4x |
filter_idx, |
119 | 4x |
function(idx) { |
120 | 4x |
input[[paste0("filter", idx, ns.sep, "col")]] |
121 |
} |
|
122 |
))) |
|
123 |
} else { |
|
124 | 11x |
character(0) |
125 |
} |
|
126 |
} else { |
|
127 | 11x |
input$select |
128 |
} |
|
129 | ||
130 | 26x |
if (!is.null(input$select_additional)) { |
131 | ! |
res <- append(res, input$select_additional) |
132 |
} |
|
133 | 26x |
res |
134 |
} |
|
135 | ||
136 | 29x |
dn <- single_data_extract_spec$dataname |
137 | 29x |
sel <- `if`(length(res) > 0, paste(res, collapse = ", "), "NULL") |
138 | 29x |
logger::log_trace("data_extract_read_srv@2 dataname: { dn }; select: { sel }.") |
139 | ||
140 | 29x |
res |
141 |
} |
|
142 |
) |
|
143 | ||
144 | 22x |
r_reshape <- reactive({ |
145 | 15x |
res <- if (is.null(input$reshape)) { |
146 | 15x |
FALSE |
147 |
} else { |
|
148 | ! |
input$reshape |
149 |
} |
|
150 | ||
151 | 15x |
dn <- single_data_extract_spec$dataname |
152 | 15x |
resh <- paste(res, collapse = ", ") |
153 | 15x |
logger::log_trace("data_extract_read_srv@3 dataname: { dn }; reshape: { resh }.") |
154 | ||
155 | 15x |
res |
156 |
}) |
|
157 | ||
158 | 22x |
reactive({ |
159 | 29x |
list( |
160 | 29x |
filters = r_filter(), |
161 | 29x |
select = r_select(), |
162 | 29x |
always_selected = single_data_extract_spec$select$always_selected, |
163 | 29x |
reshape = r_reshape(), |
164 | 29x |
iv = iv |
165 |
) |
|
166 |
}) |
|
167 |
} |
|
168 |
) |
|
169 |
} |
1 |
#' Checks `varname` argument and convert to call |
|
2 |
#' |
|
3 |
#' Checks `varname` type and parse if it's a `character` |
|
4 |
#' @param varname (`name`, `call` or `character(1)`)\cr |
|
5 |
#' name of the variable |
|
6 |
#' @keywords internal |
|
7 |
call_check_parse_varname <- function(varname) { |
|
8 | 188x |
checkmate::assert( |
9 | 188x |
checkmate::check_string(varname), |
10 | 188x |
checkmate::check_class(varname, "call"), |
11 | 188x |
checkmate::check_class(varname, "name") |
12 |
) |
|
13 | 188x |
if (is.character(varname)) { |
14 | ! |
parsed <- parse(text = varname, keep.source = FALSE) |
15 | ! |
if (length(parsed) == 1) { |
16 | ! |
varname <- parsed[[1]] |
17 |
} else { |
|
18 | ! |
stop( |
19 | ! |
sprintf( |
20 | ! |
"Problem with parsing '%s'. Not able to process multiple calls", |
21 | ! |
varname |
22 |
) |
|
23 |
) |
|
24 |
} |
|
25 |
} |
|
26 | 188x |
varname |
27 |
} |
|
28 | ||
29 |
#' Choices condition call |
|
30 |
#' |
|
31 |
#' Compose choices condition call from inputs. |
|
32 |
#' |
|
33 |
#' @param varname (`name`, `call` or `character(1)`)\cr |
|
34 |
#' name of the variable |
|
35 |
#' |
|
36 |
#' @param choices (`vector`)\cr |
|
37 |
#' `varname` values to match using the `==` (single value) or |
|
38 |
#' `%in%` (vector) condition. `choices` can be vector of any type |
|
39 |
#' but for some output might be converted: |
|
40 |
#' \itemize{ |
|
41 |
#' \item{`factor`}{ call is composed on choices converted to `character`} |
|
42 |
#' \item{`Date`}{ call is composed on choices converted to `character` using `format(choices)`} |
|
43 |
#' \item{`POSIXct`, `POSIXlt`}{ Call is composed on choices converted to `character` using |
|
44 |
#' `format(choices)`. One has to be careful here as formatted date-time variable might loose |
|
45 |
#' some precision (see `format` argument in \code{\link{format.POSIXlt}}) and output call |
|
46 |
#' could be insufficient for exact comparison. In this case one should specify |
|
47 |
#' `varname = trunc(<varname>)` and possibly convert `choices` to `character`) |
|
48 |
#' } |
|
49 |
#' } |
|
50 |
#' |
|
51 |
#' @examples |
|
52 |
#' teal.transform:::call_condition_choice("SEX", choices = c(1, 2)) |
|
53 |
#' teal.transform:::call_condition_choice(as.name("SEX"), choices = "F") |
|
54 |
#' teal.transform:::call_condition_choice("SEX", choices = c("F", "M")) |
|
55 |
#' teal.transform:::call_condition_choice("SEX", choices = factor(c("F", "M"))) |
|
56 |
#' teal.transform:::call_condition_choice("x$SEX", choices = Sys.Date()) |
|
57 |
#' teal.transform:::call_condition_choice("trunc(x$SEX)", choices = Sys.time()) |
|
58 |
#' @return a `call` |
|
59 |
#' @keywords internal |
|
60 |
call_condition_choice <- function(varname, choices) { |
|
61 | 188x |
varname <- call_check_parse_varname(varname) |
62 | ||
63 | 188x |
if (is.factor(choices)) { |
64 | ! |
choices <- as.character(choices) |
65 | 188x |
} else if (inherits(choices, "Date")) { |
66 | ! |
choices <- format(choices) |
67 | 188x |
} else if (inherits(choices, c("POSIXct", "POSIXlt"))) { |
68 | ! |
choices <- format(choices) |
69 |
} |
|
70 | ||
71 | ||
72 | 188x |
if (length(choices) == 1) { |
73 | 134x |
call("==", varname, choices) |
74 |
} else { |
|
75 | 54x |
c_call <- do.call( |
76 | 54x |
"call", |
77 | 54x |
append(list("c"), choices) |
78 |
) |
|
79 |
# c_call needed because it needs to be vector call |
|
80 |
# instead of vector. SummarizedExperiment.subset |
|
81 |
# handles only vector calls |
|
82 | 54x |
call("%in%", varname, c_call) |
83 |
} |
|
84 |
} |
|
85 | ||
86 |
#' `numeric` range condition call |
|
87 |
#' |
|
88 |
#' Compose `numeric` range condition call from inputs |
|
89 |
#' |
|
90 |
#' @param varname (`name` or `character(1)`)\cr |
|
91 |
#' name of the variable |
|
92 |
#' |
|
93 |
#' @param range (`numeric(2)`)\cr |
|
94 |
#' range of the variable |
|
95 |
#' |
|
96 |
#' @return call |
|
97 |
#' @examples |
|
98 |
#' teal.transform:::call_condition_range("AGE", range = c(1, 2)) |
|
99 |
#' teal.transform:::call_condition_range(as.name("AGE"), range = c(-1.2, 2.1)) |
|
100 |
#' teal.transform:::call_condition_range( |
|
101 |
#' teal.transform:::call_extract_list("ADSL", "AGE"), |
|
102 |
#' range = c(-1.2, 2.1) |
|
103 |
#' ) |
|
104 |
#' @return a `call` |
|
105 |
#' @keywords internal |
|
106 |
call_condition_range <- function(varname, range) { |
|
107 | ! |
checkmate::assert_numeric(range, len = 2, sorted = TRUE) |
108 | ||
109 | ! |
varname <- call_check_parse_varname(varname) |
110 | ! |
call( |
111 |
"&", |
|
112 | ! |
call(">=", varname, range[1]), |
113 | ! |
call("<=", varname, range[2]) |
114 |
) |
|
115 |
} |
|
116 | ||
117 |
#' `logical` variable condition call |
|
118 |
#' |
|
119 |
#' Compose `logical` variable condition call from inputs |
|
120 |
#' |
|
121 |
#' @param varname (`name` or `character(1)`)\cr |
|
122 |
#' name of the variable |
|
123 |
#' |
|
124 |
#' @param choice (`logical(1)`)\cr |
|
125 |
#' chosen value |
|
126 |
#' |
|
127 |
#' @return call |
|
128 |
#' @examples |
|
129 |
#' teal.transform:::call_condition_logical("event", choice = TRUE) |
|
130 |
#' teal.transform:::call_condition_logical("event", choice = FALSE) |
|
131 |
#' @return a `call` |
|
132 |
#' @keywords internal |
|
133 |
call_condition_logical <- function(varname, choice) { |
|
134 | ! |
checkmate::assert_flag(choice) |
135 | ! |
varname <- call_check_parse_varname(varname) |
136 | ||
137 | ! |
if (choice) { |
138 | ! |
varname |
139 | ! |
} else if (!choice) { |
140 | ! |
call("!", varname) |
141 |
} else { |
|
142 | ! |
stop( |
143 | ! |
"Unknown filter state", toString(choice), |
144 | ! |
" for logical var ", as.character(varname) |
145 |
) |
|
146 |
} |
|
147 |
} |
|
148 | ||
149 | ||
150 |
#' `POSIXct` range condition call |
|
151 |
#' |
|
152 |
#' Compose `POSIXct` range condition call from inputs. |
|
153 |
#' |
|
154 |
#' @param varname (`name` or `character(1)`)\cr |
|
155 |
#' name of the variable |
|
156 |
#' |
|
157 |
#' @param range (`POSIXct`)\cr |
|
158 |
#' range of the variable. Be aware that output |
|
159 |
#' uses truncated range format `"%Y-%m-%d %H:%M:%S"`, which means that |
|
160 |
#' some precision might be lost. |
|
161 |
#' |
|
162 |
#' @param timezone (`character(1)`)\cr |
|
163 |
#' specifies the time zone to be used for the conversion. |
|
164 |
#' By default `Sys.timezone()` is used. |
|
165 |
#' |
|
166 |
#' @examples |
|
167 |
#' teal.transform:::call_condition_range_posixct( |
|
168 |
#' varname = as.name("datetime"), |
|
169 |
#' range = c(Sys.time(), Sys.time() + 1), |
|
170 |
#' timezone = "UTC" |
|
171 |
#' ) |
|
172 |
#' @return a `call` |
|
173 |
#' @keywords internal |
|
174 |
call_condition_range_posixct <- function(varname, range, timezone = Sys.timezone()) { |
|
175 | ! |
checkmate::assert_posixct(range, len = 2, sorted = TRUE) |
176 | ! |
checkmate::assert_string(timezone) |
177 | ! |
varname <- call_check_parse_varname(varname) |
178 | ||
179 | ! |
range[1] <- trunc(range[1], units = c("secs")) |
180 | ! |
range[2] <- trunc(range[2] + 1, units = c("secs")) |
181 | ||
182 | ! |
range <- format( |
183 | ! |
range, |
184 | ! |
format = "%Y-%m-%d %H:%M:%S", |
185 | ! |
tz = timezone |
186 |
) |
|
187 | ||
188 | ! |
call( |
189 |
"&", |
|
190 | ! |
call(">=", varname, call("as.POSIXct", range[1], tz = timezone)), |
191 | ! |
call("<", varname, call("as.POSIXct", range[2], tz = timezone)) |
192 |
) |
|
193 |
} |
|
194 | ||
195 |
#' `Date` range condition call |
|
196 |
#' |
|
197 |
#' Compose `Date` range condition call from inputs |
|
198 |
#' |
|
199 |
#' @param varname (`name` or `character(1)`)\cr |
|
200 |
#' name of the variable |
|
201 |
#' |
|
202 |
#' @param range (`Date`)\cr |
|
203 |
#' range of the variable |
|
204 |
#' |
|
205 |
#' @examples |
|
206 |
#' teal.transform:::call_condition_range_date( |
|
207 |
#' as.name("date"), |
|
208 |
#' range = c(Sys.Date(), Sys.Date() + 1) |
|
209 |
#' ) |
|
210 |
#' @return a `call` |
|
211 |
#' @keywords internal |
|
212 |
call_condition_range_date <- function(varname, range) { |
|
213 | ! |
checkmate::assert_date(range, len = 2) |
214 | ! |
checkmate::assert_true(range[2] >= range[1]) |
215 | ! |
varname <- call_check_parse_varname(varname) |
216 | ||
217 | ! |
call( |
218 |
"&", |
|
219 | ! |
call(">=", varname, call("as.Date", as.character(range[1]))), |
220 | ! |
call("<=", varname, call("as.Date", as.character(range[2]))) |
221 |
) |
|
222 |
} |
|
223 | ||
224 |
#' Get call to subset and select array |
|
225 |
#' |
|
226 |
#' Get call to subset and select array |
|
227 |
#' @param dataname (`character(1)` or `name`)\cr |
|
228 |
#' @param row (`name`, `call`, `logical`, `integer`, `character`)\cr |
|
229 |
#' optional, name of the `row` or condition |
|
230 |
#' @param column (`name`, `call`, `logical`, `integer`, `character`)\cr |
|
231 |
#' optional, name of the `column` or condition |
|
232 |
#' @param aisle (`name`, `call`, `logical`, `integer`, `character`)\cr |
|
233 |
#' optional, name of the `row` or condition |
|
234 |
#' @return `[` call with all conditions included |
|
235 |
#' @examples |
|
236 |
#' teal.transform:::call_extract_array( |
|
237 |
#' dataname = "my_array", |
|
238 |
#' row = teal.transform:::call_condition_choice("my_array$SEX", "M"), |
|
239 |
#' column = call("c", "SEX", "AGE"), |
|
240 |
#' aisle = "RNAseq_rnaaccess" |
|
241 |
#' ) |
|
242 |
#' teal.transform:::call_extract_array( |
|
243 |
#' "mae_object", |
|
244 |
#' column = teal.transform:::call_condition_choice("SEX", "M") |
|
245 |
#' ) |
|
246 |
#' @return specific \code{\link[base]{Extract}} `call` for 3-dimensional array |
|
247 |
#' @keywords internal |
|
248 |
call_extract_array <- function(dataname = ".", row = NULL, column = NULL, aisle = NULL) { |
|
249 | ! |
checkmate::assert( |
250 | ! |
checkmate::check_string(dataname), |
251 | ! |
checkmate::check_class(dataname, "call"), |
252 | ! |
checkmate::check_class(dataname, "name") |
253 |
) |
|
254 | ! |
stopifnot(is.null(row) || is.call(row) || is.character(row) || is.logical(row) || is.integer(row) || is.name(row)) |
255 | ! |
stopifnot(is.null(column) || is.call(column) || is.vector(column) || is.name(column)) |
256 | ! |
stopifnot(is.null(aisle) || is.call(aisle) || is.vector(aisle) || is.name(aisle)) |
257 | ||
258 | ! |
if (is.language(dataname)) { |
259 | ! |
dataname <- paste(trimws(deparse(dataname, width.cutoff = 500L)), collapse = "\n") |
260 |
} |
|
261 | ||
262 | ! |
row <- if (is.null(row)) { |
263 |
"" |
|
264 |
} else { |
|
265 | ! |
paste(trimws(deparse(row, width.cutoff = 500L)), collapse = "\n") |
266 |
} |
|
267 | ! |
column <- if (is.null(column)) { |
268 |
"" |
|
269 |
} else { |
|
270 | ! |
paste(trimws(deparse(column, width.cutoff = 500L)), collapse = "\n") |
271 |
} |
|
272 | ! |
aisle <- if (is.null(aisle)) { |
273 |
"" |
|
274 |
} else { |
|
275 | ! |
paste(trimws(deparse(aisle, width.cutoff = 500L)), collapse = "\n") |
276 |
} |
|
277 | ||
278 | ! |
parse( |
279 | ! |
text = sprintf("%s[%s, %s, %s]", dataname, row, column, aisle), |
280 | ! |
keep.source = FALSE |
281 | ! |
)[[1]] |
282 |
} |
|
283 | ||
284 |
#' Get call to subset and select matrix |
|
285 |
#' |
|
286 |
#' Get call to subset and select matrix |
|
287 |
#' @param dataname (`character(1)` or `name`)\cr |
|
288 |
#' @param row (`name`, `call`, `logical`, `integer`, `character`)\cr |
|
289 |
#' optional, name of the `row` or condition |
|
290 |
#' @param column (`name`, `call`, `logical`, `integer`, `character`)\cr |
|
291 |
#' optional, name of the `column` or condition |
|
292 |
#' @return `[` call with all conditions included |
|
293 |
#' @examples |
|
294 |
#' teal.transform:::call_extract_matrix( |
|
295 |
#' dataname = "my_array", |
|
296 |
#' row = teal.transform:::call_condition_choice("my_array$SEX", "M"), |
|
297 |
#' column = call("c", "SEX", "AGE") |
|
298 |
#' ) |
|
299 |
#' teal.transform:::call_extract_matrix( |
|
300 |
#' "mae_object", |
|
301 |
#' column = teal.transform:::call_condition_choice("SEX", "M") |
|
302 |
#' ) |
|
303 |
#' @return specific \code{\link[base]{Extract}} `call` for matrix |
|
304 |
#' @keywords internal |
|
305 |
call_extract_matrix <- function(dataname = ".", row = NULL, column = NULL) { |
|
306 | ! |
checkmate::assert( |
307 | ! |
checkmate::check_string(dataname), |
308 | ! |
checkmate::check_class(dataname, "call"), |
309 | ! |
checkmate::check_class(dataname, "name") |
310 |
) |
|
311 | ! |
stopifnot(is.null(row) || is.call(row) || is.character(row) || is.logical(row) || is.integer(row) || is.name(row)) |
312 | ! |
stopifnot(is.null(column) || is.call(column) || is.vector(column) || is.name(column)) |
313 | ||
314 | ! |
if (is.language(dataname)) { |
315 | ! |
dataname <- paste(trimws(deparse(dataname, width.cutoff = 500L)), collapse = "\n") |
316 |
} |
|
317 | ||
318 | ! |
row <- if (is.null(row)) { |
319 |
"" |
|
320 |
} else { |
|
321 | ! |
paste(trimws(deparse(row, width.cutoff = 500L)), collapse = "\n") |
322 |
} |
|
323 | ! |
column <- if (is.null(column)) { |
324 |
"" |
|
325 |
} else { |
|
326 | ! |
paste(trimws(deparse(column, width.cutoff = 500L)), collapse = "\n") |
327 |
} |
|
328 | ||
329 | ! |
parse( |
330 | ! |
text = sprintf("%s[%s, %s]", dataname, row, column), |
331 | ! |
keep.source = FALSE |
332 | ! |
)[[1]] |
333 |
} |
|
334 | ||
335 | ||
336 |
#' Compose extract call with `$` operator |
|
337 |
#' |
|
338 |
#' Compose extract call with `$` operator |
|
339 |
#' |
|
340 |
#' @param dataname (`character(1)` or `name`)\cr |
|
341 |
#' name of the object |
|
342 |
#' |
|
343 |
#' @param varname (`character(1)` or `name`)\cr |
|
344 |
#' name of the slot in data |
|
345 |
#' |
|
346 |
#' @param dollar (`logical(1)`)\cr |
|
347 |
#' whether returned call should use `$` or `[[` operator |
|
348 |
#' |
|
349 |
#' @return `$` or `[[` call |
|
350 |
#' @examples |
|
351 |
#' teal.transform:::call_extract_list("ADSL", "SEX") |
|
352 |
#' teal.transform:::call_extract_list("ADSL", "named element") |
|
353 |
#' teal.transform:::call_extract_list(as.name("ADSL"), as.name("AGE")) |
|
354 |
#' teal.transform:::call_extract_list(as.name("weird name"), as.name("AGE")) |
|
355 |
#' teal.transform:::call_extract_list(as.name("ADSL"), "AGE", dollar = FALSE) |
|
356 |
#' @keywords internal |
|
357 |
call_extract_list <- function(dataname, varname, dollar = TRUE) { |
|
358 | ! |
checkmate::assert_flag(dollar) |
359 | ! |
checkmate::assert( |
360 | ! |
checkmate::check_string(varname), |
361 | ! |
checkmate::check_class(varname, "name"), |
362 | ! |
checkmate::assert( |
363 | ! |
combine = "and", |
364 | ! |
checkmate::check_class(varname, "call"), |
365 | ! |
checkmate::check_false(dollar) |
366 |
) |
|
367 |
) |
|
368 | ||
369 | ! |
dataname <- call_check_parse_varname(dataname) |
370 | ||
371 | ! |
if (dollar) { |
372 | ! |
call("$", dataname, varname) |
373 |
} else { |
|
374 | ! |
call("[[", dataname, varname) |
375 |
} |
|
376 |
} |
|
377 | ||
378 |
#' Create a call using a function in a given namespace |
|
379 |
#' |
|
380 |
#' The arguments in ... need to be quoted because they will be evaluated otherwise |
|
381 |
#' |
|
382 |
#' @md |
|
383 |
#' @param name `character` function name, possibly using namespace colon `::`, also |
|
384 |
#' works with `:::` (sometimes needed, but strongly discouraged) |
|
385 |
#' @param ... arguments to pass to function with name `name` |
|
386 |
#' @param unlist_args `list` extra arguments passed in a single list, |
|
387 |
#' avoids the use of `do.call` with this function |
|
388 |
#' @examples |
|
389 |
#' |
|
390 |
#' print_call_and_eval <- function(x) { |
|
391 |
#' eval(print(x)) |
|
392 |
#' } |
|
393 |
#' |
|
394 |
#' print_call_and_eval( |
|
395 |
#' teal.transform:::call_with_colon("glue::glue", "x = {x}", x = 10) |
|
396 |
#' ) |
|
397 |
#' \dontrun{ |
|
398 |
#' # mtcars$cyl evaluated |
|
399 |
#' print_call_and_eval( |
|
400 |
#' teal.transform:::call_with_colon("dplyr::filter", as.name("mtcars"), mtcars$cyl == 6) |
|
401 |
#' ) |
|
402 |
#' |
|
403 |
#' # mtcars$cyl argument not evaluated immediately (in call expression) |
|
404 |
#' print_call_and_eval( |
|
405 |
#' teal.transform:::call_with_colon("dplyr::filter", as.name("mtcars"), quote(cyl == 6)) |
|
406 |
#' ) |
|
407 |
#' |
|
408 |
#' # does not work because argument is evaluated and the |
|
409 |
#' # non-dplyr filter does not look inside mtcars |
|
410 |
#' # cannot eval because it does not pass checks because of non-standard evaluation |
|
411 |
#' call("filter", as.name("mtcars"), quote(cyl == 6)) |
|
412 |
#' # works, but non-dplyr filter is taken |
|
413 |
#' call("filter", as.name("mtcars"), mtcars$cyl == 6) |
|
414 |
#' |
|
415 |
#' nb_args <- function(...) nargs() |
|
416 |
#' print_call_and_eval( |
|
417 |
#' teal.transform:::call_with_colon("nb_args", arg1 = 1, unlist_args = list(arg2 = 2, args3 = 3)) |
|
418 |
#' ) |
|
419 |
#' # duplicate arguments |
|
420 |
#' print_call_and_eval( |
|
421 |
#' teal.transform:::call_with_colon("nb_args", arg1 = 1, unlist_args = list(arg2 = 2, args2 = 2)) |
|
422 |
#' ) |
|
423 |
#' } |
|
424 |
#' @keywords internal |
|
425 |
call_with_colon <- function(name, ..., unlist_args = list()) { |
|
426 | ! |
checkmate::assert_string(name) |
427 | ! |
checkmate::assert_list(unlist_args) |
428 | ! |
as.call(c( |
429 | ! |
parse(text = name, keep.source = FALSE)[[1]], |
430 | ! |
c(list(...), unlist_args) |
431 |
)) |
|
432 |
} |
|
433 | ||
434 | ||
435 |
#' Combine calls by operator |
|
436 |
#' |
|
437 |
#' Combine list of calls by specific operator |
|
438 |
#' |
|
439 |
#' @param operator (`character(1)` or `name`)\cr |
|
440 |
#' name/symbol of the operator. |
|
441 |
#' |
|
442 |
#' @param calls (`list` of calls)\cr |
|
443 |
#' list containing calls to be combined by `operator` |
|
444 |
#' |
|
445 |
#' @return call |
|
446 |
#' @examples |
|
447 |
#' teal.transform:::calls_combine_by( |
|
448 |
#' "&", |
|
449 |
#' calls = list( |
|
450 |
#' teal.transform:::call_condition_choice("SEX", "F"), |
|
451 |
#' teal.transform:::call_condition_range("AGE", c(20, 50)), |
|
452 |
#' teal.transform:::call_condition_choice("ARM", "ARM: A"), |
|
453 |
#' TRUE |
|
454 |
#' ) |
|
455 |
#' ) |
|
456 |
#' @return a combined `call` |
|
457 |
#' @keywords internal |
|
458 |
calls_combine_by <- function(operator, calls) { |
|
459 | 98x |
checkmate::assert_string(operator) |
460 | 98x |
stopifnot( |
461 | 98x |
all( |
462 | 98x |
vapply( |
463 | 98x |
X = calls, |
464 | 98x |
FUN.VALUE = logical(1), |
465 | 98x |
FUN = function(x) is.language(x) || is.logical(x) |
466 |
) |
|
467 |
) |
|
468 |
) |
|
469 | ||
470 | 98x |
Reduce( |
471 | 98x |
x = calls, |
472 | 98x |
f = function(x, y) call(operator, x, y) |
473 |
) |
|
474 |
} |
1 |
#' Returns a `shiny.tag` with the UI elements for a `data_extract_spec` |
|
2 |
#' |
|
3 |
#' @details Creates a `shiny.tag` element defining the UI elements corresponding |
|
4 |
#' a single `data_extract_spec` object. |
|
5 |
#' |
|
6 |
#' @param id (`character(1)`) the id of the module |
|
7 |
#' @param single_data_extract_spec (`data_extract_spec`) the [data_extract_spec()] object to handle. |
|
8 |
#' |
|
9 |
#' @return `shiny.tag` the HTML element defining the UI |
|
10 |
#' @keywords internal |
|
11 |
#' |
|
12 |
#' @examples |
|
13 |
#' teal.transform:::data_extract_single_ui(id = "test", data_extract_spec("extract")) |
|
14 |
data_extract_single_ui <- function(id = NULL, single_data_extract_spec) { |
|
15 | 4x |
stopifnot(inherits(single_data_extract_spec, "data_extract_spec")) |
16 | 4x |
ns <- NS(id) |
17 | ||
18 |
## filter input |
|
19 | 4x |
extract_spec_filter <- single_data_extract_spec$filter |
20 | 4x |
filter_display <- do.call( |
21 | 4x |
div, |
22 | 4x |
lapply( |
23 | 4x |
seq_along(extract_spec_filter), |
24 | 4x |
function(idx) { |
25 | 6x |
x <- extract_spec_filter[[idx]] |
26 | 6x |
if (inherits(x, "filter_spec")) { |
27 | 6x |
data_extract_filter_ui(filter = x, id = ns(paste0("filter", idx))) |
28 |
} else { |
|
29 | ! |
stop("Unsupported object class") |
30 |
} |
|
31 |
} |
|
32 |
) |
|
33 |
) |
|
34 | ||
35 |
## select input |
|
36 | 4x |
extract_spec_select <- single_data_extract_spec$select |
37 | 4x |
if (!is.null(extract_spec_select$fixed)) { |
38 | 4x |
attr(extract_spec_select$fixed, which = "dataname") <- single_data_extract_spec$dataname |
39 |
} |
|
40 | ||
41 | 4x |
select_display <- if (is.null(extract_spec_select)) { |
42 | ! |
NULL |
43 |
} else { |
|
44 | 4x |
data_extract_select_ui(extract_spec_select, id = ns("select")) |
45 |
} |
|
46 | ||
47 |
## reshape input |
|
48 | 4x |
extract_spec_reshape <- single_data_extract_spec$reshape |
49 | 4x |
reshape_display <- checkboxInput( |
50 | 4x |
inputId = ns("reshape"), |
51 | 4x |
label = "Reshape long to wide format", |
52 | 4x |
value = extract_spec_reshape |
53 |
) |
|
54 |
# always disable reshape button and hide if it is not pre-configured |
|
55 | 4x |
reshape_display <- shinyjs::disabled(reshape_display) |
56 | 4x |
if (!extract_spec_reshape) reshape_display <- shinyjs::hidden(reshape_display) |
57 | ||
58 |
## all combined |
|
59 | 4x |
div(filter_display, select_display, reshape_display) |
60 |
} |
|
61 | ||
62 |
#' The server function for a single `data_extract_spec` object |
|
63 |
#' |
|
64 |
#' @details The Shiny server function for handling a single |
|
65 |
#' [data_extract_spec] object. |
|
66 |
#' |
|
67 |
#' @inheritParams data_extract_filter_srv |
|
68 |
#' @inheritParams data_extract_single_ui |
|
69 |
#' |
|
70 |
#' @return `NULL` |
|
71 |
#' @keywords internal |
|
72 |
data_extract_single_srv <- function(id, datasets, single_data_extract_spec) { |
|
73 | 22x |
moduleServer( |
74 | 22x |
id, |
75 | 22x |
function(input, output, session) { |
76 | 22x |
logger::log_trace( |
77 | 22x |
"data_extract_single_srv initialized with dataset: { single_data_extract_spec$dataname }." |
78 |
) |
|
79 | 22x |
for (idx in seq_along(single_data_extract_spec$filter)) { |
80 | 8x |
x <- single_data_extract_spec$filter[[idx]] |
81 | 8x |
if (inherits(x, "filter_spec")) { |
82 | 8x |
data_extract_filter_srv( |
83 | 8x |
id = paste0("filter", idx), |
84 | 8x |
datasets = datasets, |
85 | 8x |
filter = x |
86 |
) |
|
87 |
} |
|
88 | 8x |
NULL |
89 |
} |
|
90 |
} |
|
91 |
) |
|
92 |
} |
1 |
#' Aggregates data extract selectors |
|
2 |
#' |
|
3 |
#' Simplifies selector_list into aggregated list with one element per |
|
4 |
#' same selector - same dataset, same filter configuration and same reshape status |
|
5 |
#' @inheritParams get_merge_call |
|
6 |
#' |
|
7 |
#' @return (\code{list}) simplified selectors with aggregated set of filters, |
|
8 |
#' selections, reshapes etc. All necessary data for merging |
|
9 |
#' @keywords internal |
|
10 |
get_dplyr_call_data <- function(selector_list, join_keys = teal.data::join_keys()) { |
|
11 | 163x |
logger::log_trace("get_dplyr_call_data called with: { paste(names(selector_list), collapse = ', ') } selectors.") |
12 | 163x |
checkmate::assert_class(join_keys, "JoinKeys") |
13 | 163x |
lapply(selector_list, check_selector) |
14 | ||
15 | 163x |
all_merge_key_list <- get_merge_key_grid(selector_list, join_keys) |
16 | 163x |
res <- lapply( |
17 | 163x |
seq_along(selector_list), |
18 | 163x |
function(idx) { |
19 | 361x |
internal_id <- selector_list[[idx]]$internal_id |
20 | ||
21 | 361x |
merge_keys_list <- all_merge_key_list[[idx]] |
22 | ||
23 | 361x |
merge_keys <- if (length(merge_keys_list) > 1) { |
24 | 328x |
unique(unlist(lapply(merge_keys_list[-idx], names))) |
25 |
} else { |
|
26 | 33x |
names(merge_keys_list[[1]]) |
27 |
} |
|
28 | ||
29 | 361x |
if (isFALSE(selector_list[[idx]]$reshape)) { |
30 | 272x |
unite_cols <- character(0) |
31 | 272x |
pivot_longer_cols <- character(0) |
32 | 272x |
unite_vals <- character(0) |
33 |
} else { |
|
34 | 89x |
unite_cols <- get_reshape_unite_col(selector_list[[idx]]) |
35 | 89x |
pivot_longer_cols <- get_pivot_longer_col(selector_list[[idx]]) |
36 | 89x |
unite_vals <- get_reshape_unite_vals(selector_list[[idx]]) |
37 |
} |
|
38 | ||
39 | 361x |
selector_cols <- c(selector_list[[idx]]$select) |
40 | 361x |
init_select_cols <- unique(c(pivot_longer_cols, selector_cols)) |
41 | 361x |
init_select_cols_with_keys <- unique(c(merge_keys, unite_cols, pivot_longer_cols, selector_cols)) |
42 |
# can change order of keys |
|
43 | ||
44 | 361x |
list( |
45 | 361x |
internal_id = internal_id, |
46 | 361x |
merge_keys_list = merge_keys_list, |
47 | 361x |
unite_cols = unite_cols, |
48 | 361x |
unite_vals = unite_vals, |
49 | 361x |
pivot_longer_cols = pivot_longer_cols, |
50 | 361x |
selector_cols = selector_cols, |
51 | 361x |
init_select_cols_with_keys = init_select_cols_with_keys, |
52 | 361x |
init_select_cols = init_select_cols |
53 |
) |
|
54 |
} |
|
55 |
) |
|
56 | ||
57 |
# rename duplicated non-key columns |
|
58 | 163x |
all_cols <- unlist(lapply(res, `[[`, "init_select_cols")) |
59 | 163x |
for (idx1 in seq_along(res)) { |
60 | 361x |
init_select_cols <- res[[idx1]]$init_select_cols |
61 | 361x |
internal_id <- res[[idx1]]$internal_id |
62 | 361x |
selector_cols <- res[[idx1]]$selector_cols |
63 | 361x |
unite_cols <- res[[idx1]]$unite_cols |
64 | 361x |
unite_vals <- res[[idx1]]$unite_vals |
65 | 361x |
pivot_longer_cols <- res[[idx1]]$pivot_longer_cols |
66 | 361x |
merge_keys <- unique(unlist(res[[idx1]]$merge_keys_list)) |
67 | ||
68 | 361x |
init_select_cols_renamed <- rename_duplicated_cols( |
69 | 361x |
setdiff(init_select_cols, merge_keys), |
70 | 361x |
internal_id, |
71 | 361x |
setdiff(selector_cols, unite_cols), |
72 | 361x |
all_cols |
73 |
) |
|
74 | ||
75 | 361x |
pivot_longer_cols_renamed <- rename_duplicated_cols( |
76 | 361x |
pivot_longer_cols, |
77 | 361x |
internal_id, |
78 | 361x |
setdiff(selector_cols, unite_cols), |
79 | 361x |
all_cols |
80 |
) |
|
81 | ||
82 | 361x |
pivot_longer_unite_cols_renamed <- if (rlang::is_empty(unite_vals)) { # nolint |
83 | 278x |
pivot_longer_cols_renamed |
84 |
} else { |
|
85 | 83x |
Reduce( |
86 | 83x |
append, |
87 | 83x |
mapply( |
88 | 83x |
function(x1, name) { |
89 | 114x |
stats::setNames(paste(x1, unite_vals, sep = "_"), rep(name, length(unite_vals))) |
90 |
}, |
|
91 | 83x |
x1 = pivot_longer_cols_renamed, |
92 | 83x |
name = pivot_longer_cols, |
93 | 83x |
SIMPLIFY = FALSE, |
94 | 83x |
USE.NAMES = FALSE |
95 |
) |
|
96 |
) |
|
97 |
} |
|
98 | ||
99 | 361x |
selector_cols_renamed <- rename_duplicated_cols( |
100 | 361x |
init_select_cols, |
101 | 361x |
internal_id, |
102 | 361x |
setdiff(selector_cols, unite_cols), |
103 | 361x |
all_cols[!all_cols %in% merge_keys] |
104 |
) |
|
105 | ||
106 | 361x |
out_cols_renamed <- if (!rlang::is_empty(pivot_longer_unite_cols_renamed)) { |
107 | 86x |
pivot_longer_unite_cols_renamed |
108 |
} else { |
|
109 | 275x |
selector_cols_renamed |
110 |
} |
|
111 | ||
112 | 361x |
res[[idx1]]$init_select_cols_renamed <- init_select_cols_renamed |
113 | 361x |
res[[idx1]]$pivot_longer_cols_renamed <- pivot_longer_cols_renamed |
114 | 361x |
res[[idx1]]$out_cols_renamed <- out_cols_renamed |
115 |
} |
|
116 | 163x |
res |
117 |
} |
|
118 | ||
119 |
#' Parses filter, select, rename and reshape call |
|
120 |
#' |
|
121 |
#' Parse filter, select, rename and reshape call |
|
122 |
#' @inheritParams get_dplyr_call_data |
|
123 |
#' @param idx optional (\code{integer}) current selector index in all selectors list |
|
124 |
#' @param dplyr_call_data (\code{list}) simplified selectors with aggregated set of filters, |
|
125 |
#' selections, reshapes etc. All necessary data for merging |
|
126 |
#' @param data (`NULL` or named `list`). |
|
127 |
#' |
|
128 |
#' @return (\code{call}) filter, select, rename and reshape call |
|
129 |
#' @keywords internal |
|
130 |
#' |
|
131 |
#' @examples |
|
132 |
#' # one dataset |
|
133 |
#' teal.transform:::get_dplyr_call( |
|
134 |
#' list(list( |
|
135 |
#' dataname = "ADSL", |
|
136 |
#' filters = NULL, |
|
137 |
#' select = character(0), |
|
138 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
139 |
#' reshape = FALSE, |
|
140 |
#' internal_id = "test1" |
|
141 |
#' )) |
|
142 |
#' ) |
|
143 |
#' teal.transform:::get_dplyr_call( |
|
144 |
#' list(list( |
|
145 |
#' dataname = "ADSL", |
|
146 |
#' filters = list(list(columns = "SEX", selected = list("F", "M"))), |
|
147 |
#' select = character(0), |
|
148 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
149 |
#' reshape = FALSE, |
|
150 |
#' internal_id = "test1" |
|
151 |
#' )) |
|
152 |
#' ) |
|
153 |
#' teal.transform:::get_dplyr_call( |
|
154 |
#' list(list( |
|
155 |
#' dataname = "ADSL", |
|
156 |
#' filters = list(list(columns = "SEX", selected = list("F", "M"))), |
|
157 |
#' select = c("AVAL"), |
|
158 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
159 |
#' reshape = FALSE, |
|
160 |
#' internal_id = "test1" |
|
161 |
#' )) |
|
162 |
#' ) |
|
163 |
#' |
|
164 |
#' # two datasets with rename part |
|
165 |
#' teal.transform:::get_dplyr_call( |
|
166 |
#' list( |
|
167 |
#' list( |
|
168 |
#' dataname = "ADSL", |
|
169 |
#' filters = NULL, |
|
170 |
#' select = c("COL_1", "COL_2"), |
|
171 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
172 |
#' reshape = FALSE, |
|
173 |
#' internal_id = "test1" |
|
174 |
#' ), |
|
175 |
#' list( |
|
176 |
#' dataname = "ADSL", |
|
177 |
#' filters = NULL, |
|
178 |
#' select = c("COL_2", "COL_3"), |
|
179 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
180 |
#' reshape = FALSE, |
|
181 |
#' internal_id = "test2" |
|
182 |
#' ) |
|
183 |
#' ), |
|
184 |
#' idx = 1L |
|
185 |
#' ) |
|
186 |
#' |
|
187 |
#' # long dataset with reshape part |
|
188 |
#' teal.transform:::get_dplyr_call( |
|
189 |
#' list(list( |
|
190 |
#' dataname = "ADLB", |
|
191 |
#' filters = list(list( |
|
192 |
#' columns = c("PARAMCD", "AVISIT"), |
|
193 |
#' selected = list(c("ALBCV", "SCREENING"), c("ALBCV", "BASELINE")) |
|
194 |
#' )), |
|
195 |
#' select = c("AVAL"), |
|
196 |
#' keys = teal.data::get_cdisc_keys("ADLB"), |
|
197 |
#' reshape = TRUE, |
|
198 |
#' internal_id = "test1" |
|
199 |
#' )) |
|
200 |
#' ) |
|
201 |
get_dplyr_call <- function(selector_list, |
|
202 |
idx = 1L, |
|
203 |
join_keys = teal.data::join_keys(), |
|
204 |
dplyr_call_data = get_dplyr_call_data(selector_list, join_keys = join_keys), |
|
205 |
datasets = NULL) { |
|
206 | 121x |
logger::log_trace( |
207 | 121x |
paste( |
208 | 121x |
"get_dplyr_call called with:", |
209 | 121x |
"{ paste(names(datasets), collapse = ', ') } datasets;", |
210 | 121x |
"{ paste(names(selector_list), collapse = ', ') } selectors." |
211 |
) |
|
212 |
) |
|
213 | 121x |
lapply(selector_list, check_selector) |
214 | 121x |
checkmate::assert_class(join_keys, "JoinKeys", null.ok = TRUE) |
215 | 121x |
checkmate::assert_integer(idx, len = 1, any.missing = FALSE) |
216 | ||
217 | 121x |
n_selectors <- length(selector_list) |
218 | ||
219 | 121x |
dataname_filtered <- as.name(selector_list[[idx]]$dataname) |
220 | ||
221 | 121x |
filter_call <- get_filter_call(selector_list[[idx]]$filters, selector_list[[idx]]$dataname, datasets) |
222 | ||
223 | 121x |
select_call <- get_select_call(dplyr_call_data[[idx]]$init_select_cols_with_keys) |
224 | ||
225 | 121x |
rename_call <- if (n_selectors > 1) { |
226 | 90x |
get_rename_call(dplyr_call_data = dplyr_call_data, idx = idx) |
227 |
} else { |
|
228 | 31x |
NULL |
229 |
} |
|
230 | ||
231 | 121x |
reshape_call <- if (isTRUE(selector_list[[idx]]$reshape)) { |
232 | 29x |
get_reshape_call(dplyr_call_data = dplyr_call_data, idx = idx) |
233 |
} else { |
|
234 | 92x |
NULL |
235 |
} |
|
236 | ||
237 | 121x |
final_call <- Reduce( |
238 | 121x |
function(x, y) call("%>%", x, y), |
239 | 121x |
Filter(function(x) !is.null(x), c(dataname_filtered, filter_call, select_call, rename_call, reshape_call)) |
240 |
) |
|
241 | ||
242 | 121x |
return(final_call) |
243 |
} |
|
244 | ||
245 |
#' Parse \code{dplyr} select call |
|
246 |
#' |
|
247 |
#' @param select (\code{character}) vector of selected column names |
|
248 |
#' |
|
249 |
#' @return (\code{call}) \code{dplyr} select call |
|
250 |
#' @keywords internal |
|
251 |
#' |
|
252 |
#' @examples |
|
253 |
#' teal.transform:::get_select_call(letters) |
|
254 |
get_select_call <- function(select) { |
|
255 | 124x |
logger::log_trace("get_select_call called with: { paste(select, collapse = ', ') } columns.") |
256 | 124x |
if (is.null(select) || length(select) == 0) { |
257 | 1x |
return(NULL) |
258 |
} |
|
259 | ||
260 | 123x |
select <- unique(select) |
261 | ||
262 | 123x |
as.call(c(list(quote(dplyr::select)), lapply(select, as.name))) |
263 |
} |
|
264 | ||
265 |
#' Returns \code{dplyr} filter call |
|
266 |
#' |
|
267 |
#' @param filter (\code{list}) Either list of lists or list with \code{select} and \code{selected} items. |
|
268 |
#' @param dataname (\code{NULL} or \code{character}) name of dataset. |
|
269 |
#' @param datasets (\code{NULL} or \code{named `list`}). |
|
270 |
#' @return (\code{call}) \code{dplyr} filter call |
|
271 |
#' @keywords internal |
|
272 |
#' |
|
273 |
#' @examples |
|
274 |
#' teal.transform:::get_filter_call( |
|
275 |
#' filter = list(list(columns = "SEX", selected = list(NA, "F", "M"))) |
|
276 |
#' ) |
|
277 |
#' teal.transform:::get_filter_call(filter = list( |
|
278 |
#' list(columns = "SEX", selected = list(NA, "F", "M")), |
|
279 |
#' list(columns = "VAR", selected = list("LEVEL1", "LEVEL2")) |
|
280 |
#' )) |
|
281 |
get_filter_call <- function(filter, dataname = NULL, datasets = NULL) { |
|
282 | 142x |
logger::log_trace( |
283 | 142x |
paste( |
284 | 142x |
"get_filter_call called with:", |
285 | 142x |
"{ dataname } dataset;", |
286 | 142x |
"{ paste(sapply(filter, function(x) x$columns), collapse = ', ') } filters." |
287 |
) |
|
288 |
) |
|
289 | 142x |
checkmate::assert_list(datasets, types = "reactive", names = "named", null.ok = TRUE) |
290 | 141x |
if (is.null(filter)) { |
291 | 38x |
return(NULL) |
292 |
} |
|
293 | ||
294 | 103x |
stopifnot((!is.null(dataname) && is.null(datasets)) || |
295 | 103x |
(is.null(dataname) && is.null(datasets)) || |
296 | 103x |
(!is.null(datasets) && isTRUE(dataname %in% names(datasets)))) |
297 | ||
298 | 103x |
get_filter_call_internal <- function(filter, dataname, datasets) { |
299 | 161x |
if (rlang::is_empty(filter$selected)) { |
300 | 2x |
return(FALSE) |
301 |
} |
|
302 | ||
303 | 159x |
keys <- filter$columns |
304 | 159x |
datas_vars <- if (!is.null(datasets)) datasets[[dataname]]() else NULL |
305 | ||
306 | 159x |
if (!is.null(datas_vars)) { |
307 | 17x |
u_variables <- unique(apply(datas_vars[, keys, drop = FALSE], 1, function(x) paste(x, collapse = "-"))) |
308 | 17x |
selected <- if (length(keys) == 1) { |
309 | 11x |
selected_single <- unlist(filter$selected) |
310 |
# We need character NA as for rest vars the NA is translated to "NA" by paste function |
|
311 | 11x |
selected_single[is.na(selected_single)] <- "NA" |
312 | 11x |
selected_single |
313 |
} else { |
|
314 | 6x |
unlist(lapply(filter$selected, function(x) paste(x, collapse = "-"))) |
315 |
} |
|
316 |
# we don't want to process the key which all values are selected |
|
317 |
# this means that call for this key is redundant and will be skipped |
|
318 | 17x |
if (all(u_variables %in% selected)) { |
319 | 6x |
keys <- NULL |
320 |
} |
|
321 |
} |
|
322 | ||
323 | 159x |
if (length(keys) == 1) { |
324 | 135x |
key_name <- unlist(keys) |
325 | 135x |
key_value <- unlist(filter$selected) |
326 | 135x |
varname <- if (isTRUE(inherits(datas_vars[[key_name]], c("POSIXct", "POSIXlt", "POSIXt")))) { |
327 | 1x |
bquote(trunc(.(as.name(key_name)))) |
328 |
} else { |
|
329 | 134x |
as.name(key_name) |
330 |
} |
|
331 | ||
332 | 135x |
if (length(key_value) == 1 && is.na(key_value)) { |
333 | 1x |
call("is.na", as.name(key_name)) |
334 |
} else { |
|
335 | 134x |
call_condition_choice(varname = varname, choices = key_value) |
336 |
} |
|
337 | 24x |
} else if (length(keys) > 1) { |
338 | 18x |
calls_combine_by( |
339 |
"|", |
|
340 | 18x |
lapply( |
341 | 18x |
filter$selected, |
342 | 18x |
function(keys_values) { |
343 | 27x |
res <- calls_combine_by( |
344 |
"&", |
|
345 | 27x |
Map( |
346 | 27x |
keys, |
347 | 27x |
keys_values, |
348 | 27x |
f = function(key_name, key_value) { |
349 | 60x |
if (is.na(key_value)) { |
350 | 6x |
call("is.na", as.name(key_name)) |
351 |
} else { |
|
352 | 54x |
varname <- if (isTRUE(inherits(datas_vars[[key_name]], c("POSIXct", "POSIXlt", "POSIXt")))) { |
353 | 2x |
bquote(trunc(.(as.name(key_name)))) |
354 |
} else { |
|
355 | 52x |
as.name(key_name) |
356 |
} |
|
357 | ||
358 | 54x |
call_condition_choice( |
359 | 54x |
varname = varname, |
360 | 54x |
key_value |
361 |
) |
|
362 |
} |
|
363 |
} |
|
364 |
) |
|
365 |
) |
|
366 | 27x |
call("(", res) |
367 |
} |
|
368 |
) |
|
369 |
) |
|
370 |
} |
|
371 |
} |
|
372 | ||
373 | 103x |
internal <- if (length(filter) == 1) { |
374 | 50x |
get_filter_call_internal(filter[[1]], dataname, datasets) |
375 |
} else { |
|
376 | 53x |
res <- Filter(Negate(is.null), Map(function(x) get_filter_call_internal(x, dataname, datasets), filter)) |
377 | 53x |
calls_combine_by("&", res) |
378 |
} |
|
379 | ||
380 | ||
381 | 103x |
if (!is.null(internal)) { |
382 | 98x |
as.call(c(quote(dplyr::filter), internal)) |
383 |
} else { |
|
384 | 5x |
NULL |
385 |
} |
|
386 |
} |
|
387 | ||
388 |
rename_duplicated_cols <- function(x, internal_id, selected_cols, all_cols) { |
|
389 | 1083x |
all_cols_dups <- all_cols[duplicated(all_cols)] |
390 | 1083x |
vapply( |
391 | 1083x |
x, |
392 | 1083x |
function(y) { |
393 | 1538x |
ifelse(y %in% selected_cols && y %in% all_cols_dups, paste0(internal_id, ".", y), y) |
394 |
}, |
|
395 | 1083x |
character(1) |
396 |
) |
|
397 |
} |
|
398 | ||
399 |
#' Returns \code{dplyr} rename call |
|
400 |
#' |
|
401 |
#' Rename is used only if there are duplicated columns |
|
402 |
#' |
|
403 |
#' @inheritParams get_dplyr_call |
|
404 |
#' |
|
405 |
#' @return (\code{call}) \code{dplyr} rename call |
|
406 |
#' @keywords internal |
|
407 |
#' @references get_rename_dict |
|
408 |
#' |
|
409 |
#' @examples |
|
410 |
#' x <- list( |
|
411 |
#' list( |
|
412 |
#' dataname = "ADSL", |
|
413 |
#' filters = NULL, |
|
414 |
#' select = utils::head(letters, 3), |
|
415 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
416 |
#' reshape = FALSE, |
|
417 |
#' internal_id = "test1" |
|
418 |
#' ), |
|
419 |
#' list( |
|
420 |
#' dataname = "ADSL", |
|
421 |
#' filters = NULL, |
|
422 |
#' select = letters, |
|
423 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
424 |
#' reshape = FALSE, |
|
425 |
#' internal_id = "test2" |
|
426 |
#' ), |
|
427 |
#' list( |
|
428 |
#' dataname = "ADSL", |
|
429 |
#' filters = NULL, |
|
430 |
#' select = utils::tail(letters, 3), |
|
431 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
432 |
#' reshape = FALSE, |
|
433 |
#' internal_id = "test3" |
|
434 |
#' ), |
|
435 |
#' list( |
|
436 |
#' dataname = "ADSL", |
|
437 |
#' filters = NULL, |
|
438 |
#' select = c("aa", "bb"), |
|
439 |
#' keys = teal.data::get_cdisc_keys("ADSL"), |
|
440 |
#' reshape = FALSE, |
|
441 |
#' internal_id = "test4" |
|
442 |
#' ) |
|
443 |
#' ) |
|
444 |
#' teal.transform:::get_rename_call(x, 1L) |
|
445 |
#' teal.transform:::get_rename_call(x, 2L) |
|
446 |
#' teal.transform:::get_rename_call(x, 3L) |
|
447 |
#' teal.transform:::get_rename_call(x, 4L) |
|
448 |
get_rename_call <- function(selector_list = list(), |
|
449 |
idx = 1L, |
|
450 |
join_keys = teal.data::join_keys(), |
|
451 |
dplyr_call_data = get_dplyr_call_data(selector_list, join_keys = join_keys)) { |
|
452 | 94x |
checkmate::assert_integer(idx, len = 1, any.missing = FALSE) |
453 | 94x |
stopifnot(length(dplyr_call_data) >= idx) |
454 | 94x |
logger::log_trace( |
455 | 94x |
paste( |
456 | 94x |
"get_rename_call called with:", |
457 | 94x |
"{ dplyr_call_data[[idx]]$internal_id } selector;", |
458 | 94x |
"{ paste(dplyr_call_data[[idx]]$init_select_cols_renamed, collapse = ', ') } renamed columns." |
459 |
) |
|
460 |
) |
|
461 | ||
462 | 94x |
lapply(selector_list, check_selector) |
463 | ||
464 | 94x |
rename_dict <- dplyr_call_data[[idx]]$init_select_cols_renamed |
465 | 94x |
rename_dict <- rename_dict[names(rename_dict) != rename_dict] |
466 | ||
467 | 94x |
if (is.null(rename_dict) || length(rename_dict) == 0) { |
468 | 16x |
return(NULL) |
469 |
} |
|
470 | ||
471 | 78x |
internal <- stats::setNames(lapply(names(rename_dict), as.name), rename_dict) |
472 | ||
473 | 78x |
as.call(append(quote(dplyr::rename), internal)) |
474 |
} |
|
475 | ||
476 |
#' Returns \code{dplyr} reshape call |
|
477 |
#' |
|
478 |
#' @inheritParams get_dplyr_call |
|
479 |
#' |
|
480 |
#' @return (\code{list}) list of multiple \code{dplyr} calls that reshape data |
|
481 |
#' @keywords internal |
|
482 |
#' |
|
483 |
#' @examples |
|
484 |
#' filters <- list( |
|
485 |
#' columns = c("PARAMCD", "AVISIT"), |
|
486 |
#' selected = list(c("ALT", "SCREENING"), c("ALT", "BASELINE")) |
|
487 |
#' ) |
|
488 |
#' select <- "AVAL" |
|
489 |
#' internal_id <- "ADLB" |
|
490 |
#' |
|
491 |
#' x <- list( |
|
492 |
#' list( |
|
493 |
#' dataname = "ADLB", |
|
494 |
#' filters = list(list( |
|
495 |
#' columns = c("PARAMCD", "AVISIT"), |
|
496 |
#' selected = list(c("ALBCV", "SCREENING"), c("ALBCV", "BASELINE")), |
|
497 |
#' multiple = FALSE |
|
498 |
#' )), |
|
499 |
#' select = "AVAL", |
|
500 |
#' keys = teal.data::get_cdisc_keys("ADLB"), |
|
501 |
#' reshape = TRUE, |
|
502 |
#' internal_id = "test" |
|
503 |
#' ) |
|
504 |
#' ) |
|
505 |
#' teal.transform:::get_reshape_call(x, 1L) |
|
506 |
get_reshape_call <- function(selector_list = list(), |
|
507 |
idx = 1L, |
|
508 |
join_keys = teal.data::join_keys(), |
|
509 |
dplyr_call_data = get_dplyr_call_data(selector_list, join_keys = join_keys)) { |
|
510 | 31x |
checkmate::assert_integer(idx, len = 1, any.missing = FALSE) |
511 | 31x |
stopifnot(length(dplyr_call_data) >= idx) |
512 | 31x |
logger::log_trace( |
513 | 31x |
paste( |
514 | 31x |
"get_reshape_call called with:", |
515 | 31x |
"{ dplyr_call_data[[idx]]$internal_id } selector;", |
516 | 31x |
"{ paste(dplyr_call_data[[idx]]$unite_cols, collapse = ', ') } reshaping columns;", |
517 | 31x |
"{ paste(dplyr_call_data[[idx]]$pivot_longer_cols, collapse = ', ') } reshaped columns." |
518 |
) |
|
519 |
) |
|
520 | 31x |
lapply(selector_list, check_selector) |
521 | ||
522 | 31x |
pl_cols <- unname(dplyr_call_data[[idx]]$pivot_longer_cols_renamed) |
523 | ||
524 | 31x |
pivot_longer_call <- as.call(list( |
525 | 31x |
quote(tidyr::pivot_longer), |
526 | 31x |
cols = if (length(pl_cols)) pl_cols else quote(tidyselect::everything()), |
527 | 31x |
names_to = "MEASURE", |
528 | 31x |
values_to = "VALUE" |
529 |
)) |
|
530 | ||
531 | 31x |
unite_call <- as.call(c( |
532 | 31x |
list(quote(tidyr::unite)), |
533 | 31x |
quote(KEY), |
534 | 31x |
quote(MEASURE), |
535 | 31x |
lapply( |
536 | 31x |
dplyr_call_data[[idx]]$unite_cols, |
537 | 31x |
function(x) { |
538 | 49x |
as.name(x) |
539 |
} |
|
540 |
) |
|
541 |
)) |
|
542 | ||
543 | 31x |
pivot_wider_call <- as.call(list( |
544 | 31x |
quote(tidyr::pivot_wider), |
545 | 31x |
names_from = "KEY", |
546 | 31x |
values_from = "VALUE" |
547 |
)) |
|
548 | ||
549 | 31x |
c(pivot_longer_call, unite_call, pivot_wider_call) |
550 |
} |
|
551 | ||
552 | ||
553 |
#' Get pivot longer columns |
|
554 |
#' |
|
555 |
#' Get values names which are spread into columns. |
|
556 |
#' @param selector one element of selector_list obtained by \code{get_dplyr_call_data}. |
|
557 |
#' @keywords internal |
|
558 |
get_pivot_longer_col <- function(selector) { |
|
559 | 89x |
logger::log_trace("get_reshape_unite_col called with: { selector$internal_id } selector.") |
560 | 89x |
setdiff(selector$select, selector$keys) |
561 |
} |
|
562 | ||
563 |
#' Get unite columns |
|
564 |
#' |
|
565 |
#' Get key names which spreads values into columns. Reshape is done only |
|
566 |
#' on keys which are in \code{filter_spec}. |
|
567 |
#' @inheritParams get_pivot_longer_col |
|
568 |
#' @keywords internal |
|
569 |
get_reshape_unite_col <- function(selector) { |
|
570 | 379x |
logger::log_trace("get_reshape_unite_col called with: { selector$internal_id } selector.") |
571 | 379x |
intersect( |
572 | 379x |
selector$keys, |
573 | 379x |
unlist(lapply(selector$filters, `[[`, "columns")) |
574 |
) |
|
575 |
} |
|
576 | ||
577 |
#' Get unite columns values |
|
578 |
#' |
|
579 |
#' Get key values (levels) of the unite columns. |
|
580 |
#' @inheritParams get_pivot_longer_col |
|
581 |
#' @keywords internal |
|
582 |
get_reshape_unite_vals <- function(selector) { |
|
583 | 123x |
logger::log_trace("get_reshape_unite_vals called with: { selector$internal_id } selector.") |
584 | 123x |
unite_cols <- get_reshape_unite_col(selector) |
585 | 123x |
filters <- selector$filters |
586 | 123x |
filters_columns <- lapply(filters, `[[`, "columns") |
587 | ||
588 |
# first check if combined filter exists then check one by one |
|
589 | 123x |
filters_idx <- which(vapply(filters_columns, function(x) identical(unite_cols, x), logical(1))) |
590 | 123x |
if (length(filters_idx) == 0) { |
591 | 70x |
filters_idx <- which(filters_columns %in% unite_cols) |
592 |
} |
|
593 | ||
594 | 123x |
unite_cols_vals <- lapply( |
595 | 123x |
filters[filters_idx], |
596 | 123x |
function(x) { |
597 | 175x |
vapply(x$selected, paste, character(1), collapse = "_") |
598 |
} |
|
599 |
) |
|
600 | 123x |
unite_cols_vals <- unite_cols_vals[vapply(unite_cols_vals, length, integer(1)) > 0] |
601 | ||
602 | 123x |
res <- if (length(unite_cols_vals) > 0) { |
603 | 114x |
grid <- do.call(expand.grid, args = list(unite_cols_vals, stringsAsFactors = FALSE)) |
604 | 114x |
apply(grid, 1, paste, collapse = "_") |
605 |
} else { |
|
606 | 9x |
character(0) |
607 |
} |
|
608 | ||
609 | 123x |
res |
610 |
} |
1 |
#' Column selection input specification |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' \code{select_spec} is used inside teal to create a \code{\link[shiny]{selectInput}} |
|
5 |
#' that will select columns from a dataset. |
|
6 |
#' |
|
7 |
#' @param choices (\code{character}) or (\code{delayed_data}) object. |
|
8 |
#' Named character vector to define the choices |
|
9 |
#' of a shiny \code{\link[shiny]{selectInput}}. These have to be columns in the |
|
10 |
#' dataset defined in the \code{\link{data_extract_spec}} where this is called. |
|
11 |
#' \code{delayed_data} objects can be created via \code{\link{variable_choices}} or \code{\link{value_choices}}. |
|
12 |
#' |
|
13 |
#' @param selected optional (\code{character} or \code{NULL} or \code{all_choices} or \code{delayed_data} object). |
|
14 |
#' Named character vector to define the selected values of a shiny \code{\link[shiny]{selectInput}}. |
|
15 |
#' Passing an `all_choices()` object indicates selecting all possible choices. |
|
16 |
#' Defaults to the first value of \code{choices} or \code{NULL} for delayed data loading. |
|
17 |
#' |
|
18 |
#' @param multiple (\code{logical}) Whether multiple values shall be allowed in the |
|
19 |
#' shiny \code{\link[shiny]{selectInput}}. |
|
20 |
#' |
|
21 |
#' @param fixed optional (\code{logical}). \code{\link{data_extract_spec}} specific feature to |
|
22 |
#' hide the choices selected in case they are not needed. Setting fixed to \code{TRUE} |
|
23 |
#' will not allow the user to select columns. It will then lead to a selection of |
|
24 |
#' columns in the dataset that is defined by the developer of the app. |
|
25 |
#' |
|
26 |
#' @param always_selected (\code{character}) Additional column names from the data set that should |
|
27 |
#' always be selected |
|
28 |
#' |
|
29 |
#' @param ordered (`logical(1)`) Flags whether selection order should be tracked. |
|
30 |
#' |
|
31 |
#' @param label optional (\code{character}). Define a label on top of this specific |
|
32 |
#' shiny \code{\link[shiny]{selectInput}}. The default value is \code{"Select"}. |
|
33 |
#' |
|
34 |
#' @return A \code{select_spec}-S3 class object or \code{delayed_select_spec}-S3-class object. |
|
35 |
#' It contains all input values. |
|
36 |
#' If \code{select_spec}, then the function double checks the \code{choices} and \code{selected} inputs. |
|
37 |
#' |
|
38 |
#' |
|
39 |
#' @rdname select_spec |
|
40 |
#' |
|
41 |
#' @export |
|
42 |
#' |
|
43 |
#' @examples |
|
44 |
#' # Selection with just one column allowed |
|
45 |
#' select_spec( |
|
46 |
#' choices = c("AVAL", "BMRKR1", "AGE"), |
|
47 |
#' selected = c("AVAL"), |
|
48 |
#' multiple = FALSE, |
|
49 |
#' fixed = FALSE, |
|
50 |
#' label = "Column" |
|
51 |
#' ) |
|
52 |
#' |
|
53 |
#' # Selection with just multiple columns allowed |
|
54 |
#' select_spec( |
|
55 |
#' choices = c("AVAL", "BMRKR1", "AGE"), |
|
56 |
#' selected = c("AVAL", "BMRKR1"), |
|
57 |
#' multiple = TRUE, |
|
58 |
#' fixed = FALSE, |
|
59 |
#' label = "Columns" |
|
60 |
#' ) |
|
61 |
#' |
|
62 |
#' # Selection without user access |
|
63 |
#' select_spec( |
|
64 |
#' choices = c("AVAL", "BMRKR1"), |
|
65 |
#' selected = c("AVAL", "BMRKR1"), |
|
66 |
#' multiple = TRUE, |
|
67 |
#' fixed = TRUE, |
|
68 |
#' label = "Columns" |
|
69 |
#' ) |
|
70 |
#' |
|
71 |
#' # Delayed version |
|
72 |
#' select_spec( |
|
73 |
#' label = "Select variable:", |
|
74 |
#' choices = variable_choices("ADSL", c("BMRKR1", "BMRKR2")), |
|
75 |
#' selected = "BMRKR1", |
|
76 |
#' multiple = FALSE, |
|
77 |
#' fixed = FALSE |
|
78 |
#' ) |
|
79 |
#' |
|
80 |
#' # all_choices passed to selected |
|
81 |
#' select_spec( |
|
82 |
#' label = "Select variable:", |
|
83 |
#' choices = variable_choices("ADSL", c("BMRKR1", "BMRKR2")), |
|
84 |
#' selected = all_choices() |
|
85 |
#' ) |
|
86 |
#' |
|
87 |
#' # Both below objects are semantically the same |
|
88 |
#' select_spec(choices = variable_choices("ADSL"), selected = variable_choices("ADSL")) |
|
89 |
#' select_spec(choices = variable_choices("ADSL"), selected = all_choices()) |
|
90 |
select_spec <- function(choices, |
|
91 |
selected = `if`(inherits(choices, "delayed_data"), NULL, choices[1]), |
|
92 |
multiple = length(selected) > 1 || inherits(selected, "all_choices"), |
|
93 |
fixed = FALSE, |
|
94 |
always_selected = NULL, |
|
95 |
ordered = FALSE, |
|
96 |
label = "Select") { |
|
97 | 142x |
checkmate::assert_flag(multiple) |
98 | 140x |
checkmate::assert_flag(fixed) |
99 | 139x |
checkmate::assert_character(always_selected, min.len = 1, null.ok = TRUE, any.missing = FALSE) |
100 | 139x |
checkmate::assert_flag(ordered) |
101 | 139x |
checkmate::assert_string(label, null.ok = TRUE) |
102 | 138x |
stopifnot(multiple || !inherits(selected, "all_choices")) |
103 | ! |
if (fixed) stopifnot(is.null(always_selected)) |
104 | ||
105 | 3x |
if (inherits(selected, "all_choices")) selected <- choices |
106 | 138x |
if (inherits(choices, "delayed_data") || inherits(selected, "delayed_data")) { |
107 | 39x |
select_spec.delayed_data(choices, selected, multiple, fixed, always_selected, ordered, label) |
108 |
} else { |
|
109 | 99x |
select_spec.default(choices, selected, multiple, fixed, always_selected, ordered, label) |
110 |
} |
|
111 |
} |
|
112 | ||
113 |
#' @rdname select_spec |
|
114 |
#' @export |
|
115 |
select_spec.delayed_data <- function(choices, # nolint |
|
116 |
selected = NULL, |
|
117 |
multiple = length(selected) > 1, |
|
118 |
fixed = FALSE, |
|
119 |
always_selected = NULL, |
|
120 |
ordered = FALSE, |
|
121 |
label = NULL) { |
|
122 | 39x |
stopifnot(is.null(selected) || is.atomic(selected) || inherits(selected, "delayed_data")) |
123 | 39x |
stopifnot(is.null(choices) || is.atomic(choices) || inherits(choices, "delayed_data")) |
124 | ||
125 | 39x |
structure( |
126 | 39x |
list( |
127 | 39x |
choices = choices, |
128 | 39x |
selected = selected, |
129 | 39x |
multiple = multiple, |
130 | 39x |
fixed = fixed, |
131 | 39x |
always_selected = always_selected, |
132 | 39x |
ordered = ordered, |
133 | 39x |
label = label |
134 |
), |
|
135 | 39x |
class = c("delayed_select_spec", "delayed_data", "select_spec") |
136 |
) |
|
137 |
} |
|
138 | ||
139 |
#' @rdname select_spec |
|
140 |
#' @export |
|
141 |
select_spec.default <- function(choices, # nolint |
|
142 |
selected = choices[1], |
|
143 |
multiple = length(selected) > 1, |
|
144 |
fixed = FALSE, |
|
145 |
always_selected = NULL, |
|
146 |
ordered = FALSE, |
|
147 |
label = NULL) { |
|
148 | 99x |
stopifnot(is.null(choices) || is.atomic(choices)) |
149 | 98x |
stopifnot(is.null(selected) || is.atomic(selected)) |
150 | ||
151 |
# if names is NULL, shiny will put strange labels (with quotes etc.) in the selectInputs, so we set it to the values |
|
152 | 97x |
if (is.null(names(choices))) { |
153 | 24x |
names(choices) <- as.character(choices) |
154 |
} |
|
155 | ||
156 |
# Deal with selected |
|
157 | 97x |
if (length(selected) > 0) { |
158 | 77x |
stopifnot(is.atomic(selected)) |
159 | 77x |
stopifnot(all(selected %in% choices)) |
160 | 77x |
stopifnot(multiple || length(selected) == 1) |
161 | 76x |
if (is.null(names(selected))) { |
162 | 41x |
names(selected) <- as.character(selected) |
163 |
} |
|
164 |
} |
|
165 | ||
166 | 96x |
if (length(intersect(choices, always_selected)) > 0) { |
167 | ! |
warning("You cannot allow the user to select 'always_selected' columns. |
168 | ! |
'choices' and 'always_selected' will be intersected") |
169 | ! |
test_c <- choices[which(!choices %in% always_selected)] |
170 | ! |
if (length(test_c) > 0) { |
171 | ! |
class(test_c) <- c("choices_labeled", "character") |
172 | ! |
choices <- test_c |
173 |
} else { |
|
174 | ! |
choices <- NULL |
175 |
} |
|
176 |
} |
|
177 | ||
178 | 96x |
res <- list( |
179 | 96x |
choices = choices, selected = selected, multiple = multiple, fixed = fixed, |
180 | 96x |
always_selected = always_selected, ordered = ordered, label = label |
181 |
) |
|
182 | 96x |
class(res) <- "select_spec" |
183 | ||
184 | 96x |
return(res) |
185 |
} |
1 |
#' Returns non-key column names from data |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' @export |
|
5 |
#' |
|
6 |
#' @param data (\code{data.frame}) Data with attribute \code{filter_and_columns}. This can only be |
|
7 |
#' created by \code{\link{data_extract_srv}}. which returns a shiny \code{\link[shiny]{reactive}}. |
|
8 |
#' |
|
9 |
#' @return A named character vector with the non-key columns of the \code{data}.. |
|
10 |
#' |
|
11 |
#' @references \link{data_extract_srv} |
|
12 |
get_dataset_prefixed_col_names <- function(data) { |
|
13 | ! |
if (!is.null(attr(data, "filter_and_columns")$columns) && attr(data, "filter_and_columns")$columns != "") { |
14 | ! |
paste(attr(data, "dataname"), attr(data, "filter_and_columns")$columns, sep = ".") |
15 |
} else { |
|
16 | ! |
NULL |
17 |
} |
|
18 |
} |
1 |
#' Data extract filter specification |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' It consists in choices and additionally the variable names for the choices |
|
5 |
#' |
|
6 |
#' @export |
|
7 |
#' |
|
8 |
#' @inheritParams select_spec |
|
9 |
#' |
|
10 |
#' @param vars (\code{character}) or (\code{delayed_data}) object. |
|
11 |
#' Character vector giving the columns to be filtered. These should be |
|
12 |
#' key variables of the data set to be filtered. |
|
13 |
#' \code{delayed_data} objects can be created via [variable_choices()], [value_choices()], |
|
14 |
#' or \code{\link{choices_selected}}. |
|
15 |
#' @param sep (\code{character}) A separator string to split the \code{choices} or |
|
16 |
#' \code{selected} inputs into the values of the different columns |
|
17 |
#' @param choices (\code{character} or \code{numeric} or \code{logical} or (\code{delayed_data}) object. |
|
18 |
#' Named character vector to define the choices |
|
19 |
#' of a shiny \code{\link[shiny]{selectInput}}. These choices will be used to filter the |
|
20 |
#' dataset. |
|
21 |
#' |
|
22 |
#' These shall be filter values of the \code{vars} input separated by the separator(\code{sep}). Please |
|
23 |
#' watch out that the filter values have to follow the order of the \code{vars} input. In the following |
|
24 |
#' example we will show how to filter two columns: |
|
25 |
#' |
|
26 |
#' \code{vars = c("PARAMCD","AVISIT")} and \code{choices = c("CRP - BASELINE", "ALT - BASELINE")} |
|
27 |
#' will lead to a filtering of |
|
28 |
#' \code{(PARAMCD == "CRP" & AVISIT == "BASELINE") | (PARAMCD == "ALT" & AVISIT == "BASELINE")}. |
|
29 |
#' |
|
30 |
#' The \code{sep} input has to be \code{" - "} in this case. |
|
31 |
#' |
|
32 |
#' \code{delayed_data} objects can be created via \code{\link{variable_choices}} or \code{\link{value_choices}}. |
|
33 |
#' |
|
34 |
#' @param selected (\code{character} or \code{numeric} or \code{logical} or |
|
35 |
#' (\code{delayed_data} or (\code{all_choices})) object. |
|
36 |
#' Named character vector to define the selected |
|
37 |
#' values of a shiny \code{\link[shiny]{selectInput}} (default values). This value will |
|
38 |
#' be displayed inside the shiny app upon start. The `all_choices` object indicates selecting |
|
39 |
#' all possible choices. |
|
40 |
#' |
|
41 |
#' @param drop_keys optional, (\code{logical}) whether to drop filter column from the dataset keys, |
|
42 |
#' \code{TRUE} on default. |
|
43 |
#' |
|
44 |
#' @param label optional (\code{character}). Define a label on top of this specific |
|
45 |
#' shiny \code{\link[shiny]{selectInput}}. The default value is \code{"Filter by"}. |
|
46 |
#' |
|
47 |
#' @return \code{filter_spec}-S3-class object or \code{delayed_filter_spec}-S3-class object. |
|
48 |
#' |
|
49 |
#' @details |
|
50 |
#' |
|
51 |
#' The \code{filter_spec} is used inside \code{teal} apps to allow filtering datasets |
|
52 |
#' for their key variables. Imagine having an adverse events table. It has |
|
53 |
#' the columns \code{PARAMCD} and \code{CNSR}. \code{PARAMCD} contains the levels |
|
54 |
#' \code{"OS"}, \code{"PFS"}, \code{"EFS"}. \code{CNSR} contains the levels \code{"0"} and \code{"1"}. |
|
55 |
#' The first example should show how a \code{filter_spec} setup will influence |
|
56 |
#' the drop-down menu the app user will see. |
|
57 |
#' |
|
58 |
#' |
|
59 |
#' @examples |
|
60 |
#' # for Adverse Events table |
|
61 |
#' filter_spec( |
|
62 |
#' vars = c("PARAMCD", "CNSR"), |
|
63 |
#' sep = "-", |
|
64 |
#' choices = c("OS-1" = "OS-1", "OS-0" = "OS-0", "PFS-1" = "PFS-1"), |
|
65 |
#' selected = "OS-1", |
|
66 |
#' multiple = FALSE, |
|
67 |
#' label = "Choose endpoint and Censor" |
|
68 |
#' ) |
|
69 |
#' |
|
70 |
#' # filtering a single variable |
|
71 |
#' filter_spec( |
|
72 |
#' vars = c("PARAMCD"), |
|
73 |
#' sep = "-", |
|
74 |
#' choices = c("OS", "PFS", "EFS"), |
|
75 |
#' selected = "OS", |
|
76 |
#' multiple = FALSE, |
|
77 |
#' label = "Choose endpoint" |
|
78 |
#' ) |
|
79 |
#' |
|
80 |
#' # filtering a single variable by multiple levels of the variable |
|
81 |
#' filter_spec( |
|
82 |
#' vars = c("PARAMCD"), |
|
83 |
#' sep = "-", |
|
84 |
#' choices = c("OS", "PFS", "EFS"), |
|
85 |
#' selected = c("OS", "PFS"), |
|
86 |
#' multiple = TRUE, |
|
87 |
#' label = "Choose endpoint" |
|
88 |
#' ) |
|
89 |
#' |
|
90 |
#' # delayed version |
|
91 |
#' filter_spec( |
|
92 |
#' vars = variable_choices("ADSL", "SEX"), |
|
93 |
#' sep = "-", |
|
94 |
#' choices = value_choices("ADSL", "SEX", "SEX"), |
|
95 |
#' selected = "F", |
|
96 |
#' multiple = FALSE, |
|
97 |
#' label = "Choose endpoint and Censor" |
|
98 |
#' ) |
|
99 |
#' # using `choices_selected()` |
|
100 |
#' filter_spec( |
|
101 |
#' vars = choices_selected(variable_choices("ADSL", subset = c("SEX", "AGE")), "SEX", fixed = FALSE), |
|
102 |
#' multiple = TRUE |
|
103 |
#' ) |
|
104 |
#' |
|
105 |
#' filter_spec( |
|
106 |
#' vars = choices_selected(variable_choices("ADSL"), "SEX", fixed = TRUE), |
|
107 |
#' multiple = TRUE |
|
108 |
#' ) |
|
109 |
#' |
|
110 |
#' # choose all choices |
|
111 |
#' adsl_filter <- filter_spec( |
|
112 |
#' vars = choices_selected(variable_choices("ADSL"), "SEX", fixed = FALSE), |
|
113 |
#' choices = value_choices("ADSL", "SEX"), |
|
114 |
#' selected = all_choices() |
|
115 |
#' ) |
|
116 |
filter_spec <- function(vars, |
|
117 |
choices = NULL, |
|
118 |
selected = `if`(inherits(choices, "delayed_data"), NULL, choices[1]), |
|
119 |
multiple = length(selected) > 1 || inherits(selected, "all_choices"), |
|
120 |
label = "Filter by", |
|
121 |
sep = attr(choices, "sep"), |
|
122 |
drop_keys = FALSE) { |
|
123 | 69x |
if (is.null(sep)) sep <- " - " |
124 | 109x |
checkmate::assert( |
125 | 109x |
checkmate::check_character(vars, min.len = 1, any.missing = FALSE), |
126 | 109x |
checkmate::check_class(vars, "delayed_data"), |
127 | 109x |
checkmate::check_class(vars, "choices_selected") |
128 |
) |
|
129 | 106x |
checkmate::assert( |
130 | 106x |
checkmate::check_null(choices), |
131 | 106x |
checkmate::check_character(choices, min.len = 1, any.missing = FALSE), |
132 | 106x |
checkmate::check_numeric(choices, min.len = 1, any.missing = FALSE), |
133 | 106x |
checkmate::check_logical(choices, min.len = 1, any.missing = FALSE), |
134 | 106x |
checkmate::check_class(choices, "delayed_data") |
135 |
) |
|
136 | 104x |
checkmate::assert( |
137 | 104x |
checkmate::check_null(selected), |
138 | 104x |
checkmate::check_character(selected, min.len = 1, any.missing = FALSE), |
139 | 104x |
checkmate::check_numeric(selected, min.len = 1, any.missing = FALSE), |
140 | 104x |
checkmate::check_logical(selected, min.len = 1, any.missing = FALSE), |
141 | 104x |
checkmate::check_class(selected, "delayed_data"), |
142 | 104x |
checkmate::check_class(selected, "all_choices") |
143 |
) |
|
144 | ||
145 | 103x |
checkmate::assert_flag(multiple) |
146 | 102x |
checkmate::assert_string(label, null.ok = TRUE) |
147 | 100x |
checkmate::assert_string(sep) |
148 | 99x |
checkmate::assert_flag(drop_keys) |
149 | 99x |
stopifnot(multiple || !inherits(selected, "all_choices")) |
150 | ||
151 | 1x |
if (inherits(selected, "all_choices") && !is.null(choices)) selected <- choices |
152 | ||
153 | 99x |
if (inherits(vars, "choices_selected")) { |
154 | 8x |
filter_spec_internal( |
155 | 8x |
vars_choices = vars$choices, |
156 | 8x |
vars_selected = vars$selected, |
157 | 8x |
vars_label = if (vars$fixed) NULL else label, |
158 | 8x |
vars_fixed = vars$fixed, |
159 | 8x |
vars_multiple = if (is.null(vars$selected)) FALSE else length(vars$selected) > 1, |
160 | 8x |
choices = choices, |
161 | 8x |
selected = selected, |
162 | 8x |
label = if (vars$fixed) label else NULL, |
163 | 8x |
fixed = FALSE, |
164 | 8x |
multiple = multiple, |
165 | 8x |
sep = sep, |
166 | 8x |
drop_keys = drop_keys |
167 |
) |
|
168 |
} else { |
|
169 | 91x |
filter_spec_internal( |
170 | 91x |
vars_choices = vars, |
171 | 91x |
vars_selected = vars, |
172 | 91x |
vars_label = NULL, |
173 | 91x |
vars_fixed = TRUE, |
174 | 91x |
vars_multiple = TRUE, |
175 | 91x |
choices = choices, |
176 | 91x |
selected = selected, |
177 | 91x |
label = label, |
178 | 91x |
fixed = FALSE, |
179 | 91x |
multiple = multiple, |
180 | 91x |
sep = sep, |
181 | 91x |
drop_keys = drop_keys |
182 |
) |
|
183 |
} |
|
184 |
} |
|
185 | ||
186 | ||
187 |
#' Data extract dynamic filter specification |
|
188 |
#' |
|
189 |
#' This function returns a configuration for the \code{data_extract_ui} module. This function covers |
|
190 |
#' the configuration of filtering datasets (so called `filter_spec`), which then is used to build |
|
191 |
#' the UI element in the `teal` app. |
|
192 |
#' |
|
193 |
#' @inheritParams filter_spec |
|
194 |
#' @param vars_choices (`character` or `delayed_data`) \cr |
|
195 |
#' the vector of dataset column names available to build dynamic filter |
|
196 |
#' \code{delayed_data} objects can be created via \code{\link{variable_choices}}. |
|
197 |
#' @param vars_selected (`NULL` or named `character`) \cr |
|
198 |
#' the selected column name out from `choices`. |
|
199 |
#' @param vars_label (`character`)\cr |
|
200 |
#' the title printed on the UI element generated on the basis of this \code{filter_spec}. |
|
201 |
#' @param vars_fixed (`logical`)\cr |
|
202 |
#' if true allow to change the selected variables in the UI element; otherwise, do not allow. |
|
203 |
#' @param vars_multiple (`logical`)\cr |
|
204 |
#' if true allow to select multiple variables in the UI elements; otherwise, do not allow. |
|
205 |
#' @param fixed (`logical`)\cr |
|
206 |
#' if true allow to change the initially selected values of the variables; otherwise, do not allow. |
|
207 |
#' @param dataname (`character`)\cr |
|
208 |
#' the name of the dataset this filter covers. Set during the initialization of the teal application. |
|
209 |
#' @param initialized (`logical`)\cr |
|
210 |
#' indicates whether this filter was already initialized in the application. |
|
211 |
#' TRUE if this filter was already consumed by the server function; FALSE otherwise. |
|
212 |
#' |
|
213 |
#' @return `filter_spec` or `delayed_filter_spec` S3-class object. |
|
214 |
#' @keywords internal |
|
215 |
#' |
|
216 |
#' @seealso filter_spec |
|
217 |
#' |
|
218 |
#' @examples |
|
219 |
#' teal.transform:::filter_spec_internal( |
|
220 |
#' vars_choices = c("PARAMCD", "AVISIT"), |
|
221 |
#' vars_selected = "PARAMCD", |
|
222 |
#' vars_multiple = TRUE |
|
223 |
#' ) |
|
224 |
#' |
|
225 |
#' ADRS <- teal.transform::rADRS |
|
226 |
#' teal.transform:::filter_spec_internal( |
|
227 |
#' vars_choices = variable_choices(ADRS), |
|
228 |
#' vars_selected = "PARAMCD", |
|
229 |
#' vars_multiple = TRUE |
|
230 |
#' ) |
|
231 |
#' |
|
232 |
#' teal.transform:::filter_spec_internal( |
|
233 |
#' vars_choices = variable_choices("ADRS"), |
|
234 |
#' vars_selected = "PARAMCD", |
|
235 |
#' vars_multiple = TRUE |
|
236 |
#' ) |
|
237 |
filter_spec_internal <- function(vars_choices, |
|
238 |
vars_selected = NULL, |
|
239 |
vars_label = NULL, |
|
240 |
vars_fixed = FALSE, |
|
241 |
vars_multiple = TRUE, |
|
242 |
choices = NULL, |
|
243 |
selected = NULL, |
|
244 |
label = NULL, |
|
245 |
fixed = FALSE, |
|
246 |
multiple = TRUE, |
|
247 |
sep = attr(vars_choices, "sep"), |
|
248 |
drop_keys = FALSE, |
|
249 |
dataname = NULL, |
|
250 |
initialized = FALSE) { |
|
251 | 12x |
if (is.null(sep)) sep <- " - " |
252 | 149x |
checkmate::assert_string(vars_label, null.ok = TRUE) |
253 | 149x |
checkmate::assert_flag(vars_fixed) |
254 | 149x |
checkmate::assert_flag(vars_multiple) |
255 | 149x |
checkmate::assert_string(label, null.ok = TRUE) |
256 | 149x |
checkmate::assert_flag(fixed) |
257 | 149x |
checkmate::assert_flag(multiple) |
258 | 149x |
checkmate::assert_string(sep) |
259 | 149x |
checkmate::assert_flag(drop_keys) |
260 | ||
261 | 149x |
if (inherits(vars_choices, "delayed_data") || |
262 | 149x |
inherits(vars_selected, "delayed_data") || |
263 | 149x |
inherits(choices, "delayed_data") || |
264 | 149x |
inherits(selected, "delayed_data")) { |
265 | 39x |
filter_spec_internal.delayed_data( |
266 | 39x |
vars_choices = vars_choices, |
267 | 39x |
vars_selected = vars_selected, |
268 | 39x |
vars_label = vars_label, |
269 | 39x |
vars_fixed = vars_fixed, |
270 | 39x |
vars_multiple = vars_multiple, |
271 | 39x |
choices = choices, |
272 | 39x |
selected = selected, |
273 | 39x |
label = label, |
274 | 39x |
multiple = multiple, |
275 | 39x |
fixed = fixed, |
276 | 39x |
sep = sep, |
277 | 39x |
drop_keys = drop_keys, |
278 | 39x |
dataname = dataname, |
279 | 39x |
initialized = initialized |
280 |
) |
|
281 |
} else { |
|
282 | 110x |
UseMethod("filter_spec_internal") |
283 |
} |
|
284 |
} |
|
285 | ||
286 |
#' @rdname filter_spec_internal |
|
287 |
#' @export |
|
288 |
filter_spec_internal.delayed_data <- function(vars_choices, # nolint |
|
289 |
vars_selected = NULL, |
|
290 |
vars_label = NULL, |
|
291 |
vars_fixed = FALSE, |
|
292 |
vars_multiple = TRUE, |
|
293 |
choices = NULL, |
|
294 |
selected = NULL, |
|
295 |
label = NULL, |
|
296 |
fixed = FALSE, |
|
297 |
multiple = TRUE, |
|
298 |
sep = attr(vars_choices, "sep"), |
|
299 |
drop_keys = FALSE, |
|
300 |
dataname = NULL, |
|
301 |
initialized = FALSE) { |
|
302 | ! |
if (is.null(sep)) sep <- " - " |
303 | 39x |
checkmate::assert( |
304 | 39x |
checkmate::check_character(vars_choices, min.len = 1, any.missing = FALSE), |
305 | 39x |
checkmate::check_numeric(vars_choices, min.len = 1, any.missing = FALSE), |
306 | 39x |
checkmate::check_logical(vars_choices, min.len = 1, any.missing = FALSE), |
307 | 39x |
checkmate::check_class(vars_choices, "delayed_data") |
308 |
) |
|
309 | ||
310 | 39x |
checkmate::assert( |
311 | 39x |
checkmate::check_null(vars_selected), |
312 | 39x |
checkmate::check_character(vars_selected, min.len = 1, any.missing = FALSE), |
313 | 39x |
checkmate::check_numeric(vars_selected, min.len = 1, any.missing = FALSE), |
314 | 39x |
checkmate::check_logical(vars_selected, min.len = 1, any.missing = FALSE), |
315 | 39x |
checkmate::check_class(vars_selected, "delayed_data") |
316 |
) |
|
317 | ||
318 | 39x |
checkmate::assert( |
319 | 39x |
checkmate::check_null(choices), |
320 | 39x |
checkmate::check_character(choices, min.len = 1, any.missing = FALSE), |
321 | 39x |
checkmate::check_numeric(choices, min.len = 1, any.missing = FALSE), |
322 | 39x |
checkmate::check_logical(choices, min.len = 1, any.missing = FALSE), |
323 | 39x |
checkmate::check_class(choices, "delayed_data") |
324 |
) |
|
325 | ||
326 | 39x |
checkmate::assert( |
327 | 39x |
checkmate::check_null(selected), |
328 | 39x |
checkmate::check_character(selected, min.len = 1, any.missing = FALSE), |
329 | 39x |
checkmate::check_numeric(selected, min.len = 1, any.missing = FALSE), |
330 | 39x |
checkmate::check_logical(selected, min.len = 1, any.missing = FALSE), |
331 | 39x |
checkmate::check_class(selected, "delayed_data"), |
332 | 39x |
checkmate::check_class(selected, "all_choices") |
333 |
) |
|
334 | ||
335 | 39x |
out <- structure( |
336 | 39x |
list( |
337 | 39x |
vars_choices = vars_choices, |
338 | 39x |
vars_selected = vars_selected, |
339 | 39x |
vars_label = vars_label, |
340 | 39x |
vars_fixed = vars_fixed, |
341 | 39x |
vars_multiple = vars_multiple, |
342 | 39x |
choices = choices, |
343 | 39x |
selected = selected, |
344 | 39x |
label = label, |
345 | 39x |
multiple = multiple, |
346 | 39x |
fixed = fixed, |
347 | 39x |
sep = sep, |
348 | 39x |
drop_keys = drop_keys, |
349 | 39x |
dataname = dataname, # modified by data_extract_spec, |
350 | 39x |
initialized = initialized |
351 |
), |
|
352 | 39x |
class = c( |
353 | 39x |
"delayed_filter_spec", |
354 | 39x |
"filter_spec", |
355 | 39x |
"delayed_data" |
356 |
) |
|
357 |
) |
|
358 | 39x |
return(out) |
359 |
} |
|
360 | ||
361 |
#' @rdname filter_spec_internal |
|
362 |
#' @export |
|
363 |
filter_spec_internal.default <- function(vars_choices, |
|
364 |
vars_selected = NULL, |
|
365 |
vars_label = NULL, |
|
366 |
vars_fixed = FALSE, |
|
367 |
vars_multiple = TRUE, |
|
368 |
choices = NULL, |
|
369 |
selected = NULL, |
|
370 |
label = NULL, |
|
371 |
fixed = FALSE, |
|
372 |
multiple = TRUE, |
|
373 |
sep = attr(vars_choices, "sep"), |
|
374 |
drop_keys = FALSE, |
|
375 |
dataname = NULL, |
|
376 |
initialized = FALSE) { |
|
377 | 7x |
if (is.null(sep)) sep <- " - " |
378 | 110x |
checkmate::assert( |
379 | 110x |
checkmate::check_character(vars_choices, min.len = 1, any.missing = FALSE), |
380 | 110x |
checkmate::check_numeric(vars_choices, min.len = 1, any.missing = FALSE), |
381 | 110x |
checkmate::check_logical(vars_choices, min.len = 1, any.missing = FALSE) |
382 |
) |
|
383 | 110x |
stopifnot(all(!duplicated(vars_choices))) |
384 | ||
385 | 110x |
if (!is.null(vars_selected)) { |
386 | 109x |
stopifnot(vars_multiple || length(vars_selected) == 1) |
387 | 109x |
checkmate::assert( |
388 | 109x |
checkmate::check_character(vars_selected, min.len = 1, any.missing = FALSE), |
389 | 109x |
checkmate::check_numeric(vars_selected, min.len = 1, any.missing = FALSE), |
390 | 109x |
checkmate::check_logical(vars_selected, min.len = 1, any.missing = FALSE) |
391 |
) |
|
392 | 109x |
stopifnot(all(!duplicated(vars_selected))) |
393 | 109x |
stopifnot(all(vars_selected %in% vars_choices)) |
394 |
} |
|
395 | ||
396 | 110x |
if (!is.null(choices)) { |
397 | 94x |
stopifnot(all(!duplicated(choices))) |
398 | 93x |
split_choices <- split_by_sep(choices, sep) |
399 | 93x |
stopifnot(all(vapply(split_choices, length, integer(1)) == length(vars_selected))) |
400 |
} |
|
401 | ||
402 | 106x |
if (!is.null(selected) && !inherits(selected, "all_choices")) { |
403 | 90x |
stopifnot(multiple || length(selected) == 1) |
404 | 89x |
checkmate::assert( |
405 | 89x |
checkmate::check_character(selected, min.len = 1, any.missing = FALSE), |
406 | 89x |
checkmate::check_numeric(selected, min.len = 1, any.missing = FALSE), |
407 | 89x |
checkmate::check_logical(selected, min.len = 1, any.missing = FALSE) |
408 |
) |
|
409 | 89x |
stopifnot(all(!duplicated(selected))) |
410 | 89x |
stopifnot(all(selected %in% choices)) |
411 |
} |
|
412 | ||
413 | 105x |
res <- list( |
414 | 105x |
vars_choices = vars_choices, |
415 | 105x |
vars_selected = vars_selected, |
416 | 105x |
vars_label = vars_label, |
417 | 105x |
vars_fixed = vars_fixed, |
418 | 105x |
vars_multiple = vars_multiple, |
419 | 105x |
choices = choices, |
420 | 105x |
selected = selected, |
421 | 105x |
label = label, |
422 | 105x |
multiple = multiple, |
423 | 105x |
fixed = fixed, |
424 | 105x |
sep = sep, |
425 | 105x |
drop_keys = drop_keys, |
426 | 105x |
dataname = dataname, # modified by data_extract_spec |
427 | 105x |
initialized = initialized |
428 |
) |
|
429 | 105x |
class(res) <- "filter_spec" |
430 | ||
431 | 105x |
return(res) |
432 |
} |
1 |
#' Get merge call from a list of selectors |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Returns list of calls depending on selector(s) and type of the merge |
|
5 |
#' Order of merge is the same as in selectors passed to the function. |
|
6 |
#' @inheritParams merge_datasets |
|
7 |
#' @param join_keys (`JoinKeys`) nested list of keys used for joining |
|
8 |
#' @param dplyr_call_data (`list`) simplified selectors with aggregated set of filters, |
|
9 |
#' |
|
10 |
#' @return (`list` with `call` elements) |
|
11 |
#' |
|
12 |
#' @export |
|
13 |
get_merge_call <- function(selector_list, |
|
14 |
join_keys = teal.data::join_keys(), |
|
15 |
dplyr_call_data = get_dplyr_call_data(selector_list, join_keys = join_keys), |
|
16 |
merge_function = "dplyr::full_join", |
|
17 |
anl_name = "ANL") { |
|
18 | 68x |
if (!missing(selector_list)) { |
19 | 68x |
checkmate::assert_list(selector_list, min.len = 1) |
20 | 68x |
lapply(selector_list, check_selector) |
21 | 68x |
logger::log_trace( |
22 | 68x |
paste( |
23 | 68x |
"get_merge_call called with: { paste(names(selector_list), collapse = ', ') } selectors;", |
24 | 68x |
"{ merge_function } merge function." |
25 |
) |
|
26 |
) |
|
27 |
} else { |
|
28 | ! |
logger::log_trace( |
29 | ! |
paste( |
30 | ! |
"get_merge_call called with:", |
31 | ! |
"{ paste(sapply(dplyr_call_data, `[[`, 'internal_id'), collapse = ', ') } selectors;", |
32 | ! |
"{ merge_function } merge function." |
33 |
) |
|
34 |
) |
|
35 |
} |
|
36 | ||
37 | 68x |
checkmate::assert_string(anl_name) |
38 | 68x |
stopifnot(attr(regexec("[A-Za-z0-9\\_]*", anl_name)[[1]], "match.length") == nchar(anl_name)) |
39 | 68x |
check_merge_function(merge_function) |
40 | ||
41 | ||
42 | 66x |
n_selectors <- if (!missing(selector_list)) { |
43 | 66x |
length(selector_list) |
44 |
} else { |
|
45 | ! |
length(dplyr_call_data) |
46 |
} |
|
47 | ||
48 | 66x |
anl_merge_calls <- list( |
49 | 66x |
call("<-", as.name(anl_name), as.name(paste0(anl_name, "_", 1))) |
50 |
) |
|
51 | ||
52 | 66x |
for (idx in seq_len(n_selectors)[-1]) { |
53 | 59x |
anl_merge_call_i <- call( |
54 |
"<-", |
|
55 | 59x |
as.name(anl_name), |
56 | 59x |
{ # nolint |
57 | 59x |
merge_key_i <- get_merge_key_i(idx = idx, dplyr_call_data = dplyr_call_data) |
58 | 59x |
is_merge_key_pair <- vapply(merge_key_i, function(x) length(names(x)) == 1, logical(1)) |
59 | ||
60 | 59x |
join_call <- as.call( |
61 | 59x |
c( |
62 | 59x |
rlang::parse_expr(merge_function), |
63 | 59x |
list( |
64 | 59x |
as.name(anl_name), |
65 | 59x |
as.name(paste0(anl_name, "_", idx)) |
66 |
), |
|
67 | 59x |
if (!rlang::is_empty(merge_key_i)) { |
68 | 59x |
list( |
69 | 59x |
by = parse_merge_key_i(merge_key = merge_key_i) |
70 |
) |
|
71 |
} |
|
72 |
) |
|
73 |
) |
|
74 | ||
75 |
# mutate call to get second key if any pair key |
|
76 |
# e.g. full_join(dt1, dt2, by = c("key1" = "key2")) %>% mutate(key2 = key1) |
|
77 |
# it's because dplyr joins preserve only key from LHS data |
|
78 | 59x |
mutate_call <- if (any(is_merge_key_pair)) { |
79 | 1x |
merge_key_pairs <- merge_key_i[is_merge_key_pair] |
80 |
# drop duplicates ignoring names |
|
81 | 1x |
idx <- vapply(unique(unlist(merge_key_pairs)), function(x1) { |
82 | 2x |
which.min(vapply(merge_key_pairs, function(x2) x2 == x1, logical(1))) |
83 | 1x |
}, integer(1)) |
84 | ||
85 | 1x |
merge_key_pairs <- merge_key_pairs[idx] |
86 | 1x |
as.call( |
87 | 1x |
append( |
88 | 1x |
quote(dplyr::mutate), |
89 | 1x |
stats::setNames( |
90 | 1x |
lapply(merge_key_pairs, function(x) as.name(names(x))), |
91 | 1x |
merge_key_pairs |
92 |
) |
|
93 |
) |
|
94 |
) |
|
95 |
} else { |
|
96 | 58x |
NULL |
97 |
} |
|
98 | ||
99 | 59x |
Reduce( |
100 | 59x |
function(x, y) call("%>%", x, y), |
101 | 59x |
c(join_call, mutate_call) |
102 |
) |
|
103 |
} |
|
104 |
) |
|
105 | ||
106 | 59x |
anl_merge_calls <- append( |
107 | 59x |
anl_merge_calls, |
108 | 59x |
anl_merge_call_i |
109 |
) |
|
110 |
} |
|
111 | 66x |
return(anl_merge_calls) |
112 |
} |
|
113 | ||
114 |
#' Gets keys list from keys list |
|
115 |
#' |
|
116 |
#' @inheritParams get_merge_call |
|
117 |
#' @return list of key pairs between all datasets |
|
118 |
#' @keywords internal |
|
119 |
get_merge_key_grid <- function(selector_list, join_keys = teal.data::join_keys()) { |
|
120 | 163x |
logger::log_trace( |
121 | 163x |
"get_merge_key_grid called with: { paste(names(selector_list), collapse = ', ') } selectors." |
122 |
) |
|
123 | ||
124 | 163x |
lapply( |
125 | 163x |
selector_list, |
126 | 163x |
function(selector_from) { |
127 | 361x |
lapply( |
128 | 361x |
selector_list, |
129 | 361x |
function(selector_to) { |
130 | 911x |
get_merge_key_pair( |
131 | 911x |
selector_from, |
132 | 911x |
selector_to, |
133 | 911x |
join_keys$get(selector_from$dataname, selector_to$dataname) |
134 |
) |
|
135 |
} |
|
136 |
) |
|
137 |
} |
|
138 |
) |
|
139 |
} |
|
140 | ||
141 | ||
142 |
#' Gets keys vector from keys list |
|
143 |
#' |
|
144 |
#' @param selector_from (`list`) `data_extract_srv` |
|
145 |
#' @param selector_to (`list`) `data_extract_srv` |
|
146 |
#' @param key_from (`character`) keys used in the first selector while joining |
|
147 |
#' |
|
148 |
#' @details This function covers up to now 4 cases |
|
149 |
#' \itemize{ |
|
150 |
#' \item{dataset without parent }{ Primary keys are returned} |
|
151 |
#' \item{dataset source = dataset target}{ |
|
152 |
#' The primary keys subtracted of all key columns that |
|
153 |
#' get purely filtered. This means just one value would |
|
154 |
#' be left after filtering inside this column. Then it |
|
155 |
#' can be taken out. |
|
156 |
#' } |
|
157 |
#' \item{target `dataname` is parent }{ foreign keys} |
|
158 |
#' \item{any other case }{foreign keys} |
|
159 |
#' } |
|
160 |
#' |
|
161 |
#' @return (`character`) |
|
162 |
#' @keywords internal |
|
163 |
get_merge_key_pair <- function(selector_from, selector_to, key_from) { |
|
164 | 927x |
logger::log_trace( |
165 | 927x |
paste( |
166 | 927x |
"get_merge_key_pair called with:", |
167 | 927x |
"{ paste(selector_from$internal_id, selector_to$internal_id, sep = ', ') } selectors;", |
168 | 927x |
"{ paste(key_from, collapse = ', ') } keys." |
169 |
) |
|
170 |
) |
|
171 | 927x |
check_selector(selector_from) |
172 | 927x |
check_selector(selector_to) |
173 | 927x |
checkmate::test_character(key_from, min.len = 0, any.missing = FALSE) |
174 | ||
175 | 927x |
res <- if (identical(selector_from$dataname, selector_to$dataname)) { |
176 |
# key is dropped if reshape or if filtered out (only one level selected) |
|
177 | 627x |
keys_dropped <- if (isTRUE(selector_from$reshape)) { |
178 | 167x |
get_reshape_unite_col(selector_from) |
179 |
} else { |
|
180 | 460x |
get_dropped_filters(selector_from) |
181 |
} |
|
182 | 627x |
res <- setdiff( |
183 | 627x |
key_from, |
184 | 627x |
keys_dropped |
185 |
) |
|
186 | 430x |
if (!rlang::is_empty(res)) res <- rlang::set_names(res) |
187 | 627x |
res |
188 |
} else { |
|
189 | 300x |
key_from |
190 |
} |
|
191 | 927x |
logger::log_trace("get_merge_key_pair returns { paste(res, collapse = ', ') } merge keys.") |
192 | 927x |
res |
193 |
} |
|
194 | ||
195 |
#' Gets keys needed for join call of two selectors |
|
196 |
#' |
|
197 |
#' @inheritParams get_merge_call |
|
198 |
#' @param idx optional (`integer`) current selector index in all selectors list |
|
199 |
#' |
|
200 |
#' @return (`call`) |
|
201 |
#' @keywords internal |
|
202 |
get_merge_key_i <- function(selector_list, idx, dplyr_call_data = get_dplyr_call_data(selector_list)) { |
|
203 | 59x |
checkmate::assert_integer(idx, len = 1, any.missing = FALSE, lower = 2L) |
204 | ||
205 | 59x |
if (!missing(selector_list)) { |
206 | ! |
checkmate::assert_list(selector_list, min.len = 1) |
207 | ! |
lapply(selector_list, check_selector) |
208 | ||
209 | ! |
logger::log_trace( |
210 | ! |
paste( |
211 | ! |
"get_merge_key_i called with:", |
212 | ! |
"{ paste(names(selector_list), collapse = ', ') } selectors;", |
213 | ! |
"idx = { idx }." |
214 |
) |
|
215 |
) |
|
216 |
} else { |
|
217 | 59x |
logger::log_trace( |
218 | 59x |
paste( |
219 | 59x |
"get_merge_key_i called with", |
220 | 59x |
"{ paste(sapply(dplyr_call_data, `[[`, 'internal_id'), collapse = ', ') } selectors;", |
221 | 59x |
"idx = { idx }." |
222 |
) |
|
223 |
) |
|
224 |
} |
|
225 | ||
226 | 59x |
merge_keys_list <- lapply(dplyr_call_data, `[[`, "merge_keys_list") |
227 | ||
228 |
# keys x - get from all selectors up to the current one |
|
229 | 59x |
keys_x <- lapply(merge_keys_list[seq_len(idx - 1)], `[[`, idx) |
230 | ||
231 |
# keys y - get from the current selector |
|
232 | 59x |
keys_y <- merge_keys_list[[idx]][seq_len(idx - 1)] |
233 | ||
234 | 59x |
keys_map <- lapply( |
235 | 59x |
seq_len(idx - 1), |
236 | 59x |
function(idx2) { |
237 | 76x |
keys_x_idx2 <- keys_x[[idx2]] |
238 | 76x |
keys_y_idx2 <- keys_y[[idx2]] |
239 | 76x |
min_length <- min(length(keys_x_idx2), length(keys_y_idx2)) |
240 | ||
241 |
# In case the keys might be wrongly sorted, sort them |
|
242 | 76x |
if (!identical(keys_x_idx2[seq_len(min_length)], keys_y_idx2[seq_len(min_length)])) { |
243 | 2x |
keys_x_idx2 <- c( |
244 | 2x |
intersect(keys_x_idx2, keys_y_idx2), |
245 | 2x |
setdiff(keys_x_idx2, keys_y_idx2) |
246 |
) |
|
247 | ||
248 | 2x |
keys_y_idx2 <- c( |
249 | 2x |
intersect(keys_y_idx2, keys_x_idx2), |
250 | 2x |
setdiff(keys_y_idx2, keys_x_idx2) |
251 |
) |
|
252 |
} |
|
253 |
# cut keys case of different length |
|
254 | 76x |
keys_x_idx2 <- keys_x_idx2[seq_len(min_length)] |
255 | 76x |
keys_y_idx2 <- keys_y_idx2[seq_len(min_length)] |
256 | ||
257 | 76x |
mapply( |
258 | 76x |
function(x, y) { |
259 | 149x |
if (identical(x, y)) { |
260 | 147x |
x |
261 |
} else { |
|
262 | 2x |
stats::setNames(nm = y, x) |
263 |
} |
|
264 |
}, |
|
265 | 76x |
keys_x_idx2, |
266 | 76x |
keys_y_idx2, |
267 | 76x |
SIMPLIFY = FALSE, |
268 | 76x |
USE.NAMES = FALSE |
269 |
) |
|
270 |
} |
|
271 |
) |
|
272 | ||
273 | 59x |
keys_map <- if (length(keys_map) > 1) { |
274 | 16x |
Reduce(append, keys_map) |
275 |
} else { |
|
276 | 43x |
keys_map[[1]] |
277 |
} |
|
278 | ||
279 | 59x |
keys_map <- unique(keys_map) |
280 | 59x |
logger::log_trace("get_merge_key_i returns { paste(keys_map, collapse = ' ') } unique keys.") |
281 | 59x |
keys_map |
282 |
} |
|
283 | ||
284 |
#' Parses merge keys |
|
285 |
#' @inheritParams get_merge_call |
|
286 |
#' @param merge_key keys obtained from `get_merge_key_i` |
|
287 |
#' @param idx optional (`integer`) current selector index in all selectors list |
|
288 |
#' @keywords internal |
|
289 |
parse_merge_key_i <- function(selector_list, |
|
290 |
idx, |
|
291 |
dplyr_call_data = get_dplyr_call_data(selector_list), |
|
292 |
merge_key = get_merge_key_i(selector_list, idx, dplyr_call_data)) { |
|
293 | 59x |
logger::log_trace("parse_merge_key_i called with { paste(merge_key, collapse = ' ') } keys.") |
294 | 59x |
as.call( |
295 | 59x |
append( |
296 | 59x |
quote(c), |
297 | 59x |
unlist(merge_key) |
298 |
) |
|
299 |
) |
|
300 |
} |
|
301 | ||
302 |
#' Names of filtered-out filters dropped from selection |
|
303 |
#' |
|
304 |
#' @details Names of filtered-out filters dropped from automatic selection |
|
305 |
#' (key vars are automatically included in select). |
|
306 |
#' Dropped filter is filter which became not unique for all observations. |
|
307 |
#' This means that if variable is filtered to just one level, |
|
308 |
#' it's not a key anymore. Other variables used in filter should also be dropped from automatic |
|
309 |
#' selection, unless they have been selected. |
|
310 |
#' @inheritParams get_pivot_longer_col |
|
311 |
#' @return names `character` of the filters which should be dropped from select call |
|
312 |
#' @keywords internal |
|
313 |
get_dropped_filters <- function(selector) { |
|
314 | 460x |
logger::log_trace("get_dropped_filters called with { selector$internal_id } selector.") |
315 | 460x |
unlist( |
316 | 460x |
lapply(selector$filters, function(x) { |
317 | 522x |
if (isFALSE(x$drop_keys)) { |
318 | 19x |
NULL |
319 | 503x |
} else if (length(x$columns) > 1) { |
320 |
# concatenated filters |
|
321 | 61x |
single_selection <- sapply(seq_along(x$columns), function(i) length(unique(sapply(x$selected, `[[`, i))) == 1) |
322 | 61x |
x$columns[single_selection] |
323 |
} else { |
|
324 |
# one filter in one input |
|
325 | 294x |
if (isFALSE(x$multiple) || length(x$selected) == 1) x$columns |
326 |
} |
|
327 |
}) |
|
328 |
) |
|
329 |
} |
|
330 | ||
331 | ||
332 |
#' Gets the relabel call |
|
333 |
#' |
|
334 |
#' @description `r lifecycle::badge("stable")` |
|
335 |
#' @inheritParams merge_datasets |
|
336 |
#' @param columns_source \code{named list}\cr |
|
337 |
#' where names are column names, values are labels + additional attribute `dataname` |
|
338 |
#' |
|
339 |
#' @return (`call`) to relabel `dataset` and assign to `anl_name`. |
|
340 |
#' |
|
341 |
#' @export |
|
342 |
get_anl_relabel_call <- function(columns_source, datasets, anl_name = "ANL") { |
|
343 | 6x |
logger::log_trace( |
344 | 6x |
paste( |
345 | 6x |
"get_anl_relabel_call called with:", |
346 | 6x |
"{ paste(names(columns_source), collapse = ', ') } columns_source;", |
347 | 6x |
"{ anl_name } merged dataset." |
348 |
) |
|
349 |
) |
|
350 | 6x |
checkmate::assert_string(anl_name) |
351 | 6x |
stopifnot(attr(regexec("[A-Za-z0-9\\_]*", anl_name)[[1]], "match.length") == nchar(anl_name)) |
352 | 6x |
labels_vector <- Reduce( |
353 | 6x |
function(x, y) append(x, y), |
354 | 6x |
lapply( |
355 | 6x |
columns_source, |
356 | 6x |
function(selector) { |
357 | 10x |
column_names <- names(selector) |
358 | 10x |
if (rlang::is_empty(column_names)) { |
359 | 2x |
return(NULL) |
360 |
} |
|
361 | ||
362 | 8x |
data_used <- datasets[[attr(selector, "dataname")]] |
363 | 8x |
labels <- teal.data::col_labels(data_used(), fill = FALSE) |
364 | 8x |
column_labels <- labels[intersect(colnames(data_used()), column_names)] |
365 | ||
366 |
# NULL for no labels at all, character(0) for no labels for a given columns |
|
367 | 8x |
return( |
368 | 8x |
if (rlang::is_empty(column_labels)) { |
369 | ! |
column_labels |
370 |
} else { |
|
371 | 8x |
stats::setNames( |
372 | 8x |
column_labels, |
373 | 8x |
selector[names(column_labels)] |
374 |
) |
|
375 |
} |
|
376 |
) |
|
377 |
} |
|
378 |
) |
|
379 |
) |
|
380 | ||
381 | 6x |
if (length(labels_vector) == 0 || all(is.na(labels_vector))) { |
382 | 6x |
return(NULL) |
383 |
} |
|
384 | ||
385 | ! |
relabel_call <- call( |
386 |
"%>%", |
|
387 | ! |
as.name(anl_name), |
388 | ! |
get_relabel_call(labels_vector) |
389 |
) |
|
390 | ||
391 | ! |
relabel_and_assign_call <- call( |
392 |
"<-", |
|
393 | ! |
as.name(anl_name), |
394 | ! |
relabel_call |
395 |
) |
|
396 | ||
397 | ! |
return(relabel_and_assign_call) |
398 |
} |
|
399 | ||
400 |
#' Create relabel call from named character |
|
401 |
#' |
|
402 |
#' @description `r lifecycle::badge("stable")` |
|
403 |
#' Function creates relabel call from named character. |
|
404 |
#' @param labels (`named character`)\cr |
|
405 |
#' where name is name is function argument name and value is a function argument value. |
|
406 |
#' |
|
407 |
#' @return (`call`) object with relabel step |
|
408 |
#' @examples |
|
409 |
#' get_relabel_call( |
|
410 |
#' labels = c( |
|
411 |
#' x = as.name("ANL"), |
|
412 |
#' AGE = "Age", |
|
413 |
#' AVAL = "Continuous variable" |
|
414 |
#' ) |
|
415 |
#' ) |
|
416 |
#' |
|
417 |
#' get_relabel_call( |
|
418 |
#' labels = c( |
|
419 |
#' AGE = "Age", |
|
420 |
#' AVAL = "Continuous variable" |
|
421 |
#' ) |
|
422 |
#' ) |
|
423 |
#' @export |
|
424 |
get_relabel_call <- function(labels) { |
|
425 | 3x |
logger::log_trace("get_relabel_call called with: { paste(labels, collapse = ' ' ) } labels.") |
426 | 3x |
if (length(stats::na.omit(labels)) == 0 || is.null(names(labels))) { |
427 | 2x |
return(NULL) |
428 |
} |
|
429 | 1x |
labels <- labels[!duplicated(names(labels))] |
430 | 1x |
labels <- labels[!is.na(labels)] |
431 | ||
432 | 1x |
return( |
433 | 1x |
as.call( |
434 | 1x |
append( |
435 | 1x |
quote(teal.data::col_relabel), |
436 | 1x |
labels |
437 |
) |
|
438 |
) |
|
439 |
) |
|
440 |
} |
|
441 | ||
442 |
#' Get columns to relabel |
|
443 |
#' |
|
444 |
#' Get columns to relabel excluding these which has been reshaped (pivot_wider) |
|
445 |
#' @param columns_source `list` |
|
446 |
#' @param dplyr_call_data `list` |
|
447 |
#' @return columns_source `list` without columns which has been reshaped |
|
448 |
#' @keywords internal |
|
449 |
get_relabel_cols <- function(columns_source, dplyr_call_data) { |
|
450 | 6x |
logger::log_trace( |
451 | 6x |
"get_relabel_cols called with: { paste(names(columns_source), collapse = ', ') } columns_source." |
452 |
) |
|
453 | 6x |
pivot_longer_cols <- unlist(unname(lapply(dplyr_call_data, function(x) x[["pivot_longer_cols_renamed"]]))) |
454 | 6x |
lapply( |
455 | 6x |
columns_source, |
456 | 6x |
function(column_source) { |
457 | 10x |
dataname <- attr(column_source, "dataname") |
458 | 10x |
column_source <- column_source[!names(column_source) %in% pivot_longer_cols] |
459 | 10x |
if (length(column_source) == 0) { |
460 | 2x |
return(NULL) |
461 |
} |
|
462 | 8x |
attr(column_source, "dataname") <- dataname |
463 | 8x |
return(column_source) |
464 |
} |
|
465 |
) |
|
466 |
} |
1 |
#' @title Formatting data extracts |
|
2 |
#' @description Returns a human-readable string representation of an extracted `data_extract_spec` object. |
|
3 |
#' |
|
4 |
#' @details |
|
5 |
#' This function formats the output of [`data_extract_srv`]. See the example for more information. |
|
6 |
#' |
|
7 |
#' @param data_extract `list` the list output of `data_extract_srv` |
|
8 |
#' @return `character(1)` the string representation |
|
9 |
#' @examples |
|
10 |
#' simple_des <- data_extract_spec( |
|
11 |
#' dataname = "iris", |
|
12 |
#' filter = filter_spec(vars = "Petal.Length", choices = c("1.4", "1.5")), |
|
13 |
#' select = select_spec(choices = c("Petal.Length", "Species")) |
|
14 |
#' ) |
|
15 |
#' |
|
16 |
#' sample_filtered_data <- { |
|
17 |
#' teal.slice::init_filtered_data( |
|
18 |
#' list(iris = list(dataset = iris)) |
|
19 |
#' ) |
|
20 |
#' } |
|
21 |
#' |
|
22 |
#' if (interactive()) { |
|
23 |
#' shiny::shinyApp( |
|
24 |
#' ui = shiny::fluidPage( |
|
25 |
#' data_extract_ui( |
|
26 |
#' id = "extract", |
|
27 |
#' label = "data extract ui", |
|
28 |
#' data_extract_spec = simple_des, |
|
29 |
#' is_single_dataset = TRUE |
|
30 |
#' ), |
|
31 |
#' shiny::verbatimTextOutput("formatted_extract") |
|
32 |
#' ), |
|
33 |
#' server = function(input, output, session) { |
|
34 |
#' extracted_input <- data_extract_srv( |
|
35 |
#' id = "extract", |
|
36 |
#' datasets = sample_filtered_data, |
|
37 |
#' data_extract_spec = simple_des |
|
38 |
#' ) |
|
39 |
#' output$formatted_extract <- shiny::renderPrint({ |
|
40 |
#' cat(format_data_extract(extracted_input())) |
|
41 |
#' }) |
|
42 |
#' } |
|
43 |
#' ) |
|
44 |
#' } |
|
45 |
#' @export |
|
46 |
#' |
|
47 |
format_data_extract <- function(data_extract) { |
|
48 | 19x |
if (is.null(data_extract)) { |
49 | ! |
return(NULL) |
50 |
} |
|
51 | ||
52 | 19x |
checkmate::assert_list(data_extract) |
53 | 19x |
required_names <- c("select", "filters", "dataname") |
54 | 19x |
if (!checkmate::test_subset(required_names, choices = names(data_extract))) { |
55 | 1x |
stop(sprintf("data_extract must be a named list with names: %s", paste0(required_names, collapse = " "))) |
56 |
} |
|
57 | ||
58 | 18x |
out <- sprintf("<Data Extract for dataset: %s>", data_extract$dataname) |
59 | 18x |
out <- c(out, "Filters:") |
60 | 18x |
for (filter in data_extract$filters) { |
61 | 12x |
filtering_columns <- paste0(filter$columns, collapse = " ") |
62 | 12x |
selected_values <- paste0(filter$selected, collapse = " ") |
63 | 12x |
out <- c(out, sprintf(" Columns: %s Selected: %s", filtering_columns, selected_values)) |
64 |
} |
|
65 | ||
66 | 18x |
out <- c(out, "Selected columns:") |
67 | 18x |
selected_columns <- paste0(data_extract$select, collapse = " ") |
68 | 18x |
out <- c(out, sprintf(" %s", selected_columns)) |
69 | ||
70 | 18x |
paste0(out, collapse = "\n") |
71 |
} |
1 |
# also returns a list if only a single element |
|
2 |
#' Split by separator |
|
3 |
#' |
|
4 |
#' @description `r lifecycle::badge("stable")` |
|
5 |
#' |
|
6 |
#' @param x (`character`) Character (single) |
|
7 |
#' @param sep (`character`) Separator |
|
8 |
#' @export |
|
9 |
split_by_sep <- function(x, sep) { |
|
10 | 103x |
stopifnot(is.atomic(x)) |
11 | 103x |
if (is.character(x)) { |
12 | 96x |
strsplit(x, sep, fixed = TRUE) |
13 |
} else { |
|
14 | 7x |
x |
15 |
} |
|
16 |
} |
|
17 | ||
18 |
#' Extract labels from choices basing on attributes and names |
|
19 |
#' |
|
20 |
#' @param choices (`list` or `vector`) select choices |
|
21 |
#' @param values optional, choices subset for which labels should be extracted, `NULL` for all choices |
|
22 |
#' |
|
23 |
#' @return (`character`) vector with labels |
|
24 |
#' @keywords internal |
|
25 |
extract_choices_labels <- function(choices, values = NULL) { |
|
26 | ! |
res <- if (inherits(choices, "choices_labeled")) { |
27 | ! |
attr(choices, "raw_labels") |
28 | ! |
} else if (!is.null(names(choices)) && !setequal(names(choices), unlist(unname(choices)))) { |
29 | ! |
names(choices) |
30 |
} else { |
|
31 | ! |
NULL |
32 |
} |
|
33 | ||
34 | ! |
if (!is.null(values) && !is.null(res)) { |
35 | ! |
stopifnot(all(values %in% choices)) |
36 | ! |
res <- res[vapply(values, function(val) which(val == choices), numeric(1))] |
37 |
} |
|
38 | ||
39 | ! |
return(res) |
40 |
} |
|
41 | ||
42 | ||
43 |
#' Function to compose `validators` from `data_extract_multiple_srv` |
|
44 |
#' |
|
45 |
#' This function takes the output from `data_extract_multiple_srv` and |
|
46 |
#' collates the `shinyvalidate::InputValidator`s returned into a single |
|
47 |
#' `validator` and enables this |
|
48 |
#' |
|
49 |
#' @param iv (`shinyvalidate::InputValidator`) A `validator`. |
|
50 |
#' @param selector_list (`reactive` named list of `reactives`). |
|
51 |
#' Typically this is the output from `data_extract_multiple_srv`. |
|
52 |
#' The `validators` in this list (specifically `selector_list()[[validator_names]]()iv`) |
|
53 |
#' will be added into `iv`. |
|
54 |
#' @param validator_names (`character` or `NULL`). If `character` then only `validators` |
|
55 |
#' in the elements of `selector_list()` whose name is in this list will be added. If `NULL` |
|
56 |
#' all `validators` will be added |
|
57 |
#' @return (`shinyvalidate::InputValidator`) enabled `iv` with appropriate `validators` added into it. |
|
58 |
#' @export |
|
59 |
#' @examples |
|
60 |
#' library(shiny) |
|
61 |
#' library(shinyvalidate) |
|
62 |
#' library(shinyjs) |
|
63 |
#' library(teal.widgets) |
|
64 |
#' |
|
65 |
#' iris_extract <- data_extract_spec( |
|
66 |
#' dataname = "iris", |
|
67 |
#' select = select_spec( |
|
68 |
#' label = "Select variable:", |
|
69 |
#' choices = variable_choices(iris, colnames(iris)), |
|
70 |
#' selected = "Sepal.Length", |
|
71 |
#' multiple = TRUE, |
|
72 |
#' fixed = FALSE |
|
73 |
#' ) |
|
74 |
#' ) |
|
75 |
#' |
|
76 |
#' data_list <- list(iris = reactive(iris)) |
|
77 |
#' |
|
78 |
#' app <- shinyApp( |
|
79 |
#' ui = fluidPage( |
|
80 |
#' useShinyjs(), |
|
81 |
#' standard_layout( |
|
82 |
#' output = verbatimTextOutput("out1"), |
|
83 |
#' encoding = tagList( |
|
84 |
#' data_extract_ui( |
|
85 |
#' id = "x_var", |
|
86 |
#' label = "Please select an X column", |
|
87 |
#' data_extract_spec = iris_extract |
|
88 |
#' ), |
|
89 |
#' data_extract_ui( |
|
90 |
#' id = "y_var", |
|
91 |
#' label = "Please select a Y column", |
|
92 |
#' data_extract_spec = iris_extract |
|
93 |
#' ), |
|
94 |
#' data_extract_ui( |
|
95 |
#' id = "col_var", |
|
96 |
#' label = "Please select a color column", |
|
97 |
#' data_extract_spec = iris_extract |
|
98 |
#' ) |
|
99 |
#' ) |
|
100 |
#' ) |
|
101 |
#' ), |
|
102 |
#' server = function(input, output, session) { |
|
103 |
#' exactly_2_validation <- function() { |
|
104 |
#' ~ if (length(.) != 2) "Exactly 2 'Y' column variables must be chosen" |
|
105 |
#' } |
|
106 |
#' |
|
107 |
#' |
|
108 |
#' selector_list <- data_extract_multiple_srv( |
|
109 |
#' list(x_var = iris_extract, y_var = iris_extract, col_var = iris_extract), |
|
110 |
#' datasets = data_list, |
|
111 |
#' select_validation_rule = list( |
|
112 |
#' x_var = sv_required("Please select an X column"), |
|
113 |
#' y_var = compose_rules( |
|
114 |
#' sv_required("Exactly 2 'Y' column variables must be chosen"), |
|
115 |
#' exactly_2_validation() |
|
116 |
#' ) |
|
117 |
#' ) |
|
118 |
#' ) |
|
119 |
#' iv_r <- reactive({ |
|
120 |
#' iv <- InputValidator$new() |
|
121 |
#' compose_and_enable_validators( |
|
122 |
#' iv, |
|
123 |
#' selector_list, |
|
124 |
#' # if validator_names = NULL then all validators are used |
|
125 |
#' # to turn on only "x_var" then set this argument to "x_var" |
|
126 |
#' validator_names = NULL |
|
127 |
#' ) |
|
128 |
#' }) |
|
129 |
#' |
|
130 |
#' output$out1 <- renderPrint({ |
|
131 |
#' if (iv_r()$is_valid()) { |
|
132 |
#' ans <- lapply(selector_list(), function(x) { |
|
133 |
#' cat(format_data_extract(x()), "\n\n") |
|
134 |
#' }) |
|
135 |
#' } else { |
|
136 |
#' "Check that you have made a valid selection" |
|
137 |
#' } |
|
138 |
#' }) |
|
139 |
#' } |
|
140 |
#' ) |
|
141 |
#' if (interactive()) { |
|
142 |
#' runApp(app) |
|
143 |
#' } |
|
144 |
compose_and_enable_validators <- function(iv, selector_list, validator_names = NULL) { |
|
145 | 7x |
if (is.null(validator_names)) { |
146 | 7x |
validator_names <- names(selector_list()) |
147 |
} |
|
148 | 7x |
valid_validator_names <- intersect(validator_names, names(selector_list())) |
149 | ||
150 | 7x |
for (validator_name in valid_validator_names) { |
151 | 14x |
single_des <- selector_list()[[validator_name]]() |
152 | 14x |
if (!is.null(single_des$iv)) { |
153 | 14x |
iv$add_validator(single_des$iv) |
154 |
} |
|
155 |
} |
|
156 | 7x |
iv$enable() |
157 | 7x |
iv |
158 |
} |
1 |
#' Check selector `dataname` element |
|
2 |
#' |
|
3 |
#' @param dataname selector element |
|
4 |
#' |
|
5 |
#' @return error or nothing |
|
6 |
#' |
|
7 |
#' @noRd |
|
8 |
check_selector_dataname <- function(dataname) { |
|
9 | 2774x |
checkmate::assert_string(dataname) |
10 |
} |
|
11 | ||
12 |
#' Check selector filters element |
|
13 |
#' |
|
14 |
#' @param filters selector element generated by `data_extract_srv` |
|
15 |
#' |
|
16 |
#' @return error or nothing |
|
17 |
#' |
|
18 |
#' @noRd |
|
19 |
check_selector_filters <- function(filters) { |
|
20 | 2771x |
check_selector_filter <- function(x) { |
21 | 3080x |
is.list(x) && |
22 | 3080x |
all(c("columns", "selected") %in% names(x)) && |
23 | 3080x |
checkmate::test_character(x$columns, null.ok = TRUE, min.len = 1, any.missing = FALSE) && |
24 | 3080x |
(is.null(x$selected) || |
25 | 3080x |
all(vapply(x$selected, is.character, logical(1))) || |
26 | 3080x |
all(vapply(x$selected, is.numeric, logical(1))) |
27 |
) |
|
28 |
} |
|
29 | 2771x |
stopifnot(is.null(filters) || all(vapply(filters, check_selector_filter, logical(1)))) |
30 |
} |
|
31 | ||
32 |
#' Check selector select element |
|
33 |
#' |
|
34 |
#' @param select selector element generated by `data_extract_srv` |
|
35 |
#' |
|
36 |
#' @return error or nothing |
|
37 |
#' |
|
38 |
#' @noRd |
|
39 |
check_selector_select <- function(select) { |
|
40 | 2771x |
checkmate::assert_character(select) |
41 |
} |
|
42 | ||
43 |
#' Check selector keys element |
|
44 |
#' |
|
45 |
#' @param keys selector element generated by `data_extract_srv` |
|
46 |
#' |
|
47 |
#' @return error or nothing |
|
48 |
#' |
|
49 |
#' @noRd |
|
50 |
check_selector_keys <- function(keys) { |
|
51 | 2771x |
checkmate::assert_character(keys, min.len = 0L, any.missing = FALSE) |
52 |
} |
|
53 | ||
54 |
#' Check selector reshape element |
|
55 |
#' |
|
56 |
#' @param reshape selector element generated by `data_extract_srv` |
|
57 |
#' |
|
58 |
#' @return error or nothing |
|
59 |
#' |
|
60 |
#' @noRd |
|
61 |
check_selector_reshape <- function(reshape) { |
|
62 | 2771x |
checkmate::assert_flag(reshape) |
63 |
} |
|
64 | ||
65 |
#' Check selector internal_id element |
|
66 |
#' |
|
67 |
#' @param internal_id selector element generated by `data_extract_srv` |
|
68 |
#' |
|
69 |
#' @return error or nothing |
|
70 |
#' |
|
71 |
#' @noRd |
|
72 |
check_selector_internal_id <- function(internal_id) { |
|
73 | 2771x |
checkmate::assert_string(internal_id) |
74 |
} |
|
75 | ||
76 |
#' Check selector |
|
77 |
#' |
|
78 |
#' @param selector (`list`) of selector elements generated by `data_extract_srv` |
|
79 |
#' |
|
80 |
#' @return error or nothing |
|
81 |
#' |
|
82 |
#' @noRd |
|
83 |
check_selector <- function(selector) { |
|
84 |
# An error from the checks below is transformed to a shiny::validate error |
|
85 |
# so shiny can display it in grey not in red in an application |
|
86 | 2771x |
tryCatch( |
87 | 2771x |
expr = { |
88 | 2771x |
checkmate::assert_list(selector) |
89 | 2771x |
checkmate::assert_names( |
90 | 2771x |
names(selector), |
91 | 2771x |
must.include = c("dataname", "filters", "select", "keys", "reshape", "internal_id") |
92 |
) |
|
93 | 2771x |
check_selector_dataname(selector$dataname) |
94 | 2771x |
check_selector_filters(selector$filters) |
95 | 2771x |
check_selector_select(selector$select) |
96 | 2771x |
check_selector_keys(selector$keys) |
97 | 2771x |
check_selector_reshape(selector$reshape) |
98 | 2771x |
check_selector_internal_id(selector$internal_id) |
99 |
}, |
|
100 | 2771x |
error = function(e) shiny::validate(e$message) |
101 |
) |
|
102 |
} |
1 |
#' Merge the datasets on the keys |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("experimental")` |
|
4 |
#' It combines/merges multiple datasets with specified keys attribute. |
|
5 |
#' |
|
6 |
#' |
|
7 |
#' @details Internally this function uses calls to allow reproducibility. |
|
8 |
#' |
|
9 |
#' @inheritParams merge_expression_srv |
|
10 |
#' @return merged_dataset (`list`) containing: |
|
11 |
#' - `expr` (`list` of `call`) code needed to replicate merged dataset. |
|
12 |
#' - `columns_source` (`list`) of column names selected for particular selector. |
|
13 |
#' Each list element contains named character vector where: |
|
14 |
#' * Values are the names of the columns in the `ANL`. In case if the same column name is selected in more than one |
|
15 |
#' selector it gets prefixed by the id of the selector. For example if two `data_extract` have id `x`, `y`, then |
|
16 |
#' their duplicated selected variable (for example `AGE`) is prefixed to be `x.AGE` and `y.AGE`. |
|
17 |
#' * Names of the vector denote names of the variables in the input dataset. |
|
18 |
#' * `attr(,"dataname")` to indicate which dataset variable is merged from. |
|
19 |
#' * `attr(, "always selected")` to denote the names of the variables which need to be always selected. |
|
20 |
#' - `keys` (`list`) the keys of the merged dataset. |
|
21 |
#' - `filter_info` (`list`) The information given by the user. This information |
|
22 |
#' defines the filters that are applied on the data. Additionally it defines |
|
23 |
#' the variables that are selected from the data sets. |
|
24 |
#' @export |
|
25 |
#' |
|
26 |
#' @examples |
|
27 |
#' \dontrun{ |
|
28 |
#' # inside teal module server function |
|
29 |
#' response <- data_extract_srv( |
|
30 |
#' id = "reponse", |
|
31 |
#' data_extract_spec = response_spec, |
|
32 |
#' datasets = datasets |
|
33 |
#' ) |
|
34 |
#' regressor <- data_extract_srv( |
|
35 |
#' id = "regressor", |
|
36 |
#' data_extract_spec = regressor_spec, |
|
37 |
#' datasets = datasets |
|
38 |
#' ) |
|
39 |
#' merged_data <- merge_datasets(list(regressor(), response())) |
|
40 |
#' } |
|
41 |
merge_datasets <- function(selector_list, datasets, join_keys, merge_function = "dplyr::full_join", anl_name = "ANL") { |
|
42 | 6x |
logger::log_trace( |
43 | 6x |
paste( |
44 | 6x |
"merge_datasets called with:", |
45 | 6x |
"{ paste(names(datasets), collapse = ', ') } datasets;", |
46 | 6x |
"{ paste(names(selector_list), collapse = ', ') } selectors;", |
47 | 6x |
"{ merge_function } merge function." |
48 |
) |
|
49 |
) |
|
50 | ||
51 | 6x |
checkmate::assert_list(selector_list, min.len = 1) |
52 | 6x |
checkmate::assert_string(anl_name) |
53 | 6x |
checkmate::assert_list(datasets, names = "named") |
54 | 6x |
checkmate::assert_class(join_keys, "JoinKeys") |
55 | 6x |
stopifnot(attr(regexec("[A-Za-z0-9\\_]*", anl_name)[[1]], "match.length") == nchar(anl_name)) |
56 | 6x |
lapply(selector_list, check_selector) |
57 | 6x |
merge_selectors_out <- merge_selectors(selector_list) |
58 | 6x |
merged_selector_list <- merge_selectors_out[[1]] |
59 | 6x |
merged_selector_map_id <- merge_selectors_out[[2]] |
60 | 6x |
check_data_merge_selectors(merged_selector_list) |
61 | ||
62 | 6x |
dplyr_call_data <- get_dplyr_call_data(merged_selector_list, join_keys) |
63 | ||
64 | 6x |
validate_keys_sufficient(join_keys, merged_selector_list) |
65 | ||
66 | 6x |
columns_source <- mapply( |
67 | 6x |
function(id_from, id_to) { |
68 | 10x |
id_data <- vapply(dplyr_call_data, `[[`, character(1), "internal_id") |
69 | 10x |
out_cols <- dplyr_call_data[[which(id_to == id_data)]][["out_cols_renamed"]] |
70 | 10x |
id_selector <- vapply(selector_list, `[[`, character(1), "internal_id") |
71 | 10x |
res <- out_cols[names(out_cols) %in% selector_list[[which(id_from == id_selector)]][["select"]]] |
72 | 10x |
attr(res, "dataname") <- selector_list[[which(id_from == id_selector)]]$dataname |
73 | 10x |
always_selected <- selector_list[[which(id_from == id_selector)]]$always_selected |
74 | 10x |
if (is.null(always_selected)) { |
75 | 10x |
attr(res, "always_selected") <- character(0) |
76 |
} else { |
|
77 | ! |
attr(res, "always_selected") <- always_selected |
78 |
} |
|
79 | 10x |
res |
80 |
}, |
|
81 | 6x |
id_from = names(merged_selector_map_id), |
82 | 6x |
id_to = merged_selector_map_id, |
83 | 6x |
SIMPLIFY = FALSE |
84 |
) |
|
85 | ||
86 | 6x |
selector_datanames <- unique(vapply(merged_selector_list, `[[`, character(1), "dataname")) |
87 | ||
88 | 6x |
dplyr_calls <- lapply(seq_along(merged_selector_list), function(idx) { |
89 | 10x |
dplyr_call <- get_dplyr_call( |
90 | 10x |
selector_list = merged_selector_list, |
91 | 10x |
idx = idx, |
92 | 10x |
dplyr_call_data = dplyr_call_data, |
93 | 10x |
datasets = datasets |
94 |
) |
|
95 | 10x |
anl_i_call <- call("<-", as.name(paste0(anl_name, "_", idx)), dplyr_call) |
96 | 10x |
anl_i_call |
97 |
}) |
|
98 | ||
99 | 6x |
anl_merge_calls <- get_merge_call( |
100 | 6x |
selector_list = merged_selector_list, |
101 | 6x |
dplyr_call_data = dplyr_call_data, |
102 | 6x |
merge_function = merge_function, |
103 | 6x |
anl_name = anl_name |
104 |
) |
|
105 | ||
106 | 6x |
anl_relabel_call <- get_anl_relabel_call( |
107 | 6x |
columns_source = get_relabel_cols(columns_source, dplyr_call_data), # don't relabel reshaped cols |
108 | 6x |
datasets = datasets, |
109 | 6x |
anl_name = anl_name |
110 |
) |
|
111 | ||
112 | 6x |
all_calls_expression <- c(dplyr_calls, anl_merge_calls, anl_relabel_call) |
113 | ||
114 |
# keys in each merged_selector_list element should be identical |
|
115 |
# so take first one |
|
116 | 6x |
keys <- merged_selector_list[[1]]$keys |
117 | ||
118 | 6x |
filter_info <- lapply(merged_selector_list, "[[", "filters") |
119 | ||
120 | 6x |
res <- list( |
121 | 6x |
expr = all_calls_expression, |
122 | 6x |
columns_source = columns_source, |
123 | 6x |
keys = keys, |
124 | 6x |
filter_info = filter_info |
125 |
) |
|
126 | 6x |
logger::log_trace("merge_datasets merge code executed resulting in { anl_name } dataset.") |
127 | 6x |
return(res) |
128 |
} |
|
129 | ||
130 |
#' Merge selectors - select item if all of `dataname`, reshape, filters and keys items are identical |
|
131 |
#' |
|
132 |
#' @inheritParams merge_datasets |
|
133 |
#' |
|
134 |
#' @return error or nothing |
|
135 |
#' @keywords internal |
|
136 |
#' |
|
137 |
#' @examples |
|
138 |
#' selector_list <- list( |
|
139 |
#' # ADSL - SEX |
|
140 |
#' list( |
|
141 |
#' filters = NULL, select = "AGE", always_selected = NULL, |
|
142 |
#' reshape = FALSE, dataname = "ADSL", |
|
143 |
#' internal_id = "adsl_var", keys = c("STUDYID", "USUBJID") |
|
144 |
#' ), |
|
145 |
#' |
|
146 |
#' # ADSL - AGE |
|
147 |
#' list( |
|
148 |
#' filters = NULL, select = "SEX", always_selected = NULL, |
|
149 |
#' reshape = FALSE, dataname = "ADSL", |
|
150 |
#' internal_id = "adsl_var2", keys = c("STUDYID", "USUBJID") |
|
151 |
#' ), |
|
152 |
#' |
|
153 |
#' # ADLB - AVAL |
|
154 |
#' list( |
|
155 |
#' filters = NULL, select = "AVAL", always_selected = NULL, |
|
156 |
#' reshape = FALSE, dataname = "ADLB", |
|
157 |
#' internal_id = "adlb_var", keys = c( |
|
158 |
#' "STUDYID", "USUBJID", |
|
159 |
#' "PARAMCD", "AVISIT" |
|
160 |
#' ) |
|
161 |
#' ) |
|
162 |
#' ) |
|
163 |
#' merged_selectors <- teal.transform:::merge_selectors(selector_list) |
|
164 |
#' merged_selectors |
|
165 |
merge_selectors <- function(selector_list) { |
|
166 | 66x |
logger::log_trace("merge_selectors called with: { paste(names(selector_list), collapse = ', ') } selectors.") |
167 | 66x |
checkmate::assert_list(selector_list, min.len = 1) |
168 | 66x |
lapply(selector_list, check_selector) |
169 | ||
170 |
# merge map - idx to value |
|
171 |
# e.g. 1 2 1 means that 3rd selector is merged to 1st selector |
|
172 | 66x |
res_map_idx <- seq_along(selector_list) |
173 | 66x |
for (idx1 in res_map_idx) { |
174 | 141x |
selector_idx1 <- selector_list[[idx1]] |
175 | 141x |
for (idx2 in utils::tail(seq_along(res_map_idx), -idx1)) { |
176 | 113x |
if (res_map_idx[idx2] != idx2) { |
177 | 16x |
next |
178 |
} |
|
179 | 97x |
selector_idx2 <- selector_list[[idx2]] |
180 | 97x |
if (identical(selector_idx1$dataname, selector_idx2$dataname) && |
181 | 97x |
identical(selector_idx1$reshape, selector_idx2$reshape) && |
182 | 97x |
identical(selector_idx1$filters, selector_idx2$filters) && |
183 | 97x |
identical(selector_idx1$keys, selector_idx2$keys)) { |
184 | 19x |
res_map_idx[idx2] <- idx1 |
185 |
} |
|
186 |
} |
|
187 |
} |
|
188 | ||
189 | 66x |
res_map_id <- stats::setNames( |
190 | 66x |
vapply(selector_list[res_map_idx], `[[`, character(1), "internal_id"), |
191 | 66x |
vapply(selector_list, `[[`, character(1), "internal_id") |
192 |
) |
|
193 | ||
194 | ||
195 | 66x |
res_list <- selector_list |
196 | 66x |
for (idx in seq_along(res_map_idx)) { |
197 | 141x |
idx_val <- res_map_idx[[idx]] |
198 | 141x |
if (idx != idx_val) { |
199 |
# merge selector to the "first" identical subset |
|
200 | 19x |
res_list[[idx_val]]$select <- union(res_list[[idx_val]]$select, selector_list[[idx]]$select) |
201 |
} |
|
202 |
} |
|
203 | 66x |
for (idx in rev(seq_along(res_map_idx))) { |
204 | 141x |
idx_val <- res_map_idx[[idx]] |
205 | 141x |
if (idx != idx_val) { |
206 | 19x |
res_list[[idx]] <- NULL |
207 |
} |
|
208 |
} |
|
209 | ||
210 | 66x |
return(list(res_list, res_map_id)) |
211 |
} |
|
212 | ||
213 | ||
214 |
#' Validate data_extracts in merge_datasets |
|
215 |
#' |
|
216 |
#' Validate selected inputs from data_extract before passing to data_merge to avoid |
|
217 |
#' \code{dplyr} errors or unexpected results |
|
218 |
#' @inheritParams merge_datasets |
|
219 |
#' @return \code{NULL} if check is successful |
|
220 |
#' @keywords internal |
|
221 |
check_data_merge_selectors <- function(selector_list) { |
|
222 |
# check if reshape n empt select or just primary keys |
|
223 | 6x |
lapply(selector_list, function(x) { |
224 | 10x |
if (x$reshape & length(setdiff(x$select, x$keys)) == 0) { |
225 | ! |
validate(need( |
226 | ! |
FALSE, |
227 | ! |
"Error in data_extract_spec setup:\ |
228 | ! |
\tPlease select non-key column to be reshaped from long to wide format." |
229 |
)) |
|
230 |
} |
|
231 |
}) |
|
232 | 6x |
NULL |
233 |
} |
|
234 | ||
235 |
#' Validates whether the provided keys are sufficient to merge the datasets slices |
|
236 |
#' |
|
237 |
#' @note |
|
238 |
#' The keys are not sufficient if the datasets slices described in |
|
239 |
#' `merged_selector_list` come from datasets, which don't have the |
|
240 |
#' appropriate join keys in `join_keys`. |
|
241 |
#' |
|
242 |
#' @param join_keys (`JoinKeys`) the provided join keys |
|
243 |
#' @param merged_selector_list (`list`) the specification of datasets' slices to merge |
|
244 |
#' |
|
245 |
#' @return `TRUE` if the provided keys meet the requirements; the `shiny` |
|
246 |
#' validate error otherwise |
|
247 |
#' @keywords internal |
|
248 |
validate_keys_sufficient <- function(join_keys, merged_selector_list) { |
|
249 | 8x |
validate( |
250 | 8x |
need( |
251 | 8x |
are_needed_keys_provided(join_keys, merged_selector_list), |
252 | 8x |
message = paste( |
253 | 8x |
"Cannot merge at least two dataset extracts.", |
254 | 8x |
"Make sure all datasets used for merging have appropriate keys." |
255 |
) |
|
256 |
) |
|
257 |
) |
|
258 | ||
259 | 7x |
TRUE |
260 |
} |
|
261 | ||
262 |
#' Checks whether the provided slices have the corresponding join keys |
|
263 |
#' |
|
264 |
#' @note |
|
265 |
#' `merged_selector_list` contains a list of descriptions of data frame slices; |
|
266 |
#' each coming from a single dataset. This function checks whether all pairs |
|
267 |
#' of the datasets have the join keys needed to merge the slices. |
|
268 |
#' |
|
269 |
#' @inheritParams validate_keys_sufficient |
|
270 |
#' |
|
271 |
#' @return `TRUE` if all pairs of the slices have the corresponding keys; |
|
272 |
#' `FALSE` otherwise |
|
273 |
#' @keywords internal |
|
274 |
are_needed_keys_provided <- function(join_keys, merged_selector_list) { |
|
275 |
# because one slice doesn't have to be merged with anything |
|
276 | 13x |
if (length(merged_selector_list) <= 1) { |
277 | 6x |
return(TRUE) |
278 |
} |
|
279 | ||
280 | 7x |
do_join_keys_exist <- function(dataset_name1, dataset_name2, join_keys) { |
281 | 11x |
length(join_keys$get(dataset_name1, dataset_name2) > 0) |
282 |
} |
|
283 | ||
284 | 7x |
datasets_names <- vapply(merged_selector_list, function(slice) slice[["dataname"]], FUN.VALUE = character(1)) |
285 | 7x |
datasets_names_pairs <- utils::combn(datasets_names, m = 2) |
286 | 7x |
datasets_names_pairs <- datasets_names_pairs[, !duplicated(t(datasets_names_pairs)), drop = FALSE] |
287 | ||
288 | 7x |
datasets_pairs_keys_present <- apply( |
289 | 7x |
datasets_names_pairs, |
290 | 7x |
MARGIN = 2, |
291 | 7x |
FUN = function(names_pair) do_join_keys_exist(names_pair[1], names_pair[2], join_keys) |
292 |
) |
|
293 | ||
294 | 6x |
all(datasets_pairs_keys_present) |
295 |
} |
1 |
#' Include `CSS` files from `/inst/css/` 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 |
|
8 |
#' |
|
9 |
#' @return HTML code that includes `CSS` files |
|
10 |
#' @keywords internal |
|
11 |
include_css_files <- function(pattern = "*") { |
|
12 | 1x |
css_files <- list.files( |
13 | 1x |
system.file("css", package = "teal.transform", mustWork = TRUE), |
14 | 1x |
pattern = pattern, full.names = TRUE |
15 |
) |
|
16 | 1x |
return(singleton(lapply(css_files, includeCSS))) |
17 |
} |
1 |
#' Returns a `shiny.tag` object with the UI for a `filter_spec` object |
|
2 |
#' |
|
3 |
#' @details Creates two `optionSelectInput` elements (one for column and one for values) based |
|
4 |
#' on a definition of a [filter_spec()] object. |
|
5 |
#' |
|
6 |
#' @param filter (`filter_spec`) the object generated with [filter_spec()] |
|
7 |
#' @param id (`character(1)`) the shiny `inputId` for the generated `shiny.tag` |
|
8 |
#' |
|
9 |
#' @return `shiny.tag` defining the `filter_spec`'s UI element |
|
10 |
#' @keywords internal |
|
11 |
#' @examples |
|
12 |
#' teal.transform:::data_extract_filter_ui(filter = filter_spec(vars = "test_var"), id = "test_id") |
|
13 |
data_extract_filter_ui <- function(filter, id = "filter") { |
|
14 | 6x |
checkmate::assert_class(filter, "filter_spec") |
15 | 6x |
checkmate::assert_string(id) |
16 | ||
17 | 6x |
ns <- NS(id) |
18 | ||
19 | 6x |
html_col <- teal.widgets::optionalSelectInput( |
20 | 6x |
inputId = ns("col"), |
21 | 6x |
label = filter$vars_label, |
22 | 6x |
choices = filter$vars_choices, |
23 | 6x |
selected = filter$vars_selected, |
24 | 6x |
multiple = filter$vars_multiple, |
25 | 6x |
fixed = filter$vars_fixed |
26 |
) |
|
27 | ||
28 | 6x |
html_vals <- teal.widgets::optionalSelectInput( |
29 | 6x |
inputId = ns("vals"), |
30 | 6x |
label = filter$label, |
31 | 6x |
choices = filter$choices, |
32 | 6x |
selected = filter$selected, |
33 | 6x |
multiple = filter$multiple, |
34 | 6x |
fixed = filter$fixed |
35 |
) |
|
36 | ||
37 | 6x |
div( |
38 | 6x |
class = "filter_spec", |
39 | 6x |
if (filter$vars_fixed) shinyjs::hidden(html_col) else html_col, |
40 | 6x |
html_vals |
41 |
) |
|
42 |
} |
|
43 | ||
44 |
#' Handles events emitted from the UI generated by `data_extract_filter_ui`. |
|
45 |
#' |
|
46 |
#' @note This shiny module server updates the values of the `vals` |
|
47 |
#' [teal.widgets::optionalSelectInput()] widget. It's responsible |
|
48 |
#' for setting the initial values and the subsequent updates to |
|
49 |
#' the `vals` widget based on the input of the `col` widget. |
|
50 |
#' |
|
51 |
#' @param id (`character`) id string |
|
52 |
#' @param datasets (`named list`) a list of reactive `data.frame` type objects. |
|
53 |
#' @param filter (`filter_spec`) the filter generated by a call to [filter_spec()] |
|
54 |
#' @keywords internal |
|
55 |
data_extract_filter_srv <- function(id, datasets, filter) { |
|
56 | 8x |
checkmate::assert_list(datasets, types = "reactive", names = "named") |
57 | 8x |
moduleServer( |
58 | 8x |
id, |
59 | 8x |
function(input, output, session) { |
60 |
# We force the evaluation of filter, otherwise the observers are set up with the last element |
|
61 |
# of the list in data_extract_single_srv and not all of them (due to R lazy evaluation) |
|
62 | 8x |
force(filter) |
63 | 8x |
logger::log_trace( |
64 | 8x |
"data_extract_filter_srv initialized with: { filter$dataname } dataset." |
65 |
) |
|
66 | ||
67 | 8x |
observeEvent(input$col, |
68 | 8x |
handlerExpr = { |
69 | 6x |
if (!filter$initialized) { |
70 | 6x |
initial_inputs <- get_initial_filter_values(filter, datasets) |
71 | 6x |
choices <- initial_inputs$choices |
72 | 6x |
selected <- initial_inputs$selected |
73 | 6x |
filter$initialized <- TRUE |
74 | 6x |
filter <<- filter |
75 | ! |
} else if (!rlang::is_empty(input$col)) { |
76 | ! |
choices <- value_choices( |
77 | ! |
datasets[[filter$dataname]](), |
78 | ! |
input$col, |
79 | ! |
`if`(isTRUE(input$col == attr(filter$choices, "var_choices")), attr(filter$choices, "var_label"), NULL) |
80 |
) |
|
81 | ||
82 | ! |
selected <- if (!is.null(filter$selected)) { |
83 | ! |
filter$selected |
84 | ! |
} else if (filter$multiple) { |
85 | ! |
choices |
86 |
} else { |
|
87 | ! |
choices[1] |
88 |
} |
|
89 |
} else { |
|
90 | ! |
choices <- character(0) |
91 | ! |
selected <- character(0) |
92 |
} |
|
93 | 6x |
dn <- filter$dataname |
94 | 6x |
fc <- paste(input$col, collapse = ", ") |
95 | 6x |
logger::log_trace("data_extract_filter_srv@1 filter dataset: { dn }; filter var: { fc }.") |
96 |
# In order to force reactivity we run two updates: (i) set up dummy values (ii) set up appropriate values |
|
97 |
# It's due to a missing reactivity triggers if new selected value is identical with previously selected one. |
|
98 | 6x |
teal.widgets::updateOptionalSelectInput( |
99 | 6x |
session = session, |
100 | 6x |
inputId = "vals", |
101 | 6x |
choices = paste0(input$val, "$_<-_random_text_to_ensure_val_will_be_different_from_previous"), |
102 | 6x |
selected = paste0(input$val, "$_<-_random_text_to_ensure_val_will_be_different_from_previous") |
103 |
) |
|
104 | ||
105 | 6x |
teal.widgets::updateOptionalSelectInput( |
106 | 6x |
session = session, |
107 | 6x |
inputId = "vals", |
108 | 6x |
choices = choices, |
109 | 6x |
selected = selected |
110 |
) |
|
111 |
}, |
|
112 | 8x |
ignoreInit = FALSE, |
113 | 8x |
ignoreNULL = FALSE |
114 |
) |
|
115 |
} |
|
116 |
) |
|
117 |
} |
|
118 | ||
119 |
#' Returns the initial values for the `vals` widget |
|
120 |
#' of a `filter_spec` object. |
|
121 |
#' |
|
122 |
#' @inheritParams data_extract_filter_srv |
|
123 |
#' @return `named list` with two slots `choices` and `selected` |
|
124 |
#' @keywords internal |
|
125 |
#' |
|
126 |
#' @examples |
|
127 |
#' filtered_data_list <- list(iris = shiny::reactive(utils::head(iris))) |
|
128 |
#' filter <- filter_spec(vars = colnames(iris)[1]) |
|
129 |
#' filter$dataname <- "iris" |
|
130 |
#' shiny::isolate( |
|
131 |
#' teal.transform:::get_initial_filter_values(filter = filter, datasets = filtered_data_list) |
|
132 |
#' ) |
|
133 |
#' |
|
134 |
get_initial_filter_values <- function(filter, datasets) { |
|
135 | 9x |
initial_values <- list() |
136 | 9x |
if (is.null(filter$vars_selected)) { |
137 | 2x |
initial_values$choices <- character(0) |
138 | 2x |
initial_values$selected <- character(0) |
139 | 7x |
} else if (is.null(filter$choices)) { |
140 | 1x |
initial_values$choices <- value_choices( |
141 | 1x |
datasets[[filter$dataname]](), |
142 | 1x |
as.character(filter$vars_selected) |
143 |
) |
|
144 | 1x |
initial_values$selected <- if (inherits(filter$selected, "all_choices")) { |
145 | ! |
initial_values$choices |
146 |
} else { |
|
147 | 1x |
filter$selected |
148 |
} |
|
149 |
} else { |
|
150 | 6x |
initial_values$choices <- filter$choices |
151 | 6x |
initial_values$selected <- filter$selected |
152 |
} |
|
153 | ||
154 | 9x |
initial_values |
155 |
} |
1 |
#' Available datasets input |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' Creates \link[shiny]{helpText} with the names of datasets |
|
5 |
#' available for current module. |
|
6 |
#' @param data_extracts list of data extracts for single variable |
|
7 |
#' @export |
|
8 |
datanames_input <- function(data_extracts) { |
|
9 | ! |
datanames <- get_extract_datanames(data_extracts) |
10 | ! |
helpText( |
11 | ! |
paste0("Dataset", `if`(length(datanames) > 1, "s", ""), ":"), |
12 | ! |
tags$code(paste(datanames, collapse = ", ")) |
13 |
) |
|
14 |
} |
|
15 | ||
16 |
#' Gets names of the datasets from a list of `data_extract_spec` objects |
|
17 |
#' |
|
18 |
#' @description `r lifecycle::badge("stable")` |
|
19 |
#' Fetches `dataname` slot per \code{data_extract_spec} from a list of \code{data_extract_spec} |
|
20 |
#' and returns the unique `dataname` set. |
|
21 |
#' |
|
22 |
#' @param data_extracts A single \code{data_extract_spec} object or a list (of lists) of \code{data_extract_spec} |
|
23 |
#' |
|
24 |
#' @export |
|
25 |
get_extract_datanames <- function(data_extracts) { |
|
26 | 17x |
data_extracts <- if (inherits(data_extracts, "data_extract_spec")) { |
27 | 2x |
list(data_extracts) |
28 |
} else { |
|
29 | 15x |
data_extracts |
30 |
} |
|
31 | 17x |
checkmate::assert_list(data_extracts) |
32 | ||
33 | 14x |
data_extracts <- Filter(Negate(is.null), data_extracts) |
34 | 14x |
data_extracts <- Filter(Negate(is.logical), data_extracts) |
35 | 14x |
data_extracts <- Filter(Negate(is.choices_selected), data_extracts) |
36 | ||
37 | 14x |
stopifnot(length(data_extracts) > 0) |
38 | 13x |
stopifnot( |
39 | 13x |
checkmate::test_list(data_extracts, types = "data_extract_spec") || |
40 | 13x |
all( |
41 | 13x |
vapply(data_extracts, function(x) checkmate::test_list(x, types = "data_extract_spec"), logical(1)) |
42 |
) |
|
43 |
) |
|
44 | ||
45 | 11x |
datanames <- lapply(data_extracts, function(x) { |
46 | 20x |
if (inherits(x, "data_extract_spec")) { |
47 | 12x |
x[["dataname"]] |
48 | 8x |
} else if (checkmate::test_list(x, types = "data_extract_spec")) { |
49 | 8x |
lapply(x, `[[`, "dataname") |
50 |
} |
|
51 |
}) |
|
52 | ||
53 | 11x |
unique(unlist(datanames)) |
54 |
} |
|
55 | ||
56 |
#' Checks if the input `data_extract_spec` objects all come from the same dataset |
|
57 |
#' |
|
58 |
#' @description `r lifecycle::badge("stable")` |
|
59 |
#' @param ... either \code{data_extract_spec} objects or lists of \code{data_extract_spec} objects that do not contain |
|
60 |
#' NULL |
|
61 |
#' @return logical |
|
62 |
#' @export |
|
63 |
is_single_dataset <- function(...) { |
|
64 | ! |
data_extract_spec <- list(...) |
65 | ! |
dataset_names <- get_extract_datanames(data_extract_spec) |
66 | ! |
return(length(dataset_names) == 1) |
67 |
} |
1 |
#' Data Extract input for teal modules |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' The Data Extract input can be used to filter and select columns from a data |
|
6 |
#' set. This function enables such an input in teal. |
|
7 |
#' Please use the constructor function [data_extract_spec] to set it up. |
|
8 |
#' |
|
9 |
#' Note that no checks based on columns can be done because the data is only referred to by name. |
|
10 |
#' |
|
11 |
#' @export |
|
12 |
#' @rdname data_extract_spec |
|
13 |
#' |
|
14 |
#' @section Module Development: |
|
15 |
#' \describe{ |
|
16 |
#' `teal.transform` uses this object to construct a UI element in a module. |
|
17 |
#' } |
|
18 |
#' |
|
19 |
#' @param dataname (`character`)\cr |
|
20 |
#' The name of the dataset to be extracted. |
|
21 |
#' @param select (`NULL`, `select_spec`-S3 class or `delayed_select_spec`)\cr |
|
22 |
#' Columns to be selected from the input dataset |
|
23 |
#' mentioned in `dataname`. The setup can be created using [select_spec] function. |
|
24 |
#' @param filter (`NULL` or `filter_spec` or its respective delayed version)\cr |
|
25 |
#' Setup of the filtering of key columns inside the dataset. |
|
26 |
#' This setup can be created using the [filter_spec] function. |
|
27 |
#' Please note that if both select and filter are set to NULL, then the result will be a filter spec UI with all |
|
28 |
#' variables as possible choices and a select spec with multiple set to `TRUE`. |
|
29 |
#' @param reshape (`logical`)\cr |
|
30 |
#' whether reshape long to wide. Note that it will be used only in case of long dataset with multiple |
|
31 |
#' keys selected in filter part. |
|
32 |
#' |
|
33 |
#' @examples |
|
34 |
#' adtte_filters <- filter_spec( |
|
35 |
#' vars = c("PARAMCD", "CNSR"), |
|
36 |
#' sep = "-", |
|
37 |
#' choices = c("OS-1" = "OS-1", "OS-0" = "OS-0", "PFS-1" = "PFS-1"), |
|
38 |
#' selected = "OS-1", |
|
39 |
#' multiple = FALSE, |
|
40 |
#' label = "Choose endpoint and Censor" |
|
41 |
#' ) |
|
42 |
#' |
|
43 |
#' data_extract_spec( |
|
44 |
#' dataname = "ADTTE", |
|
45 |
#' filter = adtte_filters, |
|
46 |
#' select = select_spec( |
|
47 |
#' choices = c("AVAL", "BMRKR1", "AGE"), |
|
48 |
#' selected = c("AVAL", "BMRKR1"), |
|
49 |
#' multiple = TRUE, |
|
50 |
#' fixed = FALSE, |
|
51 |
#' label = "Column" |
|
52 |
#' ) |
|
53 |
#' ) |
|
54 |
#' |
|
55 |
#' data_extract_spec( |
|
56 |
#' dataname = "ADSL", |
|
57 |
#' filter = NULL, |
|
58 |
#' select = select_spec( |
|
59 |
#' choices = c("AGE", "SEX", "USUBJID"), |
|
60 |
#' selected = c("SEX"), |
|
61 |
#' multiple = FALSE, |
|
62 |
#' fixed = FALSE |
|
63 |
#' ) |
|
64 |
#' ) |
|
65 |
#' data_extract_spec( |
|
66 |
#' dataname = "ADSL", |
|
67 |
#' filter = filter_spec( |
|
68 |
#' vars = variable_choices("ADSL", subset = c("AGE")) |
|
69 |
#' ) |
|
70 |
#' ) |
|
71 |
#' |
|
72 |
#' dynamic_filter <- filter_spec( |
|
73 |
#' vars = choices_selected(variable_choices("ADSL"), "COUNTRY"), |
|
74 |
#' multiple = TRUE |
|
75 |
#' ) |
|
76 |
#' data_extract_spec( |
|
77 |
#' dataname = "ADSL", |
|
78 |
#' filter = dynamic_filter |
|
79 |
#' ) |
|
80 |
#' |
|
81 |
#' @references [select_spec] [filter_spec] |
|
82 |
data_extract_spec <- function(dataname, select = NULL, filter = NULL, reshape = FALSE) { |
|
83 | 129x |
checkmate::assert_string(dataname) |
84 | 129x |
stopifnot( |
85 | 129x |
is.null(select) || |
86 | 129x |
(inherits(select, "select_spec") && length(select) >= 1) |
87 |
) |
|
88 | 128x |
checkmate::assert( |
89 | 128x |
checkmate::check_null(filter), |
90 | 128x |
checkmate::check_class(filter, "filter_spec"), |
91 | 128x |
checkmate::check_list(filter, "filter_spec") |
92 |
) |
|
93 | 128x |
checkmate::assert_flag(reshape) |
94 | ||
95 | 128x |
if (is.null(select) && is.null(filter)) { |
96 | 6x |
select <- select_spec( |
97 | 6x |
choices = variable_choices(dataname), |
98 | 6x |
multiple = TRUE |
99 |
) |
|
100 | 6x |
filter <- filter_spec( |
101 | 6x |
vars = choices_selected(variable_choices(dataname)), |
102 | 6x |
selected = all_choices() |
103 |
) |
|
104 |
} |
|
105 | ||
106 | 55x |
if (inherits(filter, "filter_spec")) filter <- list(filter) |
107 | ||
108 | 115x |
for (idx in seq_along(filter)) filter[[idx]]$dataname <- dataname |
109 | ||
110 | 128x |
if (inherits(select, "delayed_select_spec") || |
111 | 128x |
any(vapply(filter, inherits, logical(1), "delayed_filter_spec"))) { |
112 | 40x |
structure( |
113 | 40x |
list(dataname = dataname, select = select, filter = filter, reshape = reshape), |
114 | 40x |
class = c("delayed_data_extract_spec", "delayed_data", "data_extract_spec") |
115 |
) |
|
116 |
} else { |
|
117 | 88x |
structure( |
118 | 88x |
list(dataname = dataname, select = select, filter = filter, reshape = reshape), |
119 | 88x |
class = "data_extract_spec" |
120 |
) |
|
121 |
} |
|
122 |
} |
1 |
# Contains modules to check the input provided to the `tm_*` functions is correct. |
|
2 |
# In general, they are checking functions, in the sense that they call `stopifnot` |
|
3 |
# if the conditions are not met. |
|
4 | ||
5 |
#' Make sure that the extract spec has list form |
|
6 |
#' |
|
7 |
#' @md |
|
8 |
#' @description `r lifecycle::badge("stable")` |
|
9 |
#' |
|
10 |
#' @param x `data_extract_spec` a single `data_extract_spec` or list of these |
|
11 |
#' @param allow_null `logical` whether x can be `NULL` |
|
12 |
#' |
|
13 |
#' @return x as a list if it is not already |
|
14 |
#' |
|
15 |
#' @export |
|
16 |
list_extract_spec <- function(x, allow_null = FALSE) { |
|
17 | 6x |
if (is.null(x)) { |
18 | ! |
stopifnot(allow_null) |
19 | ! |
return(NULL) |
20 |
} |
|
21 | 6x |
if (!checkmate::test_list(x, types = "data_extract_spec")) { |
22 | 5x |
x <- list(x) |
23 |
} |
|
24 | 6x |
stopifnot(checkmate::test_list(x, types = "data_extract_spec")) |
25 | 6x |
x |
26 |
} |
|
27 | ||
28 |
#' Checks that the extract_input specification does not allow multiple |
|
29 |
#' selection |
|
30 |
#' |
|
31 |
#' @md |
|
32 |
#' @description `r lifecycle::badge("stable")` |
|
33 |
#' |
|
34 |
#' @param extract_input `data_extract_spec` a list of `data_extract_spec` or NULL |
|
35 |
#' |
|
36 |
#' Stops if condition not met |
|
37 |
#' |
|
38 |
#' @export |
|
39 |
check_no_multiple_selection <- function(extract_input) { |
|
40 |
# bug in is_class_list when NULL |
|
41 | 3x |
checkmate::assert_list(extract_input, types = "data_extract_spec", null.ok = TRUE) |
42 | 2x |
all(vapply(extract_input, function(elem) !isTRUE(elem$select$multiple), logical(1))) || |
43 | 2x |
stop("extract_input variable should not allow multiple selection") |
44 | 1x |
invisible(NULL) |
45 |
} |
1 |
# Queue ==== |
|
2 | ||
3 |
#' @title R6 Class - A First-In-First-Out Abstract Data Type |
|
4 |
#' |
|
5 |
#' @description `r lifecycle::badge("experimental")`\cr |
|
6 |
#' Abstract data type that stores and returns any number of elements. |
|
7 |
#' |
|
8 |
#' A `Queue` object stores all elements in a single vector, |
|
9 |
#' thus all data types can be stored, but silent coercion may occur. |
|
10 |
#' |
|
11 |
#' Elements are returned in the same order that they were added. |
|
12 |
#' |
|
13 |
#' @keywords internal |
|
14 |
#' |
|
15 |
Queue <- R6::R6Class( # nolint |
|
16 |
classname = "Queue", |
|
17 |
# public methods ---- |
|
18 |
public = list( |
|
19 |
#' @description |
|
20 |
#' Adds element(s) to `Queue`. |
|
21 |
#' |
|
22 |
#' @param new_elements vector of elements to add |
|
23 |
#' |
|
24 |
#' @return self invisibly |
|
25 |
#' |
|
26 |
push = function(new_elements) { |
|
27 | 11x |
for (i in seq_along(new_elements)) { |
28 |
# new_elements[i] does not discard names if it's a named list |
|
29 | 52x |
private$array <- append(private$array, new_elements[i]) |
30 |
} |
|
31 | ||
32 | 11x |
invisible(self) |
33 |
}, |
|
34 |
#' @description |
|
35 |
#' Returns all contents of the `Queue` object. |
|
36 |
#' |
|
37 |
#' @return single vector containing all `Queue` contents |
|
38 |
#' |
|
39 |
get = function() { |
|
40 | 21x |
private$array |
41 |
}, |
|
42 |
#' @description |
|
43 |
#' Returns the first (oldest) element of the `Queue` and removes it. |
|
44 |
#' |
|
45 |
#' @return |
|
46 |
#' vector of length 1 containing the first element of `Queue` or NULL if `Queue` is empty |
|
47 |
#' |
|
48 |
pop = function() { |
|
49 | 1x |
returned_element <- self$get()[1L] |
50 | 1x |
private$array <- private$array[-1L] |
51 | 1x |
returned_element |
52 |
}, |
|
53 |
#' @description |
|
54 |
#' Removes the oldest occurrence of specified element(s) from `Queue`. |
|
55 |
#' Relies on implicit type conversions of R identify elements to remove. |
|
56 |
#' |
|
57 |
#' @param elements vector of elements to remove from `Queue` |
|
58 |
#' |
|
59 |
#' @return self invisibly |
|
60 |
#' |
|
61 |
remove = function(elements) { |
|
62 | 7x |
for (el in elements) { |
63 | 6x |
ind <- Position(function(x) identical(x, el), private$array) |
64 | 5x |
if (!is.na(ind)) private$array <- private$array[-ind] |
65 |
} |
|
66 | 7x |
invisible(self) |
67 |
}, |
|
68 |
#' @description |
|
69 |
#' Removes all elements from `Queue`. |
|
70 |
#' |
|
71 |
#' @return self invisibly |
|
72 |
#' |
|
73 |
empty = function() { |
|
74 | 1x |
private$array <- c() |
75 | 1x |
invisible(self) |
76 |
}, |
|
77 |
#' @description |
|
78 |
#' Returns the number of elements in `Queue`. |
|
79 |
#' |
|
80 |
#' @return integer of length 1 |
|
81 |
#' |
|
82 |
size = function() { |
|
83 | 4x |
length(self$get()) |
84 |
}, |
|
85 |
#' @description |
|
86 |
#' Prints this `Queue`. |
|
87 |
#' |
|
88 |
#' @param ... additional arguments to this method, ignored |
|
89 |
#' |
|
90 |
#' @return invisibly self |
|
91 |
print = function(...) { |
|
92 | 1x |
cat( |
93 | 1x |
sprintf( |
94 | 1x |
"%s\nSize: %i\nElements:\n%s\n", |
95 | 1x |
strsplit(format(self), "\n")[[1]][1], |
96 | 1x |
self$size(), |
97 | 1x |
paste(self$get(), collapse = " ") |
98 |
) |
|
99 |
) |
|
100 | 1x |
invisible(self) |
101 |
} |
|
102 |
), |
|
103 | ||
104 |
# private members ---- |
|
105 |
private = list( |
|
106 |
array = c() |
|
107 |
), |
|
108 | ||
109 |
lock_class = TRUE |
|
110 |
) |
1 |
#' Resolve delayed inputs by evaluating the code within the provided datasets |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("stable")` |
|
4 |
#' |
|
5 |
#' @param x (`delayed_data`, `list`) to resolve. |
|
6 |
#' @param datasets (`FilteredData` or named `list`) to use as a reference to resolve `x`. |
|
7 |
#' @param keys (named `list`) with primary keys for each dataset from `datasets`. `names(keys)` |
|
8 |
#' should match `names(datasets)` |
|
9 |
#' |
|
10 |
#' @return Resolved object. |
|
11 |
#' |
|
12 |
#' @export |
|
13 |
#' |
|
14 |
#' @examples |
|
15 |
#' ADSL <- teal.transform::rADSL |
|
16 |
#' shiny::isolate({ |
|
17 |
#' ds <- teal.slice::init_filtered_data( |
|
18 |
#' list(ADSL = list(dataset = ADSL)) |
|
19 |
#' ) |
|
20 |
#' |
|
21 |
#' # value_choices example |
|
22 |
#' v1 <- value_choices("ADSL", "SEX", "SEX") |
|
23 |
#' v1 |
|
24 |
#' resolve_delayed(v1, ds) |
|
25 |
#' |
|
26 |
#' # variable_choices example |
|
27 |
#' v2 <- variable_choices("ADSL", c("BMRKR1", "BMRKR2")) |
|
28 |
#' v2 |
|
29 |
#' resolve_delayed(v2, ds) |
|
30 |
#' |
|
31 |
#' # data_extract_spec example |
|
32 |
#' adsl_filter <- filter_spec( |
|
33 |
#' vars = variable_choices("ADSL", "SEX"), |
|
34 |
#' sep = "-", |
|
35 |
#' choices = value_choices("ADSL", "SEX", "SEX"), |
|
36 |
#' selected = "F", |
|
37 |
#' multiple = FALSE, |
|
38 |
#' label = "Choose endpoint and Censor" |
|
39 |
#' ) |
|
40 |
#' |
|
41 |
#' adsl_select <- select_spec( |
|
42 |
#' label = "Select variable:", |
|
43 |
#' choices = variable_choices("ADSL", c("BMRKR1", "BMRKR2")), |
|
44 |
#' selected = "BMRKR1", |
|
45 |
#' multiple = FALSE, |
|
46 |
#' fixed = FALSE |
|
47 |
#' ) |
|
48 |
#' |
|
49 |
#' adsl_de <- data_extract_spec( |
|
50 |
#' dataname = "ADSL", |
|
51 |
#' select = adsl_select, |
|
52 |
#' filter = adsl_filter |
|
53 |
#' ) |
|
54 |
#' |
|
55 |
#' resolve_delayed(adsl_filter, ds) |
|
56 |
#' resolve_delayed(adsl_select, ds) |
|
57 |
#' resolve_delayed(adsl_de, ds) |
|
58 |
#' |
|
59 |
#' # nested list (arm_ref_comp) |
|
60 |
#' arm_ref_comp <- list( |
|
61 |
#' ARMCD = list( |
|
62 |
#' ref = variable_choices("ADSL"), |
|
63 |
#' comp = variable_choices("ADSL") |
|
64 |
#' ) |
|
65 |
#' ) |
|
66 |
#' |
|
67 |
#' resolve_delayed(arm_ref_comp, ds) |
|
68 |
#' }) |
|
69 |
resolve_delayed <- function(x, datasets, keys) { |
|
70 | 38x |
UseMethod("resolve_delayed", datasets) |
71 |
} |
|
72 | ||
73 | ||
74 |
#' @export |
|
75 |
resolve_delayed.FilteredData <- function(x, |
|
76 |
datasets, |
|
77 |
keys = sapply(datasets$datanames(), datasets$get_keys, simplify = FALSE)) { |
|
78 | 36x |
datasets_list <- sapply(X = datasets$datanames(), simplify = FALSE, FUN = function(x) { |
79 | 64x |
reactive(datasets$get_data(dataname = x, filtered = TRUE)) |
80 |
}) |
|
81 | 36x |
resolve(x, datasets_list, keys) |
82 |
} |
|
83 | ||
84 |
#' @export |
|
85 |
resolve_delayed.list <- function(x, datasets, keys = NULL) { |
|
86 | 2x |
checkmate::assert_list(datasets, type = c("reactive", "data.frame"), min.len = 1, names = "named") |
87 | 2x |
checkmate::assert_list(keys, "character", names = "named", null.ok = TRUE) |
88 | 2x |
checkmate::assert( |
89 | 2x |
.var.name = "keys", |
90 | 2x |
checkmate::check_names(names(keys), subset.of = names(datasets)), |
91 | 2x |
checkmate::check_null(keys) |
92 |
) |
|
93 |
# convert to list of reactives |
|
94 | 2x |
datasets_list <- sapply(X = datasets, simplify = FALSE, FUN = function(x) { |
95 | 1x |
if (is.reactive(x)) x else reactive(x) |
96 |
}) |
|
97 | 2x |
resolve(x, datasets_list, keys) |
98 |
} |
1 |
.onLoad <- function(libname, pkgname) { # nolint |
|
2 | ! |
teal.logger::register_logger("teal.transform") |
3 | ! |
invisible() |
4 |
} |
1 |
check_merge_function <- function(merge_function) { |
|
2 | 73x |
checkmate::assert_string(merge_function) |
3 | 73x |
stopifnot(length(intersect(methods::formalArgs(eval(rlang::parse_expr(merge_function))), c("x", "y", "by"))) == 3) |
4 |
} |
1 |
#' An S3 structure representing the selection of all |
|
2 |
#' possible choices in a `filter_spec`, `select_spec` or `choices_selected` object. |
|
3 |
#' |
|
4 |
#' @description `r lifecycle::badge("experimental")` |
|
5 |
#' |
|
6 |
#' @return `all_choices` object |
|
7 |
#' |
|
8 |
#' @examples |
|
9 |
#' # Both structures are semantically identical |
|
10 |
#' filter_spec( |
|
11 |
#' vars = c("selected_variable"), |
|
12 |
#' choices = c("value1", "value2"), |
|
13 |
#' selected = c("value1", "value2") |
|
14 |
#' ) |
|
15 |
#' |
|
16 |
#' filter_spec( |
|
17 |
#' vars = c("selected_variable"), |
|
18 |
#' choices = c("value1", "value2"), |
|
19 |
#' selected = all_choices() |
|
20 |
#' ) |
|
21 |
#' |
|
22 |
#' choices_selected(choices = letters, selected = letters) |
|
23 |
#' choices_selected(choices = letters, selected = all_choices()) |
|
24 |
#' @export |
|
25 |
all_choices <- function() { |
|
26 | 12x |
structure(list(), class = "all_choices") |
27 |
} |
1 |
#' Returns a `shiny.tag.list` object with the UI for a `select_spec` object |
|
2 |
#' |
|
3 |
#' @param select (`select_spec`) A definition of a select spec element. |
|
4 |
#' Setting [select_spec()] with `ordered = TRUE` makes this selector responsive to the variable |
|
5 |
#' selection order. |
|
6 |
#' @param id (`character(1)`) The shiny `inputId` of the element |
|
7 |
#' @return `shiny.tag.list` with the UI |
|
8 |
#' @keywords internal |
|
9 |
#' @examples |
|
10 |
#' teal.transform:::data_extract_select_ui( |
|
11 |
#' select = select_spec(choices = "test_choice"), |
|
12 |
#' id = "test_id" |
|
13 |
#' ) |
|
14 |
data_extract_select_ui <- function(select, id = "select") { |
|
15 | 4x |
checkmate::assert_class(select, "select_spec") |
16 | 4x |
checkmate::assert_string(id) |
17 | ||
18 |
## select input |
|
19 | 4x |
res <- list( |
20 | 4x |
teal.widgets::optionalSelectInput( |
21 | 4x |
inputId = id, |
22 | 4x |
label = select$label, |
23 | 4x |
choices = select$choices, |
24 | 4x |
selected = select$selected, |
25 | 4x |
multiple = select$multiple, |
26 | 4x |
fixed = select$fixed |
27 |
) |
|
28 |
) |
|
29 | ||
30 | 4x |
if (!is.null(select$always_selected)) { |
31 | ! |
res <- append( |
32 | ! |
res, |
33 | ! |
list( |
34 | ! |
shinyjs::hidden( |
35 | ! |
selectInput( |
36 | ! |
inputId = paste0(id, "_additional"), |
37 | ! |
label = "", |
38 | ! |
choices = select$always_selected, |
39 | ! |
selected = select$always_selected, |
40 | ! |
multiple = length(select$always_selected) > 1 |
41 |
) |
|
42 |
), |
|
43 | ! |
helpText( |
44 | ! |
"Default Column(s)", |
45 | ! |
tags$code(paste(select$always_selected, collapse = " ")) |
46 |
) |
|
47 |
) |
|
48 |
) |
|
49 |
} |
|
50 | ||
51 | 4x |
do.call("tagList", res) |
52 |
} |
1 |
#' Data merge module |
|
2 |
#' |
|
3 |
#' @description `r lifecycle::badge("deprecated")` |
|
4 |
#' @details This function was a convenient wrapper to combine `data_extract_multiple_srv()` and |
|
5 |
#' `data_merge_srv()` when no additional processing is required. |
|
6 |
#' |
|
7 |
#' @inheritParams shiny::moduleServer |
|
8 |
#' @param datasets (`FilteredData`)\cr |
|
9 |
#' object containing data, see [teal.slice::FilteredData] for more. |
|
10 |
#' @param data_extract (named `list` of `data_extract_spec`)\cr |
|
11 |
#' @param merge_function (`character(1)`)\cr |
|
12 |
#' A character string of a function that |
|
13 |
#' accepts the arguments `x`, `y` and `by` to perform the merging of datasets. |
|
14 |
#' @param anl_name (`character(1)`)\cr |
|
15 |
#' Name of the analysis dataset. |
|
16 |
#' |
|
17 |
#' @return reactive expression with output from [data_merge_srv()]. |
|
18 |
#' |
|
19 |
#' @seealso [data_merge_srv()] |
|
20 |
#' |
|
21 |
#' @export |
|
22 |
data_merge_module <- function(datasets, |
|
23 |
data_extract, |
|
24 |
merge_function = "dplyr::full_join", |
|
25 |
anl_name = "ANL", |
|
26 |
id = "merge_id") { |
|
27 | ! |
lifecycle::deprecate_stop("0.3.1", "data_merge_module()") |
28 |
} |
|
29 | ||
30 | ||
31 |
#' Data merge module server |
|
32 |
#' |
|
33 |
#' @description `r lifecycle::badge("deprecated")` |
|
34 |
#' @details When additional processing of the `data_extract` list input was required, `data_merge_srv()` could be |
|
35 |
#' combined with `data_extract_multiple_srv()` or `data_extract_srv()` to influence the `selector_list` input. |
|
36 |
#' |
|
37 |
#' @inheritParams shiny::moduleServer |
|
38 |
#' @param selector_list (`reactive`)\cr |
|
39 |
#' output from [data_extract_multiple_srv()] or a reactive named list of outputs from [data_extract_srv()]. |
|
40 |
#' When using a reactive named list, the names must be identical to the shiny ids of the |
|
41 |
#' respective [data_extract_ui()]. |
|
42 |
#' @param datasets (`FilteredData`)\cr |
|
43 |
#' object containing data (see `teal.slice::FilteredData`). |
|
44 |
#' @param merge_function (`character(1)` or `reactive`)\cr |
|
45 |
#' A character string of a function that accepts the arguments |
|
46 |
#' `x`, `y` and `by` to perform the merging of datasets. |
|
47 |
#' @param anl_name (`character(1)`)\cr |
|
48 |
#' Name of the analysis dataset. |
|
49 |
#' |
|
50 |
#' @return reactive expression with output from [merge_datasets]. |
|
51 |
#' |
|
52 |
#' @seealso [data_extract_srv()] |
|
53 |
#' |
|
54 |
#' @export |
|
55 |
data_merge_srv <- function(id = "merge_id", |
|
56 |
selector_list, |
|
57 |
datasets, |
|
58 |
merge_function = "dplyr::full_join", |
|
59 |
anl_name = "ANL") { |
|
60 | ! |
lifecycle::deprecate_stop("0.3.1", "data_merge_srv()") |
61 |
} |