Skip to content

Commit 148101d

Browse files
committed
refactor the trigger arithmetic
1 parent 115032c commit 148101d

23 files changed

Lines changed: 291 additions & 299 deletions

NAMESPACE

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export(Population)
77
export(Timer)
88
export(Trial)
99
export(add_timepoints)
10-
export(build_trigger)
1110
export(calendar_trigger)
1211
export(clone_trial)
1312
export(collect_results)

R/Condition.R

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
.trigger_to_quosures <- function(trigger) {
2+
if (!is.null(trigger$type)) {
3+
expr <- if (identical(trigger$type, "value")) {
4+
call(trigger$op, call("[[", quote(.data), trigger$col), trigger$rhs)
5+
} else {
6+
call(trigger$op,
7+
call("sum", call("!", call("is.na", call("[[", quote(.data), trigger$col)))),
8+
trigger$rhs)
9+
}
10+
return(list(rlang::new_quosure(expr, env = rlang::current_env())))
11+
}
12+
13+
if (identical(trigger$combinator, "&")) {
14+
return(unlist(lapply(trigger$predicates, .trigger_to_quosures), recursive = FALSE))
15+
}
16+
17+
left_quos <- .trigger_to_quosures(trigger$left)
18+
right_quos <- .trigger_to_quosures(trigger$right)
19+
expr <- call("|", rlang::get_expr(left_quos[[1L]]), rlang::get_expr(right_quos[[1L]]))
20+
list(rlang::new_quosure(expr, env = rlang::current_env()))
21+
}
22+
123
#' Condition: Stateful trigger and analysis unit
224
#'
325
#' @description
@@ -29,8 +51,9 @@
2951
#' is not updated.
3052
#'
3153
#' On a successful trigger, the condition calls
32-
#' `analysis(filtered_data, current_time)` and stores the result under
33-
#' `name` (or `1L` when no name is set). If no analysis function is
54+
#' `analysis(df, current_time, ...)` and stores the result under
55+
#' `name` (or `1L` when no name is set). Any values in `analysis_args` are
56+
#' appended as additional named arguments. If no analysis function is
3457
#' provided, the filtered data frame is returned as-is with a warning.
3558
#'
3659
#' @section Fields:
@@ -39,9 +62,11 @@
3962
#' `dplyr::filter()` predicates. Pass `NULL` or an empty list to skip
4063
#' filtering and pass the full snapshot to the analysis.}
4164
#' \item{\code{analysis}}{`function` or `NULL`. Called as
42-
#' `analysis(filtered_data, current_time)` on a successful trigger.
43-
#' Should return a `data.frame` or named list. If `NULL`, the filtered
44-
#' data frame is returned with a warning.}
65+
#' `analysis(df, current_time, ...)` on a successful trigger, where `...`
66+
#' are any values from `analysis_args`. Should return a `data.frame` or
67+
#' named list. If `NULL`, the filtered data frame is returned with a warning.}
68+
#' \item{\code{analysis_args}}{`list` or `NULL`. Named list of extra arguments
69+
#' injected into every call to `analysis`.}
4570
#' \item{\code{name}}{`character` or `NULL`. Key used to label the result
4671
#' in the returned list. Falls back to `1L` when `NULL`.}
4772
#' \item{\code{cooldown}}{`numeric`. Minimum time units that must elapse
@@ -74,6 +99,8 @@
7499
#' \item [`Trial`] for running the simulation and iterating over conditions
75100
#' \item [`trigger_by_calendar()`] and [`trigger_by_fraction()`] for
76101
#' convenient `Condition` constructors
102+
#' \item [value_trigger()], [count_trigger()], [enroll_trigger()],
103+
#' [calendar_trigger()] for building safe trigger specifications
77104
#' \item [`dplyr::filter()`] for predicate syntax
78105
#' }
79106
#'
@@ -86,8 +113,8 @@
86113
#' )
87114
#'
88115
#' # Analysis function: count active subjects per arm
89-
#' count_fn <- function(dat, current_time) {
90-
#' data.frame(n_active = nrow(dat), fired_at = current_time)
116+
#' count_fn <- function(df, current_time) {
117+
#' data.frame(n_active = nrow(df), fired_at = current_time)
91118
#' }
92119
#'
93120
#' # Condition fires once when arm A has active subjects (max_triggers = 1)
@@ -115,13 +142,19 @@ Condition <- R6::R6Class(
115142
public = list(
116143
# --- fields ---
117144
#' @field where `list` of quosures (`rlang::quos()`) used as `dplyr::filter()`
118-
#' predicates. `NULL` or empty list passes the full snapshot.
145+
#' predicates, or an `rxsim_trigger` (converted automatically). `NULL` or
146+
#' empty list passes the full snapshot.
119147
where = NULL,
120148

121149
#' @field analysis `function` or `NULL`. Called as
122-
#' `analysis(filtered_data, current_time)` on a successful trigger.
150+
#' `analysis(df, current_time, ...)` on a successful trigger, where `...`
151+
#' are any values from `analysis_args`.
123152
analysis = NULL,
124153

154+
#' @field analysis_args `list` or `NULL`. Named list of extra values injected
155+
#' into the analysis function call as additional named arguments.
156+
analysis_args = NULL,
157+
125158
#' @field name `character` or `NULL`. Key labelling the result in the output
126159
#' list. Falls back to `1L` when `NULL`.
127160
name = NULL,
@@ -146,26 +179,33 @@ Condition <- R6::R6Class(
146179
#' @description
147180
#' Create a new `Condition` instance.
148181
#'
149-
#' @param where `list` of quosures (from `rlang::quos()`) used as filter
150-
#' predicates. Pass `NULL` or omit to use the full snapshot.
182+
#' @param where `rxsim_trigger` (converted automatically to quosures), a
183+
#' `list` of quosures from `rlang::quos()`, or `NULL` to use the full
184+
#' snapshot.
151185
#' @param analysis `function` or `NULL`. Called as
152-
#' `analysis(filtered_data, current_time)` on a successful trigger.
186+
#' `analysis(df, current_time, ...)` on a successful trigger, where `...`
187+
#' are the values from `analysis_args`.
188+
#' @param analysis_args `list` or `NULL`. Named list of extra arguments
189+
#' passed to the analysis function after `df` and `current_time`.
153190
#' @param name `character` or `NULL`. Result key. Defaults to `1L`.
154191
#' @param cooldown `numeric`. Minimum time between triggers. Default `0`.
155192
#' @param max_triggers `integer`. Maximum trigger count. Default `1L`.
156193
#' Use `Inf` for unlimited.
157194
#'
158195
#' @return A new `Condition` instance.
159196
initialize = function(
160-
where = NULL,
161-
analysis = NULL,
162-
name = NULL,
163-
cooldown = 0,
164-
max_triggers = 1L
197+
where = NULL,
198+
analysis = NULL,
199+
analysis_args = NULL,
200+
name = NULL,
201+
cooldown = 0,
202+
max_triggers = 1L
165203
) {
166-
self$where <- where
167-
self$analysis <- analysis
168-
self$name <- name
204+
if (inherits(where, "rxsim_trigger")) where <- .trigger_to_quosures(where)
205+
self$where <- where
206+
self$analysis <- analysis
207+
self$analysis_args <- analysis_args
208+
self$name <- name
169209

170210
cooldown <- as.numeric(cooldown)
171211
if (length(cooldown) != 1L || cooldown < 0 || is.na(cooldown)) {
@@ -230,7 +270,7 @@ Condition <- R6::R6Class(
230270
}
231271

232272
if (is.function(self$analysis)) {
233-
results[[key]] <- self$analysis(df_i, current_time)
273+
results[[key]] <- do.call(self$analysis, c(list(df_i, current_time), self$analysis_args))
234274
} else {
235275
results[[key]] <- df_i
236276
warning(

R/helpers.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ add_timepoints <- function(timer, df) {
7575
#' an_gens <- list(
7676
#' final = list(
7777
#' trigger = count_trigger("enroll_time", ">=", 20L),
78-
#' analysis = function(df, timer) {
78+
#' analysis = function(df, current_time) {
7979
#' data.frame(mean_ctrl = mean(df$data[df$arm == "control"]))
8080
#' }
8181
#' )

R/trials.R

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,11 @@ replicate_trial <- function(
150150
"Use `value_trigger()`, `count_trigger()`, `enroll_trigger()`, or `calendar_trigger()`."
151151
)
152152
}
153-
build_trigger(
154-
trigger,
155-
analysis = analysis_generators[[aname]]$analysis,
156-
name = aname
153+
Condition$new(
154+
where = trigger,
155+
analysis = analysis_generators[[aname]]$analysis,
156+
analysis_args = analysis_generators[[aname]]$analysis_args,
157+
name = aname
157158
)
158159
})
159160
Trial$new(

0 commit comments

Comments
 (0)