|
a |
|
b/R/recruit.R |
|
|
1 |
#' A class to contain the accrued patient allocations, |
|
|
2 |
#' by site, treatment/control arm and week |
|
|
3 |
#' |
|
|
4 |
#' @slot accrual 3-D array with axes site, experimental arm |
|
|
5 |
#' and week |
|
|
6 |
#' @slot target_arm_size Number of subjects required for each treatment arm. |
|
|
7 |
#' @slot target_control Number of subjects required for control arm(s). |
|
|
8 |
#' @slot target_interim Number of subjects required for treatment arm at |
|
|
9 |
#' interim analysis. |
|
|
10 |
#' @slot accrual_period Number of weeks in recruitment period. |
|
|
11 |
#' @slot interim_period Number of weeks to recruit for interim analysis. |
|
|
12 |
#' @slot phase_changes Vector of week numbers when arms closed |
|
|
13 |
#' @slot site_closures Vector of weeks sites closed; NA indicates open |
|
|
14 |
#' @slot week Current recruitment week |
|
|
15 |
#' @slot active_arms Vector of indices of open arms |
|
|
16 |
#' @slot active_sites Vector of indices of open sites |
|
|
17 |
#' @slot shared_control TRUE if a shared control arm is being used, else FALSE |
|
|
18 |
#' @slot site_in_region Vector of indices for which set of expected |
|
|
19 |
#' prevalences each site should use |
|
|
20 |
#' @slot site_cap Vector of maximum number of patients for each site |
|
|
21 |
#' @slot site_mean_rate Vector of expected recruitment rates for each site |
|
|
22 |
#' @slot site_rate Vector of recruitment rates for each site, drawn from a |
|
|
23 |
#' gamma distribution |
|
|
24 |
#' @slot start_week Vector of weeks that each site opens recruitment |
|
|
25 |
#' @slot site_index Vector of index numbers for each site |
|
|
26 |
#' @slot treatment_arm_ids Named list of lists of recruitment arms by |
|
|
27 |
#' treatment arm. |
|
|
28 |
#' |
|
|
29 |
#' @param treatment_arm_ids Named list of lists of recruitment arms by |
|
|
30 |
#' treatment arm. |
|
|
31 |
#' @param shared_control TRUE if all experimental arms share one control arm; |
|
|
32 |
#' FALSE if each has their own |
|
|
33 |
#' @param target_arm_size Number of subjects required for each treatment arm. |
|
|
34 |
#' @param target_control Number of subjects required for control arm(s). |
|
|
35 |
#' @param target_interim Number of subjects required for treatment arm at |
|
|
36 |
#' interim analysis. |
|
|
37 |
#' @param accrual_period Number of weeks in recruitment period. |
|
|
38 |
#' @param interim_period Number of weeks to recruit for interim analysis. |
|
|
39 |
#' @param centres_df Dataframe with columns "site", "start_month", "mean_rate", |
|
|
40 |
#' "region" and "site_cap" |
|
|
41 |
#' |
|
|
42 |
#' @usage accrual(treatment_arm_ids, shared_control, target_arm_size, |
|
|
43 |
#' target_control, target_interim, accrual_period, interim_period, centres_df) |
|
|
44 |
#' @name accrual |
|
|
45 |
#' |
|
|
46 |
#' @export |
|
|
47 |
#' |
|
|
48 |
accrual <- S7::new_class("accrual", |
|
|
49 |
package = "biomkrAccrual", |
|
|
50 |
properties = list( |
|
|
51 |
accrual = S7::class_integer, |
|
|
52 |
target_arm_size = S7::class_integer, |
|
|
53 |
target_control = S7::class_integer, |
|
|
54 |
target_interim = S7::class_integer, |
|
|
55 |
accrual_period = S7::class_integer, |
|
|
56 |
interim_period = S7::class_integer, |
|
|
57 |
control_ratio = S7::class_double, |
|
|
58 |
phase_changes = S7::class_integer, |
|
|
59 |
site_closures = S7::class_integer, |
|
|
60 |
week = S7::class_integer, |
|
|
61 |
active_arms = S7::class_integer, |
|
|
62 |
active_sites = S7::class_integer, |
|
|
63 |
shared_control = S7::class_logical, |
|
|
64 |
site_in_region = S7::class_integer, |
|
|
65 |
site_cap = S7::class_integer, |
|
|
66 |
site_mean_rate = S7::class_double, |
|
|
67 |
site_rate = S7::class_double, |
|
|
68 |
site_start_week = S7::class_integer, |
|
|
69 |
site_index = S7::class_integer, |
|
|
70 |
treatment_arm_ids = S7::class_list |
|
|
71 |
), |
|
|
72 |
constructor = function( |
|
|
73 |
treatment_arm_ids = S7::class_missing, |
|
|
74 |
shared_control = S7::class_missing, |
|
|
75 |
target_arm_size = S7::class_missing, |
|
|
76 |
target_control = S7::class_missing, |
|
|
77 |
target_interim = S7::class_missing, |
|
|
78 |
accrual_period = S7::class_missing, |
|
|
79 |
interim_period = S7::class_missing, |
|
|
80 |
control_ratio = S7::class_missing, |
|
|
81 |
centres_df = S7::class_missing |
|
|
82 |
) { |
|
|
83 |
# Create the object and populate it |
|
|
84 |
S7::new_object( |
|
|
85 |
# Parent class |
|
|
86 |
S7::S7_object(), |
|
|
87 |
# weeks * treatments * no_centres |
|
|
88 |
accrual = array( |
|
|
89 |
# Defaults to 0 for no patients recruited |
|
|
90 |
as.integer(0), |
|
|
91 |
dim = c( |
|
|
92 |
# Max. weeks |
|
|
93 |
accrual_period, |
|
|
94 |
# No. experimental arms including control |
|
|
95 |
length(treatment_arm_ids) + |
|
|
96 |
ifelse(shared_control, 1, length(treatment_arm_ids)), |
|
|
97 |
# No. sites |
|
|
98 |
length(unique(centres_df$site)) |
|
|
99 |
), |
|
|
100 |
dimnames = list( |
|
|
101 |
Weeks = NULL, |
|
|
102 |
Arms = c( |
|
|
103 |
names(treatment_arm_ids), |
|
|
104 |
if (shared_control) { |
|
|
105 |
"Control" |
|
|
106 |
} else { |
|
|
107 |
paste("C", names(treatment_arm_ids), sep = "-") |
|
|
108 |
} |
|
|
109 |
), |
|
|
110 |
Centres = c(paste("Centre", unique(centres_df$site))) |
|
|
111 |
) |
|
|
112 |
), |
|
|
113 |
target_arm_size = as.integer(target_arm_size), |
|
|
114 |
target_control = as.integer(target_control), |
|
|
115 |
target_interim = as.integer(target_interim), |
|
|
116 |
accrual_period = accrual_period, |
|
|
117 |
interim_period = interim_period, |
|
|
118 |
control_ratio = control_ratio, |
|
|
119 |
phase_changes = rep(NA_integer_, length(treatment_arm_ids)), |
|
|
120 |
site_closures = rep(NA_integer_, length(unique(centres_df$site))), |
|
|
121 |
week = as.integer(1), |
|
|
122 |
active_arms = seq_len(length(treatment_arm_ids)), |
|
|
123 |
active_sites = seq_len(length(unique(centres_df$site))), |
|
|
124 |
shared_control = shared_control, |
|
|
125 |
site_in_region = as.integer(centres_df$region), |
|
|
126 |
site_cap = as.integer(centres_df$site_cap), |
|
|
127 |
site_mean_rate = as.numeric(centres_df$mean_rate), |
|
|
128 |
site_rate = NA_real_, |
|
|
129 |
site_start_week = as.integer(centres_df$start_week), |
|
|
130 |
site_index = as.integer(centres_df$site), |
|
|
131 |
treatment_arm_ids = treatment_arm_ids |
|
|
132 |
) |
|
|
133 |
} |
|
|
134 |
) |
|
|
135 |
|
|
|
136 |
|
|
|
137 |
#' Sum accrual array by site, to enable checking against site caps |
|
|
138 |
#' @param obj Object of class "accrual" |
|
|
139 |
#' @return vector of total accrual by recruitment site |
|
|
140 |
#' |
|
|
141 |
#' @export |
|
|
142 |
#' |
|
|
143 |
site_sums <- S7::new_generic("site_sums", "obj") |
|
|
144 |
S7::method(site_sums, accrual) <- function(obj) { |
|
|
145 |
# Sum across dimensions 1:dims |
|
|
146 |
colSums(obj@accrual, dims = 2) |
|
|
147 |
} |
|
|
148 |
|
|
|
149 |
|
|
|
150 |
# Sum accrual array by experimental arm (including control). |
|
|
151 |
#' |
|
|
152 |
#' @param x Accrual array with dimensions Weeks, Arms and Centres. |
|
|
153 |
#' @export |
|
|
154 |
treat_sums <- function(x, ...) { |
|
|
155 |
UseMethod("treat_sums", x) |
|
|
156 |
} |
|
|
157 |
|
|
|
158 |
#' Sum accrual array by experimental arm (including control). |
|
|
159 |
#' |
|
|
160 |
#' @param x Accrual array with dimensions Weeks, Arms and Centres. |
|
|
161 |
#' @param no_treat_arms Number of treatment arms (as opposed to control |
|
|
162 |
#' arms). |
|
|
163 |
#' @param shared_control TRUE if all treatment arms share the |
|
|
164 |
#' same control arm; FALSE if each treatment arm has its own |
|
|
165 |
#' control. Defaults to TRUE. |
|
|
166 |
#' @param control_total Logical; if TRUE return single total for all |
|
|
167 |
#' control arms (not used if `shared_control` is TRUE); defaults to FALSE. |
|
|
168 |
#' |
|
|
169 |
#' @return vector of total accrual by experimental arm. |
|
|
170 |
#' |
|
|
171 |
#' @export |
|
|
172 |
#' |
|
|
173 |
treat_sums.array <- function( |
|
|
174 |
x, |
|
|
175 |
control_total = FALSE, |
|
|
176 |
no_treat_arms, |
|
|
177 |
shared_control = TRUE, |
|
|
178 |
na.rm = TRUE |
|
|
179 |
) { |
|
|
180 |
# Permute the array so that the first dimension is the |
|
|
181 |
# dimension you want to get sums for (experimental arms) |
|
|
182 |
arm_sums <- |
|
|
183 |
as.integer(colSums(rowSums(x, dims = 2, na.rm = na.rm))) |
|
|
184 |
|
|
|
185 |
# If want total for control arms rather than separate values |
|
|
186 |
if (!shared_control && control_total) { |
|
|
187 |
arm_sums <- c( |
|
|
188 |
# Treatment arms |
|
|
189 |
arm_sums[seq_along(no_treat_arms)], |
|
|
190 |
# Control |
|
|
191 |
sum(arm_sums[seq(no_treat_arms + 1, length.out = no_treat_arms)]) |
|
|
192 |
) |
|
|
193 |
} |
|
|
194 |
|
|
|
195 |
return(arm_sums) |
|
|
196 |
} |
|
|
197 |
|
|
|
198 |
|
|
|
199 |
#' Sum accrual array element of accrual object, by experimental |
|
|
200 |
#' arm (including control). |
|
|
201 |
#' |
|
|
202 |
#' @name treat_sums |
|
|
203 |
#' |
|
|
204 |
#' @param x Object of class `accrual`. |
|
|
205 |
#' @param control_total Logical; if TRUE return single total for all |
|
|
206 |
#' control arms |
|
|
207 |
#' |
|
|
208 |
#' @return vector of total accrual by experimental arm |
|
|
209 |
#' |
|
|
210 |
#' @export |
|
|
211 |
#' |
|
|
212 |
`treat_sums.biomkrAccrual::accrual` <- function( |
|
|
213 |
x, |
|
|
214 |
control_total = FALSE, |
|
|
215 |
na.rm = TRUE |
|
|
216 |
) { |
|
|
217 |
|
|
|
218 |
# Call treat_sums.array() on accrual array element |
|
|
219 |
treat_sums( |
|
|
220 |
x@accrual, |
|
|
221 |
control_total, |
|
|
222 |
length(x@phase_changes), |
|
|
223 |
x@shared_control, |
|
|
224 |
na.rm |
|
|
225 |
) |
|
|
226 |
} |
|
|
227 |
|
|
|
228 |
|
|
|
229 |
#' Select arms to cap for single site, or sites to cap for single arm |
|
|
230 |
#' @param population Vector of allocations of patients this week |
|
|
231 |
#' @param captotal Value of cap for site or arm (as appropriate) |
|
|
232 |
#' @return Vector of arms to cap (can include multiples of the same arm) |
|
|
233 |
#' |
|
|
234 |
do_choose_cap <- function(population, captotal) { |
|
|
235 |
if (length(population) == captotal) { |
|
|
236 |
# Use whole population if correct length |
|
|
237 |
capped <- population |
|
|
238 |
} else { |
|
|
239 |
# Sample |
|
|
240 |
capped <- sample(population, size = captotal) |
|
|
241 |
} |
|
|
242 |
|
|
|
243 |
return(capped) |
|
|
244 |
} |
|
|
245 |
|
|
|
246 |
|
|
|
247 |
#' Get no. weeks from months |
|
|
248 |
#' Currently 4 week month, fix later using lubridate |
|
|
249 |
#' @param months Duration in months |
|
|
250 |
#' @return Duration in weeks |
|
|
251 |
#' |
|
|
252 |
get_weeks <- function(months) { |
|
|
253 |
as.integer(round(months * 4, 0)) |
|
|
254 |
} |
|
|
255 |
|
|
|
256 |
|
|
|
257 |
#' Method to increment site rates by gamma-distributed rates of sites |
|
|
258 |
#' opening an accrual pathway in the current week. |
|
|
259 |
#' @param obj An object of type "accrual" |
|
|
260 |
#' @return Modified object with new site rates |
|
|
261 |
#' |
|
|
262 |
#' @importFrom stats rgamma |
|
|
263 |
#' |
|
|
264 |
set_site_rates <- S7::new_generic("site_start_rates", "obj") |
|
|
265 |
S7::method(set_site_rates, accrual) <- function(obj, fixed_site_rates) { |
|
|
266 |
|
|
|
267 |
# If this is the first time calling this, initialise with rate 0 |
|
|
268 |
if (any(is.na(obj@site_rate))) { |
|
|
269 |
obj@site_rate <- rep(0, dim(obj@accrual)[3]) |
|
|
270 |
} |
|
|
271 |
|
|
|
272 |
indices <- which(obj@site_start_week == obj@week) |
|
|
273 |
|
|
|
274 |
if (length(indices) > 0) { |
|
|
275 |
|
|
|
276 |
# mean_rates are in recruitment per month, convert to weeks |
|
|
277 |
if (fixed_site_rates) { |
|
|
278 |
rates <- obj@site_mean_rate(indices) / 4 |
|
|
279 |
} else { |
|
|
280 |
var_lambda <- 0.25 |
|
|
281 |
rates <- 0.25 * stats::rgamma( |
|
|
282 |
n = length(indices), |
|
|
283 |
shape = obj@site_mean_rate[indices]^2 / var_lambda, |
|
|
284 |
# Per week not per month |
|
|
285 |
rate = obj@site_mean_rate[indices] / var_lambda |
|
|
286 |
) |
|
|
287 |
} |
|
|
288 |
|
|
|
289 |
# When multiple recruitment sources at a given site, want them |
|
|
290 |
# to stack |
|
|
291 |
obj@site_rate[obj@site_index[indices]] <- |
|
|
292 |
obj@site_rate[obj@site_index[indices]] + rates |
|
|
293 |
|
|
|
294 |
} |
|
|
295 |
|
|
|
296 |
return(obj) |
|
|
297 |
} |
|
|
298 |
|
|
|
299 |
|
|
|
300 |
#' Implement site cap on a week's accrual. |
|
|
301 |
#' @param obj Accrual object |
|
|
302 |
#' @param site_cap Maximum number of patients per site |
|
|
303 |
#' |
|
|
304 |
#' @return Modified accrual object with capped week's accrual and |
|
|
305 |
#' with any capped sites removed from active_sites |
|
|
306 |
#' |
|
|
307 |
apply_site_cap <- S7::new_generic("apply_site_cap", "obj") |
|
|
308 |
S7::method(apply_site_cap, accrual) <- function(obj) { |
|
|
309 |
# If any sites exceed their cap, remove accrual from randomly |
|
|
310 |
# selected arms until sites are at cap |
|
|
311 |
# Represents sites closing during the week |
|
|
312 |
site_captotal <- site_sums(obj) - obj@site_cap |
|
|
313 |
|
|
|
314 |
if (any(site_captotal > 0)) { |
|
|
315 |
# Loop over sites which are above the cap |
|
|
316 |
for (site in which(site_captotal > 0)) { |
|
|
317 |
# Vector of instances of populated arms in week's accrual, |
|
|
318 |
# including control, e.g. c(1, 1, 2, 4) |
|
|
319 |
population <- unlist(sapply( |
|
|
320 |
which(obj@accrual[obj@week, , site] > 0), |
|
|
321 |
function(j) rep(j, obj@accrual[obj@week, j, site]) |
|
|
322 |
)) |
|
|
323 |
|
|
|
324 |
# Randomly select population instances to remove, leaving |
|
|
325 |
# enough to max out the cap |
|
|
326 |
if (length(population) > 0) { |
|
|
327 |
capped <- do_choose_cap(population, site_captotal[site]) |
|
|
328 |
|
|
|
329 |
# Remove those instances |
|
|
330 |
for (i in capped) { |
|
|
331 |
obj@accrual[obj@week, i, site] <- |
|
|
332 |
as.integer(obj@accrual[obj@week, i, site] - 1) |
|
|
333 |
} |
|
|
334 |
} |
|
|
335 |
} |
|
|
336 |
} |
|
|
337 |
|
|
|
338 |
# Don't remove sites from active_sites here, because they may |
|
|
339 |
# be reinstated during arm capping |
|
|
340 |
|
|
|
341 |
return(obj) |
|
|
342 |
} |
|
|
343 |
|
|
|
344 |
|
|
|
345 |
#' Implement arm cap on week's accrual to experimental arms |
|
|
346 |
#' @param accrual_obj Object of class `accrual` |
|
|
347 |
#' @param struct_obj Object of class `trial_structure` |
|
|
348 |
#' |
|
|
349 |
#' @return Modified accrual object with capped week's accrual |
|
|
350 |
#' |
|
|
351 |
#' @importFrom rlang abort |
|
|
352 |
#' |
|
|
353 |
apply_arm_cap <- |
|
|
354 |
S7::new_generic("apply_arm_cap", c("accrual_obj", "struct_obj")) |
|
|
355 |
S7::method(apply_arm_cap, list(accrual, trial_structure)) <- |
|
|
356 |
function(accrual_obj, struct_obj) { |
|
|
357 |
|
|
|
358 |
# Get totals for experimental arms (dropping control) |
|
|
359 |
arm_sums <- |
|
|
360 |
treat_sums(accrual_obj)[seq_len(length(accrual_obj@phase_changes))] |
|
|
361 |
|
|
|
362 |
# Compare with cap |
|
|
363 |
arm_captotal <- arm_sums - accrual_obj@target_arm_size |
|
|
364 |
|
|
|
365 |
# Inactive arms can be at cap but not exceed it |
|
|
366 |
if (any(arm_captotal[-accrual_obj@active_arms] > 0)) { |
|
|
367 |
rlang::abort(paste( |
|
|
368 |
"Inactive arm exceeded cap:", |
|
|
369 |
which(arm_captotal[-accrual_obj@active_arms] > 0) |
|
|
370 |
)) |
|
|
371 |
} |
|
|
372 |
|
|
|
373 |
### Change references to active arms |
|
|
374 |
|
|
|
375 |
# Active arm indices which exceed cap |
|
|
376 |
active_tocap <- which(arm_captotal > 0) |
|
|
377 |
|
|
|
378 |
if (length(active_tocap) > 0) { |
|
|
379 |
for (arm in active_tocap) { |
|
|
380 |
# Which sites recruited to that arm this week? |
|
|
381 |
population <- as.integer(unlist(sapply( |
|
|
382 |
which(accrual_obj@accrual[accrual_obj@week, arm, ] > 0), |
|
|
383 |
function(j) rep(j, accrual_obj@accrual[accrual_obj@week, arm, j]) |
|
|
384 |
))) |
|
|
385 |
|
|
|
386 |
# Possible accruals to remove |
|
|
387 |
if (length(population) > 0) { |
|
|
388 |
capped <- do_choose_cap(population, arm_captotal[arm]) |
|
|
389 |
|
|
|
390 |
# Remove capped instances |
|
|
391 |
for (i in capped) { |
|
|
392 |
accrual_obj@accrual[accrual_obj@week, arm, i] <- |
|
|
393 |
as.integer(accrual_obj@accrual[accrual_obj@week, arm, i] - 1) |
|
|
394 |
} |
|
|
395 |
} |
|
|
396 |
} |
|
|
397 |
} |
|
|
398 |
|
|
|
399 |
# Record closing week for capped arms |
|
|
400 |
capped_arms <- arm_captotal[accrual_obj@active_arms] >= 0 |
|
|
401 |
|
|
|
402 |
accrual_obj@phase_changes[accrual_obj@active_arms[capped_arms]] <- |
|
|
403 |
accrual_obj@week |
|
|
404 |
|
|
|
405 |
|
|
|
406 |
# Update active_arms |
|
|
407 |
accrual_obj@active_arms <- which(arm_captotal < 0) |
|
|
408 |
# Also on the trial structure object |
|
|
409 |
struct_obj <- remove_treat_arms(struct_obj, which(arm_captotal >= 0)) |
|
|
410 |
|
|
|
411 |
# Recheck site caps |
|
|
412 |
site_captotal <- site_sums(accrual_obj) - accrual_obj@site_cap |
|
|
413 |
|
|
|
414 |
# Record closing week for capped sites |
|
|
415 |
capped_sites <- site_captotal[accrual_obj@active_sites] >= 0 |
|
|
416 |
accrual_obj@site_closures[accrual_obj@active_sites[capped_sites]] <- |
|
|
417 |
accrual_obj@week |
|
|
418 |
|
|
|
419 |
# Update active sites |
|
|
420 |
accrual_obj@active_sites <- which(site_captotal < 0) |
|
|
421 |
|
|
|
422 |
return(list(accrual_obj, struct_obj)) |
|
|
423 |
} |
|
|
424 |
|
|
|
425 |
|
|
|
426 |
|
|
|
427 |
|
|
|
428 |
#' Randomise a week's expected accrual amongst the sites, according to |
|
|
429 |
#' prevalence. |
|
|
430 |
#' @param accrual_obj An object of class `accrual`. |
|
|
431 |
#' @param struct_obj An object of class `trial_structure`. |
|
|
432 |
#' @param fixed_site_rates TRUE if centre recruitment rates should |
|
|
433 |
#' be treated as exact; FALSE if they should be drawn from a gamma |
|
|
434 |
#' distribution with a mean of the specified rate. |
|
|
435 |
#' |
|
|
436 |
#' @return Matrix of week's accrual by site and recruitment arm. |
|
|
437 |
#' |
|
|
438 |
week_accrue <- S7::new_generic("week_accrue", c("accrual_obj", "struct_obj")) |
|
|
439 |
S7::method(week_accrue, list(accrual, trial_structure)) <- |
|
|
440 |
function(accrual_obj, struct_obj, fixed_site_rates) { |
|
|
441 |
|
|
|
442 |
# Update the site rates |
|
|
443 |
accrual_obj <- set_site_rates(accrual_obj, fixed_site_rates) |
|
|
444 |
|
|
|
445 |
# Initialising (sites * experimental arms) |
|
|
446 |
week_mx <- matrix( |
|
|
447 |
as.integer(0), |
|
|
448 |
nrow = dim(accrual_obj@accrual)[2], |
|
|
449 |
ncol = dim(accrual_obj@accrual)[3] |
|
|
450 |
) |
|
|
451 |
|
|
|
452 |
week_acc <- as.integer(rep(0, dim(accrual_obj@accrual)[3])) |
|
|
453 |
|
|
|
454 |
# Accrual per site is poisson distributed |
|
|
455 |
week_acc[accrual_obj@active_sites] <- rpois( |
|
|
456 |
n = length(accrual_obj@active_sites), |
|
|
457 |
lambda = accrual_obj@site_rate[accrual_obj@active_sites] |
|
|
458 |
) |
|
|
459 |
|
|
|
460 |
# Loop over recruiting sites |
|
|
461 |
for (isite in which(week_acc > 0)) { |
|
|
462 |
|
|
|
463 |
# Total probability for each experimental arm for the |
|
|
464 |
# relevant site prevalence set |
|
|
465 |
probs <- colSums(struct_obj@experimental_arm_prevalence[ |
|
|
466 |
, , accrual_obj@site_in_region[isite] |
|
|
467 |
]) |
|
|
468 |
|
|
|
469 |
|
|
|
470 |
# Sample experimental arms according to probabilities |
|
|
471 |
# Adding a dummy arm to take unassigned allocations due |
|
|
472 |
# to arm closure, representing reduced site recruitment |
|
|
473 |
assigns <- sample( |
|
|
474 |
seq_len(length(probs) + 1), |
|
|
475 |
prob = c(probs, 1 - sum(probs)), |
|
|
476 |
size = week_acc[isite], |
|
|
477 |
replace = TRUE |
|
|
478 |
) |
|
|
479 |
|
|
|
480 |
# Total assignments to each open arm plus dummy arm |
|
|
481 |
assign_table <- table(assigns) |
|
|
482 |
indices <- as.numeric(names(assign_table)) |
|
|
483 |
|
|
|
484 |
# Drop the dummy arm if anything was assigned to it |
|
|
485 |
if (max(indices) > length(probs)) { |
|
|
486 |
assign_table <- assign_table[-length(assign_table)] |
|
|
487 |
indices <- indices[-length(indices)] |
|
|
488 |
} |
|
|
489 |
|
|
|
490 |
# Increment week's assignment matrix |
|
|
491 |
week_mx[indices, isite] <- |
|
|
492 |
week_mx[indices, isite] + as.vector(assign_table) |
|
|
493 |
} |
|
|
494 |
|
|
|
495 |
return(list(accrual_obj, week_mx)) |
|
|
496 |
} |
|
|
497 |
|
|
|
498 |
|
|
|
499 |
#' Generate accrual for a week and assign it to the |
|
|
500 |
#' accrual object. Increments the property "week", |
|
|
501 |
#' which holds the next week number to accrue. |
|
|
502 |
#' @param accrual_obj An object of class "accrual" |
|
|
503 |
#' @param struct_obj An object of class "trial_structure" |
|
|
504 |
#' @param fixed_site_rates TRUE if expected site rate to be used; FALSE |
|
|
505 |
#' draws the site rate from a gamma distribution |
|
|
506 |
#' |
|
|
507 |
#' @return An object of class "accrual" |
|
|
508 |
#' |
|
|
509 |
accrue_week <- S7::new_generic("accrue_week", c("accrual_obj", "struct_obj")) |
|
|
510 |
S7::method(accrue_week, list(accrual, trial_structure)) <- |
|
|
511 |
function(accrual_obj, struct_obj, fixed_site_rates) { |
|
|
512 |
|
|
|
513 |
# Should not get here if there aren't any but |
|
|
514 |
if (length(accrual_obj@active_sites) > 0) { |
|
|
515 |
|
|
|
516 |
week_acc_ls <- week_accrue(accrual_obj, struct_obj, fixed_site_rates) |
|
|
517 |
accrual_obj <- week_acc_ls[[1]] |
|
|
518 |
week_acc <- week_acc_ls[[2]] |
|
|
519 |
|
|
|
520 |
# Assign the week's accrual to the object |
|
|
521 |
accrual_obj@accrual[accrual_obj@week, , ] <- |
|
|
522 |
week_acc |
|
|
523 |
|
|
|
524 |
# Apply site cap |
|
|
525 |
accrual_obj <- apply_site_cap(accrual_obj) |
|
|
526 |
|
|
|
527 |
# Apply arm cap, and then adjust site cap appropriately |
|
|
528 |
obj_list <- apply_arm_cap(accrual_obj, struct_obj) |
|
|
529 |
accrual_obj <- obj_list[[1]] |
|
|
530 |
struct_obj <- obj_list[[2]] |
|
|
531 |
|
|
|
532 |
} |
|
|
533 |
|
|
|
534 |
return(list(accrual_obj, struct_obj)) |
|
|
535 |
} |
|
|
536 |
|
|
|
537 |
|
|
|
538 |
|
|
|
539 |
#' Check whether an object is of class "accrual". |
|
|
540 |
#' |
|
|
541 |
#' @param x Object to test |
|
|
542 |
#' |
|
|
543 |
#' @return TRUE or FALSE |
|
|
544 |
#' |
|
|
545 |
#' @importFrom S7 new_generic method class_any |
|
|
546 |
#' |
|
|
547 |
#' @export |
|
|
548 |
#' |
|
|
549 |
is.accrual <- S7::new_generic("is.accrual", "x") |
|
|
550 |
S7::method(is.accrual, S7::class_any) <- function(x) { |
|
|
551 |
inherits(x, "biomkrAccrual::accrual") |
|
|
552 |
} |
|
|
553 |
|
|
|
554 |
|