| 1 |
#' Re-used documentation for Brier Score components |
|
| 2 |
#' |
|
| 3 |
#' @typed t: numeric |
|
| 4 |
#' timepoints to calculate the desired quantity at. |
|
| 5 |
#' @typed times: numeric |
|
| 6 |
#' observed times. |
|
| 7 |
#' @typed events: numeric |
|
| 8 |
#' event indicator for `times`. Either 1 for an event or 0 for censor. |
|
| 9 |
#' @typed event_offset: logical |
|
| 10 |
#' If `TRUE` then \eqn{G(T_i)} is evaluated at \eqn{G(T_i-)}.
|
|
| 11 |
#' Setting this as `TRUE` mirrors the implementation of the `{pec}` package.
|
|
| 12 |
#' @typed maintain_cen_order: logical |
|
| 13 |
#' If `TRUE` then, in the case of ties, |
|
| 14 |
#' censor times are always considered |
|
| 15 |
#' to have occurred after the event times when calculating the "reverse Kaplan-Meier" for the |
|
| 16 |
#' IPCW estimates. Setting this to `TRUE` mirrors the implementation of the `{prodlim}`
|
|
| 17 |
#' package. |
|
| 18 |
#' @param ... not used. |
|
| 19 |
#' |
|
| 20 |
#' @name Brier-Score-Shared |
|
| 21 |
#' @keywords internal |
|
| 22 |
NULL |
|
| 23 | ||
| 24 | ||
| 25 |
#' Reverse Kaplan-Meier |
|
| 26 |
#' |
|
| 27 |
#' @inheritParams Brier-Score-Shared |
|
| 28 |
#' @description |
|
| 29 |
#' Calculates the survival estimates of the censoring distribution |
|
| 30 |
#' using the Kaplan-Meier estimate. |
|
| 31 |
#' This is primarily used in the calculation of the IPCW estimates. |
|
| 32 |
#' |
|
| 33 |
#' @details |
|
| 34 |
#' With regards to ties between censor and event times; the standard |
|
| 35 |
#' approach is to regard events as occurring before censors. However, |
|
| 36 |
#' when modelling the censoring distribution we are regarding the |
|
| 37 |
#' censors as "events" so which should come first in the case of ties? |
|
| 38 |
#' |
|
| 39 |
#' The `reverse_km_event_first()` function maintains the rule |
|
| 40 |
#' that events always come first even if we are regarding the censors |
|
| 41 |
#' as "events". This matches the implementation of |
|
| 42 |
#' `prodlim::prodlim(..., reverse = TRUE)`. |
|
| 43 |
#' |
|
| 44 |
#' The `reverse_km_cen_first()` function provides the alternative |
|
| 45 |
#' implementation assuming that in the case of ties the censor "events" |
|
| 46 |
#' come before the event "censors". This is essentially a thin wrapper |
|
| 47 |
#' around `survival::survfit(Surv(time, 1 - event), ...)` |
|
| 48 |
#' |
|
| 49 |
#' @name reverse_km |
|
| 50 |
#' @keywords internal |
|
| 51 |
#' |
|
| 52 |
#' @returns The reverse Kaplan-Meier estimate at each requested time. |
|
| 53 |
reverse_km_event_first <- function(t, times, events) {
|
|
| 54 | 9x |
assert_numeric(t, any.missing = FALSE, finite = TRUE) |
| 55 | 9x |
assert_numeric(times, any.missing = FALSE, finite = TRUE) |
| 56 | 9x |
assert_numeric(events, any.missing = FALSE, finite = TRUE) |
| 57 | 9x |
assert_that( |
| 58 | 9x |
length(times) == length(events), |
| 59 | 9x |
all(events == 1 | events == 0) |
| 60 |
) |
|
| 61 | 9x |
events_cen <- 1 - events |
| 62 | 9x |
ord <- order(times, events_cen) |
| 63 | 9x |
times <- times[ord] |
| 64 | 9x |
events <- events[ord] |
| 65 | 9x |
events_cen <- events_cen[ord] |
| 66 | 9x |
cs_events <- cumsum(events) |
| 67 | 9x |
cs_censor <- cumsum(events_cen) |
| 68 | ||
| 69 | 9x |
g_times <- unique(times) |
| 70 | 9x |
g_n_events_cen <- tapply(events_cen, times, sum) |
| 71 | 9x |
g_cs_events_cen <- tapply(cs_censor, times, max) |
| 72 | 9x |
g_cs_events <- tapply(cs_events, times, max) |
| 73 | 9x |
g_is_cen <- tapply(events_cen, times, max) |
| 74 | ||
| 75 | 9x |
nrisk <- length(times) - g_cs_events - g_cs_events_cen + g_n_events_cen |
| 76 | 9x |
surv_interval <- 1 - g_n_events_cen / nrisk |
| 77 | 9x |
surv_interval <- ifelse(nrisk == 0, 1, surv_interval) |
| 78 | 9x |
surv <- cumprod(surv_interval) |
| 79 | ||
| 80 | 9x |
ct <- g_times[which(g_is_cen == 1)] |
| 81 | 9x |
sv <- surv[which(g_is_cen == 1)] |
| 82 | ||
| 83 | 9x |
names(surv) <- NULL |
| 84 | 9x |
names(ct) <- NULL |
| 85 | 9x |
names(sv) <- NULL |
| 86 | 9x |
list( |
| 87 | 9x |
t = t, |
| 88 | 9x |
surv = c(1, sv)[findInterval(t, ct) + 1] |
| 89 |
) |
|
| 90 |
} |
|
| 91 | ||
| 92 | ||
| 93 |
#' @rdname reverse_km |
|
| 94 |
reverse_km_cen_first <- function(t, times, events) {
|
|
| 95 | 1x |
assert_numeric(t, any.missing = FALSE, finite = TRUE) |
| 96 | 1x |
assert_numeric(times, any.missing = FALSE, finite = TRUE) |
| 97 | 1x |
assert_numeric(events, any.missing = FALSE, finite = TRUE) |
| 98 | 1x |
assert_that( |
| 99 | 1x |
length(times) == length(events), |
| 100 | 1x |
all(events == 1 | events == 0) |
| 101 |
) |
|
| 102 | 1x |
dat <- data.frame( |
| 103 | 1x |
times = times, |
| 104 | 1x |
events = events, |
| 105 | 1x |
cen_events = 1 - events |
| 106 |
) |
|
| 107 | 1x |
mod <- survival::survfit( |
| 108 | 1x |
survival::Surv(times, cen_events) ~ 1, |
| 109 | 1x |
data = dat |
| 110 |
) |
|
| 111 | 1x |
preds <- summary(mod, times = t[order(t)], extend = TRUE)$surv |
| 112 | ||
| 113 | 1x |
assert_that( |
| 114 | 1x |
length(preds) == length(t) |
| 115 |
) |
|
| 116 | 1x |
list( |
| 117 | 1x |
t = t, |
| 118 | 1x |
surv = preds[match_order(t)] |
| 119 |
) |
|
| 120 |
} |
|
| 121 | ||
| 122 |
#' Match Order |
|
| 123 |
#' |
|
| 124 |
#' @typed x: numeric |
|
| 125 |
#' a vector for which we want to generate an index so other |
|
| 126 |
#' vectors can be put into the same sort order |
|
| 127 |
#' |
|
| 128 |
#' Assuming we have a vector that is sorted then this function |
|
| 129 |
#' will return the index vector to convert that sorted vector |
|
| 130 |
#' into the same sort order as the input vector `x`. |
|
| 131 |
#' For example let `x = 8, 7, 9 , 7`. If sorted we would get |
|
| 132 |
#' `x_sort = 7, 7, 8, 9`. So in order to convert `x_sort` |
|
| 133 |
#' back into `x` we'd need an index vector of `3, 1, 4, 2`. |
|
| 134 |
#' This function is used to determine that corresponding |
|
| 135 |
#' index vector for an arbitrarily sorted vector `x`. |
|
| 136 |
#' |
|
| 137 |
#' There is no specific handling of ties. It is assuming that in the case |
|
| 138 |
#' of ties for `x` that the vector you are re-indexing also has tied values |
|
| 139 |
#' thus the specific tied element selection does not matter. |
|
| 140 |
#' @keywords internal |
|
| 141 |
#' |
|
| 142 |
#' @returns An integer vector giving the order that groups identical values. |
|
| 143 |
match_order <- function(x) {
|
|
| 144 | 2x |
order(order(x)) |
| 145 |
} |
|
| 146 | ||
| 147 | ||
| 148 |
#' Brier Score |
|
| 149 |
#' |
|
| 150 |
#' @inheritParams Brier-Score-Shared |
|
| 151 |
#' |
|
| 152 |
#' @description |
|
| 153 |
#' Implements the Brier Score as detailed in \insertCite{blanche2015}{jmpost}
|
|
| 154 |
#' |
|
| 155 |
#' @details |
|
| 156 |
#' - `bs_get_squared_dist()` - implements the squared distance part |
|
| 157 |
#' of the formula. |
|
| 158 |
#' - `bs_get_weights()` - implements the IPCW weighting |
|
| 159 |
#' |
|
| 160 |
#' @references |
|
| 161 |
#' \insertAllCited{}
|
|
| 162 |
#' |
|
| 163 |
#' @keywords internal |
|
| 164 |
#' |
|
| 165 |
#' @returns A numeric vector or matrix of Brier-score components. |
|
| 166 |
brier_score <- function( |
|
| 167 |
t, |
|
| 168 |
times, |
|
| 169 |
events, |
|
| 170 |
pred_mat, |
|
| 171 |
maintain_cen_order = TRUE, |
|
| 172 |
event_offset = TRUE |
|
| 173 |
) {
|
|
| 174 | 4x |
square_diff_mat <- bs_get_squared_dist( |
| 175 | 4x |
t = t, |
| 176 | 4x |
times = times, |
| 177 | 4x |
events = events, |
| 178 | 4x |
pred_mat = pred_mat |
| 179 |
) |
|
| 180 | ||
| 181 | 4x |
weight_mat <- bs_get_weights( |
| 182 | 4x |
t = t, |
| 183 | 4x |
times = times, |
| 184 | 4x |
events = events, |
| 185 | 4x |
event_offset = event_offset, |
| 186 | 4x |
maintain_cen_order = maintain_cen_order |
| 187 |
) |
|
| 188 | ||
| 189 |
# the following is a computational shortcut for diag(A %*% B) |
|
| 190 |
# as we don't want to compute the off-diagonal entries of the |
|
| 191 |
# matrix multiplication |
|
| 192 | 4x |
x <- colSums(weight_mat * square_diff_mat) |
| 193 | 4x |
names(x) <- t |
| 194 | 4x |
x / length(times) |
| 195 |
} |
|
| 196 | ||
| 197 | ||
| 198 |
#' @rdname brier_score |
|
| 199 |
bs_get_squared_dist <- function(t, times, events, pred_mat) {
|
|
| 200 | 5x |
assert_numeric( |
| 201 | 5x |
times, |
| 202 | 5x |
finite = TRUE, |
| 203 | 5x |
any.missing = FALSE |
| 204 |
) |
|
| 205 | 5x |
assert_numeric( |
| 206 | 5x |
t, |
| 207 | 5x |
finite = TRUE, |
| 208 | 5x |
any.missing = FALSE |
| 209 |
) |
|
| 210 | 5x |
assert_numeric( |
| 211 | 5x |
events, |
| 212 | 5x |
finite = TRUE, |
| 213 | 5x |
any.missing = FALSE, |
| 214 | 5x |
lower = 0, |
| 215 | 5x |
upper = 1 |
| 216 |
) |
|
| 217 | 5x |
assert_matrix( |
| 218 | 5x |
pred_mat, |
| 219 | 5x |
any.missing = FALSE, |
| 220 | 5x |
nrows = length(times), |
| 221 | 5x |
ncols = length(t) |
| 222 |
) |
|
| 223 | 5x |
assert_that( |
| 224 | 5x |
length(events) == length(times) |
| 225 |
) |
|
| 226 | ||
| 227 | 5x |
expected_mat <- mapply( |
| 228 | 5x |
\(ti, event) (ti <= t) * event * 1, |
| 229 | 5x |
ti = times, |
| 230 | 5x |
event = events, |
| 231 | 5x |
SIMPLIFY = FALSE |
| 232 |
) |> |
|
| 233 | 5x |
unlist() |> |
| 234 | 5x |
matrix(ncol = length(t), byrow = TRUE) |
| 235 | ||
| 236 | 5x |
assert_that( |
| 237 | 5x |
nrow(pred_mat) == nrow(expected_mat), |
| 238 | 5x |
ncol(pred_mat) == ncol(expected_mat) |
| 239 |
) |
|
| 240 | ||
| 241 | 5x |
(expected_mat - pred_mat)^2 |
| 242 |
} |
|
| 243 | ||
| 244 | ||
| 245 |
#' @rdname brier_score |
|
| 246 |
bs_get_weights <- function( |
|
| 247 |
t, |
|
| 248 |
times, |
|
| 249 |
events, |
|
| 250 |
event_offset = TRUE, |
|
| 251 |
maintain_cen_order = TRUE |
|
| 252 |
) {
|
|
| 253 | 5x |
assert_numeric( |
| 254 | 5x |
times, |
| 255 | 5x |
finite = TRUE, |
| 256 | 5x |
any.missing = FALSE |
| 257 |
) |
|
| 258 | 5x |
assert_numeric( |
| 259 | 5x |
t, |
| 260 | 5x |
finite = TRUE, |
| 261 | 5x |
any.missing = FALSE |
| 262 |
) |
|
| 263 | 5x |
assert_numeric( |
| 264 | 5x |
events, |
| 265 | 5x |
finite = TRUE, |
| 266 | 5x |
any.missing = FALSE, |
| 267 | 5x |
lower = 0, |
| 268 | 5x |
upper = 1 |
| 269 |
) |
|
| 270 | 5x |
assert_flag(event_offset, na.ok = FALSE, null.ok = FALSE) |
| 271 | 5x |
assert_flag(maintain_cen_order, na.ok = FALSE, null.ok = FALSE) |
| 272 | 5x |
n_col <- length(t) |
| 273 | 5x |
n_row <- length(times) |
| 274 | ||
| 275 | 5x |
reverse_km <- if (maintain_cen_order) {
|
| 276 | 5x |
reverse_km_event_first |
| 277 |
} else {
|
|
| 278 | ! |
reverse_km_cen_first |
| 279 |
} |
|
| 280 | 5x |
offset <- if (event_offset) -.Machine$double.eps^(1 / 2) else 0 |
| 281 | ||
| 282 | 5x |
censor_dist_t <- reverse_km(t, times, events) |
| 283 | 5x |
weight_mat_t <- 1 / |
| 284 | 5x |
matrix( |
| 285 | 5x |
rep(censor_dist_t$surv, n_row), |
| 286 | 5x |
nrow = n_row, |
| 287 | 5x |
byrow = TRUE |
| 288 |
) |
|
| 289 | ||
| 290 | 5x |
censor_dist_ti <- reverse_km(times + offset, times, events) |
| 291 | 5x |
weight_mat_ti <- 1 / |
| 292 | 5x |
matrix( |
| 293 | 5x |
rep(censor_dist_ti$surv, n_col), |
| 294 | 5x |
nrow = n_row |
| 295 |
) |
|
| 296 | ||
| 297 | 5x |
indicator_mat_t <- mapply( |
| 298 | 5x |
\(ti) (ti > t) * 1, |
| 299 | 5x |
ti = times, |
| 300 | 5x |
SIMPLIFY = FALSE |
| 301 |
) |> |
|
| 302 | 5x |
unlist() |> |
| 303 | 5x |
matrix(ncol = n_col, byrow = TRUE) |
| 304 | ||
| 305 | 5x |
indicator_mat_ti <- mapply( |
| 306 | 5x |
\(ti, event) (ti <= t) * event * 1, |
| 307 | 5x |
ti = times, |
| 308 | 5x |
event = events, |
| 309 | 5x |
SIMPLIFY = FALSE |
| 310 |
) |> |
|
| 311 | 5x |
unlist() |> |
| 312 | 5x |
matrix(ncol = n_col, byrow = TRUE) |
| 313 | ||
| 314 | 5x |
assert_that( |
| 315 | 5x |
all(indicator_mat_t + indicator_mat_ti <= 1) |
| 316 |
) |
|
| 317 | ||
| 318 | 5x |
weight_mat_t[indicator_mat_t == 0] <- 0 |
| 319 | 5x |
weight_mat_ti[indicator_mat_ti == 0] <- 0 |
| 320 | ||
| 321 | 5x |
(weight_mat_t + weight_mat_ti) |
| 322 |
} |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include StanModule.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' `Prior` Function Arguments |
|
| 6 |
#' |
|
| 7 |
#' The documentation lists all the conventional arguments for [`Prior`] |
|
| 8 |
#' constructors. |
|
| 9 |
#' |
|
| 10 |
#' @typed centre: number |
|
| 11 |
#' the central point of distribution to shrink sampled values towards |
|
| 12 |
#' (for most distributions this is the mean or median if the mean is undefined) |
|
| 13 |
#' @typed x: Prior |
|
| 14 |
#' a prior Distribution |
|
| 15 |
#' @typed object: Prior |
|
| 16 |
#' a prior Distribution |
|
| 17 |
#' @typed name: character |
|
| 18 |
#' the name of the parameter the prior distribution is for |
|
| 19 |
#' @typed size: "`numeric` or `character`" |
|
| 20 |
#' the parameter size. |
|
| 21 |
#' @typed transform: PriorTransform | NULL |
|
| 22 |
#' optional package-defined transformation from the model parameter to the |
|
| 23 |
#' prior density scale. See [prior_transform()] for support, Jacobians and |
|
| 24 |
#' examples. `NULL` keeps the prior on the model parameter scale. |
|
| 25 |
#' @param ... Not Used. |
|
| 26 |
#' |
|
| 27 |
#' @name Prior-Shared |
|
| 28 |
#' @keywords internal |
|
| 29 |
NULL |
|
| 30 | ||
| 31 |
# Prior-class ---- |
|
| 32 | ||
| 33 |
#' Prior Object and Constructor Function |
|
| 34 |
#' |
|
| 35 |
#' Specifies the prior distribution in a Stan Model |
|
| 36 |
#' |
|
| 37 |
#' @slot parameters (`list`)\cr See arguments. |
|
| 38 |
#' @slot repr_model (`string`)\cr See arguments. |
|
| 39 |
#' @slot repr_data (`string`)\cr See arguments. |
|
| 40 |
#' @slot repr_parameters (`string`)\cr See arguments. |
|
| 41 |
#' @slot repr_transformed_parameters (`string`)\cr See arguments. |
|
| 42 |
#' @slot repr_generated_quantities (`string`)\cr See arguments. |
|
| 43 |
#' @slot repr_multiplier (`function`)\cr See arguments. |
|
| 44 |
#' @slot auxiliary_initial_values (`function`)\cr See arguments. |
|
| 45 |
#' @slot auxiliary_size (`function`)\cr See arguments. |
|
| 46 |
#' @slot centre (`numeric`)\cr See arguments. |
|
| 47 |
#' @slot validation (`list`)\cr See arguments. |
|
| 48 |
#' @slot display (`string`)\cr See arguments. |
|
| 49 |
#' @slot sample (`function`)\cr See arguments. |
|
| 50 |
#' @slot limits (`numeric`)\cr See arguments. |
|
| 51 |
#' @slot .omit_zero_lower_truncation (`logical`)\cr See arguments. |
|
| 52 |
#' @slot .allow_vectors (`logical`)\cr See arguments. |
|
| 53 |
#' @slot .is_const (`logical`)\cr See arguments. |
|
| 54 |
#' |
|
| 55 |
#' @family Prior-internal |
|
| 56 |
#' @export Prior |
|
| 57 |
#' @exportClass Prior |
|
| 58 |
.Prior <- setClass( |
|
| 59 |
Class = "Prior", |
|
| 60 |
slots = c( |
|
| 61 |
"parameters" = "list", |
|
| 62 |
"display" = "character", |
|
| 63 |
"repr_model" = "character", |
|
| 64 |
"repr_data" = "character", |
|
| 65 |
"repr_parameters" = "character", |
|
| 66 |
"repr_transformed_parameters" = "character", |
|
| 67 |
"repr_generated_quantities" = "character", |
|
| 68 |
"repr_multiplier" = "function", |
|
| 69 |
"auxiliary_initial_values" = "function", |
|
| 70 |
"auxiliary_size" = "function", |
|
| 71 |
"centre" = "numeric", |
|
| 72 |
"validation" = "list", |
|
| 73 |
"sample" = "function", |
|
| 74 |
"limits" = "numeric", |
|
| 75 |
".omit_zero_lower_truncation" = "logical", |
|
| 76 |
".allow_vectors" = "logical", |
|
| 77 |
".is_const" = "logical" |
|
| 78 |
) |
|
| 79 |
) |
|
| 80 | ||
| 81 | ||
| 82 |
#' @typed parameters: list |
|
| 83 |
#' the prior distribution parameters. |
|
| 84 |
#' @inheritParams Prior-Shared |
|
| 85 |
#' @typed repr_model: string |
|
| 86 |
#' the Stan code representation for the model block. |
|
| 87 |
#' @typed repr_data: string |
|
| 88 |
#' the Stan code representation for the data block. |
|
| 89 |
#' @typed repr_parameters: string |
|
| 90 |
#' the Stan code representation for the parameters block. |
|
| 91 |
#' @typed repr_transformed_parameters: string |
|
| 92 |
#' the Stan code representation for the transformed parameters block. |
|
| 93 |
#' @typed repr_generated_quantities: string |
|
| 94 |
#' the Stan code representation for the generated quantities block. |
|
| 95 |
#' @typed repr_multiplier: function |
|
| 96 |
#' a function of `name` and `size` returning a Stan affine multiplier |
|
| 97 |
#' expression for an unbounded parameter, or an empty string for no transform. |
|
| 98 |
#' @typed auxiliary_initial_values: function |
|
| 99 |
#' a function that returns initial values for extra Stan parameters |
|
| 100 |
#' introduced by the prior. |
|
| 101 |
#' @typed auxiliary_size: function |
|
| 102 |
#' a function that returns sizes for extra Stan parameters introduced |
|
| 103 |
#' by the prior. |
|
| 104 |
#' @typed display: string |
|
| 105 |
#' the string to display when object is printed. |
|
| 106 |
#' @typed centre: numeric |
|
| 107 |
#' the central point of distribution to shrink sampled values towards |
|
| 108 |
#' @typed validation: list |
|
| 109 |
#' the prior distribution parameter validation functions. Must have |
|
| 110 |
#' the same names as the `paramaters` slot. |
|
| 111 |
#' @typed sample: function |
|
| 112 |
#' a function to sample from the prior distribution. |
|
| 113 |
#' @typed limits: numeric |
|
| 114 |
#' the lower and upper limits for a truncated distribution. |
|
| 115 |
#' @typed .omit_zero_lower_truncation: flag |
|
| 116 |
#' whether to omit a lower-zero truncation adjustment. |
|
| 117 |
#' @typed .allow_vectors: flag |
|
| 118 |
#' whether to allow vector parameters. |
|
| 119 |
#' @typed .is_const: flag |
|
| 120 |
#' whether this prior fixes the parameter at a constant value. |
|
| 121 |
#' @rdname Prior-class |
|
| 122 |
#' |
|
| 123 |
#' @returns A `Prior` object. |
|
| 124 |
#' |
|
| 125 |
#' @examples |
|
| 126 |
#' # Prior objects are normally created with a distribution helper. |
|
| 127 |
#' prior_normal(0, 1) |
|
| 128 |
Prior <- function( |
|
| 129 |
parameters, |
|
| 130 |
display, |
|
| 131 |
repr_model, |
|
| 132 |
repr_data, |
|
| 133 |
centre, |
|
| 134 |
validation, |
|
| 135 |
sample, |
|
| 136 |
repr_parameters = "", |
|
| 137 |
repr_transformed_parameters = "", |
|
| 138 |
repr_generated_quantities = "", |
|
| 139 |
auxiliary_initial_values = \(name, size) list(), |
|
| 140 |
auxiliary_size = \(name, size) list(), |
|
| 141 |
limits = c(-Inf, Inf), |
|
| 142 |
.omit_zero_lower_truncation = FALSE, |
|
| 143 |
.allow_vectors = FALSE, |
|
| 144 |
.is_const = FALSE, |
|
| 145 |
repr_multiplier = \(name, size) "", |
|
| 146 |
transform = NULL |
|
| 147 |
) {
|
|
| 148 | 2655x |
object <- .Prior( |
| 149 | 2655x |
parameters = parameters, |
| 150 | 2655x |
repr_model = repr_model, |
| 151 | 2655x |
repr_data = repr_data, |
| 152 | 2655x |
repr_parameters = repr_parameters, |
| 153 | 2655x |
repr_transformed_parameters = repr_transformed_parameters, |
| 154 | 2655x |
repr_generated_quantities = repr_generated_quantities, |
| 155 | 2655x |
repr_multiplier = repr_multiplier, |
| 156 | 2655x |
auxiliary_initial_values = auxiliary_initial_values, |
| 157 | 2655x |
auxiliary_size = auxiliary_size, |
| 158 | 2655x |
centre = centre, |
| 159 | 2655x |
display = display, |
| 160 | 2655x |
validation = validation, |
| 161 | 2655x |
sample = sample, |
| 162 | 2655x |
limits = limits, |
| 163 | 2655x |
.omit_zero_lower_truncation = .omit_zero_lower_truncation, |
| 164 | 2655x |
.allow_vectors = .allow_vectors, |
| 165 | 2655x |
.is_const = .is_const |
| 166 |
) |
|
| 167 | 50x |
if (is.null(transform)) object else prior_transform(object, transform) |
| 168 |
} |
|
| 169 | ||
| 170 | ||
| 171 |
setValidity( |
|
| 172 |
Class = "Prior", |
|
| 173 |
method = function(object) {
|
|
| 174 |
for (param in names(object@parameters)) {
|
|
| 175 |
if (!param %in% names(object@validation)) {
|
|
| 176 |
return(sprintf( |
|
| 177 |
"Parameter `%s` does not have a validation method", |
|
| 178 |
param |
|
| 179 |
)) |
|
| 180 |
} |
|
| 181 |
if ( |
|
| 182 |
!object@.allow_vectors && |
|
| 183 |
length(object@parameters[[param]]) != 1 |
|
| 184 |
) {
|
|
| 185 |
return(sprintf("Parameter `%s` must be a single value", param))
|
|
| 186 |
} |
|
| 187 |
if (!object@validation[[param]](object@parameters[[param]])) {
|
|
| 188 |
return_message <- sprintf( |
|
| 189 |
"Invalid value of `%d` for parameter `%s`", |
|
| 190 |
object@parameters[[param]], |
|
| 191 |
param |
|
| 192 |
) |
|
| 193 |
return(return_message) |
|
| 194 |
} |
|
| 195 |
} |
|
| 196 |
if (object@.allow_vectors) {
|
|
| 197 |
# Check that all parameters have either same length or are |
|
| 198 |
# length 1. |
|
| 199 |
all_lengths <- sapply( |
|
| 200 |
object@parameters, |
|
| 201 |
length |
|
| 202 |
) |
|
| 203 |
none_one_lengths <- setdiff(unique(all_lengths), 1) |
|
| 204 |
if (length(none_one_lengths) > 1) {
|
|
| 205 |
return("All parameters must be the same length or length 1")
|
|
| 206 |
} |
|
| 207 |
} |
|
| 208 |
if (length(object@limits) != 2) {
|
|
| 209 |
return("Limits must be a vector of length 2")
|
|
| 210 |
} |
|
| 211 |
if (object@limits[1] >= object@limits[2]) {
|
|
| 212 |
return("Lower limit must be less than upper limit")
|
|
| 213 |
} |
|
| 214 |
if ( |
|
| 215 |
length(object@repr_model) != 1 || !is.character(object@repr_model) |
|
| 216 |
) {
|
|
| 217 |
return("Model representation must be length 1 string")
|
|
| 218 |
} |
|
| 219 |
if ( |
|
| 220 |
length(object@repr_parameters) < 1 || |
|
| 221 |
!is.character(object@repr_parameters) |
|
| 222 |
) {
|
|
| 223 |
return("Parameters representation must be a character vector")
|
|
| 224 |
} |
|
| 225 |
if ( |
|
| 226 |
length(object@repr_transformed_parameters) < 1 || |
|
| 227 |
!is.character(object@repr_transformed_parameters) |
|
| 228 |
) {
|
|
| 229 |
return( |
|
| 230 |
"Transformed parameters representation must be a character vector" |
|
| 231 |
) |
|
| 232 |
} |
|
| 233 |
return(TRUE) |
|
| 234 |
} |
|
| 235 |
) |
|
| 236 | ||
| 237 | ||
| 238 |
#' @rdname set_limits |
|
| 239 |
#' @export |
|
| 240 |
set_limits.Prior <- function(object, lower = -Inf, upper = Inf) {
|
|
| 241 | 727x |
object@limits <- c(lower, upper) |
| 242 | 727x |
validObject(object) |
| 243 | 727x |
return(object) |
| 244 |
} |
|
| 245 | ||
| 246 | ||
| 247 |
#' `Prior` -> `Character` |
|
| 248 |
#' |
|
| 249 |
#' Converts a [`Prior`] object to a character vector |
|
| 250 |
#' @inheritParams Prior-Shared |
|
| 251 |
#' @family Prior-internal |
|
| 252 |
#' @export |
|
| 253 |
#' |
|
| 254 |
#' @returns A character vector. |
|
| 255 |
as.character.Prior <- function(x, ...) {
|
|
| 256 | 208x |
parameters_rounded <- lapply(x@parameters, round, 5) |
| 257 | ||
| 258 | 208x |
display_string <- do.call( |
| 259 | 208x |
glue::glue, |
| 260 | 208x |
append(x@display, parameters_rounded) |
| 261 |
) |
|
| 262 | 208x |
display_limits <- if (x@.is_const) "" else render_stan_limits(x) |
| 263 |
if ( |
|
| 264 | 208x |
display_limits != "" && |
| 265 | 208x |
display_string != "" && |
| 266 | 208x |
display_string != "<None>" |
| 267 |
) {
|
|
| 268 | 20x |
display_string <- paste0(display_string, display_limits) |
| 269 |
} |
|
| 270 | 208x |
return(display_string) |
| 271 |
} |
|
| 272 | ||
| 273 | ||
| 274 |
#' Creates Stan Syntax for Truncated distributions |
|
| 275 |
#' @description |
|
| 276 |
#' This function creates the Stan syntax for truncated distributions |
|
| 277 |
#' @typed object: Prior | numeric |
|
| 278 |
#' prior or lower and upper limits for a truncated distribution. |
|
| 279 |
#' @keywords internal |
|
| 280 |
#' @typedreturn character |
|
| 281 |
#' the Stan syntax for truncated distributions |
|
| 282 |
#' |
|
| 283 |
#' @returns A character vector containing the Stan truncation syntax. |
|
| 284 |
render_stan_limits <- function(object) {
|
|
| 285 | 3085x |
UseMethod("render_stan_limits")
|
| 286 |
} |
|
| 287 | ||
| 288 |
#' @describeIn render_stan_limits method for [Prior] objects. |
|
| 289 |
#' @exportS3Method NULL |
|
| 290 |
render_stan_limits.Prior <- function(object) {
|
|
| 291 |
if ( |
|
| 292 | 1778x |
object@.omit_zero_lower_truncation && |
| 293 | 1778x |
identical(object@limits, c(0, Inf)) |
| 294 |
) {
|
|
| 295 | 473x |
return("")
|
| 296 |
} |
|
| 297 | 1305x |
render_stan_limits(object@limits) |
| 298 |
} |
|
| 299 | ||
| 300 |
#' @describeIn render_stan_limits method for numeric vectors. |
|
| 301 |
#' @exportS3Method NULL |
|
| 302 |
render_stan_limits.numeric <- function(object) {
|
|
| 303 | 1310x |
limits <- object |
| 304 | 1310x |
l_bound <- if (limits[[1]] > -Inf) limits[[1]] else "" |
| 305 | 1310x |
u_bound <- if (limits[[2]] < Inf) limits[[2]] else "" |
| 306 | 1310x |
string <- "" |
| 307 | 1310x |
if (l_bound != "" || u_bound != "") {
|
| 308 | 160x |
string <- glue::glue( |
| 309 | 160x |
" T[{l_bound}, {u_bound}]",
|
| 310 | 160x |
l_bound = l_bound, |
| 311 | 160x |
u_bound = u_bound |
| 312 |
) |
|
| 313 |
} |
|
| 314 | 1310x |
return(string) |
| 315 |
} |
|
| 316 | ||
| 317 |
#' @rdname show-object |
|
| 318 |
#' @export |
|
| 319 |
setMethod( |
|
| 320 |
f = "show", |
|
| 321 |
signature = "Prior", |
|
| 322 |
definition = function(object) {
|
|
| 323 | 12x |
x <- sprintf("\nPrior Object:\n %s\n\n", as.character(object))
|
| 324 | 12x |
cat(x) |
| 325 | 12x |
return(object) |
| 326 |
} |
|
| 327 |
) |
|
| 328 | ||
| 329 |
#' `Prior` -> `StanModule` |
|
| 330 |
#' |
|
| 331 |
#' Converts a [`Prior`] object to a [`StanModule`] object |
|
| 332 |
#' |
|
| 333 |
#' @inheritParams Prior-Shared |
|
| 334 |
#' |
|
| 335 |
#' @family Prior-internal |
|
| 336 |
#' @family as.StanModule |
|
| 337 |
#' @export |
|
| 338 |
#' |
|
| 339 |
#' @returns A `StanModule` object. |
|
| 340 |
as.StanModule.Prior <- function(object, name, size = 1, ...) {
|
|
| 341 | 1739x |
stan_repr <- c( |
| 342 | 1739x |
object@repr_data, |
| 343 | 1739x |
object@repr_parameters, |
| 344 | 1739x |
object@repr_transformed_parameters, |
| 345 | 1739x |
object@repr_model, |
| 346 | 1739x |
object@repr_generated_quantities |
| 347 |
) |
|
| 348 | 1739x |
if (all(nchar(stan_repr) == 0)) {
|
| 349 | 155x |
return(StanModule()) |
| 350 |
} |
|
| 351 | ||
| 352 | 1584x |
indent_stan <- function(x) {
|
| 353 | 7920x |
x <- x[nchar(x) >= 1] |
| 354 | 7920x |
if (length(x) == 0) {
|
| 355 | 4859x |
return("")
|
| 356 |
} |
|
| 357 | 3061x |
paste0(" ", x, collapse = "\n")
|
| 358 |
} |
|
| 359 | 1584x |
trunctation <- if (object@repr_model != "") {
|
| 360 | 1575x |
paste0(render_stan_limits(object), ";") |
| 361 |
} else {
|
|
| 362 |
"" |
|
| 363 |
} |
|
| 364 | 1584x |
string <- paste( |
| 365 | 1584x |
"data {{",
|
| 366 | 1584x |
indent_stan(object@repr_data), |
| 367 |
"}}", |
|
| 368 | 1584x |
"parameters {{",
|
| 369 | 1584x |
indent_stan(object@repr_parameters), |
| 370 |
"}}", |
|
| 371 | 1584x |
"transformed parameters {{",
|
| 372 | 1584x |
indent_stan(object@repr_transformed_parameters), |
| 373 |
"}}", |
|
| 374 | 1584x |
"model {{",
|
| 375 | 1584x |
indent_stan(paste0(object@repr_model, trunctation)), |
| 376 |
"}}", |
|
| 377 | 1584x |
"generated quantities {{",
|
| 378 | 1584x |
indent_stan(object@repr_generated_quantities), |
| 379 |
"}}", |
|
| 380 | 1584x |
sep = "\n" |
| 381 |
) |
|
| 382 | 1584x |
StanModule(glue::glue(string, name = name, size = size)) |
| 383 |
} |
|
| 384 | ||
| 385 | ||
| 386 |
#' `Prior` -> `list` |
|
| 387 |
#' |
|
| 388 |
#' Converts a Prior object to a list of parameter data values |
|
| 389 |
#' for a Stan model. |
|
| 390 |
#' |
|
| 391 |
#' @inheritParams Prior-Shared |
|
| 392 |
#' |
|
| 393 |
#' @family as_stan_list |
|
| 394 |
#' @family Prior-internal |
|
| 395 |
#' @export |
|
| 396 |
#' |
|
| 397 |
#' @returns A named `list` suitable for use as Stan data. |
|
| 398 |
as_stan_list.Prior <- function(object, name, ...) {
|
|
| 399 | 542x |
vals <- object@parameters |
| 400 | 542x |
if (object@.is_const) {
|
| 401 | 34x |
names(vals) <- paste0("prior_const_", name)
|
| 402 | 34x |
return(vals) |
| 403 |
} |
|
| 404 | 508x |
vals_names <- names(vals) |
| 405 | 508x |
if (length(vals_names) >= 1) {
|
| 406 | 417x |
names(vals) <- paste0("prior_", vals_names, "_", name)
|
| 407 |
} |
|
| 408 | 508x |
return(vals) |
| 409 |
} |
|
| 410 | ||
| 411 | ||
| 412 |
#' Prior Getter Functions |
|
| 413 |
#' @description |
|
| 414 |
#' Getter functions for the slots of a [`Prior`] object |
|
| 415 |
#' @inheritParams Prior-Shared |
|
| 416 |
#' @family Prior-internal |
|
| 417 |
#' @name Prior-Getter-Methods |
|
| 418 |
NULL |
|
| 419 | ||
| 420 | ||
| 421 |
# initialValues-Prior ---- |
|
| 422 | ||
| 423 |
#' @describeIn Prior-Getter-Methods The prior's initial value |
|
| 424 |
#' @export |
|
| 425 |
#' |
|
| 426 |
#' @returns The requested prior initial values or auxiliary-parameter information. |
|
| 427 |
initialValues.Prior <- function(object, ...) {
|
|
| 428 | 151826x |
n_samples <- 100 |
| 429 | 151826x |
centre_value <- object@centre |
| 430 | 151826x |
sample_values <- object@sample(n_samples) |
| 431 | ||
| 432 | 151825x |
is_scalar <- length(centre_value) == 1 |
| 433 | 151825x |
if (is_scalar) {
|
| 434 | 150818x |
assert_that( |
| 435 | 150818x |
length(sample_values) == n_samples, |
| 436 | 150818x |
msg = "Sample function must return a vector of length n_samples" |
| 437 |
) |
|
| 438 | ||
| 439 | 150818x |
samples <- getOption("jmpost.prior_shrinkage") *
|
| 440 | 150818x |
object@centre + |
| 441 | 150818x |
(1 - getOption("jmpost.prior_shrinkage")) * object@sample(n_samples)
|
| 442 | ||
| 443 | 150818x |
valid_samples <- samples[ |
| 444 | 150818x |
samples >= min(object@limits) & samples <= max(object@limits) |
| 445 |
] |
|
| 446 | 150818x |
assert_that( |
| 447 | 150818x |
length(valid_samples) >= 1, |
| 448 | 150818x |
msg = "Unable to generate an initial value that meets the required constraints" |
| 449 |
) |
|
| 450 | 150800x |
if (length(valid_samples) == 1) {
|
| 451 | ! |
return(valid_samples) |
| 452 |
} |
|
| 453 | 150800x |
return(sample(valid_samples, 1)) |
| 454 |
} else {
|
|
| 455 | 1007x |
n_centre_vals <- length(centre_value) |
| 456 | 1007x |
assert_that( |
| 457 | 1007x |
ncol(sample_values) == n_centre_vals, |
| 458 | 1007x |
msg = paste( |
| 459 | 1007x |
"Sample function must return a matrix with n_samples rows and the same", |
| 460 | 1007x |
"number of columns as the length of the centre value" |
| 461 |
) |
|
| 462 |
) |
|
| 463 | 1007x |
samples <- getOption("jmpost.prior_shrinkage") *
|
| 464 | 1007x |
matrix( |
| 465 | 1007x |
centre_value, |
| 466 | 1007x |
nrow = n_samples, |
| 467 | 1007x |
ncol = n_centre_vals, |
| 468 | 1007x |
byrow = TRUE |
| 469 |
) + |
|
| 470 | 1007x |
(1 - getOption("jmpost.prior_shrinkage")) * sample_values
|
| 471 | 1007x |
valid_samples <- samples[ |
| 472 | 1007x |
apply(samples, 1, function(row) {
|
| 473 | 100700x |
all(row >= min(object@limits) & row <= max(object@limits)) |
| 474 |
}), |
|
| 475 |
, |
|
| 476 | 1007x |
drop = FALSE |
| 477 |
] |
|
| 478 | 1007x |
assert_that( |
| 479 | 1007x |
nrow(valid_samples) >= 1, |
| 480 | 1007x |
msg = "Unable to generate an initial value that meets the required constraints" |
| 481 |
) |
|
| 482 | 1007x |
if (nrow(valid_samples) == 1) {
|
| 483 | ! |
return(valid_samples[1, ]) |
| 484 |
} else {
|
|
| 485 | 1007x |
return(valid_samples[sample(nrow(valid_samples), 1), ]) |
| 486 |
} |
|
| 487 |
} |
|
| 488 |
} |
|
| 489 | ||
| 490 | ||
| 491 |
#' @describeIn Prior-Getter-Methods The prior's auxiliary initial values |
|
| 492 |
#' @export |
|
| 493 |
auxiliaryInitialValues.Prior <- function(object, name, size, ...) {
|
|
| 494 | 10065x |
object@auxiliary_initial_values(name = name, size = size) |
| 495 |
} |
|
| 496 | ||
| 497 | ||
| 498 |
#' @describeIn Prior-Getter-Methods The prior's auxiliary parameter sizes |
|
| 499 |
#' @export |
|
| 500 |
auxiliarySize.Prior <- function(object, name, size, ...) {
|
|
| 501 | 138x |
object@auxiliary_size(name = name, size = size) |
| 502 |
} |
|
| 503 | ||
| 504 | ||
| 505 |
# Prior-constructors ---- |
|
| 506 | ||
| 507 |
#' Normal Prior Distribution |
|
| 508 |
#' |
|
| 509 |
#' @typed mu: number |
|
| 510 |
#' mean. |
|
| 511 |
#' @typed sigma: number |
|
| 512 |
#' standard deviation. |
|
| 513 |
#' @family Prior |
|
| 514 |
#' @export |
|
| 515 |
#' |
|
| 516 |
#' @inheritParams Prior-Shared |
|
| 517 |
#' @returns A `Prior` object. |
|
| 518 |
#' |
|
| 519 |
#' @examples |
|
| 520 |
#' prior_normal(0, 1) |
|
| 521 |
prior_normal <- function(mu, sigma, transform = NULL) {
|
|
| 522 | 1299x |
Prior( |
| 523 | 1299x |
transform = transform, |
| 524 | 1299x |
parameters = list(mu = mu, sigma = sigma), |
| 525 | 1299x |
display = "normal(mu = {mu}, sigma = {sigma})",
|
| 526 | 1299x |
repr_model = "{name} ~ normal(prior_mu_{name}, prior_sigma_{name})",
|
| 527 | 1299x |
repr_data = c( |
| 528 | 1299x |
"real prior_mu_{name};",
|
| 529 | 1299x |
"real<lower=0> prior_sigma_{name};"
|
| 530 |
), |
|
| 531 | 1299x |
centre = mu, |
| 532 | 1299x |
sample = \(n) local_rnorm(n, mu, sigma), |
| 533 | 1299x |
validation = list( |
| 534 | 1299x |
mu = is.numeric, |
| 535 | 1299x |
sigma = \(x) x > 0 |
| 536 |
) |
|
| 537 |
) |
|
| 538 |
} |
|
| 539 | ||
| 540 | ||
| 541 |
#' Normal Prior for a Vector Distribution |
|
| 542 |
#' |
|
| 543 |
#' @typed mus: numeric |
|
| 544 |
#' means. |
|
| 545 |
#' @typed sigmas: numeric |
|
| 546 |
#' standard deviations. |
|
| 547 |
#' @family Prior |
|
| 548 |
#' @export |
|
| 549 |
#' |
|
| 550 |
#' @returns A `Prior` object. |
|
| 551 |
#' |
|
| 552 |
#' @examples |
|
| 553 |
#' prior_normal_vector(c(0, 1), c(1, 2)) |
|
| 554 |
prior_normal_vector <- function(mus, sigmas) {
|
|
| 555 | 4x |
Prior( |
| 556 | 4x |
parameters = list( |
| 557 | 4x |
mus = mus, |
| 558 | 4x |
sigmas = sigmas, |
| 559 | 4x |
dim_mus = length(mus), |
| 560 | 4x |
dim_sigmas = length(sigmas) |
| 561 |
), |
|
| 562 | 4x |
display = "normal(mus = [{toString(mus)}], sigmas = [{toString(sigmas)}])",
|
| 563 | 4x |
repr_model = "{name} ~ normal(prior_mus_{name}, prior_sigmas_{name})",
|
| 564 | 4x |
repr_data = c( |
| 565 | 4x |
"int<lower=1> prior_dim_mus_{name};",
|
| 566 | 4x |
"int<lower=1> prior_dim_sigmas_{name};",
|
| 567 | 4x |
"vector[prior_dim_mus_{name}] prior_mus_{name};",
|
| 568 | 4x |
"vector<lower=0>[prior_dim_sigmas_{name}] prior_sigmas_{name};"
|
| 569 |
), |
|
| 570 | 4x |
centre = mus, |
| 571 | 4x |
sample = \(n) local_rnorm_vector(n, mus, sigmas), |
| 572 | 4x |
validation = list( |
| 573 | 4x |
mus = \(x) all(is.numeric(x)), |
| 574 | 4x |
sigmas = \(x) all(x > 0), |
| 575 | 4x |
dim_mus = is.count, |
| 576 | 4x |
dim_sigmas = is.count |
| 577 |
), |
|
| 578 | 4x |
.allow_vectors = TRUE |
| 579 |
) |
|
| 580 |
} |
|
| 581 | ||
| 582 |
#' Regularized Horseshoe Prior for a Vector Distribution |
|
| 583 |
#' |
|
| 584 |
#' Unbounded coefficients use a non-centred parameterization through Stan's |
|
| 585 |
#' affine multiplier transform. By default, standard-normal coefficients are |
|
| 586 |
#' multiplied by the regularized horseshoe scales. A finite `scale_noncentred` |
|
| 587 |
#' instead uses partial non-centring, limiting the multiplier to that scale. |
|
| 588 |
#' This can reduce dependence between strongly identified coefficients and |
|
| 589 |
#' their prior scales. It changes the sampling coordinates, not the prior. |
|
| 590 |
#' The coefficient names and initial-value interface are unchanged. Coefficients |
|
| 591 |
#' with finite bounds retain a centred parameterization because Stan does not |
|
| 592 |
#' allow bounds and affine multipliers on the same declaration. |
|
| 593 |
#' |
|
| 594 |
#' @typed df: number |
|
| 595 |
#' degrees of freedom of the half-Student-t prior for local shrinkage |
|
| 596 |
#' parameters. |
|
| 597 |
#' @typed df_global: number |
|
| 598 |
#' degrees of freedom of the half-Student-t prior for the global shrinkage |
|
| 599 |
#' parameter. |
|
| 600 |
#' @typed df_slab: number |
|
| 601 |
#' degrees of freedom of the Student-t slab. |
|
| 602 |
#' @typed scale_global: number |
|
| 603 |
#' scale of the half-Student-t prior for the global shrinkage parameter. |
|
| 604 |
#' @typed scale_slab: number |
|
| 605 |
#' scale of the Student-t slab. |
|
| 606 |
#' @typed scale_noncentred: number |
|
| 607 |
#' positive computational scale for partial non-centring, in coefficient |
|
| 608 |
#' units. If the horseshoe standard deviation is `s`, the multiplier is |
|
| 609 |
#' `s / sqrt(1 + (s / scale_noncentred)^2)`. The default `Inf` gives full |
|
| 610 |
#' non-centring with multiplier `s`. This argument does not change the |
|
| 611 |
#' horseshoe hyperparameters or the prior distribution. |
|
| 612 |
#' @family Prior |
|
| 613 |
#' @export |
|
| 614 |
#' |
|
| 615 |
#' @returns A `Prior` object. |
|
| 616 |
#' |
|
| 617 |
#' @examples |
|
| 618 |
#' prior_horseshoe(df = 1, df_global = 1, df_slab = 4, scale_global = 0.1, scale_slab = 2) |
|
| 619 |
prior_horseshoe <- function( |
|
| 620 |
df = 1, |
|
| 621 |
df_global = 1, |
|
| 622 |
df_slab = 4, |
|
| 623 |
scale_global = 1, |
|
| 624 |
scale_slab = 2, |
|
| 625 |
scale_noncentred = Inf |
|
| 626 |
) {
|
|
| 627 | 22x |
assert_number(scale_noncentred, lower = 0, finite = FALSE) |
| 628 | 18x |
assert_that(scale_noncentred > 0) |
| 629 | 17x |
Prior( |
| 630 | 17x |
parameters = list( |
| 631 | 17x |
df = df, |
| 632 | 17x |
df_global = df_global, |
| 633 | 17x |
df_slab = df_slab, |
| 634 | 17x |
scale_global = scale_global, |
| 635 | 17x |
scale_slab = scale_slab |
| 636 |
), |
|
| 637 | 17x |
display = paste0( |
| 638 | 17x |
"horseshoe(df = {df}, df_global = {df_global}, ",
|
| 639 | 17x |
"df_slab = {df_slab}, scale_global = {scale_global}, ",
|
| 640 | 17x |
"scale_slab = {scale_slab})"
|
| 641 |
), |
|
| 642 | 17x |
repr_model = paste( |
| 643 |
# Important: T[0, ] for truncating to positive values! |
|
| 644 | 17x |
"prior_local_{name} ~ student_t(prior_df_{name}, 0, 1) T[0, ];",
|
| 645 | 17x |
"prior_global_{name} ~ student_t(prior_df_global_{name}, 0, prior_scale_global_{name}) T[0, ];",
|
| 646 | 17x |
"prior_slab_{name} ~ inv_gamma(prior_df_slab_{name} / 2, prior_df_slab_{name} / 2);",
|
| 647 | 17x |
"{name} ~ normal(rep_vector(0, {size}), prior_scales_{name})",
|
| 648 | 17x |
sep = "\n " |
| 649 |
), |
|
| 650 | 17x |
repr_data = c( |
| 651 | 17x |
"real<lower=0> prior_df_{name};",
|
| 652 | 17x |
"real<lower=0> prior_df_global_{name};",
|
| 653 | 17x |
"real<lower=0> prior_df_slab_{name};",
|
| 654 | 17x |
"real<lower=0> prior_scale_global_{name};",
|
| 655 | 17x |
"real<lower=0> prior_scale_slab_{name};"
|
| 656 |
), |
|
| 657 | 17x |
repr_parameters = c( |
| 658 | 17x |
"vector<lower=0>[{size}] prior_local_{name};",
|
| 659 | 17x |
"real<lower=0> prior_global_{name};",
|
| 660 | 17x |
"real<lower=0> prior_slab_{name};"
|
| 661 |
), |
|
| 662 | 17x |
repr_transformed_parameters = c( |
| 663 | 17x |
"real<lower=0> prior_c2_{name} = square(prior_scale_slab_{name}) * prior_slab_{name};",
|
| 664 | 17x |
paste( |
| 665 | 17x |
"vector<lower=0>[{size}] prior_scales_{name} =",
|
| 666 | 17x |
"scales_horseshoe(prior_local_{name}, prior_global_{name}, prior_c2_{name});"
|
| 667 |
) |
|
| 668 |
), |
|
| 669 | 17x |
repr_generated_quantities = c( |
| 670 | 17x |
paste( |
| 671 | 17x |
"vector<lower=0, upper=1>[{size}] prior_shrinkage_factors_{name} =",
|
| 672 | 17x |
"shrinkage_horseshoe(prior_local_{name}, prior_global_{name}, prior_c2_{name});"
|
| 673 |
) |
|
| 674 |
), |
|
| 675 | 17x |
repr_multiplier = \(name, size) {
|
| 676 | 18x |
multiplier <- glue::glue( |
| 677 | 18x |
"scales_horseshoe(prior_local_{name}, prior_global_{name}, ",
|
| 678 | 18x |
"square(prior_scale_slab_{name}) * prior_slab_{name})"
|
| 679 |
) |
|
| 680 | 18x |
if (is.numeric(size) && size == 1) {
|
| 681 | 2x |
multiplier <- paste0("(", multiplier, ")[1]")
|
| 682 |
} |
|
| 683 | 18x |
if (is.finite(scale_noncentred)) {
|
| 684 | 4x |
scalar <- is.numeric(size) && size == 1 |
| 685 | 4x |
divide <- if (scalar) "/" else "./" |
| 686 | 4x |
one <- if (scalar) "1" else glue::glue("rep_vector(1, {size})")
|
| 687 | 4x |
multiplier <- glue::glue( |
| 688 | 4x |
"({multiplier}) {divide} sqrt({one} + ",
|
| 689 | 4x |
"square(({multiplier}) / {scale_noncentred}))"
|
| 690 |
) |
|
| 691 |
} |
|
| 692 | 18x |
multiplier |
| 693 |
}, |
|
| 694 | 17x |
auxiliary_initial_values = \(name, size) {
|
| 695 | 7x |
local_size <- if (is.numeric(size)) size else 1 |
| 696 | 7x |
stats::setNames( |
| 697 | 7x |
list( |
| 698 | 7x |
abs(local_rt(local_size, df, 0, 1)), |
| 699 | 7x |
abs(local_rt(1, df_global, 0, scale_global)), |
| 700 | 7x |
local_rinvgamma(1, df_slab / 2, df_slab / 2) |
| 701 |
), |
|
| 702 | 7x |
paste0(c("prior_local_", "prior_global_", "prior_slab_"), name)
|
| 703 |
) |
|
| 704 |
}, |
|
| 705 | 17x |
auxiliary_size = \(name, size) {
|
| 706 | 3x |
stats::setNames( |
| 707 | 3x |
list(size, 1, 1), |
| 708 | 3x |
paste0(c("prior_local_", "prior_global_", "prior_slab_"), name)
|
| 709 |
) |
|
| 710 |
}, |
|
| 711 | 17x |
centre = 0, |
| 712 | 17x |
sample = \(n) {
|
| 713 | 14x |
local_rhorseshoe( |
| 714 | 14x |
n = n, |
| 715 | 14x |
df = df, |
| 716 | 14x |
df_global = df_global, |
| 717 | 14x |
df_slab = df_slab, |
| 718 | 14x |
scale_global = scale_global, |
| 719 | 14x |
scale_slab = scale_slab |
| 720 |
) |
|
| 721 |
}, |
|
| 722 | 17x |
validation = list( |
| 723 | 17x |
df = \(x) x > 0, |
| 724 | 17x |
df_global = \(x) x > 0, |
| 725 | 17x |
df_slab = \(x) x > 0, |
| 726 | 17x |
scale_global = \(x) x > 0, |
| 727 | 17x |
scale_slab = \(x) x > 0 |
| 728 |
) |
|
| 729 |
) |
|
| 730 |
} |
|
| 731 | ||
| 732 | ||
| 733 |
#' Standard Normal Prior Distribution |
|
| 734 |
#' |
|
| 735 |
#' |
|
| 736 |
#' @family Prior |
|
| 737 |
#' @export |
|
| 738 |
#' |
|
| 739 |
#' @inheritParams Prior-Shared |
|
| 740 |
#' @returns A `Prior` object. |
|
| 741 |
#' |
|
| 742 |
#' @examples |
|
| 743 |
#' prior_std_normal() |
|
| 744 |
prior_std_normal <- function(transform = NULL) {
|
|
| 745 | 355x |
Prior( |
| 746 | 355x |
transform = transform, |
| 747 | 355x |
parameters = list(), |
| 748 | 355x |
display = "std_normal()", |
| 749 | 355x |
repr_model = "{name} ~ std_normal()",
|
| 750 | 355x |
repr_data = "", |
| 751 | 355x |
centre = 0, |
| 752 | 355x |
sample = \(n) local_rnorm(n), |
| 753 | 355x |
validation = list() |
| 754 |
) |
|
| 755 |
} |
|
| 756 | ||
| 757 |
#' Constant Prior Distribution |
|
| 758 |
#' |
|
| 759 |
#' @typed value: number |
|
| 760 |
#' the fixed parameter value. |
|
| 761 |
#' @family Prior |
|
| 762 |
#' @export |
|
| 763 |
#' |
|
| 764 |
#' @returns A `Prior` object. |
|
| 765 |
#' |
|
| 766 |
#' @examples |
|
| 767 |
#' prior_const(0) |
|
| 768 |
prior_const <- function(value) {
|
|
| 769 | 27x |
Prior( |
| 770 | 27x |
parameters = list(value = value), |
| 771 | 27x |
display = "const(value = {value})",
|
| 772 | 27x |
repr_model = "", |
| 773 | 27x |
repr_data = "real prior_const_{name};",
|
| 774 | 27x |
centre = value, |
| 775 | 27x |
sample = \(n) rep(value, n), |
| 776 | 27x |
validation = list( |
| 777 | 27x |
value = is.numeric |
| 778 |
), |
|
| 779 | 27x |
.is_const = TRUE |
| 780 |
) |
|
| 781 |
} |
|
| 782 | ||
| 783 |
#' Constant Prior Distribution for a Vector |
|
| 784 |
#' |
|
| 785 |
#' @typed values: numeric |
|
| 786 |
#' the fixed parameter values. |
|
| 787 |
#' @family Prior |
|
| 788 |
#' @export |
|
| 789 |
#' |
|
| 790 |
#' @returns A `Prior` object. |
|
| 791 |
#' |
|
| 792 |
#' @examples |
|
| 793 |
#' prior_const_vector(c(0, 1)) |
|
| 794 |
prior_const_vector <- function(values) {
|
|
| 795 | 24x |
assert_that( |
| 796 | 24x |
is.numeric(values) && length(values) > 0, |
| 797 | 24x |
msg = "`values` must be a non-empty numeric vector" |
| 798 |
) |
|
| 799 | 22x |
Prior( |
| 800 | 22x |
parameters = list(values = values), |
| 801 | 22x |
display = "const(values = [{toString(values)}])",
|
| 802 | 22x |
repr_model = "", |
| 803 | 22x |
repr_data = "vector[{size}] prior_const_{name};",
|
| 804 | 22x |
centre = values, |
| 805 | 22x |
sample = \(n) {
|
| 806 | 1002x |
matrix( |
| 807 | 1002x |
values, |
| 808 | 1002x |
nrow = n, |
| 809 | 1002x |
ncol = length(values), |
| 810 | 1002x |
byrow = TRUE |
| 811 |
) |
|
| 812 |
}, |
|
| 813 | 22x |
validation = list( |
| 814 | 22x |
values = \(x) is.numeric(x) && length(x) > 0 |
| 815 |
), |
|
| 816 | 22x |
.allow_vectors = TRUE, |
| 817 | 22x |
.is_const = TRUE |
| 818 |
) |
|
| 819 |
} |
|
| 820 | ||
| 821 |
#' Cauchy Prior Distribution |
|
| 822 |
#' |
|
| 823 |
#' @typed mu: number |
|
| 824 |
#' mean. |
|
| 825 |
#' @typed sigma: number |
|
| 826 |
#' scale. |
|
| 827 |
#' @family Prior |
|
| 828 |
#' |
|
| 829 |
#' @export |
|
| 830 |
#' |
|
| 831 |
#' @inheritParams Prior-Shared |
|
| 832 |
#' @returns A `Prior` object. |
|
| 833 |
#' |
|
| 834 |
#' @examples |
|
| 835 |
#' prior_cauchy(0, 1) |
|
| 836 |
prior_cauchy <- function(mu, sigma, transform = NULL) {
|
|
| 837 | 17x |
Prior( |
| 838 | 17x |
transform = transform, |
| 839 | 17x |
parameters = list(mu = mu, sigma = sigma), |
| 840 | 17x |
display = "cauchy(mu = {mu}, sigma = {sigma})",
|
| 841 | 17x |
repr_model = "{name} ~ cauchy(prior_mu_{name}, prior_sigma_{name})",
|
| 842 | 17x |
repr_data = c( |
| 843 | 17x |
"real prior_mu_{name};",
|
| 844 | 17x |
"real<lower=0> prior_sigma_{name};"
|
| 845 |
), |
|
| 846 | 17x |
centre = mu, |
| 847 | 17x |
sample = \(n) local_rcauchy(n, mu, sigma), |
| 848 | 17x |
validation = list( |
| 849 | 17x |
mu = is.numeric, |
| 850 | 17x |
sigma = \(x) x > 0 |
| 851 |
), |
|
| 852 | 17x |
.omit_zero_lower_truncation = TRUE |
| 853 |
) |
|
| 854 |
} |
|
| 855 | ||
| 856 | ||
| 857 |
#' Gamma Prior Distribution |
|
| 858 |
#' |
|
| 859 |
#' @typed alpha: number |
|
| 860 |
#' shape. |
|
| 861 |
#' @typed beta: number |
|
| 862 |
#' inverse scale. |
|
| 863 |
#' @family Prior |
|
| 864 |
#' |
|
| 865 |
#' @export |
|
| 866 |
#' |
|
| 867 |
#' @inheritParams Prior-Shared |
|
| 868 |
#' @returns A `Prior` object. |
|
| 869 |
#' |
|
| 870 |
#' @examples |
|
| 871 |
#' prior_gamma(2, 1) |
|
| 872 |
prior_gamma <- function(alpha, beta, transform = NULL) {
|
|
| 873 | 118x |
Prior( |
| 874 | 118x |
transform = transform, |
| 875 | 118x |
parameters = list(alpha = alpha, beta = beta), |
| 876 | 118x |
repr_model = "{name} ~ gamma(prior_alpha_{name}, prior_beta_{name})",
|
| 877 | 118x |
display = "gamma(alpha = {alpha}, beta = {beta})",
|
| 878 | 118x |
repr_data = c( |
| 879 | 118x |
"real<lower=0> prior_alpha_{name};",
|
| 880 | 118x |
"real<lower=0> prior_beta_{name};"
|
| 881 |
), |
|
| 882 | 118x |
centre = alpha / beta, |
| 883 | 118x |
sample = \(n) local_rgamma(n, shape = alpha, rate = beta), |
| 884 | 118x |
validation = list( |
| 885 | 118x |
alpha = \(x) x > 0, |
| 886 | 118x |
beta = \(x) x > 0 |
| 887 |
), |
|
| 888 | 118x |
.omit_zero_lower_truncation = TRUE |
| 889 |
) |
|
| 890 |
} |
|
| 891 | ||
| 892 |
#' Log-Normal Prior Distribution |
|
| 893 |
#' |
|
| 894 |
#' @typed mu: number |
|
| 895 |
#' mean of the logarithm. |
|
| 896 |
#' @typed sigma: number |
|
| 897 |
#' standard deviation of the logarithm. |
|
| 898 |
#' @family Prior |
|
| 899 |
#' |
|
| 900 |
#' @export |
|
| 901 |
#' |
|
| 902 |
#' @inheritParams Prior-Shared |
|
| 903 |
#' @returns A `Prior` object. |
|
| 904 |
#' |
|
| 905 |
#' @examples |
|
| 906 |
#' prior_lognormal(0, 1) |
|
| 907 |
prior_lognormal <- function(mu, sigma, transform = NULL) {
|
|
| 908 | 563x |
Prior( |
| 909 | 563x |
transform = transform, |
| 910 | 563x |
parameters = list(mu = mu, sigma = sigma), |
| 911 | 563x |
display = "lognormal(mu = {mu}, sigma = {sigma})",
|
| 912 | 563x |
repr_model = "{name} ~ lognormal(prior_mu_{name}, prior_sigma_{name})",
|
| 913 | 563x |
repr_data = c( |
| 914 | 563x |
"real prior_mu_{name};",
|
| 915 | 563x |
"real<lower=0> prior_sigma_{name};"
|
| 916 |
), |
|
| 917 | 563x |
centre = exp(mu + (sigma^2) / 2), |
| 918 | 563x |
sample = \(n) local_rlnorm(n, mu, sigma), |
| 919 | 563x |
validation = list( |
| 920 | 563x |
mu = is.numeric, |
| 921 | 563x |
sigma = \(x) x > 0 |
| 922 |
), |
|
| 923 | 563x |
.omit_zero_lower_truncation = TRUE |
| 924 |
) |
|
| 925 |
} |
|
| 926 | ||
| 927 |
#' Beta Prior Distribution |
|
| 928 |
#' |
|
| 929 |
#' @typed a: number |
|
| 930 |
#' first parameter. |
|
| 931 |
#' @typed b: number |
|
| 932 |
#' second parameter |
|
| 933 |
#' @family Prior |
|
| 934 |
#' |
|
| 935 |
#' @export |
|
| 936 |
#' |
|
| 937 |
#' @inheritParams Prior-Shared |
|
| 938 |
#' @returns A `Prior` object. |
|
| 939 |
#' |
|
| 940 |
#' @examples |
|
| 941 |
#' prior_beta(2, 3) |
|
| 942 |
prior_beta <- function(a, b, transform = NULL) {
|
|
| 943 | 16x |
Prior( |
| 944 | 16x |
transform = transform, |
| 945 | 16x |
parameters = list(a = a, b = b), |
| 946 | 16x |
display = "beta(a = {a}, b = {b})",
|
| 947 | 16x |
repr_model = "{name} ~ beta(prior_a_{name}, prior_b_{name})",
|
| 948 | 16x |
repr_data = c( |
| 949 | 16x |
"real<lower=0> prior_a_{name};",
|
| 950 | 16x |
"real<lower=0> prior_b_{name};"
|
| 951 |
), |
|
| 952 | 16x |
centre = a / (a + b), |
| 953 | 16x |
sample = \(n) local_rbeta(n, a, b), |
| 954 | 16x |
validation = list( |
| 955 | 16x |
a = \(x) x > 0, |
| 956 | 16x |
b = \(x) x > 0 |
| 957 |
) |
|
| 958 |
) |
|
| 959 |
} |
|
| 960 | ||
| 961 |
#' Initial Values Specification |
|
| 962 |
#' |
|
| 963 |
#' @typed dist: Prior |
|
| 964 |
#' a prior Distribution |
|
| 965 |
#' @family Prior |
|
| 966 |
#' @description |
|
| 967 |
#' This function is used to specify only the initial values for a parameter. |
|
| 968 |
#' This is primarily used for hierarchical parameters whose distributions |
|
| 969 |
#' are fixed within the model and cannot be altered by the user. |
|
| 970 |
#' |
|
| 971 |
#' @export |
|
| 972 |
#' |
|
| 973 |
#' @returns A `Prior` object. |
|
| 974 |
#' |
|
| 975 |
#' @examples |
|
| 976 |
#' prior_init_only(prior_normal(0, 1)) |
|
| 977 |
prior_init_only <- function(dist) {
|
|
| 978 | 146x |
Prior( |
| 979 | 146x |
parameters = list(), |
| 980 | 146x |
display = "<None>", |
| 981 | 146x |
repr_model = "", |
| 982 | 146x |
repr_data = "", |
| 983 | 146x |
sample = \(n) {
|
| 984 | 1326x |
dist@sample(n) |
| 985 |
}, |
|
| 986 | 146x |
centre = dist@centre, |
| 987 | 146x |
validation = list(), |
| 988 | 146x |
limits = dist@limits, |
| 989 | 146x |
.omit_zero_lower_truncation = dist@.omit_zero_lower_truncation |
| 990 |
) |
|
| 991 |
} |
|
| 992 | ||
| 993 | ||
| 994 |
#' Flat Prior |
|
| 995 |
#' |
|
| 996 |
#' Specifies a constant density on the declared model parameter scale, for a |
|
| 997 |
#' scalar or each component of a vector. No prior density term or auxiliary |
|
| 998 |
#' parameters are added to the Stan model. Model-imposed parameter bounds still |
|
| 999 |
#' apply. An unbounded flat prior is improper; a proper posterior is not |
|
| 1000 |
#' guaranteed. |
|
| 1001 |
#' |
|
| 1002 |
#' @typed init: Prior |
|
| 1003 |
#' distribution used only to generate initial values, including the usual |
|
| 1004 |
#' `jmpost.prior_shrinkage` towards its centre. Its density and limits do not |
|
| 1005 |
#' constrain the model parameter. Choose initial values compatible with any |
|
| 1006 |
#' model-imposed bounds. |
|
| 1007 |
#' @family Prior |
|
| 1008 |
#' @seealso [prior_init_only()] |
|
| 1009 |
#' @export |
|
| 1010 |
#' |
|
| 1011 |
#' @returns A `Prior` object. |
|
| 1012 |
#' |
|
| 1013 |
#' @examples |
|
| 1014 |
#' prior_flat() |
|
| 1015 |
#' prior_flat(init = prior_normal(0, 2)) |
|
| 1016 |
#' SurvivalWeibullPH(beta = prior_flat()) |
|
| 1017 |
prior_flat <- function(init = prior_normal(0, 1)) {
|
|
| 1018 | 11x |
assert_that(is(init, "Prior"), msg = "`init` must be a Prior object") |
| 1019 | 10x |
Prior( |
| 1020 | 10x |
parameters = list(), |
| 1021 | 10x |
display = "flat()", |
| 1022 | 10x |
repr_model = "", |
| 1023 | 10x |
repr_data = "", |
| 1024 | 10x |
centre = init@centre, |
| 1025 | 10x |
sample = init@sample, |
| 1026 | 10x |
validation = list() |
| 1027 |
) |
|
| 1028 |
} |
|
| 1029 | ||
| 1030 | ||
| 1031 |
#' Uniform Prior Distribution |
|
| 1032 |
#' |
|
| 1033 |
#' @typed alpha: number |
|
| 1034 |
#' minimum value parameter. |
|
| 1035 |
#' @typed beta: number |
|
| 1036 |
#' maximum value parameter. |
|
| 1037 |
#' @family Prior |
|
| 1038 |
#' |
|
| 1039 |
#' @export |
|
| 1040 |
#' |
|
| 1041 |
#' @inheritParams Prior-Shared |
|
| 1042 |
#' @returns A `Prior` object. |
|
| 1043 |
#' |
|
| 1044 |
#' @examples |
|
| 1045 |
#' prior_uniform(0, 1) |
|
| 1046 |
prior_uniform <- function(alpha, beta, transform = NULL) {
|
|
| 1047 | 15x |
assert_that( |
| 1048 | 15x |
alpha < beta, |
| 1049 | 15x |
msg = "`alpha`` must be less than `beta`" |
| 1050 |
) |
|
| 1051 | 14x |
Prior( |
| 1052 | 14x |
transform = transform, |
| 1053 | 14x |
parameters = list(alpha = alpha, beta = beta), |
| 1054 | 14x |
display = "uniform(alpha = {alpha}, beta = {beta})",
|
| 1055 | 14x |
repr_model = "{name} ~ uniform(prior_alpha_{name}, prior_beta_{name})",
|
| 1056 | 14x |
repr_data = c( |
| 1057 | 14x |
"real prior_alpha_{name};",
|
| 1058 | 14x |
"real prior_beta_{name};"
|
| 1059 |
), |
|
| 1060 | 14x |
centre = 0.5 * (alpha + beta), |
| 1061 | 14x |
sample = \(n) local_runif(n, alpha, beta), |
| 1062 | 14x |
validation = list( |
| 1063 | 14x |
alpha = is.numeric, |
| 1064 | 14x |
beta = is.numeric |
| 1065 |
) |
|
| 1066 |
) |
|
| 1067 |
} |
|
| 1068 | ||
| 1069 | ||
| 1070 |
#' Student-t Prior Distribution |
|
| 1071 |
#' |
|
| 1072 |
#' @typed nu: number |
|
| 1073 |
#' Degrees of freedom parameter. |
|
| 1074 |
#' @typed mu: number |
|
| 1075 |
#' Location parameter. |
|
| 1076 |
#' @typed sigma: number |
|
| 1077 |
#' Scale parameter. |
|
| 1078 |
#' @family Prior |
|
| 1079 |
#' |
|
| 1080 |
#' @export |
|
| 1081 |
#' |
|
| 1082 |
#' @inheritParams Prior-Shared |
|
| 1083 |
#' @returns A `Prior` object. |
|
| 1084 |
#' |
|
| 1085 |
#' @examples |
|
| 1086 |
#' prior_student_t(4, 0, 1) |
|
| 1087 |
prior_student_t <- function(nu, mu, sigma, transform = NULL) {
|
|
| 1088 | 10x |
Prior( |
| 1089 | 10x |
transform = transform, |
| 1090 | 10x |
parameters = list( |
| 1091 | 10x |
nu = nu, |
| 1092 | 10x |
mu = mu, |
| 1093 | 10x |
sigma = sigma |
| 1094 |
), |
|
| 1095 | 10x |
display = "student_t(nu = {nu}, mu = {mu}, sigma = {sigma})",
|
| 1096 | 10x |
repr_model = "{name} ~ student_t(prior_nu_{name}, prior_mu_{name}, prior_sigma_{name})",
|
| 1097 | 10x |
repr_data = c( |
| 1098 | 10x |
"real<lower=0> prior_nu_{name};",
|
| 1099 | 10x |
"real prior_mu_{name};",
|
| 1100 | 10x |
"real<lower=0> prior_sigma_{name};"
|
| 1101 |
), |
|
| 1102 | 10x |
centre = mu, |
| 1103 | 10x |
sample = \(n) local_rt(n, nu, mu, sigma), |
| 1104 | 10x |
validation = list( |
| 1105 | 10x |
nu = \(x) x > 0, |
| 1106 | 10x |
mu = is.numeric, |
| 1107 | 10x |
sigma = \(x) x > 0 |
| 1108 |
) |
|
| 1109 |
) |
|
| 1110 |
} |
|
| 1111 | ||
| 1112 | ||
| 1113 |
#' Logistic Prior Distribution |
|
| 1114 |
#' |
|
| 1115 |
#' @typed mu: number |
|
| 1116 |
#' Location parameter. |
|
| 1117 |
#' @typed sigma: number |
|
| 1118 |
#' Scale parameter. |
|
| 1119 |
#' @family Prior |
|
| 1120 |
#' |
|
| 1121 |
#' @export |
|
| 1122 |
#' |
|
| 1123 |
#' @inheritParams Prior-Shared |
|
| 1124 |
#' @returns A `Prior` object. |
|
| 1125 |
#' |
|
| 1126 |
#' @examples |
|
| 1127 |
#' prior_logistic(0, 1) |
|
| 1128 |
prior_logistic <- function(mu, sigma, transform = NULL) {
|
|
| 1129 | 9x |
Prior( |
| 1130 | 9x |
transform = transform, |
| 1131 | 9x |
parameters = list( |
| 1132 | 9x |
mu = mu, |
| 1133 | 9x |
sigma = sigma |
| 1134 |
), |
|
| 1135 | 9x |
display = "logistic(mu = {mu}, sigma = {sigma})",
|
| 1136 | 9x |
repr_model = "{name} ~ logistic(prior_mu_{name}, prior_sigma_{name})",
|
| 1137 | 9x |
repr_data = c( |
| 1138 | 9x |
"real prior_mu_{name};",
|
| 1139 | 9x |
"real<lower=0> prior_sigma_{name};"
|
| 1140 |
), |
|
| 1141 | 9x |
centre = mu, |
| 1142 | 9x |
sample = \(n) local_rlogis(n, mu, sigma), |
| 1143 | 9x |
validation = list( |
| 1144 | 9x |
mu = is.numeric, |
| 1145 | 9x |
sigma = \(x) x > 0 |
| 1146 |
) |
|
| 1147 |
) |
|
| 1148 |
} |
|
| 1149 | ||
| 1150 | ||
| 1151 |
#' Log-Logistic Prior Distribution |
|
| 1152 |
#' |
|
| 1153 |
#' @typed alpha: number |
|
| 1154 |
#' Scale parameter. |
|
| 1155 |
#' @typed beta: number |
|
| 1156 |
#' Shape parameter. |
|
| 1157 |
#' @family Prior |
|
| 1158 |
#' |
|
| 1159 |
#' @export |
|
| 1160 |
#' |
|
| 1161 |
#' @inheritParams Prior-Shared |
|
| 1162 |
#' @returns A `Prior` object. |
|
| 1163 |
#' |
|
| 1164 |
#' @examples |
|
| 1165 |
#' prior_loglogistic(2, 3) |
|
| 1166 |
prior_loglogistic <- function(alpha, beta, transform = NULL) {
|
|
| 1167 | 11x |
Prior( |
| 1168 | 11x |
transform = transform, |
| 1169 | 11x |
parameters = list( |
| 1170 | 11x |
alpha = alpha, |
| 1171 | 11x |
beta = beta |
| 1172 |
), |
|
| 1173 | 11x |
display = "loglogistic(alpha = {alpha}, beta = {beta})",
|
| 1174 | 11x |
repr_model = "{name} ~ loglogistic(prior_alpha_{name}, prior_beta_{name})",
|
| 1175 | 11x |
repr_data = c( |
| 1176 | 11x |
"real<lower=0> prior_alpha_{name};",
|
| 1177 | 11x |
"real<lower=0> prior_beta_{name};"
|
| 1178 |
), |
|
| 1179 | 11x |
centre = alpha * pi / (beta * sin(pi / beta)), |
| 1180 | 11x |
sample = \(n) {
|
| 1181 | 4x |
local_rloglogis(n, alpha, beta) |
| 1182 |
}, |
|
| 1183 | 11x |
validation = list( |
| 1184 | 11x |
alpha = \(x) x > 0, |
| 1185 | 11x |
beta = \(x) x > 0 |
| 1186 |
), |
|
| 1187 | 11x |
.omit_zero_lower_truncation = TRUE |
| 1188 |
) |
|
| 1189 |
} |
|
| 1190 | ||
| 1191 | ||
| 1192 |
#' Inverse-Gamma Prior Distribution |
|
| 1193 |
#' |
|
| 1194 |
#' @typed alpha: number |
|
| 1195 |
#' Shape parameter. |
|
| 1196 |
#' @typed beta: number |
|
| 1197 |
#' Scale parameter. |
|
| 1198 |
#' @family Prior |
|
| 1199 |
#' |
|
| 1200 |
#' @export |
|
| 1201 |
#' |
|
| 1202 |
#' @inheritParams Prior-Shared |
|
| 1203 |
#' @returns A `Prior` object. |
|
| 1204 |
#' |
|
| 1205 |
#' @examples |
|
| 1206 |
#' prior_invgamma(3, 2) |
|
| 1207 |
prior_invgamma <- function(alpha, beta, transform = NULL) {
|
|
| 1208 | 17x |
Prior( |
| 1209 | 17x |
transform = transform, |
| 1210 | 17x |
parameters = list( |
| 1211 | 17x |
alpha = alpha, |
| 1212 | 17x |
beta = beta |
| 1213 |
), |
|
| 1214 | 17x |
display = "inv_gamma(alpha = {alpha}, beta = {beta})",
|
| 1215 | 17x |
repr_model = "{name} ~ inv_gamma(prior_alpha_{name}, prior_beta_{name})",
|
| 1216 | 17x |
repr_data = c( |
| 1217 | 17x |
"real<lower=0> prior_alpha_{name};",
|
| 1218 | 17x |
"real<lower=0> prior_beta_{name};"
|
| 1219 |
), |
|
| 1220 | 17x |
centre = beta / (alpha - 1), |
| 1221 | 17x |
sample = \(n) local_rinvgamma(n, alpha, beta), |
| 1222 | 17x |
validation = list( |
| 1223 | 17x |
alpha = \(x) x > 0, |
| 1224 | 17x |
beta = \(x) x > 0 |
| 1225 |
), |
|
| 1226 | 17x |
.omit_zero_lower_truncation = TRUE |
| 1227 |
) |
|
| 1228 |
} |
|
| 1229 | ||
| 1230 | ||
| 1231 |
# nolint start |
|
| 1232 |
# |
|
| 1233 |
# Developer Notes |
|
| 1234 |
# |
|
| 1235 |
# The `median.Prior` function is a rough workaround to help generate initial values for |
|
| 1236 |
# hierarchical distributions. The original implementation involved sampling initial values |
|
| 1237 |
# for the random effects using the medians of the parent distribution e.g. |
|
| 1238 |
# ``` |
|
| 1239 |
# random_effect ~ beta(a_prior@centre, b_prior@centre) |
|
| 1240 |
# ``` |
|
| 1241 |
# A problem came up though when we implemented support for constrained distributions |
|
| 1242 |
# as there was no longer any guarantee that the median/centre of the distribution is |
|
| 1243 |
# a valid value e.g. `a_prior ~ prior_normal(-200, 400)`. |
|
| 1244 |
# |
|
| 1245 |
# To resolve this issue the `median.Prior` method was created which simply samples |
|
| 1246 |
# multiple observations from the constrained distribution and then takes the median |
|
| 1247 |
# of those constrained observations; this then ensures that the value being used |
|
| 1248 |
# for the parameters is a valid value |
|
| 1249 |
# |
|
| 1250 |
# nolint end |
|
| 1251 |
#' @importFrom stats median |
|
| 1252 |
#' @export |
|
| 1253 |
#' |
|
| 1254 |
#' @returns A single numeric value giving the prior median. |
|
| 1255 |
median.Prior <- function(x, na.rm, ...) {
|
|
| 1256 | 284x |
vals <- replicate( |
| 1257 | 284x |
n = 500, |
| 1258 | 284x |
initialValues(x), |
| 1259 | 284x |
simplify = FALSE |
| 1260 |
) |> |
|
| 1261 | 284x |
unlist() |
| 1262 | 283x |
median(vals) |
| 1263 |
} |
|
| 1264 | ||
| 1265 | ||
| 1266 |
#' Stub functions for sampling from distributions |
|
| 1267 |
#' |
|
| 1268 |
#' @description |
|
| 1269 |
#' These functions only exist so that they can be mocked during unit |
|
| 1270 |
#' tests in order to provide deterministic values. In most cases |
|
| 1271 |
#' these are just straight forward pass throughs for the underlying |
|
| 1272 |
#' distributions. |
|
| 1273 |
#' |
|
| 1274 |
#' @typed alpha: number |
|
| 1275 |
#' Parameter for underlying distribution. |
|
| 1276 |
#' @typed beta: number |
|
| 1277 |
#' Parameter for underlying distribution. |
|
| 1278 |
#' @typed mu: number |
|
| 1279 |
#' Parameter for underlying distribution. |
|
| 1280 |
#' @typed sigma: number |
|
| 1281 |
#' Parameter for underlying distribution. |
|
| 1282 |
#' @typed nu: number |
|
| 1283 |
#' Parameter for underlying distribution. |
|
| 1284 |
#' @param ... Pass any additional arguments to the underlying distribution. |
|
| 1285 |
#' |
|
| 1286 |
#' @importFrom stats rbeta rcauchy rgamma rlnorm rlogis rnorm rt runif |
|
| 1287 |
#' |
|
| 1288 |
#' @details |
|
| 1289 |
#' |
|
| 1290 |
#' ## Log-Logistic |
|
| 1291 |
#' |
|
| 1292 |
#' There is no log-logistic sampling function within base R so it was implemented |
|
| 1293 |
#' in terms of sampling from the CDF distribution. Using the Stan parameterisation |
|
| 1294 |
#' the CDF is defined as: |
|
| 1295 |
#' \deqn{
|
|
| 1296 |
#' u = F(x) = \frac{1}{1 + (x/ \alpha)^{-\beta}}
|
|
| 1297 |
#' } |
|
| 1298 |
#' The inverse of this function is: |
|
| 1299 |
#' \deqn{
|
|
| 1300 |
#' x = ((u / (1 - u))^(1 / beta)) * alpha |
|
| 1301 |
#' } |
|
| 1302 |
#' |
|
| 1303 |
#' Thus we can sample u from a \eqn{Uni(0, 1)} distribution and then derive x from this.
|
|
| 1304 |
#' |
|
| 1305 |
#' ## Inverse-Gamma |
|
| 1306 |
#' |
|
| 1307 |
#' The inverse Gamma distribution is defined as 1/Gamma thus we calculate this simply |
|
| 1308 |
#' by sampling sampling from the Gamma distribution and then taking the reciprocal. |
|
| 1309 |
#' |
|
| 1310 |
#' ## Student-t |
|
| 1311 |
#' |
|
| 1312 |
#' R's sampling functions only produce the standard Student-t distribution so in order |
|
| 1313 |
#' to match Stan's implementation we multiply by the scale parameter and add the location |
|
| 1314 |
#' parameter. See this \href{https://stats.stackexchange.com/a/623611}{Stack Overflow} post
|
|
| 1315 |
#' for details |
|
| 1316 |
#' |
|
| 1317 |
#' @name Local_Sample |
|
| 1318 |
#' @keywords internal |
|
| 1319 |
NULL |
|
| 1320 | ||
| 1321 |
#' @rdname Local_Sample |
|
| 1322 |
#' |
|
| 1323 |
#' @returns A numeric vector of random draws. |
|
| 1324 | 213912x |
local_rnorm <- \(...) rnorm(...) |
| 1325 | ||
| 1326 |
#' @rdname Local_Sample |
|
| 1327 |
local_rnorm_vector <- \(n, mus, sigmas) {
|
|
| 1328 | 3x |
mapply(local_rnorm, n = n, mean = mus, sd = sigmas) |
| 1329 |
} |
|
| 1330 | ||
| 1331 |
#' @rdname Local_Sample |
|
| 1332 |
local_rhorseshoe <- \(n, df, df_global, df_slab, scale_global, scale_slab) {
|
|
| 1333 |
# Note: Half-t distribution is here the same as truncated t |
|
| 1334 |
# distribution because the t distribution is symmetric around 0. |
|
| 1335 | 14x |
local <- abs(local_rt(n, df, 0, 1)) |
| 1336 | 14x |
global <- abs(local_rt(n, df_global, 0, scale_global)) |
| 1337 | 14x |
slab <- local_rinvgamma(n, df_slab / 2, df_slab / 2) |
| 1338 | 14x |
c2 <- scale_slab^2 * slab |
| 1339 | 14x |
scales <- global * sqrt((c2 * local^2) / (c2 + global^2 * local^2)) |
| 1340 | 14x |
local_rnorm(n, mean = 0, sd = scales) |
| 1341 |
} |
|
| 1342 | ||
| 1343 |
#' @rdname Local_Sample |
|
| 1344 | 1405x |
local_rcauchy <- \(...) rcauchy(...) |
| 1345 | ||
| 1346 |
#' @rdname Local_Sample |
|
| 1347 | 1236x |
local_rgamma <- \(...) rgamma(...) |
| 1348 | ||
| 1349 |
#' @rdname Local_Sample |
|
| 1350 | 74750x |
local_rlnorm <- \(...) rlnorm(...) |
| 1351 | ||
| 1352 |
#' @rdname Local_Sample |
|
| 1353 | 8x |
local_rbeta <- \(...) rbeta(...) |
| 1354 | ||
| 1355 |
#' @rdname Local_Sample |
|
| 1356 | 1204x |
local_runif <- \(...) runif(...) |
| 1357 | ||
| 1358 |
#' @rdname Local_Sample |
|
| 1359 |
local_rt <- \(n, nu, mu, sigma) {
|
|
| 1360 | 49x |
rt(n, nu) * sigma + mu |
| 1361 |
} |
|
| 1362 | ||
| 1363 |
#' @rdname Local_Sample |
|
| 1364 | 6x |
local_rlogis <- \(...) rlogis(...) |
| 1365 | ||
| 1366 |
#' @rdname Local_Sample |
|
| 1367 |
local_rloglogis <- \(n, alpha, beta) {
|
|
| 1368 | 4x |
r <- runif(n) |
| 1369 | 4x |
((r / (1 - r))^(1 / beta)) * alpha |
| 1370 |
} |
|
| 1371 | ||
| 1372 |
#' @rdname Local_Sample |
|
| 1373 |
local_rinvgamma <- \(n, alpha, beta) {
|
|
| 1374 | 1039x |
1 / rgamma(n, alpha, rate = beta) |
| 1375 |
} |
| 1 |
#' @include LongitudinalRandomSlopeCov.R |
|
| 2 |
#' @include LongitudinalGSF.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Generalized Stein-Fojo longitudinal model with subject-level covariates |
|
| 6 |
#' |
|
| 7 |
#' Separate covariate predictors model the log-normal distribution parameters |
|
| 8 |
#' for baseline, shrinkage, growth, and the logit-normal treatment fraction. |
|
| 9 |
#' |
|
| 10 |
#' @section Available Links: |
|
| 11 |
#' - [`linkDSLD()`] |
|
| 12 |
#' - [`linkTTG()`] |
|
| 13 |
#' - [`linkIdentity()`] |
|
| 14 |
#' - [`linkGrowth()`] |
|
| 15 |
#' - [`linkShrinkage()`] |
|
| 16 |
#' |
|
| 17 |
#' @exportClass LongitudinalGSFCov |
|
| 18 |
.LongitudinalGSFCov <- setClass( |
|
| 19 |
"LongitudinalGSFCov", |
|
| 20 |
contains = "LongitudinalModel", |
|
| 21 |
slots = c( |
|
| 22 |
mu_b_formula = "formula", |
|
| 23 |
omega_b_formula = "formula", |
|
| 24 |
mu_s_formula = "formula", |
|
| 25 |
omega_s_formula = "formula", |
|
| 26 |
mu_g_formula = "formula", |
|
| 27 |
omega_g_formula = "formula", |
|
| 28 |
mu_phi_formula = "formula", |
|
| 29 |
omega_phi_formula = "formula", |
|
| 30 |
mu_b_parametrization = "character", |
|
| 31 |
omega_b_parametrization = "character", |
|
| 32 |
mu_s_parametrization = "character", |
|
| 33 |
omega_s_parametrization = "character", |
|
| 34 |
mu_g_parametrization = "character", |
|
| 35 |
omega_g_parametrization = "character", |
|
| 36 |
mu_phi_parametrization = "character", |
|
| 37 |
omega_phi_parametrization = "character", |
|
| 38 |
centred_baseline = "logical", |
|
| 39 |
centred_shrinkage = "logical", |
|
| 40 |
centred_growth = "logical", |
|
| 41 |
centred_phi = "logical" |
|
| 42 |
) |
|
| 43 |
) |
|
| 44 | ||
| 45 |
#' Construct a Generalized Stein-Fojo model with subject-level covariates |
|
| 46 |
#' |
|
| 47 |
#' Formula intercept columns are removed because each predictor has a separate |
|
| 48 |
#' intercept. `linear`, `proportional`, `exponential`, and `log-linear` |
|
| 49 |
#' parametrizations have the same meaning as in [LongitudinalSteinFojoCov()]. |
|
| 50 |
#' The `mu_phi` predictor is the location of the normal distribution on the |
|
| 51 |
#' logit scale; `phi` itself is obtained by applying the inverse-logit transform. |
|
| 52 |
#' |
|
| 53 |
#' @rdname LongitudinalGSFCov-class |
|
| 54 |
#' @typed mu_b_formula: formula |
|
| 55 |
#' One-sided formula for subject-level covariate effects on the baseline mean. |
|
| 56 |
#' @typed mu_s_formula: formula |
|
| 57 |
#' One-sided formula for subject-level covariate effects on the shrinkage mean. |
|
| 58 |
#' @typed mu_g_formula: formula |
|
| 59 |
#' One-sided formula for subject-level covariate effects on the growth mean. |
|
| 60 |
#' @typed mu_phi_formula: formula |
|
| 61 |
#' One-sided formula for subject-level covariate effects on the mean of the treatment fraction on the logit scale. |
|
| 62 |
#' @typed omega_b_formula: formula |
|
| 63 |
#' One-sided formula for subject-level covariate effects on the baseline standard deviation. |
|
| 64 |
#' @typed omega_s_formula: formula |
|
| 65 |
#' One-sided formula for subject-level covariate effects on the shrinkage standard deviation. |
|
| 66 |
#' @typed omega_g_formula: formula |
|
| 67 |
#' One-sided formula for subject-level covariate effects on the growth standard deviation. |
|
| 68 |
#' @typed omega_phi_formula: formula |
|
| 69 |
#' One-sided formula for subject-level covariate effects on the standard |
|
| 70 |
#' deviation of the treatment fraction on the logit scale. |
|
| 71 |
#' @typed mu_b_parametrization: character |
|
| 72 |
#' Parametrization of the baseline mean predictor. |
|
| 73 |
#' @typed mu_s_parametrization: character |
|
| 74 |
#' Parametrization of the shrinkage mean predictor. |
|
| 75 |
#' @typed mu_g_parametrization: character |
|
| 76 |
#' Parametrization of the growth mean predictor. |
|
| 77 |
#' @typed mu_phi_parametrization: character |
|
| 78 |
#' Parametrization of the mean predictor for the treatment fraction on the logit scale. |
|
| 79 |
#' @typed omega_b_parametrization: character |
|
| 80 |
#' Parametrization of the baseline standard deviation predictor. Only |
|
| 81 |
#' `"exponential"` and `"log-linear"` are supported. |
|
| 82 |
#' @typed omega_s_parametrization: character |
|
| 83 |
#' Parametrization of the shrinkage standard deviation predictor. Only |
|
| 84 |
#' `"exponential"` and `"log-linear"` are supported. |
|
| 85 |
#' @typed omega_g_parametrization: character |
|
| 86 |
#' Parametrization of the growth standard deviation predictor. Only `"exponential"` and `"log-linear"` are supported. |
|
| 87 |
#' @typed omega_phi_parametrization: character |
|
| 88 |
#' Parametrization of the standard deviation predictor for the treatment |
|
| 89 |
#' fraction on the logit scale. Only `"exponential"` and `"log-linear"` are |
|
| 90 |
#' supported. |
|
| 91 |
#' @typed mu_b_intercept_prior: Prior |
|
| 92 |
#' Prior for the intercept of the baseline mean predictor. |
|
| 93 |
#' @typed mu_s_intercept_prior: Prior |
|
| 94 |
#' Prior for the intercept of the shrinkage mean predictor. |
|
| 95 |
#' @typed mu_g_intercept_prior: Prior |
|
| 96 |
#' Prior for the intercept of the growth mean predictor. |
|
| 97 |
#' @typed mu_phi_intercept_prior: Prior |
|
| 98 |
#' Prior for the intercept of the mean predictor for the treatment fraction on the logit scale. |
|
| 99 |
#' @typed mu_b_coefficients_prior: Prior |
|
| 100 |
#' Prior for the covariate coefficients of the baseline mean predictor. |
|
| 101 |
#' @typed mu_s_coefficients_prior: Prior |
|
| 102 |
#' Prior for the covariate coefficients of the shrinkage mean predictor. |
|
| 103 |
#' @typed mu_g_coefficients_prior: Prior |
|
| 104 |
#' Prior for the covariate coefficients of the growth mean predictor. |
|
| 105 |
#' @typed mu_phi_coefficients_prior: Prior |
|
| 106 |
#' Prior for the covariate coefficients of the mean predictor for the treatment fraction on the logit scale. |
|
| 107 |
#' @typed omega_b_intercept_prior: Prior |
|
| 108 |
#' Prior for the intercept of the baseline standard deviation predictor. |
|
| 109 |
#' @typed omega_s_intercept_prior: Prior |
|
| 110 |
#' Prior for the intercept of the shrinkage standard deviation predictor. |
|
| 111 |
#' @typed omega_g_intercept_prior: Prior |
|
| 112 |
#' Prior for the intercept of the growth standard deviation predictor. |
|
| 113 |
#' @typed omega_phi_intercept_prior: Prior |
|
| 114 |
#' Prior for the intercept of the standard deviation predictor for the treatment fraction on the logit scale. |
|
| 115 |
#' @typed omega_b_coefficients_prior: Prior |
|
| 116 |
#' Prior for the covariate coefficients of the baseline standard deviation predictor. |
|
| 117 |
#' @typed omega_s_coefficients_prior: Prior |
|
| 118 |
#' Prior for the covariate coefficients of the shrinkage standard deviation predictor. |
|
| 119 |
#' @typed omega_g_coefficients_prior: Prior |
|
| 120 |
#' Prior for the covariate coefficients of the growth standard deviation predictor. |
|
| 121 |
#' @typed omega_phi_coefficients_prior: Prior |
|
| 122 |
#' Prior for the covariate coefficients of the standard deviation predictor |
|
| 123 |
#' for the treatment fraction on the logit scale. |
|
| 124 |
#' @typed sigma: Prior |
|
| 125 |
#' Observation-error standard deviation prior. |
|
| 126 |
#' @typed scaled_variance: logical |
|
| 127 |
#' Whether to use multiplicative observation error. |
|
| 128 |
#' @typed centred_baseline: logical |
|
| 129 |
#' Whether to use centred parameterizations. |
|
| 130 |
#' @typed centred_shrinkage: logical |
|
| 131 |
#' Whether to use centred parameterizations. |
|
| 132 |
#' @typed centred_growth: logical |
|
| 133 |
#' Whether to use centred parameterizations. |
|
| 134 |
#' @typed centred_phi: logical |
|
| 135 |
#' Whether to use centred parameterizations. |
|
| 136 |
#' @returns A `LongitudinalGSFCov` object. |
|
| 137 |
#' @export |
|
| 138 |
LongitudinalGSFCov <- function( |
|
| 139 |
mu_b_formula = ~study, |
|
| 140 |
omega_b_formula = ~study, |
|
| 141 |
mu_s_formula = ~arm, |
|
| 142 |
omega_s_formula = ~arm, |
|
| 143 |
mu_g_formula = ~arm, |
|
| 144 |
omega_g_formula = ~arm, |
|
| 145 |
mu_phi_formula = ~arm, |
|
| 146 |
omega_phi_formula = ~arm, |
|
| 147 |
mu_b_parametrization = "linear", |
|
| 148 |
omega_b_parametrization = "log-linear", |
|
| 149 |
mu_s_parametrization = "linear", |
|
| 150 |
omega_s_parametrization = "log-linear", |
|
| 151 |
mu_g_parametrization = "linear", |
|
| 152 |
omega_g_parametrization = "log-linear", |
|
| 153 |
mu_phi_parametrization = "linear", |
|
| 154 |
omega_phi_parametrization = "log-linear", |
|
| 155 |
mu_b_intercept_prior = prior_normal(log(60), 1), |
|
| 156 |
mu_b_coefficients_prior = prior_normal(0, 1), |
|
| 157 |
omega_b_intercept_prior = prior_normal(log(0.2), 1), |
|
| 158 |
omega_b_coefficients_prior = prior_normal(0, 1), |
|
| 159 |
mu_s_intercept_prior = prior_normal(log(0.5), 1), |
|
| 160 |
mu_s_coefficients_prior = prior_normal(0, 1), |
|
| 161 |
omega_s_intercept_prior = prior_normal(log(0.2), 1), |
|
| 162 |
omega_s_coefficients_prior = prior_normal(0, 1), |
|
| 163 |
mu_g_intercept_prior = prior_normal(log(0.3), 1), |
|
| 164 |
mu_g_coefficients_prior = prior_normal(0, 1), |
|
| 165 |
omega_g_intercept_prior = prior_normal(log(0.2), 1), |
|
| 166 |
omega_g_coefficients_prior = prior_normal(0, 1), |
|
| 167 |
mu_phi_intercept_prior = prior_normal(qlogis(0.5), 1), |
|
| 168 |
mu_phi_coefficients_prior = prior_normal(0, 1), |
|
| 169 |
omega_phi_intercept_prior = prior_normal(log(0.2), 1), |
|
| 170 |
omega_phi_coefficients_prior = prior_normal(0, 1), |
|
| 171 |
sigma = prior_lognormal(log(0.1), 1), |
|
| 172 |
scaled_variance = FALSE, |
|
| 173 |
centred_baseline = TRUE, |
|
| 174 |
centred_shrinkage = FALSE, |
|
| 175 |
centred_growth = FALSE, |
|
| 176 |
centred_phi = FALSE |
|
| 177 |
) {
|
|
| 178 | 13x |
lapply( |
| 179 | 13x |
c(centred_baseline, centred_shrinkage, centred_growth, centred_phi), |
| 180 | 13x |
assert_flag |
| 181 |
) |
|
| 182 | 13x |
names <- c( |
| 183 | 13x |
"mu_b", |
| 184 | 13x |
"omega_b", |
| 185 | 13x |
"mu_s", |
| 186 | 13x |
"omega_s", |
| 187 | 13x |
"mu_g", |
| 188 | 13x |
"omega_g", |
| 189 | 13x |
"mu_phi", |
| 190 | 13x |
"omega_phi" |
| 191 |
) |
|
| 192 | 13x |
formulas <- Map( |
| 193 | 13x |
.validate_covariate_formula, |
| 194 | 13x |
mget(paste0(names, "_formula"), inherits = FALSE), |
| 195 | 13x |
paste0(names, "_formula") |
| 196 |
) |
|
| 197 | 13x |
names(formulas) <- names |
| 198 | 13x |
parametrizations <- Map( |
| 199 | 13x |
function(value, name) {
|
| 200 | 104x |
.validate_covariate_parametrization( |
| 201 | 104x |
value, |
| 202 | 104x |
paste0(name, "_parametrization"), |
| 203 | 104x |
scale = startsWith(name, "omega_") |
| 204 |
) |
|
| 205 |
}, |
|
| 206 | 13x |
mget(paste0(names, "_parametrization"), inherits = FALSE), |
| 207 | 13x |
names |
| 208 |
) |
|
| 209 | 11x |
names(parametrizations) <- names |
| 210 | 11x |
for (name in names) {
|
| 211 | 88x |
prior_name <- paste0(name, "_intercept_prior") |
| 212 | 88x |
assign( |
| 213 | 88x |
prior_name, |
| 214 | 88x |
.positive_intercept_prior( |
| 215 | 88x |
get(prior_name, inherits = FALSE), |
| 216 | 88x |
parametrizations[[name]], |
| 217 | 88x |
positive = startsWith(name, "omega") && |
| 218 | 88x |
parametrizations[[name]] != "log-linear" |
| 219 |
) |
|
| 220 |
) |
|
| 221 |
} |
|
| 222 | 11x |
sigma <- set_limits(sigma, lower = getOption("jmpost.double_eps"))
|
| 223 | 11x |
predictor <- function(name) {
|
| 224 | 88x |
.covariate_predictor_stan( |
| 225 | 88x |
paste0("lm_gsfc_", name),
|
| 226 | 88x |
parametrizations[[name]] |
| 227 |
) |
|
| 228 |
} |
|
| 229 | 11x |
stan <- StanModule(decorated_render( |
| 230 | 11x |
.x = read_stan("lm-gsf-cov/model.stan"),
|
| 231 | 11x |
scaled_variance = scaled_variance, |
| 232 | 11x |
centred_baseline = centred_baseline, |
| 233 | 11x |
centred_shrinkage = centred_shrinkage, |
| 234 | 11x |
centred_growth = centred_growth, |
| 235 | 11x |
centred_phi = centred_phi, |
| 236 | 11x |
mu_b_predictor = predictor("mu_b"),
|
| 237 | 11x |
omega_b_predictor = predictor("omega_b"),
|
| 238 | 11x |
mu_s_predictor = predictor("mu_s"),
|
| 239 | 11x |
omega_s_predictor = predictor("omega_s"),
|
| 240 | 11x |
mu_g_predictor = predictor("mu_g"),
|
| 241 | 11x |
omega_g_predictor = predictor("omega_g"),
|
| 242 | 11x |
mu_phi_predictor = predictor("mu_phi"),
|
| 243 | 11x |
omega_phi_predictor = predictor("omega_phi")
|
| 244 |
)) |
|
| 245 | 11x |
intercept_priors <- mget( |
| 246 | 11x |
paste0(names, "_intercept_prior"), |
| 247 | 11x |
inherits = FALSE |
| 248 |
) |
|
| 249 | 11x |
names(intercept_priors) <- names |
| 250 | 11x |
coefficient_priors <- mget( |
| 251 | 11x |
paste0(names, "_coefficients_prior"), |
| 252 | 11x |
inherits = FALSE |
| 253 |
) |
|
| 254 | 11x |
names(coefficient_priors) <- names |
| 255 | 11x |
parameters <- unlist( |
| 256 | 11x |
lapply(names, function(name) {
|
| 257 | 88x |
list( |
| 258 | 88x |
Parameter( |
| 259 | 88x |
name = paste0("lm_gsfc_", name, "_intercept"),
|
| 260 | 88x |
prior = intercept_priors[[name]] |
| 261 |
), |
|
| 262 | 88x |
Parameter( |
| 263 | 88x |
name = paste0("lm_gsfc_", name, "_coefficients"),
|
| 264 | 88x |
prior = coefficient_priors[[name]], |
| 265 | 88x |
size = paste0("p_lm_gsfc_", name)
|
| 266 |
) |
|
| 267 |
) |
|
| 268 |
}), |
|
| 269 | 11x |
recursive = FALSE |
| 270 |
) |
|
| 271 | 11x |
subject_parameter <- function(name, centred) {
|
| 272 | 44x |
if (!centred) {
|
| 273 | 32x |
return(Parameter( |
| 274 | 32x |
name = paste0("lm_gsfc_eta_tilde_", name),
|
| 275 | 32x |
prior = prior_std_normal(), |
| 276 | 32x |
size = "n_subjects" |
| 277 |
)) |
|
| 278 |
} |
|
| 279 | 12x |
if (identical(name, "phi")) {
|
| 280 | 1x |
return(Parameter( |
| 281 | 1x |
name = "lm_gsfc_psi_phi_logit", |
| 282 | 1x |
prior = prior_init_only(prior_normal( |
| 283 | 1x |
.predictor_reference_value( |
| 284 | 1x |
intercept_priors$mu_phi, |
| 285 | 1x |
parametrizations$mu_phi |
| 286 |
), |
|
| 287 | 1x |
.predictor_reference_value( |
| 288 | 1x |
intercept_priors$omega_phi, |
| 289 | 1x |
parametrizations$omega_phi |
| 290 |
) |
|
| 291 |
)), |
|
| 292 | 1x |
size = "n_subjects" |
| 293 |
)) |
|
| 294 |
} |
|
| 295 | 11x |
Parameter( |
| 296 | 11x |
name = paste0("lm_gsfc_psi_", name),
|
| 297 | 11x |
prior = set_limits( |
| 298 | 11x |
prior_init_only(prior_lognormal( |
| 299 | 11x |
.predictor_reference_value( |
| 300 | 11x |
intercept_priors[[paste0("mu_", name)]],
|
| 301 | 11x |
parametrizations[[paste0("mu_", name)]]
|
| 302 |
), |
|
| 303 | 11x |
.predictor_reference_value( |
| 304 | 11x |
intercept_priors[[paste0("omega_", name)]],
|
| 305 | 11x |
parametrizations[[paste0("omega_", name)]]
|
| 306 |
) |
|
| 307 |
)), |
|
| 308 | 11x |
lower = getOption("jmpost.double_eps")
|
| 309 |
), |
|
| 310 | 11x |
size = "n_subjects" |
| 311 |
) |
|
| 312 |
} |
|
| 313 | 11x |
parameters <- c( |
| 314 | 11x |
parameters, |
| 315 | 11x |
list(Parameter(name = "lm_gsfc_sigma", prior = sigma)), |
| 316 | 11x |
unname(Map( |
| 317 | 11x |
subject_parameter, |
| 318 | 11x |
c("b", "s", "g", "phi"),
|
| 319 | 11x |
c(centred_baseline, centred_shrinkage, centred_growth, centred_phi) |
| 320 |
)) |
|
| 321 |
) |
|
| 322 | 11x |
.LongitudinalGSFCov( |
| 323 | 11x |
LongitudinalModel( |
| 324 | 11x |
name = "Generalized Stein-Fojo with Covariates", |
| 325 | 11x |
stan = merge(stan, StanModule("lm-gsf/functions.stan")),
|
| 326 | 11x |
parameters = do.call(ParameterList, parameters), |
| 327 | 11x |
scaled_variance = scaled_variance |
| 328 |
), |
|
| 329 | 11x |
mu_b_formula = formulas$mu_b, |
| 330 | 11x |
omega_b_formula = formulas$omega_b, |
| 331 | 11x |
mu_s_formula = formulas$mu_s, |
| 332 | 11x |
omega_s_formula = formulas$omega_s, |
| 333 | 11x |
mu_g_formula = formulas$mu_g, |
| 334 | 11x |
omega_g_formula = formulas$omega_g, |
| 335 | 11x |
mu_phi_formula = formulas$mu_phi, |
| 336 | 11x |
omega_phi_formula = formulas$omega_phi, |
| 337 | 11x |
mu_b_parametrization = parametrizations$mu_b, |
| 338 | 11x |
omega_b_parametrization = parametrizations$omega_b, |
| 339 | 11x |
mu_s_parametrization = parametrizations$mu_s, |
| 340 | 11x |
omega_s_parametrization = parametrizations$omega_s, |
| 341 | 11x |
mu_g_parametrization = parametrizations$mu_g, |
| 342 | 11x |
omega_g_parametrization = parametrizations$omega_g, |
| 343 | 11x |
mu_phi_parametrization = parametrizations$mu_phi, |
| 344 | 11x |
omega_phi_parametrization = parametrizations$omega_phi, |
| 345 | 11x |
centred_baseline = centred_baseline, |
| 346 | 11x |
centred_shrinkage = centred_shrinkage, |
| 347 | 11x |
centred_growth = centred_growth, |
| 348 | 11x |
centred_phi = centred_phi |
| 349 |
) |
|
| 350 |
} |
|
| 351 | ||
| 352 |
#' @export |
|
| 353 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 354 |
enableGQ.LongitudinalGSFCov <- function( |
|
| 355 |
object, |
|
| 356 |
generator = NULL, |
|
| 357 |
type = NULL, |
|
| 358 |
... |
|
| 359 |
) {
|
|
| 360 | 1x |
idv <- identical(type, "longitudinal") && |
| 361 | 1x |
is(generator, "QuantityGeneratorSubject") |
| 362 | 1x |
pop <- identical(type, "longitudinal") && |
| 363 | 1x |
is(generator, "QuantityGeneratorPopulation") |
| 364 | 1x |
predictor <- function(name) {
|
| 365 | 4x |
.covariate_predictor_stan( |
| 366 | 4x |
paste0("lm_gsfc_", name),
|
| 367 | 4x |
slot(object, paste0(name, "_parametrization")), |
| 368 | 4x |
design_prefix = paste0("gq_lm_gsfc_", name),
|
| 369 | 4x |
n_rows = "gq_n_quant" |
| 370 |
) |
|
| 371 |
} |
|
| 372 | 1x |
StanModule(decorated_render( |
| 373 | 1x |
.x = read_stan("lm-gsf-cov/quantities.stan"),
|
| 374 | 1x |
include_gq_longitudinal_idv = idv, |
| 375 | 1x |
include_gq_longitudinal_pop = pop, |
| 376 | 1x |
mu_b_population_predictor = predictor("mu_b"),
|
| 377 | 1x |
mu_s_population_predictor = predictor("mu_s"),
|
| 378 | 1x |
mu_g_population_predictor = predictor("mu_g"),
|
| 379 | 1x |
mu_phi_population_predictor = predictor("mu_phi")
|
| 380 |
)) |
|
| 381 |
} |
|
| 382 | ||
| 383 |
#' @rdname gq_population_stan_data |
|
| 384 |
#' @export |
|
| 385 |
gq_population_stan_data.LongitudinalGSFCov <- function( |
|
| 386 |
object, |
|
| 387 |
model, |
|
| 388 |
data = NULL, |
|
| 389 |
... |
|
| 390 |
) {
|
|
| 391 | 1x |
names <- c("mu_b", "mu_s", "mu_g", "mu_phi")
|
| 392 | 1x |
result <- list( |
| 393 | 1x |
declarations = paste( |
| 394 | 1x |
sprintf( |
| 395 | 1x |
"matrix[gq_n_quant, p_lm_gsfc_%s] gq_lm_gsfc_%s_design;", |
| 396 | 1x |
names, |
| 397 | 1x |
names |
| 398 |
), |
|
| 399 | 1x |
collapse = "\n" |
| 400 |
), |
|
| 401 | 1x |
data = list() |
| 402 |
) |
|
| 403 | 1x |
if (!is.null(data)) {
|
| 404 | ! |
assert_that( |
| 405 | ! |
!is.null(object@newdata) && |
| 406 | ! |
nrow(object@newdata) == length(object@times), |
| 407 | ! |
msg = "Population quantities for `LongitudinalGSFCov` require `GridPopulation(newdata = ...)`" |
| 408 |
) |
|
| 409 | ! |
subject_data <- as.data.frame(harmonise(data@subject)) |
| 410 | ! |
result$data <- stats::setNames( |
| 411 | ! |
lapply(names, function(name) {
|
| 412 | ! |
.covariate_prediction_design_matrix( |
| 413 | ! |
slot(model, paste0(name, "_formula")), |
| 414 | ! |
object@newdata, |
| 415 | ! |
subject_data, |
| 416 | ! |
paste0(name, "_formula") |
| 417 |
) |
|
| 418 |
}), |
|
| 419 | ! |
paste0("gq_lm_gsfc_", names, "_design")
|
| 420 |
) |
|
| 421 |
} |
|
| 422 | 1x |
result |
| 423 |
} |
|
| 424 | ||
| 425 |
#' @export |
|
| 426 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 427 |
enableLink.LongitudinalGSFCov <- function(object, ...) {
|
|
| 428 | 1x |
object@stan <- merge(object@stan, StanModule("lm-gsf-cov/link.stan"))
|
| 429 | 1x |
object |
| 430 |
} |
|
| 431 | ||
| 432 |
#' @export |
|
| 433 |
#' @returns A `LinkComponent` object. |
|
| 434 |
linkDSLD.LongitudinalGSFCov <- function( |
|
| 435 |
prior = prior_normal(0, 2), |
|
| 436 |
model, |
|
| 437 |
... |
|
| 438 |
) {
|
|
| 439 | 1x |
LinkComponent( |
| 440 | 1x |
key = "link_dsld", |
| 441 | 1x |
stan = StanModule("lm-gsf/link_dsld.stan"),
|
| 442 | 1x |
prior = prior |
| 443 |
) |
|
| 444 |
} |
|
| 445 |
#' @export |
|
| 446 |
#' @returns A `LinkComponent` object. |
|
| 447 |
linkTTG.LongitudinalGSFCov <- function(prior = prior_normal(0, 2), model, ...) {
|
|
| 448 | ! |
LinkComponent( |
| 449 | ! |
key = "link_ttg", |
| 450 | ! |
stan = StanModule("lm-gsf/link_ttg.stan"),
|
| 451 | ! |
prior = prior |
| 452 |
) |
|
| 453 |
} |
|
| 454 |
#' @export |
|
| 455 |
#' @returns A `LinkComponent` object. |
|
| 456 |
linkIdentity.LongitudinalGSFCov <- function( |
|
| 457 |
prior = prior_normal(0, 2), |
|
| 458 |
model, |
|
| 459 |
... |
|
| 460 |
) {
|
|
| 461 | ! |
LinkComponent( |
| 462 | ! |
key = "link_identity", |
| 463 | ! |
stan = StanModule("lm-gsf/link_identity.stan"),
|
| 464 | ! |
prior = prior |
| 465 |
) |
|
| 466 |
} |
|
| 467 |
#' @export |
|
| 468 |
#' @returns A `LinkComponent` object. |
|
| 469 |
linkGrowth.LongitudinalGSFCov <- function( |
|
| 470 |
prior = prior_normal(0, 2), |
|
| 471 |
model, |
|
| 472 |
... |
|
| 473 |
) {
|
|
| 474 | ! |
LinkComponent( |
| 475 | ! |
key = "link_growth", |
| 476 | ! |
stan = StanModule("lm-gsf/link_growth.stan"),
|
| 477 | ! |
prior = prior |
| 478 |
) |
|
| 479 |
} |
|
| 480 |
#' @export |
|
| 481 |
#' @returns A `LinkComponent` object. |
|
| 482 |
linkShrinkage.LongitudinalGSFCov <- function( |
|
| 483 |
prior = prior_normal(0, 2), |
|
| 484 |
model, |
|
| 485 |
... |
|
| 486 |
) {
|
|
| 487 | ! |
LinkComponent( |
| 488 | ! |
key = "link_shrinkage", |
| 489 | ! |
stan = StanModule("lm-gsf/link_shrinkage.stan"),
|
| 490 | ! |
prior = prior |
| 491 |
) |
|
| 492 |
} |
|
| 493 |
#' @rdname getPredictionNames |
|
| 494 |
#' @export |
|
| 495 |
getPredictionNames.LongitudinalGSFCov <- function(object, ...) {
|
|
| 496 | 1x |
c("b", "s", "g", "phi")
|
| 497 |
} |
|
| 498 |
#' @rdname getRandomEffectsNames |
|
| 499 |
#' @export |
|
| 500 |
getRandomEffectsNames.LongitudinalGSFCov <- function(object, ...) {
|
|
| 501 | 1x |
c( |
| 502 | 1x |
b = "lm_gsfc_psi_b", |
| 503 | 1x |
s = "lm_gsfc_psi_s", |
| 504 | 1x |
g = "lm_gsfc_psi_g", |
| 505 | 1x |
phi = "lm_gsfc_psi_phi" |
| 506 |
) |
|
| 507 |
} |
|
| 508 |
#' @rdname longitudinal_model_stan_data |
|
| 509 |
#' @export |
|
| 510 |
longitudinal_model_stan_data.LongitudinalGSFCov <- function(model, subject) {
|
|
| 511 | 5x |
subject_data <- as.data.frame(harmonise(subject)) |
| 512 | 5x |
names <- c( |
| 513 | 5x |
"mu_b", |
| 514 | 5x |
"omega_b", |
| 515 | 5x |
"mu_s", |
| 516 | 5x |
"omega_s", |
| 517 | 5x |
"mu_g", |
| 518 | 5x |
"omega_g", |
| 519 | 5x |
"mu_phi", |
| 520 | 5x |
"omega_phi" |
| 521 |
) |
|
| 522 | 5x |
designs <- stats::setNames( |
| 523 | 5x |
lapply(names, function(name) {
|
| 524 | 40x |
.covariate_design_matrix( |
| 525 | 40x |
slot(model, paste0(name, "_formula")), |
| 526 | 40x |
subject_data, |
| 527 | 40x |
paste0(name, "_formula") |
| 528 |
) |
|
| 529 |
}), |
|
| 530 | 5x |
names |
| 531 |
) |
|
| 532 | 5x |
unlist( |
| 533 | 5x |
lapply(names, function(name) {
|
| 534 | 40x |
stats::setNames( |
| 535 | 40x |
list(ncol(designs[[name]]), designs[[name]]), |
| 536 | 40x |
c( |
| 537 | 40x |
paste0("p_lm_gsfc_", name),
|
| 538 | 40x |
paste0("lm_gsfc_", name, "_design")
|
| 539 |
) |
|
| 540 |
) |
|
| 541 |
}), |
|
| 542 | 5x |
recursive = FALSE |
| 543 |
) |
|
| 544 |
} |
|
| 545 |
#' @rdname required_longitudinal_covs |
|
| 546 |
#' @export |
|
| 547 |
required_longitudinal_covs.LongitudinalGSFCov <- function(object, ...) {
|
|
| 548 | ! |
unique(unlist(lapply(c("mu_b", "mu_s", "mu_g", "mu_phi"), function(name) {
|
| 549 | ! |
all.vars(slot(object, paste0(name, "_formula"))) |
| 550 |
}))) |
|
| 551 |
} |
|
| 552 |
#' @rdname required_simulation_covariates |
|
| 553 |
#' @export |
|
| 554 |
required_simulation_covariates.LongitudinalGSFCov <- function(object, ...) {
|
|
| 555 | ! |
unique(unlist(lapply( |
| 556 | ! |
c( |
| 557 | ! |
"mu_b", |
| 558 | ! |
"omega_b", |
| 559 | ! |
"mu_s", |
| 560 | ! |
"omega_s", |
| 561 | ! |
"mu_g", |
| 562 | ! |
"omega_g", |
| 563 | ! |
"mu_phi", |
| 564 | ! |
"omega_phi" |
| 565 |
), |
|
| 566 | ! |
function(name) all.vars(slot(object, paste0(name, "_formula"))) |
| 567 |
))) |
|
| 568 |
} |
| 1 |
#' @include DataJoint.R |
|
| 2 |
#' @include Quantities.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' Re-used documentation for `SurvivalQuantities` |
|
| 7 |
#' |
|
| 8 |
#' @typed object: SurvivalQuantities |
|
| 9 |
#' survival quantities. |
|
| 10 |
#' @typed x: SurvivalQuantities |
|
| 11 |
#' survival quantities. |
|
| 12 |
#' @param ... not used. |
|
| 13 |
#' |
|
| 14 |
#' @keywords internal |
|
| 15 |
#' @name SurvivalQuantities-Shared |
|
| 16 |
NULL |
|
| 17 | ||
| 18 | ||
| 19 |
#' `SurvivalQuantities` Object & Constructor Function |
|
| 20 |
#' |
|
| 21 |
#' Constructor function to generate a `SurvivalQuantities` object. |
|
| 22 |
#' |
|
| 23 |
#' @slot quantities (`Quantities`)\cr The sampled quantities. Should contain 1 element per |
|
| 24 |
#' element of `group` |
|
| 25 |
#' @slot groups (`list`)\cr See argument section for details |
|
| 26 |
#' @slot type (`character`)\cr See See argument section for details |
|
| 27 |
#' @slot time_grid (`numeric`)\cr See argument section for details |
|
| 28 |
#' @slot data ([`DataJoint`])\cr The data that the Joint Model was fitted to to produce |
|
| 29 |
#' the samples/quantities |
|
| 30 |
#' |
|
| 31 |
#' @section Group Specification: |
|
| 32 |
#' If `groups` is a character vector of subject IDs then the survival quantities will |
|
| 33 |
#' only be calculated for those specific subjects. |
|
| 34 |
#' |
|
| 35 |
#' If `groups` is a list then any elements with more than 1 subject ID will be grouped together |
|
| 36 |
#' and their quantities will be calculated by taking a point-wise average. |
|
| 37 |
#' For example: `groups = list("g1" = c("sub1", "sub2"), "g2" = c("sub3", "sub4"))` would result
|
|
| 38 |
#' in 2 groups being created whose values are the pointwise average |
|
| 39 |
#' of `c("sub1", "sub2")` and `c("sub3", "sub4")` respectively.
|
|
| 40 |
#' |
|
| 41 |
#' If `groups=NULL` then all subjects from original dataset will be selected |
|
| 42 |
#' |
|
| 43 |
#' @family SurvivalQuantities |
|
| 44 |
#' @name SurvivalQuantities-class |
|
| 45 |
#' @export SurvivalQuantities |
|
| 46 |
#' |
|
| 47 |
#' @examples |
|
| 48 |
#' \dontrun{
|
|
| 49 |
#' SurvivalQuantities(fit, GridFixed(times = seq(0, 100, by = 10))) |
|
| 50 |
#' } |
|
| 51 |
.SurvivalQuantities <- setClass( |
|
| 52 |
Class = "SurvivalQuantities", |
|
| 53 |
slots = c( |
|
| 54 |
"quantities" = "Quantities", |
|
| 55 |
"grid" = "Grid", |
|
| 56 |
"type" = "character", |
|
| 57 |
"data" = "DataJoint" |
|
| 58 |
) |
|
| 59 |
) |
|
| 60 | ||
| 61 |
#' @typed object: JointModelSamples |
|
| 62 |
#' samples as drawn from a Joint Model. |
|
| 63 |
#' |
|
| 64 |
#' @typed grid: Grid |
|
| 65 |
#' object that specifies which subjects and time points to calculate the |
|
| 66 |
#' quantities for. See [Grid-Functions]. |
|
| 67 |
#' |
|
| 68 |
#' @typed type: character |
|
| 69 |
#' quantity to be generated. |
|
| 70 |
#' Must be one of `surv`, `haz`, `loghaz`, `cumhaz`. |
|
| 71 |
#' |
|
| 72 |
#' @rdname SurvivalQuantities-class |
|
| 73 |
#' |
|
| 74 |
#' @returns A `SurvivalQuantities` object. |
|
| 75 |
SurvivalQuantities <- function( |
|
| 76 |
object, |
|
| 77 |
grid, |
|
| 78 |
type = c("surv", "haz", "loghaz", "cumhaz")
|
|
| 79 |
) {
|
|
| 80 | 28x |
type <- match.arg(type) |
| 81 | 28x |
assert_class(object, "JointModelSamples") |
| 82 | 28x |
assert_class(grid, "Grid") |
| 83 | 26x |
assert_that( |
| 84 | 26x |
!is(grid, "GridPopulation"), |
| 85 | 26x |
msg = "GridPopulation objects are not supported for `SurvivalQuantities`" |
| 86 |
) |
|
| 87 | ||
| 88 | 25x |
time_grid <- seq( |
| 89 | 25x |
from = 0, |
| 90 | 25x |
to = max(as.list(object@data)[["event_times"]]), |
| 91 | 25x |
length = 201 |
| 92 |
) |
|
| 93 | ||
| 94 | 25x |
grid <- coalesceGridTime(grid, time_grid) |
| 95 | ||
| 96 | 25x |
generator <- as.QuantityGenerator(grid, data = object@data) |
| 97 | ||
| 98 | 25x |
assert_that( |
| 99 | 25x |
all(generator@times >= 0), |
| 100 | 25x |
msg = "Time points must be greater than or equal to 0" |
| 101 |
) |
|
| 102 | ||
| 103 | 23x |
gq <- generateQuantities( |
| 104 | 23x |
object, |
| 105 | 23x |
generator = generator, |
| 106 | 23x |
type = "survival" |
| 107 |
) |
|
| 108 | ||
| 109 | 22x |
quantities_raw <- extract_quantities(gq, type) |
| 110 | 22x |
collapser <- as.QuantityCollapser(grid, object@data) |
| 111 | 22x |
quantities <- collapse_quantities(quantities_raw, collapser) |
| 112 | ||
| 113 | 22x |
.SurvivalQuantities( |
| 114 | 22x |
quantities = Quantities( |
| 115 | 22x |
quantities, |
| 116 | 22x |
groups = collapser@groups, |
| 117 | 22x |
times = collapser@times |
| 118 |
), |
|
| 119 | 22x |
grid = grid, |
| 120 | 22x |
data = object@data, |
| 121 | 22x |
type = type |
| 122 |
) |
|
| 123 |
} |
|
| 124 | ||
| 125 | ||
| 126 |
#' `as.data.frame` |
|
| 127 |
#' |
|
| 128 |
#' @typed x: SurvivalQuantities |
|
| 129 |
#' longitudinal quantities. |
|
| 130 |
#' @param ... not used. |
|
| 131 |
#' @family SurvivalQuantities |
|
| 132 |
#' @export |
|
| 133 |
#' |
|
| 134 |
#' @returns A `data.frame` representation of the object. |
|
| 135 |
as.data.frame.SurvivalQuantities <- function(x, ...) {
|
|
| 136 | ! |
as.data.frame(x@quantities) |
| 137 |
} |
|
| 138 | ||
| 139 | ||
| 140 |
#' summary |
|
| 141 |
#' |
|
| 142 |
#' @description |
|
| 143 |
#' This method returns a `data.frame` of the longitudinal quantities. |
|
| 144 |
#' |
|
| 145 |
#' @typed conf.level: numeric |
|
| 146 |
#' confidence level of the interval. |
|
| 147 |
#' @inheritParams SurvivalQuantities-Shared |
|
| 148 |
#' |
|
| 149 |
#' @family SurvivalQuantities |
|
| 150 |
#' @family summary |
|
| 151 |
#' @export |
|
| 152 |
#' |
|
| 153 |
#' @returns A `data.frame` containing posterior summary statistics. |
|
| 154 |
summary.SurvivalQuantities <- function( |
|
| 155 |
object, |
|
| 156 |
conf.level = 0.95, |
|
| 157 |
... |
|
| 158 |
) {
|
|
| 159 | 21x |
summary(object@quantities, conf.level = conf.level) |
| 160 |
} |
|
| 161 | ||
| 162 | ||
| 163 |
#' Automatic Plotting for `SurvivalQuantities`` |
|
| 164 |
#' |
|
| 165 |
#' @inheritParams SurvivalQuantities-Shared |
|
| 166 |
#' @typed add_km: logical |
|
| 167 |
#' if `TRUE` Kaplan-Meier curves will be added to the plot for |
|
| 168 |
#' each group/subject. |
|
| 169 |
#' @typed add_wrap: logical |
|
| 170 |
#' if `TRUE` will apply a [ggplot2::facet_wrap()] to the plot |
|
| 171 |
#' by each group/subject. |
|
| 172 |
#' @typed conf.level: numeric |
|
| 173 |
#' confidence level of the interval. If values of `FALSE`, |
|
| 174 |
#' `NULL` or `0` are provided then confidence regions will not be added to the plot |
|
| 175 |
#' @param ... not used. |
|
| 176 |
#' |
|
| 177 |
#' @family SurvivalQuantities |
|
| 178 |
#' @family autoplot |
|
| 179 |
#' @export |
|
| 180 |
#' |
|
| 181 |
#' @returns A `ggplot` object. |
|
| 182 |
autoplot.SurvivalQuantities <- function( |
|
| 183 |
object, |
|
| 184 |
conf.level = 0.95, |
|
| 185 |
add_km = FALSE, |
|
| 186 |
add_wrap = TRUE, |
|
| 187 |
... |
|
| 188 |
) {
|
|
| 189 | 3x |
include_ci <- !is.null(conf.level) && |
| 190 | 3x |
is.numeric(conf.level) && |
| 191 | 3x |
conf.level > 0 |
| 192 |
# If CI aren't needed supply a default 0.95 to summary function as it needs |
|
| 193 |
# a value to be specified to work |
|
| 194 | 3x |
conf.level <- if (include_ci) conf.level else 0.95 |
| 195 | 3x |
assert_that( |
| 196 | 3x |
is.flag(add_km), |
| 197 | 3x |
length(conf.level) == 1, |
| 198 | 3x |
conf.level < 1, |
| 199 | 3x |
is.flag(add_wrap) |
| 200 |
) |
|
| 201 | 3x |
kmdf <- if (add_km) {
|
| 202 | 1x |
subset( |
| 203 | 1x |
object@data, |
| 204 | 1x |
as.list(object@grid, data = object@data) |
| 205 |
) |
|
| 206 |
} else {
|
|
| 207 | 2x |
NULL |
| 208 |
} |
|
| 209 | 3x |
all_fit_df <- summary(object, conf.level = conf.level) |
| 210 | 3x |
label <- switch( |
| 211 | 3x |
object@type, |
| 212 | 3x |
"surv" = expression(S(t)), |
| 213 | 3x |
"cumhaz" = expression(H(t)), |
| 214 | 3x |
"haz" = expression(h(t)), |
| 215 | 3x |
"loghaz" = expression(log(h(t))) |
| 216 |
) |
|
| 217 | 3x |
survival_plot( |
| 218 | 3x |
data = all_fit_df, |
| 219 | 3x |
add_ci = include_ci, |
| 220 | 3x |
add_wrap = add_wrap, |
| 221 | 3x |
kmdf = kmdf, |
| 222 | 3x |
y_label = label |
| 223 |
) |
|
| 224 |
} |
|
| 225 | ||
| 226 | ||
| 227 |
#' Survival Plot |
|
| 228 |
#' |
|
| 229 |
#' Internal plotting function to create survival plots with KM curve overlays |
|
| 230 |
#' This function predominately exists to extract core logic into its own function |
|
| 231 |
#' to enable easier unit testing. |
|
| 232 |
#' |
|
| 233 |
#' @typed data: data.frame |
|
| 234 |
#' summary statistics for a survival |
|
| 235 |
#' curve to be plotted. See details. |
|
| 236 |
#' @typed add_ci: logical |
|
| 237 |
#' should confidence intervals be added? Default = `TRUE`. |
|
| 238 |
#' @typed add_wrap: logical |
|
| 239 |
#' should the plots be wrapped by `data$group`? Default = `TRUE`. |
|
| 240 |
#' @typed kmdf: "`data.frame` or `NULL`" |
|
| 241 |
#' event times and status used to plot |
|
| 242 |
#' overlaying KM curves. If `NULL` no KM curve will be plotted. See details. |
|
| 243 |
#' @typed y_label: "`character` or `expression`" |
|
| 244 |
#' label to display on the y-axis. |
|
| 245 |
#' Default = `expression(S(t))`. |
|
| 246 |
#' @typed x_label: "`character` or `expression`" |
|
| 247 |
#' label to display on the x-axis. |
|
| 248 |
#' |
|
| 249 |
#' @details |
|
| 250 |
#' |
|
| 251 |
#' ## `data` |
|
| 252 |
#' Should contain the following columns: |
|
| 253 |
#' - `time` (`numeric`) \cr time point for the summary statistic. |
|
| 254 |
#' - `group` (`character`) \cr the group in which the observation belongs to. |
|
| 255 |
#' - `median` (`numeric`) \cr the median value for the summary statistic. |
|
| 256 |
#' - `upper` (`numeric`) \cr the upper 95% CI for the summary statistic. |
|
| 257 |
#' - `lower` (`numeric`) \cr the lower 95% CI for the summary statistic. |
|
| 258 |
#' |
|
| 259 |
#' ## `kmdf` |
|
| 260 |
#' Should contain the following columns: |
|
| 261 |
#' - `time` (`numeric`) \cr the time at which an event occurred. |
|
| 262 |
#' - `event` (`numeric`) \cr 1/0 status indicator for the event. |
|
| 263 |
#' - `group` (`character`) \cr which group the event belongs to, should correspond to values in `data$group`. |
|
| 264 |
#' @keywords internal |
|
| 265 |
#' |
|
| 266 |
#' @returns A `ggplot` object. |
|
| 267 |
survival_plot <- function( |
|
| 268 |
data, |
|
| 269 |
add_ci = TRUE, |
|
| 270 |
add_wrap = TRUE, |
|
| 271 |
kmdf = NULL, |
|
| 272 |
y_label = expression(S(t)), |
|
| 273 |
x_label = expression(t) |
|
| 274 |
) {
|
|
| 275 | 3x |
assert_that( |
| 276 | 3x |
is.flag(add_ci), |
| 277 | 3x |
is.flag(add_wrap), |
| 278 | 3x |
is.expression(y_label) || is.character(y_label), |
| 279 | 3x |
is.expression(x_label) || is.character(x_label), |
| 280 | 3x |
is.null(kmdf) | is.data.frame(kmdf) |
| 281 |
) |
|
| 282 | ||
| 283 | 3x |
p <- ggplot() + |
| 284 | 3x |
xlab(x_label) + |
| 285 | 3x |
ylab(y_label) + |
| 286 | 3x |
theme_bw() |
| 287 | ||
| 288 | 3x |
if (add_wrap) {
|
| 289 | 1x |
p <- p + facet_wrap(~group) |
| 290 | 1x |
aes_ci <- aes(x = .data$time, ymin = .data$lower, ymax = .data$upper) |
| 291 | 1x |
aes_line <- aes(x = .data$time, y = .data$median) |
| 292 | 1x |
aes_km <- aes(time = .data$time, status = .data$event) |
| 293 |
} else {
|
|
| 294 | 2x |
aes_ci <- aes( |
| 295 | 2x |
x = .data$time, |
| 296 | 2x |
ymin = .data$lower, |
| 297 | 2x |
ymax = .data$upper, |
| 298 | 2x |
fill = .data$group, |
| 299 | 2x |
group = .data$group |
| 300 |
) |
|
| 301 | 2x |
aes_line <- aes( |
| 302 | 2x |
x = .data$time, |
| 303 | 2x |
y = .data$median, |
| 304 | 2x |
colour = .data$group, |
| 305 | 2x |
group = .data$group |
| 306 |
) |
|
| 307 | 2x |
aes_km <- aes( |
| 308 | 2x |
time = .data$time, |
| 309 | 2x |
status = .data$event, |
| 310 | 2x |
group = .data$group, |
| 311 | 2x |
colour = .data$group |
| 312 |
) |
|
| 313 |
} |
|
| 314 | 3x |
p <- p + geom_line(aes_line, data = data) |
| 315 | 3x |
if (add_ci) {
|
| 316 | 1x |
p <- p + geom_ribbon(aes_ci, data = data, alpha = 0.3) |
| 317 |
} |
|
| 318 | 3x |
if (!is.null(kmdf)) {
|
| 319 | 1x |
p <- p + |
| 320 | 1x |
ggplot2.utils::geom_km(aes_km, data = kmdf) + |
| 321 | 1x |
ggplot2.utils::geom_km_ticks(aes_km, data = kmdf) |
| 322 |
} |
|
| 323 | 3x |
p |
| 324 |
} |
|
| 325 | ||
| 326 | ||
| 327 |
#' @rdname show-object |
|
| 328 |
#' @export |
|
| 329 |
setMethod( |
|
| 330 |
f = "show", |
|
| 331 |
signature = "SurvivalQuantities", |
|
| 332 |
definition = function(object) {
|
|
| 333 | 2x |
template <- c( |
| 334 | 2x |
"SurvivalQuantities Object:", |
| 335 | 2x |
" # of samples = %d", |
| 336 | 2x |
" # of quantities = %d", |
| 337 | 2x |
" Type = %s" |
| 338 |
) |
|
| 339 | 2x |
string <- sprintf( |
| 340 | 2x |
paste(template, collapse = "\n"), |
| 341 | 2x |
nrow(object@quantities), |
| 342 | 2x |
ncol(object@quantities), |
| 343 | 2x |
object@type |
| 344 |
) |
|
| 345 | 2x |
cat("\n", string, "\n\n")
|
| 346 |
} |
|
| 347 |
) |
|
| 348 | ||
| 349 | ||
| 350 |
#' `brierScore` |
|
| 351 |
#' |
|
| 352 |
#' @description |
|
| 353 |
#' Derives the Brier Scores (using Inverse Probability of Censoring Weighting) |
|
| 354 |
#' for the Survival estimates as detailed in \insertCite{blanche2015}{jmpost}.
|
|
| 355 |
#' |
|
| 356 |
#' @inheritParams SurvivalQuantities-Shared |
|
| 357 |
#' @inheritParams Brier-Score-Shared |
|
| 358 |
#' |
|
| 359 |
#' @family brierScore |
|
| 360 |
#' @family SurvivalQuantities |
|
| 361 |
#' @references |
|
| 362 |
#' \insertAllCited{}
|
|
| 363 |
#' @export |
|
| 364 |
#' |
|
| 365 |
#' @returns A `data.frame` containing Brier scores by time and group. |
|
| 366 |
brierScore.SurvivalQuantities <- function( |
|
| 367 |
object, |
|
| 368 |
maintain_cen_order = TRUE, |
|
| 369 |
event_offset = TRUE, |
|
| 370 |
... |
|
| 371 |
) {
|
|
| 372 | 3x |
assert_that( |
| 373 | 3x |
object@type == "surv", |
| 374 | 3x |
msg = paste( |
| 375 | 3x |
"Brier Score can only be calculated when the survival quantities were", |
| 376 | 3x |
"generated with `type = 'surv'`", |
| 377 | 3x |
collapse = " " |
| 378 |
) |
|
| 379 |
) |
|
| 380 | 3x |
assert_that( |
| 381 | 3x |
is(object@grid, "GridFixed"), |
| 382 | 3x |
msg = paste( |
| 383 | 3x |
"Brier Score can only be calculated when the survival quantities were", |
| 384 | 3x |
"generated with `grid = GridFixed()`", |
| 385 | 3x |
collapse = " " |
| 386 |
) |
|
| 387 |
) |
|
| 388 | ||
| 389 | 3x |
sdat <- summary(object) |
| 390 | 3x |
times <- unique(as.QuantityGenerator(object@grid, object@data)@times) |
| 391 | 3x |
times <- times[order(times)] |
| 392 | 3x |
assert_that( |
| 393 | 3x |
nrow(sdat) == length(times) * length(unique(sdat$group)) |
| 394 |
) |
|
| 395 | ||
| 396 | 3x |
subject_col <- extractVariableNames(object@data@subject)$subject |
| 397 | 3x |
time_col <- extractVariableNames(object@data@survival)$time |
| 398 | 3x |
event_col <- extractVariableNames(object@data@survival)$event |
| 399 | 3x |
groups <- as.character(object@data@survival@data[[subject_col]]) |
| 400 | 3x |
orig_times <- object@data@survival@data[[time_col]] |
| 401 | 3x |
events <- as.numeric(object@data@survival@data[[event_col]]) |
| 402 | ||
| 403 | 3x |
pred_mat <- matrix( |
| 404 | 3x |
ncol = length(times), |
| 405 | 3x |
nrow = length(unique(sdat$group)) |
| 406 |
) |
|
| 407 | 3x |
for (i in seq_along(times)) {
|
| 408 | 18x |
pred_mat[, i] <- sdat[sdat["time"] == times[i], "median"] |
| 409 | 18x |
assert_that( |
| 410 | 18x |
all(groups == sdat[sdat["time"] == times[i], "group"]) |
| 411 |
) |
|
| 412 |
} |
|
| 413 | 3x |
brier_score( |
| 414 | 3x |
t = times, |
| 415 | 3x |
times = orig_times, |
| 416 | 3x |
events = events, |
| 417 | 3x |
pred_mat = 1 - pred_mat, |
| 418 | 3x |
maintain_cen_order = maintain_cen_order, |
| 419 | 3x |
event_offset = event_offset |
| 420 |
) |
|
| 421 |
} |
| 1 |
#' @include generics.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
# STAN_BLOCKS ---- |
|
| 5 | ||
| 6 |
#' List of Stan Blocks |
|
| 7 |
#' |
|
| 8 |
#' @description |
|
| 9 |
#' A list with 1 element per standard Stan program blocks. |
|
| 10 |
#' This object is mostly used internally as a reference for |
|
| 11 |
#' what blocks are expected to exist within a given Stan program. |
|
| 12 |
#' |
|
| 13 |
#' @export |
|
| 14 |
STAN_BLOCKS <- list( |
|
| 15 |
functions = "functions", |
|
| 16 |
data = "data", |
|
| 17 |
transformed_data = "transformed data", |
|
| 18 |
parameters = "parameters", |
|
| 19 |
transformed_parameters = "transformed parameters", |
|
| 20 |
model = "model", |
|
| 21 |
generated_quantities = "generated quantities" |
|
| 22 |
) |
|
| 23 | ||
| 24 |
# add_missing_stan_blocks ---- |
|
| 25 | ||
| 26 |
#' Add Missing Stan Blocks |
|
| 27 |
#' |
|
| 28 |
#' @typed x: list |
|
| 29 |
#' list of Stan code blocks |
|
| 30 |
#' @typed stan_blocks: list |
|
| 31 |
#' reference list of stan blocks. |
|
| 32 |
#' |
|
| 33 |
#' @return Amended list `x` such that all blocks in the global variable |
|
| 34 |
#' `STAN_BLOCKS` are contained. |
|
| 35 |
#' |
|
| 36 |
#' @keywords internal |
|
| 37 |
add_missing_stan_blocks <- function(x, stan_blocks = STAN_BLOCKS) {
|
|
| 38 | 827x |
for (block in names(stan_blocks)) {
|
| 39 | 5784x |
if (is.null(x[[block]])) {
|
| 40 | 433x |
x[[block]] <- "" |
| 41 |
} |
|
| 42 |
} |
|
| 43 | 827x |
return(x) |
| 44 |
} |
|
| 45 | ||
| 46 |
# StanModule-class ---- |
|
| 47 | ||
| 48 |
#' `StanModule` Object and Constructor Function |
|
| 49 |
#' |
|
| 50 |
#' @typed x: string |
|
| 51 |
#' file path to a Stan program or a character vector |
|
| 52 |
#' of Stan code to be parsed. |
|
| 53 |
#' @param ... additional arguments passed to the constructor. |
|
| 54 |
#' |
|
| 55 |
#' @slot functions (`character`)\cr the `functions` block. |
|
| 56 |
#' @slot data (`character`)\cr the `data` block. |
|
| 57 |
#' @slot transformed_data (`character`)\cr the `transformed_data` block. |
|
| 58 |
#' @slot parameters (`character`)\cr the `parameters` block. |
|
| 59 |
#' @slot transformed_parameters (`character`)\cr the `transformed_parameters` block. |
|
| 60 |
#' @slot model (`character`)\cr the `model` block. |
|
| 61 |
#' @slot generated_quantities (`character`)\cr the `generated_quantities` block. |
|
| 62 |
#' |
|
| 63 |
#' @exportClass StanModule |
|
| 64 |
#' @export StanModule |
|
| 65 |
#' @family StanModule |
|
| 66 |
.StanModule <- setClass( |
|
| 67 |
Class = "StanModule", |
|
| 68 |
slots = list( |
|
| 69 |
functions = "character", |
|
| 70 |
data = "character", |
|
| 71 |
transformed_data = "character", |
|
| 72 |
parameters = "character", |
|
| 73 |
transformed_parameters = "character", |
|
| 74 |
model = "character", |
|
| 75 |
generated_quantities = "character" |
|
| 76 |
) |
|
| 77 |
) |
|
| 78 | ||
| 79 |
# StanModule-constructors ---- |
|
| 80 | ||
| 81 |
#' @rdname StanModule-class |
|
| 82 |
#' |
|
| 83 |
#' @returns A `StanModule` object. |
|
| 84 |
#' |
|
| 85 |
#' @examples |
|
| 86 |
#' code <- " |
|
| 87 |
#' parameters {
|
|
| 88 |
#' real x; |
|
| 89 |
#' } |
|
| 90 |
#' model {
|
|
| 91 |
#' x ~ normal(0, 1); |
|
| 92 |
#' } |
|
| 93 |
#' " |
|
| 94 |
#' StanModule(code) |
|
| 95 |
StanModule <- function( |
|
| 96 |
x = "", |
|
| 97 |
... |
|
| 98 |
) {
|
|
| 99 | 8532x |
assert_that( |
| 100 | 8532x |
is.character(x), |
| 101 | 8532x |
length(x) == 1, |
| 102 | 8532x |
msg = "`x` must be a length 1 character vector" |
| 103 |
) |
|
| 104 | 8532x |
code <- read_stan(x) |
| 105 | 8532x |
code_fragments <- as_stan_fragments(code) |
| 106 | ||
| 107 |
if ( |
|
| 108 | 8529x |
paste0(unlist(code_fragments), collapse = "") == "" && |
| 109 | 8529x |
paste0(x, collaspe = "") != "" |
| 110 |
) {
|
|
| 111 | 1x |
warning( |
| 112 | 1x |
"Non-empty input resulted in an empty StanModule object. Is the input correct?" |
| 113 |
) |
|
| 114 |
} |
|
| 115 | ||
| 116 | 8529x |
.StanModule( |
| 117 | 8529x |
functions = code_fragments$functions, |
| 118 | 8529x |
data = code_fragments$data, |
| 119 | 8529x |
transformed_data = code_fragments$transformed_data, |
| 120 | 8529x |
parameters = code_fragments$parameters, |
| 121 | 8529x |
transformed_parameters = code_fragments$transformed_parameters, |
| 122 | 8529x |
model = code_fragments$model, |
| 123 | 8529x |
generated_quantities = code_fragments$generated_quantities, |
| 124 |
... |
|
| 125 |
) |
|
| 126 |
} |
|
| 127 | ||
| 128 |
# as.character-StanModule ---- |
|
| 129 | ||
| 130 |
#' `StanModule` -> `character` |
|
| 131 |
#' @typed x: StanModule |
|
| 132 |
#' A stan program |
|
| 133 |
#' @param ... Not Used. |
|
| 134 |
#' @description |
|
| 135 |
#' Converts a [`StanModule`] object into a valid Stan program file where each |
|
| 136 |
#' line of the returned `character` vector represents a line of the program |
|
| 137 |
#' @family StanModule |
|
| 138 |
#' @export |
|
| 139 |
#' |
|
| 140 |
#' @returns A character vector. |
|
| 141 |
as.character.StanModule <- function(x, ...) {
|
|
| 142 | 948x |
as_stan_file( |
| 143 | 948x |
functions = x@functions, |
| 144 | 948x |
data = x@data, |
| 145 | 948x |
transformed_data = x@transformed_data, |
| 146 | 948x |
parameters = x@parameters, |
| 147 | 948x |
transformed_parameters = x@transformed_parameters, |
| 148 | 948x |
model = x@model, |
| 149 | 948x |
generated_quantities = x@generated_quantities |
| 150 |
) |
|
| 151 |
} |
|
| 152 | ||
| 153 | ||
| 154 |
# merge-StanModule,StanModule ---- |
|
| 155 | ||
| 156 |
#' @typed stan_blocks: list |
|
| 157 |
#' reference list of stan blocks. |
|
| 158 |
#' @rdname merge |
|
| 159 |
setMethod( |
|
| 160 |
f = "merge", |
|
| 161 |
signature = c("StanModule", "StanModule"),
|
|
| 162 |
definition = function(x, y, stan_blocks = STAN_BLOCKS, ...) {
|
|
| 163 | 3687x |
stan_fragments <- lapply( |
| 164 | 3687x |
names(stan_blocks), |
| 165 | 3687x |
\(par) {
|
| 166 | 25809x |
if (all(slot(y, par) == "")) {
|
| 167 | 18675x |
return(slot(x, par)) |
| 168 |
} |
|
| 169 | 7134x |
if (all(slot(x, par) == "")) {
|
| 170 | 2721x |
return(slot(y, par)) |
| 171 |
} |
|
| 172 | 4413x |
return(c(slot(x, par), slot(y, par))) |
| 173 |
} |
|
| 174 |
) |
|
| 175 | 3687x |
names(stan_fragments) <- names(stan_blocks) |
| 176 | 3687x |
stan_code <- do.call(as_stan_file, stan_fragments) |
| 177 | 3687x |
StanModule( |
| 178 | 3687x |
x = stan_code |
| 179 |
) |
|
| 180 |
} |
|
| 181 |
) |
|
| 182 | ||
| 183 |
# compileStanModel-StanModule,character ---- |
|
| 184 | ||
| 185 |
#' @rdname compileStanModel |
|
| 186 |
#' @export |
|
| 187 |
compileStanModel.StanModule <- function(object) {
|
|
| 188 | 55x |
exe_dir <- getOption("jmpost.cache_dir")
|
| 189 | 55x |
if (!dir.exists(exe_dir)) {
|
| 190 | 3x |
dir.create(exe_dir, recursive = TRUE) |
| 191 |
} |
|
| 192 | 55x |
stan_code <- as.character(object) |
| 193 | 55x |
hash_name <- digest::digest(stan_code, "md5") |
| 194 | 55x |
exe_name <- paste0( |
| 195 | 55x |
"model_", |
| 196 | 55x |
hash_name, |
| 197 | 55x |
if (is_windows()) ".exe" else "" |
| 198 |
) |
|
| 199 | 55x |
exe_file <- file.path(exe_dir, exe_name) |
| 200 | 55x |
source_file <- cmdstanr::write_stan_file( |
| 201 | 55x |
code = stan_code, |
| 202 | 55x |
dir = exe_dir, |
| 203 | 55x |
basename = sprintf("model_%s.stan", hash_name)
|
| 204 |
) |
|
| 205 | ||
| 206 |
# Suppress "model executable is up to date" message as |
|
| 207 |
# users are not in control of the cache so this message is meaningless |
|
| 208 | 55x |
withCallingHandlers( |
| 209 |
{
|
|
| 210 | 55x |
x <- cmdstanr::cmdstan_model( |
| 211 | 55x |
stan_file = source_file, |
| 212 | 55x |
exe_file = exe_file, |
| 213 | 55x |
quiet = TRUE |
| 214 |
) |
|
| 215 |
}, |
|
| 216 | 55x |
message = function(m) {
|
| 217 | ! |
if (m$message == "Model executable is up to date!\n") {
|
| 218 | ! |
invokeRestart("muffleMessage")
|
| 219 |
} |
|
| 220 |
} |
|
| 221 |
) |
|
| 222 | 55x |
invisible(x) |
| 223 |
} |
|
| 224 | ||
| 225 | ||
| 226 |
# as.list-StanModule ---- |
|
| 227 | ||
| 228 |
#' `StanModule` -> `list` |
|
| 229 |
#' @description |
|
| 230 |
#' Returns a named list where each element of the list corresponds |
|
| 231 |
#' to a Stan modelling block e.g. `data`, `model`, etc. |
|
| 232 |
#' @typed x: StanModule |
|
| 233 |
#' A Stan Module |
|
| 234 |
#' @typed stan_blocks: list |
|
| 235 |
#' reference list of stan blocks. |
|
| 236 |
#' @param ... Not Used. |
|
| 237 |
#' @family StanModule |
|
| 238 |
#' @export |
|
| 239 |
#' |
|
| 240 |
#' @returns A named `list` representation of the object. |
|
| 241 |
as.list.StanModule <- function(x, stan_blocks = STAN_BLOCKS, ...) {
|
|
| 242 | 764x |
string <- as.character(x) |
| 243 | 764x |
li <- as_stan_fragments(string) |
| 244 | 764x |
for (block in names(stan_blocks)) {
|
| 245 | 5348x |
li[[block]] <- paste(li[[block]], collapse = "\n") |
| 246 |
} |
|
| 247 | 764x |
return(li) |
| 248 |
} |
|
| 249 | ||
| 250 |
# is_file ---- |
|
| 251 | ||
| 252 |
#' Is String a Valid File? |
|
| 253 |
#' |
|
| 254 |
#' A utility function to check if a string is a valid file or not. |
|
| 255 |
#' Used to help address short comings of [file.exists()] that will return `TRUE` |
|
| 256 |
#' for a directory as well as a file. |
|
| 257 |
#' |
|
| 258 |
#' @typed filename: string |
|
| 259 |
#' file name. |
|
| 260 |
#' |
|
| 261 |
#' @keywords internal |
|
| 262 |
#' |
|
| 263 |
#' @returns `TRUE` if the input identifies an existing file; otherwise `FALSE`. |
|
| 264 |
is_file <- function(filename = NULL) {
|
|
| 265 | 27568x |
if (is.null(filename)) {
|
| 266 | ! |
return(FALSE) |
| 267 |
} |
|
| 268 | 27568x |
assert_that( |
| 269 | 27568x |
is.character(filename), |
| 270 | 27568x |
length(filename) == 1, |
| 271 | 27568x |
msg = "`filename` must be a length 1 character" |
| 272 |
) |
|
| 273 | 27568x |
if (nchar(filename) > 1000) {
|
| 274 | 4373x |
return(FALSE) |
| 275 |
} |
|
| 276 | 23195x |
if (is.na(filename)) {
|
| 277 | ! |
return(FALSE) |
| 278 |
} |
|
| 279 | 23195x |
return(file.exists(filename) & !dir.exists(filename)) |
| 280 |
} |
|
| 281 | ||
| 282 |
# read_stan ---- |
|
| 283 | ||
| 284 |
#' Stan Code as Character |
|
| 285 |
#' |
|
| 286 |
#' @param string Character, either the absolute path of a stan file, or the name of the stan |
|
| 287 |
#' file in the package directory or the stan code as a string. |
|
| 288 |
#' |
|
| 289 |
#' @returns A character vector containing Stan program code. |
|
| 290 |
read_stan <- function(string) {
|
|
| 291 | 9198x |
local_inst_file <- file.path("inst", "stan", string)
|
| 292 | 9198x |
system_file <- system.file(file.path("stan", string), package = "jmpost")
|
| 293 | 9198x |
local_file <- string |
| 294 | 9198x |
files <- c(local_file, local_inst_file, system_file) |
| 295 | 9198x |
for (fi in files) {
|
| 296 | 27568x |
if (is_file(fi)) {
|
| 297 | 1299x |
string <- readLines(fi) |
| 298 | 1299x |
break |
| 299 |
} |
|
| 300 |
} |
|
| 301 | 9198x |
string <- paste0(string, collapse = "\n") |
| 302 | 9198x |
return(string) |
| 303 |
} |
|
| 304 | ||
| 305 |
# as_stan_file ---- |
|
| 306 | ||
| 307 |
#' Merging Code Blocks into Stan Code Character Vector |
|
| 308 |
#' |
|
| 309 |
#' @typed functions: character |
|
| 310 |
#' code block. |
|
| 311 |
#' @typed data: character |
|
| 312 |
#' code block. |
|
| 313 |
#' @typed transformed_data: character |
|
| 314 |
#' code block. |
|
| 315 |
#' @typed parameters: character |
|
| 316 |
#' code block. |
|
| 317 |
#' @typed transformed_parameters: character |
|
| 318 |
#' code block. |
|
| 319 |
#' @typed model: character |
|
| 320 |
#' code block. |
|
| 321 |
#' @typed generated_quantities: character |
|
| 322 |
#' code block. |
|
| 323 |
#' @typed stan_blocks: list |
|
| 324 |
#' reference list of stan blocks. |
|
| 325 |
#' |
|
| 326 |
#' @return Character vector of the complete Stan code. |
|
| 327 |
#' |
|
| 328 |
#' @keywords internal |
|
| 329 |
as_stan_file <- function( |
|
| 330 |
functions = "", |
|
| 331 |
data = "", |
|
| 332 |
transformed_data = "", |
|
| 333 |
parameters = "", |
|
| 334 |
transformed_parameters = "", |
|
| 335 |
model = "", |
|
| 336 |
generated_quantities = "", |
|
| 337 |
stan_blocks = STAN_BLOCKS |
|
| 338 |
) {
|
|
| 339 | 4635x |
block_strings <- lapply( |
| 340 | 4635x |
names(stan_blocks), |
| 341 | 4635x |
function(id) {
|
| 342 | 32445x |
char <- get(id) |
| 343 | 32445x |
if (any(nchar(char) >= 1)) {
|
| 344 | 14937x |
return(sprintf( |
| 345 | 14937x |
"%s {\n%s\n}\n\n",
|
| 346 | 14937x |
stan_blocks[[id]], |
| 347 | 14937x |
paste(char, collapse = "\n") |
| 348 |
)) |
|
| 349 |
} else {
|
|
| 350 | 17508x |
return("")
|
| 351 |
} |
|
| 352 |
} |
|
| 353 |
) |
|
| 354 | 4635x |
return(paste0(block_strings, collapse = "")) |
| 355 |
} |
|
| 356 | ||
| 357 |
# as_stan_fragments ---- |
|
| 358 | ||
| 359 |
#' Conversion of Character Vector into Stan Code Block List |
|
| 360 |
#' |
|
| 361 |
#' @typed x: character |
|
| 362 |
#' the single Stan code vector. |
|
| 363 |
#' @typed stan_blocks: list |
|
| 364 |
#' reference list of stan blocks. |
|
| 365 |
#' |
|
| 366 |
#' @return A list with the Stan code blocks. |
|
| 367 |
#' |
|
| 368 |
#' @details |
|
| 369 |
#' Function only works if code is in format |
|
| 370 |
#' ``` |
|
| 371 |
#' data {
|
|
| 372 |
#' <code> |
|
| 373 |
#' } |
|
| 374 |
#' model {
|
|
| 375 |
#' <code> |
|
| 376 |
#' } |
|
| 377 |
#' ``` |
|
| 378 |
#' That is to say we do not support code in inline format i.e. |
|
| 379 |
#' ``` |
|
| 380 |
#' data { <code> }
|
|
| 381 |
#' model { <code> }
|
|
| 382 |
#' ``` |
|
| 383 |
#' |
|
| 384 |
#' @keywords internal |
|
| 385 |
as_stan_fragments <- function(x, stan_blocks = STAN_BLOCKS) {
|
|
| 386 | 9296x |
code <- unlist(stringr::str_split(x, "\n")) |
| 387 | ||
| 388 | 9296x |
errmsg <- paste( |
| 389 | 9296x |
"There were problems parsing the `%s` block.", |
| 390 | 9296x |
"Please consult the `Formatting Stan Files` section of the", |
| 391 | 9296x |
"`Extending jmpost` vignette" |
| 392 |
) |
|
| 393 | ||
| 394 |
# Check to see if any block openings exist that have code on the same line |
|
| 395 |
# e.g. `data { int i;}`. This is unsupported so we throw an error
|
|
| 396 | 9296x |
for (block in stan_blocks) {
|
| 397 | 65067x |
regex <- sprintf("^\\s*%s\\s*\\{\\s*[^\\s-]+", block)
|
| 398 | 65067x |
if (any(grepl(regex, code, perl = TRUE))) {
|
| 399 | 2x |
stop(sprintf(errmsg, block)) |
| 400 |
} |
|
| 401 |
} |
|
| 402 | ||
| 403 |
# We first look to identify the opening of a block e.g. `data {`
|
|
| 404 |
# We then regard all lines that follow as belonging to that block |
|
| 405 |
# until we see another block being opened e.g. `model{`
|
|
| 406 | 9294x |
results <- list() |
| 407 | 9294x |
target <- NULL |
| 408 | 9294x |
for (line in code) {
|
| 409 | 464399x |
for (block in names(stan_blocks)) {
|
| 410 | 3171541x |
regex <- sprintf("^\\s*%s\\s*\\{\\s*$", stan_blocks[[block]])
|
| 411 | 3171541x |
if (stringr::str_detect(line, regex)) {
|
| 412 | 27707x |
target <- block |
| 413 | 27707x |
line <- NULL |
| 414 | 27707x |
break |
| 415 |
} |
|
| 416 |
} |
|
| 417 | 464399x |
if (!is.null(target)) {
|
| 418 |
# This is memory inefficient but given the relatively small size of |
|
| 419 |
# stan files its regarded as a acceptable simplification to ease the |
|
| 420 |
# code burden |
|
| 421 | 462435x |
results[[target]] <- c(results[[target]], line) |
| 422 |
} |
|
| 423 |
} |
|
| 424 | ||
| 425 |
# Loop over each block to remove trailing "}". |
|
| 426 | 9294x |
for (block in names(results)) {
|
| 427 | 27706x |
block_length <- length(results[[block]]) |
| 428 |
# The following processing is only required if the block actually has content |
|
| 429 | 27706x |
if (block_length == 1 && results[[block]] == "") {
|
| 430 | ! |
next |
| 431 |
} |
|
| 432 | 27706x |
has_removed_char <- FALSE |
| 433 |
# Walk backwards to find the closing `}` that corresponds to the `<block> {`
|
|
| 434 | 27706x |
for (index in rev(seq_len(block_length))) {
|
| 435 | 51699x |
line <- results[[block]][[index]] |
| 436 |
# This code will exit the for loop as soon as it hits the closing `}` |
|
| 437 |
# thus if we ever see a line that ends in text/numbers it means |
|
| 438 |
# somethings gone wrong |
|
| 439 | 51699x |
if (stringr::str_detect(line, "[\\w\\d]+\\s*$")) {
|
| 440 | 1x |
stop(sprintf(errmsg, block)) |
| 441 |
} |
|
| 442 | 51698x |
if (stringr::str_detect(line, "\\}\\s*$")) {
|
| 443 | 27705x |
new_line <- stringr::str_replace(line, "\\s*\\}\\s*$", "") |
| 444 |
# If the line is now blank after removing the closing `}` then drop the line |
|
| 445 | 27705x |
keep_offset <- if (nchar(new_line) == 0) -1 else 0 |
| 446 |
# Only keep lines from the start of the block to the closing `}` |
|
| 447 |
# this is to ensure we drop blank lines that were between the end |
|
| 448 |
# of the block and the start of the next |
|
| 449 | 27705x |
keep_range <- seq_len(index + keep_offset) |
| 450 | 27705x |
results[[block]][[index]] <- new_line |
| 451 | 27705x |
results[[block]] <- results[[block]][keep_range] |
| 452 | 27705x |
has_removed_char <- TRUE |
| 453 | 27705x |
break |
| 454 |
} |
|
| 455 |
} |
|
| 456 |
# If we haven't actually removed a closing `}` then something has gone wrong... |
|
| 457 | 27705x |
if (!has_removed_char) {
|
| 458 | ! |
stop(sprintf(errmsg, block)) |
| 459 |
} |
|
| 460 |
} |
|
| 461 | ||
| 462 |
# Add any missing blocks back in |
|
| 463 | 9293x |
for (block in names(stan_blocks)) {
|
| 464 | 65051x |
if (is.null(results[[block]])) {
|
| 465 | 37346x |
results[[block]] <- "" |
| 466 |
} |
|
| 467 |
} |
|
| 468 | 9293x |
results |
| 469 |
} |
|
| 470 | ||
| 471 |
#' `StanModule` -> Printable `Character` |
|
| 472 |
#' |
|
| 473 |
#' Converts [`StanModule`] object into a printable string. |
|
| 474 |
#' @typed object: StanModule |
|
| 475 |
#' A stan program |
|
| 476 |
#' @family StanModule |
|
| 477 |
#' @typed indent: numeric |
|
| 478 |
#' how much white space to prefix the print string with. |
|
| 479 |
#' @keywords internal |
|
| 480 |
#' @export |
|
| 481 |
#' |
|
| 482 |
#' @returns A character vector suitable for printing. |
|
| 483 |
as_print_string.StanModule <- function(object, indent = 1, ...) {
|
|
| 484 | 4x |
slots <- names(getSlots("StanModule"))
|
| 485 | 4x |
components <- Filter( |
| 486 | 4x |
\(block) paste(slot(object, block), collapse = "") != "", |
| 487 | 4x |
slots |
| 488 |
) |
|
| 489 | 4x |
template <- c( |
| 490 | 4x |
"StanModule Object with components:", |
| 491 | 4x |
paste(" ", components)
|
| 492 |
) |
|
| 493 | 4x |
pad <- rep(" ", indent) |> paste(collapse = "")
|
| 494 | 4x |
template_padded <- paste(pad, template) |
| 495 | 4x |
sprintf( |
| 496 | 4x |
paste(template_padded, collapse = "\n") |
| 497 |
) |
|
| 498 |
} |
|
| 499 | ||
| 500 |
#' @rdname show-object |
|
| 501 |
#' @export |
|
| 502 |
setMethod( |
|
| 503 |
f = "show", |
|
| 504 |
signature = "StanModule", |
|
| 505 |
definition = function(object) {
|
|
| 506 | 4x |
string <- as_print_string(object) |
| 507 | 4x |
cat("\n", string, "\n\n")
|
| 508 |
} |
|
| 509 |
) |
| 1 |
#' @include SimLongitudinalGSF.R |
|
| 2 |
#' @include LongitudinalGSFCov.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Simulate a Generalized Stein-Fojo model with covariates |
|
| 6 |
#' |
|
| 7 |
#' @inheritParams LongitudinalGSFCov |
|
| 8 |
#' @typed times: numeric |
|
| 9 |
#' Observation times. |
|
| 10 |
#' @typed mu_b_intercept: numeric |
|
| 11 |
#' Intercept for the baseline mean predictor. |
|
| 12 |
#' @typed mu_s_intercept: numeric |
|
| 13 |
#' Intercept for the shrinkage mean predictor. |
|
| 14 |
#' @typed mu_g_intercept: numeric |
|
| 15 |
#' Intercept for the growth mean predictor. |
|
| 16 |
#' @typed mu_phi_intercept: numeric |
|
| 17 |
#' Intercept for the treatment fraction on the logit scale mean predictor. |
|
| 18 |
#' @typed mu_b_coefficients: numeric |
|
| 19 |
#' Covariate coefficients for the baseline mean predictor. |
|
| 20 |
#' @typed mu_s_coefficients: numeric |
|
| 21 |
#' Covariate coefficients for the shrinkage mean predictor. |
|
| 22 |
#' @typed mu_g_coefficients: numeric |
|
| 23 |
#' Covariate coefficients for the growth mean predictor. |
|
| 24 |
#' @typed mu_phi_coefficients: numeric |
|
| 25 |
#' Covariate coefficients for the treatment fraction on the logit scale mean predictor. |
|
| 26 |
#' @typed omega_b_intercept: numeric |
|
| 27 |
#' Intercept for the baseline standard deviation predictor. |
|
| 28 |
#' @typed omega_s_intercept: numeric |
|
| 29 |
#' Intercept for the shrinkage standard deviation predictor. |
|
| 30 |
#' @typed omega_g_intercept: numeric |
|
| 31 |
#' Intercept for the growth standard deviation predictor. |
|
| 32 |
#' @typed omega_phi_intercept: numeric |
|
| 33 |
#' Intercept for the treatment fraction on the logit scale standard deviation predictor. |
|
| 34 |
#' @typed omega_b_coefficients: numeric |
|
| 35 |
#' Covariate coefficients for the baseline standard deviation predictor. |
|
| 36 |
#' @typed omega_s_coefficients: numeric |
|
| 37 |
#' Covariate coefficients for the shrinkage standard deviation predictor. |
|
| 38 |
#' @typed omega_g_coefficients: numeric |
|
| 39 |
#' Covariate coefficients for the growth standard deviation predictor. |
|
| 40 |
#' @typed omega_phi_coefficients: numeric |
|
| 41 |
#' Covariate coefficients for the treatment fraction on the logit scale standard deviation predictor. |
|
| 42 |
#' @typed sigma: numeric |
|
| 43 |
#' Observation-error standard deviation. |
|
| 44 |
#' @typed link_dsld: numeric |
|
| 45 |
#' Coefficient for the DSLD association link. |
|
| 46 |
#' @typed link_ttg: numeric |
|
| 47 |
#' Coefficient for the TTG association link. |
|
| 48 |
#' @typed link_identity: numeric |
|
| 49 |
#' Coefficient for the identity association link. |
|
| 50 |
#' @typed link_growth: numeric |
|
| 51 |
#' Coefficient for the growth association link. |
|
| 52 |
#' @typed link_shrinkage: numeric |
|
| 53 |
#' Coefficient for the shrinkage association link. |
|
| 54 |
#' @exportClass SimLongitudinalGSFCov |
|
| 55 |
.SimLongitudinalGSFCov <- setClass( |
|
| 56 |
"SimLongitudinalGSFCov", |
|
| 57 |
contains = "SimLongitudinal", |
|
| 58 |
slots = c( |
|
| 59 |
mu_b_formula = "formula", |
|
| 60 |
omega_b_formula = "formula", |
|
| 61 |
mu_s_formula = "formula", |
|
| 62 |
omega_s_formula = "formula", |
|
| 63 |
mu_g_formula = "formula", |
|
| 64 |
omega_g_formula = "formula", |
|
| 65 |
mu_phi_formula = "formula", |
|
| 66 |
omega_phi_formula = "formula", |
|
| 67 |
mu_b_parametrization = "character", |
|
| 68 |
omega_b_parametrization = "character", |
|
| 69 |
mu_s_parametrization = "character", |
|
| 70 |
omega_s_parametrization = "character", |
|
| 71 |
mu_g_parametrization = "character", |
|
| 72 |
omega_g_parametrization = "character", |
|
| 73 |
mu_phi_parametrization = "character", |
|
| 74 |
omega_phi_parametrization = "character", |
|
| 75 |
mu_b_intercept = "numeric", |
|
| 76 |
mu_b_coefficients = "numeric", |
|
| 77 |
omega_b_intercept = "numeric", |
|
| 78 |
omega_b_coefficients = "numeric", |
|
| 79 |
mu_s_intercept = "numeric", |
|
| 80 |
mu_s_coefficients = "numeric", |
|
| 81 |
omega_s_intercept = "numeric", |
|
| 82 |
omega_s_coefficients = "numeric", |
|
| 83 |
mu_g_intercept = "numeric", |
|
| 84 |
mu_g_coefficients = "numeric", |
|
| 85 |
omega_g_intercept = "numeric", |
|
| 86 |
omega_g_coefficients = "numeric", |
|
| 87 |
mu_phi_intercept = "numeric", |
|
| 88 |
mu_phi_coefficients = "numeric", |
|
| 89 |
omega_phi_intercept = "numeric", |
|
| 90 |
omega_phi_coefficients = "numeric", |
|
| 91 |
sigma = "numeric", |
|
| 92 |
link_dsld = "numeric", |
|
| 93 |
link_ttg = "numeric", |
|
| 94 |
link_identity = "numeric", |
|
| 95 |
link_growth = "numeric", |
|
| 96 |
link_shrinkage = "numeric", |
|
| 97 |
scaled_variance = "logical" |
|
| 98 |
) |
|
| 99 |
) |
|
| 100 | ||
| 101 |
#' @rdname SimLongitudinalGSFCov-class |
|
| 102 |
#' @returns A `SimLongitudinalGSFCov` object. |
|
| 103 |
#' @export |
|
| 104 |
SimLongitudinalGSFCov <- function( |
|
| 105 |
times = c(-100, -50, 0, 50, 100, 150, 250, 350, 450, 550) / 365, |
|
| 106 |
mu_b_formula = ~study, |
|
| 107 |
omega_b_formula = ~study, |
|
| 108 |
mu_s_formula = ~arm, |
|
| 109 |
omega_s_formula = ~arm, |
|
| 110 |
mu_g_formula = ~arm, |
|
| 111 |
omega_g_formula = ~arm, |
|
| 112 |
mu_phi_formula = ~arm, |
|
| 113 |
omega_phi_formula = ~arm, |
|
| 114 |
mu_b_parametrization = "linear", |
|
| 115 |
omega_b_parametrization = "log-linear", |
|
| 116 |
mu_s_parametrization = "linear", |
|
| 117 |
omega_s_parametrization = "log-linear", |
|
| 118 |
mu_g_parametrization = "linear", |
|
| 119 |
omega_g_parametrization = "log-linear", |
|
| 120 |
mu_phi_parametrization = "linear", |
|
| 121 |
omega_phi_parametrization = "log-linear", |
|
| 122 |
mu_b_intercept = log(60), |
|
| 123 |
mu_b_coefficients = numeric(), |
|
| 124 |
omega_b_intercept = log(0.2), |
|
| 125 |
omega_b_coefficients = numeric(), |
|
| 126 |
mu_s_intercept = log(0.5), |
|
| 127 |
mu_s_coefficients = numeric(), |
|
| 128 |
omega_s_intercept = log(0.2), |
|
| 129 |
omega_s_coefficients = numeric(), |
|
| 130 |
mu_g_intercept = log(0.3), |
|
| 131 |
mu_g_coefficients = numeric(), |
|
| 132 |
omega_g_intercept = log(0.2), |
|
| 133 |
omega_g_coefficients = numeric(), |
|
| 134 |
mu_phi_intercept = qlogis(0.5), |
|
| 135 |
mu_phi_coefficients = numeric(), |
|
| 136 |
omega_phi_intercept = log(0.2), |
|
| 137 |
omega_phi_coefficients = numeric(), |
|
| 138 |
sigma = 0.01, |
|
| 139 |
link_dsld = 0, |
|
| 140 |
link_ttg = 0, |
|
| 141 |
link_identity = 0, |
|
| 142 |
link_growth = 0, |
|
| 143 |
link_shrinkage = 0, |
|
| 144 |
scaled_variance = FALSE |
|
| 145 |
) {
|
|
| 146 | 6x |
names <- c( |
| 147 | 6x |
"mu_b", |
| 148 | 6x |
"omega_b", |
| 149 | 6x |
"mu_s", |
| 150 | 6x |
"omega_s", |
| 151 | 6x |
"mu_g", |
| 152 | 6x |
"omega_g", |
| 153 | 6x |
"mu_phi", |
| 154 | 6x |
"omega_phi" |
| 155 |
) |
|
| 156 | 6x |
args <- list( |
| 157 | 6x |
times = times, |
| 158 | 6x |
sigma = sigma, |
| 159 | 6x |
link_dsld = link_dsld, |
| 160 | 6x |
link_ttg = link_ttg, |
| 161 | 6x |
link_identity = link_identity, |
| 162 | 6x |
link_growth = link_growth, |
| 163 | 6x |
link_shrinkage = link_shrinkage, |
| 164 | 6x |
scaled_variance = scaled_variance |
| 165 |
) |
|
| 166 | 6x |
for (name in names) {
|
| 167 | 48x |
args[[paste0(name, "_formula")]] <- .validate_covariate_formula( |
| 168 | 48x |
get(paste0(name, "_formula")), |
| 169 | 48x |
paste0(name, "_formula") |
| 170 |
) |
|
| 171 | 48x |
args[[paste0( |
| 172 | 48x |
name, |
| 173 | 48x |
"_parametrization" |
| 174 | 48x |
)]] <- .validate_covariate_parametrization( |
| 175 | 48x |
get(paste0(name, "_parametrization")), |
| 176 | 48x |
paste0(name, "_parametrization"), |
| 177 | 48x |
scale = startsWith(name, "omega_") |
| 178 |
) |
|
| 179 | 46x |
args[[paste0(name, "_intercept")]] <- get(paste0(name, "_intercept")) |
| 180 | 46x |
args[[paste0(name, "_coefficients")]] <- get(paste0( |
| 181 | 46x |
name, |
| 182 | 46x |
"_coefficients" |
| 183 |
)) |
|
| 184 |
} |
|
| 185 | 4x |
do.call(.SimLongitudinalGSFCov, args) |
| 186 |
} |
|
| 187 | ||
| 188 |
#' @rdname sampleSubjects |
|
| 189 |
#' @export |
|
| 190 |
sampleSubjects.SimLongitudinalGSFCov <- function(object, subjects_df) {
|
|
| 191 | 1x |
assert_that( |
| 192 | 1x |
nrow(subjects_df) == length(unique(subjects_df[["subject"]])), |
| 193 | 1x |
msg = "The number of rows in `subjects_df` should equal the number of unique subjects" |
| 194 |
) |
|
| 195 | 1x |
names <- c( |
| 196 | 1x |
"mu_b", |
| 197 | 1x |
"omega_b", |
| 198 | 1x |
"mu_s", |
| 199 | 1x |
"omega_s", |
| 200 | 1x |
"mu_g", |
| 201 | 1x |
"omega_g", |
| 202 | 1x |
"mu_phi", |
| 203 | 1x |
"omega_phi" |
| 204 |
) |
|
| 205 | 1x |
predicted <- stats::setNames( |
| 206 | 1x |
lapply(names, function(name) {
|
| 207 | 8x |
design <- .covariate_design_matrix( |
| 208 | 8x |
slot(object, paste0(name, "_formula")), |
| 209 | 8x |
subjects_df, |
| 210 | 8x |
paste0(name, "_formula") |
| 211 |
) |
|
| 212 | 8x |
.covariate_predictor_r( |
| 213 | 8x |
design, |
| 214 | 8x |
slot(object, paste0(name, "_intercept")), |
| 215 | 8x |
.simulation_coefficients( |
| 216 | 8x |
slot(object, paste0(name, "_coefficients")), |
| 217 | 8x |
ncol(design), |
| 218 | 8x |
paste0(name, "_coefficients") |
| 219 |
), |
|
| 220 | 8x |
slot(object, paste0(name, "_parametrization")) |
| 221 |
) |
|
| 222 |
}), |
|
| 223 | 1x |
names |
| 224 |
) |
|
| 225 | 1x |
for (name in c("omega_b", "omega_s", "omega_g", "omega_phi")) {
|
| 226 | 4x |
assert_that( |
| 227 | 4x |
all(predicted[[name]] > 0), |
| 228 | 4x |
msg = sprintf( |
| 229 | 4x |
"The %s predictor must be positive for every subject", |
| 230 | 4x |
name |
| 231 |
) |
|
| 232 |
) |
|
| 233 |
} |
|
| 234 | 1x |
subjects_df |> |
| 235 | 1x |
dplyr::mutate( |
| 236 | 1x |
psi_b = stats::rlnorm( |
| 237 | 1x |
dplyr::n(), |
| 238 | 1x |
predicted$mu_b, |
| 239 | 1x |
predicted$omega_b |
| 240 |
), |
|
| 241 | 1x |
psi_s = stats::rlnorm( |
| 242 | 1x |
dplyr::n(), |
| 243 | 1x |
predicted$mu_s, |
| 244 | 1x |
predicted$omega_s |
| 245 |
), |
|
| 246 | 1x |
psi_g = stats::rlnorm( |
| 247 | 1x |
dplyr::n(), |
| 248 | 1x |
predicted$mu_g, |
| 249 | 1x |
predicted$omega_g |
| 250 |
), |
|
| 251 | 1x |
psi_phi = stats::plogis(stats::rnorm( |
| 252 | 1x |
dplyr::n(), |
| 253 | 1x |
predicted$mu_phi, |
| 254 | 1x |
predicted$omega_phi |
| 255 |
)) |
|
| 256 |
) |
|
| 257 |
} |
|
| 258 | ||
| 259 |
#' @rdname sampleObservations |
|
| 260 |
#' @export |
|
| 261 |
sampleObservations.SimLongitudinalGSFCov <- function(object, times_df) {
|
|
| 262 | 1x |
times_df |> |
| 263 | 1x |
dplyr::mutate( |
| 264 | 1x |
mu_sld = gsf_sld( |
| 265 | 1x |
.data$time, |
| 266 | 1x |
.data$psi_b, |
| 267 | 1x |
.data$psi_s, |
| 268 | 1x |
.data$psi_g, |
| 269 | 1x |
.data$psi_phi |
| 270 |
), |
|
| 271 | 1x |
dsld = gsf_dsld( |
| 272 | 1x |
.data$time, |
| 273 | 1x |
.data$psi_b, |
| 274 | 1x |
.data$psi_s, |
| 275 | 1x |
.data$psi_g, |
| 276 | 1x |
.data$psi_phi |
| 277 |
), |
|
| 278 | 1x |
ttg = gsf_ttg( |
| 279 | 1x |
.data$time, |
| 280 | 1x |
.data$psi_b, |
| 281 | 1x |
.data$psi_s, |
| 282 | 1x |
.data$psi_g, |
| 283 | 1x |
.data$psi_phi |
| 284 |
), |
|
| 285 | 1x |
sld_sd = ifelse( |
| 286 | 1x |
object@scaled_variance, |
| 287 | 1x |
pmax(.data$mu_sld * object@sigma, .Machine$double.eps), |
| 288 | 1x |
object@sigma |
| 289 |
), |
|
| 290 | 1x |
sld = stats::rnorm(dplyr::n(), .data$mu_sld, .data$sld_sd), |
| 291 | 1x |
log_haz_link = object@link_dsld * |
| 292 | 1x |
.data$dsld + |
| 293 | 1x |
object@link_ttg * .data$ttg + |
| 294 | 1x |
object@link_identity * .data$mu_sld + |
| 295 | 1x |
object@link_growth * log(.data$psi_g) + |
| 296 | 1x |
object@link_shrinkage * log(.data$psi_s) |
| 297 |
) |
|
| 298 |
} |
|
| 299 | ||
| 300 |
#' @rdname as_print_string |
|
| 301 |
#' @exportS3Method NULL |
|
| 302 |
as_print_string.SimLongitudinalGSFCov <- function(object, ...) {
|
|
| 303 | ! |
"SimLongitudinalGSFCov" |
| 304 |
} |
| 1 |
#' @include StanModule.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
#' @include ParameterList.R |
|
| 4 |
#' @include DataJoint.R |
|
| 5 |
#' @include DataSurvival.R |
|
| 6 |
#' @include DataLongitudinal.R |
|
| 7 |
#' @include LongitudinalModel.R |
|
| 8 |
#' @include SurvivalModel.R |
|
| 9 |
#' @include Link.R |
|
| 10 |
#' @include constants.R |
|
| 11 |
NULL |
|
| 12 | ||
| 13 | ||
| 14 |
#' Re-used documentation for `JointModel-Shared` |
|
| 15 |
#' |
|
| 16 |
#' @typed object: JointModel |
|
| 17 |
#' Joint model specification. |
|
| 18 |
#' @typed x: JointModel |
|
| 19 |
#' Joint model specification. |
|
| 20 |
#' @param ... Not Used. |
|
| 21 |
#' |
|
| 22 |
#' @name JointModel-Shared |
|
| 23 |
#' @keywords internal |
|
| 24 |
NULL |
|
| 25 | ||
| 26 |
setClassUnion("LongitudinalModel_OR_NULL", c("LongitudinalModel", "NULL"))
|
|
| 27 |
setClassUnion("SurvivalModel_OR_NULL", c("SurvivalModel", "NULL"))
|
|
| 28 | ||
| 29 |
# JointModel-class ---- |
|
| 30 | ||
| 31 |
#' Joint Model Object and Constructor Function |
|
| 32 |
#' |
|
| 33 |
#' @slot longitudinal ([`LongitudinalModel`] or `NULL`)\cr the longitudinal model. |
|
| 34 |
#' @slot survival ([`SurvivalModel`] or `NULL`)\cr the survival model. |
|
| 35 |
#' @slot link (`Link`)\cr the link. |
|
| 36 |
#' @slot parameters (`ParameterList`)\cr the parameter declaration and prior specification. |
|
| 37 |
#' |
|
| 38 |
#' @family JointModel |
|
| 39 |
#' @export JointModel |
|
| 40 |
#' @exportClass JointModel |
|
| 41 |
.JointModel <- setClass( |
|
| 42 |
Class = "JointModel", |
|
| 43 |
slots = list( |
|
| 44 |
longitudinal = "LongitudinalModel_OR_NULL", |
|
| 45 |
survival = "SurvivalModel_OR_NULL", |
|
| 46 |
link = "Link", |
|
| 47 |
parameters = "ParameterList" |
|
| 48 |
) |
|
| 49 |
) |
|
| 50 | ||
| 51 |
#' @typed longitudinal: "`LongitudinalModel` or `NULL`" |
|
| 52 |
#' the longitudinal model. |
|
| 53 |
#' @typed survival: "`SurvivalModel` or `NULL`" |
|
| 54 |
#' the survival model. |
|
| 55 |
#' @typed link: Link |
|
| 56 |
#' the link. |
|
| 57 |
#' @rdname JointModel-class |
|
| 58 |
#' |
|
| 59 |
#' @returns A `JointModel` object. |
|
| 60 |
#' |
|
| 61 |
#' @examples |
|
| 62 |
#' JointModel(LongitudinalGSF(), SurvivalWeibullPH(), linkDSLD()) |
|
| 63 |
JointModel <- function( |
|
| 64 |
longitudinal = NULL, |
|
| 65 |
survival = NULL, |
|
| 66 |
link = Link() |
|
| 67 |
) {
|
|
| 68 | 131x |
link <- resolvePromise(Link(link), longitudinal) |
| 69 | ||
| 70 | 131x |
if (length(link) > 0) {
|
| 71 | 30x |
longitudinal <- enableLink(longitudinal) |
| 72 |
} |
|
| 73 | ||
| 74 | 131x |
parameters <- Reduce( |
| 75 | 131x |
merge, |
| 76 | 131x |
list( |
| 77 | 131x |
getParameters(longitudinal), |
| 78 | 131x |
getParameters(survival), |
| 79 | 131x |
getParameters(link) |
| 80 |
) |
|
| 81 |
) |
|
| 82 | ||
| 83 | 131x |
.JointModel( |
| 84 | 131x |
longitudinal = longitudinal, |
| 85 | 131x |
survival = survival, |
| 86 | 131x |
link = link, |
| 87 | 131x |
parameters = parameters |
| 88 |
) |
|
| 89 |
} |
|
| 90 | ||
| 91 | ||
| 92 |
#' @export |
|
| 93 |
#' |
|
| 94 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 95 |
enableGQ.JointModel <- function(object, ...) {
|
|
| 96 | 51x |
merge( |
| 97 | 51x |
enableGQ(object@survival, ...), |
| 98 | 51x |
enableGQ(object@longitudinal, ...) |
| 99 |
) |
|
| 100 |
} |
|
| 101 | ||
| 102 | ||
| 103 |
#' `JointModel` -> `StanModule` |
|
| 104 |
#' |
|
| 105 |
#' Converts a [`JointModel`] object to a [`StanModule`] object |
|
| 106 |
#' |
|
| 107 |
#' @inheritParams JointModel-Shared |
|
| 108 |
#' @family JointModel |
|
| 109 |
#' @family as.StanModule |
|
| 110 |
#' @export |
|
| 111 |
#' |
|
| 112 |
#' @returns A `StanModule` object. |
|
| 113 |
as.StanModule.JointModel <- function(object, ...) {
|
|
| 114 | 143x |
base_model <- read_stan("base/base.stan")
|
| 115 | ||
| 116 | 143x |
stan_full <- decorated_render( |
| 117 | 143x |
.x = base_model, |
| 118 | 143x |
longitudinal = add_missing_stan_blocks(as.list(object@longitudinal)), |
| 119 | 143x |
survival = add_missing_stan_blocks(as.list(object@survival)), |
| 120 | 143x |
link = add_missing_stan_blocks(as.list(object@link)), |
| 121 | 143x |
priors = add_missing_stan_blocks(as.list(object@parameters)), |
| 122 | 143x |
has_os_submodel = !is.null(object@survival), |
| 123 | 143x |
has_long_submodel = !is.null(object@longitudinal) |
| 124 |
) |
|
| 125 |
# Unresolved Jinja code within the longitudinal / Survival / Link |
|
| 126 |
# models won't be resolved by the above call to `decorated_render`. |
|
| 127 |
# Instead they it will just be inserted into the template asis. Thus |
|
| 128 |
# we run `decorated_render` again to resolve any lingering Jinja code |
|
| 129 |
# Main example being models that don't have any Jinja code but still |
|
| 130 |
# use the `decorated_render` constants `machine_double_eps`. |
|
| 131 | 143x |
stan_full <- decorated_render(.x = stan_full) |
| 132 | ||
| 133 | 143x |
merge( |
| 134 | 143x |
StanModule("base/functions.stan"),
|
| 135 | 143x |
StanModule(stan_full) |
| 136 |
) |
|
| 137 |
} |
|
| 138 | ||
| 139 | ||
| 140 |
#' `JointModel` -> `character` |
|
| 141 |
#' |
|
| 142 |
#' Renders a [`JointModel`] object to a stan program |
|
| 143 |
#' |
|
| 144 |
#' @inheritParams JointModel-Shared |
|
| 145 |
#' @family JointModel |
|
| 146 |
#' @export |
|
| 147 |
#' |
|
| 148 |
#' @returns A character vector. |
|
| 149 |
as.character.JointModel <- function(x, ...) {
|
|
| 150 | 38x |
as.character(as.StanModule(x)) |
| 151 |
} |
|
| 152 | ||
| 153 | ||
| 154 |
# write_stan-JointModel ---- |
|
| 155 | ||
| 156 |
#' @rdname write_stan |
|
| 157 |
#' @export |
|
| 158 |
write_stan.JointModel <- function(object, destination, ...) {
|
|
| 159 | ! |
if (is_connection(destination)) {
|
| 160 | ! |
return(writeLines(as.character(object), con = destination)) |
| 161 |
} |
|
| 162 | ! |
fi <- file(destination, open = "w") |
| 163 | ! |
writeLines(as.character(object), con = fi) |
| 164 | ! |
close(fi) |
| 165 | ! |
invisible() |
| 166 |
} |
|
| 167 | ||
| 168 | ||
| 169 |
# compileStanModel-JointModel ---- |
|
| 170 | ||
| 171 |
#' @rdname compileStanModel |
|
| 172 |
#' @export |
|
| 173 |
compileStanModel.JointModel <- function(object) {
|
|
| 174 | 18x |
object |> |
| 175 | 18x |
as.StanModule() |> |
| 176 | 18x |
compileStanModel() |> |
| 177 | 18x |
invisible() |
| 178 |
} |
|
| 179 | ||
| 180 | ||
| 181 |
# sampleStanModel-JointModel ---- |
|
| 182 | ||
| 183 |
#' @rdname sampleStanModel |
|
| 184 |
#' |
|
| 185 |
#' @typed data: "`DataJoint` or `list`" |
|
| 186 |
#' input data. |
|
| 187 |
#' @export |
|
| 188 |
sampleStanModel.JointModel <- function(object, data, ...) {
|
|
| 189 | 18x |
assert_class(data, "DataJoint") |
| 190 | ||
| 191 | 18x |
if (!is.null(object@survival)) {
|
| 192 | 14x |
assert_that( |
| 193 | 14x |
!is.null(data@survival), |
| 194 | 14x |
msg = "`DataSurvival` can't be missing if a `SurvivalModel` has been specified" |
| 195 |
) |
|
| 196 |
} |
|
| 197 | 18x |
if (!is.null(object@longitudinal)) {
|
| 198 | 15x |
assert_that( |
| 199 | 15x |
!is.null(data@longitudinal), |
| 200 | 15x |
msg = "`DataLongitudinal` can't be missing if a `LongitudinalModel` has been specified" |
| 201 |
) |
|
| 202 |
} |
|
| 203 | ||
| 204 | 18x |
args <- list(...) |
| 205 | ||
| 206 | 18x |
args[["data"]] <- append( |
| 207 | 18x |
as_stan_list(data, object), |
| 208 | 18x |
as_stan_list(object@parameters) |
| 209 |
) |
|
| 210 | ||
| 211 | 18x |
args[["chains"]] <- if ("chains" %in% names(args)) {
|
| 212 | 18x |
args[["chains"]] |
| 213 |
} else {
|
|
| 214 |
# Magic constant from R/constants.R |
|
| 215 | ! |
CMDSTAN_DEFAULT_CHAINS |
| 216 |
} |
|
| 217 | ||
| 218 | 18x |
initial_values <- if ("init" %in% names(args)) {
|
| 219 | ! |
args[["init"]] |
| 220 |
} else {
|
|
| 221 | 18x |
initialValues(object, n_chains = args[["chains"]]) |
| 222 |
} |
|
| 223 | ||
| 224 | 18x |
args[["init"]] <- ensure_initial_values( |
| 225 | 18x |
initial_values, |
| 226 | 18x |
args[["data"]], |
| 227 | 18x |
object@parameters |
| 228 |
) |
|
| 229 | ||
| 230 | 18x |
model <- compileStanModel(object) |
| 231 | ||
| 232 | 18x |
results <- do.call( |
| 233 | 18x |
model$sample, |
| 234 | 18x |
args |
| 235 |
) |
|
| 236 | ||
| 237 | 18x |
.JointModelSamples( |
| 238 | 18x |
model = object, |
| 239 | 18x |
data = data, |
| 240 | 18x |
results = results |
| 241 |
) |
|
| 242 |
} |
|
| 243 | ||
| 244 | ||
| 245 |
#' Ensure that initial values are correctly specified |
|
| 246 |
#' |
|
| 247 |
#' @typed initial_values: list |
|
| 248 |
#' A list of lists containing the initial values |
|
| 249 |
#' must be 1 list per desired chain. All elements should have identical names |
|
| 250 |
#' @typed data: list |
|
| 251 |
#' specifies the size to expand each of our initial values to be. |
|
| 252 |
#' That is elements of size 1 in `initial_values` will be expanded to be the same |
|
| 253 |
#' size as the corresponding element in `data` by broadcasting the value. |
|
| 254 |
#' @typed parameters: ParameterList |
|
| 255 |
#' the parameters object |
|
| 256 |
#' |
|
| 257 |
#' @details |
|
| 258 |
#' This function is mostly a thin wrapper around `expand_initial_values` to |
|
| 259 |
#' enable easier unit testing. |
|
| 260 |
#' |
|
| 261 |
#' @keywords internal |
|
| 262 |
#' |
|
| 263 |
#' @returns A list of initial values, one element per chain. |
|
| 264 |
ensure_initial_values <- function(initial_values, data, parameters) {
|
|
| 265 | 20x |
if (is.function(initial_values)) {
|
| 266 | ! |
return(initial_values) |
| 267 |
} |
|
| 268 | ||
| 269 | 20x |
assert_class(data, "list") |
| 270 | 20x |
assert_class(parameters, "ParameterList") |
| 271 | 20x |
assert_class(initial_values, "list") |
| 272 | ||
| 273 | 20x |
values_sizes <- size(parameters) |
| 274 | 20x |
values_sizes_complete <- replace_with_lookup( |
| 275 | 20x |
values_sizes, |
| 276 | 20x |
data |
| 277 |
) |
|
| 278 | 20x |
lapply( |
| 279 | 20x |
initial_values, |
| 280 | 20x |
expand_initial_values, |
| 281 | 20x |
sizes = values_sizes_complete |
| 282 |
) |
|
| 283 |
} |
|
| 284 | ||
| 285 | ||
| 286 |
#' @rdname initialValues |
|
| 287 |
#' @export |
|
| 288 |
initialValues.JointModel <- function(object, n_chains, ...) {
|
|
| 289 | 25x |
initialValues(object@parameters, n_chains) |
| 290 |
} |
|
| 291 | ||
| 292 | ||
| 293 |
pad_with_white_space <- function(x, pad = 4) {
|
|
| 294 | 13x |
padding <- paste0(rep(" ", each = pad), collapse = "")
|
| 295 | 13x |
x_sep <- x |> |
| 296 | 13x |
strsplit(split = "\n") |> |
| 297 | 13x |
unlist() |
| 298 | 13x |
x_padded <- paste(padding, x_sep) |> |
| 299 | 13x |
paste(collapse = "\n") |
| 300 | 13x |
return(x_padded) |
| 301 |
} |
|
| 302 | ||
| 303 | ||
| 304 |
#' @rdname show-object |
|
| 305 |
#' @export |
|
| 306 |
setMethod( |
|
| 307 |
f = "show", |
|
| 308 |
signature = "JointModel", |
|
| 309 |
definition = function(object) {
|
|
| 310 | 5x |
survival_string <- if (is.null(object@survival)) {
|
| 311 | 1x |
"\n Not Specified\n" |
| 312 |
} else {
|
|
| 313 | 4x |
as_print_string(object@survival) |> pad_with_white_space() |
| 314 |
} |
|
| 315 | ||
| 316 | 5x |
longitudinal_string <- if (is.null(object@longitudinal)) {
|
| 317 | 1x |
"\n Not Specified\n" |
| 318 |
} else {
|
|
| 319 | 4x |
as_print_string(object@longitudinal) |> pad_with_white_space() |
| 320 |
} |
|
| 321 | ||
| 322 | 5x |
link_string <- as_print_string(object@link) |> pad_with_white_space() |
| 323 | ||
| 324 | 5x |
string <- "\nA Joint Model with:\n\n Survival:%s\n Longitudinal:%s\n Link:%s\n" |
| 325 | 5x |
cat(sprintf( |
| 326 | 5x |
string, |
| 327 | 5x |
survival_string, |
| 328 | 5x |
longitudinal_string, |
| 329 | 5x |
link_string |
| 330 |
)) |
|
| 331 |
} |
|
| 332 |
) |
|
| 333 | ||
| 334 | ||
| 335 |
#' @rdname getRandomEffectsNames |
|
| 336 |
#' @export |
|
| 337 |
getRandomEffectsNames.JointModel <- function(object, ...) {
|
|
| 338 | 2x |
if (is.null(object@longitudinal)) {
|
| 339 | ! |
return(NULL) |
| 340 |
} |
|
| 341 | 2x |
getRandomEffectsNames(object@longitudinal) |
| 342 |
} |
| 1 |
#' @include LongitudinalModel.R |
|
| 2 |
#' @include StanModule.R |
|
| 3 |
#' @include generics.R |
|
| 4 |
#' @include ParameterList.R |
|
| 5 |
#' @include Parameter.R |
|
| 6 |
#' @include Link.R |
|
| 7 |
NULL |
|
| 8 | ||
| 9 |
# LongitudinalGSF-class ---- |
|
| 10 | ||
| 11 |
#' `LongitudinalGSF` |
|
| 12 |
#' |
|
| 13 |
#' This class extends the general [`LongitudinalModel`] class for using the |
|
| 14 |
#' Generalized Stein-Fojo (GSF) model for the longitudinal outcome. |
|
| 15 |
#' |
|
| 16 |
#' @section Available Links: |
|
| 17 |
#' - [`linkDSLD()`] |
|
| 18 |
#' - [`linkTTG()`] |
|
| 19 |
#' - [`linkIdentity()`] |
|
| 20 |
#' - [`linkGrowth()`] |
|
| 21 |
#' @exportClass LongitudinalGSF |
|
| 22 |
.LongitudinalGSF <- setClass( |
|
| 23 |
Class = "LongitudinalGSF", |
|
| 24 |
contains = "LongitudinalModel" |
|
| 25 |
) |
|
| 26 | ||
| 27 |
# LongitudinalGSF-constructors ---- |
|
| 28 | ||
| 29 |
#' @rdname LongitudinalGSF-class |
|
| 30 |
#' |
|
| 31 |
#' @typed mu_bsld: Prior |
|
| 32 |
#' for the mean baseline value `mu_bsld`. |
|
| 33 |
#' @typed mu_ks: Prior |
|
| 34 |
#' for the mean shrinkage rate `mu_ks`. |
|
| 35 |
#' @typed mu_kg: Prior |
|
| 36 |
#' for the mean growth rate `mu_kg`. |
|
| 37 |
#' @typed mu_phi: Prior |
|
| 38 |
#' for the logit-scale location of the proportion of cells affected by the |
|
| 39 |
#' treatment `mu_phi`. |
|
| 40 |
#' |
|
| 41 |
#' @typed omega_bsld: Prior |
|
| 42 |
#' for the baseline value standard deviation `omega_bsld`. |
|
| 43 |
#' @typed omega_ks: Prior |
|
| 44 |
#' for the shrinkage rate standard deviation `omega_ks`. |
|
| 45 |
#' @typed omega_kg: Prior |
|
| 46 |
#' for the growth rate standard deviation `omega_kg`. |
|
| 47 |
#' @typed omega_phi: Prior |
|
| 48 |
#' for the logit-scale standard deviation of the proportion of cells affected |
|
| 49 |
#' by the treatment `omega_phi`. |
|
| 50 |
#' |
|
| 51 |
#' @typed sigma: Prior |
|
| 52 |
#' for the variance of the longitudinal values `sigma`. |
|
| 53 |
#' |
|
| 54 |
#' @typed centred: logical |
|
| 55 |
#' whether to use the centred parameterization. |
|
| 56 |
#' @typed scaled_variance: logical |
|
| 57 |
#' whether the variance should be scaled by the expected value, corresponding to a multiplicative model. |
|
| 58 |
#' As a default, the variance is not scaled by the expected value, corresponding to an additive model. |
|
| 59 |
#' (See the "Statistical Specifications" vignette for more details.) |
|
| 60 |
#' |
|
| 61 |
#' @importFrom stats qlogis |
|
| 62 |
#' @export |
|
| 63 |
#' |
|
| 64 |
#' @returns A `LongitudinalGSF` object. |
|
| 65 |
#' |
|
| 66 |
#' @examples |
|
| 67 |
#' LongitudinalGSF() |
|
| 68 |
LongitudinalGSF <- function( |
|
| 69 |
mu_bsld = prior_normal(log(60), 1), |
|
| 70 |
mu_ks = prior_normal(log(0.5), 1), |
|
| 71 |
mu_kg = prior_normal(log(0.3), 1), |
|
| 72 |
mu_phi = prior_normal(qlogis(0.5), 1), |
|
| 73 | ||
| 74 |
omega_bsld = prior_lognormal(log(0.2), 1), |
|
| 75 |
omega_ks = prior_lognormal(log(0.2), 1), |
|
| 76 |
omega_kg = prior_lognormal(log(0.2), 1), |
|
| 77 |
omega_phi = prior_lognormal(log(0.2), 1), |
|
| 78 | ||
| 79 |
sigma = prior_lognormal(log(0.1), 1), |
|
| 80 | ||
| 81 |
scaled_variance = FALSE, |
|
| 82 |
centred = FALSE |
|
| 83 |
) {
|
|
| 84 | 35x |
gsf_model <- StanModule(decorated_render( |
| 85 | 35x |
.x = read_stan("lm-gsf/model.stan"),
|
| 86 | 35x |
centred = centred, |
| 87 | 35x |
scaled_variance = scaled_variance |
| 88 |
)) |
|
| 89 | ||
| 90 |
# Apply constraints |
|
| 91 | 35x |
omega_bsld <- set_limits(omega_bsld, lower = getOption("jmpost.double_eps"))
|
| 92 | 35x |
omega_ks <- set_limits(omega_ks, lower = getOption("jmpost.double_eps"))
|
| 93 | 35x |
omega_kg <- set_limits(omega_kg, lower = getOption("jmpost.double_eps"))
|
| 94 | 35x |
omega_phi <- set_limits(omega_phi, lower = getOption("jmpost.double_eps"))
|
| 95 | 35x |
sigma <- set_limits(sigma, lower = getOption("jmpost.double_eps"))
|
| 96 | ||
| 97 | 35x |
parameters <- list( |
| 98 | 35x |
Parameter(name = "lm_gsf_mu_bsld", prior = mu_bsld, size = "n_studies"), |
| 99 | 35x |
Parameter(name = "lm_gsf_mu_ks", prior = mu_ks, size = "n_arms"), |
| 100 | 35x |
Parameter(name = "lm_gsf_mu_kg", prior = mu_kg, size = "n_arms"), |
| 101 | 35x |
Parameter(name = "lm_gsf_mu_phi", prior = mu_phi, size = "n_arms"), |
| 102 | ||
| 103 | 35x |
Parameter( |
| 104 | 35x |
name = "lm_gsf_omega_bsld", |
| 105 | 35x |
prior = omega_bsld, |
| 106 | 35x |
size = "n_studies" |
| 107 |
), |
|
| 108 | 35x |
Parameter(name = "lm_gsf_omega_ks", prior = omega_ks, size = "n_arms"), |
| 109 | 35x |
Parameter(name = "lm_gsf_omega_kg", prior = omega_kg, size = "n_arms"), |
| 110 | 35x |
Parameter( |
| 111 | 35x |
name = "lm_gsf_omega_phi", |
| 112 | 35x |
prior = omega_phi, |
| 113 | 35x |
size = "n_arms" |
| 114 |
), |
|
| 115 | ||
| 116 | 35x |
Parameter(name = "lm_gsf_sigma", prior = sigma, size = 1) |
| 117 |
) |
|
| 118 | ||
| 119 | 35x |
assert_flag(centred) |
| 120 | 35x |
parameters_extra <- if (centred) {
|
| 121 | 5x |
list( |
| 122 | 5x |
Parameter( |
| 123 | 5x |
name = "lm_gsf_psi_bsld", |
| 124 | 5x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 125 | 5x |
median(mu_bsld), |
| 126 | 5x |
median(omega_bsld) |
| 127 | 5x |
)), lower = getOption("jmpost.double_eps")),
|
| 128 | 5x |
size = "n_subjects" |
| 129 |
), |
|
| 130 | 5x |
Parameter( |
| 131 | 5x |
name = "lm_gsf_psi_ks", |
| 132 | 5x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 133 | 5x |
median(mu_ks), |
| 134 | 5x |
median(omega_ks) |
| 135 | 5x |
)), lower = getOption("jmpost.double_eps")),
|
| 136 | 5x |
size = "n_subjects" |
| 137 |
), |
|
| 138 | 5x |
Parameter( |
| 139 | 5x |
name = "lm_gsf_psi_kg", |
| 140 | 5x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 141 | 5x |
median(mu_kg), |
| 142 | 5x |
median(omega_kg) |
| 143 | 5x |
)), lower = getOption("jmpost.double_eps")),
|
| 144 | 5x |
size = "n_subjects" |
| 145 |
), |
|
| 146 | 5x |
Parameter( |
| 147 | 5x |
name = "lm_gsf_psi_phi_logit", |
| 148 | 5x |
prior = prior_init_only(prior_normal( |
| 149 | 5x |
median(mu_phi), |
| 150 | 5x |
median(omega_phi) |
| 151 |
)), |
|
| 152 | 5x |
size = "n_subjects" |
| 153 |
) |
|
| 154 |
) |
|
| 155 |
} else {
|
|
| 156 | 30x |
list( |
| 157 | 30x |
Parameter( |
| 158 | 30x |
name = "lm_gsf_eta_tilde_bsld", |
| 159 | 30x |
prior = prior_std_normal(), |
| 160 | 30x |
size = "n_subjects" |
| 161 |
), |
|
| 162 | 30x |
Parameter( |
| 163 | 30x |
name = "lm_gsf_eta_tilde_ks", |
| 164 | 30x |
prior = prior_std_normal(), |
| 165 | 30x |
size = "n_subjects" |
| 166 |
), |
|
| 167 | 30x |
Parameter( |
| 168 | 30x |
name = "lm_gsf_eta_tilde_kg", |
| 169 | 30x |
prior = prior_std_normal(), |
| 170 | 30x |
size = "n_subjects" |
| 171 |
), |
|
| 172 | 30x |
Parameter( |
| 173 | 30x |
name = "lm_gsf_eta_tilde_phi", |
| 174 | 30x |
prior = prior_std_normal(), |
| 175 | 30x |
size = "n_subjects" |
| 176 |
) |
|
| 177 |
) |
|
| 178 |
} |
|
| 179 | 35x |
parameters <- append(parameters, parameters_extra) |
| 180 | ||
| 181 | 35x |
x <- LongitudinalModel( |
| 182 | 35x |
name = "Generalized Stein-Fojo", |
| 183 | 35x |
stan = merge( |
| 184 | 35x |
gsf_model, |
| 185 | 35x |
StanModule("lm-gsf/functions.stan")
|
| 186 |
), |
|
| 187 | 35x |
parameters = do.call(ParameterList, parameters), |
| 188 | 35x |
scaled_variance = scaled_variance |
| 189 |
) |
|
| 190 | 35x |
.LongitudinalGSF(x) |
| 191 |
} |
|
| 192 | ||
| 193 | ||
| 194 |
#' @export |
|
| 195 |
#' |
|
| 196 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 197 |
enableGQ.LongitudinalGSF <- function(object, ...) {
|
|
| 198 | 7x |
StanModule("lm-gsf/quantities.stan")
|
| 199 |
} |
|
| 200 | ||
| 201 | ||
| 202 |
#' @export |
|
| 203 |
#' |
|
| 204 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 205 |
enableLink.LongitudinalGSF <- function(object, ...) {
|
|
| 206 | 11x |
object@stan <- merge( |
| 207 | 11x |
object@stan, |
| 208 | 11x |
StanModule("lm-gsf/link.stan")
|
| 209 |
) |
|
| 210 | 11x |
object |
| 211 |
} |
|
| 212 | ||
| 213 |
#' @export |
|
| 214 |
#' |
|
| 215 |
#' @returns A `LinkComponent` object. |
|
| 216 |
linkDSLD.LongitudinalGSF <- function(prior = prior_normal(0, 2), model, ...) {
|
|
| 217 | 10x |
LinkComponent( |
| 218 | 10x |
key = "link_dsld", |
| 219 | 10x |
stan = StanModule("lm-gsf/link_dsld.stan"),
|
| 220 | 10x |
prior = prior |
| 221 |
) |
|
| 222 |
} |
|
| 223 | ||
| 224 |
#' @export |
|
| 225 |
#' |
|
| 226 |
#' @returns A `LinkComponent` object. |
|
| 227 |
linkTTG.LongitudinalGSF <- function(prior = prior_normal(0, 2), model, ...) {
|
|
| 228 | 5x |
LinkComponent( |
| 229 | 5x |
key = "link_ttg", |
| 230 | 5x |
stan = StanModule("lm-gsf/link_ttg.stan"),
|
| 231 | 5x |
prior = prior |
| 232 |
) |
|
| 233 |
} |
|
| 234 | ||
| 235 |
#' @export |
|
| 236 |
#' |
|
| 237 |
#' @returns A `LinkComponent` object. |
|
| 238 |
linkIdentity.LongitudinalGSF <- function( |
|
| 239 |
prior = prior_normal(0, 2), |
|
| 240 |
model, |
|
| 241 |
... |
|
| 242 |
) {
|
|
| 243 | 5x |
LinkComponent( |
| 244 | 5x |
key = "link_identity", |
| 245 | 5x |
stan = StanModule("lm-gsf/link_identity.stan"),
|
| 246 | 5x |
prior = prior |
| 247 |
) |
|
| 248 |
} |
|
| 249 | ||
| 250 |
#' @export |
|
| 251 |
#' |
|
| 252 |
#' @returns A `LinkComponent` object. |
|
| 253 |
linkGrowth.LongitudinalGSF <- function(prior = prior_normal(0, 2), model, ...) {
|
|
| 254 | 7x |
LinkComponent( |
| 255 | 7x |
key = "link_growth", |
| 256 | 7x |
stan = StanModule("lm-gsf/link_growth.stan"),
|
| 257 | 7x |
prior = prior |
| 258 |
) |
|
| 259 |
} |
|
| 260 | ||
| 261 | ||
| 262 |
#' @export |
|
| 263 |
#' |
|
| 264 |
#' @returns A `LinkComponent` object. |
|
| 265 |
linkShrinkage.LongitudinalGSF <- function( |
|
| 266 |
prior = prior_normal(0, 2), |
|
| 267 |
model, |
|
| 268 |
... |
|
| 269 |
) {
|
|
| 270 | 5x |
LinkComponent( |
| 271 | 5x |
key = "link_shrinkage", |
| 272 | 5x |
stan = StanModule("lm-gsf/link_shrinkage.stan"),
|
| 273 | 5x |
prior = prior |
| 274 |
) |
|
| 275 |
} |
|
| 276 | ||
| 277 |
#' @rdname getPredictionNames |
|
| 278 |
#' @export |
|
| 279 |
getPredictionNames.LongitudinalGSF <- function(object, ...) {
|
|
| 280 | 3x |
c("b", "s", "g", "phi")
|
| 281 |
} |
|
| 282 | ||
| 283 |
#' @rdname getRandomEffectsNames |
|
| 284 |
#' @export |
|
| 285 |
getRandomEffectsNames.LongitudinalGSF <- function(object, ...) {
|
|
| 286 | 1x |
c( |
| 287 | 1x |
"b" = "lm_gsf_psi_bsld", |
| 288 | 1x |
"s" = "lm_gsf_psi_ks", |
| 289 | 1x |
"g" = "lm_gsf_psi_kg", |
| 290 | 1x |
"phi" = "lm_gsf_psi_phi" |
| 291 |
) |
|
| 292 |
} |
| 1 |
#' Row Numbers of Data with Missing Variables |
|
| 2 |
#' |
|
| 3 |
#' @typed df: data.frame |
|
| 4 |
#' input data. |
|
| 5 |
#' @typed formula: "`formula` or `NULL`" |
|
| 6 |
#' which variables to inspect for missingness, if `NULL` |
|
| 7 |
#' all variables are considered. |
|
| 8 |
#' |
|
| 9 |
#' @returns Numeric vector specifying which rows contain at least 1 missing observation |
|
| 10 |
#' in any of the inspected variables. |
|
| 11 |
#' |
|
| 12 |
#' @keywords internal |
|
| 13 |
get_missing_rownumbers <- function(df, formula = NULL) {
|
|
| 14 | 668x |
if (is.null(formula)) {
|
| 15 | ! |
formula <- ~. |
| 16 |
} |
|
| 17 | 668x |
mdf <- stats::model.frame(formula, data = df, na.action = stats::na.pass) |
| 18 | 668x |
which(!stats::complete.cases(mdf)) |
| 19 |
} |
|
| 20 | ||
| 21 |
#' Remove Rows with Missing Variables |
|
| 22 |
#' |
|
| 23 |
#' Removes any rows from a data set that contain missing values in the inspected |
|
| 24 |
#' variables. Allows users to specify which variables to inspect for missing values |
|
| 25 |
#' based on either a formula or a character vector of variable names. |
|
| 26 |
#' |
|
| 27 |
#' @typed data: data.frame |
|
| 28 |
#' input data. |
|
| 29 |
#' @typed formula: "`formula` or `NULL`" |
|
| 30 |
#' which variables to inspect for missingness. |
|
| 31 |
#' @typed extra_vars: character |
|
| 32 |
#' additional variables to inspect for missingness. |
|
| 33 |
#' |
|
| 34 |
#' @returns The `data` after removing observations that contain missing values in the required variables. |
|
| 35 |
#' Note that additional variables not listed in `formula` or `extra_vars` are not dropped and may |
|
| 36 |
#' still contain missing values. |
|
| 37 |
#' |
|
| 38 |
#' @keywords internal |
|
| 39 |
remove_missing_rows <- function(data, formula, extra_vars = NULL) {
|
|
| 40 | 667x |
if (!is.null(extra_vars)) {
|
| 41 | 1x |
extra_vars <- paste(extra_vars, collapse = " + ") |
| 42 | 1x |
formula_update_string <- paste0(". ~ . + ", extra_vars)
|
| 43 | 1x |
formula <- stats::update(formula, formula_update_string) |
| 44 |
} |
|
| 45 | ||
| 46 | 667x |
missing_rows <- get_missing_rownumbers(data, formula) |
| 47 | ||
| 48 | 667x |
if (length(missing_rows) == 0) {
|
| 49 | 665x |
return(data) |
| 50 |
} |
|
| 51 | 2x |
message(sprintf( |
| 52 | 2x |
"Note that %d observations were removed as one of more required variables contained missing values", |
| 53 | 2x |
length(missing_rows) |
| 54 |
)) |
|
| 55 | 2x |
data_reduced <- data[-missing_rows, ] |
| 56 | 2x |
rownames(data_reduced) <- NULL |
| 57 | 2x |
data_reduced |
| 58 |
} |
|
| 59 | ||
| 60 |
#' Replicate Single Values in a List |
|
| 61 |
#' |
|
| 62 |
#' @typed initial_values: list |
|
| 63 |
#' initial values with names. |
|
| 64 |
#' @typed sizes: list |
|
| 65 |
#' each size corresponds to an element in `initial_values`, |
|
| 66 |
#' matched by the names. An attribute `array` must be attached to each element, |
|
| 67 |
#' see [replace_with_lookup()]. |
|
| 68 |
#' |
|
| 69 |
#' @returns A named list of values, with any single values in the `initial_values` list |
|
| 70 |
#' replicated according to the corresponding values in the `sizes` list. |
|
| 71 |
#' Even when the size is 1, the value is passed as an `array` if the corresponding |
|
| 72 |
#' attribute is `TRUE` in `sizes`. |
|
| 73 |
#' |
|
| 74 |
#' @note The resulting list has the same names as the original lists. |
|
| 75 |
#' |
|
| 76 |
#' @keywords internal |
|
| 77 |
expand_initial_values <- function(initial_values, sizes) {
|
|
| 78 | 37x |
assert_that( |
| 79 | 37x |
is.list(initial_values), |
| 80 | 37x |
msg = "`initial_values` must be a list" |
| 81 |
) |
|
| 82 | 37x |
assert_that( |
| 83 | 37x |
is.list(sizes), |
| 84 | 37x |
msg = "`sizes` must be a list" |
| 85 |
) |
|
| 86 | 37x |
assert_that( |
| 87 | 37x |
all(names(sizes) %in% names(initial_values)), |
| 88 | 37x |
all(names(initial_values) %in% names(sizes)), |
| 89 | 37x |
msg = "`initial_values` and `sizes` must have identical names" |
| 90 |
) |
|
| 91 | ||
| 92 | 37x |
for (name in names(initial_values)) {
|
| 93 |
# Check for single values and replicate them according to sizes. |
|
| 94 | 249x |
if (length(initial_values[[name]]) == 1) {
|
| 95 | 246x |
initial_values[[name]] <- rep(initial_values[[name]], sizes[[name]]) |
| 96 |
} |
|
| 97 |
# Check for array handling. |
|
| 98 | 249x |
needs_array <- attr(sizes[[name]], "array") |
| 99 | 249x |
assert_that( |
| 100 | 249x |
is.flag(needs_array), |
| 101 | 249x |
msg = "each sizes element must have array flag attribute" |
| 102 |
) |
|
| 103 | 249x |
if (needs_array) {
|
| 104 | 173x |
initial_values[[name]] <- as.array(initial_values[[name]]) |
| 105 |
} |
|
| 106 |
} |
|
| 107 | ||
| 108 |
# Check that each element of initial_values has the same length as specified in sizes. |
|
| 109 | 37x |
for (name in names(initial_values)) {
|
| 110 | 249x |
assert_that( |
| 111 | 249x |
length(initial_values[[name]]) == sizes[[name]], |
| 112 | 249x |
msg = "length of element in `initial_values` does not match specified size" |
| 113 |
) |
|
| 114 |
} |
|
| 115 | ||
| 116 | 37x |
initial_values |
| 117 |
} |
|
| 118 | ||
| 119 |
#' Replace Character Size by Looked Up Numbers |
|
| 120 |
#' |
|
| 121 |
#' @typed sizes: list |
|
| 122 |
#' may include character elements that correspond to |
|
| 123 |
#' names in the data list. |
|
| 124 |
#' @typed data: list |
|
| 125 |
#' data containing numeric values. |
|
| 126 |
#' |
|
| 127 |
#' @returns A list of sizes with character elements in `sizes` |
|
| 128 |
#' replaced by their corresponding numeric values in `data`. |
|
| 129 |
#' |
|
| 130 |
#' @details An attribute `array` for each returned list element indicates |
|
| 131 |
#' whether the parameter needs to be handled |
|
| 132 |
#' as an array. This is the case when the size is larger than 1, or when |
|
| 133 |
#' the size was looked up in the `data`, because in that case it is flexible |
|
| 134 |
#' and hence is handled as an array in the Stan code. |
|
| 135 |
#' |
|
| 136 |
#' @note Each element in the final list of sizes must be a single number. |
|
| 137 |
#' |
|
| 138 |
#' @keywords internal |
|
| 139 |
replace_with_lookup <- function(sizes, data) {
|
|
| 140 | 23x |
assert_that(is.list(sizes), msg = "`sizes` must be a list") |
| 141 | 23x |
assert_that(is.list(data), msg = "`data` must be a list") |
| 142 | ||
| 143 | 23x |
for (idx in seq_along(sizes)) {
|
| 144 | 154x |
val <- sizes[[idx]] |
| 145 | 154x |
if (is.character(val)) {
|
| 146 | 108x |
assert_that( |
| 147 | 108x |
length(val) == 1, |
| 148 | 108x |
msg = "character elements of `sizes` must be strings" |
| 149 |
) |
|
| 150 | 108x |
assert_that( |
| 151 | 108x |
val %in% names(data), |
| 152 | 108x |
msg = sprintf("`%s` is not available in `data`", val)
|
| 153 |
) |
|
| 154 | 108x |
new_val <- data[[val]] |
| 155 | 108x |
assert_that( |
| 156 | 108x |
is.number(new_val), |
| 157 | 108x |
msg = "Selected values from data must be single numbers" |
| 158 |
) |
|
| 159 | 107x |
sizes[[idx]] <- structure(new_val, array = TRUE) |
| 160 |
} else {
|
|
| 161 | 46x |
assert_that( |
| 162 | 46x |
is.number(val), |
| 163 | 46x |
msg = "Existing values in sizes must be single numbers" |
| 164 |
) |
|
| 165 | 45x |
sizes[[idx]] <- structure(val, array = val > 1) |
| 166 |
} |
|
| 167 |
} |
|
| 168 | 21x |
sizes |
| 169 |
} |
|
| 170 | ||
| 171 |
#' Obtain Median and Credible Intervals from MCMC samples |
|
| 172 |
#' |
|
| 173 |
#' @typed samples: matrix |
|
| 174 |
#' with samples in rows and parameters in columns. |
|
| 175 |
#' @typed level: number |
|
| 176 |
#' credibility level to use for the credible intervals. |
|
| 177 |
#' |
|
| 178 |
#' @returns A `data.frame` with columns `median`, `lower` and `upper`. |
|
| 179 |
#' @keywords internal |
|
| 180 |
samples_median_ci <- function(samples, level = 0.95) {
|
|
| 181 | 41x |
assert_that(is.matrix(samples)) |
| 182 | 41x |
assert_that(is.number(level), level < 1, level > 0) |
| 183 | ||
| 184 | 41x |
samples_median <- apply(samples, MARGIN = 2L, FUN = stats::median) |
| 185 | 41x |
probs <- c((1 - level) / 2, (1 + level) / 2) |
| 186 | 41x |
samples_ci <- t(apply( |
| 187 | 41x |
samples, |
| 188 | 41x |
MARGIN = 2L, |
| 189 | 41x |
FUN = stats::quantile, |
| 190 | 41x |
probs = probs |
| 191 |
)) |
|
| 192 | 41x |
colnames(samples_ci) <- c("lower", "upper")
|
| 193 | 41x |
as.data.frame(cbind( |
| 194 | 41x |
median = samples_median, |
| 195 | 41x |
samples_ci |
| 196 |
)) |
|
| 197 |
} |
|
| 198 | ||
| 199 | ||
| 200 |
#' `decorated_render` |
|
| 201 |
#' |
|
| 202 |
#' Simple wrapper around [jinjar::render()] that provides some additional default |
|
| 203 |
#' variables about the system (avoids each call to jinjar having to specify them) |
|
| 204 |
#' @param ... Arguments passed onto [jinjar::render()] |
|
| 205 |
#' @returns See [jinjar::render()] |
|
| 206 |
#' @keywords internal |
|
| 207 |
decorated_render <- function(...) {
|
|
| 208 | 812x |
jinjar::render( |
| 209 |
..., |
|
| 210 | 812x |
machine_double_eps = getOption("jmpost.double_eps"),
|
| 211 | 812x |
machine_double_neg_eps = getOption("jmpost.double_neg_eps"),
|
| 212 |
) |
|
| 213 |
} |
|
| 214 | ||
| 215 | ||
| 216 |
is_windows <- function() {
|
|
| 217 | 59x |
sysname <- Sys.info()["sysname"] |
| 218 | 59x |
return(sysname == "Windows") |
| 219 |
} |
|
| 220 | ||
| 221 |
#' `validate_time_grid` |
|
| 222 |
#' |
|
| 223 |
#' Validate that the provided time grid is: |
|
| 224 |
#' - finite |
|
| 225 |
#' - numeric |
|
| 226 |
#' - non-missing |
|
| 227 |
#' - sorted |
|
| 228 |
#' - unique |
|
| 229 |
#' |
|
| 230 |
#' @typed time_grid: numeric |
|
| 231 |
#' A vector of times which quantities will be |
|
| 232 |
#' evaluated at. |
|
| 233 |
#' |
|
| 234 |
#' @keywords internal |
|
| 235 |
#' |
|
| 236 |
#' @returns Invisibly returns `time_grid`; invalid grids raise an error. |
|
| 237 |
validate_time_grid <- function(time_grid) {
|
|
| 238 | 93x |
assert_that( |
| 239 | 93x |
!any(is.na(time_grid)), |
| 240 | 93x |
is.numeric(time_grid), |
| 241 | 93x |
!is.null(time_grid), |
| 242 | 93x |
!is.unsorted(time_grid), |
| 243 | 93x |
!any(duplicated(time_grid)), |
| 244 | 93x |
all(is.finite(time_grid)), |
| 245 | 93x |
msg = "`time_grid` needs to be finite, sorted, unique valued numeric vector" |
| 246 |
) |
|
| 247 | 89x |
invisible(time_grid) |
| 248 |
} |
|
| 249 | ||
| 250 | ||
| 251 |
#' `expand_subjects` |
|
| 252 |
#' |
|
| 253 |
#' This function checks and expands a given subjects vector. |
|
| 254 |
#' The input vector must be unique and contain only values |
|
| 255 |
#' as specified by `all_subjects` |
|
| 256 |
#' |
|
| 257 |
#' @typed subjects: "`character` or `NULL`" |
|
| 258 |
#' Character vector representing the subjects. |
|
| 259 |
#' If NULL, it will be set to the value of `all_subjects`. |
|
| 260 |
#' @typed all_subjects: character |
|
| 261 |
#' Character vector representing all possible subjects. |
|
| 262 |
#' @return Returns the expanded `subjects` vector. |
|
| 263 |
#' @keywords internal |
|
| 264 |
expand_subjects <- function(subjects, all_subjects) {
|
|
| 265 | 38x |
assert_that( |
| 266 | 38x |
is.character(all_subjects), |
| 267 | 38x |
msg = "`all_subjects` must be a character vector" |
| 268 |
) |
|
| 269 | 38x |
if (is.null(subjects)) {
|
| 270 | 3x |
subjects <- unique(all_subjects) |
| 271 |
} |
|
| 272 | 38x |
assert_that( |
| 273 | 38x |
is.character(subjects), |
| 274 | 38x |
all(subjects %in% all_subjects), |
| 275 | 38x |
!any(duplicated(subjects)), |
| 276 | 38x |
msg = "`subjects` should be a unique character vector containing only values from the original df" |
| 277 |
) |
|
| 278 | 30x |
return(subjects) |
| 279 |
} |
|
| 280 | ||
| 281 | ||
| 282 |
#' Decompose subjects into Relevant Components |
|
| 283 |
#' |
|
| 284 |
#' This function takes in a character vector or list of subjects and decomposes it into a |
|
| 285 |
#' structured format. |
|
| 286 |
#' |
|
| 287 |
#' The primary use of this function is to correctly setup indexing variables for |
|
| 288 |
#' predicting survival quantities (see [SurvivalQuantities()]) |
|
| 289 |
#' |
|
| 290 |
#' @typed subjects: "`character` or `list`" |
|
| 291 |
#' subject identifiers. If `NULL` will be set to `all_subjects`. |
|
| 292 |
#' |
|
| 293 |
#' @typed all_subjects: character |
|
| 294 |
#' the set of allowable subject identifiers. |
|
| 295 |
#' Will cause an error if any value of `subjects` is not in this vector. |
|
| 296 |
#' |
|
| 297 |
#' @return A list containing three components: |
|
| 298 |
#' - `groups`: (`list`)\cr each element of the list is a character vector |
|
| 299 |
#' specifying which subjects belong to a given "group" where the "group" is the element name |
|
| 300 |
#' - `unique_values`: (`character`)\cr vector of the unique subjects within `subjects` |
|
| 301 |
#' - `indexes`: (`list`)\cr each element is a named and is a numeric index vector |
|
| 302 |
#' that maps the values of `grouped` to `unique_values` |
|
| 303 |
#' @examples |
|
| 304 |
#' \dontrun{
|
|
| 305 |
#' result <- decompose_subjects(c("A", "B"), c("A", "B", "C", "D"))
|
|
| 306 |
#' result <- decompose_subjects( |
|
| 307 |
#' list("g1" = c("A", "B"), "g2" = c("B", "C")),
|
|
| 308 |
#' c("A", "B", "C", "D")
|
|
| 309 |
#' ) |
|
| 310 |
#' } |
|
| 311 |
#' @seealso [expand_subjects()], [SurvivalQuantities()] |
|
| 312 |
#' @keywords internal |
|
| 313 |
decompose_subjects <- function(subjects, all_subjects) {
|
|
| 314 | 13x |
if (is.character(subjects) || is.null(subjects)) {
|
| 315 | 7x |
subjects <- expand_subjects(subjects, all_subjects) |
| 316 | 4x |
names(subjects) <- subjects |
| 317 | 4x |
subjects <- as.list(subjects) |
| 318 |
} |
|
| 319 | 10x |
subjects <- lapply( |
| 320 | 10x |
subjects, |
| 321 | 10x |
expand_subjects, |
| 322 | 10x |
all_subjects = all_subjects |
| 323 |
) |
|
| 324 | 8x |
assert_that( |
| 325 | 8x |
is.list(subjects), |
| 326 | 8x |
length(unique(names(subjects))) == length(subjects), |
| 327 | 8x |
all(vapply(subjects, is.character, logical(1))) |
| 328 |
) |
|
| 329 | 8x |
subjects_vec_unordered <- unique(unlist(subjects)) |
| 330 | 8x |
subjects_vec <- subjects_vec_unordered[order(subjects_vec_unordered)] |
| 331 | 8x |
subjects_lookup <- stats::setNames(seq_along(subjects_vec), subjects_vec) |
| 332 | 8x |
subjects_index <- lapply( |
| 333 | 8x |
subjects, |
| 334 | 8x |
\(x) {
|
| 335 | 22x |
z <- subjects_lookup[x] |
| 336 | 22x |
names(z) <- NULL |
| 337 | 22x |
z |
| 338 |
} |
|
| 339 |
) |
|
| 340 | 8x |
list( |
| 341 | 8x |
groups = subjects, |
| 342 | 8x |
unique_values = subjects_vec, |
| 343 | 8x |
indexes = subjects_index |
| 344 |
) |
|
| 345 |
} |
|
| 346 | ||
| 347 |
is_cmdstanr_available <- function() {
|
|
| 348 | 4x |
requireNamespace("cmdstanr", quietly = TRUE)
|
| 349 |
} |
|
| 350 | ||
| 351 | ||
| 352 |
is_connection <- function(obj) {
|
|
| 353 | ! |
inherits(obj, "connection") |
| 354 |
} |
|
| 355 | ||
| 356 |
#' Copy a Connection to a Temporary File |
|
| 357 |
#' |
|
| 358 |
#' @param connection (`connection`)\cr a connection, opened or not. If it is |
|
| 359 |
#' not open it is opened in binary mode and closed again afterwards. |
|
| 360 |
#' |
|
| 361 |
#' @returns The path to a temporary file containing the connection's bytes. |
|
| 362 |
#' @export |
|
| 363 |
connection_to_file <- function(connection) {
|
|
| 364 | 4x |
temp_file <- tempfile() |
| 365 | 4x |
out <- file(temp_file, open = "wb") |
| 366 | 4x |
on.exit(close(out), add = TRUE) |
| 367 | ||
| 368 | 4x |
if (!isOpen(connection)) {
|
| 369 | 3x |
open(connection, "rb") |
| 370 | 2x |
on.exit(close(connection), add = TRUE) |
| 371 |
} |
|
| 372 | ||
| 373 | 3x |
chunk_size <- 1e7 |
| 374 | 3x |
repeat {
|
| 375 | 6x |
chunk <- readBin(connection, what = "raw", n = chunk_size) |
| 376 | 6x |
if (length(chunk) == 0) {
|
| 377 | 3x |
break |
| 378 |
} |
|
| 379 | 3x |
writeBin(chunk, out) |
| 380 |
} |
|
| 381 | 3x |
temp_file |
| 382 |
} |
|
| 383 | ||
| 384 |
#' Read a SAS Dataset from a Zip Archive |
|
| 385 |
#' |
|
| 386 |
#' @param zip_file (`string`)\cr path to the zip archive. |
|
| 387 |
#' @param file (`string`)\cr name of the `.sas7bdat` file within the archive. |
|
| 388 |
#' |
|
| 389 |
#' @returns A [tibble::tibble()] as returned by [haven::read_sas()]. |
|
| 390 |
#' |
|
| 391 |
#' @examplesIf requireNamespace("archive", quietly = TRUE) && requireNamespace("haven", quietly = TRUE)
|
|
| 392 |
#' zip_file <- system.file("extdata", "prime_subset.zip", package = "jmpost")
|
|
| 393 |
#' adsl <- read_sas7bdat_from_zip(zip_file, "adsl_pds2019.sas7bdat") |
|
| 394 |
#' @export |
|
| 395 |
read_sas7bdat_from_zip <- function( |
|
| 396 |
zip_file, |
|
| 397 |
file |
|
| 398 |
) {
|
|
| 399 | 2x |
file_connection <- archive::archive_read( |
| 400 | 2x |
zip_file, |
| 401 | 2x |
file = file, |
| 402 | 2x |
mode = "b" |
| 403 |
) |
|
| 404 | 2x |
file_path <- connection_to_file(file_connection) |
| 405 | 1x |
on.exit(unlink(file_path), add = TRUE) |
| 406 | ||
| 407 | 1x |
haven::read_sas(file_path) |
| 408 |
} |
| 1 |
#' Re-used documentation for `Quantities` |
|
| 2 |
#' |
|
| 3 |
#' @typed x: Quantities |
|
| 4 |
#' generated quantities. |
|
| 5 |
#' @typed object: Quantities |
|
| 6 |
#' generated quantities. |
|
| 7 |
#' @typed type: character |
|
| 8 |
#' sets the `type` variable. |
|
| 9 |
#' @typed conf.level: numeric |
|
| 10 |
#' confidence level of the interval. |
|
| 11 |
#' @param ... not used. |
|
| 12 |
#' |
|
| 13 |
#' @keywords internal |
|
| 14 |
#' @name Quantities-Shared |
|
| 15 |
NULL |
|
| 16 | ||
| 17 | ||
| 18 |
#' Generated Quantities Container |
|
| 19 |
#' |
|
| 20 |
#' A simple wrapper around a `matrix` to store required metadata |
|
| 21 |
#' |
|
| 22 |
#' @typed quantities: matrix |
|
| 23 |
#' of generated quantities. |
|
| 24 |
#' @typed times: numeric |
|
| 25 |
#' labels specifying which time point the quantity was generated at. |
|
| 26 |
#' @typed groups: character |
|
| 27 |
#' labels for which group the quantity belongs to. |
|
| 28 |
#' |
|
| 29 |
#' @slot quantities (`matrix`)\cr See Arguments for details. |
|
| 30 |
#' @slot times (`numeric`)\cr See Arguments for details. |
|
| 31 |
#' @slot groups (`character`)\cr See Arguments for details. |
|
| 32 |
#' |
|
| 33 |
#' @details |
|
| 34 |
#' Each row of the matrix represents a sample and each column represents a distinct quantity. |
|
| 35 |
#' As such the number of columns in the matrix should equal the length of `times` and `groups` |
|
| 36 |
#' which provide metadata for who the quantity belongs to and at what time point it was generated at. |
|
| 37 |
#' |
|
| 38 |
#' @keywords internal |
|
| 39 |
#' @name Quantities-class |
|
| 40 |
#' @family Quantities |
|
| 41 |
.Quantities <- setClass( |
|
| 42 |
"Quantities", |
|
| 43 |
slots = list( |
|
| 44 |
"quantities" = "matrix", |
|
| 45 |
"times" = "numeric", |
|
| 46 |
"groups" = "character" |
|
| 47 |
) |
|
| 48 |
) |
|
| 49 |
#' @rdname Quantities-class |
|
| 50 |
#' |
|
| 51 |
#' @returns A `Quantities` object. |
|
| 52 |
Quantities <- function(quantities, times, groups) {
|
|
| 53 | 40x |
.Quantities( |
| 54 | 40x |
quantities = quantities, |
| 55 | 40x |
times = times, |
| 56 | 40x |
groups = groups |
| 57 |
) |
|
| 58 |
} |
|
| 59 | ||
| 60 |
setValidity( |
|
| 61 |
Class = "Quantities", |
|
| 62 |
method = function(object) {
|
|
| 63 |
if (length(object@times) != length(object@groups)) {
|
|
| 64 |
return("Length of `times` must be equal to the length of `groups`")
|
|
| 65 |
} |
|
| 66 |
if (length(object@times) != ncol(object@quantities)) {
|
|
| 67 |
return( |
|
| 68 |
"Length of `times` must be equal to the number of columns in `quantities`" |
|
| 69 |
) |
|
| 70 |
} |
|
| 71 |
return(TRUE) |
|
| 72 |
} |
|
| 73 |
) |
|
| 74 | ||
| 75 | ||
| 76 |
#' @export |
|
| 77 |
#' |
|
| 78 |
#' @returns An integer vector containing the dimensions of the `Quantities` object. |
|
| 79 |
dim.Quantities <- function(x) {
|
|
| 80 | 13x |
dim(x@quantities) |
| 81 |
} |
|
| 82 | ||
| 83 | ||
| 84 |
#' `Quantities` -> `data.frame` |
|
| 85 |
#' |
|
| 86 |
#' @inheritParams Quantities-Shared |
|
| 87 |
#' |
|
| 88 |
#' @keywords internal |
|
| 89 |
#' @family Quantities |
|
| 90 |
#' @export |
|
| 91 |
#' |
|
| 92 |
#' @returns A `data.frame` representation of the object. |
|
| 93 |
as.data.frame.Quantities <- function(x, ...) {
|
|
| 94 | 2x |
data.frame( |
| 95 | 2x |
group = rep(x@groups, each = nrow(x@quantities)), |
| 96 | 2x |
time = rep(x@times, each = nrow(x@quantities)), |
| 97 | 2x |
values = as.vector(x@quantities) |
| 98 |
) |
|
| 99 |
} |
|
| 100 | ||
| 101 | ||
| 102 |
#' summary |
|
| 103 |
#' |
|
| 104 |
#' @description |
|
| 105 |
#' This method returns a summary statistic `data.frame` of the quantities. Note that this |
|
| 106 |
#' is just an internal utility method in order to share common code between |
|
| 107 |
#' [LongitudinalQuantities] and [SurvivalQuantities] |
|
| 108 |
#' |
|
| 109 |
#' @inheritParams Quantities-Shared |
|
| 110 |
#' |
|
| 111 |
#' @returns |
|
| 112 |
#' A `data.frame` with the following variables: |
|
| 113 |
#' - `median` (`numeric`) \cr the median value of the quantity. |
|
| 114 |
#' - `lower` (`numeric`) \cr the lower CI value of the quantity. |
|
| 115 |
#' - `upper` (`numeric`) \cr the upper CI value of the quantity. |
|
| 116 |
#' - `time` (`numeric`) \cr the time point which the quantity is for. |
|
| 117 |
#' - `group` (`character`) \cr which group the quantity belongs to. |
|
| 118 |
#' - `type` (`character`) \cr what type of quantity is it. |
|
| 119 |
#' |
|
| 120 |
#' @keywords internal |
|
| 121 |
#' @family Quantities |
|
| 122 |
#' @export |
|
| 123 |
summary.Quantities <- function(object, conf.level = 0.95, ...) {
|
|
| 124 | 34x |
quantities_summarised <- samples_median_ci( |
| 125 | 34x |
object@quantities, |
| 126 | 34x |
level = conf.level |
| 127 |
) |
|
| 128 | ||
| 129 | 34x |
quantities_summarised$group <- object@groups |
| 130 | 34x |
quantities_summarised$time <- object@times |
| 131 | 34x |
quantities_summarised[, c("group", "time", "median", "lower", "upper")]
|
| 132 |
} |
|
| 133 | ||
| 134 | ||
| 135 |
#' Create Grouped Quantities |
|
| 136 |
#' |
|
| 137 |
#' This function takes a matrix of quantity samples and aggregates them by calculating the |
|
| 138 |
#' pointwise average. |
|
| 139 |
#' |
|
| 140 |
#' @typed quantities_raw: matrix |
|
| 141 |
#' of samples with 1 row per sample and 1 column per |
|
| 142 |
#' distinct quantity. |
|
| 143 |
#' |
|
| 144 |
#' @typed collapser: QuantityCollapser |
|
| 145 |
#' specifies which columns to combine together. |
|
| 146 |
#' |
|
| 147 |
#' @details |
|
| 148 |
#' This function essentially implements the group wise average by collapsing multiple columns |
|
| 149 |
#' together based on the specification provided by the `QuantityCollapser` object. |
|
| 150 |
#' The [Grid-Dev] page provides an example of what this function implements |
|
| 151 |
#' |
|
| 152 |
#' @keywords internal |
|
| 153 |
#' |
|
| 154 |
#' @returns An array of collapsed posterior quantities. |
|
| 155 |
collapse_quantities <- function(quantities_raw, collapser) {
|
|
| 156 | 40x |
assert_class(quantities_raw, "matrix") |
| 157 | 40x |
assert_class(collapser, "QuantityCollapser") |
| 158 | ||
| 159 | 40x |
quantities <- matrix( |
| 160 | 40x |
NA, |
| 161 | 40x |
nrow = nrow(quantities_raw), |
| 162 | 40x |
ncol = length(collapser) |
| 163 |
) |
|
| 164 | ||
| 165 | 40x |
for (idx in seq_along(collapser)) {
|
| 166 | 27182x |
quantities[, idx] <- quantities_raw[, |
| 167 | 27182x |
collapser@indexes[[idx]], |
| 168 | 27182x |
drop = FALSE |
| 169 |
] |> |
|
| 170 | 27182x |
rowMeans() |
| 171 |
} |
|
| 172 | ||
| 173 | 40x |
return(quantities) |
| 174 |
} |
|
| 175 | ||
| 176 |
#' Extract Survival Quantities |
|
| 177 |
#' |
|
| 178 |
#' Utility function to extract generated quantities from a [cmdstanr::CmdStanGQ] object. |
|
| 179 |
#' Multiple quantities are generated by default so this is a convenience function to extract |
|
| 180 |
#' the desired ones and return them them as a user friendly [posterior::draws_matrix] object |
|
| 181 |
#' |
|
| 182 |
#' @typed gq: CmdStanGQ |
|
| 183 |
#' a [cmdstanr::CmdStanGQ] object created by [generateQuantities()]. |
|
| 184 |
#' @typed type: character |
|
| 185 |
#' quantity to be generated. |
|
| 186 |
#' Must be one of `surv`, `haz`, `loghaz`, `cumhaz`, `lm_identity`. |
|
| 187 |
#' @keywords internal |
|
| 188 |
#' |
|
| 189 |
#' @returns A [posterior::draws_matrix] containing the requested generated quantities. |
|
| 190 |
extract_quantities <- function( |
|
| 191 |
gq, |
|
| 192 |
type = c("surv", "haz", "loghaz", "cumhaz", "lm_identity")
|
|
| 193 |
) {
|
|
| 194 | 41x |
type <- match.arg(type) |
| 195 | 41x |
assert_class(gq, "CmdStanGQ") |
| 196 | 41x |
meta <- switch( |
| 197 | 41x |
type, |
| 198 | 41x |
surv = list("log_surv_fit_at_time_grid", exp),
|
| 199 | 41x |
cumhaz = list("log_surv_fit_at_time_grid", \(x) -x),
|
| 200 | 41x |
haz = list("log_haz_fit_at_time_grid", exp),
|
| 201 | 41x |
loghaz = list("log_haz_fit_at_time_grid", identity),
|
| 202 | 41x |
lm_identity = list("y_fit_at_time_grid", identity)
|
| 203 |
) |
|
| 204 | 41x |
result <- gq$draws(meta[[1]], format = "draws_matrix") |
| 205 | 41x |
result_transformed <- meta[[2]](result) |
| 206 | 41x |
cnames <- colnames(result_transformed) |
| 207 | 41x |
colnames(result_transformed) <- gsub(meta[[1]], "quantity", cnames) |
| 208 | 41x |
result_transformed |
| 209 |
} |
|
| 210 | ||
| 211 | ||
| 212 |
#' `Quantities` -> Printable `Character` |
|
| 213 |
#' |
|
| 214 |
#' Converts [`Quantities`] object into a printable string. |
|
| 215 |
#' @inheritParams Quantities-Shared |
|
| 216 |
#' @family Quantities |
|
| 217 |
#' @keywords internal |
|
| 218 |
#' @export |
|
| 219 |
#' |
|
| 220 |
#' @returns A character vector suitable for printing. |
|
| 221 |
as_print_string.Quantities <- function(object, indent = 1, ...) {
|
|
| 222 | 1x |
template <- c( |
| 223 | 1x |
"Quantities Object:", |
| 224 | 1x |
" # of samples = %d", |
| 225 | 1x |
" # of quantities = %d" |
| 226 |
) |
|
| 227 | 1x |
pad <- rep(" ", indent) |> paste(collapse = "")
|
| 228 | 1x |
template_padded <- paste(pad, template) |
| 229 | 1x |
sprintf( |
| 230 | 1x |
paste(template_padded, collapse = "\n"), |
| 231 | 1x |
nrow(object), |
| 232 | 1x |
ncol(object) |
| 233 |
) |
|
| 234 |
} |
|
| 235 | ||
| 236 | ||
| 237 |
#' @rdname show-object |
|
| 238 |
#' @export |
|
| 239 |
setMethod( |
|
| 240 |
f = "show", |
|
| 241 |
signature = "Quantities", |
|
| 242 |
definition = function(object) {
|
|
| 243 | 1x |
string <- as_print_string(object) |
| 244 | 1x |
cat("\n", string, "\n\n")
|
| 245 |
} |
|
| 246 |
) |
| 1 |
#' @include SimLongitudinalClaretBruno.R |
|
| 2 |
#' @include LongitudinalClaretBrunoCov.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Simulate a Claret-Bruno longitudinal model with covariates |
|
| 6 |
#' |
|
| 7 |
#' @inheritParams LongitudinalClaretBrunoCov |
|
| 8 |
#' @typed times: numeric |
|
| 9 |
#' Observation times. |
|
| 10 |
#' @typed mu_b_intercept: numeric |
|
| 11 |
#' Intercept for the baseline tumour size mean predictor. |
|
| 12 |
#' @typed mu_g_intercept: numeric |
|
| 13 |
#' Intercept for the growth mean predictor. |
|
| 14 |
#' @typed mu_c_intercept: numeric |
|
| 15 |
#' Intercept for the resistance mean predictor. |
|
| 16 |
#' @typed mu_p_intercept: numeric |
|
| 17 |
#' Intercept for the growth inhibition mean predictor. |
|
| 18 |
#' @typed mu_b_coefficients: numeric |
|
| 19 |
#' Covariate coefficients for the baseline tumour size mean predictor. |
|
| 20 |
#' @typed mu_g_coefficients: numeric |
|
| 21 |
#' Covariate coefficients for the growth mean predictor. |
|
| 22 |
#' @typed mu_c_coefficients: numeric |
|
| 23 |
#' Covariate coefficients for the resistance mean predictor. |
|
| 24 |
#' @typed mu_p_coefficients: numeric |
|
| 25 |
#' Covariate coefficients for the growth inhibition mean predictor. |
|
| 26 |
#' @typed omega_b_intercept: numeric |
|
| 27 |
#' Intercept for the baseline tumour size standard deviation predictor. |
|
| 28 |
#' @typed omega_g_intercept: numeric |
|
| 29 |
#' Intercept for the growth standard deviation predictor. |
|
| 30 |
#' @typed omega_c_intercept: numeric |
|
| 31 |
#' Intercept for the resistance standard deviation predictor. |
|
| 32 |
#' @typed omega_p_intercept: numeric |
|
| 33 |
#' Intercept for the growth inhibition standard deviation predictor. |
|
| 34 |
#' @typed omega_b_coefficients: numeric |
|
| 35 |
#' Covariate coefficients for the baseline tumour size standard deviation predictor. |
|
| 36 |
#' @typed omega_g_coefficients: numeric |
|
| 37 |
#' Covariate coefficients for the growth standard deviation predictor. |
|
| 38 |
#' @typed omega_c_coefficients: numeric |
|
| 39 |
#' Covariate coefficients for the resistance standard deviation predictor. |
|
| 40 |
#' @typed omega_p_coefficients: numeric |
|
| 41 |
#' Covariate coefficients for the growth inhibition standard deviation predictor. |
|
| 42 |
#' @typed sigma: numeric |
|
| 43 |
#' Observation-error standard deviation. |
|
| 44 |
#' @typed link_dsld: numeric |
|
| 45 |
#' Coefficient for the DSLD association link. |
|
| 46 |
#' @typed link_ttg: numeric |
|
| 47 |
#' Coefficient for the TTG association link. |
|
| 48 |
#' @typed link_identity: numeric |
|
| 49 |
#' Coefficient for the identity association link. |
|
| 50 |
#' @typed link_growth: numeric |
|
| 51 |
#' Coefficient for the growth association link. |
|
| 52 |
#' @exportClass SimLongitudinalClaretBrunoCov |
|
| 53 |
.SimLongitudinalClaretBrunoCov <- setClass( |
|
| 54 |
"SimLongitudinalClaretBrunoCov", |
|
| 55 |
contains = "SimLongitudinal", |
|
| 56 |
slots = c( |
|
| 57 |
mu_b_formula = "formula", |
|
| 58 |
omega_b_formula = "formula", |
|
| 59 |
mu_g_formula = "formula", |
|
| 60 |
omega_g_formula = "formula", |
|
| 61 |
mu_c_formula = "formula", |
|
| 62 |
omega_c_formula = "formula", |
|
| 63 |
mu_p_formula = "formula", |
|
| 64 |
omega_p_formula = "formula", |
|
| 65 |
mu_b_parametrization = "character", |
|
| 66 |
omega_b_parametrization = "character", |
|
| 67 |
mu_g_parametrization = "character", |
|
| 68 |
omega_g_parametrization = "character", |
|
| 69 |
mu_c_parametrization = "character", |
|
| 70 |
omega_c_parametrization = "character", |
|
| 71 |
mu_p_parametrization = "character", |
|
| 72 |
omega_p_parametrization = "character", |
|
| 73 |
mu_b_intercept = "numeric", |
|
| 74 |
mu_b_coefficients = "numeric", |
|
| 75 |
omega_b_intercept = "numeric", |
|
| 76 |
omega_b_coefficients = "numeric", |
|
| 77 |
mu_g_intercept = "numeric", |
|
| 78 |
mu_g_coefficients = "numeric", |
|
| 79 |
omega_g_intercept = "numeric", |
|
| 80 |
omega_g_coefficients = "numeric", |
|
| 81 |
mu_c_intercept = "numeric", |
|
| 82 |
mu_c_coefficients = "numeric", |
|
| 83 |
omega_c_intercept = "numeric", |
|
| 84 |
omega_c_coefficients = "numeric", |
|
| 85 |
mu_p_intercept = "numeric", |
|
| 86 |
mu_p_coefficients = "numeric", |
|
| 87 |
omega_p_intercept = "numeric", |
|
| 88 |
omega_p_coefficients = "numeric", |
|
| 89 |
sigma = "numeric", |
|
| 90 |
link_dsld = "numeric", |
|
| 91 |
link_ttg = "numeric", |
|
| 92 |
link_identity = "numeric", |
|
| 93 |
link_growth = "numeric", |
|
| 94 |
scaled_variance = "logical" |
|
| 95 |
) |
|
| 96 |
) |
|
| 97 | ||
| 98 |
#' @rdname SimLongitudinalClaretBrunoCov-class |
|
| 99 |
#' @returns A `SimLongitudinalClaretBrunoCov` object. |
|
| 100 |
#' @export |
|
| 101 |
SimLongitudinalClaretBrunoCov <- function( |
|
| 102 |
times = c(-100, -50, 0, 50, 100, 150, 250, 350, 450, 550) / 365, |
|
| 103 |
mu_b_formula = ~study, |
|
| 104 |
omega_b_formula = ~study, |
|
| 105 |
mu_g_formula = ~arm, |
|
| 106 |
omega_g_formula = ~arm, |
|
| 107 |
mu_c_formula = ~arm, |
|
| 108 |
omega_c_formula = ~arm, |
|
| 109 |
mu_p_formula = ~arm, |
|
| 110 |
omega_p_formula = ~arm, |
|
| 111 |
mu_b_parametrization = "linear", |
|
| 112 |
omega_b_parametrization = "log-linear", |
|
| 113 |
mu_g_parametrization = "linear", |
|
| 114 |
omega_g_parametrization = "log-linear", |
|
| 115 |
mu_c_parametrization = "linear", |
|
| 116 |
omega_c_parametrization = "log-linear", |
|
| 117 |
mu_p_parametrization = "linear", |
|
| 118 |
omega_p_parametrization = "log-linear", |
|
| 119 |
mu_b_intercept = log(60), |
|
| 120 |
mu_b_coefficients = numeric(), |
|
| 121 |
omega_b_intercept = log(0.2), |
|
| 122 |
omega_b_coefficients = numeric(), |
|
| 123 |
mu_g_intercept = log(1), |
|
| 124 |
mu_g_coefficients = numeric(), |
|
| 125 |
omega_g_intercept = log(0.2), |
|
| 126 |
omega_g_coefficients = numeric(), |
|
| 127 |
mu_c_intercept = log(0.4), |
|
| 128 |
mu_c_coefficients = numeric(), |
|
| 129 |
omega_c_intercept = log(0.2), |
|
| 130 |
omega_c_coefficients = numeric(), |
|
| 131 |
mu_p_intercept = log(2), |
|
| 132 |
mu_p_coefficients = numeric(), |
|
| 133 |
omega_p_intercept = log(0.2), |
|
| 134 |
omega_p_coefficients = numeric(), |
|
| 135 |
sigma = 0.01, |
|
| 136 |
link_dsld = 0, |
|
| 137 |
link_ttg = 0, |
|
| 138 |
link_identity = 0, |
|
| 139 |
link_growth = 0, |
|
| 140 |
scaled_variance = FALSE |
|
| 141 |
) {
|
|
| 142 | 6x |
names <- c( |
| 143 | 6x |
"mu_b", |
| 144 | 6x |
"omega_b", |
| 145 | 6x |
"mu_g", |
| 146 | 6x |
"omega_g", |
| 147 | 6x |
"mu_c", |
| 148 | 6x |
"omega_c", |
| 149 | 6x |
"mu_p", |
| 150 | 6x |
"omega_p" |
| 151 |
) |
|
| 152 | 6x |
args <- list( |
| 153 | 6x |
times = times, |
| 154 | 6x |
sigma = sigma, |
| 155 | 6x |
link_dsld = link_dsld, |
| 156 | 6x |
link_ttg = link_ttg, |
| 157 | 6x |
link_identity = link_identity, |
| 158 | 6x |
link_growth = link_growth, |
| 159 | 6x |
scaled_variance = scaled_variance |
| 160 |
) |
|
| 161 | 6x |
for (name in names) {
|
| 162 | 48x |
args[[paste0(name, "_formula")]] <- .validate_covariate_formula( |
| 163 | 48x |
get(paste0(name, "_formula")), |
| 164 | 48x |
paste0(name, "_formula") |
| 165 |
) |
|
| 166 | 48x |
args[[paste0( |
| 167 | 48x |
name, |
| 168 | 48x |
"_parametrization" |
| 169 | 48x |
)]] <- .validate_covariate_parametrization( |
| 170 | 48x |
get(paste0(name, "_parametrization")), |
| 171 | 48x |
paste0(name, "_parametrization"), |
| 172 | 48x |
scale = startsWith(name, "omega_") |
| 173 |
) |
|
| 174 | 46x |
args[[paste0(name, "_intercept")]] <- get(paste0(name, "_intercept")) |
| 175 | 46x |
args[[paste0(name, "_coefficients")]] <- get(paste0( |
| 176 | 46x |
name, |
| 177 | 46x |
"_coefficients" |
| 178 |
)) |
|
| 179 |
} |
|
| 180 | 4x |
do.call(.SimLongitudinalClaretBrunoCov, args) |
| 181 |
} |
|
| 182 | ||
| 183 |
#' @rdname sampleSubjects |
|
| 184 |
#' @export |
|
| 185 |
sampleSubjects.SimLongitudinalClaretBrunoCov <- function(object, subjects_df) {
|
|
| 186 | 1x |
assert_that( |
| 187 | 1x |
nrow(subjects_df) == length(unique(subjects_df[["subject"]])), |
| 188 | 1x |
msg = "The number of rows in `subjects_df` should equal the number of unique subjects" |
| 189 |
) |
|
| 190 | 1x |
names <- c( |
| 191 | 1x |
"mu_b", |
| 192 | 1x |
"omega_b", |
| 193 | 1x |
"mu_g", |
| 194 | 1x |
"omega_g", |
| 195 | 1x |
"mu_c", |
| 196 | 1x |
"omega_c", |
| 197 | 1x |
"mu_p", |
| 198 | 1x |
"omega_p" |
| 199 |
) |
|
| 200 | 1x |
predicted <- stats::setNames( |
| 201 | 1x |
lapply(names, function(name) {
|
| 202 | 8x |
design <- .covariate_design_matrix( |
| 203 | 8x |
slot(object, paste0(name, "_formula")), |
| 204 | 8x |
subjects_df, |
| 205 | 8x |
paste0(name, "_formula") |
| 206 |
) |
|
| 207 | 8x |
.covariate_predictor_r( |
| 208 | 8x |
design, |
| 209 | 8x |
slot(object, paste0(name, "_intercept")), |
| 210 | 8x |
.simulation_coefficients( |
| 211 | 8x |
slot(object, paste0(name, "_coefficients")), |
| 212 | 8x |
ncol(design), |
| 213 | 8x |
paste0(name, "_coefficients") |
| 214 |
), |
|
| 215 | 8x |
slot(object, paste0(name, "_parametrization")) |
| 216 |
) |
|
| 217 |
}), |
|
| 218 | 1x |
names |
| 219 |
) |
|
| 220 | 1x |
for (name in c("omega_b", "omega_g", "omega_c", "omega_p")) {
|
| 221 | 4x |
assert_that( |
| 222 | 4x |
all(predicted[[name]] > 0), |
| 223 | 4x |
msg = sprintf( |
| 224 | 4x |
"The %s predictor must be positive for every subject", |
| 225 | 4x |
name |
| 226 |
) |
|
| 227 |
) |
|
| 228 |
} |
|
| 229 | 1x |
subjects_df |> |
| 230 | 1x |
dplyr::mutate( |
| 231 | 1x |
psi_b = stats::rlnorm( |
| 232 | 1x |
dplyr::n(), |
| 233 | 1x |
predicted$mu_b, |
| 234 | 1x |
predicted$omega_b |
| 235 |
), |
|
| 236 | 1x |
psi_g = stats::rlnorm( |
| 237 | 1x |
dplyr::n(), |
| 238 | 1x |
predicted$mu_g, |
| 239 | 1x |
predicted$omega_g |
| 240 |
), |
|
| 241 | 1x |
psi_c = stats::rlnorm( |
| 242 | 1x |
dplyr::n(), |
| 243 | 1x |
predicted$mu_c, |
| 244 | 1x |
predicted$omega_c |
| 245 |
), |
|
| 246 | 1x |
psi_p = stats::rlnorm(dplyr::n(), predicted$mu_p, predicted$omega_p) |
| 247 |
) |
|
| 248 |
} |
|
| 249 | ||
| 250 |
#' @rdname sampleObservations |
|
| 251 |
#' @export |
|
| 252 |
sampleObservations.SimLongitudinalClaretBrunoCov <- function(object, times_df) {
|
|
| 253 | 1x |
times_df |> |
| 254 | 1x |
dplyr::mutate( |
| 255 | 1x |
mu_sld = clbr_sld( |
| 256 | 1x |
.data$time, |
| 257 | 1x |
.data$psi_b, |
| 258 | 1x |
.data$psi_g, |
| 259 | 1x |
.data$psi_c, |
| 260 | 1x |
.data$psi_p |
| 261 |
), |
|
| 262 | 1x |
dsld = clbr_dsld( |
| 263 | 1x |
.data$time, |
| 264 | 1x |
.data$psi_b, |
| 265 | 1x |
.data$psi_g, |
| 266 | 1x |
.data$psi_c, |
| 267 | 1x |
.data$psi_p |
| 268 |
), |
|
| 269 | 1x |
ttg = clbr_ttg( |
| 270 | 1x |
.data$time, |
| 271 | 1x |
.data$psi_b, |
| 272 | 1x |
.data$psi_g, |
| 273 | 1x |
.data$psi_c, |
| 274 | 1x |
.data$psi_p |
| 275 |
), |
|
| 276 | 1x |
sld_sd = ifelse( |
| 277 | 1x |
object@scaled_variance, |
| 278 | 1x |
pmax(.data$mu_sld * object@sigma, .Machine$double.eps), |
| 279 | 1x |
object@sigma |
| 280 |
), |
|
| 281 | 1x |
sld = stats::rnorm(dplyr::n(), .data$mu_sld, .data$sld_sd), |
| 282 | 1x |
log_haz_link = object@link_dsld * |
| 283 | 1x |
.data$dsld + |
| 284 | 1x |
object@link_ttg * .data$ttg + |
| 285 | 1x |
object@link_identity * .data$mu_sld + |
| 286 | 1x |
object@link_growth * log(.data$psi_g) |
| 287 |
) |
|
| 288 |
} |
|
| 289 | ||
| 290 |
#' @rdname as_print_string |
|
| 291 |
#' @exportS3Method NULL |
|
| 292 |
as_print_string.SimLongitudinalClaretBrunoCov <- function(object, ...) {
|
|
| 293 | ! |
"SimLongitudinalClaretBrunoCov" |
| 294 |
} |
| 1 |
#' @include LongitudinalRandomSlopeCov.R |
|
| 2 |
#' @include LongitudinalClaretBruno.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Claret-Bruno longitudinal model with subject-level covariates |
|
| 6 |
#' |
|
| 7 |
#' Separate covariate predictors model the log-normal distribution parameters |
|
| 8 |
#' for baseline tumour size, growth, resistance, and growth inhibition. |
|
| 9 |
#' |
|
| 10 |
#' @section Available Links: |
|
| 11 |
#' - [`linkDSLD()`] |
|
| 12 |
#' - [`linkTTG()`] |
|
| 13 |
#' - [`linkIdentity()`] |
|
| 14 |
#' - [`linkGrowth()`] |
|
| 15 |
#' @exportClass LongitudinalClaretBrunoCov |
|
| 16 |
.LongitudinalClaretBrunoCov <- setClass( |
|
| 17 |
"LongitudinalClaretBrunoCov", |
|
| 18 |
contains = "LongitudinalModel", |
|
| 19 |
slots = c( |
|
| 20 |
mu_b_formula = "formula", |
|
| 21 |
omega_b_formula = "formula", |
|
| 22 |
mu_g_formula = "formula", |
|
| 23 |
omega_g_formula = "formula", |
|
| 24 |
mu_c_formula = "formula", |
|
| 25 |
omega_c_formula = "formula", |
|
| 26 |
mu_p_formula = "formula", |
|
| 27 |
omega_p_formula = "formula", |
|
| 28 |
mu_b_parametrization = "character", |
|
| 29 |
omega_b_parametrization = "character", |
|
| 30 |
mu_g_parametrization = "character", |
|
| 31 |
omega_g_parametrization = "character", |
|
| 32 |
mu_c_parametrization = "character", |
|
| 33 |
omega_c_parametrization = "character", |
|
| 34 |
mu_p_parametrization = "character", |
|
| 35 |
omega_p_parametrization = "character", |
|
| 36 |
centred_baseline = "logical", |
|
| 37 |
centred_growth = "logical", |
|
| 38 |
centred_resistance = "logical", |
|
| 39 |
centred_inhibition = "logical" |
|
| 40 |
) |
|
| 41 |
) |
|
| 42 | ||
| 43 |
#' Construct a Claret-Bruno model with subject-level covariates |
|
| 44 |
#' |
|
| 45 |
#' Formula intercept columns are removed because every predictor has a separate |
|
| 46 |
#' intercept. Predictor parametrizations have the same meaning as in |
|
| 47 |
#' [LongitudinalSteinFojoCov()]. |
|
| 48 |
#' |
|
| 49 |
#' @rdname LongitudinalClaretBrunoCov-class |
|
| 50 |
#' @typed mu_b_formula: formula |
|
| 51 |
#' One-sided formula for subject-level covariate effects on the baseline tumour size mean. |
|
| 52 |
#' @typed mu_g_formula: formula |
|
| 53 |
#' One-sided formula for subject-level covariate effects on the growth mean. |
|
| 54 |
#' @typed mu_c_formula: formula |
|
| 55 |
#' One-sided formula for subject-level covariate effects on the resistance mean. |
|
| 56 |
#' @typed mu_p_formula: formula |
|
| 57 |
#' One-sided formula for subject-level covariate effects on the growth inhibition mean. |
|
| 58 |
#' @typed omega_b_formula: formula |
|
| 59 |
#' One-sided formula for subject-level covariate effects on the baseline tumour size standard deviation. |
|
| 60 |
#' @typed omega_g_formula: formula |
|
| 61 |
#' One-sided formula for subject-level covariate effects on the growth standard deviation. |
|
| 62 |
#' @typed omega_c_formula: formula |
|
| 63 |
#' One-sided formula for subject-level covariate effects on the resistance standard deviation. |
|
| 64 |
#' @typed omega_p_formula: formula |
|
| 65 |
#' One-sided formula for subject-level covariate effects on the growth inhibition standard deviation. |
|
| 66 |
#' @typed mu_b_parametrization: character |
|
| 67 |
#' Parametrization of the baseline tumour size mean predictor. |
|
| 68 |
#' @typed mu_g_parametrization: character |
|
| 69 |
#' Parametrization of the growth mean predictor. |
|
| 70 |
#' @typed mu_c_parametrization: character |
|
| 71 |
#' Parametrization of the resistance mean predictor. |
|
| 72 |
#' @typed mu_p_parametrization: character |
|
| 73 |
#' Parametrization of the growth inhibition mean predictor. |
|
| 74 |
#' @typed omega_b_parametrization: character |
|
| 75 |
#' Parametrization of the baseline tumour size standard deviation predictor. |
|
| 76 |
#' Only `"exponential"` and `"log-linear"` are supported. |
|
| 77 |
#' @typed omega_g_parametrization: character |
|
| 78 |
#' Parametrization of the growth standard deviation predictor. Only `"exponential"` and `"log-linear"` are supported. |
|
| 79 |
#' @typed omega_c_parametrization: character |
|
| 80 |
#' Parametrization of the resistance standard deviation predictor. Only |
|
| 81 |
#' `"exponential"` and `"log-linear"` are supported. |
|
| 82 |
#' @typed omega_p_parametrization: character |
|
| 83 |
#' Parametrization of the growth inhibition standard deviation predictor. |
|
| 84 |
#' Only `"exponential"` and `"log-linear"` are supported. |
|
| 85 |
#' @typed mu_b_intercept_prior: Prior |
|
| 86 |
#' Prior for the intercept of the baseline tumour size mean predictor. |
|
| 87 |
#' @typed mu_g_intercept_prior: Prior |
|
| 88 |
#' Prior for the intercept of the growth mean predictor. |
|
| 89 |
#' @typed mu_c_intercept_prior: Prior |
|
| 90 |
#' Prior for the intercept of the resistance mean predictor. |
|
| 91 |
#' @typed mu_p_intercept_prior: Prior |
|
| 92 |
#' Prior for the intercept of the growth inhibition mean predictor. |
|
| 93 |
#' @typed mu_b_coefficients_prior: Prior |
|
| 94 |
#' Prior for the covariate coefficients of the baseline tumour size mean predictor. |
|
| 95 |
#' @typed mu_g_coefficients_prior: Prior |
|
| 96 |
#' Prior for the covariate coefficients of the growth mean predictor. |
|
| 97 |
#' @typed mu_c_coefficients_prior: Prior |
|
| 98 |
#' Prior for the covariate coefficients of the resistance mean predictor. |
|
| 99 |
#' @typed mu_p_coefficients_prior: Prior |
|
| 100 |
#' Prior for the covariate coefficients of the growth inhibition mean predictor. |
|
| 101 |
#' @typed omega_b_intercept_prior: Prior |
|
| 102 |
#' Prior for the intercept of the baseline tumour size standard deviation predictor. |
|
| 103 |
#' @typed omega_g_intercept_prior: Prior |
|
| 104 |
#' Prior for the intercept of the growth standard deviation predictor. |
|
| 105 |
#' @typed omega_c_intercept_prior: Prior |
|
| 106 |
#' Prior for the intercept of the resistance standard deviation predictor. |
|
| 107 |
#' @typed omega_p_intercept_prior: Prior |
|
| 108 |
#' Prior for the intercept of the growth inhibition standard deviation predictor. |
|
| 109 |
#' @typed omega_b_coefficients_prior: Prior |
|
| 110 |
#' Prior for the covariate coefficients of the baseline tumour size standard deviation predictor. |
|
| 111 |
#' @typed omega_g_coefficients_prior: Prior |
|
| 112 |
#' Prior for the covariate coefficients of the growth standard deviation predictor. |
|
| 113 |
#' @typed omega_c_coefficients_prior: Prior |
|
| 114 |
#' Prior for the covariate coefficients of the resistance standard deviation predictor. |
|
| 115 |
#' @typed omega_p_coefficients_prior: Prior |
|
| 116 |
#' Prior for the covariate coefficients of the growth inhibition standard deviation predictor. |
|
| 117 |
#' @typed sigma: Prior |
|
| 118 |
#' Observation-error standard deviation prior. |
|
| 119 |
#' @typed scaled_variance: logical |
|
| 120 |
#' Whether to use multiplicative observation error. |
|
| 121 |
#' @typed centred_baseline: logical |
|
| 122 |
#' Whether to use centred parameterizations. |
|
| 123 |
#' @typed centred_growth: logical |
|
| 124 |
#' Whether to use centred parameterizations. |
|
| 125 |
#' @typed centred_resistance: logical |
|
| 126 |
#' Whether to use centred parameterizations. |
|
| 127 |
#' @typed centred_inhibition: logical |
|
| 128 |
#' Whether to use centred parameterizations. |
|
| 129 |
#' @returns A `LongitudinalClaretBrunoCov` object. |
|
| 130 |
#' @export |
|
| 131 |
LongitudinalClaretBrunoCov <- function( |
|
| 132 |
mu_b_formula = ~study, |
|
| 133 |
omega_b_formula = ~study, |
|
| 134 |
mu_g_formula = ~arm, |
|
| 135 |
omega_g_formula = ~arm, |
|
| 136 |
mu_c_formula = ~arm, |
|
| 137 |
omega_c_formula = ~arm, |
|
| 138 |
mu_p_formula = ~arm, |
|
| 139 |
omega_p_formula = ~arm, |
|
| 140 |
mu_b_parametrization = "linear", |
|
| 141 |
omega_b_parametrization = "log-linear", |
|
| 142 |
mu_g_parametrization = "linear", |
|
| 143 |
omega_g_parametrization = "log-linear", |
|
| 144 |
mu_c_parametrization = "linear", |
|
| 145 |
omega_c_parametrization = "log-linear", |
|
| 146 |
mu_p_parametrization = "linear", |
|
| 147 |
omega_p_parametrization = "log-linear", |
|
| 148 |
mu_b_intercept_prior = prior_normal(log(60), 1), |
|
| 149 |
mu_b_coefficients_prior = prior_normal(0, 1), |
|
| 150 |
omega_b_intercept_prior = prior_normal(log(0.2), 1), |
|
| 151 |
omega_b_coefficients_prior = prior_normal(0, 1), |
|
| 152 |
mu_g_intercept_prior = prior_normal(log(1), 1), |
|
| 153 |
mu_g_coefficients_prior = prior_normal(0, 1), |
|
| 154 |
omega_g_intercept_prior = prior_normal(log(0.2), 1), |
|
| 155 |
omega_g_coefficients_prior = prior_normal(0, 1), |
|
| 156 |
mu_c_intercept_prior = prior_normal(log(0.4), 1), |
|
| 157 |
mu_c_coefficients_prior = prior_normal(0, 1), |
|
| 158 |
omega_c_intercept_prior = prior_normal(log(0.2), 1), |
|
| 159 |
omega_c_coefficients_prior = prior_normal(0, 1), |
|
| 160 |
mu_p_intercept_prior = prior_normal(log(2), 1), |
|
| 161 |
mu_p_coefficients_prior = prior_normal(0, 1), |
|
| 162 |
omega_p_intercept_prior = prior_normal(log(0.2), 1), |
|
| 163 |
omega_p_coefficients_prior = prior_normal(0, 1), |
|
| 164 |
sigma = prior_lognormal(log(0.1), 1), |
|
| 165 |
scaled_variance = FALSE, |
|
| 166 |
centred_baseline = TRUE, |
|
| 167 |
centred_growth = FALSE, |
|
| 168 |
centred_resistance = FALSE, |
|
| 169 |
centred_inhibition = FALSE |
|
| 170 |
) {
|
|
| 171 | 11x |
lapply( |
| 172 | 11x |
c( |
| 173 | 11x |
centred_baseline, |
| 174 | 11x |
centred_growth, |
| 175 | 11x |
centred_resistance, |
| 176 | 11x |
centred_inhibition |
| 177 |
), |
|
| 178 | 11x |
assert_flag |
| 179 |
) |
|
| 180 | 11x |
names <- c( |
| 181 | 11x |
"mu_b", |
| 182 | 11x |
"omega_b", |
| 183 | 11x |
"mu_g", |
| 184 | 11x |
"omega_g", |
| 185 | 11x |
"mu_c", |
| 186 | 11x |
"omega_c", |
| 187 | 11x |
"mu_p", |
| 188 | 11x |
"omega_p" |
| 189 |
) |
|
| 190 | 11x |
formulas <- Map( |
| 191 | 11x |
.validate_covariate_formula, |
| 192 | 11x |
mget(paste0(names, "_formula"), inherits = FALSE), |
| 193 | 11x |
paste0(names, "_formula") |
| 194 |
) |
|
| 195 | 11x |
names(formulas) <- names |
| 196 | 11x |
parametrizations <- Map( |
| 197 | 11x |
function(value, name) {
|
| 198 | 88x |
.validate_covariate_parametrization( |
| 199 | 88x |
value, |
| 200 | 88x |
paste0(name, "_parametrization"), |
| 201 | 88x |
scale = startsWith(name, "omega_") |
| 202 |
) |
|
| 203 |
}, |
|
| 204 | 11x |
mget(paste0(names, "_parametrization"), inherits = FALSE), |
| 205 | 11x |
names |
| 206 |
) |
|
| 207 | 9x |
names(parametrizations) <- names |
| 208 | 9x |
for (name in names) {
|
| 209 | 72x |
prior_name <- paste0(name, "_intercept_prior") |
| 210 | 72x |
assign( |
| 211 | 72x |
prior_name, |
| 212 | 72x |
.positive_intercept_prior( |
| 213 | 72x |
get(prior_name, inherits = FALSE), |
| 214 | 72x |
parametrizations[[name]], |
| 215 | 72x |
positive = startsWith(name, "omega") && |
| 216 | 72x |
parametrizations[[name]] != "log-linear" |
| 217 |
) |
|
| 218 |
) |
|
| 219 |
} |
|
| 220 | 9x |
sigma <- set_limits(sigma, lower = getOption("jmpost.double_eps"))
|
| 221 | 9x |
predictor <- function(name) {
|
| 222 | 72x |
.covariate_predictor_stan( |
| 223 | 72x |
paste0("lm_clbrc_", name),
|
| 224 | 72x |
parametrizations[[name]] |
| 225 |
) |
|
| 226 |
} |
|
| 227 | 9x |
predictor_args <- stats::setNames( |
| 228 | 9x |
lapply(names, predictor), |
| 229 | 9x |
paste0(names, "_predictor") |
| 230 |
) |
|
| 231 | 9x |
stan <- StanModule(do.call( |
| 232 | 9x |
decorated_render, |
| 233 | 9x |
c( |
| 234 | 9x |
list( |
| 235 | 9x |
.x = read_stan("lm-claret-bruno-cov/model.stan"),
|
| 236 | 9x |
scaled_variance = scaled_variance, |
| 237 | 9x |
centred_baseline = centred_baseline, |
| 238 | 9x |
centred_growth = centred_growth, |
| 239 | 9x |
centred_resistance = centred_resistance, |
| 240 | 9x |
centred_inhibition = centred_inhibition |
| 241 |
), |
|
| 242 | 9x |
predictor_args |
| 243 |
) |
|
| 244 |
)) |
|
| 245 | 9x |
intercept_priors <- mget( |
| 246 | 9x |
paste0(names, "_intercept_prior"), |
| 247 | 9x |
inherits = FALSE |
| 248 |
) |
|
| 249 | 9x |
names(intercept_priors) <- names |
| 250 | 9x |
coefficient_priors <- mget( |
| 251 | 9x |
paste0(names, "_coefficients_prior"), |
| 252 | 9x |
inherits = FALSE |
| 253 |
) |
|
| 254 | 9x |
names(coefficient_priors) <- names |
| 255 | 9x |
parameters <- unlist( |
| 256 | 9x |
lapply(names, function(name) {
|
| 257 | 72x |
list( |
| 258 | 72x |
Parameter( |
| 259 | 72x |
name = paste0("lm_clbrc_", name, "_intercept"),
|
| 260 | 72x |
prior = intercept_priors[[name]] |
| 261 |
), |
|
| 262 | 72x |
Parameter( |
| 263 | 72x |
name = paste0("lm_clbrc_", name, "_coefficients"),
|
| 264 | 72x |
prior = coefficient_priors[[name]], |
| 265 | 72x |
size = paste0("p_lm_clbrc_", name)
|
| 266 |
) |
|
| 267 |
) |
|
| 268 |
}), |
|
| 269 | 9x |
recursive = FALSE |
| 270 |
) |
|
| 271 | 9x |
subject_parameter <- function(name, centred) {
|
| 272 | 36x |
if (!centred) {
|
| 273 | 27x |
return(Parameter( |
| 274 | 27x |
name = paste0("lm_clbrc_eta_tilde_", name),
|
| 275 | 27x |
prior = prior_std_normal(), |
| 276 | 27x |
size = "n_subjects" |
| 277 |
)) |
|
| 278 |
} |
|
| 279 | 9x |
Parameter( |
| 280 | 9x |
name = paste0("lm_clbrc_psi_", name),
|
| 281 | 9x |
prior = set_limits( |
| 282 | 9x |
prior_init_only(prior_lognormal( |
| 283 | 9x |
.predictor_reference_value( |
| 284 | 9x |
intercept_priors[[paste0("mu_", name)]],
|
| 285 | 9x |
parametrizations[[paste0("mu_", name)]]
|
| 286 |
), |
|
| 287 | 9x |
.predictor_reference_value( |
| 288 | 9x |
intercept_priors[[paste0("omega_", name)]],
|
| 289 | 9x |
parametrizations[[paste0("omega_", name)]]
|
| 290 |
) |
|
| 291 |
)), |
|
| 292 | 9x |
lower = getOption("jmpost.double_eps")
|
| 293 |
), |
|
| 294 | 9x |
size = "n_subjects" |
| 295 |
) |
|
| 296 |
} |
|
| 297 | 9x |
parameters <- c( |
| 298 | 9x |
parameters, |
| 299 | 9x |
list(Parameter(name = "lm_clbrc_sigma", prior = sigma)), |
| 300 | 9x |
unname(Map( |
| 301 | 9x |
subject_parameter, |
| 302 | 9x |
c("b", "g", "c", "p"),
|
| 303 | 9x |
c( |
| 304 | 9x |
centred_baseline, |
| 305 | 9x |
centred_growth, |
| 306 | 9x |
centred_resistance, |
| 307 | 9x |
centred_inhibition |
| 308 |
) |
|
| 309 |
)) |
|
| 310 |
) |
|
| 311 | 9x |
args <- c( |
| 312 | 9x |
list(LongitudinalModel( |
| 313 | 9x |
name = "Claret-Bruno with Covariates", |
| 314 | 9x |
stan = merge(stan, StanModule("lm-claret-bruno/functions.stan")),
|
| 315 | 9x |
parameters = do.call(ParameterList, parameters), |
| 316 | 9x |
scaled_variance = scaled_variance |
| 317 |
)), |
|
| 318 | 9x |
stats::setNames(formulas, paste0(names, "_formula")), |
| 319 | 9x |
stats::setNames(parametrizations, paste0(names, "_parametrization")), |
| 320 | 9x |
list( |
| 321 | 9x |
centred_baseline = centred_baseline, |
| 322 | 9x |
centred_growth = centred_growth, |
| 323 | 9x |
centred_resistance = centred_resistance, |
| 324 | 9x |
centred_inhibition = centred_inhibition |
| 325 |
) |
|
| 326 |
) |
|
| 327 | 9x |
do.call(.LongitudinalClaretBrunoCov, args) |
| 328 |
} |
|
| 329 | ||
| 330 |
#' @export |
|
| 331 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 332 |
enableGQ.LongitudinalClaretBrunoCov <- function( |
|
| 333 |
object, |
|
| 334 |
generator = NULL, |
|
| 335 |
type = NULL, |
|
| 336 |
... |
|
| 337 |
) {
|
|
| 338 | 1x |
idv <- identical(type, "longitudinal") && |
| 339 | 1x |
is(generator, "QuantityGeneratorSubject") |
| 340 | 1x |
pop <- identical(type, "longitudinal") && |
| 341 | 1x |
is(generator, "QuantityGeneratorPopulation") |
| 342 | 1x |
predictor <- function(name) {
|
| 343 | 4x |
.covariate_predictor_stan( |
| 344 | 4x |
paste0("lm_clbrc_", name),
|
| 345 | 4x |
slot(object, paste0(name, "_parametrization")), |
| 346 | 4x |
design_prefix = paste0("gq_lm_clbrc_", name),
|
| 347 | 4x |
n_rows = "gq_n_quant" |
| 348 |
) |
|
| 349 |
} |
|
| 350 | 1x |
StanModule(decorated_render( |
| 351 | 1x |
.x = read_stan("lm-claret-bruno-cov/quantities.stan"),
|
| 352 | 1x |
include_gq_longitudinal_idv = idv, |
| 353 | 1x |
include_gq_longitudinal_pop = pop, |
| 354 | 1x |
mu_b_population_predictor = predictor("mu_b"),
|
| 355 | 1x |
mu_g_population_predictor = predictor("mu_g"),
|
| 356 | 1x |
mu_c_population_predictor = predictor("mu_c"),
|
| 357 | 1x |
mu_p_population_predictor = predictor("mu_p")
|
| 358 |
)) |
|
| 359 |
} |
|
| 360 | ||
| 361 |
#' @rdname gq_population_stan_data |
|
| 362 |
#' @export |
|
| 363 |
gq_population_stan_data.LongitudinalClaretBrunoCov <- function( |
|
| 364 |
object, |
|
| 365 |
model, |
|
| 366 |
data = NULL, |
|
| 367 |
... |
|
| 368 |
) {
|
|
| 369 | 1x |
names <- c("mu_b", "mu_g", "mu_c", "mu_p")
|
| 370 | 1x |
result <- list( |
| 371 | 1x |
declarations = paste( |
| 372 | 1x |
sprintf( |
| 373 | 1x |
"matrix[gq_n_quant, p_lm_clbrc_%s] gq_lm_clbrc_%s_design;", |
| 374 | 1x |
names, |
| 375 | 1x |
names |
| 376 |
), |
|
| 377 | 1x |
collapse = "\n" |
| 378 |
), |
|
| 379 | 1x |
data = list() |
| 380 |
) |
|
| 381 | 1x |
if (!is.null(data)) {
|
| 382 | ! |
assert_that( |
| 383 | ! |
!is.null(object@newdata) && |
| 384 | ! |
nrow(object@newdata) == length(object@times), |
| 385 | ! |
msg = "Population quantities for `LongitudinalClaretBrunoCov` require `GridPopulation(newdata = ...)`" |
| 386 |
) |
|
| 387 | ! |
subject_data <- as.data.frame(harmonise(data@subject)) |
| 388 | ! |
result$data <- stats::setNames( |
| 389 | ! |
lapply(names, function(name) {
|
| 390 | ! |
.covariate_prediction_design_matrix( |
| 391 | ! |
slot(model, paste0(name, "_formula")), |
| 392 | ! |
object@newdata, |
| 393 | ! |
subject_data, |
| 394 | ! |
paste0(name, "_formula") |
| 395 |
) |
|
| 396 |
}), |
|
| 397 | ! |
paste0("gq_lm_clbrc_", names, "_design")
|
| 398 |
) |
|
| 399 |
} |
|
| 400 | 1x |
result |
| 401 |
} |
|
| 402 | ||
| 403 |
#' @export |
|
| 404 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 405 |
enableLink.LongitudinalClaretBrunoCov <- function(object, ...) {
|
|
| 406 | 1x |
object@stan <- merge( |
| 407 | 1x |
object@stan, |
| 408 | 1x |
StanModule("lm-claret-bruno-cov/link.stan")
|
| 409 |
) |
|
| 410 | 1x |
object |
| 411 |
} |
|
| 412 |
#' @export |
|
| 413 |
#' @returns A `LinkComponent` object. |
|
| 414 |
linkDSLD.LongitudinalClaretBrunoCov <- function( |
|
| 415 |
prior = prior_normal(0, 2), |
|
| 416 |
model, |
|
| 417 |
... |
|
| 418 |
) {
|
|
| 419 | 1x |
LinkComponent( |
| 420 | 1x |
key = "link_dsld", |
| 421 | 1x |
stan = StanModule("lm-claret-bruno/link_dsld.stan"),
|
| 422 | 1x |
prior = prior |
| 423 |
) |
|
| 424 |
} |
|
| 425 |
#' @export |
|
| 426 |
#' @returns A `LinkComponent` object. |
|
| 427 |
linkTTG.LongitudinalClaretBrunoCov <- function( |
|
| 428 |
prior = prior_normal(0, 2), |
|
| 429 |
model, |
|
| 430 |
... |
|
| 431 |
) {
|
|
| 432 | ! |
LinkComponent( |
| 433 | ! |
key = "link_ttg", |
| 434 | ! |
stan = StanModule("lm-claret-bruno/link_ttg.stan"),
|
| 435 | ! |
prior = prior |
| 436 |
) |
|
| 437 |
} |
|
| 438 |
#' @export |
|
| 439 |
#' @returns A `LinkComponent` object. |
|
| 440 |
linkIdentity.LongitudinalClaretBrunoCov <- function( |
|
| 441 |
prior = prior_normal(0, 2), |
|
| 442 |
model, |
|
| 443 |
... |
|
| 444 |
) {
|
|
| 445 | ! |
LinkComponent( |
| 446 | ! |
key = "link_identity", |
| 447 | ! |
stan = StanModule("lm-claret-bruno/link_identity.stan"),
|
| 448 | ! |
prior = prior |
| 449 |
) |
|
| 450 |
} |
|
| 451 |
#' @export |
|
| 452 |
#' @returns A `LinkComponent` object. |
|
| 453 |
linkGrowth.LongitudinalClaretBrunoCov <- function( |
|
| 454 |
prior = prior_normal(0, 2), |
|
| 455 |
model, |
|
| 456 |
... |
|
| 457 |
) {
|
|
| 458 | ! |
LinkComponent( |
| 459 | ! |
key = "link_growth", |
| 460 | ! |
stan = StanModule("lm-claret-bruno/link_growth.stan"),
|
| 461 | ! |
prior = prior |
| 462 |
) |
|
| 463 |
} |
|
| 464 |
#' @rdname getPredictionNames |
|
| 465 |
#' @export |
|
| 466 |
getPredictionNames.LongitudinalClaretBrunoCov <- function(object, ...) {
|
|
| 467 | 1x |
c("b", "g", "c", "p")
|
| 468 |
} |
|
| 469 |
#' @rdname getRandomEffectsNames |
|
| 470 |
#' @export |
|
| 471 |
getRandomEffectsNames.LongitudinalClaretBrunoCov <- function(object, ...) {
|
|
| 472 | 1x |
c( |
| 473 | 1x |
b = "lm_clbrc_psi_b", |
| 474 | 1x |
g = "lm_clbrc_psi_g", |
| 475 | 1x |
c = "lm_clbrc_psi_c", |
| 476 | 1x |
p = "lm_clbrc_psi_p" |
| 477 |
) |
|
| 478 |
} |
|
| 479 | ||
| 480 |
#' @rdname longitudinal_model_stan_data |
|
| 481 |
#' @export |
|
| 482 |
longitudinal_model_stan_data.LongitudinalClaretBrunoCov <- function( |
|
| 483 |
model, |
|
| 484 |
subject |
|
| 485 |
) {
|
|
| 486 | 5x |
subject_data <- as.data.frame(harmonise(subject)) |
| 487 | 5x |
names <- c( |
| 488 | 5x |
"mu_b", |
| 489 | 5x |
"omega_b", |
| 490 | 5x |
"mu_g", |
| 491 | 5x |
"omega_g", |
| 492 | 5x |
"mu_c", |
| 493 | 5x |
"omega_c", |
| 494 | 5x |
"mu_p", |
| 495 | 5x |
"omega_p" |
| 496 |
) |
|
| 497 | 5x |
designs <- stats::setNames( |
| 498 | 5x |
lapply(names, function(name) {
|
| 499 | 40x |
.covariate_design_matrix( |
| 500 | 40x |
slot(model, paste0(name, "_formula")), |
| 501 | 40x |
subject_data, |
| 502 | 40x |
paste0(name, "_formula") |
| 503 |
) |
|
| 504 |
}), |
|
| 505 | 5x |
names |
| 506 |
) |
|
| 507 | 5x |
unlist( |
| 508 | 5x |
lapply(names, function(name) {
|
| 509 | 40x |
stats::setNames( |
| 510 | 40x |
list(ncol(designs[[name]]), designs[[name]]), |
| 511 | 40x |
c( |
| 512 | 40x |
paste0("p_lm_clbrc_", name),
|
| 513 | 40x |
paste0("lm_clbrc_", name, "_design")
|
| 514 |
) |
|
| 515 |
) |
|
| 516 |
}), |
|
| 517 | 5x |
recursive = FALSE |
| 518 |
) |
|
| 519 |
} |
|
| 520 |
#' @rdname required_longitudinal_covs |
|
| 521 |
#' @export |
|
| 522 |
required_longitudinal_covs.LongitudinalClaretBrunoCov <- function(object, ...) {
|
|
| 523 | ! |
unique(unlist(lapply(c("mu_b", "mu_g", "mu_c", "mu_p"), function(name) {
|
| 524 | ! |
all.vars(slot(object, paste0(name, "_formula"))) |
| 525 |
}))) |
|
| 526 |
} |
|
| 527 |
#' @rdname required_simulation_covariates |
|
| 528 |
#' @export |
|
| 529 |
required_simulation_covariates.LongitudinalClaretBrunoCov <- function( |
|
| 530 |
object, |
|
| 531 |
... |
|
| 532 |
) {
|
|
| 533 | ! |
unique(unlist(lapply( |
| 534 | ! |
c( |
| 535 | ! |
"mu_b", |
| 536 | ! |
"omega_b", |
| 537 | ! |
"mu_g", |
| 538 | ! |
"omega_g", |
| 539 | ! |
"mu_c", |
| 540 | ! |
"omega_c", |
| 541 | ! |
"mu_p", |
| 542 | ! |
"omega_p" |
| 543 |
), |
|
| 544 | ! |
function(name) all.vars(slot(object, paste0(name, "_formula"))) |
| 545 |
))) |
|
| 546 |
} |
| 1 |
#' Package-Defined Prior Transformation |
|
| 2 |
#' |
|
| 3 |
#' Describes a transformation from a model parameter to the scale on which a |
|
| 4 |
#' prior density is evaluated. Only package-defined transformations are |
|
| 5 |
#' supported; the object contains no user-supplied R or Stan functions. |
|
| 6 |
#' |
|
| 7 |
#' @slot kind (`character`)\cr Transformation identifier. |
|
| 8 |
#' @slot scale (`numeric`)\cr Positive multiplier inside the exponential. |
|
| 9 |
#' @seealso [prior_transform_exp()], [prior_transform_square()] |
|
| 10 |
#' @aliases PriorTransform |
|
| 11 |
#' @exportClass PriorTransform |
|
| 12 |
.PriorTransform <- setClass( |
|
| 13 |
"PriorTransform", |
|
| 14 |
slots = c(kind = "character", scale = "numeric") |
|
| 15 |
) |
|
| 16 | ||
| 17 |
setValidity("PriorTransform", function(object) {
|
|
| 18 |
if ( |
|
| 19 |
length(object@kind) != 1 || |
|
| 20 |
!object@kind %in% c("exp", "square")
|
|
| 21 |
) {
|
|
| 22 |
return("Unknown prior transformation")
|
|
| 23 |
} |
|
| 24 |
if ( |
|
| 25 |
length(object@scale) != 1 || |
|
| 26 |
!is.finite(object@scale) || |
|
| 27 |
object@scale <= 0 |
|
| 28 |
) {
|
|
| 29 |
return("`scale` must be a finite positive number")
|
|
| 30 |
} |
|
| 31 |
if (object@kind == "square" && object@scale != 1) {
|
|
| 32 |
return("The square transformation does not accept a scale")
|
|
| 33 |
} |
|
| 34 |
TRUE |
|
| 35 |
}) |
|
| 36 | ||
| 37 | ||
| 38 |
#' Transformations to the Prior Density Scale |
|
| 39 |
#' |
|
| 40 |
#' `prior_transform_exp()` describes `exp(scale * x)` for an unconstrained |
|
| 41 |
#' model parameter `x`. `prior_transform_square()` describes `x^2` on the |
|
| 42 |
#' non-negative branch only. Both map to a non-negative density scale, with |
|
| 43 |
#' strictly positive values in the interior of the parameter domain. |
|
| 44 |
#' |
|
| 45 |
#' @details |
|
| 46 |
#' Supply these objects as the `transform` argument to a scalar distribution |
|
| 47 |
#' helper such as [prior_normal()] or to [prior_transform()]. |
|
| 48 |
#' |
|
| 49 |
#' The intended density is `p(g(x)) * abs(g'(x))`, including the Jacobian. |
|
| 50 |
#' The square transformation requires a non-negative model parameter so that |
|
| 51 |
#' it is one-to-one. It must not silently fold an unconstrained parameter. |
|
| 52 |
#' |
|
| 53 |
#' @typed scale: number |
|
| 54 |
#' finite positive multiplier inside the exponential. Use `2` for a prior |
|
| 55 |
#' on the variance when `x` is a log standard deviation. |
|
| 56 |
#' @returns A `PriorTransform` object. |
|
| 57 |
#' @name prior-transformations |
|
| 58 |
#' @export |
|
| 59 |
#' @examples |
|
| 60 |
#' prior_transform_exp() |
|
| 61 |
#' prior_transform_exp(scale = 2) |
|
| 62 |
#' prior_transform_square() |
|
| 63 |
#' prior_normal(60, 60, transform = prior_transform_exp()) |
|
| 64 |
prior_transform_exp <- function(scale = 1) {
|
|
| 65 | 50x |
.PriorTransform(kind = "exp", scale = scale) |
| 66 |
} |
|
| 67 | ||
| 68 | ||
| 69 |
#' @rdname prior-transformations |
|
| 70 |
#' @export |
|
| 71 |
prior_transform_square <- function() {
|
|
| 72 | 21x |
.PriorTransform(kind = "square", scale = 1) |
| 73 |
} |
|
| 74 | ||
| 75 | ||
| 76 |
# Bounds include endpoints for mapping truncation limits; Stan samples in the |
|
| 77 |
# interior of the domain. |
|
| 78 |
.prior_transform_domain <- function(object) {
|
|
| 79 | 1x |
switch(object@kind, exp = c(-Inf, Inf), square = c(0, Inf)) |
| 80 |
} |
|
| 81 | ||
| 82 |
.prior_transform_forward <- function(object, x) {
|
|
| 83 | 2170x |
if (object@kind == "square" && any(x < 0)) {
|
| 84 | 1x |
stop("The square prior transformation requires non-negative values")
|
| 85 |
} |
|
| 86 | 67x |
switch(object@kind, exp = exp(object@scale * x), square = x^2) |
| 87 |
} |
|
| 88 | ||
| 89 |
.prior_transform_inverse <- function(object, y) {
|
|
| 90 | 2337x |
if (any(y < 0)) {
|
| 91 | 3x |
stop("The inverse prior transformation requires non-negative values")
|
| 92 |
} |
|
| 93 | 95x |
switch(object@kind, exp = log(y) / object@scale, square = sqrt(y)) |
| 94 |
} |
|
| 95 | ||
| 96 |
.prior_transform_stan <- function(object, x) {
|
|
| 97 |
# Scalar expressions; vector reduction of Jacobians belongs in the renderer. |
|
| 98 | 73x |
if (object@kind == "exp") {
|
| 99 | 44x |
scaled_x <- if (object@scale == 1) {
|
| 100 | 37x |
x |
| 101 |
} else {
|
|
| 102 | 7x |
glue::glue("{object@scale} * ({x})")
|
| 103 |
} |
|
| 104 | 44x |
return(list( |
| 105 | 44x |
value = as.character(glue::glue("exp({scaled_x})")),
|
| 106 | 44x |
log_jacobian = if (object@scale == 1) {
|
| 107 | 37x |
x |
| 108 |
} else {
|
|
| 109 | 7x |
as.character(glue::glue("log({object@scale}) + {scaled_x}"))
|
| 110 |
} |
|
| 111 |
)) |
|
| 112 |
} |
|
| 113 | 29x |
list( |
| 114 | 29x |
value = as.character(glue::glue("square({x})")),
|
| 115 | 29x |
log_jacobian = as.character(glue::glue("log(2) + log({x})"))
|
| 116 |
) |
|
| 117 |
} |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include Prior.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' `Parameter` Function Arguments |
|
| 7 |
#' |
|
| 8 |
#' The documentation lists all the conventional arguments for [`Parameter`] |
|
| 9 |
#' constructors. |
|
| 10 |
#' |
|
| 11 |
#' @typed x: Parameter |
|
| 12 |
#' a prior Distribution |
|
| 13 |
#' @typed object: Parameter |
|
| 14 |
#' a prior Distribution |
|
| 15 |
#' @param ... Not Used. |
|
| 16 |
#' |
|
| 17 |
#' @name Parameter-Shared |
|
| 18 |
#' @keywords internal |
|
| 19 |
NULL |
|
| 20 | ||
| 21 | ||
| 22 |
setClassUnion(name = "numeric_OR_character", c("numeric", "character"))
|
|
| 23 | ||
| 24 |
# Parameter-class ---- |
|
| 25 | ||
| 26 |
#' `Parameter` |
|
| 27 |
#' |
|
| 28 |
#' Stores the name, the prior distribution and the size of a parameter. |
|
| 29 |
#' If `size` is a string then this indicates the name of the variable |
|
| 30 |
#' within the stan data object that specifies the size of this parameter. |
|
| 31 |
#' |
|
| 32 |
#' @slot name (`string`)\cr of the parameter. |
|
| 33 |
#' @slot prior (`Prior`)\cr for the parameter. |
|
| 34 |
#' @slot size (`numeric` or `string`)\cr dimension of the parameter. |
|
| 35 |
#' |
|
| 36 |
#' @family Parameter |
|
| 37 |
#' @exportClass Parameter |
|
| 38 |
#' @export Parameter |
|
| 39 |
.Parameter <- setClass( |
|
| 40 |
Class = "Parameter", |
|
| 41 |
slots = list( |
|
| 42 |
"name" = "character", |
|
| 43 |
"prior" = "Prior", |
|
| 44 |
"size" = "numeric_OR_character" |
|
| 45 |
) |
|
| 46 |
) |
|
| 47 |
#' @typed prior: Prior |
|
| 48 |
#' for the parameter. |
|
| 49 |
#' @typed name: string |
|
| 50 |
#' of the parameter. |
|
| 51 |
#' @typed size: "`numeric` or `string`" |
|
| 52 |
#' dimension of the parameter. |
|
| 53 |
#' @rdname Parameter-class |
|
| 54 |
#' |
|
| 55 |
#' @returns A `Parameter` object. |
|
| 56 |
#' |
|
| 57 |
#' @examples |
|
| 58 |
#' Parameter(prior = prior_normal(0, 1), name = "beta") |
|
| 59 |
Parameter <- function(prior, name, size = 1) {
|
|
| 60 | 2416x |
.Parameter( |
| 61 | 2416x |
prior = prior, |
| 62 | 2416x |
name = name, |
| 63 | 2416x |
size = size |
| 64 |
) |
|
| 65 |
} |
|
| 66 |
setValidity( |
|
| 67 |
Class = "Parameter", |
|
| 68 |
method = function(object) {
|
|
| 69 |
if (!length(object@name) == 1) {
|
|
| 70 |
return("Name must be a length 1 character vector")
|
|
| 71 |
} |
|
| 72 |
if (is.character(object@size)) {
|
|
| 73 |
if (!length(object@size) == 1) {
|
|
| 74 |
return( |
|
| 75 |
"Size must be a numeric vector or length 1 character vector" |
|
| 76 |
) |
|
| 77 |
} |
|
| 78 |
} |
|
| 79 |
return(TRUE) |
|
| 80 |
} |
|
| 81 |
) |
|
| 82 | ||
| 83 | ||
| 84 |
#' `Parameter` -> `StanModule` |
|
| 85 |
#' |
|
| 86 |
#' Converts a [`Parameter`] object to a [`StanModule`] object |
|
| 87 |
#' |
|
| 88 |
#' @inheritParams Parameter-Shared |
|
| 89 |
#' |
|
| 90 |
#' @family Parameter |
|
| 91 |
#' @family as.StanModule |
|
| 92 |
#' @export |
|
| 93 |
#' |
|
| 94 |
#' @returns A `StanModule` object. |
|
| 95 |
as.StanModule.Parameter <- function(object, ...) {
|
|
| 96 |
# Affine multipliers may depend on the prior's auxiliary parameters, which |
|
| 97 |
# must therefore be declared before the coefficient itself. |
|
| 98 | 1650x |
merge( |
| 99 | 1650x |
as.StanModule(object@prior, name = object@name, size = object@size), |
| 100 | 1650x |
as.StanModule.ParameterDeclaration(object) |
| 101 |
) |
|
| 102 |
} |
|
| 103 | ||
| 104 |
#' Render Stan Parameter Constraints |
|
| 105 |
#' |
|
| 106 |
#' Converts a lower/upper limit vector from a [`Prior`] into a Stan declaration |
|
| 107 |
#' constraint, e.g. `<lower=0.1, upper=1>`. |
|
| 108 |
#' |
|
| 109 |
#' @typed limits: numeric |
|
| 110 |
#' length-two vector containing lower and upper parameter limits. |
|
| 111 |
#' |
|
| 112 |
#' @return A length-one character vector. |
|
| 113 |
#' |
|
| 114 |
#' @keywords internal |
|
| 115 |
render_stan_parameter_limits <- function(limits) {
|
|
| 116 | 1664x |
limits_names <- c("lower", "upper")
|
| 117 | 1664x |
limits <- stats::setNames(limits, limits_names) |
| 118 | 1664x |
limits <- limits[is.finite(limits)] |
| 119 | 1664x |
if (length(limits) == 0) {
|
| 120 | 1104x |
return("")
|
| 121 |
} |
|
| 122 | 560x |
constraints <- paste(names(limits), limits, sep = "=", collapse = ", ") |
| 123 | 560x |
paste0("<", constraints, ">")
|
| 124 |
} |
|
| 125 | ||
| 126 |
#' Render Stan Parameter Declaration |
|
| 127 |
#' |
|
| 128 |
#' Creates a Stan declaration for a sampled scalar or vector parameter. |
|
| 129 |
#' |
|
| 130 |
#' @typed name: string |
|
| 131 |
#' parameter name. |
|
| 132 |
#' @typed size: numeric_OR_character |
|
| 133 |
#' parameter size. |
|
| 134 |
#' @typed limits: numeric |
|
| 135 |
#' lower and upper parameter limits. |
|
| 136 |
#' @typed multiplier: string |
|
| 137 |
#' Stan affine multiplier expression for an unbounded parameter. |
|
| 138 |
#' |
|
| 139 |
#' @return A length-one character vector. |
|
| 140 |
#' |
|
| 141 |
#' @keywords internal |
|
| 142 |
render_stan_parameter_declaration <- function( |
|
| 143 |
name, |
|
| 144 |
size, |
|
| 145 |
limits, |
|
| 146 |
multiplier = "" |
|
| 147 |
) {
|
|
| 148 | 1649x |
constraints <- render_stan_parameter_limits(limits) |
| 149 |
# Stan does not allow bounds and an affine multiplier together. Retain the |
|
| 150 |
# centred declaration for bounded parameters and its existing truncation. |
|
| 151 | 1649x |
if (constraints == "" && nzchar(multiplier)) {
|
| 152 | 15x |
constraints <- glue::glue("<multiplier={multiplier}>")
|
| 153 |
} |
|
| 154 | 1649x |
if (length(size) == 1 && is.numeric(size) && size == 1) {
|
| 155 | 554x |
return(glue::glue("real{constraints} {name};"))
|
| 156 |
} |
|
| 157 | 1095x |
glue::glue("vector{constraints}[{size}] {name};")
|
| 158 |
} |
|
| 159 | ||
| 160 |
#' Render Stan Constant Parameter Declaration |
|
| 161 |
#' |
|
| 162 |
#' Creates a Stan transformed-parameter declaration that fixes a scalar or vector |
|
| 163 |
#' parameter at a data-supplied constant value. |
|
| 164 |
#' |
|
| 165 |
#' @typed name: string |
|
| 166 |
#' parameter name. |
|
| 167 |
#' @typed size: numeric_OR_character |
|
| 168 |
#' parameter size. |
|
| 169 |
#' @typed limits: numeric |
|
| 170 |
#' lower and upper parameter limits. |
|
| 171 |
#' @typed is_vector: flag |
|
| 172 |
#' whether the supplied constant is a vector. |
|
| 173 |
#' |
|
| 174 |
#' @return A length-one character vector. |
|
| 175 |
#' |
|
| 176 |
#' @keywords internal |
|
| 177 |
render_stan_const_declaration <- function( |
|
| 178 |
name, |
|
| 179 |
size, |
|
| 180 |
limits, |
|
| 181 |
is_vector = FALSE |
|
| 182 |
) {
|
|
| 183 | 10x |
constraints <- render_stan_parameter_limits(limits) |
| 184 | 10x |
value <- glue::glue("prior_const_{name}")
|
| 185 | 10x |
if (length(size) == 1 && is.numeric(size) && size == 1) {
|
| 186 | 5x |
if (is_vector) {
|
| 187 | 1x |
value <- glue::glue("{value}[1]")
|
| 188 |
} |
|
| 189 | 5x |
return(glue::glue("real{constraints} {name} = {value};"))
|
| 190 |
} |
|
| 191 | 5x |
if (is_vector) {
|
| 192 | 3x |
return(glue::glue( |
| 193 | 3x |
"vector{constraints}[{size}] {name} = {value};"
|
| 194 |
)) |
|
| 195 |
} |
|
| 196 | 2x |
glue::glue( |
| 197 | 2x |
"vector{constraints}[{size}] {name} = rep_vector({value}, {size});"
|
| 198 |
) |
|
| 199 |
} |
|
| 200 | ||
| 201 |
#' `Parameter` Declaration -> `StanModule` |
|
| 202 |
#' |
|
| 203 |
#' Creates only the Stan declaration for a [`Parameter`]. Sampled parameters are |
|
| 204 |
#' declared in the `parameters` block; constant parameters are declared in the |
|
| 205 |
#' `transformed parameters` block. |
|
| 206 |
#' |
|
| 207 |
#' @inheritParams Parameter-Shared |
|
| 208 |
#' @param ... not used. |
|
| 209 |
#' |
|
| 210 |
#' @return A [`StanModule`] object. |
|
| 211 |
#' |
|
| 212 |
#' @export |
|
| 213 |
as.StanModule.ParameterDeclaration <- function(object, ...) {
|
|
| 214 | 1653x |
declaration <- if (object@prior@.is_const) {
|
| 215 | 6x |
render_stan_const_declaration( |
| 216 | 6x |
name = object@name, |
| 217 | 6x |
size = object@size, |
| 218 | 6x |
limits = object@prior@limits, |
| 219 | 6x |
is_vector = object@prior@.allow_vectors |
| 220 |
) |
|
| 221 |
} else {
|
|
| 222 |
# Saved Prior objects from before affine transforms were introduced |
|
| 223 |
# have no multiplier slot. They retain their original declarations. |
|
| 224 | 1647x |
repr_multiplier <- attr(object@prior, "repr_multiplier", exact = TRUE) |
| 225 | 1647x |
multiplier <- if (is.null(repr_multiplier)) {
|
| 226 |
"" |
|
| 227 |
} else {
|
|
| 228 | 1646x |
repr_multiplier(name = object@name, size = object@size) |
| 229 |
} |
|
| 230 | 1647x |
render_stan_parameter_declaration( |
| 231 | 1647x |
name = object@name, |
| 232 | 1647x |
size = object@size, |
| 233 | 1647x |
limits = object@prior@limits, |
| 234 | 1647x |
multiplier = multiplier |
| 235 |
) |
|
| 236 |
} |
|
| 237 | ||
| 238 | 1653x |
block <- if (object@prior@.is_const) {
|
| 239 | 6x |
"transformed parameters" |
| 240 |
} else {
|
|
| 241 | 1647x |
"parameters" |
| 242 |
} |
|
| 243 | 1653x |
StanModule(glue::glue( |
| 244 | 1653x |
"{block} {{
|
| 245 | 1653x |
{declaration}
|
| 246 |
}}" |
|
| 247 |
)) |
|
| 248 |
} |
|
| 249 | ||
| 250 | ||
| 251 |
#' `Parameter` -> `list` |
|
| 252 |
#' |
|
| 253 |
#' Converts a Parameter object to a list of parameter data values |
|
| 254 |
#' for a Stan model. |
|
| 255 |
#' |
|
| 256 |
#' @inheritParams Parameter-Shared |
|
| 257 |
#' |
|
| 258 |
#' @family as_stan_list |
|
| 259 |
#' @family Parameter |
|
| 260 |
#' @export |
|
| 261 |
#' |
|
| 262 |
#' @returns A named `list` suitable for use as Stan data. |
|
| 263 |
as_stan_list.Parameter <- function(object, ...) {
|
|
| 264 | 489x |
as_stan_list(object@prior, name = object@name) |
| 265 |
} |
|
| 266 | ||
| 267 | ||
| 268 |
#' Parameter Getter Functions |
|
| 269 |
#' |
|
| 270 |
#' @typed x: Paramater |
|
| 271 |
#' A model parameter |
|
| 272 |
#' @typed object: Paramater |
|
| 273 |
#' A model parameter |
|
| 274 |
#' @param ... Not used. |
|
| 275 |
#' |
|
| 276 |
#' @description |
|
| 277 |
#' Getter functions for the slots of a [`Parameter`] object |
|
| 278 |
#' @family Parameter |
|
| 279 |
#' @name Parameter-Getter-Methods |
|
| 280 |
NULL |
|
| 281 | ||
| 282 |
#' @describeIn Parameter-Getter-Methods The parameter's name |
|
| 283 |
#' @export |
|
| 284 |
#' |
|
| 285 |
#' @returns The requested parameter property: its name, initial values, or size. |
|
| 286 | 11081x |
names.Parameter <- function(x) x@name |
| 287 | ||
| 288 |
#' @describeIn Parameter-Getter-Methods The parameter's initial values |
|
| 289 |
#' @export |
|
| 290 | 10079x |
initialValues.Parameter <- function(object, ...) initialValues(object@prior) |
| 291 | ||
| 292 |
#' @describeIn Parameter-Getter-Methods The parameter's dimensionality |
|
| 293 |
#' @export |
|
| 294 | 144x |
size.Parameter <- function(object) object@size |
| 295 | ||
| 296 | ||
| 297 |
#' `Parameter` -> `Character` |
|
| 298 |
#' |
|
| 299 |
#' Converts a [`Parameter`] object to a character vector |
|
| 300 |
#' @inheritParams Parameter-Shared |
|
| 301 |
#' @family Parameter |
|
| 302 |
#' @export |
|
| 303 |
#' |
|
| 304 |
#' @returns A character vector. |
|
| 305 |
as.character.Parameter <- function(x, ...) {
|
|
| 306 | 173x |
if (x@prior@.is_const) {
|
| 307 | 2x |
return(paste0(x@name, " = ", as.character(x@prior))) |
| 308 |
} |
|
| 309 | 171x |
paste0(x@name, " ~ ", as.character(x@prior)) |
| 310 |
} |
|
| 311 | ||
| 312 | ||
| 313 |
#' @rdname show-object |
|
| 314 |
#' @export |
|
| 315 |
setMethod( |
|
| 316 |
f = "show", |
|
| 317 |
signature = "Parameter", |
|
| 318 |
definition = function(object) {
|
|
| 319 | 3x |
x <- sprintf("\nParameter Object:\n %s\n\n", as.character(object))
|
| 320 | 3x |
cat(x) |
| 321 | 3x |
return(object) |
| 322 |
} |
|
| 323 |
) |
| 1 |
#' Simulate Patients from Posterior Predictive Distribution |
|
| 2 |
#' @typed object: JointModelSamples |
|
| 3 |
#' the object containing the posterior samples. |
|
| 4 |
#' @typed newdata: data.frame |
|
| 5 |
#' A data frame containing data in the same format as |
|
| 6 |
#' the `object@data@survival@data`. Importantly, it should contain the same covariates and factor levels |
|
| 7 |
#' as the variables used in the survival formula `object@data@survival@formula` and |
|
| 8 |
#' the same columns used for `study`, `id`, and `arm`. |
|
| 9 |
#' @param ... unused |
|
| 10 |
#' Unused. |
|
| 11 |
#' @typed times: numeric |
|
| 12 |
#' times to simulate SLD for all patients. |
|
| 13 |
#' @typed jitter_var: numeric |
|
| 14 |
#' variances to add noise to the observed SLD `times`. The first value is for any times |
|
| 15 |
#' less than 0 and the second for any times after 0. All positive (negative) times will remain positive (negative). |
|
| 16 |
#' Jitter values are generated from a normal distribution with mean 0 and the given variances. |
|
| 17 |
#' @typed time_max: number |
|
| 18 |
#' the maximum time to simulate to. |
|
| 19 |
#' @typed time_step: number |
|
| 20 |
#' the time interval between evaluating the log-hazard function. |
|
| 21 |
#' @typed lambda_censor: number |
|
| 22 |
#' the censoring rate, as the parameter of an exponential distribution. |
|
| 23 |
#' @typed scaled_variance: flag |
|
| 24 |
#' Should variance be scaled by the expected value. Must be set the same as was used for |
|
| 25 |
#' model fitting. |
|
| 26 |
#' @param seed ignored. |
|
| 27 |
#' @param nsim ignored. |
|
| 28 |
#' @details Simulates a set of patients based on the covariates of those |
|
| 29 |
#' used in the model fit or from `newdata`, which must contain the |
|
| 30 |
#' same column names and factor levels. |
|
| 31 |
#' |
|
| 32 |
#' @importFrom stats simulate |
|
| 33 |
#' @export |
|
| 34 |
#' |
|
| 35 |
#' @returns A `SimJointData` object containing posterior-predictive simulations. |
|
| 36 |
simulate.JointModelSamples <- function( |
|
| 37 |
object, |
|
| 38 |
nsim = NULL, |
|
| 39 |
seed = NULL, |
|
| 40 |
newdata = NULL, |
|
| 41 |
times = c(-2, 0, 10, 50, 100), |
|
| 42 |
jitter_var = c(0, 0), |
|
| 43 |
time_max = 2000, |
|
| 44 |
time_step = 1, |
|
| 45 |
lambda_censor = 1 / 3000, |
|
| 46 |
scaled_variance = FALSE, |
|
| 47 |
... |
|
| 48 |
) {
|
|
| 49 | ! |
longitudinal_covariates <- required_simulation_covariates( |
| 50 | ! |
object@model@longitudinal |
| 51 |
) |
|
| 52 | ! |
subj_data <- if (is.null(newdata)) {
|
| 53 | ! |
dplyr::left_join( |
| 54 | ! |
object@data@subject@data[, unique(c( |
| 55 | ! |
object@data@subject@subject, |
| 56 | ! |
object@data@subject@arm, |
| 57 | ! |
object@data@subject@study, |
| 58 | ! |
longitudinal_covariates |
| 59 |
))], |
|
| 60 | ! |
object@data@survival@data[, c( |
| 61 | ! |
object@data@subject@subject, |
| 62 | ! |
all.vars(delete.response(terms(object@data@survival@formula))) |
| 63 |
)], |
|
| 64 | ! |
by = object@data@subject@subject |
| 65 |
) |
|
| 66 |
} else {
|
|
| 67 | ! |
newdata |
| 68 |
} |
|
| 69 | ||
| 70 | ! |
subject_data <- DataSubject( |
| 71 | ! |
subj_data, |
| 72 | ! |
subject = object@data@subject@subject, |
| 73 | ! |
arm = object@data@subject@arm, |
| 74 | ! |
study = object@data@subject@study |
| 75 |
) |
|
| 76 | ||
| 77 | ! |
n_patients <- nrow(subject_data@data) |
| 78 | ||
| 79 | ! |
requiredVars <- list( |
| 80 | ! |
longitudinal = lapply( |
| 81 | ! |
object@model@survival@parameters@parameters, |
| 82 | ! |
function(x) x@name |
| 83 |
), |
|
| 84 | ! |
survival = lapply( |
| 85 | ! |
object@model@longitudinal@parameters@parameters, |
| 86 | ! |
function(x) x@name |
| 87 |
), |
|
| 88 | ! |
link = lapply(object@model@link@components, function(x) x@key) |
| 89 |
) |
|
| 90 | ||
| 91 | ! |
draws <- object@results$draws( |
| 92 | ! |
variables = unlist(requiredVars), |
| 93 | ! |
format = "draws_matrix" |
| 94 |
) |
|
| 95 | ! |
draw_id <- sample.int(nrow(draws), n_patients, replace = FALSE) |
| 96 | ||
| 97 | ! |
long_models <- list() |
| 98 | ! |
surv_models <- list() |
| 99 | ! |
for (i in seq.int(n_patients)) {
|
| 100 | ! |
draw <- draws[draw_id[i], ] |
| 101 | ! |
long_models[[i]] <- createLongitudinalSimObject( |
| 102 | ! |
object@model@longitudinal, |
| 103 | ! |
draw, |
| 104 | ! |
times = add_jitter(times, jitter_var = jitter_var), |
| 105 | ! |
scaled_variance = scaled_variance |
| 106 |
) |
|
| 107 | ! |
surv_models[[i]] <- createSurvivalSimObject( |
| 108 | ! |
object@model@survival, |
| 109 | ! |
draw, |
| 110 | ! |
lambda_censor = lambda_censor, |
| 111 | ! |
time_step = time_step, |
| 112 | ! |
time_max = time_max |
| 113 |
) |
|
| 114 |
} |
|
| 115 | ||
| 116 | ! |
SimJointDataResults( |
| 117 | ! |
subject = subject_data, |
| 118 | ! |
surv_formula = object@data@survival@formula, |
| 119 | ! |
longitudinal = long_models, |
| 120 | ! |
survival = surv_models |
| 121 |
) |
|
| 122 |
} |
|
| 123 | ||
| 124 | ||
| 125 |
add_jitter <- function(times, jitter_var = c(0, 0)) {
|
|
| 126 | ! |
stopifnot(all(jitter_var >= 0)) |
| 127 | ! |
if (jitter_var[1] > 0) {
|
| 128 | ! |
neg_times <- times < 0 |
| 129 | ! |
times[neg_times] <- pmin( |
| 130 | ! |
times[neg_times] + rnorm(sum(neg_times), sd = sqrt(jitter_var[1])), |
| 131 | ! |
-.Machine$double.eps / 2 |
| 132 |
) |
|
| 133 |
} |
|
| 134 | ! |
if (jitter_var[2] > 0) {
|
| 135 | ! |
pos_times <- times > 0 |
| 136 | ! |
times[pos_times] <- pmax( |
| 137 | ! |
times[pos_times] + rnorm(sum(pos_times), sd = sqrt(jitter_var[2])), |
| 138 | ! |
.Machine$double.eps / 2 |
| 139 |
) |
|
| 140 |
} |
|
| 141 | ! |
times |
| 142 |
} |
|
| 143 | ||
| 144 | ||
| 145 |
# Longitudinal Sim Object constructors -------- |
|
| 146 |
#' @noRd |
|
| 147 |
#' |
|
| 148 |
#' @returns A `SimLongitudinal` object configured from the posterior draw. |
|
| 149 |
createLongitudinalSimObject <- function(object, draw, ...) {
|
|
| 150 | 4x |
UseMethod("createLongitudinalSimObject")
|
| 151 |
} |
|
| 152 | ||
| 153 |
#' Get draws value by name |
|
| 154 |
#' @typed draws: matrix |
|
| 155 |
#' the draws input. |
|
| 156 |
#' @typed name: character |
|
| 157 |
#' to match column names of `draws` with [startsWith]. |
|
| 158 |
#' @return A vector of matching values or `NULL` if no match. |
|
| 159 |
#' @noRd |
|
| 160 |
get_vars <- function(draws, name) {
|
|
| 161 | 70x |
result <- as.numeric(draws[, startsWith(colnames(draws), name)]) |
| 162 | 70x |
if (length(result) == 0) {
|
| 163 | 16x |
NULL |
| 164 |
} else {
|
|
| 165 | 54x |
result |
| 166 |
} |
|
| 167 |
} |
|
| 168 | ||
| 169 |
#' @exportS3Method |
|
| 170 |
#' |
|
| 171 |
#' @returns A `SimLongitudinal` object configured from the posterior draw. |
|
| 172 |
createLongitudinalSimObject.LongitudinalRandomSlope <- function( |
|
| 173 |
object, |
|
| 174 |
draw, |
|
| 175 |
... |
|
| 176 |
) {
|
|
| 177 | ! |
args <- list(...) |
| 178 | ||
| 179 | ! |
args$intercept <- get_vars(draw, "lm_rs_intercept") |
| 180 | ! |
args$slope_mu <- get_vars(draw, "lm_rs_slope_mu") |
| 181 | ! |
args$slope_sigma <- get_vars(draw, "lm_rs_slope_sigma") |
| 182 | ! |
args$sigma <- get_vars(draw, "lm_rs_sigma") |
| 183 | ! |
args$link_dsld <- get_vars(draw, "link_dsld") |
| 184 | ! |
args$link_identity <- get_vars(draw, "link_identity") |
| 185 | ! |
args$scaled_variance <- NULL # not defined for random slope |
| 186 | ! |
do.call(SimLongitudinalRandomSlope, args) |
| 187 |
} |
|
| 188 | ||
| 189 |
#' @exportS3Method |
|
| 190 |
#' |
|
| 191 |
#' @returns A `SimLongitudinal` object configured from the posterior draw. |
|
| 192 |
createLongitudinalSimObject.LongitudinalRandomSlopeCov <- function( |
|
| 193 |
object, |
|
| 194 |
draw, |
|
| 195 |
... |
|
| 196 |
) {
|
|
| 197 | 1x |
args <- list(...) |
| 198 | 1x |
parameter_names <- c("mu", "slope_mu", "slope_sigma")
|
| 199 | 1x |
for (name in parameter_names) {
|
| 200 | 3x |
args[[paste0(name, "_formula")]] <- slot( |
| 201 | 3x |
object, |
| 202 | 3x |
paste0(name, "_formula") |
| 203 |
) |
|
| 204 | 3x |
args[[paste0(name, "_parametrization")]] <- slot( |
| 205 | 3x |
object, |
| 206 | 3x |
paste0(name, "_parametrization") |
| 207 |
) |
|
| 208 | 3x |
args[[paste0(name, "_intercept")]] <- get_vars( |
| 209 | 3x |
draw, |
| 210 | 3x |
paste0("lm_rsc_", name, "_intercept")
|
| 211 |
) |
|
| 212 | 3x |
args[[paste0(name, "_coefficients")]] <- get_vars( |
| 213 | 3x |
draw, |
| 214 | 3x |
paste0("lm_rsc_", name, "_coefficients")
|
| 215 |
) |
|
| 216 |
} |
|
| 217 | 1x |
args$sigma <- get_vars(draw, "lm_rsc_sigma") |
| 218 | 1x |
args$link_dsld <- get_vars(draw, "link_dsld") |
| 219 | 1x |
args$link_identity <- get_vars(draw, "link_identity") |
| 220 | 1x |
do.call(SimLongitudinalRandomSlopeCov, args) |
| 221 |
} |
|
| 222 | ||
| 223 |
#' @exportS3Method |
|
| 224 |
#' |
|
| 225 |
#' @returns A `SimLongitudinal` object configured from the posterior draw. |
|
| 226 |
createLongitudinalSimObject.LongitudinalSteinFojo <- function( |
|
| 227 |
object, |
|
| 228 |
draw, |
|
| 229 |
... |
|
| 230 |
) {
|
|
| 231 | ! |
args <- list(...) |
| 232 | ! |
args$sigma <- get_vars(draw, "lm_sf_sigma") |
| 233 | ! |
args$mu_s <- get_vars(draw, "lm_sf_mu_ks") |
| 234 | ! |
args$mu_g <- get_vars(draw, "lm_sf_mu_kg") |
| 235 | ! |
args$mu_b <- get_vars(draw, "lm_sf_mu_bsld") |
| 236 | ! |
args$omega_s <- get_vars(draw, "lm_sf_omega_ks") |
| 237 | ! |
args$omega_g <- get_vars(draw, "lm_sf_omega_kg") |
| 238 | ! |
args$omega_b <- get_vars(draw, "lm_sf_omega_bsld") |
| 239 | ! |
args$link_dsld <- get_vars(draw, "link_dsld") |
| 240 | ! |
args$link_ttg <- get_vars(draw, "link_ttg") |
| 241 | ! |
args$link_identity <- get_vars(draw, "link_identity") |
| 242 | ! |
args$link_growth <- get_vars(draw, "link_growth") |
| 243 | ! |
args$link_shrinkage <- get_vars(draw, "link_shrinkage") |
| 244 | ! |
do.call(SimLongitudinalSteinFojo, args) |
| 245 |
} |
|
| 246 | ||
| 247 |
#' @exportS3Method |
|
| 248 |
#' |
|
| 249 |
#' @returns A `SimLongitudinal` object configured from the posterior draw. |
|
| 250 |
createLongitudinalSimObject.LongitudinalSteinFojoCov <- function( |
|
| 251 |
object, |
|
| 252 |
draw, |
|
| 253 |
... |
|
| 254 |
) {
|
|
| 255 | 1x |
args <- list(...) |
| 256 | 1x |
parameter_names <- c( |
| 257 | 1x |
"mu_b", |
| 258 | 1x |
"omega_b", |
| 259 | 1x |
"mu_s", |
| 260 | 1x |
"omega_s", |
| 261 | 1x |
"mu_g", |
| 262 | 1x |
"omega_g" |
| 263 |
) |
|
| 264 | 1x |
for (name in parameter_names) {
|
| 265 | 6x |
args[[paste0(name, "_formula")]] <- slot( |
| 266 | 6x |
object, |
| 267 | 6x |
paste0(name, "_formula") |
| 268 |
) |
|
| 269 | 6x |
args[[paste0(name, "_parametrization")]] <- slot( |
| 270 | 6x |
object, |
| 271 | 6x |
paste0(name, "_parametrization") |
| 272 |
) |
|
| 273 | 6x |
args[[paste0(name, "_intercept")]] <- get_vars( |
| 274 | 6x |
draw, |
| 275 | 6x |
paste0("lm_sfc_", name, "_intercept")
|
| 276 |
) |
|
| 277 | 6x |
args[[paste0(name, "_coefficients")]] <- get_vars( |
| 278 | 6x |
draw, |
| 279 | 6x |
paste0("lm_sfc_", name, "_coefficients")
|
| 280 |
) |
|
| 281 |
} |
|
| 282 | 1x |
args$sigma <- get_vars(draw, "lm_sfc_sigma") |
| 283 | 1x |
args$link_dsld <- get_vars(draw, "link_dsld") |
| 284 | 1x |
args$link_ttg <- get_vars(draw, "link_ttg") |
| 285 | 1x |
args$link_identity <- get_vars(draw, "link_identity") |
| 286 | 1x |
args$link_growth <- get_vars(draw, "link_growth") |
| 287 | 1x |
args$link_shrinkage <- get_vars(draw, "link_shrinkage") |
| 288 | 1x |
do.call(SimLongitudinalSteinFojoCov, args) |
| 289 |
} |
|
| 290 | ||
| 291 |
#' @exportS3Method |
|
| 292 |
#' |
|
| 293 |
#' @returns A `SimLongitudinal` object configured from the posterior draw. |
|
| 294 |
createLongitudinalSimObject.LongitudinalGSF <- function(object, draw, ...) {
|
|
| 295 | ! |
args <- list(...) |
| 296 | ! |
args$sigma <- get_vars(draw, "lm_gsf_sigma") |
| 297 | ! |
args$mu_s <- get_vars(draw, "lm_gsf_mu_ks") |
| 298 | ! |
args$mu_g <- get_vars(draw, "lm_gsf_mu_kg") |
| 299 | ! |
args$mu_b <- get_vars(draw, "lm_gsf_mu_bsld") |
| 300 | ! |
args$mu_phi <- get_vars(draw, "lm_gsf_mu_phi") |
| 301 | ! |
args$omega_s <- get_vars(draw, "lm_gsf_omega_ks") |
| 302 | ! |
args$omega_g <- get_vars(draw, "lm_gsf_omega_kg") |
| 303 | ! |
args$omega_b <- get_vars(draw, "lm_gsf_omega_bsld") |
| 304 | ! |
args$omega_phi <- get_vars(draw, "lm_gsf_omega_phi") |
| 305 | ! |
args$link_dsld <- get_vars(draw, "link_dsld") |
| 306 | ! |
args$link_ttg <- get_vars(draw, "link_ttg") |
| 307 | ! |
args$link_identity <- get_vars(draw, "link_identity") |
| 308 | ! |
args$link_growth <- get_vars(draw, "link_growth") |
| 309 | ! |
args$link_shrinkage <- get_vars(draw, "link_shrinkage") |
| 310 | ||
| 311 | ! |
do.call(SimLongitudinalGSF, args) |
| 312 |
} |
|
| 313 | ||
| 314 |
#' @exportS3Method |
|
| 315 |
createLongitudinalSimObject.LongitudinalGSFCov <- function(object, draw, ...) {
|
|
| 316 | 1x |
args <- list(...) |
| 317 | 1x |
parameter_names <- c( |
| 318 | 1x |
"mu_b", |
| 319 | 1x |
"omega_b", |
| 320 | 1x |
"mu_s", |
| 321 | 1x |
"omega_s", |
| 322 | 1x |
"mu_g", |
| 323 | 1x |
"omega_g", |
| 324 | 1x |
"mu_phi", |
| 325 | 1x |
"omega_phi" |
| 326 |
) |
|
| 327 | 1x |
for (name in parameter_names) {
|
| 328 | 8x |
args[[paste0(name, "_formula")]] <- slot( |
| 329 | 8x |
object, |
| 330 | 8x |
paste0(name, "_formula") |
| 331 |
) |
|
| 332 | 8x |
args[[paste0(name, "_parametrization")]] <- slot( |
| 333 | 8x |
object, |
| 334 | 8x |
paste0(name, "_parametrization") |
| 335 |
) |
|
| 336 | 8x |
args[[paste0(name, "_intercept")]] <- get_vars( |
| 337 | 8x |
draw, |
| 338 | 8x |
paste0("lm_gsfc_", name, "_intercept")
|
| 339 |
) |
|
| 340 | 8x |
args[[paste0(name, "_coefficients")]] <- get_vars( |
| 341 | 8x |
draw, |
| 342 | 8x |
paste0("lm_gsfc_", name, "_coefficients")
|
| 343 |
) |
|
| 344 |
} |
|
| 345 | 1x |
args$sigma <- get_vars(draw, "lm_gsfc_sigma") |
| 346 | 1x |
for (name in c("dsld", "ttg", "identity", "growth", "shrinkage")) {
|
| 347 | 5x |
args[[paste0("link_", name)]] <- get_vars(draw, paste0("link_", name))
|
| 348 |
} |
|
| 349 | 1x |
do.call(SimLongitudinalGSFCov, args) |
| 350 |
} |
|
| 351 | ||
| 352 |
#' @exportS3Method |
|
| 353 |
#' |
|
| 354 |
#' @returns A `SimLongitudinal` object configured from the posterior draw. |
|
| 355 |
createLongitudinalSimObject.LongitudinalClaretBruno <- function( |
|
| 356 |
object, |
|
| 357 |
draw, |
|
| 358 |
... |
|
| 359 |
) {
|
|
| 360 | ! |
args <- list(...) |
| 361 | ! |
args$sigma <- get_vars(draw, "lm_clbr_sigma") |
| 362 | ! |
args$mu_b <- get_vars(draw, "lm_clbr_mu_b") |
| 363 | ! |
args$mu_g <- get_vars(draw, "lm_clbr_mu_g") |
| 364 | ! |
args$mu_c <- get_vars(draw, "lm_clbr_mu_c") |
| 365 | ! |
args$mu_p <- get_vars(draw, "lm_clbr_mu_p") |
| 366 | ! |
args$omega_b <- get_vars(draw, "lm_clbr_omega_b") |
| 367 | ! |
args$omega_g <- get_vars(draw, "lm_clbr_omega_g") |
| 368 | ! |
args$omega_c <- get_vars(draw, "lm_clbr_omega_c") |
| 369 | ! |
args$omega_p <- get_vars(draw, "lm_clbr_omega_p") |
| 370 | ! |
args$link_dsld <- get_vars(draw, "link_dsld") |
| 371 | ! |
args$link_ttg <- get_vars(draw, "link_ttg") |
| 372 | ! |
args$link_identity <- get_vars(draw, "link_identity") |
| 373 | ! |
args$link_growth <- get_vars(draw, "link_growth") |
| 374 | ! |
args$link_shrinkage <- get_vars(draw, "link_shrinkage") |
| 375 | ! |
do.call(SimLongitudinalClaretBruno, args) |
| 376 |
} |
|
| 377 | ||
| 378 |
#' @exportS3Method |
|
| 379 |
#' |
|
| 380 |
#' @returns A `SimLongitudinal` object configured from the posterior draw. |
|
| 381 |
createLongitudinalSimObject.LongitudinalClaretBrunoCov <- function( |
|
| 382 |
object, |
|
| 383 |
draw, |
|
| 384 |
... |
|
| 385 |
) {
|
|
| 386 | 1x |
args <- list(...) |
| 387 | 1x |
parameter_names <- c( |
| 388 | 1x |
"mu_b", |
| 389 | 1x |
"omega_b", |
| 390 | 1x |
"mu_g", |
| 391 | 1x |
"omega_g", |
| 392 | 1x |
"mu_c", |
| 393 | 1x |
"omega_c", |
| 394 | 1x |
"mu_p", |
| 395 | 1x |
"omega_p" |
| 396 |
) |
|
| 397 | 1x |
for (name in parameter_names) {
|
| 398 | 8x |
args[[paste0(name, "_formula")]] <- slot( |
| 399 | 8x |
object, |
| 400 | 8x |
paste0(name, "_formula") |
| 401 |
) |
|
| 402 | 8x |
args[[paste0(name, "_parametrization")]] <- slot( |
| 403 | 8x |
object, |
| 404 | 8x |
paste0(name, "_parametrization") |
| 405 |
) |
|
| 406 | 8x |
args[[paste0(name, "_intercept")]] <- get_vars( |
| 407 | 8x |
draw, |
| 408 | 8x |
paste0("lm_clbrc_", name, "_intercept")
|
| 409 |
) |
|
| 410 | 8x |
args[[paste0(name, "_coefficients")]] <- get_vars( |
| 411 | 8x |
draw, |
| 412 | 8x |
paste0("lm_clbrc_", name, "_coefficients")
|
| 413 |
) |
|
| 414 |
} |
|
| 415 | 1x |
args$sigma <- get_vars(draw, "lm_clbrc_sigma") |
| 416 | 1x |
for (name in c("dsld", "ttg", "identity", "growth")) {
|
| 417 | 4x |
args[[paste0("link_", name)]] <- get_vars(draw, paste0("link_", name))
|
| 418 |
} |
|
| 419 | 1x |
do.call(SimLongitudinalClaretBrunoCov, args) |
| 420 |
} |
|
| 421 | ||
| 422 | ||
| 423 |
# Survival Sim Object constructors -------- |
|
| 424 | ||
| 425 |
#' @noRd |
|
| 426 |
#' |
|
| 427 |
#' @returns A `SimSurvival` object configured from the posterior draw. |
|
| 428 |
createSurvivalSimObject <- function(object, draw, ...) {
|
|
| 429 | ! |
UseMethod("createSurvivalSimObject")
|
| 430 |
} |
|
| 431 | ||
| 432 |
#' @exportS3Method |
|
| 433 |
#' |
|
| 434 |
#' @returns A `SimSurvival` object configured from the posterior draw. |
|
| 435 |
createSurvivalSimObject.SurvivalWeibullPH <- function(object, draw, ...) {
|
|
| 436 | ! |
args <- list(...) |
| 437 | ! |
args$lambda <- get_vars(draw, "sm_weibull_ph_lambda") |
| 438 | ! |
args$gamma <- get_vars(draw, "sm_weibull_ph_gamma") |
| 439 | ||
| 440 | ! |
result <- do.call(SimSurvivalWeibullPH, args) |
| 441 | ! |
result@beta_os_cov <- get_vars(draw, "beta_os_cov") |
| 442 | ! |
result |
| 443 |
} |
|
| 444 | ||
| 445 |
#' @exportS3Method |
|
| 446 |
#' |
|
| 447 |
#' @returns A `SimSurvival` object configured from the posterior draw. |
|
| 448 |
createSurvivalSimObject.SurvivalExponential <- function(object, draw, ...) {
|
|
| 449 | ! |
args <- list(...) |
| 450 | ! |
args$lambda <- get_vars(draw, "sm_exp_lambda") |
| 451 | ||
| 452 | ! |
result <- do.call(SimSurvivalExponential, args) |
| 453 | ! |
result@beta_os_cov <- get_vars(draw, "beta_os_cov") |
| 454 | ! |
result |
| 455 |
} |
|
| 456 | ||
| 457 |
#' @exportS3Method |
|
| 458 |
#' |
|
| 459 |
#' @returns A `SimSurvival` object configured from the posterior draw. |
|
| 460 |
createSurvivalSimObject.SurvivalGamma <- function(object, draw, ...) {
|
|
| 461 | ! |
lapply(object@model@survival@parameters@parameters, function(x) x@name) |
| 462 | ! |
lapply(object@model@link@components, function(x) x@key) |
| 463 | ! |
args <- list(...) |
| 464 | ! |
args$k <- get_vars(draw, "sm_gamma_k") |
| 465 | ! |
args$theta <- get_vars(draw, "sm_gamma_theta") |
| 466 | ||
| 467 | ! |
result <- do.call(SimSurvivalGamma, args) |
| 468 | ! |
result@beta_os_cov <- get_vars(draw, "beta_os_cov") |
| 469 | ! |
result |
| 470 |
} |
|
| 471 | ||
| 472 |
#' @exportS3Method |
|
| 473 |
#' |
|
| 474 |
#' @returns A `SimSurvival` object configured from the posterior draw. |
|
| 475 |
createSurvivalSimObject.SurvivalLogLogistic <- function(object, draw, ...) {
|
|
| 476 | ! |
lapply(object@model@survival@parameters@parameters, function(x) x@name) |
| 477 | ! |
lapply(object@model@link@components, function(x) x@key) |
| 478 | ! |
args <- list(...) |
| 479 | ! |
args$a <- get_vars(draw, "sm_loglogis_a") |
| 480 | ! |
args$b <- get_vars(draw, "sm_loglogis_b") |
| 481 | ||
| 482 | ! |
result <- do.call(SimSurvivalLogLogistic, args) |
| 483 | ! |
result@beta_os_cov <- get_vars(draw, "beta_os_cov") |
| 484 | ! |
result |
| 485 |
} |
|
| 486 | ||
| 487 | ||
| 488 |
sampleSubjectsFromObs <- function(object, subjects_df, covs_matrix) {
|
|
| 489 | 1x |
subjects_df$log_haz_cov <- rowSums( |
| 490 | 1x |
covs_matrix[, -1] * t(sapply(object, function(x) x@beta_os_cov)) |
| 491 |
) |
|
| 492 | 1x |
subjects_df |> |
| 493 | 1x |
dplyr::mutate(survival = stats::runif(dplyr::n())) |> |
| 494 | 1x |
dplyr::mutate(chazard_limit = -log(.data$survival)) |> |
| 495 | 1x |
dplyr::mutate( |
| 496 | 1x |
time_cen = stats::rexp( |
| 497 | 1x |
dplyr::n(), |
| 498 | 1x |
sapply(object, function(x) x@lambda_censor) |
| 499 |
) |
|
| 500 |
) |
|
| 501 |
} |
|
| 502 | ||
| 503 |
# Simulate patient function ------ |
|
| 504 |
SimJointDataResults <- function( |
|
| 505 |
subject, |
|
| 506 |
surv_formula, |
|
| 507 |
longitudinal, |
|
| 508 |
survival, |
|
| 509 |
.silent = FALSE |
|
| 510 |
) {
|
|
| 511 |
# take hazard windows from first element. All have the same |
|
| 512 |
# @time_step and @time_max parameters |
|
| 513 | 1x |
hazard_evaluation_info <- hazardWindows(survival[[1]]) |
| 514 | ||
| 515 | 1x |
group_counts <- as.data.frame(table(subject@data[, c( |
| 516 | 1x |
subject@study, |
| 517 | 1x |
subject@arm |
| 518 |
)])) |
|
| 519 | ||
| 520 | 1x |
n_group <- group_counts$Freq |
| 521 | 1x |
arms <- group_counts[, 2] |
| 522 | 1x |
studies <- group_counts[, 1] |
| 523 | 1x |
n_subjects <- sum(n_group) |
| 524 | 1x |
n_times <- length(hazard_evaluation_info$midpoint) |
| 525 | ||
| 526 | 1x |
baseline <- data.frame( |
| 527 | 1x |
subject = subject@data[[subject@subject]], |
| 528 | 1x |
study = subject@data[[subject@study]], |
| 529 | 1x |
arm = subject@data[[subject@arm]] |
| 530 |
) |
|
| 531 | 1x |
additional_subject_columns <- setdiff( |
| 532 | 1x |
names(subject@data), |
| 533 | 1x |
c(subject@subject, subject@study, subject@arm) |
| 534 |
) |
|
| 535 | 1x |
baseline <- cbind( |
| 536 | 1x |
baseline, |
| 537 | 1x |
subject@data[, additional_subject_columns, drop = FALSE] |
| 538 |
) |
|
| 539 | ||
| 540 | 1x |
cov_cols <- cbind( |
| 541 | 1x |
data.frame(subject = subject@data[[subject@subject]]), |
| 542 | 1x |
subject@data[, |
| 543 | 1x |
all.vars(delete.response(terms(surv_formula))), |
| 544 | 1x |
drop = FALSE |
| 545 |
] |
|
| 546 |
) |
|
| 547 | ||
| 548 | 1x |
os_baseline <- sampleSubjectsFromObs( |
| 549 | 1x |
survival, |
| 550 | 1x |
subjects_df = baseline, |
| 551 | 1x |
model.matrix(delete.response(terms(surv_formula)), subject@data) |
| 552 |
) |
|
| 553 | ||
| 554 | 1x |
lm_baseline <- dplyr::bind_rows( |
| 555 | 1x |
lapply(seq.int(longitudinal), function(i) {
|
| 556 | 2x |
sampleSubjects(longitudinal[[i]], subjects_df = baseline[i, ]) |
| 557 |
}) |
|
| 558 |
) |
|
| 559 | ||
| 560 | 1x |
lm_times <- lapply(seq.int(longitudinal), function(i) {
|
| 561 | 2x |
longitudinal[[i]]@times |
| 562 |
}) |
|
| 563 | ||
| 564 | 1x |
lm_dat_no_obvs <- |
| 565 | 1x |
cbind( |
| 566 | 1x |
baseline[rep(seq.int(nrow(baseline)), times = lengths(lm_times)), ], |
| 567 | 1x |
time = unlist(lm_times) |
| 568 |
) |> |
|
| 569 | 1x |
dplyr::left_join(lm_baseline, by = c("subject", "study", "arm"))
|
| 570 | ||
| 571 | 1x |
lm_dat <- dplyr::bind_rows( |
| 572 | 1x |
.mapply( |
| 573 | 1x |
sampleObservations, |
| 574 | 1x |
dots = list( |
| 575 | 1x |
times_df = split(lm_dat_no_obvs, ~subject), |
| 576 | 1x |
object = longitudinal |
| 577 |
), |
|
| 578 | 1x |
MoreArgs = NULL |
| 579 |
) |
|
| 580 |
) |
|
| 581 | ||
| 582 | 1x |
hazard_eval_df <- dplyr::tibble( |
| 583 | 1x |
subject = rep(lm_baseline$subject, each = n_times), |
| 584 | 1x |
arm = rep(lm_baseline$arm, each = n_times), |
| 585 | 1x |
study = rep(lm_baseline$study, each = n_times), |
| 586 | 1x |
midpoint = rep( |
| 587 | 1x |
as.double(hazard_evaluation_info$midpoint), |
| 588 | 1x |
times = n_subjects |
| 589 |
), |
|
| 590 | 1x |
time = rep(as.double(hazard_evaluation_info$upper), times = n_subjects), |
| 591 | 1x |
width = rep(as.double(hazard_evaluation_info$width), times = n_subjects) |
| 592 |
) |
|
| 593 | ||
| 594 | 1x |
lm_link_dat <- dplyr::bind_rows( |
| 595 | 1x |
.mapply( |
| 596 | 1x |
sampleObservations, |
| 597 | 1x |
dots = list( |
| 598 | 1x |
times_df = split( |
| 599 | 1x |
dplyr::left_join( |
| 600 | 1x |
hazard_eval_df, |
| 601 | 1x |
lm_baseline, |
| 602 | 1x |
by = c("subject", "study", "arm")
|
| 603 |
), |
|
| 604 | 1x |
~subject |
| 605 |
), |
|
| 606 | 1x |
object = longitudinal |
| 607 |
), |
|
| 608 | 1x |
MoreArgs = NULL |
| 609 |
) |
|
| 610 | 1x |
)[, c( |
| 611 | 1x |
"subject", |
| 612 | 1x |
"study", |
| 613 | 1x |
"arm", |
| 614 | 1x |
"log_haz_link", |
| 615 | 1x |
"time", |
| 616 | 1x |
"width", |
| 617 | 1x |
"midpoint" |
| 618 |
)] |
|
| 619 | ||
| 620 | 1x |
os_eval_df <- lm_link_dat |> |
| 621 | 1x |
dplyr::left_join(os_baseline, by = c("subject", "study", "arm"))
|
| 622 | ||
| 623 | 1x |
times_df <- split(os_eval_df, ~subject) |
| 624 | 1x |
os_data_list <- list() |
| 625 | 1x |
for (i in seq_along(times_df)) {
|
| 626 | 2x |
times_df[[i]]$log_bl_haz <- survival[[i]]@loghazard( |
| 627 | 2x |
times_df[[i]]$midpoint |
| 628 |
) |
|
| 629 |
} |
|
| 630 | 1x |
os_dat <- sampleObservations2(dplyr::bind_rows(times_df)) |
| 631 | ||
| 632 | 1x |
os_dat <- dplyr::left_join(os_dat, cov_cols, by = "subject") |
| 633 | ||
| 634 | 1x |
lm_dat2 <- lm_dat |> |
| 635 | 1x |
dplyr::left_join( |
| 636 | 1x |
dplyr::select(os_dat, "subject", os_time = "time"), |
| 637 | 1x |
by = "subject" |
| 638 |
) |> |
|
| 639 | 1x |
dplyr::mutate(observed = (.data$time <= .data$os_time)) |> |
| 640 | 1x |
dplyr::arrange(dplyr::pick(c("subject", "time")))
|
| 641 | ||
| 642 | 1x |
assert_that( |
| 643 | 1x |
length(unique(os_dat$subject)) == length(os_dat$subject), |
| 644 | 1x |
length(os_dat$subject) == n_subjects, |
| 645 | 1x |
all(os_dat$time >= 0), |
| 646 | 1x |
all(os_dat$event %in% c(0, 1)), |
| 647 | 1x |
msg = "Assumptions for the Survival data are not met (please report this issue)" |
| 648 |
) |
|
| 649 | ||
| 650 | 1x |
assert_that( |
| 651 | 1x |
nrow(lm_dat2) == n_subjects * length(longitudinal[[1]]@times), |
| 652 | 1x |
length(unique(lm_dat2$subject)) == n_subjects, |
| 653 | 1x |
msg = "Assumptions for the Longitudinal data are not met (please report this issue)" |
| 654 |
) |
|
| 655 | ||
| 656 | 1x |
original_names <- c("subject", "arm", "study")
|
| 657 | 1x |
names(original_names) <- c(subject@subject, subject@arm, subject@study) |
| 658 | ||
| 659 | 1x |
return( |
| 660 | 1x |
.SimJointData( |
| 661 | 1x |
survival = os_dat |> dplyr::rename(dplyr::all_of(original_names)), |
| 662 | 1x |
longitudinal = lm_dat2[, c( |
| 663 | 1x |
"subject", |
| 664 | 1x |
"arm", |
| 665 | 1x |
"study", |
| 666 | 1x |
"time", |
| 667 | 1x |
"sld", |
| 668 | 1x |
"observed" |
| 669 |
)] |> |
|
| 670 | 1x |
dplyr::rename(dplyr::all_of(original_names)) |
| 671 |
) |
|
| 672 |
) |
|
| 673 |
} |
|
| 674 | ||
| 675 | ||
| 676 |
# adapted from sampleObservations so that $midpoint and $log_bl_haz are already in times_df. |
|
| 677 |
sampleObservations2 <- function(times_df) {
|
|
| 678 | 1x |
assert_that( |
| 679 | 1x |
all(times_df$time >= 0), |
| 680 | 1x |
msg = "All time points must be greater than or equal to 0" |
| 681 |
) |
|
| 682 | ||
| 683 | 1x |
os_dat_chaz <- times_df |> |
| 684 |
# Fix to avoid issue with log(0) = NaN values |
|
| 685 | 1x |
dplyr::mutate( |
| 686 | 1x |
log_bl_haz = dplyr::if_else( |
| 687 | 1x |
.data$midpoint == 0, |
| 688 | 1x |
-999, |
| 689 | 1x |
.data$log_bl_haz |
| 690 |
), |
|
| 691 | 1x |
hazard_instant = exp( |
| 692 | 1x |
.data$log_bl_haz + .data$log_haz_cov + .data$log_haz_link |
| 693 |
), |
|
| 694 |
# Reset Inf values to large number to avoid NaN issues downstream |
|
| 695 |
# This is suitable as Hazard limits tend to be in the range of -10 to 10 so large numbers |
|
| 696 |
# are essentially equivalent to infinity for simulation purposes |
|
| 697 | 1x |
hazard_instant = dplyr::if_else( |
| 698 | 1x |
.data$hazard_instant == Inf, |
| 699 | 1x |
999, |
| 700 | 1x |
.data$hazard_instant |
| 701 |
), |
|
| 702 | 1x |
hazard_instant = dplyr::if_else( |
| 703 | 1x |
.data$hazard_instant == -Inf, |
| 704 | 1x |
-999, |
| 705 | 1x |
.data$hazard_instant |
| 706 |
), |
|
| 707 | 1x |
hazard_interval = .data$hazard_instant * .data$width |
| 708 |
) |> |
|
| 709 | 1x |
dplyr::mutate(chazard = cumsum(.data$hazard_interval), .by = "subject") |
| 710 | ||
| 711 | 1x |
os_had_event <- os_dat_chaz |> |
| 712 | 1x |
dplyr::filter(.data$chazard >= .data$chazard_limit) |> |
| 713 | 1x |
dplyr::slice_head(by = "subject") |> |
| 714 | 1x |
dplyr::mutate(event = 1) |
| 715 | ||
| 716 | 1x |
os_had_censor <- os_dat_chaz |> |
| 717 | 1x |
dplyr::filter(!.data$subject %in% os_had_event$subject) |> |
| 718 | 1x |
dplyr::slice_tail(by = "subject") |> |
| 719 | ||
| 720 | 1x |
dplyr::mutate(event = 0) |
| 721 | ||
| 722 | 1x |
if (!(nrow(os_had_censor) == 0)) {
|
| 723 | ! |
message(sprintf( |
| 724 | ! |
"INFO: %i subject(s) did not die before max(times)", |
| 725 | ! |
nrow(os_had_censor) |
| 726 |
)) |
|
| 727 |
} |
|
| 728 | ||
| 729 | 1x |
os_dat_complete <- os_had_event |> |
| 730 | 1x |
dplyr::bind_rows(os_had_censor) |> |
| 731 | 1x |
dplyr::mutate( |
| 732 | 1x |
real_time = .data$time, |
| 733 | 1x |
event = dplyr::if_else( |
| 734 | 1x |
.data$real_time <= .data$time_cen, |
| 735 | 1x |
.data$event, |
| 736 | 1x |
0 |
| 737 |
), |
|
| 738 | 1x |
time = dplyr::if_else( |
| 739 | 1x |
.data$real_time <= .data$time_cen, |
| 740 | 1x |
.data$real_time, |
| 741 | 1x |
.data$time_cen |
| 742 |
) |
|
| 743 |
) |> |
|
| 744 | 1x |
dplyr::arrange(.data$subject) |
| 745 | ||
| 746 | 1x |
keep_cols <- colnames(os_dat_complete) %in% |
| 747 | 1x |
c("subject", "study", "arm", "time", "event", "cov_cont", "cov_cat")
|
| 748 | 1x |
os_dat_complete[, keep_cols] |
| 749 |
} |
| 1 |
#' @include LongitudinalRandomSlope.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
#' Random-slope longitudinal model with subject-level covariates |
|
| 5 |
#' |
|
| 6 |
#' Extends the random-slope longitudinal model by modelling the subject |
|
| 7 |
#' intercept, mean slope, and random-slope standard deviation using separate |
|
| 8 |
#' subject-level design matrices. |
|
| 9 |
#' |
|
| 10 |
#' @section Available Links: |
|
| 11 |
#' - [`linkDSLD()`] |
|
| 12 |
#' - [`linkIdentity()`] |
|
| 13 |
#' - [`linkGrowth()`] |
|
| 14 |
#' |
|
| 15 |
#' @slot mu_formula (`formula`) Formula for the subject intercept. |
|
| 16 |
#' @slot slope_mu_formula (`formula`) Formula for the mean slope. |
|
| 17 |
#' @slot slope_sigma_formula (`formula`) Formula for the random-slope standard deviation. |
|
| 18 |
#' @slot mu_parametrization (`character`) Parametrization for the subject intercept. |
|
| 19 |
#' @slot slope_mu_parametrization (`character`) Parametrization for the mean slope. |
|
| 20 |
#' @slot slope_sigma_parametrization (`character`) Parametrization for the slope standard deviation. |
|
| 21 |
#' |
|
| 22 |
#' @exportClass LongitudinalRandomSlopeCov |
|
| 23 |
.LongitudinalRandomSlopeCov <- setClass( |
|
| 24 |
Class = "LongitudinalRandomSlopeCov", |
|
| 25 |
contains = "LongitudinalModel", |
|
| 26 |
slots = c( |
|
| 27 |
mu_formula = "formula", |
|
| 28 |
slope_mu_formula = "formula", |
|
| 29 |
slope_sigma_formula = "formula", |
|
| 30 |
mu_parametrization = "character", |
|
| 31 |
slope_mu_parametrization = "character", |
|
| 32 |
slope_sigma_parametrization = "character" |
|
| 33 |
) |
|
| 34 |
) |
|
| 35 | ||
| 36 |
#' Supported covariate-predictor parametrizations |
|
| 37 |
#' |
|
| 38 |
#' Names of the parametrizations accepted by the covariate-based longitudinal |
|
| 39 |
#' models and simulators. |
|
| 40 |
#' |
|
| 41 |
#' @keywords internal |
|
| 42 |
#' @returns A character vector. |
|
| 43 |
.longitudinal_cov_parametrizations <- c( |
|
| 44 |
"linear", |
|
| 45 |
"proportional", |
|
| 46 |
"exponential", |
|
| 47 |
"log-linear" |
|
| 48 |
) |
|
| 49 | ||
| 50 |
.longitudinal_cov_sigma_parametrizations <- c("exponential", "log-linear")
|
|
| 51 | ||
| 52 |
#' Validate a longitudinal covariate formula |
|
| 53 |
#' |
|
| 54 |
#' @param x The object to validate as a one-sided formula. |
|
| 55 |
#' @param argument Name of the user-facing argument, used in error messages. |
|
| 56 |
#' |
|
| 57 |
#' @keywords internal |
|
| 58 |
#' @returns `x`, validated as a one-sided formula. |
|
| 59 |
.validate_covariate_formula <- function(x, argument) {
|
|
| 60 | 566x |
assert_formula(x) |
| 61 | 566x |
assert_that( |
| 62 | 566x |
length(x) == 2, |
| 63 | 566x |
msg = sprintf("`%s` must be a one-sided formula", argument)
|
| 64 |
) |
|
| 65 | 565x |
x |
| 66 |
} |
|
| 67 | ||
| 68 |
#' Validate a covariate-predictor parametrization |
|
| 69 |
#' |
|
| 70 |
#' @param x Character scalar naming the parametrization. |
|
| 71 |
#' @param argument Name of the user-facing argument, used in error messages. |
|
| 72 |
#' @param scale Whether the predictor represents a standard deviation. |
|
| 73 |
#' |
|
| 74 |
#' @keywords internal |
|
| 75 |
#' @returns `x`, validated against the supported parametrizations. |
|
| 76 |
.validate_covariate_parametrization <- function(x, argument, scale = FALSE) {
|
|
| 77 | 547x |
assert_string(x, na.ok = FALSE) |
| 78 | 547x |
allowed <- if (scale) {
|
| 79 | 257x |
.longitudinal_cov_sigma_parametrizations |
| 80 |
} else {
|
|
| 81 | 290x |
.longitudinal_cov_parametrizations |
| 82 |
} |
|
| 83 | 547x |
assert_that( |
| 84 | 547x |
x %in% allowed, |
| 85 | 547x |
msg = sprintf( |
| 86 | 547x |
"`%s` must be one of %s", |
| 87 | 547x |
argument, |
| 88 | 547x |
paste( |
| 89 | 547x |
sprintf("`%s`", allowed),
|
| 90 | 547x |
collapse = ", " |
| 91 |
) |
|
| 92 |
) |
|
| 93 |
) |
|
| 94 | 530x |
x |
| 95 |
} |
|
| 96 | ||
| 97 |
#' Build a subject-level covariate design matrix |
|
| 98 |
#' |
|
| 99 |
#' Builds a model matrix and removes its intercept column. Single-level factors |
|
| 100 |
#' are supported and contribute no columns under reference coding. |
|
| 101 |
#' |
|
| 102 |
#' @param formula One-sided formula describing the subject-level covariates. |
|
| 103 |
#' @param data Data frame containing one row per subject. |
|
| 104 |
#' @param argument Name of the user-facing formula argument, used in errors. |
|
| 105 |
#' |
|
| 106 |
#' @keywords internal |
|
| 107 |
#' @returns A numeric design matrix without an intercept column. |
|
| 108 |
.covariate_design_matrix <- function(formula, data, argument = "formula") {
|
|
| 109 | 220x |
variables <- all.vars(formula) |
| 110 | 220x |
missing_variables <- setdiff(variables, names(data)) |
| 111 | 220x |
assert_that( |
| 112 | 220x |
length(missing_variables) == 0, |
| 113 | 220x |
msg = sprintf( |
| 114 | 220x |
"All variables in `%s` must be present in the subject data; missing: %s", |
| 115 | 220x |
argument, |
| 116 | 220x |
paste(missing_variables, collapse = ", ") |
| 117 |
) |
|
| 118 |
) |
|
| 119 | ||
| 120 | 218x |
model_frame <- stats::model.frame( |
| 121 | 218x |
formula, |
| 122 | 218x |
data = data, |
| 123 | 218x |
na.action = stats::na.fail |
| 124 |
) |
|
| 125 | 217x |
sentinel_level <- ".__jmpost_unused_level__" |
| 126 | 217x |
for (variable in names(model_frame)) {
|
| 127 | 218x |
values <- model_frame[[variable]] |
| 128 | 218x |
if (is.character(values)) {
|
| 129 | ! |
values <- factor(values) |
| 130 |
} |
|
| 131 | 218x |
if (is.factor(values) && nlevels(values) == 1) {
|
| 132 | 3x |
levels(values) <- c(levels(values), sentinel_level) |
| 133 |
} |
|
| 134 | 218x |
model_frame[[variable]] <- values |
| 135 |
} |
|
| 136 | 217x |
design <- stats::model.matrix(formula, data = model_frame) |
| 137 | 217x |
remove_columns <- colnames(design) == "(Intercept)" | |
| 138 | 217x |
grepl(sentinel_level, colnames(design), fixed = TRUE) |
| 139 | 217x |
if (any(remove_columns)) {
|
| 140 | 217x |
design <- design[, !remove_columns, drop = FALSE] |
| 141 |
} |
|
| 142 | 217x |
rownames(design) <- NULL |
| 143 | 217x |
design |
| 144 |
} |
|
| 145 | ||
| 146 |
#' Render a covariate predictor as Stan code |
|
| 147 |
#' |
|
| 148 |
#' @param prefix Prefix shared by the intercept and coefficient Stan variables. |
|
| 149 |
#' @param parametrization Character scalar naming the predictor |
|
| 150 |
#' parametrization. |
|
| 151 |
#' @param design_prefix Prefix for the design-matrix Stan variable. |
|
| 152 |
#' @param n_rows Stan expression giving the number of predictor rows. |
|
| 153 |
#' |
|
| 154 |
#' @keywords internal |
|
| 155 |
#' @returns A character scalar containing a Stan expression. |
|
| 156 |
.covariate_predictor_stan <- function( |
|
| 157 |
prefix, |
|
| 158 |
parametrization, |
|
| 159 |
design_prefix = prefix, |
|
| 160 |
n_rows = "n_subjects" |
|
| 161 |
) {
|
|
| 162 | 379x |
intercept <- paste0(prefix, "_intercept") |
| 163 | 379x |
design <- paste0(design_prefix, "_design") |
| 164 | 379x |
coefficients <- paste0(prefix, "_coefficients") |
| 165 | ||
| 166 |
switch( |
|
| 167 | 379x |
parametrization, |
| 168 | 207x |
linear = sprintf( |
| 169 | 207x |
"rep_vector(%s, %s) + %s * %s", |
| 170 | 207x |
intercept, |
| 171 | 207x |
n_rows, |
| 172 | 207x |
design, |
| 173 | 207x |
coefficients |
| 174 |
), |
|
| 175 | ! |
proportional = sprintf( |
| 176 | ! |
"%s * (rep_vector(1, %s) + %s * %s)", |
| 177 | ! |
intercept, |
| 178 | ! |
n_rows, |
| 179 | ! |
design, |
| 180 | ! |
coefficients |
| 181 |
), |
|
| 182 | 6x |
exponential = sprintf( |
| 183 | 6x |
"%s ^ (%s * %s)", |
| 184 | 6x |
intercept, |
| 185 | 6x |
design, |
| 186 | 6x |
coefficients |
| 187 |
), |
|
| 188 | 166x |
`log-linear` = sprintf( |
| 189 | 166x |
"exp(rep_vector(%s, %s) + %s * %s)", |
| 190 | 166x |
intercept, |
| 191 | 166x |
n_rows, |
| 192 | 166x |
design, |
| 193 | 166x |
coefficients |
| 194 |
) |
|
| 195 |
) |
|
| 196 |
} |
|
| 197 | ||
| 198 |
#' Build a covariate design matrix for new subject profiles |
|
| 199 |
#' |
|
| 200 |
#' Uses the fitted subject data to preserve factor levels, contrasts, and column |
|
| 201 |
#' order when constructing a design matrix for population predictions. |
|
| 202 |
#' |
|
| 203 |
#' @param formula One-sided formula describing the subject-level covariates. |
|
| 204 |
#' @param newdata Data frame containing the population profiles to predict. |
|
| 205 |
#' @param reference_data Subject data used to fit the model. |
|
| 206 |
#' @param argument Name of the user-facing formula argument, used in errors. |
|
| 207 |
#' |
|
| 208 |
#' @keywords internal |
|
| 209 |
#' @returns A numeric design matrix matching the fitted design matrix. |
|
| 210 |
.covariate_prediction_design_matrix <- function( |
|
| 211 |
formula, |
|
| 212 |
newdata, |
|
| 213 |
reference_data, |
|
| 214 |
argument = "formula" |
|
| 215 |
) {
|
|
| 216 | 4x |
variables <- all.vars(formula) |
| 217 | 4x |
missing_variables <- setdiff(variables, names(newdata)) |
| 218 | 4x |
assert_that( |
| 219 | 4x |
length(missing_variables) == 0, |
| 220 | 4x |
msg = sprintf( |
| 221 | 4x |
"All variables in `%s` must be present in `newdata`; missing: %s", |
| 222 | 4x |
argument, |
| 223 | 4x |
paste(missing_variables, collapse = ", ") |
| 224 |
) |
|
| 225 |
) |
|
| 226 | ||
| 227 | 4x |
reference_frame <- stats::model.frame( |
| 228 | 4x |
formula, |
| 229 | 4x |
data = reference_data, |
| 230 | 4x |
na.action = stats::na.fail |
| 231 |
) |
|
| 232 | 4x |
sentinel_level <- ".__jmpost_unused_level__" |
| 233 | 4x |
for (variable in names(reference_frame)) {
|
| 234 | 6x |
values <- reference_frame[[variable]] |
| 235 | 6x |
if (is.character(values)) {
|
| 236 | ! |
values <- factor(values) |
| 237 |
} |
|
| 238 | 6x |
if (is.factor(values) && nlevels(values) == 1) {
|
| 239 | ! |
levels(values) <- c(levels(values), sentinel_level) |
| 240 |
} |
|
| 241 | 6x |
reference_frame[[variable]] <- values |
| 242 |
} |
|
| 243 | ||
| 244 | 4x |
model_terms <- stats::terms(reference_frame) |
| 245 | 4x |
reference_design <- stats::model.matrix(model_terms, reference_frame) |
| 246 | 4x |
xlevels <- stats::.getXlevels(model_terms, reference_frame) |
| 247 | 4x |
new_frame <- stats::model.frame( |
| 248 | 4x |
model_terms, |
| 249 | 4x |
data = newdata, |
| 250 | 4x |
xlev = xlevels, |
| 251 | 4x |
na.action = stats::na.fail |
| 252 |
) |
|
| 253 | 4x |
design <- stats::model.matrix( |
| 254 | 4x |
model_terms, |
| 255 | 4x |
new_frame, |
| 256 | 4x |
contrasts.arg = attr(reference_design, "contrasts") |
| 257 |
) |
|
| 258 | 4x |
remove_columns <- colnames(design) == "(Intercept)" | |
| 259 | 4x |
grepl(sentinel_level, colnames(design), fixed = TRUE) |
| 260 | 4x |
if (any(remove_columns)) {
|
| 261 | 4x |
design <- design[, !remove_columns, drop = FALSE] |
| 262 |
} |
|
| 263 | 4x |
rownames(design) <- NULL |
| 264 | ||
| 265 | 4x |
reference_columns <- colnames(.covariate_design_matrix( |
| 266 | 4x |
formula, |
| 267 | 4x |
reference_data, |
| 268 | 4x |
argument |
| 269 |
)) |
|
| 270 | 4x |
assert_that( |
| 271 | 4x |
identical(colnames(design), reference_columns), |
| 272 | 4x |
msg = sprintf( |
| 273 | 4x |
"The design matrix for `%s` does not match the fitted model", |
| 274 | 4x |
argument |
| 275 |
) |
|
| 276 |
) |
|
| 277 | 4x |
design |
| 278 |
} |
|
| 279 | ||
| 280 |
#' Apply positivity constraints to a predictor intercept prior |
|
| 281 |
#' |
|
| 282 |
#' @param prior A [`Prior`] object for the predictor intercept. |
|
| 283 |
#' @param parametrization Character scalar naming the predictor |
|
| 284 |
#' parametrization. |
|
| 285 |
#' @param positive Whether the resulting parameter must be positive even when |
|
| 286 |
#' the parametrization itself does not require a positive intercept. |
|
| 287 |
#' |
|
| 288 |
#' @keywords internal |
|
| 289 |
#' @returns The input prior, with a positive lower limit when required. |
|
| 290 |
.positive_intercept_prior <- function( |
|
| 291 |
prior, |
|
| 292 |
parametrization, |
|
| 293 |
positive = FALSE |
|
| 294 |
) {
|
|
| 295 | 353x |
if (parametrization == "exponential" || positive) {
|
| 296 | 5x |
set_limits(prior, lower = getOption("jmpost.double_eps"))
|
| 297 |
} else {
|
|
| 298 | 348x |
prior |
| 299 |
} |
|
| 300 |
} |
|
| 301 | ||
| 302 |
#' Obtain a predictor's reference-level initial value |
|
| 303 |
#' |
|
| 304 |
#' @param prior A [`Prior`] object for the predictor intercept. |
|
| 305 |
#' @param parametrization Character scalar naming the predictor |
|
| 306 |
#' parametrization. |
|
| 307 |
#' |
|
| 308 |
#' @keywords internal |
|
| 309 |
#' @returns The median reference-level parameter value on its natural scale. |
|
| 310 |
.predictor_reference_value <- function(prior, parametrization) {
|
|
| 311 | 132x |
value <- median(prior) |
| 312 | 132x |
if (parametrization == "log-linear") {
|
| 313 | 64x |
exp(value) |
| 314 | 68x |
} else if (parametrization == "exponential") {
|
| 315 | 3x |
1 |
| 316 |
} else {
|
|
| 317 | 65x |
value |
| 318 |
} |
|
| 319 |
} |
|
| 320 | ||
| 321 |
#' Construct a random-slope model with subject-level covariates |
|
| 322 |
#' |
|
| 323 |
#' The intercept column generated by each formula is removed. The separately |
|
| 324 |
#' specified intercept parameter is the reference-level value except for the |
|
| 325 |
#' `exponential` parametrization, whose reference-level value is one. |
|
| 326 |
#' `linear` uses `intercept + X beta`, `proportional` uses |
|
| 327 |
#' `intercept * (1 + X beta)`, `exponential` uses |
|
| 328 |
#' `intercept ^ (X beta)`, and `log-linear` uses |
|
| 329 |
#' `exp(intercept + X beta)`. |
|
| 330 |
#' |
|
| 331 |
#' @rdname LongitudinalRandomSlopeCov-class |
|
| 332 |
#' @typed mu_formula: formula |
|
| 333 |
#' One-sided formula for subject-level covariate effects on the subject intercept. |
|
| 334 |
#' @typed slope_mu_formula: formula |
|
| 335 |
#' One-sided formula for subject-level covariate effects on the mean slope. |
|
| 336 |
#' @typed slope_sigma_formula: formula |
|
| 337 |
#' One-sided formula for subject-level covariate effects on the slope standard deviation. |
|
| 338 |
#' @typed mu_parametrization: character |
|
| 339 |
#' Parametrization of the subject intercept predictor. |
|
| 340 |
#' @typed slope_mu_parametrization: character |
|
| 341 |
#' Parametrization of the mean slope predictor. |
|
| 342 |
#' @typed slope_sigma_parametrization: character |
|
| 343 |
#' Parametrization of the slope standard deviation predictor. Only `"exponential"` and `"log-linear"` are supported. |
|
| 344 |
#' @typed mu_intercept_prior: Prior |
|
| 345 |
#' Prior for the intercept of the subject intercept predictor. |
|
| 346 |
#' @typed mu_coefficients_prior: Prior |
|
| 347 |
#' Prior for the covariate coefficients of the subject intercept predictor. |
|
| 348 |
#' @typed slope_mu_intercept_prior: Prior |
|
| 349 |
#' Prior for the intercept of the mean slope predictor. |
|
| 350 |
#' @typed slope_mu_coefficients_prior: Prior |
|
| 351 |
#' Prior for the covariate coefficients of the mean slope predictor. |
|
| 352 |
#' @typed slope_sigma_intercept_prior: Prior |
|
| 353 |
#' Prior for the intercept of the slope standard deviation predictor. |
|
| 354 |
#' @typed slope_sigma_coefficients_prior: Prior |
|
| 355 |
#' Prior for the covariate coefficients of the slope standard deviation predictor. |
|
| 356 |
#' @typed sigma: Prior |
|
| 357 |
#' Prior for the observation-error standard deviation. |
|
| 358 |
#' @typed scaled_variance: logical |
|
| 359 |
#' Whether to use multiplicative observation error. |
|
| 360 |
#' |
|
| 361 |
#' @returns A `LongitudinalRandomSlopeCov` object. |
|
| 362 |
#' @export |
|
| 363 |
#' |
|
| 364 |
#' @examples |
|
| 365 |
#' LongitudinalRandomSlopeCov() |
|
| 366 |
LongitudinalRandomSlopeCov <- function( |
|
| 367 |
mu_formula = ~study, |
|
| 368 |
slope_mu_formula = ~arm, |
|
| 369 |
slope_sigma_formula = ~arm, |
|
| 370 |
mu_parametrization = "linear", |
|
| 371 |
slope_mu_parametrization = "linear", |
|
| 372 |
slope_sigma_parametrization = "log-linear", |
|
| 373 |
mu_intercept_prior = prior_normal(30, 10), |
|
| 374 |
mu_coefficients_prior = prior_normal(0, 3), |
|
| 375 |
slope_mu_intercept_prior = prior_normal(1, 3), |
|
| 376 |
slope_mu_coefficients_prior = prior_normal(0, 3), |
|
| 377 |
slope_sigma_intercept_prior = prior_normal(0, 1.5), |
|
| 378 |
slope_sigma_coefficients_prior = prior_normal(0, 1.5), |
|
| 379 |
sigma = prior_lognormal(0, 1.5), |
|
| 380 |
scaled_variance = FALSE |
|
| 381 |
) {
|
|
| 382 | 24x |
mu_formula <- .validate_covariate_formula(mu_formula, "mu_formula") |
| 383 | 23x |
slope_mu_formula <- .validate_covariate_formula( |
| 384 | 23x |
slope_mu_formula, |
| 385 | 23x |
"slope_mu_formula" |
| 386 |
) |
|
| 387 | 23x |
slope_sigma_formula <- .validate_covariate_formula( |
| 388 | 23x |
slope_sigma_formula, |
| 389 | 23x |
"slope_sigma_formula" |
| 390 |
) |
|
| 391 | 23x |
mu_parametrization <- .validate_covariate_parametrization( |
| 392 | 23x |
mu_parametrization, |
| 393 | 23x |
"mu_parametrization" |
| 394 |
) |
|
| 395 | 22x |
slope_mu_parametrization <- .validate_covariate_parametrization( |
| 396 | 22x |
slope_mu_parametrization, |
| 397 | 22x |
"slope_mu_parametrization" |
| 398 |
) |
|
| 399 | 22x |
slope_sigma_parametrization <- .validate_covariate_parametrization( |
| 400 | 22x |
slope_sigma_parametrization, |
| 401 | 22x |
"slope_sigma_parametrization", |
| 402 | 22x |
scale = TRUE |
| 403 |
) |
|
| 404 | ||
| 405 | 20x |
mu_intercept_prior <- .positive_intercept_prior( |
| 406 | 20x |
mu_intercept_prior, |
| 407 | 20x |
mu_parametrization |
| 408 |
) |
|
| 409 | 20x |
slope_mu_intercept_prior <- .positive_intercept_prior( |
| 410 | 20x |
slope_mu_intercept_prior, |
| 411 | 20x |
slope_mu_parametrization |
| 412 |
) |
|
| 413 | 20x |
slope_sigma_intercept_prior <- .positive_intercept_prior( |
| 414 | 20x |
slope_sigma_intercept_prior, |
| 415 | 20x |
slope_sigma_parametrization, |
| 416 | 20x |
positive = slope_sigma_parametrization != "log-linear" |
| 417 |
) |
|
| 418 | 20x |
sigma <- set_limits(sigma, lower = getOption("jmpost.double_eps"))
|
| 419 | ||
| 420 | 20x |
stan <- StanModule(decorated_render( |
| 421 | 20x |
.x = read_stan("lm-random-slope-cov/model.stan"),
|
| 422 | 20x |
scaled_variance = scaled_variance, |
| 423 | 20x |
mu_predictor = .covariate_predictor_stan( |
| 424 | 20x |
"lm_rsc_mu", |
| 425 | 20x |
mu_parametrization |
| 426 |
), |
|
| 427 | 20x |
slope_mu_predictor = .covariate_predictor_stan( |
| 428 | 20x |
"lm_rsc_slope_mu", |
| 429 | 20x |
slope_mu_parametrization |
| 430 |
), |
|
| 431 | 20x |
slope_sigma_predictor = .covariate_predictor_stan( |
| 432 | 20x |
"lm_rsc_slope_sigma", |
| 433 | 20x |
slope_sigma_parametrization |
| 434 |
) |
|
| 435 |
)) |
|
| 436 | ||
| 437 | 20x |
.LongitudinalRandomSlopeCov( |
| 438 | 20x |
LongitudinalModel( |
| 439 | 20x |
name = "Random Slope with Covariates", |
| 440 | 20x |
stan = stan, |
| 441 | 20x |
scaled_variance = scaled_variance, |
| 442 | 20x |
parameters = ParameterList( |
| 443 | 20x |
Parameter( |
| 444 | 20x |
name = "lm_rsc_mu_intercept", |
| 445 | 20x |
prior = mu_intercept_prior |
| 446 |
), |
|
| 447 | 20x |
Parameter( |
| 448 | 20x |
name = "lm_rsc_mu_coefficients", |
| 449 | 20x |
prior = mu_coefficients_prior, |
| 450 | 20x |
size = "p_lm_rsc_mu" |
| 451 |
), |
|
| 452 | 20x |
Parameter( |
| 453 | 20x |
name = "lm_rsc_slope_mu_intercept", |
| 454 | 20x |
prior = slope_mu_intercept_prior |
| 455 |
), |
|
| 456 | 20x |
Parameter( |
| 457 | 20x |
name = "lm_rsc_slope_mu_coefficients", |
| 458 | 20x |
prior = slope_mu_coefficients_prior, |
| 459 | 20x |
size = "p_lm_rsc_slope_mu" |
| 460 |
), |
|
| 461 | 20x |
Parameter( |
| 462 | 20x |
name = "lm_rsc_slope_sigma_intercept", |
| 463 | 20x |
prior = slope_sigma_intercept_prior |
| 464 |
), |
|
| 465 | 20x |
Parameter( |
| 466 | 20x |
name = "lm_rsc_slope_sigma_coefficients", |
| 467 | 20x |
prior = slope_sigma_coefficients_prior, |
| 468 | 20x |
size = "p_lm_rsc_slope_sigma" |
| 469 |
), |
|
| 470 | 20x |
Parameter( |
| 471 | 20x |
name = "lm_rsc_sigma", |
| 472 | 20x |
prior = sigma |
| 473 |
), |
|
| 474 | 20x |
Parameter( |
| 475 | 20x |
name = "lm_rsc_ind_rnd_slope", |
| 476 | 20x |
prior = prior_init_only(prior_normal( |
| 477 | 20x |
.predictor_reference_value( |
| 478 | 20x |
slope_mu_intercept_prior, |
| 479 | 20x |
slope_mu_parametrization |
| 480 |
), |
|
| 481 | 20x |
.predictor_reference_value( |
| 482 | 20x |
slope_sigma_intercept_prior, |
| 483 | 20x |
slope_sigma_parametrization |
| 484 |
) |
|
| 485 |
)), |
|
| 486 | 20x |
size = "n_subjects" |
| 487 |
) |
|
| 488 |
) |
|
| 489 |
), |
|
| 490 | 20x |
mu_formula = mu_formula, |
| 491 | 20x |
slope_mu_formula = slope_mu_formula, |
| 492 | 20x |
slope_sigma_formula = slope_sigma_formula, |
| 493 | 20x |
mu_parametrization = mu_parametrization, |
| 494 | 20x |
slope_mu_parametrization = slope_mu_parametrization, |
| 495 | 20x |
slope_sigma_parametrization = slope_sigma_parametrization |
| 496 |
) |
|
| 497 |
} |
|
| 498 | ||
| 499 |
#' @export |
|
| 500 |
#' |
|
| 501 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 502 |
enableGQ.LongitudinalRandomSlopeCov <- function( |
|
| 503 |
object, |
|
| 504 |
generator = NULL, |
|
| 505 |
type = NULL, |
|
| 506 |
... |
|
| 507 |
) {
|
|
| 508 | 4x |
include_subject <- identical(type, "longitudinal") && |
| 509 | 4x |
is(generator, "QuantityGeneratorSubject") |
| 510 | 4x |
include_population <- identical(type, "longitudinal") && |
| 511 | 4x |
is(generator, "QuantityGeneratorPopulation") |
| 512 | ||
| 513 | 4x |
StanModule(decorated_render( |
| 514 | 4x |
.x = read_stan("lm-random-slope-cov/quantities.stan"),
|
| 515 | 4x |
include_gq_longitudinal_idv = include_subject, |
| 516 | 4x |
include_gq_longitudinal_pop = include_population, |
| 517 | 4x |
mu_population_predictor = .covariate_predictor_stan( |
| 518 | 4x |
"lm_rsc_mu", |
| 519 | 4x |
object@mu_parametrization, |
| 520 | 4x |
design_prefix = "gq_lm_rsc_mu", |
| 521 | 4x |
n_rows = "gq_n_quant" |
| 522 |
), |
|
| 523 | 4x |
slope_mu_population_predictor = .covariate_predictor_stan( |
| 524 | 4x |
"lm_rsc_slope_mu", |
| 525 | 4x |
object@slope_mu_parametrization, |
| 526 | 4x |
design_prefix = "gq_lm_rsc_slope_mu", |
| 527 | 4x |
n_rows = "gq_n_quant" |
| 528 |
) |
|
| 529 |
)) |
|
| 530 |
} |
|
| 531 | ||
| 532 |
#' @rdname gq_population_stan_data |
|
| 533 |
#' @export |
|
| 534 |
gq_population_stan_data.LongitudinalRandomSlopeCov <- function( |
|
| 535 |
object, |
|
| 536 |
model, |
|
| 537 |
data = NULL, |
|
| 538 |
... |
|
| 539 |
) {
|
|
| 540 | 3x |
result <- list( |
| 541 | 3x |
declarations = paste( |
| 542 | 3x |
"matrix[gq_n_quant, p_lm_rsc_mu] gq_lm_rsc_mu_design;", |
| 543 | 3x |
paste0( |
| 544 | 3x |
"matrix[gq_n_quant, p_lm_rsc_slope_mu] ", |
| 545 | 3x |
"gq_lm_rsc_slope_mu_design;" |
| 546 |
), |
|
| 547 | 3x |
sep = "\n" |
| 548 |
), |
|
| 549 | 3x |
data = list() |
| 550 |
) |
|
| 551 | 3x |
if (!is.null(data)) {
|
| 552 | 2x |
assert_that( |
| 553 | 2x |
!is.null(object@newdata) && |
| 554 | 2x |
nrow(object@newdata) == length(object@times), |
| 555 | 2x |
msg = paste0( |
| 556 | 2x |
"Population quantities for `LongitudinalRandomSlopeCov` ", |
| 557 | 2x |
"require `GridPopulation(newdata = ...)`" |
| 558 |
) |
|
| 559 |
) |
|
| 560 | 2x |
result$data <- .random_slope_cov_population_stan_data( |
| 561 | 2x |
model, |
| 562 | 2x |
data@subject, |
| 563 | 2x |
object@newdata |
| 564 |
) |
|
| 565 |
} |
|
| 566 | 3x |
result |
| 567 |
} |
|
| 568 | ||
| 569 |
#' @export |
|
| 570 |
#' |
|
| 571 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 572 |
enableLink.LongitudinalRandomSlopeCov <- function(object, ...) {
|
|
| 573 | 2x |
object@stan <- merge( |
| 574 | 2x |
object@stan, |
| 575 | 2x |
StanModule("lm-random-slope-cov/link.stan")
|
| 576 |
) |
|
| 577 | 2x |
object |
| 578 |
} |
|
| 579 | ||
| 580 |
#' @export |
|
| 581 |
#' |
|
| 582 |
#' @returns A `LinkComponent` object. |
|
| 583 |
linkDSLD.LongitudinalRandomSlopeCov <- function( |
|
| 584 |
prior = prior_normal(0, 2), |
|
| 585 |
model, |
|
| 586 |
... |
|
| 587 |
) {
|
|
| 588 | 2x |
LinkComponent( |
| 589 | 2x |
key = "link_dsld", |
| 590 | 2x |
stan = StanModule("lm-random-slope/link_dsld.stan"),
|
| 591 | 2x |
prior = prior |
| 592 |
) |
|
| 593 |
} |
|
| 594 | ||
| 595 |
#' @export |
|
| 596 |
#' |
|
| 597 |
#' @returns A `LinkComponent` object. |
|
| 598 |
linkIdentity.LongitudinalRandomSlopeCov <- function( |
|
| 599 |
prior = prior_normal(0, 2), |
|
| 600 |
model, |
|
| 601 |
... |
|
| 602 |
) {
|
|
| 603 | ! |
LinkComponent( |
| 604 | ! |
key = "link_identity", |
| 605 | ! |
stan = StanModule("lm-random-slope/link_identity.stan"),
|
| 606 | ! |
prior = prior |
| 607 |
) |
|
| 608 |
} |
|
| 609 | ||
| 610 |
#' @export |
|
| 611 |
#' |
|
| 612 |
#' @returns A `LinkComponent` object. |
|
| 613 |
linkGrowth.LongitudinalRandomSlopeCov <- function( |
|
| 614 |
prior = prior_normal(0, 2), |
|
| 615 |
model, |
|
| 616 |
... |
|
| 617 |
) {
|
|
| 618 | ! |
LinkComponent( |
| 619 | ! |
key = "link_growth", |
| 620 | ! |
stan = StanModule("lm-random-slope/link_growth.stan"),
|
| 621 | ! |
prior = prior |
| 622 |
) |
|
| 623 |
} |
|
| 624 | ||
| 625 |
#' @rdname getPredictionNames |
|
| 626 |
#' @export |
|
| 627 |
getPredictionNames.LongitudinalRandomSlopeCov <- function(object, ...) {
|
|
| 628 | 1x |
c("intercept", "slope")
|
| 629 |
} |
|
| 630 | ||
| 631 |
#' @rdname getRandomEffectsNames |
|
| 632 |
#' @export |
|
| 633 |
getRandomEffectsNames.LongitudinalRandomSlopeCov <- function(object, ...) {
|
|
| 634 | 2x |
c("slope" = "lm_rsc_ind_rnd_slope")
|
| 635 |
} |
|
| 636 | ||
| 637 |
#' @rdname required_longitudinal_covs |
|
| 638 |
#' @export |
|
| 639 |
required_longitudinal_covs.LongitudinalRandomSlopeCov <- function(object, ...) {
|
|
| 640 | 7x |
unique(c( |
| 641 | 7x |
all.vars(object@mu_formula), |
| 642 | 7x |
all.vars(object@slope_mu_formula) |
| 643 |
)) |
|
| 644 |
} |
|
| 645 | ||
| 646 |
#' @rdname required_simulation_covariates |
|
| 647 |
#' @export |
|
| 648 |
required_simulation_covariates.LongitudinalRandomSlopeCov <- function( |
|
| 649 |
object, |
|
| 650 |
... |
|
| 651 |
) {
|
|
| 652 | 1x |
unique(unlist(lapply(c("mu", "slope_mu", "slope_sigma"), function(name) {
|
| 653 | 3x |
all.vars(slot(object, paste0(name, "_formula"))) |
| 654 |
}))) |
|
| 655 |
} |
|
| 656 | ||
| 657 |
#' @rdname longitudinal_model_stan_data |
|
| 658 |
#' @keywords internal |
|
| 659 |
#' @export |
|
| 660 |
longitudinal_model_stan_data.LongitudinalRandomSlopeCov <- function( |
|
| 661 |
model, |
|
| 662 |
subject |
|
| 663 |
) {
|
|
| 664 | 8x |
subject_data <- as.data.frame(harmonise(subject)) |
| 665 | 8x |
mu_design <- .covariate_design_matrix( |
| 666 | 8x |
model@mu_formula, |
| 667 | 8x |
subject_data, |
| 668 | 8x |
"mu_formula" |
| 669 |
) |
|
| 670 | 8x |
slope_mu_design <- .covariate_design_matrix( |
| 671 | 8x |
model@slope_mu_formula, |
| 672 | 8x |
subject_data, |
| 673 | 8x |
"slope_mu_formula" |
| 674 |
) |
|
| 675 | 8x |
slope_sigma_design <- .covariate_design_matrix( |
| 676 | 8x |
model@slope_sigma_formula, |
| 677 | 8x |
subject_data, |
| 678 | 8x |
"slope_sigma_formula" |
| 679 |
) |
|
| 680 | ||
| 681 | 8x |
list( |
| 682 | 8x |
p_lm_rsc_mu = ncol(mu_design), |
| 683 | 8x |
lm_rsc_mu_design = mu_design, |
| 684 | 8x |
p_lm_rsc_slope_mu = ncol(slope_mu_design), |
| 685 | 8x |
lm_rsc_slope_mu_design = slope_mu_design, |
| 686 | 8x |
p_lm_rsc_slope_sigma = ncol(slope_sigma_design), |
| 687 | 8x |
lm_rsc_slope_sigma_design = slope_sigma_design |
| 688 |
) |
|
| 689 |
} |
|
| 690 | ||
| 691 |
#' Create population-prediction Stan data for a covariate random-slope model |
|
| 692 |
#' |
|
| 693 |
#' @param model A [`LongitudinalRandomSlopeCov`] object. |
|
| 694 |
#' @param subject A [`DataSubject`] object containing the fitted subject data. |
|
| 695 |
#' @param newdata Population profiles supplied through [`GridPopulation()`]. |
|
| 696 |
#' |
|
| 697 |
#' @keywords internal |
|
| 698 |
#' @returns A named list of population generated-quantities design matrices. |
|
| 699 |
.random_slope_cov_population_stan_data <- function( |
|
| 700 |
model, |
|
| 701 |
subject, |
|
| 702 |
newdata |
|
| 703 |
) {
|
|
| 704 | 2x |
subject_data <- as.data.frame(harmonise(subject)) |
| 705 | 2x |
list( |
| 706 | 2x |
gq_lm_rsc_mu_design = .covariate_prediction_design_matrix( |
| 707 | 2x |
model@mu_formula, |
| 708 | 2x |
newdata, |
| 709 | 2x |
subject_data, |
| 710 | 2x |
"mu_formula" |
| 711 |
), |
|
| 712 | 2x |
gq_lm_rsc_slope_mu_design = .covariate_prediction_design_matrix( |
| 713 | 2x |
model@slope_mu_formula, |
| 714 | 2x |
newdata, |
| 715 | 2x |
subject_data, |
| 716 | 2x |
"slope_mu_formula" |
| 717 |
) |
|
| 718 |
) |
|
| 719 |
} |
| 1 |
#' Calculate Population Hazard Ratios |
|
| 2 |
#' |
|
| 3 |
#' Calculates hazard ratios marginalised over subject specific random effects using the |
|
| 4 |
#' approach proposed by \insertCite{oudenhoven2020marginal}{jmpost}.
|
|
| 5 |
#' |
|
| 6 |
#' @typed object: JointModelSamples |
|
| 7 |
#' samples as drawn from a Joint Model. |
|
| 8 |
#' @typed hr_formula: formula |
|
| 9 |
#' defines the terms to include in the hazard ratio calculation. |
|
| 10 |
#' By default this uses the right side of the formula used in the survival model. |
|
| 11 |
#' Set to `NULL` not include any terms |
|
| 12 |
#' @typed baseline: formula |
|
| 13 |
#' terms to model baseline hazard using variable `time`. |
|
| 14 |
#' Default is a B-spline from [splines]: `~bs(time, df = 10)` |
|
| 15 |
#' @typed quantiles: numeric |
|
| 16 |
#' vector of two values in (0, 1) for calculating quantiles from log hazard ratio |
|
| 17 |
#' distributions. |
|
| 18 |
#' |
|
| 19 |
#' @references \insertAllCited{}
|
|
| 20 |
#' |
|
| 21 |
#' @returns A list containing a summary of parameter distributions as a `data.frame` and a |
|
| 22 |
#' matrix containing the parameter estimates for each sample. |
|
| 23 |
#' @export |
|
| 24 |
#' @importFrom splines bs |
|
| 25 |
#' |
|
| 26 |
#' @examples |
|
| 27 |
#' \dontrun{
|
|
| 28 |
#' populationHR(fit, quantiles = c(0.01, 0.99)) |
|
| 29 |
#' } |
|
| 30 |
populationHR <- function( |
|
| 31 |
object, |
|
| 32 |
hr_formula = object@data@survival@formula, |
|
| 33 |
baseline = ~ bs(time, df = 10), |
|
| 34 |
quantiles = c(0.025, 0.975) |
|
| 35 |
) {
|
|
| 36 | 8x |
assert_class(object, "JointModelSamples") |
| 37 | 7x |
assert_formula(hr_formula) |
| 38 | 6x |
assert_formula(baseline) |
| 39 | 6x |
assert_numeric( |
| 40 | 6x |
quantiles, |
| 41 | 6x |
lower = 0, |
| 42 | 6x |
upper = 1, |
| 43 | 6x |
any.missing = FALSE, |
| 44 | 6x |
unique = TRUE |
| 45 |
) |
|
| 46 | 5x |
if (!"time" %in% all.vars(baseline)) {
|
| 47 | 1x |
stop("baseline formula should include a time term.")
|
| 48 |
} |
|
| 49 | ||
| 50 |
# Extract the variable names used in the data |
|
| 51 | 4x |
subject_var <- object@data@subject@subject |
| 52 | 4x |
arm_var <- object@data@subject@arm |
| 53 | 4x |
long_time_var <- all.vars(delete.response(terms( |
| 54 | 4x |
object@data@longitudinal@formula |
| 55 |
))) |
|
| 56 | 4x |
surv_time_var <- all.vars(object@data@survival@formula[[2]][[2]]) |
| 57 | 4x |
surv_covs <- all.vars(delete.response(terms(hr_formula))) |
| 58 | 4x |
if (!all(surv_covs %in% colnames(object@data@survival@data))) {
|
| 59 | 1x |
stop("All variables in hr_formula must be in survival data")
|
| 60 |
} |
|
| 61 | ||
| 62 | 3x |
marginal_formula <- stats::reformulate(c( |
| 63 | 3x |
attr(terms(baseline), "term.labels"), |
| 64 | 3x |
attr(terms(hr_formula), "term.labels") |
| 65 |
)) |
|
| 66 | ||
| 67 |
# Get the survival quantities at the observed longitudinal and survival times for each patient: |
|
| 68 | 3x |
times_df <- rbind( |
| 69 | 3x |
stats::setNames( |
| 70 | 3x |
object@data@longitudinal@data[, c(subject_var, long_time_var)], |
| 71 | 3x |
c("subject", "time")
|
| 72 |
), |
|
| 73 | 3x |
stats::setNames( |
| 74 | 3x |
object@data@survival@data[, c(subject_var, surv_time_var)], |
| 75 | 3x |
c("subject", "time")
|
| 76 |
) |
|
| 77 |
) |
|
| 78 | 3x |
times_df <- times_df[order(times_df$subject, times_df$time), ] |
| 79 | 3x |
times_df <- times_df[times_df$time > 0, ] |
| 80 | 3x |
times_df <- times_df[!duplicated.data.frame(times_df), ] |
| 81 | ||
| 82 |
# Generate samples of the log hazard log h_i(t) for each patient i at these time points t. |
|
| 83 | 3x |
grid_spec <- split(times_df$time, times_df$subject) |
| 84 | 3x |
log_haz_samples <- SurvivalQuantities( |
| 85 | 3x |
object, |
| 86 | 3x |
grid = GridManual(grid_spec), |
| 87 | 3x |
type = "loghaz" |
| 88 | 3x |
)@quantities@quantities |> |
| 89 | 3x |
t() |
| 90 | ||
| 91 |
# Construct \tilde{X} in paper's notation with one row per patient's time
|
|
| 92 | 3x |
W_df <- dplyr::left_join( |
| 93 | 3x |
times_df, |
| 94 | 3x |
stats::setNames( |
| 95 | 3x |
object@data@survival@data[, c(subject_var, surv_covs)], |
| 96 | 3x |
c("subject", surv_covs)
|
| 97 |
), |
|
| 98 | 3x |
by = "subject" |
| 99 |
) |
|
| 100 | 3x |
W_mat <- model.matrix(marginal_formula, W_df) |
| 101 | ||
| 102 |
# As model matrix contains baseline and covariates, we don't need the intercept term |
|
| 103 |
# but we want factor variables encoded relative to the intercept |
|
| 104 | 3x |
W_mat <- W_mat[, colnames(W_mat) != "(Intercept)", drop = FALSE] |
| 105 | ||
| 106 | 3x |
estimates <- stats::lm.fit(x = W_mat, y = log_haz_samples)$coefficients |
| 107 | 3x |
tidy_res <- apply(estimates, 1, function(x) {
|
| 108 | 26x |
quantiles <- stats::quantile(x, probs = quantiles) |
| 109 | 26x |
c(mean = mean(x), median = median(x), quantiles) |
| 110 |
}) |> |
|
| 111 | 3x |
t() |> |
| 112 | 3x |
data.frame() |
| 113 | ||
| 114 | 3x |
list(summary = tidy_res, estimates) |
| 115 |
} |
| 1 |
# "missing" = no argument provided |
|
| 2 |
# "NULL" = explicit NULL |
|
| 3 |
setClassUnion("empty", c("missing", "NULL"))
|
|
| 4 |
setClassUnion("numeric_or_NULL", c("numeric", "NULL"))
|
|
| 5 |
setClassUnion("character_or_NULL", c("character", "NULL"))
|
|
| 6 |
setClassUnion("data.frame_or_NULL", c("data.frame", "NULL"))
|
|
| 7 | ||
| 8 |
# merge ---- |
|
| 9 | ||
| 10 |
#' `merge` |
|
| 11 |
#' |
|
| 12 |
#' Merge two `StanModule` or `ParameterList` objects. |
|
| 13 |
#' |
|
| 14 |
#' @param x first module. |
|
| 15 |
#' @param y second module. |
|
| 16 |
#' @param ... additional arguments. |
|
| 17 |
#' |
|
| 18 |
#' @export |
|
| 19 |
# Needs to be S4 for multiple dispatch ! |
|
| 20 |
setGeneric( |
|
| 21 |
name = "merge", |
|
| 22 |
def = function(x, y, ...) standardGeneric("merge")
|
|
| 23 |
) |
|
| 24 | ||
| 25 | ||
| 26 |
# show ---- |
|
| 27 | ||
| 28 |
#' Printing of Different Classes |
|
| 29 |
#' |
|
| 30 |
#' These methods print objects of different classes. |
|
| 31 |
#' |
|
| 32 |
#' @name show |
|
| 33 |
#' @aliases show |
|
| 34 |
#' |
|
| 35 |
#' @param object what to print. |
|
| 36 |
#' |
|
| 37 |
#' @export |
|
| 38 |
#' |
|
| 39 |
#' @examples |
|
| 40 |
#' show(prior_normal(0, 1)) |
|
| 41 |
NULL |
|
| 42 | ||
| 43 | ||
| 44 |
# write_stan ---- |
|
| 45 | ||
| 46 |
#' `write_stan` |
|
| 47 |
#' |
|
| 48 |
#' Write the Stan code for a Stan module. |
|
| 49 |
#' |
|
| 50 |
#' @param object the module. |
|
| 51 |
#' @typed destination: "`character` or `connection`" |
|
| 52 |
#' Where to write stan code to. |
|
| 53 |
#' @param ... Additional arguments |
|
| 54 |
#' |
|
| 55 |
#' @export |
|
| 56 |
#' |
|
| 57 |
#' @returns Invisibly returns `NULL` after writing the Stan program. |
|
| 58 |
#' |
|
| 59 |
#' @examples |
|
| 60 |
#' path <- tempfile(fileext = ".stan") |
|
| 61 |
#' write_stan(JointModel(LongitudinalGSF()), path) |
|
| 62 |
#' unlink(path) |
|
| 63 |
write_stan <- function(object, destination, ...) {
|
|
| 64 | ! |
UseMethod("write_stan")
|
| 65 |
} |
|
| 66 | ||
| 67 |
# compileStanModel ---- |
|
| 68 | ||
| 69 |
#' `compileStanModel` |
|
| 70 |
#' |
|
| 71 |
#' Compile the Stan module. |
|
| 72 |
#' |
|
| 73 |
#' @param object the module. |
|
| 74 |
#' |
|
| 75 |
#' @export |
|
| 76 |
#' |
|
| 77 |
#' @returns A compiled `cmdstanr::CmdStanModel` object. |
|
| 78 |
#' |
|
| 79 |
#' @examples |
|
| 80 |
#' \dontrun{
|
|
| 81 |
#' compileStanModel(JointModel(LongitudinalGSF())) |
|
| 82 |
#' } |
|
| 83 |
compileStanModel <- function(object) {
|
|
| 84 | 73x |
UseMethod("compileStanModel")
|
| 85 |
} |
|
| 86 | ||
| 87 | ||
| 88 |
# sampleStanModel ---- |
|
| 89 | ||
| 90 |
#' `sampleStanModel` |
|
| 91 |
#' |
|
| 92 |
#' Sample from a Stan Module. |
|
| 93 |
#' |
|
| 94 |
#' @param object the module. |
|
| 95 |
#' @param ... additional arguments. |
|
| 96 |
#' |
|
| 97 |
#' @export |
|
| 98 |
#' |
|
| 99 |
#' @returns A `JointModelSamples` object. |
|
| 100 |
#' |
|
| 101 |
#' @examples |
|
| 102 |
#' \dontrun{
|
|
| 103 |
#' sampleStanModel(JointModel(LongitudinalGSF()), data = joint_data) |
|
| 104 |
#' } |
|
| 105 |
sampleStanModel <- function(object, ...) {
|
|
| 106 | 18x |
UseMethod("sampleStanModel")
|
| 107 |
} |
|
| 108 | ||
| 109 | ||
| 110 |
# as.StanModule ---- |
|
| 111 | ||
| 112 |
#' `as.StanModule` |
|
| 113 |
#' |
|
| 114 |
#' Converts an object into a [`StanModule`]. |
|
| 115 |
#' |
|
| 116 |
#' @param object what to convert. |
|
| 117 |
#' @param ... additional options. |
|
| 118 |
#' @family as.StanModule |
|
| 119 |
#' @keywords internal |
|
| 120 |
#' |
|
| 121 |
#' @returns A `StanModule` object. |
|
| 122 |
as.StanModule <- function(object, ...) {
|
|
| 123 | 3941x |
UseMethod("as.StanModule")
|
| 124 |
} |
|
| 125 | ||
| 126 | ||
| 127 |
#' `getParameters` |
|
| 128 |
#' |
|
| 129 |
#' Extract any modelling parameters as a [`ParameterList`] object |
|
| 130 |
#' from a model. |
|
| 131 |
#' |
|
| 132 |
#' @param object where to obtain the parameters from. |
|
| 133 |
#' @param ... additional options. |
|
| 134 |
#' |
|
| 135 |
#' @export |
|
| 136 |
getParameters <- function(object, ...) {
|
|
| 137 | 480x |
UseMethod("getParameters")
|
| 138 |
} |
|
| 139 | ||
| 140 | ||
| 141 |
# extractVariableNames ---- |
|
| 142 | ||
| 143 |
#' Extract Mapping to Standardised Variable Names |
|
| 144 |
#' |
|
| 145 |
#' @description |
|
| 146 |
#' Extract a `list` that maps the variable names in a user-defined |
|
| 147 |
#' `data.frame` to standardised values. |
|
| 148 |
#' |
|
| 149 |
#' @param object the data object. |
|
| 150 |
#' @family extractVariableNames |
|
| 151 |
#' @keywords internal |
|
| 152 |
#' |
|
| 153 |
#' @returns A named list mapping source variables to standard names. |
|
| 154 |
extractVariableNames <- function(object) {
|
|
| 155 | 2133x |
UseMethod("extractVariableNames")
|
| 156 |
} |
|
| 157 | ||
| 158 | ||
| 159 |
# initialValues ---- |
|
| 160 | ||
| 161 |
#' `initialValues` |
|
| 162 |
#' |
|
| 163 |
#' Obtain the `list` of initial values to be passed to the Stan sampler. |
|
| 164 |
#' |
|
| 165 |
#' @param object where to get the initial values from. |
|
| 166 |
#' @param n_chains the number of initial values to generate. See details. |
|
| 167 |
#' @param ... Not currently used. |
|
| 168 |
#' |
|
| 169 |
#' @details |
|
| 170 |
#' There are multiple ways of specifying initial values to Stan, see the `init` argument |
|
| 171 |
#' in [cmdstanr::model-method-sample] for full details. Within this package we supply |
|
| 172 |
#' initial values via a list of lists where each inner list contains the initial values |
|
| 173 |
#' for a single chain. As such the `n_chains` argument specifies the number of inner lists |
|
| 174 |
#' to generate. |
|
| 175 |
#' |
|
| 176 |
#' See the Vignette for further details of how to specify initial values. |
|
| 177 |
#' |
|
| 178 |
#' @export |
|
| 179 |
#' |
|
| 180 |
#' @returns Initial values for the object, usually as a numeric vector or list. |
|
| 181 |
#' |
|
| 182 |
#' @examples |
|
| 183 |
#' initialValues(prior_normal(0, 1)) |
|
| 184 |
initialValues <- function(object, ...) {
|
|
| 185 | 162016x |
UseMethod("initialValues")
|
| 186 |
} |
|
| 187 | ||
| 188 | ||
| 189 |
# size ---- |
|
| 190 | ||
| 191 |
#' `size` |
|
| 192 |
#' |
|
| 193 |
#' Obtain the `list` of parameter sizes. |
|
| 194 |
#' |
|
| 195 |
#' @param object where to get the parameter sizes from. |
|
| 196 |
#' |
|
| 197 |
#' @keywords internal |
|
| 198 |
#' |
|
| 199 |
#' @returns A numeric value or named list describing parameter sizes. |
|
| 200 |
size <- function(object) {
|
|
| 201 | 164x |
UseMethod("size")
|
| 202 |
} |
|
| 203 | ||
| 204 | ||
| 205 |
# auxiliaryInitialValues ---- |
|
| 206 | ||
| 207 |
#' `auxiliaryInitialValues` |
|
| 208 |
#' |
|
| 209 |
#' Obtain initial values for any additional Stan parameters introduced by an object. |
|
| 210 |
#' |
|
| 211 |
#' @param object where to get the auxiliary initial values from. |
|
| 212 |
#' @param ... additional options. |
|
| 213 |
#' |
|
| 214 |
#' @details |
|
| 215 |
#' Some objects, such as shrinkage priors, declare additional Stan parameters |
|
| 216 |
#' beyond the main model parameter. This helper returns named initial values for |
|
| 217 |
#' those additional parameters. |
|
| 218 |
#' |
|
| 219 |
#' @keywords internal |
|
| 220 |
#' |
|
| 221 |
#' @returns A named `list` of initial values for auxiliary parameters. |
|
| 222 |
auxiliaryInitialValues <- function(object, ...) {
|
|
| 223 | 10065x |
UseMethod("auxiliaryInitialValues")
|
| 224 |
} |
|
| 225 | ||
| 226 | ||
| 227 |
# auxiliarySize ---- |
|
| 228 | ||
| 229 |
#' `auxiliarySize` |
|
| 230 |
#' |
|
| 231 |
#' Obtain sizes for any additional Stan parameters introduced by an object. |
|
| 232 |
#' |
|
| 233 |
#' @param object where to get the auxiliary parameter sizes from. |
|
| 234 |
#' @param ... additional options. |
|
| 235 |
#' |
|
| 236 |
#' @details |
|
| 237 |
#' Some objects, such as shrinkage priors, declare additional Stan parameters |
|
| 238 |
#' beyond the main model parameter. This helper returns named sizes for those |
|
| 239 |
#' additional parameters so their initial values can be expanded consistently. |
|
| 240 |
#' |
|
| 241 |
#' @keywords internal |
|
| 242 |
#' |
|
| 243 |
#' @returns A named `list` of auxiliary parameter sizes. |
|
| 244 |
auxiliarySize <- function(object, ...) {
|
|
| 245 | 138x |
UseMethod("auxiliarySize")
|
| 246 |
} |
|
| 247 | ||
| 248 | ||
| 249 |
# generateQuantities ---- |
|
| 250 | ||
| 251 |
#' `generateQuantities` |
|
| 252 |
#' |
|
| 253 |
#' Obtain the generated quantities from a Stan Model. |
|
| 254 |
#' |
|
| 255 |
#' @param object object to obtain generated quantities from |
|
| 256 |
#' @param ... additional options. |
|
| 257 |
#' |
|
| 258 |
#' @export |
|
| 259 |
#' |
|
| 260 |
#' @returns A `cmdstanr::CmdStanGQ` object containing generated quantities. |
|
| 261 |
#' |
|
| 262 |
#' @examples |
|
| 263 |
#' \dontrun{
|
|
| 264 |
#' generateQuantities(fit, generator, "survival") |
|
| 265 |
#' } |
|
| 266 |
generateQuantities <- function(object, ...) {
|
|
| 267 | 38x |
UseMethod("generateQuantities")
|
| 268 |
} |
|
| 269 | ||
| 270 | ||
| 271 |
#' Population Generated-Quantities Stan Data |
|
| 272 |
#' |
|
| 273 |
#' Obtain the model-specific Stan declarations and data needed for longitudinal |
|
| 274 |
#' population generated quantities. |
|
| 275 |
#' |
|
| 276 |
#' This generic first dispatches on the quantity generator. Its |
|
| 277 |
#' [`QuantityGeneratorPopulation`] method then dispatches on `model`, allowing |
|
| 278 |
#' longitudinal-model authors to provide a |
|
| 279 |
#' `gq_population_stan_data.<model-class>()` method alongside their model. |
|
| 280 |
#' |
|
| 281 |
#' @param object A quantity generator. |
|
| 282 |
#' @param model A longitudinal model. |
|
| 283 |
#' @param data A [`DataJoint`] object, or `NULL` when only declarations are |
|
| 284 |
#' required. |
|
| 285 |
#' @param ... Additional options. |
|
| 286 |
#' @export |
|
| 287 |
#' |
|
| 288 |
#' @returns A list with `declarations`, a character scalar of Stan data-block |
|
| 289 |
#' declarations, and `data`, a named list of Stan data values. |
|
| 290 |
gq_population_stan_data <- function(object, model, data = NULL, ...) {
|
|
| 291 | 33x |
UseMethod("gq_population_stan_data")
|
| 292 |
} |
|
| 293 | ||
| 294 |
#' @rdname gq_population_stan_data |
|
| 295 |
#' @export |
|
| 296 |
gq_population_stan_data.default <- function( |
|
| 297 |
object, |
|
| 298 |
model, |
|
| 299 |
data = NULL, |
|
| 300 |
... |
|
| 301 |
) {
|
|
| 302 | 26x |
list(declarations = "", data = list()) |
| 303 |
} |
|
| 304 | ||
| 305 | ||
| 306 |
#' Prepare Data Object |
|
| 307 |
#' |
|
| 308 |
#' @typed object: "`DataSubject` or `DataLongitudinal` or `DataSurvival`" |
|
| 309 |
#' data object to "harmonise" |
|
| 310 |
#' @typed subject_var: character |
|
| 311 |
#' the name of the variable containing the subject identifier. |
|
| 312 |
#' @typed subject_ord: character |
|
| 313 |
#' the expected levels (in order) of the subject identifier. |
|
| 314 |
#' @param ... not used. |
|
| 315 |
#' |
|
| 316 |
#' @details |
|
| 317 |
#' This utility function prepares the datasets in the data objects in order to ensure they |
|
| 318 |
#' are consistent and compatible with each other. |
|
| 319 |
#' |
|
| 320 |
#' In particular it ensures that the `subject` variable, as specified by `DataSubject`, |
|
| 321 |
#' is available in `DataLongitudinal` and `DataSurvival` and that all levels are present |
|
| 322 |
#' in all 3 data objects. |
|
| 323 |
#' |
|
| 324 |
#' It also sorts the datasets to ensure that indexes are consistent e.g. index 1 for |
|
| 325 |
#' `DataSubject@data` corresponds to the same subject as index 1 for `DataSurvival@data`. |
|
| 326 |
#' For `DataLongitudinal` the data is additionally sorted by time and outcome value. |
|
| 327 |
#' |
|
| 328 |
#' @seealso [`DataJoint`], [`DataSurvival`], [`DataSubject`], [`DataLongitudinal`] |
|
| 329 |
#' |
|
| 330 |
#' @keywords internal |
|
| 331 |
#' @return Returns the original object but with the data standardised (see details) |
|
| 332 |
harmonise <- function(object, ...) {
|
|
| 333 | 582x |
UseMethod("harmonise")
|
| 334 |
} |
|
| 335 | ||
| 336 | ||
| 337 |
#' @rdname harmonise |
|
| 338 |
harmonise.default <- function(object, ...) {
|
|
| 339 | 66x |
NULL |
| 340 |
} |
|
| 341 | ||
| 342 | ||
| 343 |
#' `as_stan_list` |
|
| 344 |
#' |
|
| 345 |
#' @description |
|
| 346 |
#' Extracts a list of data elements from an object to be used as input |
|
| 347 |
#' to a Stan Model |
|
| 348 |
#' |
|
| 349 |
#' @param object to be converted. |
|
| 350 |
#' @param ... additional options. |
|
| 351 |
#' |
|
| 352 |
#' @family as_stan_list |
|
| 353 |
#' @export |
|
| 354 |
#' |
|
| 355 |
#' @returns A named `list` suitable for use as Stan data. |
|
| 356 |
#' |
|
| 357 |
#' @examples |
|
| 358 |
#' as_stan_list(Parameter(prior_normal(0, 1), "beta")) |
|
| 359 |
as_stan_list <- function(object, ...) {
|
|
| 360 | 2527x |
UseMethod("as_stan_list")
|
| 361 |
} |
|
| 362 | ||
| 363 |
#' @rdname as_stan_list |
|
| 364 |
#' @export |
|
| 365 |
as_stan_list.default <- function(object, ...) {
|
|
| 366 | 50x |
NULL |
| 367 |
} |
|
| 368 | ||
| 369 | ||
| 370 |
#' `as_print_string` |
|
| 371 |
#' |
|
| 372 |
#' @description |
|
| 373 |
#' Returns the character representation of an object which is suitable |
|
| 374 |
#' for printing to the console |
|
| 375 |
#' |
|
| 376 |
#' @param object to be converted to string. |
|
| 377 |
#' @param ... additional options. |
|
| 378 |
#' |
|
| 379 |
#' @family as_print_string |
|
| 380 |
#' @keywords internal |
|
| 381 |
#' |
|
| 382 |
#' @returns A character vector suitable for printing. |
|
| 383 |
as_print_string <- function(object, ...) {
|
|
| 384 | 135x |
UseMethod("as_print_string")
|
| 385 |
} |
|
| 386 | ||
| 387 |
#' Show an Object |
|
| 388 |
#' |
|
| 389 |
#' Prints an object to the console. |
|
| 390 |
#' |
|
| 391 |
#' @param object Object to be printed |
|
| 392 |
#' |
|
| 393 |
#' @name show-object |
|
| 394 |
NULL |
|
| 395 | ||
| 396 | ||
| 397 |
#' `brierScore` |
|
| 398 |
#' |
|
| 399 |
#' @description |
|
| 400 |
#' Returns the Brier Score for a given model |
|
| 401 |
#' |
|
| 402 |
#' @param object to calculate Brier Score for. |
|
| 403 |
#' @param ... additional options. |
|
| 404 |
#' |
|
| 405 |
#' @family brierScore |
|
| 406 |
#' @export |
|
| 407 |
#' |
|
| 408 |
#' @returns A numeric Brier score or a `data.frame` of scores over time. |
|
| 409 |
#' |
|
| 410 |
#' @examples |
|
| 411 |
#' \dontrun{
|
|
| 412 |
#' brierScore(survival_quantities) |
|
| 413 |
#' } |
|
| 414 |
brierScore <- function(object, ...) {
|
|
| 415 | 3x |
UseMethod("brierScore")
|
| 416 |
} |
|
| 417 | ||
| 418 | ||
| 419 |
#' Generate Simulated Observations |
|
| 420 |
#' |
|
| 421 |
#' @typed object: "`SimLongitudinal` or `SimSurvival`" |
|
| 422 |
#' object to generate observations from. |
|
| 423 |
#' @typed times_df: data.frame |
|
| 424 |
#' the times at which to generate observations. See details. |
|
| 425 |
#' |
|
| 426 |
#' @details |
|
| 427 |
#' The `times_df` argument should be a `data.frame` as created by `sampleSubjects` but |
|
| 428 |
#' replicated for each time point at which observations are to be generated. That is if you want |
|
| 429 |
#' to generate observations for times `c(0, 1, 2, 3)` then `times_df` should be created as: |
|
| 430 |
#' ``` |
|
| 431 |
#' subject_dat <- sampleSubjects(object, ...) |
|
| 432 |
#' times_df <- tidyr::expand_grid( |
|
| 433 |
#' subject_dat, |
|
| 434 |
#' time = c(0, 1, 2, 3) |
|
| 435 |
#' ) |
|
| 436 |
#' ``` |
|
| 437 |
#' |
|
| 438 |
#' @export |
|
| 439 |
#' |
|
| 440 |
#' @returns A `data.frame` containing simulated observations. |
|
| 441 |
#' |
|
| 442 |
#' @examples |
|
| 443 |
#' sim <- SimLongitudinalRandomSlope(slope_mu = 0.01, slope_sigma = 0.5) |
|
| 444 |
#' subjects <- data.frame(study = factor("S"), arm = factor("A"), subject = "1")
|
|
| 445 |
#' subject_parameters <- sampleSubjects(sim, subjects) |
|
| 446 |
#' sampleObservations(sim, transform(subject_parameters, time = 0)) |
|
| 447 |
sampleObservations <- function(object, times_df) {
|
|
| 448 | 88x |
UseMethod("sampleObservations")
|
| 449 |
} |
|
| 450 | ||
| 451 | ||
| 452 |
#' Generate Simulated Subjects |
|
| 453 |
#' |
|
| 454 |
#' @typed object: "`SimLongitudinal` or `SimSurvival`" |
|
| 455 |
#' object to generate subjects from. |
|
| 456 |
#' @typed subjects_df: data.frame |
|
| 457 |
#' the subjects to generate observations for. See details. |
|
| 458 |
#' |
|
| 459 |
#' @details |
|
| 460 |
#' The `subjects_df` argument should be a `data.frame` with 1 row per desired subject to create |
|
| 461 |
#' with the following columns: |
|
| 462 |
#' - `study` (`factor`) the study identifier. |
|
| 463 |
#' - `arm` (`factor`) the treatment arm identifier. |
|
| 464 |
#' - `subject` (`character`) the subject identifier. |
|
| 465 |
#' |
|
| 466 |
#' This method takes care of generating all the individual subject data required for the |
|
| 467 |
#' [`sampleObservations`] method to generate the observations. |
|
| 468 |
#' @export |
|
| 469 |
#' |
|
| 470 |
#' @returns A `data.frame` containing simulated subject-level parameters. |
|
| 471 |
#' |
|
| 472 |
#' @examples |
|
| 473 |
#' subjects <- data.frame(study = factor("S"), arm = factor("A"), subject = "1")
|
|
| 474 |
#' sim <- SimLongitudinalRandomSlope(slope_mu = 0.01, slope_sigma = 0.5) |
|
| 475 |
#' sampleSubjects(sim, subjects) |
|
| 476 |
sampleSubjects <- function(object, subjects_df) {
|
|
| 477 | 62x |
UseMethod("sampleSubjects")
|
| 478 |
} |
|
| 479 | ||
| 480 | ||
| 481 |
#' Generate time windows for evaluating a hazard function |
|
| 482 |
#' |
|
| 483 |
#' @typed object: SurvivalModel |
|
| 484 |
#' object to generate time windows for. |
|
| 485 |
#' @param ... Not used. |
|
| 486 |
#' |
|
| 487 |
#' |
|
| 488 |
#' @returns A `data.frame` describing the hazard-evaluation intervals. |
|
| 489 |
hazardWindows <- function(object, ...) {
|
|
| 490 | 26x |
UseMethod("hazardWindows")
|
| 491 |
} |
|
| 492 | ||
| 493 |
#' @rdname Quant-Dev |
|
| 494 |
#' @export |
|
| 495 |
#' |
|
| 496 |
#' @returns A `QuantityGenerator` or `QuantityCollapser` object, according to the function called. |
|
| 497 |
#' |
|
| 498 |
#' @examples |
|
| 499 |
#' \dontrun{
|
|
| 500 |
#' grid <- GridFixed(times = c(0, 10)) |
|
| 501 |
#' as.QuantityGenerator(grid, joint_data) |
|
| 502 |
#' as.QuantityCollapser(grid, joint_data) |
|
| 503 |
#' } |
|
| 504 |
as.QuantityGenerator <- function(object, ...) {
|
|
| 505 | 117x |
UseMethod("as.QuantityGenerator")
|
| 506 |
} |
|
| 507 | ||
| 508 |
#' @rdname Quant-Dev |
|
| 509 |
#' @export |
|
| 510 |
as.QuantityCollapser <- function(object, ...) {
|
|
| 511 | 44x |
UseMethod("as.QuantityCollapser")
|
| 512 |
} |
|
| 513 | ||
| 514 | ||
| 515 |
#' Coalesce Time |
|
| 516 |
#' |
|
| 517 |
#' @typed object: Grid |
|
| 518 |
#' object to coalesce time for. |
|
| 519 |
#' @typed times: numeric |
|
| 520 |
#' the times to coalesce to. |
|
| 521 |
#' @param ... Not used |
|
| 522 |
#' |
|
| 523 |
#' Method used to replace NULL times on grid objects (if appropriate) |
|
| 524 |
#' |
|
| 525 |
#' @keywords internal |
|
| 526 |
#' |
|
| 527 |
#' @returns A `Grid` object with missing time points replaced. |
|
| 528 |
coalesceGridTime <- function(object, times, ...) {
|
|
| 529 | 43x |
UseMethod("coalesceGridTime")
|
| 530 |
} |
|
| 531 |
#' @export |
|
| 532 |
#' |
|
| 533 |
#' @returns The input object, unchanged. |
|
| 534 |
coalesceGridTime.default <- function(object, times, ...) {
|
|
| 535 | 5x |
object |
| 536 |
} |
|
| 537 | ||
| 538 | ||
| 539 |
#' Resolve a Promise |
|
| 540 |
#' |
|
| 541 |
#' @typed object: ANY |
|
| 542 |
#' an object to resolve. |
|
| 543 |
#' @typed ...: ANY |
|
| 544 |
#' additional arguments. |
|
| 545 |
#' |
|
| 546 |
#' If `object` is not a promise will just return itself else will resolve the promise |
|
| 547 |
#' and return the promised object. |
|
| 548 |
#' |
|
| 549 |
#' @export |
|
| 550 |
#' |
|
| 551 |
#' @returns The input object, or the value obtained by resolving a promise. |
|
| 552 |
#' |
|
| 553 |
#' @examples |
|
| 554 |
#' resolvePromise(1) |
|
| 555 |
#' resolvePromise(linkDSLD(), LongitudinalGSF()) |
|
| 556 |
resolvePromise <- function(object, ...) {
|
|
| 557 | 191x |
UseMethod("resolvePromise")
|
| 558 |
} |
|
| 559 | ||
| 560 |
#' @rdname resolvePromise |
|
| 561 |
#' @export |
|
| 562 |
resolvePromise.default <- function(object, ...) {
|
|
| 563 | ! |
object |
| 564 |
} |
|
| 565 | ||
| 566 |
#' Enable Link Generic |
|
| 567 |
#' |
|
| 568 |
#' @typed object: LongitudinalModel |
|
| 569 |
#' to enable link for. |
|
| 570 |
#' @param ... Not used. |
|
| 571 |
#' |
|
| 572 |
#' Optional hook method that is called on a [`LongitudinalModel`] only if a link method |
|
| 573 |
#' is provided to [`JointModel`]. This can be used to allow the model to include any |
|
| 574 |
#' optional stan code that is only required if there are links present. |
|
| 575 |
#' |
|
| 576 |
#' @return [`LongitudinalModel`] object |
|
| 577 |
#' |
|
| 578 |
#' @export |
|
| 579 |
#' |
|
| 580 |
#' @examples |
|
| 581 |
#' enableLink(LongitudinalGSF()) |
|
| 582 |
enableLink <- function(object, ...) {
|
|
| 583 | 30x |
UseMethod("enableLink")
|
| 584 |
} |
|
| 585 |
#' @export |
|
| 586 |
#' |
|
| 587 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 588 |
enableLink.default <- function(object, ...) {
|
|
| 589 | ! |
object |
| 590 |
} |
|
| 591 | ||
| 592 | ||
| 593 |
#' Enable Generated Quantities Generic |
|
| 594 |
#' |
|
| 595 |
#' @typed object: StanModel |
|
| 596 |
#' to enable generated quantities for. |
|
| 597 |
#' @param ... Not used. |
|
| 598 |
#' |
|
| 599 |
#' Optional hook method that is called on a [`StanModel`] if attempting to use |
|
| 600 |
#' either [`LongitudinalQuantities`] or [`SurvivalQuantities`] |
|
| 601 |
#' |
|
| 602 |
#' @return [`StanModule`] object |
|
| 603 |
#' |
|
| 604 |
#' @export |
|
| 605 |
#' |
|
| 606 |
#' @examples |
|
| 607 |
#' enableGQ(LongitudinalGSF()) |
|
| 608 |
enableGQ <- function(object, ...) {
|
|
| 609 | 153x |
UseMethod("enableGQ")
|
| 610 |
} |
|
| 611 |
#' @export |
|
| 612 |
#' |
|
| 613 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 614 |
enableGQ.default <- function(object, ...) {
|
|
| 615 | 53x |
StanModule() |
| 616 |
} |
|
| 617 | ||
| 618 | ||
| 619 |
#' Get Prediction Names |
|
| 620 |
#' |
|
| 621 |
#' Utility function that returns the names of the required parameters for predicting |
|
| 622 |
#' survival quantities with [`GridPrediction`]. |
|
| 623 |
#' |
|
| 624 |
#' @typed object: LongitudinalModel |
|
| 625 |
#' A longitudinal model object |
|
| 626 |
#' @param ... Not used. |
|
| 627 |
#' @export |
|
| 628 |
#' |
|
| 629 |
#' @returns A character vector of required prediction parameter names, or `NULL`. |
|
| 630 |
#' |
|
| 631 |
#' @examples |
|
| 632 |
#' getPredictionNames(LongitudinalGSF()) |
|
| 633 |
getPredictionNames <- function(object, ...) {
|
|
| 634 | 10x |
UseMethod("getPredictionNames")
|
| 635 |
} |
|
| 636 | ||
| 637 |
#' @rdname getPredictionNames |
|
| 638 |
#' @export |
|
| 639 |
getPredictionNames.default <- function(object, ...) {
|
|
| 640 | 1x |
NULL |
| 641 |
} |
|
| 642 | ||
| 643 | ||
| 644 |
#' Get Random Effects Names |
|
| 645 |
#' |
|
| 646 |
#' Utility function that returns the names of the random effects parameters. |
|
| 647 |
#' The main use for this is to allow the [`LongitudinalRandomEffects`] function |
|
| 648 |
#' to know which parameters it needs to extract and to what common names |
|
| 649 |
#' it should map the parameters to. |
|
| 650 |
#' |
|
| 651 |
#' @typed object: LongitudinalModel |
|
| 652 |
#' A longitudinal model object |
|
| 653 |
#' @param ... Not used. |
|
| 654 |
#' @export |
|
| 655 |
#' |
|
| 656 |
#' @returns A named character vector of random-effect parameter names, or `NULL`. |
|
| 657 |
#' |
|
| 658 |
#' @examples |
|
| 659 |
#' getRandomEffectsNames(LongitudinalGSF()) |
|
| 660 |
getRandomEffectsNames <- function(object, ...) {
|
|
| 661 | 8x |
UseMethod("getRandomEffectsNames")
|
| 662 |
} |
|
| 663 | ||
| 664 |
#' @rdname getRandomEffectsNames |
|
| 665 |
#' @export |
|
| 666 |
getRandomEffectsNames.default <- function(object, ...) {
|
|
| 667 | ! |
NULL |
| 668 |
} |
|
| 669 | ||
| 670 | ||
| 671 |
#' Required Longitudinal Covariates |
|
| 672 |
#' |
|
| 673 |
#' Return the subject-level covariates required to generate longitudinal |
|
| 674 |
#' population quantities for a model. |
|
| 675 |
#' |
|
| 676 |
#' @typed object: LongitudinalModel |
|
| 677 |
#' A longitudinal model object. |
|
| 678 |
#' @param ... Not used. |
|
| 679 |
#' @export |
|
| 680 |
#' |
|
| 681 |
#' @returns A character vector of covariate names. |
|
| 682 |
required_longitudinal_covs <- function(object, ...) {
|
|
| 683 | 17x |
UseMethod("required_longitudinal_covs")
|
| 684 |
} |
|
| 685 | ||
| 686 |
#' @rdname required_longitudinal_covs |
|
| 687 |
#' @export |
|
| 688 |
required_longitudinal_covs.default <- function(object, ...) {
|
|
| 689 | 9x |
character() |
| 690 |
} |
|
| 691 | ||
| 692 | ||
| 693 |
#' Required Longitudinal Simulation Covariates |
|
| 694 |
#' |
|
| 695 |
#' Return the subject-level covariates required to simulate a longitudinal |
|
| 696 |
#' model from posterior draws. Unlike [required_longitudinal_covs()], this |
|
| 697 |
#' includes covariates used only by variability predictors. |
|
| 698 |
#' |
|
| 699 |
#' @typed object: LongitudinalModel |
|
| 700 |
#' A longitudinal model object. |
|
| 701 |
#' @param ... Not used. |
|
| 702 |
#' @export |
|
| 703 |
#' |
|
| 704 |
#' @returns A character vector of covariate names. |
|
| 705 |
required_simulation_covariates <- function(object, ...) {
|
|
| 706 | 3x |
UseMethod("required_simulation_covariates")
|
| 707 |
} |
|
| 708 | ||
| 709 |
#' @rdname required_simulation_covariates |
|
| 710 |
#' @export |
|
| 711 |
required_simulation_covariates.default <- function(object, ...) {
|
|
| 712 | 1x |
character() |
| 713 |
} |
|
| 714 | ||
| 715 | ||
| 716 |
#' As Formula |
|
| 717 |
#' |
|
| 718 |
#' Utility wrapper function to convert an object to a formula. |
|
| 719 |
#' @typed x: ANY |
|
| 720 |
#' object to convert to a formula. |
|
| 721 |
#' @param ... Not used. |
|
| 722 |
#' @export |
|
| 723 |
#' |
|
| 724 |
#' @returns A `formula` object. |
|
| 725 |
#' |
|
| 726 |
#' @examples |
|
| 727 |
#' as_formula("response ~ time")
|
|
| 728 |
as_formula <- function(x, ...) {
|
|
| 729 | 7x |
UseMethod("as_formula")
|
| 730 |
} |
|
| 731 | ||
| 732 |
#' @importFrom stats as.formula |
|
| 733 |
#' @export |
|
| 734 |
#' |
|
| 735 |
#' @returns A `formula` object. |
|
| 736 |
as_formula.default <- function(x, ...) {
|
|
| 737 | ! |
as.formula(x, ...) |
| 738 |
} |
|
| 739 | ||
| 740 | ||
| 741 |
#' Set Constraints |
|
| 742 |
#' |
|
| 743 |
#' Applies constraints to a prior distribution to ensure any sampled numbers |
|
| 744 |
#' from the distribution fall within the constraints |
|
| 745 |
#' |
|
| 746 |
#' @typed object: Prior |
|
| 747 |
#' a prior distribution to apply constraints to |
|
| 748 |
#' @typed lower: numeric |
|
| 749 |
#' lower constraint boundary |
|
| 750 |
#' @typed upper: numeric |
|
| 751 |
#' upper constraint boundary |
|
| 752 |
#' |
|
| 753 |
#' @export |
|
| 754 |
#' |
|
| 755 |
#' @returns A `Prior` object with the requested bounds. |
|
| 756 |
#' |
|
| 757 |
#' @examples |
|
| 758 |
#' set_limits(prior_normal(0, 1), lower = 0) |
|
| 759 |
set_limits <- function(object, lower = -Inf, upper = Inf) {
|
|
| 760 | 733x |
UseMethod("set_limits")
|
| 761 |
} |
|
| 762 | ||
| 763 | ||
| 764 |
#' Save Object to File |
|
| 765 |
#' |
|
| 766 |
#' @typed object: ANY |
|
| 767 |
#' object to save. |
|
| 768 |
#' @typed file: character |
|
| 769 |
#' file to save object to. |
|
| 770 |
#' @typed ...: ANY |
|
| 771 |
#' additional arguments. |
|
| 772 |
#' |
|
| 773 |
#' @family saveObject |
|
| 774 |
#' @export |
|
| 775 |
#' |
|
| 776 |
#' @returns Invisibly returns `NULL` after saving the object. |
|
| 777 |
#' |
|
| 778 |
#' @examples |
|
| 779 |
#' \dontrun{
|
|
| 780 |
#' saveObject(fit, "joint-model-fit.rds") |
|
| 781 |
#' } |
|
| 782 |
saveObject <- function(object, file, ...) {
|
|
| 783 | 1x |
UseMethod("saveObject")
|
| 784 |
} |
|
| 785 | ||
| 786 | ||
| 787 |
#' Extract Covariate Names |
|
| 788 |
#' |
|
| 789 |
#' @typed object: ANY |
|
| 790 |
#' the object to extract covariate names from. |
|
| 791 |
#' @param ... additional arguments added by methods. |
|
| 792 |
#' |
|
| 793 |
#' @family covariates |
|
| 794 |
#' @export |
|
| 795 |
#' |
|
| 796 |
#' @returns For [`DataSurvival`], a character vector containing the covariate |
|
| 797 |
#' names. For [`LongitudinalModel`], a named list of character vectors, one |
|
| 798 |
#' per covariate coefficient parameter; see [covariates.LongitudinalModel()]. |
|
| 799 |
#' |
|
| 800 |
#' @examples |
|
| 801 |
#' surv_data <- DataSurvival(os_data, Surv(os_time, os_event) ~ age + sex) |
|
| 802 |
#' covariates(surv_data) |
|
| 803 |
covariates <- function(object, ...) {
|
|
| 804 | 45x |
UseMethod("covariates")
|
| 805 |
} |
|
| 806 | ||
| 807 |
#' Extract Covariate Shrinkage Factors from the Posterior Samples |
|
| 808 |
#' |
|
| 809 |
#' @typed object: ANY |
|
| 810 |
#' the object to extract shrinkage factors from. |
|
| 811 |
#' @param ... additional arguments added by methods. |
|
| 812 |
#' |
|
| 813 |
#' @family shrinkage |
|
| 814 |
#' @export |
|
| 815 |
#' |
|
| 816 |
#' @returns For [`JointModelSamples`], a posterior draws object containing the |
|
| 817 |
#' sampled prior shrinkage factors; see [shrinkage.JointModelSamples()]. |
|
| 818 |
#' |
|
| 819 |
#' @examples |
|
| 820 |
#' \dontrun{
|
|
| 821 |
#' shrinkage(fit) |
|
| 822 |
#' } |
|
| 823 |
shrinkage <- function(object, ...) {
|
|
| 824 | 11x |
UseMethod("shrinkage")
|
| 825 |
} |
| 1 |
#' @include LongitudinalRandomSlopeCov.R |
|
| 2 |
#' @include LongitudinalSteinFojo.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Stein-Fojo longitudinal model with subject-level covariates |
|
| 6 |
#' |
|
| 7 |
#' Models the log-normal distribution parameters for baseline, shrinkage, and |
|
| 8 |
#' growth using separate subject-level covariate predictors. |
|
| 9 |
#' |
|
| 10 |
#' @section Available Links: |
|
| 11 |
#' - [`linkDSLD()`] |
|
| 12 |
#' - [`linkTTG()`] |
|
| 13 |
#' - [`linkIdentity()`] |
|
| 14 |
#' - [`linkGrowth()`] |
|
| 15 |
#' - [`linkShrinkage()`] |
|
| 16 |
#' |
|
| 17 |
#' @exportClass LongitudinalSteinFojoCov |
|
| 18 |
.LongitudinalSteinFojoCov <- setClass( |
|
| 19 |
Class = "LongitudinalSteinFojoCov", |
|
| 20 |
contains = "LongitudinalModel", |
|
| 21 |
slots = c( |
|
| 22 |
mu_b_formula = "formula", |
|
| 23 |
omega_b_formula = "formula", |
|
| 24 |
mu_s_formula = "formula", |
|
| 25 |
omega_s_formula = "formula", |
|
| 26 |
mu_g_formula = "formula", |
|
| 27 |
omega_g_formula = "formula", |
|
| 28 |
mu_b_parametrization = "character", |
|
| 29 |
omega_b_parametrization = "character", |
|
| 30 |
mu_s_parametrization = "character", |
|
| 31 |
omega_s_parametrization = "character", |
|
| 32 |
mu_g_parametrization = "character", |
|
| 33 |
omega_g_parametrization = "character", |
|
| 34 |
centred_baseline = "logical", |
|
| 35 |
centred_shrinkage = "logical", |
|
| 36 |
centred_growth = "logical" |
|
| 37 |
) |
|
| 38 |
) |
|
| 39 | ||
| 40 |
#' Construct a Stein-Fojo model with subject-level covariates |
|
| 41 |
#' |
|
| 42 |
#' The intercept column generated by each formula is removed. The separately |
|
| 43 |
#' specified intercept parameter is the reference-level value except for the |
|
| 44 |
#' `exponential` parametrization, whose reference-level value is one. |
|
| 45 |
#' `linear` uses `intercept + X beta`, `proportional` uses |
|
| 46 |
#' `intercept * (1 + X beta)`, `exponential` uses |
|
| 47 |
#' `intercept ^ (X beta)`, and `log-linear` uses |
|
| 48 |
#' `exp(intercept + X beta)`. |
|
| 49 |
#' |
|
| 50 |
#' @rdname LongitudinalSteinFojoCov-class |
|
| 51 |
#' @typed mu_b_formula: formula |
|
| 52 |
#' One-sided formula for subject-level covariate effects on the baseline mean. |
|
| 53 |
#' @typed mu_s_formula: formula |
|
| 54 |
#' One-sided formula for subject-level covariate effects on the shrinkage mean. |
|
| 55 |
#' @typed mu_g_formula: formula |
|
| 56 |
#' One-sided formula for subject-level covariate effects on the growth mean. |
|
| 57 |
#' @typed omega_b_formula: formula |
|
| 58 |
#' One-sided formula for subject-level covariate effects on the baseline standard deviation. |
|
| 59 |
#' @typed omega_s_formula: formula |
|
| 60 |
#' One-sided formula for subject-level covariate effects on the shrinkage standard deviation. |
|
| 61 |
#' @typed omega_g_formula: formula |
|
| 62 |
#' One-sided formula for subject-level covariate effects on the growth standard deviation. |
|
| 63 |
#' @typed mu_b_parametrization: character |
|
| 64 |
#' Parametrization of the baseline mean predictor. |
|
| 65 |
#' @typed mu_s_parametrization: character |
|
| 66 |
#' Parametrization of the shrinkage mean predictor. |
|
| 67 |
#' @typed mu_g_parametrization: character |
|
| 68 |
#' Parametrization of the growth mean predictor. |
|
| 69 |
#' @typed omega_b_parametrization: character |
|
| 70 |
#' Parametrization of the baseline standard deviation predictor. Only |
|
| 71 |
#' `"exponential"` and `"log-linear"` are supported. |
|
| 72 |
#' @typed omega_s_parametrization: character |
|
| 73 |
#' Parametrization of the shrinkage standard deviation predictor. Only |
|
| 74 |
#' `"exponential"` and `"log-linear"` are supported. |
|
| 75 |
#' @typed omega_g_parametrization: character |
|
| 76 |
#' Parametrization of the growth standard deviation predictor. Only `"exponential"` and `"log-linear"` are supported. |
|
| 77 |
#' @typed mu_b_intercept_prior: Prior |
|
| 78 |
#' Prior for the intercept of the baseline mean predictor. |
|
| 79 |
#' @typed mu_b_coefficients_prior: Prior |
|
| 80 |
#' Prior for the covariate coefficients of the baseline mean predictor. |
|
| 81 |
#' @typed omega_b_intercept_prior: Prior |
|
| 82 |
#' Prior for the intercept of the baseline standard deviation predictor. |
|
| 83 |
#' @typed omega_b_coefficients_prior: Prior |
|
| 84 |
#' Prior for the covariate coefficients of the baseline standard deviation predictor. |
|
| 85 |
#' @typed mu_s_intercept_prior: Prior |
|
| 86 |
#' Prior for the intercept of the shrinkage mean predictor. |
|
| 87 |
#' @typed mu_s_coefficients_prior: Prior |
|
| 88 |
#' Prior for the covariate coefficients of the shrinkage mean predictor. |
|
| 89 |
#' @typed omega_s_intercept_prior: Prior |
|
| 90 |
#' Prior for the intercept of the shrinkage standard deviation predictor. |
|
| 91 |
#' @typed omega_s_coefficients_prior: Prior |
|
| 92 |
#' Prior for the covariate coefficients of the shrinkage standard deviation predictor. |
|
| 93 |
#' @typed mu_g_intercept_prior: Prior |
|
| 94 |
#' Prior for the intercept of the growth mean predictor. |
|
| 95 |
#' @typed mu_g_coefficients_prior: Prior |
|
| 96 |
#' Prior for the covariate coefficients of the growth mean predictor. |
|
| 97 |
#' @typed omega_g_intercept_prior: Prior |
|
| 98 |
#' Prior for the intercept of the growth standard deviation predictor. |
|
| 99 |
#' @typed omega_g_coefficients_prior: Prior |
|
| 100 |
#' Prior for the covariate coefficients of the growth standard deviation predictor. |
|
| 101 |
#' @typed sigma: Prior |
|
| 102 |
#' Prior for the observation-error standard deviation. |
|
| 103 |
#' @typed scaled_variance: logical |
|
| 104 |
#' Whether to use multiplicative observation error. |
|
| 105 |
#' @typed centred_baseline: logical |
|
| 106 |
#' Whether to use a centred parameterization for the subject-specific baseline effects. |
|
| 107 |
#' @typed centred_shrinkage: logical |
|
| 108 |
#' Whether to use a centred parameterization for the subject-specific shrinkage effects. |
|
| 109 |
#' @typed centred_growth: logical |
|
| 110 |
#' Whether to use a centred parameterization for the subject-specific growth effects. |
|
| 111 |
#' |
|
| 112 |
#' @returns A `LongitudinalSteinFojoCov` object. |
|
| 113 |
#' @export |
|
| 114 |
#' |
|
| 115 |
#' @examples |
|
| 116 |
#' LongitudinalSteinFojoCov() |
|
| 117 |
LongitudinalSteinFojoCov <- function( |
|
| 118 |
mu_b_formula = ~study, |
|
| 119 |
omega_b_formula = ~study, |
|
| 120 |
mu_s_formula = ~arm, |
|
| 121 |
omega_s_formula = ~arm, |
|
| 122 |
mu_g_formula = ~arm, |
|
| 123 |
omega_g_formula = ~arm, |
|
| 124 |
mu_b_parametrization = "linear", |
|
| 125 |
omega_b_parametrization = "log-linear", |
|
| 126 |
mu_s_parametrization = "linear", |
|
| 127 |
omega_s_parametrization = "log-linear", |
|
| 128 |
mu_g_parametrization = "linear", |
|
| 129 |
omega_g_parametrization = "log-linear", |
|
| 130 |
mu_b_intercept_prior = prior_normal(log(60), 1), |
|
| 131 |
mu_b_coefficients_prior = prior_normal(0, 1), |
|
| 132 |
omega_b_intercept_prior = prior_normal(log(0.2), 1), |
|
| 133 |
omega_b_coefficients_prior = prior_normal(0, 1), |
|
| 134 |
mu_s_intercept_prior = prior_normal(log(0.5), 1), |
|
| 135 |
mu_s_coefficients_prior = prior_normal(0, 1), |
|
| 136 |
omega_s_intercept_prior = prior_normal(log(0.2), 1), |
|
| 137 |
omega_s_coefficients_prior = prior_normal(0, 1), |
|
| 138 |
mu_g_intercept_prior = prior_normal(log(0.3), 1), |
|
| 139 |
mu_g_coefficients_prior = prior_normal(0, 1), |
|
| 140 |
omega_g_intercept_prior = prior_normal(log(0.2), 1), |
|
| 141 |
omega_g_coefficients_prior = prior_normal(0, 1), |
|
| 142 |
sigma = prior_lognormal(log(0.1), 1), |
|
| 143 |
scaled_variance = FALSE, |
|
| 144 |
centred_baseline = TRUE, |
|
| 145 |
centred_shrinkage = FALSE, |
|
| 146 |
centred_growth = FALSE |
|
| 147 |
) {
|
|
| 148 | 24x |
assert_flag(centred_baseline) |
| 149 | 24x |
assert_flag(centred_shrinkage) |
| 150 | 24x |
assert_flag(centred_growth) |
| 151 | ||
| 152 | 24x |
formula_names <- c( |
| 153 | 24x |
"mu_b", |
| 154 | 24x |
"omega_b", |
| 155 | 24x |
"mu_s", |
| 156 | 24x |
"omega_s", |
| 157 | 24x |
"mu_g", |
| 158 | 24x |
"omega_g" |
| 159 |
) |
|
| 160 | 24x |
formulas <- mget(paste0(formula_names, "_formula"), inherits = FALSE) |
| 161 | 24x |
formulas <- Map( |
| 162 | 24x |
.validate_covariate_formula, |
| 163 | 24x |
formulas, |
| 164 | 24x |
paste0(formula_names, "_formula") |
| 165 |
) |
|
| 166 | 24x |
names(formulas) <- formula_names |
| 167 | ||
| 168 | 24x |
parametrizations <- mget( |
| 169 | 24x |
paste0(formula_names, "_parametrization"), |
| 170 | 24x |
inherits = FALSE |
| 171 |
) |
|
| 172 | 24x |
parametrizations <- Map( |
| 173 | 24x |
function(value, name) {
|
| 174 | 136x |
.validate_covariate_parametrization( |
| 175 | 136x |
value, |
| 176 | 136x |
paste0(name, "_parametrization"), |
| 177 | 136x |
scale = startsWith(name, "omega_") |
| 178 |
) |
|
| 179 |
}, |
|
| 180 | 24x |
parametrizations, |
| 181 | 24x |
formula_names |
| 182 |
) |
|
| 183 | 22x |
names(parametrizations) <- formula_names |
| 184 | ||
| 185 | 22x |
for (name in c("omega_b", "omega_s", "omega_g")) {
|
| 186 | 66x |
prior_name <- paste0(name, "_intercept_prior") |
| 187 | 66x |
assign( |
| 188 | 66x |
prior_name, |
| 189 | 66x |
.positive_intercept_prior( |
| 190 | 66x |
get(prior_name, inherits = FALSE), |
| 191 | 66x |
parametrizations[[name]], |
| 192 | 66x |
positive = parametrizations[[name]] != "log-linear" |
| 193 |
) |
|
| 194 |
) |
|
| 195 |
} |
|
| 196 | 22x |
for (name in c("mu_b", "mu_s", "mu_g")) {
|
| 197 | 66x |
prior_name <- paste0(name, "_intercept_prior") |
| 198 | 66x |
assign( |
| 199 | 66x |
prior_name, |
| 200 | 66x |
.positive_intercept_prior( |
| 201 | 66x |
get(prior_name, inherits = FALSE), |
| 202 | 66x |
parametrizations[[name]] |
| 203 |
) |
|
| 204 |
) |
|
| 205 |
} |
|
| 206 | 22x |
sigma <- set_limits(sigma, lower = getOption("jmpost.double_eps"))
|
| 207 | ||
| 208 | 22x |
stan_predictors <- lapply(formula_names, function(name) {
|
| 209 | 132x |
.covariate_predictor_stan( |
| 210 | 132x |
paste0("lm_sfc_", name),
|
| 211 | 132x |
parametrizations[[name]] |
| 212 |
) |
|
| 213 |
}) |
|
| 214 | 22x |
names(stan_predictors) <- formula_names |
| 215 | 22x |
stan <- StanModule(decorated_render( |
| 216 | 22x |
.x = read_stan("lm-stein-fojo-cov/model.stan"),
|
| 217 | 22x |
scaled_variance = scaled_variance, |
| 218 | 22x |
centred_baseline = centred_baseline, |
| 219 | 22x |
centred_shrinkage = centred_shrinkage, |
| 220 | 22x |
centred_growth = centred_growth, |
| 221 | 22x |
mu_b_predictor = stan_predictors$mu_b, |
| 222 | 22x |
omega_b_predictor = stan_predictors$omega_b, |
| 223 | 22x |
mu_s_predictor = stan_predictors$mu_s, |
| 224 | 22x |
omega_s_predictor = stan_predictors$omega_s, |
| 225 | 22x |
mu_g_predictor = stan_predictors$mu_g, |
| 226 | 22x |
omega_g_predictor = stan_predictors$omega_g |
| 227 |
)) |
|
| 228 | ||
| 229 | 22x |
parameters <- list() |
| 230 | 22x |
for (name in formula_names) {
|
| 231 | 132x |
parameters <- append( |
| 232 | 132x |
parameters, |
| 233 | 132x |
list( |
| 234 | 132x |
Parameter( |
| 235 | 132x |
name = paste0("lm_sfc_", name, "_intercept"),
|
| 236 | 132x |
prior = get( |
| 237 | 132x |
paste0(name, "_intercept_prior"), |
| 238 | 132x |
inherits = FALSE |
| 239 |
) |
|
| 240 |
), |
|
| 241 | 132x |
Parameter( |
| 242 | 132x |
name = paste0("lm_sfc_", name, "_coefficients"),
|
| 243 | 132x |
prior = get( |
| 244 | 132x |
paste0(name, "_coefficients_prior"), |
| 245 | 132x |
inherits = FALSE |
| 246 |
), |
|
| 247 | 132x |
size = paste0("p_lm_sfc_", name)
|
| 248 |
) |
|
| 249 |
) |
|
| 250 |
) |
|
| 251 |
} |
|
| 252 | 22x |
intercept_priors <- mget( |
| 253 | 22x |
paste0(formula_names, "_intercept_prior"), |
| 254 | 22x |
inherits = FALSE |
| 255 |
) |
|
| 256 | 22x |
names(intercept_priors) <- formula_names |
| 257 | 22x |
subject_parameter <- function(name, centred) {
|
| 258 | 66x |
if (!centred) {
|
| 259 | 42x |
return(Parameter( |
| 260 | 42x |
name = paste0("lm_sfc_eta_tilde_", name),
|
| 261 | 42x |
prior = prior_std_normal(), |
| 262 | 42x |
size = "n_subjects" |
| 263 |
)) |
|
| 264 |
} |
|
| 265 | 24x |
mu_prior <- intercept_priors[[paste0("mu_", name)]]
|
| 266 | 24x |
omega_prior <- intercept_priors[[paste0("omega_", name)]]
|
| 267 | 24x |
Parameter( |
| 268 | 24x |
name = paste0("lm_sfc_psi_", name),
|
| 269 | 24x |
prior = set_limits( |
| 270 | 24x |
prior_init_only(prior_lognormal( |
| 271 | 24x |
.predictor_reference_value( |
| 272 | 24x |
mu_prior, |
| 273 | 24x |
parametrizations[[paste0("mu_", name)]]
|
| 274 |
), |
|
| 275 | 24x |
.predictor_reference_value( |
| 276 | 24x |
omega_prior, |
| 277 | 24x |
parametrizations[[paste0("omega_", name)]]
|
| 278 |
) |
|
| 279 |
)), |
|
| 280 | 24x |
lower = getOption("jmpost.double_eps")
|
| 281 |
), |
|
| 282 | 24x |
size = "n_subjects" |
| 283 |
) |
|
| 284 |
} |
|
| 285 | 22x |
parameters <- append( |
| 286 | 22x |
parameters, |
| 287 | 22x |
c( |
| 288 | 22x |
list(Parameter(name = "lm_sfc_sigma", prior = sigma)), |
| 289 | 22x |
unname(Map( |
| 290 | 22x |
subject_parameter, |
| 291 | 22x |
name = c("b", "s", "g"),
|
| 292 | 22x |
centred = c( |
| 293 | 22x |
centred_baseline, |
| 294 | 22x |
centred_shrinkage, |
| 295 | 22x |
centred_growth |
| 296 |
) |
|
| 297 |
)) |
|
| 298 |
) |
|
| 299 |
) |
|
| 300 | ||
| 301 | 22x |
.LongitudinalSteinFojoCov( |
| 302 | 22x |
LongitudinalModel( |
| 303 | 22x |
name = "Stein-Fojo with Covariates", |
| 304 | 22x |
stan = merge(stan, StanModule("lm-stein-fojo/functions.stan")),
|
| 305 | 22x |
parameters = do.call(ParameterList, parameters), |
| 306 | 22x |
scaled_variance = scaled_variance |
| 307 |
), |
|
| 308 | 22x |
mu_b_formula = formulas$mu_b, |
| 309 | 22x |
omega_b_formula = formulas$omega_b, |
| 310 | 22x |
mu_s_formula = formulas$mu_s, |
| 311 | 22x |
omega_s_formula = formulas$omega_s, |
| 312 | 22x |
mu_g_formula = formulas$mu_g, |
| 313 | 22x |
omega_g_formula = formulas$omega_g, |
| 314 | 22x |
mu_b_parametrization = parametrizations$mu_b, |
| 315 | 22x |
omega_b_parametrization = parametrizations$omega_b, |
| 316 | 22x |
mu_s_parametrization = parametrizations$mu_s, |
| 317 | 22x |
omega_s_parametrization = parametrizations$omega_s, |
| 318 | 22x |
mu_g_parametrization = parametrizations$mu_g, |
| 319 | 22x |
omega_g_parametrization = parametrizations$omega_g, |
| 320 | 22x |
centred_baseline = centred_baseline, |
| 321 | 22x |
centred_shrinkage = centred_shrinkage, |
| 322 | 22x |
centred_growth = centred_growth |
| 323 |
) |
|
| 324 |
} |
|
| 325 | ||
| 326 |
#' @export |
|
| 327 |
#' |
|
| 328 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 329 |
enableGQ.LongitudinalSteinFojoCov <- function( |
|
| 330 |
object, |
|
| 331 |
generator = NULL, |
|
| 332 |
type = NULL, |
|
| 333 |
... |
|
| 334 |
) {
|
|
| 335 | 1x |
include_subject <- identical(type, "longitudinal") && |
| 336 | 1x |
is(generator, "QuantityGeneratorSubject") |
| 337 | 1x |
include_population <- identical(type, "longitudinal") && |
| 338 | 1x |
is(generator, "QuantityGeneratorPopulation") |
| 339 | 1x |
predictor <- function(name) {
|
| 340 | 3x |
.covariate_predictor_stan( |
| 341 | 3x |
paste0("lm_sfc_", name),
|
| 342 | 3x |
slot(object, paste0(name, "_parametrization")), |
| 343 | 3x |
design_prefix = paste0("gq_lm_sfc_", name),
|
| 344 | 3x |
n_rows = "gq_n_quant" |
| 345 |
) |
|
| 346 |
} |
|
| 347 | 1x |
StanModule(decorated_render( |
| 348 | 1x |
.x = read_stan("lm-stein-fojo-cov/quantities.stan"),
|
| 349 | 1x |
include_gq_longitudinal_idv = include_subject, |
| 350 | 1x |
include_gq_longitudinal_pop = include_population, |
| 351 | 1x |
mu_b_population_predictor = predictor("mu_b"),
|
| 352 | 1x |
mu_s_population_predictor = predictor("mu_s"),
|
| 353 | 1x |
mu_g_population_predictor = predictor("mu_g")
|
| 354 |
)) |
|
| 355 |
} |
|
| 356 | ||
| 357 |
#' @rdname gq_population_stan_data |
|
| 358 |
#' @export |
|
| 359 |
gq_population_stan_data.LongitudinalSteinFojoCov <- function( |
|
| 360 |
object, |
|
| 361 |
model, |
|
| 362 |
data = NULL, |
|
| 363 |
... |
|
| 364 |
) {
|
|
| 365 | 1x |
result <- list( |
| 366 | 1x |
declarations = paste( |
| 367 | 1x |
"matrix[gq_n_quant, p_lm_sfc_mu_b] gq_lm_sfc_mu_b_design;", |
| 368 | 1x |
"matrix[gq_n_quant, p_lm_sfc_mu_s] gq_lm_sfc_mu_s_design;", |
| 369 | 1x |
"matrix[gq_n_quant, p_lm_sfc_mu_g] gq_lm_sfc_mu_g_design;", |
| 370 | 1x |
sep = "\n" |
| 371 |
), |
|
| 372 | 1x |
data = list() |
| 373 |
) |
|
| 374 | 1x |
if (!is.null(data)) {
|
| 375 | ! |
assert_that( |
| 376 | ! |
!is.null(object@newdata) && |
| 377 | ! |
nrow(object@newdata) == length(object@times), |
| 378 | ! |
msg = paste0( |
| 379 | ! |
"Population quantities for `LongitudinalSteinFojoCov` ", |
| 380 | ! |
"require `GridPopulation(newdata = ...)`" |
| 381 |
) |
|
| 382 |
) |
|
| 383 | ! |
result$data <- .stein_fojo_cov_population_stan_data( |
| 384 | ! |
model, |
| 385 | ! |
data@subject, |
| 386 | ! |
object@newdata |
| 387 |
) |
|
| 388 |
} |
|
| 389 | 1x |
result |
| 390 |
} |
|
| 391 | ||
| 392 |
#' @export |
|
| 393 |
#' |
|
| 394 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 395 |
enableLink.LongitudinalSteinFojoCov <- function(object, ...) {
|
|
| 396 | 2x |
object@stan <- merge( |
| 397 | 2x |
object@stan, |
| 398 | 2x |
StanModule("lm-stein-fojo-cov/link.stan")
|
| 399 |
) |
|
| 400 | 2x |
object |
| 401 |
} |
|
| 402 | ||
| 403 |
#' @export |
|
| 404 |
#' @returns A `LinkComponent` object. |
|
| 405 |
linkDSLD.LongitudinalSteinFojoCov <- function( |
|
| 406 |
prior = prior_normal(0, 2), |
|
| 407 |
model, |
|
| 408 |
... |
|
| 409 |
) {
|
|
| 410 | 2x |
LinkComponent( |
| 411 | 2x |
key = "link_dsld", |
| 412 | 2x |
stan = StanModule("lm-stein-fojo/link_dsld.stan"),
|
| 413 | 2x |
prior = prior |
| 414 |
) |
|
| 415 |
} |
|
| 416 | ||
| 417 |
#' @export |
|
| 418 |
#' @returns A `LinkComponent` object. |
|
| 419 |
linkTTG.LongitudinalSteinFojoCov <- function( |
|
| 420 |
prior = prior_normal(0, 2), |
|
| 421 |
model, |
|
| 422 |
... |
|
| 423 |
) {
|
|
| 424 | ! |
LinkComponent( |
| 425 | ! |
key = "link_ttg", |
| 426 | ! |
stan = StanModule("lm-stein-fojo/link_ttg.stan"),
|
| 427 | ! |
prior = prior |
| 428 |
) |
|
| 429 |
} |
|
| 430 | ||
| 431 |
#' @export |
|
| 432 |
#' @returns A `LinkComponent` object. |
|
| 433 |
linkIdentity.LongitudinalSteinFojoCov <- function( |
|
| 434 |
prior = prior_normal(0, 2), |
|
| 435 |
model, |
|
| 436 |
... |
|
| 437 |
) {
|
|
| 438 | ! |
LinkComponent( |
| 439 | ! |
key = "link_identity", |
| 440 | ! |
stan = StanModule("lm-stein-fojo/link_identity.stan"),
|
| 441 | ! |
prior = prior |
| 442 |
) |
|
| 443 |
} |
|
| 444 | ||
| 445 |
#' @export |
|
| 446 |
#' @returns A `LinkComponent` object. |
|
| 447 |
linkGrowth.LongitudinalSteinFojoCov <- function( |
|
| 448 |
prior = prior_normal(0, 2), |
|
| 449 |
model, |
|
| 450 |
... |
|
| 451 |
) {
|
|
| 452 | ! |
LinkComponent( |
| 453 | ! |
key = "link_growth", |
| 454 | ! |
stan = StanModule("lm-stein-fojo/link_growth.stan"),
|
| 455 | ! |
prior = prior |
| 456 |
) |
|
| 457 |
} |
|
| 458 | ||
| 459 |
#' @export |
|
| 460 |
#' @returns A `LinkComponent` object. |
|
| 461 |
linkShrinkage.LongitudinalSteinFojoCov <- function( |
|
| 462 |
prior = prior_normal(0, 2), |
|
| 463 |
model, |
|
| 464 |
... |
|
| 465 |
) {
|
|
| 466 | ! |
LinkComponent( |
| 467 | ! |
key = "link_shrinkage", |
| 468 | ! |
stan = StanModule("lm-stein-fojo/link_shrinkage.stan"),
|
| 469 | ! |
prior = prior |
| 470 |
) |
|
| 471 |
} |
|
| 472 | ||
| 473 |
#' @rdname getPredictionNames |
|
| 474 |
#' @export |
|
| 475 |
getPredictionNames.LongitudinalSteinFojoCov <- function(object, ...) {
|
|
| 476 | 1x |
c("b", "s", "g")
|
| 477 |
} |
|
| 478 | ||
| 479 |
#' @rdname getRandomEffectsNames |
|
| 480 |
#' @export |
|
| 481 |
getRandomEffectsNames.LongitudinalSteinFojoCov <- function(object, ...) {
|
|
| 482 | 1x |
c(b = "lm_sfc_psi_b", s = "lm_sfc_psi_s", g = "lm_sfc_psi_g") |
| 483 |
} |
|
| 484 | ||
| 485 |
#' @rdname longitudinal_model_stan_data |
|
| 486 |
#' @keywords internal |
|
| 487 |
#' @export |
|
| 488 |
longitudinal_model_stan_data.LongitudinalSteinFojoCov <- function( |
|
| 489 |
model, |
|
| 490 |
subject |
|
| 491 |
) {
|
|
| 492 | 12x |
subject_data <- as.data.frame(harmonise(subject)) |
| 493 | 12x |
parameter_names <- c( |
| 494 | 12x |
"mu_b", |
| 495 | 12x |
"omega_b", |
| 496 | 12x |
"mu_s", |
| 497 | 12x |
"omega_s", |
| 498 | 12x |
"mu_g", |
| 499 | 12x |
"omega_g" |
| 500 |
) |
|
| 501 | 12x |
designs <- lapply(parameter_names, function(name) {
|
| 502 | 71x |
.covariate_design_matrix( |
| 503 | 71x |
slot(model, paste0(name, "_formula")), |
| 504 | 71x |
subject_data, |
| 505 | 71x |
paste0(name, "_formula") |
| 506 |
) |
|
| 507 |
}) |
|
| 508 | 11x |
names(designs) <- parameter_names |
| 509 | 11x |
result <- list() |
| 510 | 11x |
for (name in parameter_names) {
|
| 511 | 66x |
result[[paste0("p_lm_sfc_", name)]] <- ncol(designs[[name]])
|
| 512 | 66x |
result[[paste0("lm_sfc_", name, "_design")]] <- designs[[name]]
|
| 513 |
} |
|
| 514 | 11x |
result |
| 515 |
} |
|
| 516 | ||
| 517 |
#' @rdname required_longitudinal_covs |
|
| 518 |
#' @export |
|
| 519 |
required_longitudinal_covs.LongitudinalSteinFojoCov <- function(object, ...) {
|
|
| 520 | 1x |
unique(unlist(lapply(c("mu_b", "mu_s", "mu_g"), function(name) {
|
| 521 | 3x |
all.vars(slot(object, paste0(name, "_formula"))) |
| 522 |
}))) |
|
| 523 |
} |
|
| 524 | ||
| 525 |
#' @rdname required_simulation_covariates |
|
| 526 |
#' @export |
|
| 527 |
required_simulation_covariates.LongitudinalSteinFojoCov <- function( |
|
| 528 |
object, |
|
| 529 |
... |
|
| 530 |
) {
|
|
| 531 | 1x |
unique(unlist(lapply( |
| 532 | 1x |
c("mu_b", "omega_b", "mu_s", "omega_s", "mu_g", "omega_g"),
|
| 533 | 1x |
function(name) all.vars(slot(object, paste0(name, "_formula"))) |
| 534 |
))) |
|
| 535 |
} |
|
| 536 | ||
| 537 |
#' Create covariate Stein-Fojo population-prediction Stan data |
|
| 538 |
#' |
|
| 539 |
#' @keywords internal |
|
| 540 |
#' @returns A named list of population generated-quantities design matrices. |
|
| 541 |
.stein_fojo_cov_population_stan_data <- function(model, subject, newdata) {
|
|
| 542 | ! |
subject_data <- as.data.frame(harmonise(subject)) |
| 543 | ! |
parameter_names <- c("mu_b", "mu_s", "mu_g")
|
| 544 | ! |
result <- lapply(parameter_names, function(name) {
|
| 545 | ! |
.covariate_prediction_design_matrix( |
| 546 | ! |
slot(model, paste0(name, "_formula")), |
| 547 | ! |
newdata, |
| 548 | ! |
subject_data, |
| 549 | ! |
paste0(name, "_formula") |
| 550 |
) |
|
| 551 |
}) |
|
| 552 | ! |
names(result) <- paste0("gq_lm_sfc_", parameter_names, "_design")
|
| 553 | ! |
result |
| 554 |
} |
| 1 |
#' `SimSurvival` Function Arguments |
|
| 2 |
#' |
|
| 3 |
#' The documentation lists all the conventional arguments for [`SimSurvival`] |
|
| 4 |
#' constructors. |
|
| 5 |
#' |
|
| 6 |
#' @typed time_max: number |
|
| 7 |
#' the maximum time to simulate to. |
|
| 8 |
#' @typed time_step: number |
|
| 9 |
#' the time interval between evaluating the log-hazard function. |
|
| 10 |
#' @typed lambda_censor: number |
|
| 11 |
#' the censoring rate. |
|
| 12 |
#' @typed beta_cont: number |
|
| 13 |
#' the continuous covariate coefficient. |
|
| 14 |
#' @typed beta_cat: numeric |
|
| 15 |
#' the categorical covariate coefficients. |
|
| 16 |
#' @typed loghazard: function |
|
| 17 |
#' the log hazard function. |
|
| 18 |
#' @typed name: character |
|
| 19 |
#' the name of the object. |
|
| 20 |
#' @param ... Not Used. |
|
| 21 |
#' |
|
| 22 |
#' @section Hazard Evaluation: |
|
| 23 |
#' |
|
| 24 |
#' Event times are simulated by sampling a cumulative hazard limit from a \eqn{U(0, 1)} distribution
|
|
| 25 |
#' for |
|
| 26 |
#' each subject and then counting how much hazard they've been exposed to by evaluating the |
|
| 27 |
#' log-hazard function at a set interval. The `time_max` argument sets the upper bound for the |
|
| 28 |
#' number of time points to evaluate the log-hazard function at with subjects who have not had an |
|
| 29 |
#' event being censored at `time_max`. The `time_step` argument sets the interval at which to |
|
| 30 |
#' evaluate the log-hazard function. Setting smaller values for `time_step` will increase the |
|
| 31 |
#' precision of the simulation at the cost of increased computation time. Likewise, setting large |
|
| 32 |
#' values for `time_max` will minimize the number of censored subjects at the cost of |
|
| 33 |
#' increased computation time. |
|
| 34 |
#' |
|
| 35 |
#' @name SimSurvival-Shared |
|
| 36 |
#' @keywords internal |
|
| 37 |
NULL |
|
| 38 | ||
| 39 | ||
| 40 |
#' Abstract Simulation Class for Survival Data |
|
| 41 |
#' |
|
| 42 |
#' @inheritParams SimSurvival-Shared |
|
| 43 |
#' @inheritSection SimSurvival-Shared Hazard Evaluation |
|
| 44 |
#' |
|
| 45 |
#' @slot time_max (`numeric`)\cr See arguments. |
|
| 46 |
#' @slot time_step (`numeric`)\cr See arguments. |
|
| 47 |
#' @slot lambda_censor (`numeric`)\cr See arguments. |
|
| 48 |
#' @slot beta_cont (`numeric`)\cr See arguments. |
|
| 49 |
#' @slot beta_cat (`numeric`)\cr See arguments. |
|
| 50 |
#' @slot loghazard (`function`)\cr See arguments. |
|
| 51 |
#' @slot name (`character`)\cr See arguments. |
|
| 52 |
#' @slot beta_os_cov (`numeric`) Additional regression coefficients for survival models. |
|
| 53 |
#' |
|
| 54 |
#' @family SimSurvival |
|
| 55 |
#' @exportClass SimSurvival |
|
| 56 |
#' @name SimSurvival-class |
|
| 57 |
#' |
|
| 58 |
#' @examples |
|
| 59 |
#' SimSurvival(loghazard = function(time, ...) rep(log(1 / 100), length(time))) |
|
| 60 |
.SimSurvival <- setClass( |
|
| 61 |
"SimSurvival", |
|
| 62 |
slots = c( |
|
| 63 |
time_max = "numeric", |
|
| 64 |
time_step = "numeric", |
|
| 65 |
lambda_censor = "numeric", |
|
| 66 |
beta_cont = "numeric", |
|
| 67 |
beta_cat = "numeric", |
|
| 68 |
beta_os_cov = "numeric", |
|
| 69 |
loghazard = "function", |
|
| 70 |
name = "character" |
|
| 71 |
) |
|
| 72 |
) |
|
| 73 | ||
| 74 |
#' @rdname SimSurvival-class |
|
| 75 |
#' @export |
|
| 76 |
#' |
|
| 77 |
#' @returns A `SimSurvival` object. |
|
| 78 |
SimSurvival <- function( |
|
| 79 |
time_max = 2000, |
|
| 80 |
time_step = 1, |
|
| 81 |
lambda_censor = 1 / 3000, |
|
| 82 |
beta_cont = 0.2, |
|
| 83 |
beta_cat = c("A" = 0, "B" = -0.4, "C" = 0.2),
|
|
| 84 |
loghazard, |
|
| 85 |
name = "SimSurvival" |
|
| 86 |
) {
|
|
| 87 | 32x |
.SimSurvival( |
| 88 | 32x |
time_max = time_max, |
| 89 | 32x |
time_step = time_step, |
| 90 | 32x |
lambda_censor = lambda_censor, |
| 91 | 32x |
beta_cont = beta_cont, |
| 92 | 32x |
beta_cat = beta_cat, |
| 93 | 32x |
loghazard = loghazard, |
| 94 | 32x |
name = name |
| 95 |
) |
|
| 96 |
} |
|
| 97 | ||
| 98 | ||
| 99 |
#' @rdname show-object |
|
| 100 |
#' @export |
|
| 101 |
setMethod( |
|
| 102 |
f = "show", |
|
| 103 |
signature = "SimSurvival", |
|
| 104 |
definition = function(object) {
|
|
| 105 | 4x |
x <- sprintf("\nA %s Object\n\n", as_print_string(object))
|
| 106 | 4x |
cat(x) |
| 107 | 4x |
return(object) |
| 108 |
} |
|
| 109 |
) |
|
| 110 | ||
| 111 |
#' @rdname as_print_string |
|
| 112 |
#' @exportS3Method NULL |
|
| 113 |
as_print_string.SimSurvival <- function(object, ...) {
|
|
| 114 | 4x |
return(object@name) |
| 115 |
} |
|
| 116 | ||
| 117 | ||
| 118 |
#' Construct Time Intervals |
|
| 119 |
#' |
|
| 120 |
#' @typed object: SimSurvival |
|
| 121 |
#' the survival simulation object to create evaluation points for. |
|
| 122 |
#' @param ... Not Used. |
|
| 123 |
#' |
|
| 124 |
#' @return A `tibble` with `lower`, `upper`, `time`, `eval` and `width`. |
|
| 125 |
#' @keywords internal |
|
| 126 |
#' @exportS3Method NULL |
|
| 127 |
hazardWindows.SimSurvival <- function(object, ...) {
|
|
| 128 | 26x |
times <- seq(0, object@time_max, object@time_step) |
| 129 | 26x |
bound_lower <- times[-length(times)] |
| 130 | 26x |
bound_upper <- times[-1] |
| 131 | 26x |
bound_width <- bound_upper - bound_lower |
| 132 | 26x |
mid_point <- bound_upper - (bound_width / 2) |
| 133 | 26x |
tibble::tibble( |
| 134 | 26x |
lower = bound_lower, |
| 135 | 26x |
upper = bound_upper, |
| 136 | 26x |
midpoint = mid_point, |
| 137 | 26x |
width = bound_width |
| 138 |
) |
|
| 139 |
} |
|
| 140 | ||
| 141 |
#' @rdname sampleSubjects |
|
| 142 |
#' @export |
|
| 143 |
sampleSubjects.SimSurvival <- function(object, subjects_df) {
|
|
| 144 | 25x |
subjects_df |> |
| 145 | 25x |
dplyr::mutate(cov_cont = stats::rnorm(dplyr::n())) |> |
| 146 | 25x |
dplyr::mutate( |
| 147 | 25x |
cov_cat = factor( |
| 148 | 25x |
sample( |
| 149 | 25x |
names(object@beta_cat), |
| 150 | 25x |
replace = TRUE, |
| 151 | 25x |
size = dplyr::n() |
| 152 |
), |
|
| 153 | 25x |
levels = names(object@beta_cat) |
| 154 |
) |
|
| 155 |
) |> |
|
| 156 | 25x |
dplyr::mutate( |
| 157 | 25x |
log_haz_cov = .data$cov_cont * |
| 158 | 25x |
object@beta_cont + |
| 159 | 25x |
object@beta_cat[.data$cov_cat] |
| 160 |
) |> |
|
| 161 | 25x |
dplyr::mutate(survival = stats::runif(dplyr::n())) |> |
| 162 | 25x |
dplyr::mutate(chazard_limit = -log(.data$survival)) |> |
| 163 | 25x |
dplyr::mutate(time_cen = stats::rexp(dplyr::n(), object@lambda_censor)) |
| 164 |
} |
|
| 165 | ||
| 166 | ||
| 167 |
#' @rdname sampleObservations |
|
| 168 |
#' @export |
|
| 169 |
sampleObservations.SimSurvival <- function(object, times_df) {
|
|
| 170 | 25x |
assert_that( |
| 171 | 25x |
all(times_df$time >= 0), |
| 172 | 25x |
msg = "All time points must be greater than or equal to 0" |
| 173 |
) |
|
| 174 | ||
| 175 | 25x |
os_dat_chaz <- times_df |> |
| 176 | 25x |
dplyr::mutate(log_bl_haz = object@loghazard(.data$midpoint)) |> |
| 177 |
# Fix to avoid issue with log(0) = NaN values |
|
| 178 | 25x |
dplyr::mutate( |
| 179 | 25x |
log_bl_haz = dplyr::if_else( |
| 180 | 25x |
.data$midpoint == 0, |
| 181 | 25x |
-999, |
| 182 | 25x |
.data$log_bl_haz |
| 183 |
) |
|
| 184 |
) |> |
|
| 185 | 25x |
dplyr::mutate( |
| 186 | 25x |
hazard_instant = exp( |
| 187 | 25x |
.data$log_bl_haz + .data$log_haz_cov + .data$log_haz_link |
| 188 |
) |
|
| 189 |
) |> |
|
| 190 |
# Reset Inf values to large number to avoid NaN issues downstream |
|
| 191 |
# This is suitable as Hazard limits tend to be in the range of -10 to 10 so large numbers |
|
| 192 |
# are essentially equivalent to infinity for simulation purposes |
|
| 193 | 25x |
dplyr::mutate( |
| 194 | 25x |
hazard_instant = dplyr::if_else( |
| 195 | 25x |
.data$hazard_instant == Inf, |
| 196 | 25x |
999, |
| 197 | 25x |
.data$hazard_instant |
| 198 |
) |
|
| 199 |
) |> |
|
| 200 | 25x |
dplyr::mutate( |
| 201 | 25x |
hazard_instant = dplyr::if_else( |
| 202 | 25x |
.data$hazard_instant == -Inf, |
| 203 | 25x |
-999, |
| 204 | 25x |
.data$hazard_instant |
| 205 |
) |
|
| 206 |
) |> |
|
| 207 | 25x |
dplyr::mutate(hazard_interval = .data$hazard_instant * .data$width) |> |
| 208 | 25x |
dplyr::group_by(.data$subject) |> |
| 209 | 25x |
dplyr::mutate(chazard = cumsum(.data$hazard_interval)) |> |
| 210 | 25x |
dplyr::ungroup() |
| 211 | ||
| 212 | 25x |
os_had_event <- os_dat_chaz |> |
| 213 | 25x |
dplyr::filter(.data$chazard >= .data$chazard_limit) |> |
| 214 | 25x |
dplyr::group_by(.data$subject) |> |
| 215 | 25x |
dplyr::slice(1) |> |
| 216 | 25x |
dplyr::ungroup() |> |
| 217 | 25x |
dplyr::mutate(event = 1) |
| 218 | ||
| 219 | 25x |
os_had_censor <- os_dat_chaz |> |
| 220 | 25x |
dplyr::filter(!.data$subject %in% os_had_event$subject) |> |
| 221 | 25x |
dplyr::group_by(.data$subject) |> |
| 222 | 25x |
dplyr::slice(dplyr::n()) |> |
| 223 | 25x |
dplyr::ungroup() |> |
| 224 | 25x |
dplyr::mutate(event = 0) |
| 225 | ||
| 226 | 25x |
if (!(nrow(os_had_censor) == 0)) {
|
| 227 | 9x |
message(sprintf( |
| 228 | 9x |
"INFO: %i subject(s) did not die before max(times)", |
| 229 | 9x |
nrow(os_had_censor) |
| 230 |
)) |
|
| 231 |
} |
|
| 232 | ||
| 233 | 25x |
os_dat_complete <- os_had_event |> |
| 234 | 25x |
dplyr::bind_rows(os_had_censor) |> |
| 235 | 25x |
dplyr::mutate(real_time = .data$time) |> |
| 236 | 25x |
dplyr::mutate( |
| 237 | 25x |
event = dplyr::if_else( |
| 238 | 25x |
.data$real_time <= .data$time_cen, |
| 239 | 25x |
.data$event, |
| 240 | 25x |
0 |
| 241 |
) |
|
| 242 |
) |> |
|
| 243 | 25x |
dplyr::mutate( |
| 244 | 25x |
time = dplyr::if_else( |
| 245 | 25x |
.data$real_time <= .data$time_cen, |
| 246 | 25x |
.data$real_time, |
| 247 | 25x |
.data$time_cen |
| 248 |
) |
|
| 249 |
) |> |
|
| 250 | 25x |
dplyr::arrange(.data$subject) |
| 251 | ||
| 252 | 25x |
keep_cols <- colnames(os_dat_complete) %in% |
| 253 | 25x |
c("subject", "study", "arm", "time", "event", "cov_cont", "cov_cat")
|
| 254 | 25x |
os_dat_complete[, keep_cols] |
| 255 |
} |
|
| 256 | ||
| 257 | ||
| 258 |
#' Simulate Survival Data from a Weibull Proportional Hazard Model |
|
| 259 |
#' |
|
| 260 |
#' @typed lambda: number |
|
| 261 |
#' the scale parameter. |
|
| 262 |
#' @typed gamma: number |
|
| 263 |
#' the shape parameter. |
|
| 264 |
#' |
|
| 265 |
#' @inheritParams SimSurvival-Shared |
|
| 266 |
#' @inheritSection SimSurvival-Shared Hazard Evaluation |
|
| 267 |
#' |
|
| 268 |
#' @family SimSurvival |
|
| 269 |
#' |
|
| 270 |
#' @export |
|
| 271 |
#' |
|
| 272 |
#' @returns A `SimSurvival` object. |
|
| 273 |
#' |
|
| 274 |
#' @examples |
|
| 275 |
#' SimSurvivalWeibullPH(lambda = 1 / 300, gamma = 1.2) |
|
| 276 |
SimSurvivalWeibullPH <- function( |
|
| 277 |
lambda, |
|
| 278 |
gamma, |
|
| 279 |
time_max = 2000, |
|
| 280 |
time_step = 1, |
|
| 281 |
lambda_censor = 1 / 3000, |
|
| 282 |
beta_cont = 0.2, |
|
| 283 |
beta_cat = c("A" = 0, "B" = -0.4, "C" = 0.2)
|
|
| 284 |
) {
|
|
| 285 | 4x |
SimSurvival( |
| 286 | 4x |
time_max = time_max, |
| 287 | 4x |
time_step = time_step, |
| 288 | 4x |
lambda_censor = lambda_censor, |
| 289 | 4x |
beta_cont = beta_cont, |
| 290 | 4x |
beta_cat = beta_cat, |
| 291 | 4x |
loghazard = function(time) {
|
| 292 | 3x |
log(lambda) + log(gamma) + (gamma - 1) * log(time) |
| 293 |
}, |
|
| 294 | 4x |
name = "SimSurvivalWeibullPH" |
| 295 |
) |
|
| 296 |
} |
|
| 297 | ||
| 298 |
#' Simulate Survival Data from a Log-Logistic Proportional Hazard Model |
|
| 299 |
#' |
|
| 300 |
#' @typed a: number |
|
| 301 |
#' the scale parameter. |
|
| 302 |
#' @typed b: number |
|
| 303 |
#' the shape parameter. |
|
| 304 |
#' |
|
| 305 |
#' @inheritParams SimSurvival-Shared |
|
| 306 |
#' @inheritSection SimSurvival-Shared Hazard Evaluation |
|
| 307 |
#' |
|
| 308 |
#' @family SimSurvival |
|
| 309 |
#' @export |
|
| 310 |
#' |
|
| 311 |
#' @returns A `SimSurvival` object. |
|
| 312 |
#' |
|
| 313 |
#' @examples |
|
| 314 |
#' SimSurvivalLogLogistic(a = 300, b = 2) |
|
| 315 |
SimSurvivalLogLogistic <- function( |
|
| 316 |
a, |
|
| 317 |
b, |
|
| 318 |
time_max = 2000, |
|
| 319 |
time_step = 1, |
|
| 320 |
lambda_censor = 1 / 3000, |
|
| 321 |
beta_cont = 0.2, |
|
| 322 |
beta_cat = c("A" = 0, "B" = -0.4, "C" = 0.2)
|
|
| 323 |
) {
|
|
| 324 | 2x |
SimSurvival( |
| 325 | 2x |
time_max = time_max, |
| 326 | 2x |
time_step = time_step, |
| 327 | 2x |
lambda_censor = lambda_censor, |
| 328 | 2x |
beta_cont = beta_cont, |
| 329 | 2x |
beta_cat = beta_cat, |
| 330 | 2x |
loghazard = function(time) {
|
| 331 | 1x |
c1 <- -log(a) + log(b) + (b - 1) * (-log(a) + log(time)) |
| 332 | 1x |
c2 <- log(1 + (time / a)^b) |
| 333 | 1x |
return(c1 - c2) |
| 334 |
}, |
|
| 335 | 2x |
name = "SimSurvivalLogLogistic" |
| 336 |
) |
|
| 337 |
} |
|
| 338 | ||
| 339 | ||
| 340 |
#' Simulate Survival Data from a Exponential Proportional Hazard Model |
|
| 341 |
#' |
|
| 342 |
#' @typed lambda: number |
|
| 343 |
#' the rate parameter. |
|
| 344 |
#' |
|
| 345 |
#' @inheritParams SimSurvival-Shared |
|
| 346 |
#' @inheritSection SimSurvival-Shared Hazard Evaluation |
|
| 347 |
#' |
|
| 348 |
#' @family SimSurvival |
|
| 349 |
#' |
|
| 350 |
#' @export |
|
| 351 |
#' |
|
| 352 |
#' @returns A `SimSurvival` object. |
|
| 353 |
#' |
|
| 354 |
#' @examples |
|
| 355 |
#' SimSurvivalExponential(lambda = 1 / 300) |
|
| 356 |
SimSurvivalExponential <- function( |
|
| 357 |
lambda, |
|
| 358 |
time_max = 2000, |
|
| 359 |
time_step = 1, |
|
| 360 |
lambda_censor = 1 / 3000, |
|
| 361 |
beta_cont = 0.2, |
|
| 362 |
beta_cat = c("A" = 0, "B" = -0.4, "C" = 0.2)
|
|
| 363 |
) {
|
|
| 364 | 24x |
SimSurvival( |
| 365 | 24x |
time_max = time_max, |
| 366 | 24x |
time_step = time_step, |
| 367 | 24x |
lambda_censor = lambda_censor, |
| 368 | 24x |
beta_cont = beta_cont, |
| 369 | 24x |
beta_cat = beta_cat, |
| 370 | 24x |
loghazard = function(time) {
|
| 371 | 24x |
log(lambda) |
| 372 |
}, |
|
| 373 | 24x |
name = "SimSurvivalExponential" |
| 374 |
) |
|
| 375 |
} |
|
| 376 | ||
| 377 | ||
| 378 |
#' Simulate Survival Data from a Gamma Proportional Hazard Model |
|
| 379 |
#' |
|
| 380 |
#' @typed k: number |
|
| 381 |
#' the shape parameter. |
|
| 382 |
#' @typed theta: number |
|
| 383 |
#' the scale parameter. |
|
| 384 |
#' |
|
| 385 |
#' @inheritParams SimSurvival-Shared |
|
| 386 |
#' @inheritSection SimSurvival-Shared Hazard Evaluation |
|
| 387 |
#' |
|
| 388 |
#' @family SimSurvival |
|
| 389 |
#' |
|
| 390 |
#' @importFrom stats dgamma pgamma |
|
| 391 |
#' |
|
| 392 |
#' @export |
|
| 393 |
#' |
|
| 394 |
#' @returns A `SimSurvival` object. |
|
| 395 |
#' |
|
| 396 |
#' @examples |
|
| 397 |
#' SimSurvivalGamma(k = 2, theta = 300) |
|
| 398 |
SimSurvivalGamma <- function( |
|
| 399 |
k, |
|
| 400 |
theta, |
|
| 401 |
time_max = 2000, |
|
| 402 |
time_step = 1, |
|
| 403 |
lambda_censor = 1 / 3000, |
|
| 404 |
beta_cont = 0.2, |
|
| 405 |
beta_cat = c("A" = 0, "B" = -0.4, "C" = 0.2)
|
|
| 406 |
) {
|
|
| 407 | 1x |
SimSurvival( |
| 408 | 1x |
time_max = time_max, |
| 409 | 1x |
time_step = time_step, |
| 410 | 1x |
lambda_censor = lambda_censor, |
| 411 | 1x |
beta_cont = beta_cont, |
| 412 | 1x |
beta_cat = beta_cat, |
| 413 | 1x |
loghazard = function(time) {
|
| 414 | 1x |
dgamma(time, k, scale = theta, log = TRUE) - |
| 415 | 1x |
pgamma(time, k, scale = theta, lower.tail = FALSE, log.p = TRUE) |
| 416 |
}, |
|
| 417 | 1x |
name = "SimSurvivalGamma" |
| 418 |
) |
|
| 419 |
} |
| 1 |
#' @include Grid.R |
|
| 2 |
#' @include GridFixed.R |
|
| 3 |
#' @include generics.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 |
#' @rdname Grid-Dev |
|
| 7 |
.GridGrouped <- setClass( |
|
| 8 |
"GridGrouped", |
|
| 9 |
contains = "Grid", |
|
| 10 |
slots = c( |
|
| 11 |
"groups" = "list", |
|
| 12 |
"times" = "numeric_or_NULL" |
|
| 13 |
) |
|
| 14 |
) |
|
| 15 | ||
| 16 |
#' @rdname Grid-Functions |
|
| 17 |
#' @export |
|
| 18 |
GridGrouped <- function(groups, times = NULL) {
|
|
| 19 | 13x |
.GridGrouped( |
| 20 | 13x |
groups = groups, |
| 21 | 13x |
times = times |
| 22 |
) |
|
| 23 |
} |
|
| 24 | ||
| 25 | ||
| 26 |
setValidity( |
|
| 27 |
"GridGrouped", |
|
| 28 |
function(object) {
|
|
| 29 |
if (!all(vapply(object@groups, is.character, logical(1)))) {
|
|
| 30 |
return("Each element of `groups` must be a character vector")
|
|
| 31 |
} |
|
| 32 |
gnames <- names(object@groups) |
|
| 33 |
gnames <- gnames[!is.na(gnames) & gnames != ""] |
|
| 34 |
if (length(gnames) != length(object@groups)) {
|
|
| 35 |
return("Each element of `groups` must be named")
|
|
| 36 |
} |
|
| 37 |
return(TRUE) |
|
| 38 |
} |
|
| 39 |
) |
|
| 40 | ||
| 41 | ||
| 42 |
#' @rdname Quant-Dev |
|
| 43 |
#' @export |
|
| 44 |
as.QuantityGenerator.GridGrouped <- function(object, data, ...) {
|
|
| 45 | 17x |
assert_class(data, "DataJoint") |
| 46 | 17x |
data_list <- as.list(data) |
| 47 | 17x |
subjects_unique <- unique(unlist(object@groups)) |
| 48 | 17x |
assert_that( |
| 49 | 17x |
all(subjects_unique %in% names(data_list$subject_to_index)) |
| 50 |
) |
|
| 51 | 17x |
as.QuantityGenerator( |
| 52 | 17x |
GridFixed( |
| 53 | 17x |
times = object@times, |
| 54 | 17x |
subjects = subjects_unique |
| 55 |
), |
|
| 56 | 17x |
data = data |
| 57 |
) |
|
| 58 |
} |
|
| 59 | ||
| 60 | ||
| 61 |
#' @rdname Quant-Dev |
|
| 62 |
#' @export |
|
| 63 |
as.QuantityCollapser.GridGrouped <- function(object, data, ...) {
|
|
| 64 | 8x |
assert_class(data, "DataJoint") |
| 65 | 8x |
data_list <- as.list(data) |
| 66 | 8x |
assert_that( |
| 67 | 8x |
all( |
| 68 | 8x |
unique(unlist(object@groups)) %in% names(data_list$subject_to_index) |
| 69 |
) |
|
| 70 |
) |
|
| 71 | ||
| 72 | 8x |
validate_time_grid(object@times) |
| 73 | ||
| 74 | 8x |
group_grid <- expand.grid( |
| 75 | 8x |
group = names(object@groups), |
| 76 | 8x |
time = object@times, |
| 77 | 8x |
stringsAsFactors = FALSE |
| 78 |
) |
|
| 79 | ||
| 80 | 8x |
generator <- as.QuantityGenerator(object, data) |
| 81 | ||
| 82 | 8x |
select_indexes <- mapply( |
| 83 | 8x |
function(group, time) {
|
| 84 | 490x |
correct_subject <- generator@subjects %in% object@groups[[group]] |
| 85 | 490x |
correct_time <- generator@times == time |
| 86 | 490x |
seq_along(correct_time)[correct_subject & correct_time] |
| 87 |
}, |
|
| 88 | 8x |
group_grid$group, |
| 89 | 8x |
group_grid$time, |
| 90 | 8x |
SIMPLIFY = FALSE |
| 91 |
) |
|
| 92 | 8x |
names(select_indexes) <- NULL |
| 93 | ||
| 94 | 8x |
QuantityCollapser( |
| 95 | 8x |
times = group_grid$time, |
| 96 | 8x |
groups = group_grid$group, |
| 97 | 8x |
indexes = select_indexes |
| 98 |
) |
|
| 99 |
} |
|
| 100 | ||
| 101 |
#' @export |
|
| 102 |
#' |
|
| 103 |
#' @returns A named `list` representation of the object. |
|
| 104 |
as.list.GridGrouped <- function(x, ...) {
|
|
| 105 | 1x |
x@groups |
| 106 |
} |
|
| 107 | ||
| 108 |
#' @rdname coalesceGridTime |
|
| 109 |
#' @export |
|
| 110 |
coalesceGridTime.GridGrouped <- function(object, times, ...) {
|
|
| 111 | 10x |
if (is.null(object@times)) {
|
| 112 | 2x |
object <- GridGrouped( |
| 113 | 2x |
groups = object@groups, |
| 114 | 2x |
times = times |
| 115 |
) |
|
| 116 |
} |
|
| 117 | 10x |
object |
| 118 |
} |
| 1 |
#' @include SimLongitudinal.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Simulate Longitudinal Data from a Stein-Fojo Model |
|
| 6 |
#' |
|
| 7 |
#' @typed times: numeric |
|
| 8 |
#' the times to generate observations at. |
|
| 9 |
#' @typed sigma: number |
|
| 10 |
#' the variance of the longitudinal values. |
|
| 11 |
#' @typed mu_s: numeric |
|
| 12 |
#' the mean shrinkage rates for the two treatment arms. |
|
| 13 |
#' @typed mu_g: numeric |
|
| 14 |
#' the mean growth rates for the two treatment arms. |
|
| 15 |
#' @typed mu_b: numeric |
|
| 16 |
#' the mean baseline values for the two treatment arms. |
|
| 17 |
#' @typed omega_b: number |
|
| 18 |
#' the baseline value standard deviation. |
|
| 19 |
#' @typed omega_s: number |
|
| 20 |
#' the shrinkage rate standard deviation. |
|
| 21 |
#' @typed omega_g: number |
|
| 22 |
#' the growth rate standard deviation. |
|
| 23 |
#' @typed link_dsld: number |
|
| 24 |
#' the link coefficient for the derivative contribution. |
|
| 25 |
#' @typed link_ttg: number |
|
| 26 |
#' the link coefficient for the time-to-growth contribution. |
|
| 27 |
#' @typed link_identity: number |
|
| 28 |
#' the link coefficient for the SLD Identity contribution. |
|
| 29 |
#' @typed link_growth: number |
|
| 30 |
#' the link coefficient for the log-growth parameter contribution. |
|
| 31 |
#' @typed link_shrinkage: number |
|
| 32 |
#' the link coefficient for the log-shrinkage parameter contribution. |
|
| 33 |
#' @typed scaled_variance: logical |
|
| 34 |
#' whether the variance should be scaled by the expected value |
|
| 35 |
#' (see the "Statistical Specifications" vignette for more details) |
|
| 36 |
#' |
|
| 37 |
#' @slot sigma (`numeric`)\cr See arguments. |
|
| 38 |
#' @slot mu_s (`numeric`)\cr See arguments. |
|
| 39 |
#' @slot mu_g (`numeric`)\cr See arguments. |
|
| 40 |
#' @slot mu_b (`numeric`)\cr See arguments. |
|
| 41 |
#' @slot omega_b (`numeric`)\cr See arguments. |
|
| 42 |
#' @slot omega_s (`numeric`)\cr See arguments. |
|
| 43 |
#' @slot omega_g (`numeric`)\cr See arguments. |
|
| 44 |
#' @slot link_dsld (`numeric`)\cr See arguments. |
|
| 45 |
#' @slot link_ttg (`numeric`)\cr See arguments. |
|
| 46 |
#' @slot link_identity (`numeric`)\cr See arguments. |
|
| 47 |
#' @slot link_growth (`numeric`)\cr See arguments. |
|
| 48 |
#' @slot link_shrinkage (`numeric`)\cr See arguments. |
|
| 49 |
#' @slot scaled_variance (`logical`)\cr See arguments. |
|
| 50 |
#' |
|
| 51 |
#' @family SimLongitudinal |
|
| 52 |
#' @name SimLongitudinalSteinFojo-class |
|
| 53 |
#' @exportClass SimLongitudinalSteinFojo |
|
| 54 |
#' |
|
| 55 |
#' @examples |
|
| 56 |
#' SimLongitudinalSteinFojo(times = c(0, 30, 60)) |
|
| 57 |
.SimLongitudinalSteinFojo <- setClass( |
|
| 58 |
"SimLongitudinalSteinFojo", |
|
| 59 |
contains = "SimLongitudinal", |
|
| 60 |
slots = c( |
|
| 61 |
sigma = "numeric", |
|
| 62 |
mu_s = "numeric", |
|
| 63 |
mu_g = "numeric", |
|
| 64 |
mu_b = "numeric", |
|
| 65 |
omega_b = "numeric", |
|
| 66 |
omega_s = "numeric", |
|
| 67 |
omega_g = "numeric", |
|
| 68 |
link_dsld = "numeric", |
|
| 69 |
link_ttg = "numeric", |
|
| 70 |
link_identity = "numeric", |
|
| 71 |
link_growth = "numeric", |
|
| 72 |
link_shrinkage = "numeric", |
|
| 73 |
scaled_variance = "logical" |
|
| 74 |
) |
|
| 75 |
) |
|
| 76 | ||
| 77 |
#' @rdname SimLongitudinalSteinFojo-class |
|
| 78 |
#' @export |
|
| 79 |
#' |
|
| 80 |
#' @returns A `SimLongitudinalSteinFojo` object. |
|
| 81 |
SimLongitudinalSteinFojo <- function( |
|
| 82 |
times = c(-100, -50, 0, 50, 100, 150, 250, 350, 450, 550) / 365, |
|
| 83 |
sigma = 0.01, |
|
| 84 |
mu_s = log(c(0.6, 0.4)), |
|
| 85 |
mu_g = log(c(0.25, 0.35)), |
|
| 86 |
mu_b = log(60), |
|
| 87 |
omega_b = 0.2, |
|
| 88 |
omega_s = 0.2, |
|
| 89 |
omega_g = 0.2, |
|
| 90 |
link_dsld = 0, |
|
| 91 |
link_ttg = 0, |
|
| 92 |
link_identity = 0, |
|
| 93 |
link_growth = 0, |
|
| 94 |
link_shrinkage = 0, |
|
| 95 |
scaled_variance = FALSE |
|
| 96 |
) {
|
|
| 97 | 2x |
if (length(omega_b) == 1) {
|
| 98 | 2x |
omega_b <- rep(omega_b, length(mu_b)) |
| 99 |
} |
|
| 100 | 2x |
if (length(omega_s) == 1) {
|
| 101 | 2x |
omega_s <- rep(omega_s, length(mu_s)) |
| 102 |
} |
|
| 103 | 2x |
if (length(omega_g) == 1) {
|
| 104 | 2x |
omega_g <- rep(omega_g, length(mu_g)) |
| 105 |
} |
|
| 106 | ||
| 107 | 2x |
.SimLongitudinalSteinFojo( |
| 108 | 2x |
times = times, |
| 109 | 2x |
sigma = sigma, |
| 110 | 2x |
mu_s = mu_s, |
| 111 | 2x |
mu_g = mu_g, |
| 112 | 2x |
mu_b = mu_b, |
| 113 | 2x |
omega_b = omega_b, |
| 114 | 2x |
omega_s = omega_s, |
| 115 | 2x |
omega_g = omega_g, |
| 116 | 2x |
link_dsld = link_dsld, |
| 117 | 2x |
link_ttg = link_ttg, |
| 118 | 2x |
link_identity = link_identity, |
| 119 | 2x |
link_growth = link_growth, |
| 120 | 2x |
link_shrinkage = link_shrinkage, |
| 121 | 2x |
scaled_variance = scaled_variance |
| 122 |
) |
|
| 123 |
} |
|
| 124 | ||
| 125 | ||
| 126 |
setValidity( |
|
| 127 |
"SimLongitudinalSteinFojo", |
|
| 128 |
function(object) {
|
|
| 129 |
par_lengths <- c( |
|
| 130 |
length(object@mu_s), |
|
| 131 |
length(object@mu_g) |
|
| 132 |
) |
|
| 133 |
if (length(unique(par_lengths)) != 1) {
|
|
| 134 |
return( |
|
| 135 |
"The parameters `mu_s` and `mu_g` must have the same length." |
|
| 136 |
) |
|
| 137 |
} |
|
| 138 |
pairs <- list( |
|
| 139 |
"omega_b" = "mu_b", |
|
| 140 |
"omega_s" = "mu_s", |
|
| 141 |
"omega_g" = "mu_g" |
|
| 142 |
) |
|
| 143 |
for (i in seq_along(pairs)) {
|
|
| 144 |
omega <- slot(object, names(pairs)[[i]]) |
|
| 145 |
mu <- slot(object, pairs[[i]]) |
|
| 146 |
if (!(length(omega) == length(mu))) {
|
|
| 147 |
return( |
|
| 148 |
sprintf( |
|
| 149 |
"`%s` must be length 1 or the same length as `%s`", |
|
| 150 |
omega, |
|
| 151 |
mu |
|
| 152 |
) |
|
| 153 |
) |
|
| 154 |
} |
|
| 155 |
} |
|
| 156 |
len_1_pars <- c( |
|
| 157 |
"sigma", |
|
| 158 |
"link_dsld", |
|
| 159 |
"link_ttg", |
|
| 160 |
"link_identity", |
|
| 161 |
"link_growth", |
|
| 162 |
"link_shrinkage" |
|
| 163 |
) |
|
| 164 |
for (par in len_1_pars) {
|
|
| 165 |
if (length(slot(object, par)) != 1) {
|
|
| 166 |
return(sprintf( |
|
| 167 |
"The `%s` parameter must be a length 1 numeric.", |
|
| 168 |
par |
|
| 169 |
)) |
|
| 170 |
} |
|
| 171 |
} |
|
| 172 |
return(TRUE) |
|
| 173 |
} |
|
| 174 |
) |
|
| 175 | ||
| 176 |
#' @rdname as_print_string |
|
| 177 |
#' @exportS3Method NULL |
|
| 178 |
as_print_string.SimLongitudinalSteinFojo <- function(object, ...) {
|
|
| 179 | 1x |
return("SimLongitudinalSteinFojo")
|
| 180 |
} |
|
| 181 | ||
| 182 |
#' @rdname sampleObservations |
|
| 183 |
#' @export |
|
| 184 |
sampleObservations.SimLongitudinalSteinFojo <- function(object, times_df) {
|
|
| 185 | 1x |
times_df |> |
| 186 | 1x |
dplyr::mutate( |
| 187 | 1x |
mu_sld = sf_sld(.data$time, .data$psi_b, .data$psi_s, .data$psi_g), |
| 188 | 1x |
dsld = sf_dsld(.data$time, .data$psi_b, .data$psi_s, .data$psi_g), |
| 189 | 1x |
ttg = sf_ttg(.data$time, .data$psi_b, .data$psi_s, .data$psi_g), |
| 190 | 1x |
sld_sd = ifelse( |
| 191 | 1x |
object@scaled_variance, |
| 192 | 1x |
.data$mu_sld * object@sigma, |
| 193 | 1x |
object@sigma |
| 194 |
), |
|
| 195 | 1x |
sld = stats::rnorm(dplyr::n(), .data$mu_sld, .data$sld_sd), |
| 196 | 1x |
log_haz_link = (object@link_dsld * .data$dsld) + |
| 197 | 1x |
(object@link_ttg * .data$ttg) + |
| 198 | 1x |
(object@link_identity * .data$mu_sld) + |
| 199 | 1x |
(object@link_growth * log(.data$psi_g)) + |
| 200 | 1x |
(object@link_shrinkage * log(.data$psi_s)) |
| 201 |
) |
|
| 202 |
} |
|
| 203 | ||
| 204 | ||
| 205 |
#' @rdname sampleSubjects |
|
| 206 |
#' @export |
|
| 207 |
sampleSubjects.SimLongitudinalSteinFojo <- function(object, subjects_df) {
|
|
| 208 | 1x |
assert_that( |
| 209 | 1x |
is.factor(subjects_df$study), |
| 210 | 1x |
is.factor(subjects_df$arm), |
| 211 | 1x |
length(levels(subjects_df$study)) == length(object@mu_b), |
| 212 | 1x |
length(levels(subjects_df$arm)) == length(object@mu_s) |
| 213 |
) |
|
| 214 | ||
| 215 | 1x |
res <- subjects_df |> |
| 216 | 1x |
dplyr::distinct(.data$subject, .data$arm, .data$study) |> |
| 217 | 1x |
dplyr::mutate( |
| 218 | 1x |
study_idx = as.numeric(.data$study), |
| 219 | 1x |
arm_idx = as.numeric(.data$arm), |
| 220 | 1x |
psi_b = stats::rlnorm( |
| 221 | 1x |
dplyr::n(), |
| 222 | 1x |
object@mu_b[.data$study_idx], |
| 223 | 1x |
object@omega_b[.data$study_idx] |
| 224 |
), |
|
| 225 | 1x |
psi_s = stats::rlnorm( |
| 226 | 1x |
dplyr::n(), |
| 227 | 1x |
object@mu_s[.data$arm_idx], |
| 228 | 1x |
object@omega_s[.data$arm_idx] |
| 229 |
), |
|
| 230 | 1x |
psi_g = stats::rlnorm( |
| 231 | 1x |
dplyr::n(), |
| 232 | 1x |
object@mu_g[.data$arm_idx], |
| 233 | 1x |
object@omega_g[.data$arm_idx] |
| 234 |
) |
|
| 235 |
) |
|
| 236 | ||
| 237 | 1x |
res[, c("subject", "arm", "study", "psi_b", "psi_s", "psi_g")]
|
| 238 |
} |
|
| 239 | ||
| 240 | ||
| 241 |
#' Stein-Fojo Functionals |
|
| 242 |
#' |
|
| 243 |
#' @typed time: numeric |
|
| 244 |
#' time grid. |
|
| 245 |
#' @typed b: number |
|
| 246 |
#' baseline. |
|
| 247 |
#' @typed s: number |
|
| 248 |
#' shrinkage. |
|
| 249 |
#' @typed g: number |
|
| 250 |
#' growth. |
|
| 251 |
#' |
|
| 252 |
#' @returns The function results. |
|
| 253 |
#' @keywords internal |
|
| 254 |
sf_sld <- function(time, b, s, g) {
|
|
| 255 | 2x |
s <- dplyr::if_else(time >= 0, s, 0) |
| 256 | 2x |
b * (exp(-s * time) + exp(g * time) - 1) |
| 257 |
} |
|
| 258 | ||
| 259 | ||
| 260 |
#' @rdname sf_sld |
|
| 261 |
sf_ttg <- function(time, b, s, g) {
|
|
| 262 | 2x |
t1 <- (log(s) - log(g)) / (g + s) |
| 263 | 2x |
t1[t1 <= 0] <- 0 |
| 264 | 2x |
return(t1) |
| 265 |
} |
|
| 266 | ||
| 267 | ||
| 268 |
#' @rdname sf_sld |
|
| 269 |
sf_dsld <- function(time, b, s, g) {
|
|
| 270 | 2x |
s <- dplyr::if_else(time >= 0, s, 0) |
| 271 | 2x |
t1 <- g * exp(g * time) |
| 272 | 2x |
t2 <- s * exp(-s * time) |
| 273 | 2x |
return(b * (t1 - t2)) |
| 274 |
} |
| 1 |
#' Re-used documentation for `DataSubject` |
|
| 2 |
#' |
|
| 3 |
#' @typed object: DataSubject |
|
| 4 |
#' subject-level data. |
|
| 5 |
#' @typed x: DataSubject |
|
| 6 |
#' subject-level data. |
|
| 7 |
#' @param ... Not Used. |
|
| 8 |
#' |
|
| 9 |
#' @name DataSubject-Shared |
|
| 10 |
#' @keywords internal |
|
| 11 |
NULL |
|
| 12 | ||
| 13 | ||
| 14 |
#' Subject Data Object and Constructor Function |
|
| 15 |
#' |
|
| 16 |
#' The [`DataSubject`] class handles the processing of the subject data for |
|
| 17 |
#' fitting a [`JointModel`]. |
|
| 18 |
#' |
|
| 19 |
#' @slot data (`data.frame`)\cr the subject-level data. |
|
| 20 |
#' @slot subject (`character`)\cr the name of the variable containing the subject identifier. |
|
| 21 |
#' @slot arm (`character`)\cr the name of the variable containing the arm identifier. |
|
| 22 |
#' @slot study (`character`)\cr the name of the variable containing the study identifier. |
|
| 23 |
#' |
|
| 24 |
#' @family DataObjects |
|
| 25 |
#' @family DataSubject |
|
| 26 |
#' @exportClass DataSubject |
|
| 27 |
#' @export DataSubject |
|
| 28 |
.DataSubject <- setClass( |
|
| 29 |
Class = "DataSubject", |
|
| 30 |
representation = list( |
|
| 31 |
data = "data.frame", |
|
| 32 |
subject = "character", |
|
| 33 |
arm = "character", |
|
| 34 |
study = "character" |
|
| 35 |
) |
|
| 36 |
) |
|
| 37 | ||
| 38 | ||
| 39 |
#' @typed data: data.frame |
|
| 40 |
#' the subject-level data. |
|
| 41 |
#' @typed subject: character |
|
| 42 |
#' the name of the variable containing the subject identifier. |
|
| 43 |
#' @typed arm: character |
|
| 44 |
#' the name of the variable containing the arm identifier. |
|
| 45 |
#' @typed study: character |
|
| 46 |
#' the name of the variable containing the study identifier. |
|
| 47 |
#' @rdname DataSubject-class |
|
| 48 |
#' @export |
|
| 49 |
#' |
|
| 50 |
#' @returns A `DataSubject` object. |
|
| 51 |
#' |
|
| 52 |
#' @examples |
|
| 53 |
#' dat <- transform(os_data, study = "Study 1") |
|
| 54 |
#' DataSubject(dat, subject = "id", arm = "arm", study = "study") |
|
| 55 |
DataSubject <- function(data, subject, arm, study) {
|
|
| 56 | 538x |
vars <- c(subject, arm, study) |
| 57 | 538x |
vars_frm_chr <- paste0("~ ", paste(vars, collapse = " + "))
|
| 58 | 538x |
.DataSubject( |
| 59 | 538x |
data = remove_missing_rows(data, stats::as.formula(vars_frm_chr)), |
| 60 | 538x |
subject = subject, |
| 61 | 538x |
arm = arm, |
| 62 | 538x |
study = study |
| 63 |
) |
|
| 64 |
} |
|
| 65 | ||
| 66 |
setValidity( |
|
| 67 |
"DataSubject", |
|
| 68 |
method = function(object) {
|
|
| 69 |
if (length(object@subject) != 1) {
|
|
| 70 |
return("`subject` must be a length 1 character")
|
|
| 71 |
} |
|
| 72 |
if (length(object@arm) != 1) {
|
|
| 73 |
return("`arm` must be a length 1 character")
|
|
| 74 |
} |
|
| 75 |
if (length(object@study) != 1) {
|
|
| 76 |
return("`study` must be a length 1 character")
|
|
| 77 |
} |
|
| 78 |
if (nrow(object@data) == 0) {
|
|
| 79 |
return("`data` should not have 0 rows")
|
|
| 80 |
} |
|
| 81 |
sbj <- object@data[[object@subject]] |
|
| 82 |
if (!(is(sbj, "character") || is(sbj, "factor"))) {
|
|
| 83 |
return("`data[[subject]]` should be of type character or factor")
|
|
| 84 |
} |
|
| 85 |
} |
|
| 86 |
) |
|
| 87 | ||
| 88 | ||
| 89 |
#' @inheritParams DataSubject-Shared |
|
| 90 |
#' @inherit extractVariableNames description title |
|
| 91 |
#' |
|
| 92 |
#' @returns |
|
| 93 |
#' A list with the following named elements: |
|
| 94 |
#' - `subject` (`character`)\cr the name of the variable containing the subject identifier. |
|
| 95 |
#' - `arm` (`character`)\cr the name of the variable containing the arm identifier. |
|
| 96 |
#' - `study` (`character`) \cr the name of the variable containing the study identifier. |
|
| 97 |
#' @family DataSubject |
|
| 98 |
#' @family extractVariableNames |
|
| 99 |
#' @export |
|
| 100 |
#' @keywords internal |
|
| 101 |
extractVariableNames.DataSubject <- function(object) {
|
|
| 102 | 1292x |
list( |
| 103 | 1292x |
subject = object@subject, |
| 104 | 1292x |
arm = object@arm, |
| 105 | 1292x |
study = object@study |
| 106 |
) |
|
| 107 |
} |
|
| 108 | ||
| 109 | ||
| 110 |
#' @rdname as_stan_list.DataObject |
|
| 111 |
#' @family DataSubject |
|
| 112 |
#' @export |
|
| 113 |
as_stan_list.DataSubject <- function(object, ...) {
|
|
| 114 | 348x |
df <- as.data.frame(harmonise(object)) |
| 115 | 348x |
vars <- extractVariableNames(object) |
| 116 | ||
| 117 | 348x |
unique_arm_study_combos <- unique( |
| 118 | 348x |
data.frame( |
| 119 | 348x |
arm = as.numeric(df[[vars$arm]]), |
| 120 | 348x |
study = as.numeric(df[[vars$study]]) |
| 121 |
) |
|
| 122 |
) |
|
| 123 | ||
| 124 | 348x |
list( |
| 125 | 348x |
n_subjects = nrow(df), |
| 126 | 348x |
n_studies = length(unique(df[[vars$study]])), |
| 127 | 348x |
n_arms = length(unique(df[[vars$arm]])), |
| 128 | 348x |
subject_study_index = as.numeric(df[[vars$study]]), |
| 129 | 348x |
subject_arm_index = as.numeric(df[[vars$arm]]), |
| 130 | 348x |
subject_to_index = stats::setNames( |
| 131 | 348x |
seq_len(nlevels(df[[vars$subject]])), |
| 132 | 348x |
levels(df[[vars$subject]]) |
| 133 |
), |
|
| 134 | 348x |
arm_to_index = stats::setNames( |
| 135 | 348x |
seq_len(nlevels(df[[vars$arm]])), |
| 136 | 348x |
levels(df[[vars$arm]]) |
| 137 |
), |
|
| 138 | 348x |
study_to_index = stats::setNames( |
| 139 | 348x |
seq_len(nlevels(df[[vars$study]])), |
| 140 | 348x |
levels(df[[vars$study]]) |
| 141 |
), |
|
| 142 | 348x |
pop_arm_index = unique_arm_study_combos$arm, |
| 143 | 348x |
pop_study_index = unique_arm_study_combos$study |
| 144 |
) |
|
| 145 |
} |
|
| 146 | ||
| 147 |
#' @rdname as_stan_list.DataObject |
|
| 148 |
#' @export |
|
| 149 |
as.list.DataSubject <- function(x, ...) {
|
|
| 150 | ! |
as_stan_list(x, ...) |
| 151 |
} |
|
| 152 | ||
| 153 | ||
| 154 |
#' `DataSubject` -> `data.frame` |
|
| 155 |
#' |
|
| 156 |
#' @inheritParams DataSubject-Shared |
|
| 157 |
#' |
|
| 158 |
#' @description |
|
| 159 |
#' Converts a [`DataSubject`] object into a `data.frame`. |
|
| 160 |
#' The subject variable is cast to factor. |
|
| 161 |
#' @family DataSubject |
|
| 162 |
#' @export |
|
| 163 |
#' |
|
| 164 |
#' @returns A `data.frame` representation of the object. |
|
| 165 |
as.data.frame.DataSubject <- function(x, ...) {
|
|
| 166 | 1031x |
x <- x@data |
| 167 | 1031x |
rownames(x) <- NULL |
| 168 | 1031x |
x |
| 169 |
} |
|
| 170 | ||
| 171 | ||
| 172 |
#' @rdname harmonise |
|
| 173 |
#' @exportS3Method NULL |
|
| 174 |
harmonise.DataSubject <- function(object, ...) {
|
|
| 175 | 455x |
data <- as.data.frame(object) |
| 176 | 455x |
vars <- extractVariableNames(object) |
| 177 | 455x |
assert_that( |
| 178 | 455x |
vars$subject %in% names(data), |
| 179 | 455x |
vars$arm %in% names(data), |
| 180 | 455x |
vars$study %in% names(data) |
| 181 |
) |
|
| 182 | 455x |
assert_character( |
| 183 | 455x |
as.character(data[[vars$subject]]), |
| 184 | 455x |
any.missing = FALSE, |
| 185 | 455x |
unique = TRUE |
| 186 |
) |
|
| 187 | 454x |
data[[vars$subject]] <- factor(data[[vars$subject]]) |
| 188 | 454x |
data[[vars$arm]] <- factor(data[[vars$arm]]) |
| 189 | 454x |
data[[vars$study]] <- factor(data[[vars$study]]) |
| 190 | 454x |
data <- data[order(data[[vars$subject]]), ] |
| 191 | 454x |
DataSubject( |
| 192 | 454x |
data = data, |
| 193 | 454x |
subject = object@subject, |
| 194 | 454x |
arm = object@arm, |
| 195 | 454x |
study = object@study |
| 196 |
) |
|
| 197 |
} |
|
| 198 | ||
| 199 |
#' `DataSubject` -> Printable `Character` |
|
| 200 |
#' |
|
| 201 |
#' Converts [`DataSubject`] object into a printable string. |
|
| 202 |
#' @inheritParams DataSubject-Shared |
|
| 203 |
#' @family DataSubject |
|
| 204 |
#' @keywords internal |
|
| 205 |
#' @typed indent: numeric |
|
| 206 |
#' how much white space to prefix the print string with. |
|
| 207 |
#' @export |
|
| 208 |
#' |
|
| 209 |
#' @returns A character vector suitable for printing. |
|
| 210 |
as_print_string.DataSubject <- function(object, indent = 1, ...) {
|
|
| 211 | 4x |
template <- c( |
| 212 | 4x |
"Subject-Data Object:", |
| 213 | 4x |
" # of Subjects = %d", |
| 214 | 4x |
" # of Studies = %d", |
| 215 | 4x |
" # of Arms = %d" |
| 216 |
) |
|
| 217 | 4x |
pad <- rep(" ", indent) |> paste(collapse = "")
|
| 218 | 4x |
template_padded <- paste(pad, template) |
| 219 | 4x |
vars <- extractVariableNames(object) |
| 220 | 4x |
sprintf( |
| 221 | 4x |
paste(template_padded, collapse = "\n"), |
| 222 | 4x |
nrow(object@data), |
| 223 | 4x |
length(unique(object@data[[vars$study]])), |
| 224 | 4x |
length(unique(object@data[[vars$arm]])) |
| 225 |
) |
|
| 226 |
} |
|
| 227 | ||
| 228 |
#' @rdname show-object |
|
| 229 |
#' @export |
|
| 230 |
setMethod( |
|
| 231 |
f = "show", |
|
| 232 |
signature = "DataSubject", |
|
| 233 |
definition = function(object) {
|
|
| 234 | 1x |
string <- as_print_string(object) |
| 235 | 1x |
cat("\n", string, "\n\n")
|
| 236 |
} |
|
| 237 |
) |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include utilities.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Re-used documentation for `DataLongitudinal` |
|
| 6 |
#' |
|
| 7 |
#' @typed object: DataLongitudinal |
|
| 8 |
#' Longitudinal Data. |
|
| 9 |
#' @typed x: DataLongitudinal |
|
| 10 |
#' Longitudinal Data. |
|
| 11 |
#' @param ... Not Used. |
|
| 12 |
#' |
|
| 13 |
#' @name DataLongitudinal-Shared |
|
| 14 |
#' @keywords internal |
|
| 15 |
NULL |
|
| 16 | ||
| 17 | ||
| 18 |
# DataLongitudinal-class ---- |
|
| 19 | ||
| 20 |
#' Longitudinal Data Object and Constructor Function |
|
| 21 |
#' |
|
| 22 |
#' The [`DataLongitudinal`] class handles the processing of the longitudinal data for fitting a Joint Model. |
|
| 23 |
#' |
|
| 24 |
#' |
|
| 25 |
#' @slot data (`data.frame`)\cr See Arguments for details; Note that |
|
| 26 |
#' observations that contain missing values in the required variables are removed. |
|
| 27 |
#' @slot formula (`formula`)\cr See Arguments for details |
|
| 28 |
#' @slot threshold (`numeric`)\cr See Arguments for details |
|
| 29 |
#' |
|
| 30 |
#' @family DataObjects |
|
| 31 |
#' @family DataLongitudinal |
|
| 32 |
#' @export DataLongitudinal |
|
| 33 |
#' @exportClass DataLongitudinal |
|
| 34 |
.DataLongitudinal <- setClass( |
|
| 35 |
Class = "DataLongitudinal", |
|
| 36 |
representation = list( |
|
| 37 |
data = "data.frame", |
|
| 38 |
formula = "formula", |
|
| 39 |
threshold = "numeric_or_NULL" |
|
| 40 |
) |
|
| 41 |
) |
|
| 42 | ||
| 43 | ||
| 44 |
#' @typed data: data.frame |
|
| 45 |
#' containing the observed longitudinal data. |
|
| 46 |
#' @typed formula: formula |
|
| 47 |
#' of the form `outcome ~ time`, and cannot contain any additional covariates. |
|
| 48 |
#' @typed threshold: numeric |
|
| 49 |
#' cut-off value to be used to declare an observation as censored |
|
| 50 |
#' (below detection limit). |
|
| 51 |
#' @rdname DataLongitudinal-class |
|
| 52 |
#' |
|
| 53 |
#' @returns A `DataLongitudinal` object. |
|
| 54 |
#' |
|
| 55 |
#' @examples |
|
| 56 |
#' DataLongitudinal(tumor_data, sld ~ year, threshold = 5) |
|
| 57 |
DataLongitudinal <- function(data, formula, threshold = NULL) {
|
|
| 58 | 60x |
.DataLongitudinal( |
| 59 | 60x |
data = remove_missing_rows(data, formula), |
| 60 | 60x |
formula = formula, |
| 61 | 60x |
threshold = threshold |
| 62 |
) |
|
| 63 |
} |
|
| 64 | ||
| 65 |
setValidity( |
|
| 66 |
"DataLongitudinal", |
|
| 67 |
function(object) {
|
|
| 68 |
if (!length(object@formula) == 3) {
|
|
| 69 |
return("`formula` should be a 2 sided formula")
|
|
| 70 |
} |
|
| 71 |
if (!length(object@formula[[3]]) == 1) {
|
|
| 72 |
return("the RHS of `formula` should only have 1 value")
|
|
| 73 |
} |
|
| 74 |
if (!length(object@threshold) <= 1) {
|
|
| 75 |
return("`threshold` must be of length 1 or `NULL`")
|
|
| 76 |
} |
|
| 77 |
vars <- extractVariableNames(object) |
|
| 78 |
vars$threshold <- NULL |
|
| 79 |
vars$frm <- NULL |
|
| 80 |
for (i in unlist(vars)) {
|
|
| 81 |
if (!i %in% names(object@data)) {
|
|
| 82 |
return(sprintf("Variable `%s` is not in data", i))
|
|
| 83 |
} |
|
| 84 |
} |
|
| 85 |
return(TRUE) |
|
| 86 |
} |
|
| 87 |
) |
|
| 88 | ||
| 89 | ||
| 90 |
#' @rdname harmonise |
|
| 91 |
#' @exportS3Method NULL |
|
| 92 |
harmonise.DataLongitudinal <- function(object, subject_var, subject_ord, ...) {
|
|
| 93 | 30x |
data <- as.data.frame(object) |
| 94 | 30x |
vars <- extractVariableNames(object) |
| 95 | 30x |
assert_string(subject_var, na.ok = FALSE) |
| 96 | 30x |
assert_character(subject_ord, any.missing = FALSE) |
| 97 | 30x |
assert_that( |
| 98 | 30x |
subject_var %in% names(data), |
| 99 | 30x |
msg = sprintf( |
| 100 | 30x |
"Subject variable `%s` not found in `longitudinal`", |
| 101 | 30x |
subject_var |
| 102 |
) |
|
| 103 |
) |
|
| 104 | 29x |
assert_that( |
| 105 | 29x |
all(data[[subject_var]] %in% subject_ord), |
| 106 | 29x |
msg = "There are subjects in `longitudinal` that are not present in `subjects`" |
| 107 |
) |
|
| 108 | 28x |
assert_that( |
| 109 | 28x |
all(subject_ord %in% data[[subject_var]]), |
| 110 | 28x |
msg = "There are subjects in `subjects` that are not present in `longitudinal`" |
| 111 |
) |
|
| 112 | 27x |
data[[subject_var]] <- factor( |
| 113 | 27x |
as.character(data[[subject_var]]), |
| 114 | 27x |
levels = subject_ord |
| 115 |
) |
|
| 116 | 27x |
data_re_ord <- order( |
| 117 | 27x |
data[[subject_var]], |
| 118 | 27x |
data[[vars$time]], |
| 119 | 27x |
data[[vars$outcome]] |
| 120 |
) |
|
| 121 | 27x |
data_ord <- data[data_re_ord, ] |
| 122 | 27x |
DataLongitudinal( |
| 123 | 27x |
data = data_ord, |
| 124 | 27x |
formula = object@formula, |
| 125 | 27x |
threshold = object@threshold |
| 126 |
) |
|
| 127 |
} |
|
| 128 | ||
| 129 | ||
| 130 |
#' `DataLongitudinal` -> `data.frame` |
|
| 131 |
#' |
|
| 132 |
#' @inheritParams DataLongitudinal-Shared |
|
| 133 |
#' |
|
| 134 |
#' @description |
|
| 135 |
#' Converts a [`DataLongitudinal`] object into a `data.frame`. |
|
| 136 |
#' The subject variable is cast to factor. |
|
| 137 |
#' @family DataLongitudinal |
|
| 138 |
#' @export |
|
| 139 |
#' |
|
| 140 |
#' @returns A `data.frame` representation of the object. |
|
| 141 |
as.data.frame.DataLongitudinal <- function(x, ...) {
|
|
| 142 | 378x |
x <- x@data |
| 143 | 378x |
rownames(x) <- NULL |
| 144 | 378x |
x |
| 145 |
} |
|
| 146 | ||
| 147 | ||
| 148 |
#' @inheritParams DataLongitudinal-Shared |
|
| 149 |
#' @inherit extractVariableNames description title |
|
| 150 |
#' |
|
| 151 |
#' @returns |
|
| 152 |
#' A list with the following named elements: |
|
| 153 |
#' - `subject` (`character`)\cr The name of the variable containing the subject identifier |
|
| 154 |
#' - `frm` (`formula`)\cr of the form `outcome ~ time` |
|
| 155 |
#' - `time` (`character`)\cr The name of the variable containing the outcome time |
|
| 156 |
#' - `outcome` (`character`)\cr The name of the variable containing the outcome values |
|
| 157 |
#' - `threshold` (`numeric`)\cr cut-off value to be used to declare an observation as censored |
|
| 158 |
#' (below detection limit). |
|
| 159 |
#' @family DataLongitudinal |
|
| 160 |
#' @family extractVariableNames |
|
| 161 |
#' @exportS3Method NULL |
|
| 162 |
extractVariableNames.DataLongitudinal <- function(object) {
|
|
| 163 | 412x |
list( |
| 164 | 412x |
frm = object@formula, |
| 165 | 412x |
time = as.character(object@formula[[3]]), |
| 166 | 412x |
outcome = as.character(object@formula[[2]]), |
| 167 | 412x |
threshold = object@threshold |
| 168 |
) |
|
| 169 |
} |
|
| 170 | ||
| 171 | ||
| 172 |
#' @rdname as_stan_list.DataObject |
|
| 173 |
#' @family DataLongitudinal |
|
| 174 |
#' @export |
|
| 175 |
as_stan_list.DataLongitudinal <- function(object, subject_var, ...) {
|
|
| 176 | 318x |
df <- as.data.frame(object) |
| 177 | 318x |
vars <- extractVariableNames(object) |
| 178 | ||
| 179 | 318x |
assert_factor(df[[subject_var]]) |
| 180 | ||
| 181 | 318x |
mat_sld_index <- stats::model.matrix( |
| 182 | 318x |
stats::as.formula(paste("~ -1 + ", subject_var)),
|
| 183 | 318x |
data = df |
| 184 |
) |> |
|
| 185 | 318x |
t() |
| 186 | ||
| 187 | 318x |
adj_threshold <- if (is.null(vars$threshold)) {
|
| 188 | 64x |
-999999 |
| 189 |
} else {
|
|
| 190 | 254x |
vars$threshold |
| 191 |
} |
|
| 192 | ||
| 193 | 318x |
index_obs <- which(df[[vars$outcome]] >= adj_threshold) |
| 194 | 318x |
index_cen <- which(df[[vars$outcome]] < adj_threshold) |
| 195 | ||
| 196 | 318x |
sparse_mat_inds_all_y <- rstan::extract_sparse_parts(mat_sld_index) |
| 197 | 318x |
sparse_mat_inds_obs_y <- rstan::extract_sparse_parts( |
| 198 | 318x |
mat_sld_index[, index_obs] |
| 199 |
) |
|
| 200 | 318x |
sparse_mat_inds_cens_y <- rstan::extract_sparse_parts( |
| 201 | 318x |
mat_sld_index[, index_cen] |
| 202 |
) |
|
| 203 | ||
| 204 | 318x |
model_data <- list( |
| 205 | 318x |
n_tumour_all = nrow(df), |
| 206 | ||
| 207 |
# Number of individuals and tumour assessments. |
|
| 208 | 318x |
n_tumour_obs = length(index_obs), |
| 209 | 318x |
n_tumour_cens = length(index_cen), |
| 210 | ||
| 211 |
# Index vectors |
|
| 212 | 318x |
subject_tumour_index = as.numeric(df[[subject_var]]), |
| 213 | 318x |
subject_tumour_index_obs = index_obs, |
| 214 | 318x |
subject_tumour_index_cens = index_cen, |
| 215 | ||
| 216 | 318x |
tumour_value = df[[vars$outcome]], |
| 217 | 318x |
tumour_time = df[[vars$time]], |
| 218 | 318x |
tumour_value_lloq = adj_threshold, |
| 219 | ||
| 220 |
# Sparse matrix parameters |
|
| 221 |
# Matrix of individuals x observed tumour assessments. |
|
| 222 | 318x |
n_mat_inds_obs_y = c( |
| 223 | 318x |
length(sparse_mat_inds_obs_y$w), |
| 224 | 318x |
length(sparse_mat_inds_obs_y$v), |
| 225 | 318x |
length(sparse_mat_inds_obs_y$u) |
| 226 |
), |
|
| 227 | 318x |
w_mat_inds_obs_y = sparse_mat_inds_obs_y$w, |
| 228 | 318x |
v_mat_inds_obs_y = sparse_mat_inds_obs_y$v, |
| 229 | 318x |
u_mat_inds_obs_y = sparse_mat_inds_obs_y$u, |
| 230 | ||
| 231 |
# Matrix of individuals x censored tumour assessments. |
|
| 232 | 318x |
n_mat_inds_cens_y = c( |
| 233 | 318x |
length(sparse_mat_inds_cens_y$w), |
| 234 | 318x |
length(sparse_mat_inds_cens_y$v), |
| 235 | 318x |
length(sparse_mat_inds_cens_y$u) |
| 236 |
), |
|
| 237 | 318x |
w_mat_inds_cens_y = sparse_mat_inds_cens_y$w, |
| 238 | 318x |
v_mat_inds_cens_y = sparse_mat_inds_cens_y$v, |
| 239 | 318x |
u_mat_inds_cens_y = sparse_mat_inds_cens_y$u, |
| 240 | ||
| 241 |
# Matrix of all individuals tumour assessments |
|
| 242 | 318x |
n_mat_inds_all_y = c( |
| 243 | 318x |
length(sparse_mat_inds_all_y$w), |
| 244 | 318x |
length(sparse_mat_inds_all_y$v), |
| 245 | 318x |
length(sparse_mat_inds_all_y$u) |
| 246 |
), |
|
| 247 | 318x |
w_mat_inds_all_y = sparse_mat_inds_all_y$w, |
| 248 | 318x |
v_mat_inds_all_y = sparse_mat_inds_all_y$v, |
| 249 | 318x |
u_mat_inds_all_y = sparse_mat_inds_all_y$u |
| 250 |
) |
|
| 251 | ||
| 252 | 318x |
return(model_data) |
| 253 |
} |
|
| 254 | ||
| 255 |
#' @rdname as_stan_list.DataObject |
|
| 256 |
#' @export |
|
| 257 |
as.list.DataLongitudinal <- function(x, ...) {
|
|
| 258 | ! |
as_stan_list(x, ...) |
| 259 |
} |
|
| 260 | ||
| 261 | ||
| 262 |
#' `DataLongitudinal` -> Printable `Character` |
|
| 263 |
#' |
|
| 264 |
#' Converts [`DataLongitudinal`] object into a printable string. |
|
| 265 |
#' @inheritParams DataLongitudinal-Shared |
|
| 266 |
#' @family DataLongitudinal |
|
| 267 |
#' @typed indent: numeric |
|
| 268 |
#' how much white space to prefix the print string with. |
|
| 269 |
#' @keywords internal |
|
| 270 |
#' @export |
|
| 271 |
#' |
|
| 272 |
#' @returns A character vector suitable for printing. |
|
| 273 |
as_print_string.DataLongitudinal <- function(object, indent = 1, ...) {
|
|
| 274 | 4x |
template <- c( |
| 275 | 4x |
"Longitudinal-Data Object:", |
| 276 | 4x |
" # of Rows = %d", |
| 277 | 4x |
" # of Columns = %d", |
| 278 | 4x |
" # of Cen-Obvs = %d", |
| 279 | 4x |
" Formula = %s" |
| 280 |
) |
|
| 281 | 4x |
pad <- rep(" ", indent) |> paste(collapse = "")
|
| 282 | 4x |
template_padded <- paste(pad, template) |
| 283 | 4x |
vars <- extractVariableNames(object) |
| 284 | 4x |
sprintf( |
| 285 | 4x |
paste(template_padded, collapse = "\n"), |
| 286 | 4x |
nrow(object@data), |
| 287 | 4x |
ncol(object@data), |
| 288 | 4x |
sum(object@data[[vars$outcome]] < vars$threshold), |
| 289 | 4x |
Reduce(paste, deparse(vars$frm)) |
| 290 |
) |
|
| 291 |
} |
|
| 292 | ||
| 293 |
#' @rdname show-object |
|
| 294 |
#' @export |
|
| 295 |
setMethod( |
|
| 296 |
f = "show", |
|
| 297 |
signature = "DataLongitudinal", |
|
| 298 |
definition = function(object) {
|
|
| 299 | 2x |
string <- as_print_string(object) |
| 300 | 2x |
cat("\n", string, "\n\n")
|
| 301 |
} |
|
| 302 |
) |
| 1 |
#' @include Prior.R |
|
| 2 |
#' @include PriorTransform.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Prior on a Transformed Parameter |
|
| 6 |
#' |
|
| 7 |
#' Places a distribution on `g(x)` rather than directly on the declared model |
|
| 8 |
#' parameter `x`, where `g` is a package-defined transformation. The Stan |
|
| 9 |
#' density includes the log absolute Jacobian of `g`. The same scalar |
|
| 10 |
#' distribution is applied independently to each component of a vector. |
|
| 11 |
#' |
|
| 12 |
#' @typed dist: Prior |
|
| 13 |
#' a scalar distribution prior: normal, standard normal, Cauchy, gamma, |
|
| 14 |
#' log-normal, beta, uniform, Student-t, logistic, log-logistic or |
|
| 15 |
#' inverse-gamma. |
|
| 16 |
#' Constant, flat, initialization-only, vector-specific, horseshoe and already |
|
| 17 |
#' transformed priors are not supported. |
|
| 18 |
#' @typed transform: PriorTransform |
|
| 19 |
#' package-defined transformation from the model parameter to the prior |
|
| 20 |
#' density scale, created by [prior_transform_exp()] or |
|
| 21 |
#' [prior_transform_square()]. |
|
| 22 |
#' @details |
|
| 23 |
#' Equivalently, supply `transform` directly to a scalar distribution helper. |
|
| 24 |
#' The density is proportional to `p(g(x)) * abs(g'(x))`. Distribution support |
|
| 25 |
#' and any existing limits on `dist` are intersected with the transformation's |
|
| 26 |
#' range and mapped back to bounds on `x`. Subsequent [set_limits()] calls use |
|
| 27 |
#' the model parameter scale and retain these support restrictions. Truncation |
|
| 28 |
#' normalisation is evaluated on the density scale. |
|
| 29 |
#' |
|
| 30 |
#' Initial values are drawn on the density scale within the mapped bounds, |
|
| 31 |
#' inverse-transformed and shrunk towards a centre on the model parameter |
|
| 32 |
#' scale using `jmpost.prior_shrinkage`. If the inverse-transformed distribution |
|
| 33 |
#' centre is outside the bounds or non-finite, an interior reference value is |
|
| 34 |
#' used. Initialization uses bounded rejection sampling and reports an error |
|
| 35 |
#' when the admissible region has too little probability to find valid draws. |
|
| 36 |
#' |
|
| 37 |
#' This changes the prior distribution. Predictor parametrizations and Stan |
|
| 38 |
#' affine multipliers serve different purposes and do not supply this Jacobian. |
|
| 39 |
#' |
|
| 40 |
#' @returns A `TransformedPrior` object, inheriting from `Prior`. |
|
| 41 |
#' @family Prior |
|
| 42 |
#' @export |
|
| 43 |
#' @examples |
|
| 44 |
#' prior_normal(60, 60, transform = prior_transform_exp()) |
|
| 45 |
#' prior_invgamma(1.5, 0.3, transform = prior_transform_exp(scale = 2)) |
|
| 46 |
#' prior_transform(prior_normal(100, 100), prior_transform_square()) |
|
| 47 |
prior_transform <- function(dist, transform) {
|
|
| 48 | 80x |
assert_that(is(dist, "Prior"), msg = "`dist` must be a Prior object") |
| 49 | 79x |
assert_that( |
| 50 | 79x |
is(transform, "PriorTransform"), |
| 51 | 79x |
msg = "`transform` must be a package-defined PriorTransform object" |
| 52 |
) |
|
| 53 | 78x |
validObject(transform) |
| 54 | 78x |
density <- sub("^\\{name\\} ~ ([a-z_]+)\\(.*$", "\\1", dist@repr_model)
|
| 55 | 78x |
supported <- c( |
| 56 | 78x |
"normal", "std_normal", "cauchy", "gamma", "lognormal", "beta", |
| 57 | 78x |
"uniform", "student_t", "logistic", "loglogistic", "inv_gamma" |
| 58 |
) |
|
| 59 | 78x |
assert_that( |
| 60 | 78x |
!is(dist, "TransformedPrior") && !dist@.is_const && |
| 61 | 78x |
!dist@.allow_vectors && density %in% supported, |
| 62 | 78x |
msg = "`dist` must be a supported, untransformed scalar prior" |
| 63 |
) |
|
| 64 | 72x |
support <- switch( |
| 65 | 72x |
density, |
| 66 | 72x |
gamma = , lognormal = , loglogistic = , inv_gamma = c(0, Inf), |
| 67 | 72x |
beta = c(0, 1), |
| 68 | 72x |
uniform = c(dist@parameters$alpha, dist@parameters$beta), |
| 69 | 72x |
c(-Inf, Inf) |
| 70 |
) |
|
| 71 |
# Store intrinsic density support separately from later model-scale bounds. |
|
| 72 | 72x |
density_limits <- c( |
| 73 | 72x |
max(0, support[1], dist@limits[1]), |
| 74 | 72x |
min(support[2], dist@limits[2]) |
| 75 |
) |
|
| 76 | 72x |
assert_that( |
| 77 | 72x |
density_limits[1] < density_limits[2], |
| 78 | 72x |
msg = "The prior support does not overlap the transformation range" |
| 79 |
) |
|
| 80 | 71x |
dist@limits <- density_limits |
| 81 | 71x |
dist@.omit_zero_lower_truncation <- density %in% |
| 82 | 71x |
c("gamma", "lognormal", "loglogistic", "inv_gamma")
|
| 83 | 71x |
limits <- .prior_transform_inverse(transform, density_limits) |
| 84 | 71x |
object <- .TransformedPrior( |
| 85 | 71x |
dist, |
| 86 | 71x |
distribution = dist, |
| 87 | 71x |
transform = transform, |
| 88 | 71x |
limits = limits |
| 89 |
) |
|
| 90 | 71x |
.update_transformed_prior_initialization(object) |
| 91 |
} |
|
| 92 | ||
| 93 | ||
| 94 |
#' @rdname prior_transform |
|
| 95 |
#' @slot distribution (`Prior`)\cr Distribution on the transformed scale. |
|
| 96 |
#' @slot transform (`PriorTransform`)\cr Transformation to the density scale. |
|
| 97 |
#' @exportClass TransformedPrior |
|
| 98 |
.TransformedPrior <- setClass( |
|
| 99 |
"TransformedPrior", |
|
| 100 |
contains = "Prior", |
|
| 101 |
slots = c(distribution = "Prior", transform = "PriorTransform") |
|
| 102 |
) |
|
| 103 | ||
| 104 | ||
| 105 |
.update_transformed_prior_initialization <- function(object) {
|
|
| 106 | 76x |
dist <- object@distribution |
| 107 | 76x |
transform <- object@transform |
| 108 | 76x |
limits <- object@limits |
| 109 | 76x |
centre <- dist@centre |
| 110 | 76x |
centre <- if (is.finite(centre) && centre > 0) {
|
| 111 | 51x |
.prior_transform_inverse(transform, centre) |
| 112 |
} else {
|
|
| 113 | 76x |
NA_real_ |
| 114 |
} |
|
| 115 | 76x |
if (!is.finite(centre) || centre <= limits[1] || centre >= limits[2]) {
|
| 116 | 26x |
centre <- if (all(is.finite(limits))) {
|
| 117 | 3x |
limits[1] / 2 + limits[2] / 2 |
| 118 | 26x |
} else if (is.finite(limits[1])) {
|
| 119 | 9x |
limits[1] + max(1, abs(limits[1]) * 0.1) |
| 120 | 26x |
} else if (is.finite(limits[2])) {
|
| 121 | ! |
limits[2] - max(1, abs(limits[2]) * 0.1) |
| 122 |
} else {
|
|
| 123 | 14x |
0 |
| 124 |
} |
|
| 125 |
} |
|
| 126 | 76x |
object@centre <- centre |
| 127 | 76x |
object@sample <- function(n) {
|
| 128 | 2090x |
density_limits <- .prior_transform_forward(transform, limits) |
| 129 | 2090x |
values <- numeric() |
| 130 | 2090x |
for (attempt in seq_len(100)) {
|
| 131 | 2201x |
draws <- dist@sample(max(100, 2 * n)) |
| 132 | 2201x |
draws <- draws[ |
| 133 | 2201x |
is.finite(draws) & draws > density_limits[1] & |
| 134 | 2201x |
draws < density_limits[2] |
| 135 |
] |
|
| 136 | 2201x |
values <- c(values, .prior_transform_inverse(transform, draws)) |
| 137 | 2201x |
values <- values[ |
| 138 | 2201x |
is.finite(values) & values > limits[1] & values < limits[2] |
| 139 |
] |
|
| 140 | 2089x |
if (length(values) >= n) return(values[seq_len(n)]) |
| 141 |
} |
|
| 142 | 1x |
stop("Unable to generate initial values within transformed bounds")
|
| 143 |
} |
|
| 144 | 76x |
object |
| 145 |
} |
|
| 146 | ||
| 147 | ||
| 148 |
#' @rdname set_limits |
|
| 149 |
#' @export |
|
| 150 |
set_limits.TransformedPrior <- function(object, lower = -Inf, upper = Inf) {
|
|
| 151 | 6x |
support <- .prior_transform_inverse( |
| 152 | 6x |
object@transform, object@distribution@limits |
| 153 |
) |
|
| 154 | 6x |
object@limits <- c(max(lower, support[1]), min(upper, support[2])) |
| 155 | 6x |
validObject(object) |
| 156 | 5x |
.update_transformed_prior_initialization(object) |
| 157 |
} |
|
| 158 | ||
| 159 | ||
| 160 |
#' @rdname as.character.Prior |
|
| 161 |
#' @export |
|
| 162 |
as.character.TransformedPrior <- function(x, ...) {
|
|
| 163 | 2x |
transform <- .prior_transform_stan(x@transform, "x")$value |
| 164 | 2x |
limits <- render_stan_limits(x@limits) |
| 165 | 2x |
paste0( |
| 166 | 2x |
as.character(x@distribution), " on ", transform, |
| 167 | 2x |
if (nzchar(limits)) paste0("; x", limits) else ""
|
| 168 |
) |
|
| 169 |
} |
|
| 170 | ||
| 171 | ||
| 172 |
#' @rdname as.StanModule.Prior |
|
| 173 |
#' @export |
|
| 174 |
as.StanModule.TransformedPrior <- function(object, name, size = 1, ...) {
|
|
| 175 | 68x |
scalar <- is.numeric(size) && length(size) == 1 && size == 1 |
| 176 | 68x |
x <- if (scalar) "{name}" else "{name}[prior_i]"
|
| 177 | 68x |
code <- .prior_transform_stan(object@transform, x) |
| 178 | 68x |
dist <- object@distribution |
| 179 | 68x |
dist@limits <- .prior_transform_forward(object@transform, object@limits) |
| 180 | 68x |
dist@repr_model <- sub("{name}", code$value, dist@repr_model, fixed = TRUE)
|
| 181 | 68x |
module <- as.StanModule.Prior(dist, name = name, size = size) |
| 182 | 68x |
jacobian <- glue::glue(code$log_jacobian, name = name) |
| 183 | 68x |
module@model <- paste0(module@model, "\n target += ", jacobian, ";") |
| 184 | 68x |
if (!scalar) {
|
| 185 | 5x |
module@model <- paste0( |
| 186 | 5x |
" if (", size, " > 0) {\n",
|
| 187 | 5x |
" for (prior_i in 1:", size, ") {\n",
|
| 188 | 5x |
paste0( |
| 189 | 5x |
" ", strsplit(module@model, "\n")[[1]], collapse = "\n" |
| 190 |
), |
|
| 191 | 5x |
"\n }\n }" |
| 192 |
) |
|
| 193 |
} |
|
| 194 | 68x |
module |
| 195 |
} |
| 1 |
#' @include Grid.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' @rdname Grid-Dev |
|
| 7 |
.GridManual <- setClass( |
|
| 8 |
"GridManual", |
|
| 9 |
contains = "Grid", |
|
| 10 |
slots = c( |
|
| 11 |
"spec" = "list" |
|
| 12 |
) |
|
| 13 |
) |
|
| 14 | ||
| 15 | ||
| 16 |
#' @rdname Grid-Functions |
|
| 17 |
#' @export |
|
| 18 |
GridManual <- function(spec) {
|
|
| 19 | 8x |
.GridManual( |
| 20 | 8x |
spec = spec |
| 21 |
) |
|
| 22 |
} |
|
| 23 | ||
| 24 | ||
| 25 |
setValidity( |
|
| 26 |
"GridManual", |
|
| 27 |
function(object) {
|
|
| 28 |
subject_names <- names(object@spec) |
|
| 29 |
subject_names_valid <- subject_names[ |
|
| 30 |
!is.na(subject_names) & subject_names != "" |
|
| 31 |
] |
|
| 32 |
if (length(subject_names_valid) != length(object@spec)) {
|
|
| 33 |
return("Each element of `subjects` must be named")
|
|
| 34 |
} |
|
| 35 |
for (times in object@spec) {
|
|
| 36 |
if (!is.numeric(times)) {
|
|
| 37 |
return("Each element of `spec` must be a numeric vector")
|
|
| 38 |
} |
|
| 39 |
if (length(times) != length(unique(times))) {
|
|
| 40 |
return("Each time vector per subject must be unique")
|
|
| 41 |
} |
|
| 42 |
} |
|
| 43 |
return(TRUE) |
|
| 44 |
} |
|
| 45 |
) |
|
| 46 | ||
| 47 | ||
| 48 |
#' @rdname Quant-Dev |
|
| 49 |
#' @export |
|
| 50 |
as.QuantityGenerator.GridManual <- function(object, data, ...) {
|
|
| 51 | 14x |
assert_class(data, "DataJoint") |
| 52 | 14x |
data_list <- as.list(data) |
| 53 | 14x |
assert_that( |
| 54 | 14x |
all(names(object@spec) %in% names(data_list$subject_to_index)), |
| 55 | 14x |
msg = "All subject names must be in the `DataSubject` object" |
| 56 |
) |
|
| 57 | 14x |
lens <- vapply(object@spec, length, numeric(1)) |
| 58 | 14x |
QuantityGeneratorSubject( |
| 59 | 14x |
times = unlist(object@spec, use.names = FALSE), |
| 60 | 14x |
subjects = rep(names(object@spec), lens) |
| 61 |
) |
|
| 62 |
} |
|
| 63 | ||
| 64 | ||
| 65 |
#' @rdname Quant-Dev |
|
| 66 |
#' @export |
|
| 67 |
as.QuantityCollapser.GridManual <- function(object, data, ...) {
|
|
| 68 | 6x |
generator <- as.QuantityGenerator(object, data) |
| 69 | 6x |
QuantityCollapser( |
| 70 | 6x |
times = generator@times, |
| 71 | 6x |
groups = generator@subjects, |
| 72 | 6x |
indexes = as.list(seq_along(generator@times)) |
| 73 |
) |
|
| 74 |
} |
|
| 75 | ||
| 76 | ||
| 77 |
#' @export |
|
| 78 |
#' |
|
| 79 |
#' @returns A named `list` representation of the object. |
|
| 80 |
as.list.GridManual <- function(x, data, ...) {
|
|
| 81 | ! |
subs <- as.list(names(x@spec)) |
| 82 | ! |
names(subs) <- names(x@spec) |
| 83 | ! |
subs |
| 84 |
} |
| 1 |
#' Re-used documentation for `RandomEffectQuantities` |
|
| 2 |
#' |
|
| 3 |
#' @typed x: RandomEffectQuantities |
|
| 4 |
#' generated quantities. |
|
| 5 |
#' @typed object: RandomEffectQuantities |
|
| 6 |
#' generated quantities. |
|
| 7 |
#' @typed conf.level: numeric |
|
| 8 |
#' confidence level of the interval. |
|
| 9 |
#' @param ... not used. |
|
| 10 |
#' |
|
| 11 |
#' @keywords internal |
|
| 12 |
#' @name RandomEffectQuantities-Shared |
|
| 13 |
NULL |
|
| 14 | ||
| 15 | ||
| 16 |
#' Random Effects Quantities Container |
|
| 17 |
#' |
|
| 18 |
#' A simple wrapper around a `matrix` to store required metadata for patient level |
|
| 19 |
#' random effects data |
|
| 20 |
#' |
|
| 21 |
#' @typed quantities: matrix |
|
| 22 |
#' of random effects values. |
|
| 23 |
#' @typed subject: character |
|
| 24 |
#' labels specifying which subjects the values belong to. |
|
| 25 |
#' @typed parameter: character |
|
| 26 |
#' labels specifying which parameter the value is. |
|
| 27 |
#' |
|
| 28 |
#' @slot quantities (`matrix`)\cr See Arguments for details. |
|
| 29 |
#' @slot subject (`numeric`)\cr See Arguments for details. |
|
| 30 |
#' @slot parameter (`character`)\cr See Arguments for details. |
|
| 31 |
#' |
|
| 32 |
#' @details |
|
| 33 |
#' Each row of the matrix represents a sample and each column represents a distinct subject |
|
| 34 |
#' specific parameter. |
|
| 35 |
#' As such the number of columns in the matrix should equal the length of `subject` and `parameter` |
|
| 36 |
#' which provide metadata for who the parameter corresponds to as well as which parameter it is. |
|
| 37 |
#' |
|
| 38 |
#' @keywords internal |
|
| 39 |
#' @name RandomEffectQuantities-class |
|
| 40 |
#' @family RandomEffectQuantities |
|
| 41 |
.RandomEffectQuantities <- setClass( |
|
| 42 |
"RandomEffectQuantities", |
|
| 43 |
slots = list( |
|
| 44 |
"quantities" = "matrix", |
|
| 45 |
"subject" = "character", |
|
| 46 |
"parameter" = "character" |
|
| 47 |
) |
|
| 48 |
) |
|
| 49 |
#' @rdname RandomEffectQuantities-class |
|
| 50 |
#' |
|
| 51 |
#' @returns A `RandomEffectQuantities` object. |
|
| 52 |
RandomEffectQuantities <- function(quantities, subject, parameter) {
|
|
| 53 | 2x |
.RandomEffectQuantities( |
| 54 | 2x |
quantities = quantities, |
| 55 | 2x |
subject = subject, |
| 56 | 2x |
parameter = parameter |
| 57 |
) |
|
| 58 |
} |
|
| 59 | ||
| 60 |
setValidity( |
|
| 61 |
Class = "RandomEffectQuantities", |
|
| 62 |
method = function(object) {
|
|
| 63 |
if (length(object@subject) != ncol(object@quantities)) {
|
|
| 64 |
return( |
|
| 65 |
"Length of `subject` must be equal to the number of columns in `quantities`" |
|
| 66 |
) |
|
| 67 |
} |
|
| 68 |
if (length(object@parameter) != ncol(object@quantities)) {
|
|
| 69 |
return( |
|
| 70 |
"Length of `parameter` must be equal to the number of columns in `quantities`" |
|
| 71 |
) |
|
| 72 |
} |
|
| 73 |
TRUE |
|
| 74 |
} |
|
| 75 |
) |
|
| 76 | ||
| 77 | ||
| 78 |
#' `RandomEffectQuantities` -> Printable `Character` |
|
| 79 |
#' |
|
| 80 |
#' Converts [`RandomEffectQuantities`] object into a printable string. |
|
| 81 |
#' @inheritParams RandomEffectQuantities-Shared |
|
| 82 |
#' @typed indent: numeric |
|
| 83 |
#' the number of spaces to indent the string by. |
|
| 84 |
#' @family RandomEffectQuantities |
|
| 85 |
#' @keywords internal |
|
| 86 |
#' @export |
|
| 87 |
#' |
|
| 88 |
#' @returns A character vector suitable for printing. |
|
| 89 |
as_print_string.RandomEffectQuantities <- function(object, indent = 1, ...) {
|
|
| 90 | ! |
parameter_string <- paste0(" ", unique(object@parameter))
|
| 91 | ! |
template <- c( |
| 92 | ! |
"RandomEffectQuantities Object:", |
| 93 | ! |
" # of samples = %d", |
| 94 | ! |
" # of unique subjects = %d", |
| 95 | ! |
" For parameters:", |
| 96 | ! |
parameter_string |
| 97 |
) |
|
| 98 | ! |
pad <- rep(" ", indent) |> paste(collapse = "")
|
| 99 | ! |
template_padded <- paste(pad, template) |
| 100 | ! |
sprintf( |
| 101 | ! |
paste(template_padded, collapse = "\n"), |
| 102 | ! |
nrow(object@quantities), |
| 103 | ! |
length(unique(object@subject)) |
| 104 |
) |
|
| 105 |
} |
|
| 106 | ||
| 107 | ||
| 108 |
#' @rdname show-object |
|
| 109 |
#' @export |
|
| 110 |
setMethod( |
|
| 111 |
f = "show", |
|
| 112 |
signature = "RandomEffectQuantities", |
|
| 113 |
definition = function(object) {
|
|
| 114 | ! |
string <- as_print_string(object) |
| 115 | ! |
cat("\n", string, "\n\n")
|
| 116 |
} |
|
| 117 |
) |
|
| 118 | ||
| 119 | ||
| 120 |
#' `RandomEffectQuantities` -> `data.frame` |
|
| 121 |
#' |
|
| 122 |
#' Returns a `data.frame` of the subject-level random effect parameter samples. |
|
| 123 |
#' |
|
| 124 |
#' @inheritParams RandomEffectQuantities-Shared |
|
| 125 |
#' |
|
| 126 |
#' @keywords internal |
|
| 127 |
#' @family RandomEffectQuantities |
|
| 128 |
#' @export |
|
| 129 |
#' |
|
| 130 |
#' @returns A `data.frame` representation of the object. |
|
| 131 |
as.data.frame.RandomEffectQuantities <- function(x, ...) {
|
|
| 132 | 1x |
data.frame( |
| 133 | 1x |
subject = rep(x@subject, each = nrow(x@quantities)), |
| 134 | 1x |
parameter = rep(x@parameter, each = nrow(x@quantities)), |
| 135 | 1x |
values = as.vector(x@quantities) |
| 136 |
) |
|
| 137 |
} |
|
| 138 | ||
| 139 | ||
| 140 |
#' summary |
|
| 141 |
#' |
|
| 142 |
#' @description |
|
| 143 |
#' This method returns a summary statistic `data.frame` of the random effect parameters |
|
| 144 |
#' |
|
| 145 |
#' @inheritParams RandomEffectQuantities-Shared |
|
| 146 |
#' |
|
| 147 |
#' @returns |
|
| 148 |
#' A `data.frame` with the following variables: |
|
| 149 |
#' - `subject` (`character`) \cr the subject identifier. |
|
| 150 |
#' - `parameter` (`character`) \cr the parameter identifier. |
|
| 151 |
#' - `median` (`numeric`) \cr the median value of the quantity. |
|
| 152 |
#' - `lower` (`numeric`) \cr the lower CI value of the quantity. |
|
| 153 |
#' - `upper` (`numeric`) \cr the upper CI value of the quantity. |
|
| 154 |
#' |
|
| 155 |
#' @keywords internal |
|
| 156 |
#' @family RandomEffectQuantities |
|
| 157 |
#' @export |
|
| 158 |
summary.RandomEffectQuantities <- function(object, conf.level = 0.95, ...) {
|
|
| 159 | 1x |
quantities_summarised <- samples_median_ci( |
| 160 | 1x |
object@quantities, |
| 161 | 1x |
level = conf.level |
| 162 |
) |
|
| 163 | ||
| 164 | 1x |
quantities_summarised$subject <- object@subject |
| 165 | 1x |
quantities_summarised$parameter <- object@parameter |
| 166 | 1x |
quantities_summarised[, c( |
| 167 | 1x |
"subject", |
| 168 | 1x |
"parameter", |
| 169 | 1x |
"median", |
| 170 | 1x |
"lower", |
| 171 | 1x |
"upper" |
| 172 |
)] |
|
| 173 |
} |
|
| 174 | ||
| 175 | ||
| 176 |
#' Extract Random Effects Samples from a Longitudinal Model |
|
| 177 |
#' |
|
| 178 |
#' Helper function to extract subject-level random effects samples from the longitudinal |
|
| 179 |
#' sub-model of a joint model samples object. |
|
| 180 |
#' |
|
| 181 |
#' @typed object: JointModelSamples |
|
| 182 |
#' samples as drawn from a Joint Model. |
|
| 183 |
#' @family RandomEffectQuantities |
|
| 184 |
#' @family JointModelSamples |
|
| 185 |
#' @export |
|
| 186 |
#' |
|
| 187 |
#' @returns A `RandomEffectQuantities` object. |
|
| 188 |
#' |
|
| 189 |
#' @examples |
|
| 190 |
#' \dontrun{
|
|
| 191 |
#' LongitudinalRandomEffects(fit) |
|
| 192 |
#' } |
|
| 193 |
LongitudinalRandomEffects <- function(object) {
|
|
| 194 | 2x |
assert_class(object, "JointModelSamples") |
| 195 | ||
| 196 | 2x |
subject_indexes <- as.list(object@data)$subject_to_index |
| 197 | 2x |
random_effects_names <- getRandomEffectsNames(object@model) |
| 198 | 2x |
expanded <- expand.grid( |
| 199 | 2x |
parameter_long = random_effects_names, |
| 200 | 2x |
subject_indexes = subject_indexes |
| 201 |
) |
|
| 202 | 2x |
expanded$subject <- names(subject_indexes)[expanded$subject_indexes] |
| 203 | 2x |
expanded$parameter <- names(random_effects_names)[expanded$parameter_long] |
| 204 | 2x |
stan_parameter_names <- sprintf( |
| 205 | 2x |
"%s[%i]", |
| 206 | 2x |
expanded$parameter_long, |
| 207 | 2x |
expanded$subject_indexes |
| 208 |
) |
|
| 209 | 2x |
draw_matrix <- posterior::as_draws_matrix(object@results$draws( |
| 210 | 2x |
stan_parameter_names |
| 211 |
)) |
|
| 212 | 2x |
class(draw_matrix) <- "matrix" |
| 213 | 2x |
rownames(draw_matrix) <- NULL |
| 214 | 2x |
colnames(draw_matrix) <- NULL |
| 215 | ||
| 216 | 2x |
RandomEffectQuantities( |
| 217 | 2x |
draw_matrix, |
| 218 | 2x |
subject = expanded$subject, |
| 219 | 2x |
parameter = expanded$parameter |
| 220 |
) |
|
| 221 |
} |
| 1 |
#' @include DataSurvival.R |
|
| 2 |
#' @include DataLongitudinal.R |
|
| 3 |
#' @include DataSubject.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 | ||
| 7 |
#' Re-used documentation for `DataJoint` |
|
| 8 |
#' |
|
| 9 |
#' @typed object: DataJoint |
|
| 10 |
#' Survival and Longitudinal Data. |
|
| 11 |
#' @typed x: DataJoint |
|
| 12 |
#' Survival and Longitudinal Data. |
|
| 13 |
#' @param ... Not Used. |
|
| 14 |
#' |
|
| 15 |
#' @name DataJoint-Shared |
|
| 16 |
#' @keywords internal |
|
| 17 |
NULL |
|
| 18 | ||
| 19 |
setClassUnion("DataLongitudinal_or_NULL", c("DataLongitudinal", "NULL"))
|
|
| 20 |
setClassUnion("DataSurvival_or_NULL", c("DataSurvival", "NULL"))
|
|
| 21 | ||
| 22 | ||
| 23 |
# DataJoint-class ---- |
|
| 24 | ||
| 25 |
#' @title |
|
| 26 |
#' Joint Data Object and Constructor Function |
|
| 27 |
#' |
|
| 28 |
#' @description |
|
| 29 |
#' The `DataJoint` class handles combining data from a [`DataSurvival`] object and a |
|
| 30 |
#' [`DataLongitudinal`] object. |
|
| 31 |
#' |
|
| 32 |
#' @slot subject (`DataSubject`)\cr See Argument for details. |
|
| 33 |
#' @slot survival (`DataSurvival`)\cr See Argument for details. |
|
| 34 |
#' @slot longitudinal (`DataLongitudinal`)\cr See Argument for details. |
|
| 35 |
#' |
|
| 36 |
#' @family DataObjects |
|
| 37 |
#' @family DataJoint |
|
| 38 |
#' @export DataJoint |
|
| 39 |
#' @exportClass DataJoint |
|
| 40 |
.DataJoint <- setClass( |
|
| 41 |
Class = "DataJoint", |
|
| 42 |
representation = list( |
|
| 43 |
subject = "DataSubject", |
|
| 44 |
survival = "DataSurvival_or_NULL", |
|
| 45 |
longitudinal = "DataLongitudinal_or_NULL" |
|
| 46 |
) |
|
| 47 |
) |
|
| 48 | ||
| 49 |
#' @typed subject: DataSubject |
|
| 50 |
#' object created by [DataSubject()]. |
|
| 51 |
#' @typed survival: DataSurvival |
|
| 52 |
#' object created by [DataSurvival()]. |
|
| 53 |
#' @typed longitudinal: DataLongitudinal |
|
| 54 |
#' object created by [DataLongitudinal()]. |
|
| 55 |
#' @rdname DataJoint-class |
|
| 56 |
#' |
|
| 57 |
#' @returns A `DataJoint` object. |
|
| 58 |
#' |
|
| 59 |
#' @examples |
|
| 60 |
#' subjects <- data.frame(id = c("1", "2"), arm = "A", study = "S")
|
|
| 61 |
#' survival <- data.frame(id = c("1", "2"), time = c(5, 8), event = c(1, 0))
|
|
| 62 |
#' longitudinal <- data.frame( |
|
| 63 |
#' id = rep(c("1", "2"), each = 2),
|
|
| 64 |
#' time = rep(c(0, 1), 2), |
|
| 65 |
#' response = c(10, 9, 12, 11) |
|
| 66 |
#' ) |
|
| 67 |
#' DataJoint( |
|
| 68 |
#' DataSubject(subjects, "id", "arm", "study"), |
|
| 69 |
#' DataSurvival(survival, Surv(time, event) ~ 1), |
|
| 70 |
#' DataLongitudinal(longitudinal, response ~ time) |
|
| 71 |
#' ) |
|
| 72 |
DataJoint <- function(subject, survival = NULL, longitudinal = NULL) {
|
|
| 73 | 66x |
subject_suited <- harmonise(subject) |
| 74 | 65x |
vars <- extractVariableNames(subject) |
| 75 | 65x |
subject_var <- vars$subject |
| 76 | 65x |
subject_ord <- levels(as.data.frame(subject_suited)[[vars$subject]]) |
| 77 | ||
| 78 | 65x |
survival_suited <- harmonise( |
| 79 | 65x |
survival, |
| 80 | 65x |
subject_var = subject_var, |
| 81 | 65x |
subject_ord = subject_ord |
| 82 |
) |
|
| 83 | ||
| 84 | 62x |
longitudinal_suited <- harmonise( |
| 85 | 62x |
longitudinal, |
| 86 | 62x |
subject_var = subject_var, |
| 87 | 62x |
subject_ord = subject_ord |
| 88 |
) |
|
| 89 | ||
| 90 | 59x |
.DataJoint( |
| 91 | 59x |
subject = subject_suited, |
| 92 | 59x |
survival = survival_suited, |
| 93 | 59x |
longitudinal = longitudinal_suited |
| 94 |
) |
|
| 95 |
} |
|
| 96 | ||
| 97 | ||
| 98 |
setValidity( |
|
| 99 |
Class = "DataJoint", |
|
| 100 |
method = function(object) {
|
|
| 101 |
vars <- extractVariableNames(object@subject) |
|
| 102 |
subject_var <- vars$subject |
|
| 103 |
subject_ord <- as.character(as.data.frame(object@subject)[[ |
|
| 104 |
vars$subject |
|
| 105 |
]]) |
|
| 106 |
if (!is.null(object@survival)) {
|
|
| 107 |
survival_df <- as.data.frame(object@survival) |
|
| 108 |
if (!subject_var %in% names(survival_df)) {
|
|
| 109 |
return(sprintf( |
|
| 110 |
"Unable to find `%s` in `survival`", |
|
| 111 |
sujbect_var |
|
| 112 |
)) |
|
| 113 |
} |
|
| 114 |
if (!all(survival_df[[subject_var]] %in% subject_ord)) {
|
|
| 115 |
return( |
|
| 116 |
"There are subjects in `survival` that are not in `subject`" |
|
| 117 |
) |
|
| 118 |
} |
|
| 119 |
if ( |
|
| 120 |
!nrow(survival_df) == length(unique(survival_df[[subject_var]])) |
|
| 121 |
) {
|
|
| 122 |
return("There are duplicate subjects in `survival`")
|
|
| 123 |
} |
|
| 124 |
} |
|
| 125 |
if (!is.null(object@longitudinal)) {
|
|
| 126 |
long_df <- as.data.frame(object@longitudinal) |
|
| 127 |
if (!subject_var %in% names(long_df)) {
|
|
| 128 |
return(sprintf( |
|
| 129 |
"Unable to find `%s` in `longitudinal`", |
|
| 130 |
sujbect_var |
|
| 131 |
)) |
|
| 132 |
} |
|
| 133 |
if (!all(long_df[[subject_var]] %in% subject_ord)) {
|
|
| 134 |
return( |
|
| 135 |
"There are subjects in `longitudinal` that are not in `subject`" |
|
| 136 |
) |
|
| 137 |
} |
|
| 138 |
} |
|
| 139 |
subject_df <- as.data.frame(object@subject) |
|
| 140 |
if (!subject_var %in% names(subject_df)) {
|
|
| 141 |
return(sprintf("Unable to find `%s` in `subject`", sujbect_var))
|
|
| 142 |
} |
|
| 143 |
if (!nrow(subject_df) == length(unique(subject_df[[subject_var]]))) {
|
|
| 144 |
return("There are duplicate subjects in `subject`")
|
|
| 145 |
} |
|
| 146 |
return(TRUE) |
|
| 147 |
} |
|
| 148 |
) |
|
| 149 | ||
| 150 | ||
| 151 |
# DataJoint-as.list ---- |
|
| 152 | ||
| 153 |
#' Data Object -> `list` |
|
| 154 |
#' |
|
| 155 |
#' @typed object: "`DataSubject` or `DataLongitudinal` or `DataSurvival`" |
|
| 156 |
#' data object to convert to a `list`. |
|
| 157 |
#' @typed x: "`DataSubject` or `DataLongitudinal` or `DataSurvival`" |
|
| 158 |
#' data object to convert to a `list`. |
|
| 159 |
#' @typed subject_var: character |
|
| 160 |
#' the name of the variable |
|
| 161 |
#' containing the subject identifier. |
|
| 162 |
#' @param model (`JointModel` or `NULL`) Optional model specification used to |
|
| 163 |
#' add model-specific data components. |
|
| 164 |
#' @param ... not used. |
|
| 165 |
#' |
|
| 166 |
#' @description |
|
| 167 |
#' Coerces a data object into a `list` of data components required |
|
| 168 |
#' for fitting a [`JointModel`]. See the "Extending jmpost" vignette for more details. |
|
| 169 |
#' |
|
| 170 |
#' @name as_stan_list.DataObject |
|
| 171 |
#' @family as_stan_list |
|
| 172 |
#' @family DataJoint |
|
| 173 |
#' @export |
|
| 174 |
#' |
|
| 175 |
#' @returns A named `list` suitable for use as Stan data. |
|
| 176 |
as_stan_list.DataJoint <- function(object, model = NULL, ...) {
|
|
| 177 | 345x |
vars <- extractVariableNames(object@subject) |
| 178 | 345x |
subject_var <- vars$subject |
| 179 | 345x |
subject_data <- as_stan_list(object@subject) |
| 180 | ||
| 181 | 345x |
if (!is.null(model)) {
|
| 182 | 58x |
assert_class(model, "JointModel") |
| 183 | 58x |
if (!is.null(model@longitudinal)) {
|
| 184 | 53x |
subject_data <- append( |
| 185 | 53x |
subject_data, |
| 186 | 53x |
longitudinal_model_stan_data( |
| 187 | 53x |
model = model@longitudinal, |
| 188 | 53x |
subject = object@subject |
| 189 |
) |
|
| 190 |
) |
|
| 191 |
} |
|
| 192 |
} |
|
| 193 | ||
| 194 | 345x |
subject_data |> |
| 195 | 345x |
append(as_stan_list(object@survival)) |> |
| 196 | 345x |
append(as_stan_list( |
| 197 | 345x |
object@longitudinal, |
| 198 | 345x |
subject_var = subject_var |
| 199 |
)) |
|
| 200 |
} |
|
| 201 | ||
| 202 |
#' @rdname as_stan_list.DataObject |
|
| 203 |
#' @export |
|
| 204 |
as.list.DataJoint <- function(x, ...) {
|
|
| 205 | 283x |
as_stan_list(x, ...) |
| 206 |
} |
|
| 207 | ||
| 208 | ||
| 209 |
#' Subsetting `DataJoint` as a `data.frame` |
|
| 210 |
#' |
|
| 211 |
#' @typed x: DataJoint |
|
| 212 |
#' object created by [DataJoint()]. |
|
| 213 |
#' @typed subjects: "`character` or `list`" |
|
| 214 |
#' subjects that you wish to subset the `data.frame` |
|
| 215 |
#' to contain. See details. |
|
| 216 |
#' @param ... Not used. |
|
| 217 |
#' |
|
| 218 |
#' @description |
|
| 219 |
#' |
|
| 220 |
#' Coerces the object into a `data.frame` containing just event times and status |
|
| 221 |
#' filtering for specific subjects If `subjects` is a list then an additional variable `group` will be added |
|
| 222 |
#' onto the dataset specifying which group the row belongs to. |
|
| 223 |
#' |
|
| 224 |
#' @examples |
|
| 225 |
#' \dontrun{
|
|
| 226 |
#' subjects <- c("SUB1", "SUB2", "SUB3", "SUB4")
|
|
| 227 |
#' subset(x, subjects) |
|
| 228 |
#' |
|
| 229 |
#' groups <- list( |
|
| 230 |
#' "g1" = c("SUB1", "SUB3", "SUB4"),
|
|
| 231 |
#' "g2" = c("SUB2", "SUB3")
|
|
| 232 |
#' ) |
|
| 233 |
#' subset(x, groups) |
|
| 234 |
#' } |
|
| 235 |
#' @family DataJoint |
|
| 236 |
#' @export |
|
| 237 |
#' |
|
| 238 |
#' @returns A `DataJoint` object restricted to the requested subjects. |
|
| 239 |
subset.DataJoint <- function(x, subjects, ...) {
|
|
| 240 | 2x |
data <- as.list(x) |
| 241 | 2x |
dat <- data.frame( |
| 242 | 2x |
time = data[["event_times"]], |
| 243 | 2x |
event = as.numeric( |
| 244 | 2x |
seq_along(data[["event_times"]]) %in% data[["subject_event_index"]] |
| 245 |
), |
|
| 246 | 2x |
subject = names(data[["subject_to_index"]]) |
| 247 |
) |
|
| 248 | 2x |
subset_and_add_grouping(dat, subjects) |
| 249 |
} |
|
| 250 | ||
| 251 | ||
| 252 |
#' `subset_and_add_grouping` |
|
| 253 |
#' |
|
| 254 |
#' @typed dat: data.frame |
|
| 255 |
#' must have a column called `subject` which corresponds to the |
|
| 256 |
#' values passed to `groupings`. |
|
| 257 |
#' @typed groupings: "`character` or `list`" |
|
| 258 |
#' subjects that you wish to subset the dataset |
|
| 259 |
#' to contain. If `groupings` is a list then an additional variable `group` will be added |
|
| 260 |
#' onto the dataset specifying which group the row belongs to. |
|
| 261 |
#' |
|
| 262 |
#' @details |
|
| 263 |
#' Example of usage |
|
| 264 |
#' ``` |
|
| 265 |
#' subjects <- c("SUB1", "SUB2", "SUB3", "SUB4")
|
|
| 266 |
#' subset_and_add_grouping(dat, subjects) |
|
| 267 |
#' |
|
| 268 |
#' groups <- list( |
|
| 269 |
#' "g1" = c("SUB1", "SUB3", "SUB4"),
|
|
| 270 |
#' "g2" = c("SUB2", "SUB3")
|
|
| 271 |
#' ) |
|
| 272 |
#' subset_and_add_grouping(dat, groups) |
|
| 273 |
#' ``` |
|
| 274 |
#' |
|
| 275 |
#' @keywords internal |
|
| 276 |
#' |
|
| 277 |
#' @returns A subsetted `data.frame` with a grouping column. |
|
| 278 |
subset_and_add_grouping <- function(dat, groupings) {
|
|
| 279 | 8x |
groupings <- decompose_subjects(groupings, dat$subject)$groups |
| 280 | 5x |
dat_subset_list <- lapply( |
| 281 | 5x |
seq_along(groupings), |
| 282 | 5x |
\(i) {
|
| 283 | 13x |
dat_reduced <- dat[dat$subject %in% groupings[[i]], , drop = FALSE] |
| 284 | 13x |
dat_reduced[["group"]] <- names(groupings)[[i]] |
| 285 | 13x |
dat_reduced |
| 286 |
} |
|
| 287 |
) |
|
| 288 | 5x |
x <- Reduce(rbind, dat_subset_list) |
| 289 | 5x |
row.names(x) <- NULL |
| 290 | 5x |
x |
| 291 |
} |
|
| 292 | ||
| 293 | ||
| 294 |
#' Extract Observed Longitudinal Values |
|
| 295 |
#' |
|
| 296 |
#' Utility function to extract the observed longitudinal values from a [`DataJoint`] object |
|
| 297 |
#' @typed object: DataJoint |
|
| 298 |
#' data used to fit a [`JointModel`]. |
|
| 299 |
#' @return A data.frame with the following columns |
|
| 300 |
#' - `subject` (`character`)\cr The subject identifier |
|
| 301 |
#' - `time` (`numeric`)\cr The time at which the observation occurred |
|
| 302 |
#' - `Yob` (`numeric`)\cr The observed value |
|
| 303 |
#' @keywords internal |
|
| 304 |
extract_observed_values <- function(object) {
|
|
| 305 | 2x |
assert_class(object, "DataJoint") |
| 306 | 2x |
data <- as.list(object) |
| 307 | 2x |
x <- data.frame( |
| 308 | 2x |
subject = names(data$subject_to_index)[data$subject_tumour_index], |
| 309 | 2x |
time = data$tumour_time, |
| 310 | 2x |
Yob = data$tumour_value |
| 311 |
) |
|
| 312 | 2x |
row.names(x) <- NULL |
| 313 | 2x |
x |
| 314 |
} |
|
| 315 | ||
| 316 |
#' @rdname show-object |
|
| 317 |
#' @export |
|
| 318 |
#' |
|
| 319 |
#' @returns Invisibly returns `NULL` after printing the object. |
|
| 320 |
setMethod( |
|
| 321 |
f = "show", |
|
| 322 |
signature = "DataJoint", |
|
| 323 |
definition = function(object) {
|
|
| 324 | 3x |
string_survival <- if (is.null(object@survival)) {
|
| 325 | 1x |
" Survival-Data Object:\n Not Specified" |
| 326 |
} else {
|
|
| 327 | 2x |
as_print_string(object@survival, indent = 5) |
| 328 |
} |
|
| 329 | ||
| 330 | 3x |
string_longitudinal <- if (is.null(object@longitudinal)) {
|
| 331 | 1x |
" Longitudinal-Data Object:\n Not Specified" |
| 332 |
} else {
|
|
| 333 | 2x |
as_print_string(object@longitudinal, indent = 5) |
| 334 |
} |
|
| 335 | ||
| 336 | 3x |
template <- c( |
| 337 | 3x |
"Joint-Data Object Containing:", |
| 338 |
"", |
|
| 339 | 3x |
as_print_string(object@subject, indent = 5), |
| 340 |
"", |
|
| 341 | 3x |
string_survival, |
| 342 |
"", |
|
| 343 | 3x |
string_longitudinal |
| 344 |
) |
|
| 345 | 3x |
cat("\n", paste(template, collapse = "\n"), "\n\n")
|
| 346 |
} |
|
| 347 |
) |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include Parameter.R |
|
| 3 |
#' @include Prior.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 | ||
| 7 |
#' ParameterList-Shared |
|
| 8 |
#' @typed object: ParameterList |
|
| 9 |
#' A List of [`Parameter`] Objects. |
|
| 10 |
#' @typed x: ParameterList |
|
| 11 |
#' A List of [`Parameter`] Objects. |
|
| 12 |
#' @param ... Not Used. |
|
| 13 |
#' @keywords internal |
|
| 14 |
#' @name ParameterList-Shared |
|
| 15 |
NULL |
|
| 16 | ||
| 17 | ||
| 18 |
# ParameterList-class ---- |
|
| 19 | ||
| 20 |
#' `ParameterList` |
|
| 21 |
#' |
|
| 22 |
#' This class extends the general [`list`] type for containing [`Parameter`] |
|
| 23 |
#' specifications. When converted to a [`StanModule`], the list supplies the |
|
| 24 |
#' Stan declarations and prior statements for those parameters. |
|
| 25 |
#' |
|
| 26 |
#' |
|
| 27 | ||
| 28 |
#' @slot parameters (`list`) \cr a list of [`Parameter`] objects |
|
| 29 |
#' @family ParameterList |
|
| 30 |
#' @export ParameterList |
|
| 31 |
#' @exportClass ParameterList |
|
| 32 |
.ParameterList <- setClass( |
|
| 33 |
Class = "ParameterList", |
|
| 34 |
slots = c( |
|
| 35 |
parameters = "list" |
|
| 36 |
) |
|
| 37 |
) |
|
| 38 |
#' @typed ...: Parameter |
|
| 39 |
#' which parameter specifications to include. |
|
| 40 |
#' @rdname ParameterList-class |
|
| 41 |
#' |
|
| 42 |
#' @returns A `ParameterList` object. |
|
| 43 |
#' |
|
| 44 |
#' @examples |
|
| 45 |
#' ParameterList( |
|
| 46 |
#' Parameter(prior_normal(0, 1), "intercept"), |
|
| 47 |
#' Parameter(prior_normal(0, 2), "slope") |
|
| 48 |
#' ) |
|
| 49 |
ParameterList <- function(...) {
|
|
| 50 | 488x |
.ParameterList(parameters = list(...)) |
| 51 |
} |
|
| 52 | ||
| 53 |
# ParameterList-validity ---- |
|
| 54 | ||
| 55 |
setValidity( |
|
| 56 |
Class = "ParameterList", |
|
| 57 |
method = function(object) {
|
|
| 58 |
is_parameters <- vapply( |
|
| 59 |
object@parameters, |
|
| 60 |
function(x) is(x, "Parameter"), |
|
| 61 |
logical(1) |
|
| 62 |
) |
|
| 63 |
if (!all(is_parameters)) {
|
|
| 64 |
return("all elements must be of class 'Parameter'")
|
|
| 65 |
} |
|
| 66 |
return(TRUE) |
|
| 67 |
} |
|
| 68 |
) |
|
| 69 | ||
| 70 | ||
| 71 |
# as.StanModule-ParameterList ---- |
|
| 72 | ||
| 73 |
#' `ParameterList` -> `StanModule` |
|
| 74 |
#' |
|
| 75 |
#' Converts a [`ParameterList`] object to a [`StanModule`] object |
|
| 76 |
#' |
|
| 77 |
#' @inheritParams ParameterList-Shared |
|
| 78 |
#' |
|
| 79 |
#' @family ParameterList |
|
| 80 |
#' @family as.StanModule |
|
| 81 |
#' @export |
|
| 82 |
#' |
|
| 83 |
#' @returns A `StanModule` object. |
|
| 84 |
as.StanModule.ParameterList <- function(object, ...) {
|
|
| 85 | 145x |
stan_modules <- lapply( |
| 86 | 145x |
object@parameters, |
| 87 | 145x |
as.StanModule |
| 88 |
) |
|
| 89 | 145x |
assert_that( |
| 90 | 145x |
all(vapply(stan_modules, inherits, logical(1), "StanModule")) |
| 91 |
) |
|
| 92 | 145x |
Reduce(merge, stan_modules) |
| 93 |
} |
|
| 94 | ||
| 95 | ||
| 96 |
#' `ParameterList` -> `list` |
|
| 97 |
#' |
|
| 98 |
#' Converts a ParameterList object to a list of parameter data values |
|
| 99 |
#' for a Stan model. |
|
| 100 |
#' |
|
| 101 |
#' @inheritParams ParameterList-Shared |
|
| 102 |
#' |
|
| 103 |
#' @family as_stan_list |
|
| 104 |
#' @family ParameterList |
|
| 105 |
#' @export |
|
| 106 |
#' |
|
| 107 |
#' @returns A named `list` suitable for use as Stan data. |
|
| 108 |
as_stan_list.ParameterList <- function(object, ...) {
|
|
| 109 | 64x |
stan_lists <- lapply( |
| 110 | 64x |
object@parameters, |
| 111 | 64x |
as_stan_list |
| 112 |
) |
|
| 113 | 64x |
assert_that( |
| 114 | 64x |
all(vapply(stan_lists, is.list, logical(1))) |
| 115 |
) |
|
| 116 | 64x |
Reduce(append, stan_lists) |
| 117 |
} |
|
| 118 | ||
| 119 | ||
| 120 |
# merge-ParameterList,ParameterList ---- |
|
| 121 | ||
| 122 |
#' @rdname merge |
|
| 123 |
setMethod( |
|
| 124 |
f = "merge", |
|
| 125 |
signature = c(x = "ParameterList", y = "ParameterList"), |
|
| 126 |
definition = function(x, y) {
|
|
| 127 | 98x |
parameters <- append(x@parameters, y@parameters) |
| 128 | 98x |
do.call(ParameterList, parameters) |
| 129 |
} |
|
| 130 |
) |
|
| 131 | ||
| 132 |
# as.list-ParameterList ---- |
|
| 133 | ||
| 134 |
#' `ParameterList` -> `list` |
|
| 135 |
#' @description |
|
| 136 |
#' Returns a named list where each element of the list corresponds |
|
| 137 |
#' to a Stan modelling block e.g. `data`, `model`, etc. |
|
| 138 |
#' @inheritParams ParameterList-Shared |
|
| 139 |
#' @family ParameterList |
|
| 140 |
#' @export |
|
| 141 |
#' |
|
| 142 |
#' @returns A named `list` representation of the object. |
|
| 143 |
as.list.ParameterList <- function(x, ...) {
|
|
| 144 | 143x |
as.list(as.StanModule(x)) |
| 145 |
} |
|
| 146 | ||
| 147 | ||
| 148 |
#' Parameter-List Getter Functions |
|
| 149 |
#' @description |
|
| 150 |
#' Getter functions for the slots of a [`ParameterList`] object |
|
| 151 |
#' @inheritParams ParameterList-Shared |
|
| 152 |
#' @family ParameterList |
|
| 153 |
#' @typed n_chains: integer |
|
| 154 |
#' the number of chains. |
|
| 155 |
#' @name ParameterList-Getter-Methods |
|
| 156 |
NULL |
|
| 157 | ||
| 158 | ||
| 159 |
#' @describeIn ParameterList-Getter-Methods The parameter-list's parameter names |
|
| 160 |
#' @export |
|
| 161 |
#' |
|
| 162 |
#' @returns The requested parameter-list property: names, initial values, or total size. |
|
| 163 |
names.ParameterList <- function(x) {
|
|
| 164 | 163x |
vapply(x@parameters, names, character(1)) |
| 165 |
} |
|
| 166 | ||
| 167 | ||
| 168 |
#' @describeIn ParameterList-Getter-Methods The parameter-list's parameter initial values |
|
| 169 |
#' @export |
|
| 170 |
initialValues.ParameterList <- function(object, n_chains, ...) {
|
|
| 171 | 58x |
parameters <- Filter(\(x) !x@prior@.is_const, object@parameters) |
| 172 |
# Generate initial values as a list of lists. This is to ensure it is in the required |
|
| 173 |
# format as specified by cmdstanr see the `init` argument of |
|
| 174 |
# `help("model-method-sample", "cmdstanr")` for more details
|
|
| 175 | 58x |
lapply( |
| 176 | 58x |
seq_len(n_chains), |
| 177 | 58x |
\(i) {
|
| 178 | 974x |
vals <- lapply(parameters, function(parameter) {
|
| 179 | 10078x |
c( |
| 180 | 10078x |
stats::setNames( |
| 181 | 10078x |
list(initialValues(parameter)), |
| 182 | 10078x |
names(parameter) |
| 183 |
), |
|
| 184 | 10078x |
auxiliaryInitialValues( |
| 185 | 10078x |
parameter@prior, |
| 186 | 10078x |
name = names(parameter), |
| 187 | 10078x |
size = size(parameter) |
| 188 |
) |
|
| 189 |
) |
|
| 190 |
}) |
|
| 191 | 959x |
unlist(vals, recursive = FALSE) |
| 192 |
} |
|
| 193 |
) |
|
| 194 |
} |
|
| 195 | ||
| 196 | ||
| 197 |
#' @describeIn ParameterList-Getter-Methods The parameter-list's parameter dimensionality |
|
| 198 |
#' @export |
|
| 199 |
size.ParameterList <- function(object) {
|
|
| 200 | 20x |
parameters <- Filter(\(x) !x@prior@.is_const, object@parameters) |
| 201 | 20x |
sizes <- lapply(parameters, function(parameter) {
|
| 202 | 136x |
c( |
| 203 | 136x |
stats::setNames(list(size(parameter)), names(parameter)), |
| 204 | 136x |
auxiliarySize( |
| 205 | 136x |
parameter@prior, |
| 206 | 136x |
name = names(parameter), |
| 207 | 136x |
size = size(parameter) |
| 208 |
) |
|
| 209 |
) |
|
| 210 |
}) |
|
| 211 | 20x |
unlist(sizes, recursive = FALSE) |
| 212 |
} |
|
| 213 | ||
| 214 | ||
| 215 |
#' `ParameterList` -> Printable `Character` |
|
| 216 |
#' |
|
| 217 |
#' Converts [`ParameterList`] object into a printable string. |
|
| 218 |
#' @inheritParams ParameterList-Shared |
|
| 219 |
#' @family ParameterList |
|
| 220 |
#' @keywords internal |
|
| 221 |
#' @export |
|
| 222 |
#' |
|
| 223 |
#' @returns A character vector suitable for printing. |
|
| 224 |
as_print_string.ParameterList <- function(object, ...) {
|
|
| 225 | 42x |
x <- vapply(object@parameters, as.character, character(1)) |
| 226 | 42x |
if (length(x) == 0) {
|
| 227 | ! |
x <- "<No Parameters>" |
| 228 |
} |
|
| 229 | 42x |
return(x) |
| 230 |
} |
|
| 231 | ||
| 232 | ||
| 233 |
#' @rdname show-object |
|
| 234 |
#' @export |
|
| 235 |
setMethod( |
|
| 236 |
f = "show", |
|
| 237 |
signature = "ParameterList", |
|
| 238 |
definition = function(object) {
|
|
| 239 | 1x |
chrs <- as_print_string(object) |
| 240 | 1x |
string <- paste(" ", chrs) |> paste(collapse = "\n")
|
| 241 | 1x |
x <- sprintf("\nParameterList Object:\n%s\n\n", string)
|
| 242 | 1x |
cat(x) |
| 243 | 1x |
return(object) |
| 244 |
} |
|
| 245 |
) |
| 1 |
#' @include Grid.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' @rdname Grid-Dev |
|
| 7 |
.GridEven <- setClass( |
|
| 8 |
"GridEven", |
|
| 9 |
contains = "Grid", |
|
| 10 |
slots = c( |
|
| 11 |
"subjects" = "character", |
|
| 12 |
"length.out" = "numeric" |
|
| 13 |
) |
|
| 14 |
) |
|
| 15 | ||
| 16 | ||
| 17 |
#' @rdname Grid-Functions |
|
| 18 |
#' @export |
|
| 19 |
#' |
|
| 20 |
#' @returns A `Grid` object of the requested type. |
|
| 21 |
GridEven <- function(subjects = NULL, length.out = 30) {
|
|
| 22 | 1x |
.GridEven( |
| 23 | 1x |
subjects = subjects, |
| 24 | 1x |
length.out = length.out |
| 25 |
) |
|
| 26 |
} |
|
| 27 | ||
| 28 | ||
| 29 |
setValidity( |
|
| 30 |
"GridEven", |
|
| 31 |
function(object) {
|
|
| 32 |
if (length(object@length.out) != 1 || all(object@length.out <= 0)) {
|
|
| 33 |
return("The `length.out` argument must be a positive integer")
|
|
| 34 |
} |
|
| 35 |
} |
|
| 36 |
) |
|
| 37 | ||
| 38 | ||
| 39 |
#' @rdname Quant-Dev |
|
| 40 |
#' @export |
|
| 41 |
as.QuantityGenerator.GridEven <- function(object, data, ...) {
|
|
| 42 | 2x |
assert_class(data, "DataJoint") |
| 43 | 2x |
data_list <- as.list(data) |
| 44 | 2x |
subjects <- unlist(as.list(object, data = data), use.names = FALSE) |
| 45 | 2x |
assert_that( |
| 46 | 2x |
all(subjects %in% names(data_list$subject_to_index)), |
| 47 | 2x |
msg = "All subject names must be in the `DataSubject` object" |
| 48 |
) |
|
| 49 | ||
| 50 | 2x |
spec <- lapply( |
| 51 | 2x |
subjects, |
| 52 | 2x |
\(sub) {
|
| 53 | 4x |
subject_index <- data_list$subject_to_index[[sub]] |
| 54 | 4x |
time_index <- data_list$subject_tumour_index == subject_index |
| 55 | 4x |
subject_times <- data_list$tumour_time[time_index] |
| 56 | 4x |
seq( |
| 57 | 4x |
min(subject_times), |
| 58 | 4x |
max(subject_times), |
| 59 | 4x |
length.out = object@length.out |
| 60 |
) |
|
| 61 |
} |
|
| 62 |
) |
|
| 63 | 2x |
names(spec) <- subjects |
| 64 | ||
| 65 | 2x |
as.QuantityGenerator( |
| 66 | 2x |
GridManual(spec), |
| 67 | 2x |
data = data |
| 68 |
) |
|
| 69 |
} |
|
| 70 | ||
| 71 | ||
| 72 |
#' @rdname Quant-Dev |
|
| 73 |
#' @export |
|
| 74 |
as.QuantityCollapser.GridEven <- function(object, data, ...) {
|
|
| 75 | 1x |
generator <- as.QuantityGenerator(object, data) |
| 76 | 1x |
QuantityCollapser( |
| 77 | 1x |
times = generator@times, |
| 78 | 1x |
groups = generator@subjects, |
| 79 | 1x |
indexes = as.list(seq_along(generator@times)) |
| 80 |
) |
|
| 81 |
} |
|
| 82 | ||
| 83 | ||
| 84 |
#' @export |
|
| 85 |
#' |
|
| 86 |
#' @returns A named `list` representation of the object. |
|
| 87 |
as.list.GridEven <- function(x, data, ...) {
|
|
| 88 | 2x |
subjects_to_list(x@subjects, data) |
| 89 |
} |
| 1 |
#' @include LongitudinalModel.R |
|
| 2 |
#' @include StanModule.R |
|
| 3 |
#' @include generics.R |
|
| 4 |
#' @include ParameterList.R |
|
| 5 |
#' @include Parameter.R |
|
| 6 |
#' @include Link.R |
|
| 7 |
NULL |
|
| 8 | ||
| 9 | ||
| 10 |
#' `LongitudinalSteinFojo` |
|
| 11 |
#' |
|
| 12 |
#' This class extends the general [`LongitudinalModel`] class for using the |
|
| 13 |
#' Stein-Fojo model for the longitudinal outcome. |
|
| 14 |
#' |
|
| 15 |
#' @section Available Links: |
|
| 16 |
#' - [`linkDSLD()`] |
|
| 17 |
#' - [`linkTTG()`] |
|
| 18 |
#' - [`linkIdentity()`] |
|
| 19 |
#' - [`linkGrowth()`] |
|
| 20 |
#' @exportClass LongitudinalSteinFojo |
|
| 21 |
.LongitudinalSteinFojo <- setClass( |
|
| 22 |
Class = "LongitudinalSteinFojo", |
|
| 23 |
contains = "LongitudinalModel" |
|
| 24 |
) |
|
| 25 | ||
| 26 | ||
| 27 |
#' @rdname LongitudinalSteinFojo-class |
|
| 28 |
#' |
|
| 29 |
#' @typed mu_bsld: Prior |
|
| 30 |
#' for the mean baseline value `mu_bsld`. |
|
| 31 |
#' @typed mu_ks: Prior |
|
| 32 |
#' for the mean shrinkage rate `mu_ks`. |
|
| 33 |
#' @typed mu_kg: Prior |
|
| 34 |
#' for the mean growth rate `mu_kg`. |
|
| 35 |
#' |
|
| 36 |
#' @typed omega_bsld: Prior |
|
| 37 |
#' for the baseline value standard deviation `omega_bsld`. |
|
| 38 |
#' @typed omega_ks: Prior |
|
| 39 |
#' for the shrinkage rate standard deviation `omega_ks`. |
|
| 40 |
#' @typed omega_kg: Prior |
|
| 41 |
#' for the growth rate standard deviation `omega_kg`. |
|
| 42 |
#' |
|
| 43 |
#' @typed sigma: Prior |
|
| 44 |
#' for the variance of the longitudinal values `sigma`. |
|
| 45 |
#' |
|
| 46 |
#' @typed scaled_variance: logical |
|
| 47 |
#' whether the variance should be scaled by the expected value, corresponding to a multiplicative model. |
|
| 48 |
#' As a default, the variance is not scaled by the expected value, corresponding to an additive model. |
|
| 49 |
#' (See the "Statistical Specifications" vignette for more details.) |
|
| 50 |
#' @typed centred: logical |
|
| 51 |
#' whether to use the centred parameterization. |
|
| 52 |
#' |
|
| 53 |
#' @export |
|
| 54 |
#' |
|
| 55 |
#' @returns A `LongitudinalSteinFojo` object. |
|
| 56 |
#' |
|
| 57 |
#' @examples |
|
| 58 |
#' LongitudinalSteinFojo() |
|
| 59 |
LongitudinalSteinFojo <- function( |
|
| 60 |
mu_bsld = prior_normal(log(60), 1), |
|
| 61 |
mu_ks = prior_normal(log(0.5), 1), |
|
| 62 |
mu_kg = prior_normal(log(0.3), 1), |
|
| 63 | ||
| 64 |
omega_bsld = prior_lognormal(log(0.2), 1), |
|
| 65 |
omega_ks = prior_lognormal(log(0.2), 1), |
|
| 66 |
omega_kg = prior_lognormal(log(0.2), 1), |
|
| 67 | ||
| 68 |
sigma = prior_lognormal(log(0.1), 1), |
|
| 69 | ||
| 70 |
scaled_variance = FALSE, |
|
| 71 |
centred = FALSE |
|
| 72 |
) {
|
|
| 73 | 24x |
sf_model <- StanModule(decorated_render( |
| 74 | 24x |
.x = read_stan("lm-stein-fojo/model.stan"),
|
| 75 | 24x |
centred = centred, |
| 76 | 24x |
scaled_variance = scaled_variance |
| 77 |
)) |
|
| 78 | ||
| 79 |
# Apply constriants |
|
| 80 | 24x |
omega_bsld <- set_limits(omega_bsld, lower = getOption("jmpost.double_eps"))
|
| 81 | 24x |
omega_ks <- set_limits(omega_ks, lower = getOption("jmpost.double_eps"))
|
| 82 | 24x |
omega_kg <- set_limits(omega_kg, lower = getOption("jmpost.double_eps"))
|
| 83 | 24x |
sigma <- set_limits(sigma, lower = getOption("jmpost.double_eps"))
|
| 84 | ||
| 85 | 24x |
parameters <- list( |
| 86 | 24x |
Parameter(name = "lm_sf_mu_bsld", prior = mu_bsld, size = "n_studies"), |
| 87 | 24x |
Parameter(name = "lm_sf_mu_ks", prior = mu_ks, size = "n_arms"), |
| 88 | 24x |
Parameter(name = "lm_sf_mu_kg", prior = mu_kg, size = "n_arms"), |
| 89 | ||
| 90 | 24x |
Parameter( |
| 91 | 24x |
name = "lm_sf_omega_bsld", |
| 92 | 24x |
prior = omega_bsld, |
| 93 | 24x |
size = "n_studies" |
| 94 |
), |
|
| 95 | 24x |
Parameter(name = "lm_sf_omega_ks", prior = omega_ks, size = "n_arms"), |
| 96 | 24x |
Parameter(name = "lm_sf_omega_kg", prior = omega_kg, size = "n_arms"), |
| 97 | ||
| 98 | 24x |
Parameter(name = "lm_sf_sigma", prior = sigma, size = 1) |
| 99 |
) |
|
| 100 | ||
| 101 | 24x |
assert_flag(centred) |
| 102 | 24x |
parameters_extra <- if (centred) {
|
| 103 | 3x |
list( |
| 104 | 3x |
Parameter( |
| 105 | 3x |
name = "lm_sf_psi_bsld", |
| 106 | 3x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 107 | 3x |
median(mu_bsld), |
| 108 | 3x |
median(omega_bsld) |
| 109 | 3x |
)), lower = getOption("jmpost.double_eps")),
|
| 110 | 3x |
size = "n_subjects" |
| 111 |
), |
|
| 112 | 3x |
Parameter( |
| 113 | 3x |
name = "lm_sf_psi_ks", |
| 114 | 3x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 115 | 3x |
median(mu_ks), |
| 116 | 3x |
median(omega_ks) |
| 117 | 3x |
)), lower = getOption("jmpost.double_eps")),
|
| 118 | 3x |
size = "n_subjects" |
| 119 |
), |
|
| 120 | 3x |
Parameter( |
| 121 | 3x |
name = "lm_sf_psi_kg", |
| 122 | 3x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 123 | 3x |
median(mu_kg), |
| 124 | 3x |
median(omega_kg) |
| 125 | 3x |
)), lower = getOption("jmpost.double_eps")),
|
| 126 | 3x |
size = "n_subjects" |
| 127 |
) |
|
| 128 |
) |
|
| 129 |
} else {
|
|
| 130 | 21x |
list( |
| 131 | 21x |
Parameter( |
| 132 | 21x |
name = "lm_sf_eta_tilde_bsld", |
| 133 | 21x |
prior = prior_std_normal(), |
| 134 | 21x |
size = "n_subjects" |
| 135 |
), |
|
| 136 | 21x |
Parameter( |
| 137 | 21x |
name = "lm_sf_eta_tilde_ks", |
| 138 | 21x |
prior = prior_std_normal(), |
| 139 | 21x |
size = "n_subjects" |
| 140 |
), |
|
| 141 | 21x |
Parameter( |
| 142 | 21x |
name = "lm_sf_eta_tilde_kg", |
| 143 | 21x |
prior = prior_std_normal(), |
| 144 | 21x |
size = "n_subjects" |
| 145 |
) |
|
| 146 |
) |
|
| 147 |
} |
|
| 148 | 24x |
parameters <- append(parameters, parameters_extra) |
| 149 | ||
| 150 | 24x |
x <- LongitudinalModel( |
| 151 | 24x |
name = "Stein-Fojo", |
| 152 | 24x |
stan = merge( |
| 153 | 24x |
sf_model, |
| 154 | 24x |
StanModule("lm-stein-fojo/functions.stan")
|
| 155 |
), |
|
| 156 | 24x |
parameters = do.call(ParameterList, parameters), |
| 157 | 24x |
scaled_variance = scaled_variance |
| 158 |
) |
|
| 159 | 24x |
.LongitudinalSteinFojo(x) |
| 160 |
} |
|
| 161 | ||
| 162 | ||
| 163 |
#' @export |
|
| 164 |
#' |
|
| 165 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 166 |
enableGQ.LongitudinalSteinFojo <- function(object, ...) {
|
|
| 167 | 2x |
StanModule("lm-stein-fojo/quantities.stan")
|
| 168 |
} |
|
| 169 | ||
| 170 |
#' @export |
|
| 171 |
#' |
|
| 172 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 173 |
enableLink.LongitudinalSteinFojo <- function(object, ...) {
|
|
| 174 | 3x |
object@stan <- merge( |
| 175 | 3x |
object@stan, |
| 176 | 3x |
StanModule("lm-stein-fojo/link.stan")
|
| 177 |
) |
|
| 178 | 3x |
object |
| 179 |
} |
|
| 180 | ||
| 181 |
#' @export |
|
| 182 |
#' |
|
| 183 |
#' @returns A `LinkComponent` object. |
|
| 184 |
linkDSLD.LongitudinalSteinFojo <- function( |
|
| 185 |
prior = prior_normal(0, 2), |
|
| 186 |
model, |
|
| 187 |
... |
|
| 188 |
) {
|
|
| 189 | 2x |
LinkComponent( |
| 190 | 2x |
key = "link_dsld", |
| 191 | 2x |
stan = StanModule("lm-stein-fojo/link_dsld.stan"),
|
| 192 | 2x |
prior = prior |
| 193 |
) |
|
| 194 |
} |
|
| 195 | ||
| 196 |
#' @export |
|
| 197 |
#' |
|
| 198 |
#' @returns A `LinkComponent` object. |
|
| 199 |
linkTTG.LongitudinalSteinFojo <- function( |
|
| 200 |
prior = prior_normal(0, 2), |
|
| 201 |
model, |
|
| 202 |
... |
|
| 203 |
) {
|
|
| 204 | 1x |
LinkComponent( |
| 205 | 1x |
key = "link_ttg", |
| 206 | 1x |
stan = StanModule("lm-stein-fojo/link_ttg.stan"),
|
| 207 | 1x |
prior = prior |
| 208 |
) |
|
| 209 |
} |
|
| 210 | ||
| 211 |
#' @export |
|
| 212 |
#' |
|
| 213 |
#' @returns A `LinkComponent` object. |
|
| 214 |
linkIdentity.LongitudinalSteinFojo <- function( |
|
| 215 |
prior = prior_normal(0, 2), |
|
| 216 |
model, |
|
| 217 |
... |
|
| 218 |
) {
|
|
| 219 | ! |
LinkComponent( |
| 220 | ! |
key = "link_identity", |
| 221 | ! |
stan = StanModule("lm-stein-fojo/link_identity.stan"),
|
| 222 | ! |
prior = prior |
| 223 |
) |
|
| 224 |
} |
|
| 225 | ||
| 226 |
#' @export |
|
| 227 |
#' |
|
| 228 |
#' @returns A `LinkComponent` object. |
|
| 229 |
linkGrowth.LongitudinalSteinFojo <- function( |
|
| 230 |
prior = prior_normal(0, 2), |
|
| 231 |
model, |
|
| 232 |
... |
|
| 233 |
) {
|
|
| 234 | 2x |
LinkComponent( |
| 235 | 2x |
key = "link_growth", |
| 236 | 2x |
stan = StanModule("lm-stein-fojo/link_growth.stan"),
|
| 237 | 2x |
prior = prior |
| 238 |
) |
|
| 239 |
} |
|
| 240 | ||
| 241 |
#' @export |
|
| 242 |
#' |
|
| 243 |
#' @returns A `LinkComponent` object. |
|
| 244 |
linkShrinkage.LongitudinalSteinFojo <- function( |
|
| 245 |
prior = prior_normal(0, 2), |
|
| 246 |
model, |
|
| 247 |
... |
|
| 248 |
) {
|
|
| 249 | 1x |
LinkComponent( |
| 250 | 1x |
key = "link_shrinkage", |
| 251 | 1x |
stan = StanModule("lm-stein-fojo/link_shrinkage.stan"),
|
| 252 | 1x |
prior = prior |
| 253 |
) |
|
| 254 |
} |
|
| 255 | ||
| 256 |
#' @rdname getPredictionNames |
|
| 257 |
#' @export |
|
| 258 |
getPredictionNames.LongitudinalSteinFojo <- function(object, ...) {
|
|
| 259 | 1x |
c("b", "s", "g")
|
| 260 |
} |
|
| 261 | ||
| 262 | ||
| 263 |
#' @rdname getRandomEffectsNames |
|
| 264 |
#' @export |
|
| 265 |
getRandomEffectsNames.LongitudinalSteinFojo <- function(object, ...) {
|
|
| 266 | ! |
c( |
| 267 | ! |
"b" = "lm_sf_psi_bsld", |
| 268 | ! |
"s" = "lm_sf_psi_ks", |
| 269 | ! |
"g" = "lm_sf_psi_kg" |
| 270 |
) |
|
| 271 |
} |
| 1 |
#' @include JointModel.R |
|
| 2 |
#' @include SurvivalQuantities.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
setOldClass("CmdStanMCMC")
|
|
| 6 | ||
| 7 |
# JointModelSamples-class ---- |
|
| 8 | ||
| 9 |
#' `JointModelSamples` |
|
| 10 |
#' |
|
| 11 |
#' Contains samples from a [`JointModel`]. |
|
| 12 |
#' |
|
| 13 |
#' @slot model ([`JointModel`])\cr the model that the samples were drawn from. |
|
| 14 |
#' @slot data ([`DataJoint`])\cr the data that the model was fitted on. |
|
| 15 |
#' @slot results ([`cmdstanr::CmdStanMCMC`])\cr the STAN samples. |
|
| 16 |
#' |
|
| 17 |
#' @aliases JointModelSamples |
|
| 18 |
#' @export |
|
| 19 |
#' |
|
| 20 |
#' @returns A `JointModelSamples` object. |
|
| 21 |
#' |
|
| 22 |
#' @examples |
|
| 23 |
#' \dontrun{
|
|
| 24 |
#' # JointModelSamples objects are returned by sampleStanModel(). |
|
| 25 |
#' fit <- sampleStanModel(JointModel(LongitudinalGSF()), data = joint_data) |
|
| 26 |
#' } |
|
| 27 |
.JointModelSamples <- setClass( |
|
| 28 |
"JointModelSamples", |
|
| 29 |
slots = c( |
|
| 30 |
model = "JointModel", |
|
| 31 |
data = "DataJoint", |
|
| 32 |
results = "CmdStanMCMC" |
|
| 33 |
) |
|
| 34 |
) |
|
| 35 | ||
| 36 | ||
| 37 |
#' @rdname generateQuantities |
|
| 38 |
#' @typed generator: QuantityGenerator |
|
| 39 |
#' object that specifies which subjects and time points |
|
| 40 |
#' to calculate the quantities at |
|
| 41 |
#' @typed type: character |
|
| 42 |
#' type of quantities to be generated, must be either "survival" or |
|
| 43 |
#' "longitudinal". |
|
| 44 |
#' @export |
|
| 45 |
generateQuantities.JointModelSamples <- function(object, generator, type, ...) {
|
|
| 46 | 38x |
data <- as_stan_list(object@data, object@model) |> |
| 47 | 38x |
append(as_stan_list(object@model@parameters)) |> |
| 48 | 38x |
append(as_stan_list( |
| 49 | 38x |
generator, |
| 50 | 38x |
data = object@data, |
| 51 | 38x |
model = object@model |
| 52 |
)) |
|
| 53 | ||
| 54 | 37x |
stanobj <- as.StanModule(object, generator = generator, type = type) |
| 55 | 37x |
model <- compileStanModel(stanobj) |
| 56 | ||
| 57 | 37x |
devnull <- utils::capture.output( |
| 58 | 37x |
results <- model$generate_quantities( |
| 59 | 37x |
data = data, |
| 60 | 37x |
fitted_params = object@results |
| 61 |
) |
|
| 62 |
) |
|
| 63 | 37x |
return(results) |
| 64 |
} |
|
| 65 | ||
| 66 | ||
| 67 |
#' `JointModelSamples` -> `StanModule` |
|
| 68 |
#' |
|
| 69 |
#' Converts a `JointModelSamples` object into a `StanModule` object ensuring |
|
| 70 |
#' that the resulting `StanModule` object is able to generate post sampling |
|
| 71 |
#' quantities. |
|
| 72 |
#' |
|
| 73 |
#' @inheritParams generateQuantities |
|
| 74 |
#' @export |
|
| 75 |
#' |
|
| 76 |
#' @returns A `StanModule` object. |
|
| 77 |
as.StanModule.JointModelSamples <- function(object, generator, type, ...) {
|
|
| 78 | 51x |
assert_that( |
| 79 | 51x |
is(generator, "QuantityGenerator"), |
| 80 | 51x |
length(type) == 1, |
| 81 | 51x |
type %in% c("survival", "longitudinal")
|
| 82 |
) |
|
| 83 | ||
| 84 | 51x |
longitudinal_gq_population_data <- if (type == "longitudinal") {
|
| 85 | 28x |
gq_population_stan_data( |
| 86 | 28x |
generator, |
| 87 | 28x |
model = object@model@longitudinal |
| 88 |
) |
|
| 89 |
} else {
|
|
| 90 | 23x |
list(declarations = "", data = list()) |
| 91 |
} |
|
| 92 | ||
| 93 | 51x |
quant_stanobj <- read_stan("base/quantities.stan") |>
|
| 94 | 51x |
decorated_render( |
| 95 | 51x |
include_gq_longitudinal_idv = (type == "longitudinal") & |
| 96 | 51x |
is(generator, "QuantityGeneratorSubject"), |
| 97 | 51x |
include_gq_longitudinal_pop = (type == "longitudinal") & |
| 98 | 51x |
is(generator, "QuantityGeneratorPopulation"), |
| 99 | 51x |
include_gq_survival_idv = (type == "survival") & |
| 100 | 51x |
is(generator, "QuantityGeneratorSubject"), |
| 101 | 51x |
include_gq_survival_pred = (type == "survival") & |
| 102 | 51x |
is(generator, "QuantityGeneratorPrediction"), |
| 103 | 51x |
longitudinal_gq_population_data = |
| 104 | 51x |
longitudinal_gq_population_data$declarations |
| 105 |
) |> |
|
| 106 | 51x |
StanModule() |
| 107 | ||
| 108 | 51x |
stanobj <- Reduce( |
| 109 | 51x |
merge, |
| 110 | 51x |
list( |
| 111 | 51x |
as.StanModule(object@model), |
| 112 | 51x |
enableGQ( |
| 113 | 51x |
object@model, |
| 114 | 51x |
generator = generator, |
| 115 | 51x |
type = type |
| 116 |
), |
|
| 117 | 51x |
quant_stanobj |
| 118 |
) |
|
| 119 |
) |
|
| 120 | 51x |
stanobj |
| 121 |
} |
|
| 122 | ||
| 123 | ||
| 124 |
#' `JointModelSamples` -> Printable `Character` |
|
| 125 |
#' |
|
| 126 |
#' Converts [`JointModelSamples`] object into a printable string. |
|
| 127 |
#' |
|
| 128 |
#' @typed object: JointModelSamples |
|
| 129 |
#' samples as drawn from a [`JointModel`]. |
|
| 130 |
#' @family JointModelSamples |
|
| 131 |
#' @typed indent: numeric |
|
| 132 |
#' how much white space to prefix the print string with. |
|
| 133 |
#' @keywords internal |
|
| 134 |
#' @export |
|
| 135 |
#' |
|
| 136 |
#' @returns A character vector suitable for printing. |
|
| 137 |
as_print_string.JointModelSamples <- function(object, indent = 1, ...) {
|
|
| 138 | 18x |
sizes <- vapply( |
| 139 | 18x |
cmdstanr::as.CmdStanMCMC(object)$metadata()[["stan_variable_sizes"]], |
| 140 | 18x |
\(x) {
|
| 141 | 280x |
if (length(x) == 1 && x == 1) {
|
| 142 | 88x |
return("")
|
| 143 |
} |
|
| 144 | 192x |
paste0("[", paste(x, collapse = ", "), "]")
|
| 145 |
}, |
|
| 146 | 18x |
character(1) |
| 147 |
) |
|
| 148 | 18x |
variable_string <- paste0( |
| 149 |
" ", |
|
| 150 | 18x |
cmdstanr::as.CmdStanMCMC(object)$metadata()[["stan_variables"]], |
| 151 | 18x |
sizes |
| 152 |
) |
|
| 153 | 18x |
template <- c( |
| 154 | 18x |
"JointModelSamples Object with:", |
| 155 |
"", |
|
| 156 | 18x |
" # of samples per chain = %d", |
| 157 | 18x |
" # of chains = %d", |
| 158 |
"", |
|
| 159 | 18x |
" Variables:", |
| 160 | 18x |
variable_string[order(variable_string)] |
| 161 |
) |
|
| 162 | 18x |
pad <- rep(" ", indent) |> paste(collapse = "")
|
| 163 | 18x |
template_padded <- paste(pad, template) |
| 164 | 18x |
sprintf( |
| 165 | 18x |
paste(template_padded, collapse = "\n"), |
| 166 | 18x |
cmdstanr::as.CmdStanMCMC(object)$metadata()$iter_sampling, |
| 167 | 18x |
cmdstanr::as.CmdStanMCMC(object)$num_chains() |
| 168 |
) |
|
| 169 |
} |
|
| 170 | ||
| 171 |
#' @rdname show-object |
|
| 172 |
#' @export |
|
| 173 |
setMethod( |
|
| 174 |
f = "show", |
|
| 175 |
signature = "JointModelSamples", |
|
| 176 |
definition = function(object) {
|
|
| 177 | 18x |
string <- as_print_string(object) |
| 178 | 18x |
cat("\n", string, "\n\n")
|
| 179 |
} |
|
| 180 |
) |
|
| 181 | ||
| 182 | ||
| 183 |
#' @rdname as.CmdStanMCMC |
|
| 184 |
#' |
|
| 185 |
#' @returns The underlying `cmdstanr::CmdStanMCMC` object. |
|
| 186 |
as.CmdStanMCMC.JointModelSamples <- function(object, ...) {
|
|
| 187 | 105x |
return(object@results) |
| 188 |
} |
|
| 189 | ||
| 190 | ||
| 191 |
#' Save a `JointModelSamples` object to a file. |
|
| 192 |
#' |
|
| 193 |
#' This function is just a wrapper around `saveRDS` that saves the object to a file |
|
| 194 |
#' ensuring that all of the Stan samples are correctly stored. Note that as |
|
| 195 |
#' `cmdstanr` objects store their samples as a csv file the samples may be lost |
|
| 196 |
#' if you call `saveRDS` directly on the object. |
|
| 197 |
#' |
|
| 198 |
#' @typed object: JointModelSamples |
|
| 199 |
#' the object to save. |
|
| 200 |
#' @typed file: character |
|
| 201 |
#' the file to save the object to. |
|
| 202 |
#' @typed ...: ANY |
|
| 203 |
#' additional arguments to [`saveRDS`]. |
|
| 204 |
#' |
|
| 205 |
#' @family saveObject |
|
| 206 |
#' |
|
| 207 |
#' @export |
|
| 208 |
#' |
|
| 209 |
#' @returns Invisibly returns `NULL` after saving the object. |
|
| 210 |
saveObject.JointModelSamples <- function(object, file, ...) {
|
|
| 211 | 1x |
object@results$draws() |
| 212 | 1x |
try(object@results$sampler_diagnostics(), silent = TRUE) |
| 213 | 1x |
try(object@results$init(), silent = TRUE) |
| 214 | 1x |
try(object@results$profiles(), silent = TRUE) |
| 215 | 1x |
saveRDS(object, file, ...) |
| 216 |
} |
|
| 217 | ||
| 218 |
#' Summarise posterior covariate coefficients |
|
| 219 |
#' |
|
| 220 |
#' Extracts posterior summaries of survival and longitudinal covariate |
|
| 221 |
#' coefficients from a fitted joint model. Uses the existing [stats::coef()] |
|
| 222 |
#' generic. Survival-only and longitudinal-only fits are also supported. |
|
| 223 |
#' |
|
| 224 |
#' Covariate labels come from the fitted design matrices, including factor |
|
| 225 |
#' contrasts and interactions. Longitudinal coefficients are grouped by their |
|
| 226 |
#' predictor, such as `mu_g`, `mu_s`, or `omega_g`; all covariate predictors |
|
| 227 |
#' reported by [covariates.LongitudinalModel()] are included. |
|
| 228 |
#' |
|
| 229 |
#' Coefficients remain on their fitted predictor scales. No exponentiation or |
|
| 230 |
#' other transformation is applied. Intercepts, association-link coefficients, |
|
| 231 |
#' and other model parameters are not included. An error is raised if there |
|
| 232 |
#' are no covariate coefficients or a required coefficient was not sampled. |
|
| 233 |
#' |
|
| 234 |
#' @typed object: JointModelSamples |
|
| 235 |
#' samples from a fitted joint model. |
|
| 236 |
#' @param ... Summary functions or named arguments passed to the underlying |
|
| 237 |
#' `cmdstanr::CmdStanMCMC` object's `$summary()` method, which uses |
|
| 238 |
#' [posterior::summarise_draws()]. Supply a custom quantile function to |
|
| 239 |
#' change the credible interval (see examples). Do not supply `variables`: |
|
| 240 |
#' the method selects the coefficient variables automatically. |
|
| 241 |
#' @returns A tibble with one row per covariate coefficient, in design-matrix |
|
| 242 |
#' order (survival first, then longitudinal predictors). The columns are: |
|
| 243 |
#' * `component`: `"survival"` or `"longitudinal"`. |
|
| 244 |
#' * `predictor`: the longitudinal predictor name, or `NA` for survival. |
|
| 245 |
#' * `covariate`: the corresponding design-matrix column name. |
|
| 246 |
#' * `variable`: the original indexed Stan variable name. |
|
| 247 |
#' * Summary columns returned by `$summary()`. By default these include |
|
| 248 |
#' `mean`, `median`, `sd`, `mad`, `q5`, `q95`, `rhat`, `ess_bulk`, and |
|
| 249 |
#' `ess_tail`. The default intervals are 90% credible intervals. |
|
| 250 |
#' |
|
| 251 |
#' @family JointModelSamples |
|
| 252 |
#' @seealso [shrinkage.JointModelSamples()] |
|
| 253 |
#' @export |
|
| 254 |
#' @examples |
|
| 255 |
#' \dontrun{
|
|
| 256 |
#' # fit is a JointModelSamples object returned by sampleStanModel(). |
|
| 257 |
#' coef(fit) |
|
| 258 |
#' coef(fit, "median", intervals = function(x) {
|
|
| 259 |
#' stats::quantile(x, probs = c(0.025, 0.975)) |
|
| 260 |
#' }) |
|
| 261 |
#' coef(fit, "mean", "sd") |
|
| 262 |
#' } |
|
| 263 |
coef.JointModelSamples <- function(object, ...) {
|
|
| 264 | 13x |
covariate_names <- list() |
| 265 |
if ( |
|
| 266 | 13x |
!is.null(object@model@survival) && |
| 267 | 13x |
is(object@data@survival, "DataSurvival") |
| 268 |
) {
|
|
| 269 | 7x |
covariate_names$beta_os_cov <- covariates(object@data@survival) |
| 270 |
} |
|
| 271 | 13x |
if (!is.null(object@model@longitudinal)) {
|
| 272 | 9x |
covariate_names <- c( |
| 273 | 9x |
covariate_names, |
| 274 | 9x |
covariates(object@model@longitudinal, data = object@data@subject) |
| 275 |
) |
|
| 276 |
} |
|
| 277 | ||
| 278 | 13x |
rows <- lapply(names(covariate_names), function(parameter) {
|
| 279 | 44x |
covariates <- covariate_names[[parameter]] |
| 280 | 44x |
if (length(covariates) == 0L) {
|
| 281 | 9x |
return(NULL) |
| 282 |
} |
|
| 283 | 35x |
survival <- identical(parameter, "beta_os_cov") |
| 284 | 35x |
predictor <- if (survival) {
|
| 285 | 35x |
NA_character_ |
| 286 |
} else {
|
|
| 287 | 28x |
sub("_coefficients$", "", sub("^lm_[^_]+_", "", parameter))
|
| 288 |
} |
|
| 289 | 35x |
tibble::tibble( |
| 290 | 35x |
component = if (survival) "survival" else "longitudinal", |
| 291 | 35x |
predictor = predictor, |
| 292 | 35x |
covariate = covariates, |
| 293 | 35x |
variable = paste0(parameter, "[", seq_along(covariates), "]") |
| 294 |
) |
|
| 295 |
}) |
|
| 296 | 13x |
labels <- dplyr::bind_rows(rows) |
| 297 | 13x |
assert_that( |
| 298 | 13x |
nrow(labels) > 0L, |
| 299 | 13x |
msg = "No covariate coefficients are available for this model." |
| 300 |
) |
|
| 301 | ||
| 302 | 11x |
estimates <- cmdstanr::as.CmdStanMCMC(object)$summary( |
| 303 | 11x |
variables = labels$variable, |
| 304 |
... |
|
| 305 |
) |
|
| 306 | 11x |
indices <- match(labels$variable, estimates$variable) |
| 307 | 11x |
assert_that( |
| 308 | 11x |
!anyNA(indices), |
| 309 | 11x |
msg = "Sampled coefficients do not match the model design matrices." |
| 310 |
) |
|
| 311 | 10x |
dplyr::bind_cols( |
| 312 | 10x |
labels, |
| 313 | 10x |
estimates[indices, setdiff(names(estimates), "variable"), drop = FALSE] |
| 314 |
) |
|
| 315 |
} |
|
| 316 | ||
| 317 |
#' Extract shrinkage factors from a `JointModelSamples` object |
|
| 318 |
#' |
|
| 319 |
#' Extracts all sampled variables whose names start with |
|
| 320 |
#' `prior_shrinkage_factors_`, including those generated by horseshoe priors on |
|
| 321 |
#' survival and longitudinal coefficients. These are prior shrinkage factors, |
|
| 322 |
#' distinct from the tumour-shrinkage rate in a longitudinal model. |
|
| 323 |
#' |
|
| 324 |
#' Survival coefficient factors retain their covariate names. Longitudinal |
|
| 325 |
#' coefficient factors are labelled `longitudinal.<predictor>:<covariate>`, for |
|
| 326 |
#' example `longitudinal.mu_g:age` or `longitudinal.omega_s:armB`. Their |
|
| 327 |
#' covariate names are obtained from [covariates.LongitudinalModel()]. Factors |
|
| 328 |
#' without a known covariate mapping retain their Stan variable names. |
|
| 329 |
#' |
|
| 330 |
#' Survival-only and longitudinal-only fits are supported. An error is raised |
|
| 331 |
#' if no prior shrinkage factors were sampled or if sampled indices cannot be |
|
| 332 |
#' matched to a known design matrix. |
|
| 333 |
#' |
|
| 334 |
#' @typed object: JointModelSamples |
|
| 335 |
#' the object to extract shrinkage factors from. |
|
| 336 |
#' @param ... not used. |
|
| 337 |
#' @returns A posterior draws object containing the shrinkage factors, with |
|
| 338 |
#' iterations, chains, and variable order preserved. It can be summarised |
|
| 339 |
#' with [posterior::summarise_draws()]. |
|
| 340 |
#' |
|
| 341 |
#' @family shrinkage |
|
| 342 |
#' @export |
|
| 343 |
shrinkage.JointModelSamples <- function(object, ...) {
|
|
| 344 | 11x |
draws <- cmdstanr::as.CmdStanMCMC(object)$draws() |
| 345 | 11x |
prefix <- "prior_shrinkage_factors_" |
| 346 | 11x |
variables <- posterior::variables(draws) |
| 347 | 11x |
variables <- variables[startsWith(variables, prefix)] |
| 348 | 11x |
assert_that( |
| 349 | 11x |
length(variables) > 0, |
| 350 | 11x |
msg = "No sampled prior shrinkage factors are available." |
| 351 |
) |
|
| 352 | 10x |
shrinkage_draws <- subset(draws, variable = variables) |
| 353 | 10x |
parameters <- sub("\\[.*$", "", substring(variables, nchar(prefix) + 1))
|
| 354 | 10x |
covariate_names <- .shrinkage_covariate_names(object, unique(parameters)) |
| 355 | 10x |
labels <- variables |
| 356 | 10x |
for (parameter in names(covariate_names)) {
|
| 357 | 12x |
selected <- which(parameters == parameter) |
| 358 | 12x |
expected <- paste0( |
| 359 | 12x |
prefix, |
| 360 | 12x |
parameter, |
| 361 |
"[", |
|
| 362 | 12x |
seq_along(covariate_names[[parameter]]), |
| 363 |
"]" |
|
| 364 |
) |
|
| 365 | 12x |
indices <- match(variables[selected], expected) |
| 366 | 12x |
assert_that( |
| 367 | 12x |
!anyNA(indices), |
| 368 | 12x |
msg = sprintf( |
| 369 | 12x |
"Shrinkage factors for `%s` do not match its design matrix.", |
| 370 | 12x |
parameter |
| 371 |
) |
|
| 372 |
) |
|
| 373 | 11x |
labels[selected] <- covariate_names[[parameter]][indices] |
| 374 |
} |
|
| 375 | 9x |
assert_that( |
| 376 | 9x |
!anyDuplicated(labels), |
| 377 | 9x |
msg = "Shrinkage factor labels are not unique." |
| 378 |
) |
|
| 379 | 9x |
posterior::variables(shrinkage_draws) <- labels |
| 380 | 9x |
shrinkage_draws |
| 381 |
} |
|
| 382 | ||
| 383 |
#' Build covariate labels for sampled shrinkage parameters |
|
| 384 |
#' |
|
| 385 |
#' @param object A [`JointModelSamples`] object supplying model and data. |
|
| 386 |
#' @param parameters Character vector of parameter names, without the |
|
| 387 |
#' `prior_shrinkage_factors_` prefix or element indices. |
|
| 388 |
#' @returns A named list of covariate labels for recognised parameters. |
|
| 389 |
#' Unmapped parameters are omitted so the caller can retain their Stan names. |
|
| 390 |
#' @keywords internal |
|
| 391 |
.shrinkage_covariate_names <- function(object, parameters) {
|
|
| 392 | 10x |
result <- list() |
| 393 |
if ( |
|
| 394 | 10x |
"beta_os_cov" %in% parameters && |
| 395 | 10x |
is(object@data@survival, "DataSurvival") |
| 396 |
) {
|
|
| 397 | 4x |
result$beta_os_cov <- covariates(object@data@survival) |
| 398 |
} |
|
| 399 | ||
| 400 | 10x |
if (is.null(object@model@longitudinal)) {
|
| 401 | 2x |
return(result) |
| 402 |
} |
|
| 403 | ||
| 404 | 8x |
longitudinal_covariates <- covariates( |
| 405 | 8x |
object@model@longitudinal, |
| 406 | 8x |
data = object@data@subject |
| 407 |
) |
|
| 408 | 8x |
for (parameter in intersect(parameters, names(longitudinal_covariates))) {
|
| 409 | 8x |
covariate_names <- longitudinal_covariates[[parameter]] |
| 410 | 8x |
if (is.null(covariate_names)) {
|
| 411 | ! |
next |
| 412 |
} |
|
| 413 | 8x |
predictor <- sub("^lm_[^_]+_", "", parameter)
|
| 414 | 8x |
predictor <- sub("_coefficients$", "", predictor)
|
| 415 | 8x |
result[[parameter]] <- paste0( |
| 416 | 8x |
"longitudinal.", predictor, ":", covariate_names |
| 417 |
) |
|
| 418 |
} |
|
| 419 | 8x |
result |
| 420 |
} |
| 1 |
#' @include SimLongitudinal.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Simulate Longitudinal Data from a Claret-Bruno Model |
|
| 6 |
#' |
|
| 7 |
#' @typed times: numeric |
|
| 8 |
#' the times to generate observations at. |
|
| 9 |
#' @typed sigma: number |
|
| 10 |
#' the variance of the longitudinal values. |
|
| 11 |
#' |
|
| 12 |
#' @typed mu_b: numeric |
|
| 13 |
#' the mean population baseline sld value. |
|
| 14 |
#' @typed mu_g: numeric |
|
| 15 |
#' the mean population growth rate. |
|
| 16 |
#' @typed mu_c: numeric |
|
| 17 |
#' the mean population resistance rate. |
|
| 18 |
#' @typed mu_p: numeric |
|
| 19 |
#' the mean population growth inhibition. |
|
| 20 |
#' |
|
| 21 |
#' @typed omega_b: number |
|
| 22 |
#' the population standard deviation for the baseline sld value. |
|
| 23 |
#' @typed omega_g: number |
|
| 24 |
#' the population standard deviation for the growth rate. |
|
| 25 |
#' @typed omega_c: number |
|
| 26 |
#' the population standard deviation for the resistance rate. |
|
| 27 |
#' @typed omega_p: number |
|
| 28 |
#' the population standard deviation for the growth inhibition. |
|
| 29 |
#' |
|
| 30 |
#' @typed link_dsld: number |
|
| 31 |
#' the link coefficient for the derivative contribution. |
|
| 32 |
#' @typed link_ttg: number |
|
| 33 |
#' the link coefficient for the time-to-growth contribution. |
|
| 34 |
#' @typed link_identity: number |
|
| 35 |
#' the link coefficient for the SLD Identity contribution. |
|
| 36 |
#' @typed link_growth: number |
|
| 37 |
#' the link coefficient for the growth parameter contribution. |
|
| 38 |
#' |
|
| 39 |
#' @typed scaled_variance: logical |
|
| 40 |
#' whether the variance should be scaled by the expected value |
|
| 41 |
#' (see the "Statistical Specifications" vignette for more details) |
|
| 42 |
#' |
|
| 43 |
#' @slot sigma (`numeric`)\cr See arguments. |
|
| 44 |
#' |
|
| 45 |
#' @slot mu_b (`numeric`)\cr See arguments. |
|
| 46 |
#' @slot mu_g (`numeric`)\cr See arguments. |
|
| 47 |
#' @slot mu_c (`numeric`)\cr See arguments. |
|
| 48 |
#' @slot mu_p (`numeric`)\cr See arguments. |
|
| 49 |
#' |
|
| 50 |
#' @slot omega_b (`numeric`)\cr See arguments. |
|
| 51 |
#' @slot omega_g (`numeric`)\cr See arguments. |
|
| 52 |
#' @slot omega_c (`numeric`)\cr See arguments. |
|
| 53 |
#' @slot omega_p (`numeric`)\cr See arguments. |
|
| 54 |
#' |
|
| 55 |
#' @slot link_dsld (`numeric`)\cr See arguments. |
|
| 56 |
#' @slot link_ttg (`numeric`)\cr See arguments. |
|
| 57 |
#' @slot link_identity (`numeric`)\cr See arguments. |
|
| 58 |
#' @slot link_growth (`numeric`)\cr See arguments. |
|
| 59 |
#' |
|
| 60 |
#' @slot scaled_variance (`logical`)\cr See arguments. |
|
| 61 |
#' |
|
| 62 |
#' @family SimLongitudinal |
|
| 63 |
#' @name SimLongitudinalClaretBruno-class |
|
| 64 |
#' @exportClass SimLongitudinalClaretBruno |
|
| 65 |
#' |
|
| 66 |
#' @examples |
|
| 67 |
#' SimLongitudinalClaretBruno(times = c(0, 30, 60)) |
|
| 68 |
.SimLongitudinalClaretBruno <- setClass( |
|
| 69 |
"SimLongitudinalClaretBruno", |
|
| 70 |
contains = "SimLongitudinal", |
|
| 71 |
slots = c( |
|
| 72 |
sigma = "numeric", |
|
| 73 |
mu_b = "numeric", |
|
| 74 |
mu_g = "numeric", |
|
| 75 |
mu_c = "numeric", |
|
| 76 |
mu_p = "numeric", |
|
| 77 |
omega_b = "numeric", |
|
| 78 |
omega_g = "numeric", |
|
| 79 |
omega_c = "numeric", |
|
| 80 |
omega_p = "numeric", |
|
| 81 |
link_dsld = "numeric", |
|
| 82 |
link_ttg = "numeric", |
|
| 83 |
link_identity = "numeric", |
|
| 84 |
link_growth = "numeric", |
|
| 85 |
scaled_variance = "logical" |
|
| 86 |
) |
|
| 87 |
) |
|
| 88 | ||
| 89 |
#' @rdname SimLongitudinalClaretBruno-class |
|
| 90 |
#' @export |
|
| 91 |
#' |
|
| 92 |
#' @returns A `SimLongitudinalClaretBruno` object. |
|
| 93 |
SimLongitudinalClaretBruno <- function( |
|
| 94 |
times = c(-100, -50, 0, 50, 100, 150, 250, 350, 450, 550) / 365, |
|
| 95 |
sigma = 0.01, |
|
| 96 |
mu_b = log(60), |
|
| 97 |
mu_g = log(c(0.9, 1.1)), |
|
| 98 |
mu_c = log(c(0.25, 0.35)), |
|
| 99 |
mu_p = log(c(1.5, 2)), |
|
| 100 |
omega_b = 0.2, |
|
| 101 |
omega_g = 0.2, |
|
| 102 |
omega_c = 0.2, |
|
| 103 |
omega_p = 0.2, |
|
| 104 |
link_dsld = 0, |
|
| 105 |
link_ttg = 0, |
|
| 106 |
link_identity = 0, |
|
| 107 |
link_growth = 0, |
|
| 108 |
scaled_variance = FALSE |
|
| 109 |
) {
|
|
| 110 | 2x |
if (length(omega_b) == 1) {
|
| 111 | 2x |
omega_b <- rep(omega_b, length(mu_b)) |
| 112 |
} |
|
| 113 | 2x |
if (length(omega_g) == 1) {
|
| 114 | 2x |
omega_g <- rep(omega_g, length(mu_g)) |
| 115 |
} |
|
| 116 | 2x |
if (length(omega_c) == 1) {
|
| 117 | 2x |
omega_c <- rep(omega_c, length(mu_c)) |
| 118 |
} |
|
| 119 | 2x |
if (length(omega_p) == 1) {
|
| 120 | 2x |
omega_p <- rep(omega_p, length(mu_p)) |
| 121 |
} |
|
| 122 | ||
| 123 | 2x |
.SimLongitudinalClaretBruno( |
| 124 | 2x |
times = times, |
| 125 | 2x |
sigma = sigma, |
| 126 | 2x |
mu_b = mu_b, |
| 127 | 2x |
mu_g = mu_g, |
| 128 | 2x |
mu_c = mu_c, |
| 129 | 2x |
mu_p = mu_p, |
| 130 | 2x |
omega_b = omega_b, |
| 131 | 2x |
omega_g = omega_g, |
| 132 | 2x |
omega_c = omega_c, |
| 133 | 2x |
omega_p = omega_p, |
| 134 | 2x |
link_dsld = link_dsld, |
| 135 | 2x |
link_ttg = link_ttg, |
| 136 | 2x |
link_identity = link_identity, |
| 137 | 2x |
link_growth = link_growth, |
| 138 | 2x |
scaled_variance = scaled_variance |
| 139 |
) |
|
| 140 |
} |
|
| 141 | ||
| 142 | ||
| 143 |
setValidity( |
|
| 144 |
"SimLongitudinalClaretBruno", |
|
| 145 |
function(object) {
|
|
| 146 |
par_lengths <- c( |
|
| 147 |
length(object@mu_g), |
|
| 148 |
length(object@mu_c), |
|
| 149 |
length(object@mu_p) |
|
| 150 |
) |
|
| 151 |
if (length(unique(par_lengths)) != 1) {
|
|
| 152 |
return( |
|
| 153 |
"The parameters `mu_g`, `mu_c` & `mu_p` must have the same length." |
|
| 154 |
) |
|
| 155 |
} |
|
| 156 |
pairs <- list( |
|
| 157 |
"omega_b" = "mu_b", |
|
| 158 |
"omega_g" = "mu_g", |
|
| 159 |
"omega_c" = "mu_c", |
|
| 160 |
"omega_p" = "mu_p" |
|
| 161 |
) |
|
| 162 |
for (i in seq_along(pairs)) {
|
|
| 163 |
omega <- slot(object, names(pairs)[[i]]) |
|
| 164 |
mu <- slot(object, pairs[[i]]) |
|
| 165 |
if (!(length(omega) == length(mu))) {
|
|
| 166 |
return( |
|
| 167 |
sprintf( |
|
| 168 |
"`%s` must be length 1 or the same length as `%s`", |
|
| 169 |
omega, |
|
| 170 |
mu |
|
| 171 |
) |
|
| 172 |
) |
|
| 173 |
} |
|
| 174 |
} |
|
| 175 |
len_1_pars <- c( |
|
| 176 |
"sigma", |
|
| 177 |
"link_dsld", |
|
| 178 |
"link_ttg", |
|
| 179 |
"link_identity", |
|
| 180 |
"link_growth" |
|
| 181 |
) |
|
| 182 |
for (par in len_1_pars) {
|
|
| 183 |
if (length(slot(object, par)) != 1) {
|
|
| 184 |
return(sprintf( |
|
| 185 |
"The `%s` parameter must be a length 1 numeric.", |
|
| 186 |
par |
|
| 187 |
)) |
|
| 188 |
} |
|
| 189 |
} |
|
| 190 |
return(TRUE) |
|
| 191 |
} |
|
| 192 |
) |
|
| 193 | ||
| 194 |
#' @rdname as_print_string |
|
| 195 |
#' @exportS3Method NULL |
|
| 196 |
as_print_string.SimLongitudinalClaretBruno <- function(object, ...) {
|
|
| 197 | 1x |
return("SimLongitudinalClaretBruno")
|
| 198 |
} |
|
| 199 | ||
| 200 |
#' @rdname sampleObservations |
|
| 201 |
#' @export |
|
| 202 |
sampleObservations.SimLongitudinalClaretBruno <- function(object, times_df) {
|
|
| 203 | 1x |
times_df |> |
| 204 | 1x |
dplyr::mutate( |
| 205 | 1x |
mu_sld = clbr_sld( |
| 206 | 1x |
.data$time, |
| 207 | 1x |
.data$ind_b, |
| 208 | 1x |
.data$ind_g, |
| 209 | 1x |
.data$ind_c, |
| 210 | 1x |
.data$ind_p |
| 211 |
), |
|
| 212 | 1x |
dsld = clbr_dsld( |
| 213 | 1x |
.data$time, |
| 214 | 1x |
.data$ind_b, |
| 215 | 1x |
.data$ind_g, |
| 216 | 1x |
.data$ind_c, |
| 217 | 1x |
.data$ind_p |
| 218 |
), |
|
| 219 | 1x |
ttg = clbr_ttg( |
| 220 | 1x |
.data$time, |
| 221 | 1x |
.data$ind_b, |
| 222 | 1x |
.data$ind_g, |
| 223 | 1x |
.data$ind_c, |
| 224 | 1x |
.data$ind_p |
| 225 |
), |
|
| 226 | 1x |
sld_sd = ifelse( |
| 227 | 1x |
object@scaled_variance, |
| 228 | 1x |
.data$mu_sld * object@sigma, |
| 229 | 1x |
object@sigma |
| 230 |
), |
|
| 231 | 1x |
sld = stats::rnorm(dplyr::n(), .data$mu_sld, .data$sld_sd), |
| 232 | ||
| 233 | 1x |
log_haz_link = (object@link_dsld * .data$dsld) + |
| 234 | 1x |
(object@link_ttg * .data$ttg) + |
| 235 | 1x |
(object@link_identity * .data$mu_sld) + |
| 236 | 1x |
(object@link_growth * log(.data$ind_g)) |
| 237 |
) |
|
| 238 |
} |
|
| 239 | ||
| 240 | ||
| 241 |
#' @rdname sampleSubjects |
|
| 242 |
#' @export |
|
| 243 |
sampleSubjects.SimLongitudinalClaretBruno <- function(object, subjects_df) {
|
|
| 244 | 1x |
assert_that( |
| 245 | 1x |
is.factor(subjects_df$study), |
| 246 | 1x |
is.factor(subjects_df$arm), |
| 247 | 1x |
length(levels(subjects_df$study)) == length(object@mu_b), |
| 248 | 1x |
length(levels(subjects_df$arm)) == length(object@mu_g), |
| 249 | 1x |
length(levels(subjects_df$arm)) == length(object@mu_c), |
| 250 | 1x |
length(levels(subjects_df$arm)) == length(object@mu_p) |
| 251 |
) |
|
| 252 | ||
| 253 | 1x |
res <- subjects_df |> |
| 254 | 1x |
dplyr::distinct(.data$subject, .data$arm, .data$study) |> |
| 255 | 1x |
dplyr::mutate(study_idx = as.numeric(.data$study)) |> |
| 256 | 1x |
dplyr::mutate(arm_idx = as.numeric(.data$arm)) |> |
| 257 | 1x |
dplyr::mutate( |
| 258 | 1x |
ind_b = stats::rlnorm( |
| 259 | 1x |
dplyr::n(), |
| 260 | 1x |
object@mu_b[.data$study_idx], |
| 261 | 1x |
object@omega_b[.data$study_idx] |
| 262 |
) |
|
| 263 |
) |> |
|
| 264 | 1x |
dplyr::mutate( |
| 265 | 1x |
ind_g = stats::rlnorm( |
| 266 | 1x |
dplyr::n(), |
| 267 | 1x |
object@mu_g[.data$arm_idx], |
| 268 | 1x |
object@omega_g[.data$arm_idx] |
| 269 |
) |
|
| 270 |
) |> |
|
| 271 | 1x |
dplyr::mutate( |
| 272 | 1x |
ind_c = stats::rlnorm( |
| 273 | 1x |
dplyr::n(), |
| 274 | 1x |
object@mu_c[.data$arm_idx], |
| 275 | 1x |
object@omega_c[.data$arm_idx] |
| 276 |
) |
|
| 277 |
) |> |
|
| 278 | 1x |
dplyr::mutate( |
| 279 | 1x |
ind_p = stats::rlnorm( |
| 280 | 1x |
dplyr::n(), |
| 281 | 1x |
object@mu_p[.data$arm_idx], |
| 282 | 1x |
object@omega_p[.data$arm_idx] |
| 283 |
) |
|
| 284 |
) |
|
| 285 | ||
| 286 | 1x |
res[, c("subject", "arm", "study", "ind_b", "ind_g", "ind_c", "ind_p")]
|
| 287 |
} |
|
| 288 | ||
| 289 | ||
| 290 |
#' Claret-Bruno Functionals |
|
| 291 |
#' |
|
| 292 |
#' @typed t: numeric |
|
| 293 |
#' time grid. |
|
| 294 |
#' @typed b: number |
|
| 295 |
#' baseline sld. |
|
| 296 |
#' @typed g: number |
|
| 297 |
#' growth rate. |
|
| 298 |
#' @typed c: number |
|
| 299 |
#' resistance rate. |
|
| 300 |
#' @typed p: number |
|
| 301 |
#' growth inhibition. |
|
| 302 |
#' |
|
| 303 |
#' @returns The function results. |
|
| 304 |
#' @keywords internal |
|
| 305 |
clbr_sld <- function(t, b, g, c, p) {
|
|
| 306 | 4x |
p <- ifelse(t >= 0, p, 0) |
| 307 | 4x |
b * exp((g * t) - (p / c) * (1 - exp(-c * t))) |
| 308 |
} |
|
| 309 | ||
| 310 |
#' @rdname clbr_sld |
|
| 311 |
clbr_ttg <- function(t, b, g, c, p) {
|
|
| 312 | 2x |
log(p / g) / c |
| 313 |
} |
|
| 314 | ||
| 315 |
#' @rdname clbr_sld |
|
| 316 |
clbr_dsld <- function(t, b, g, c, p) {
|
|
| 317 | 2x |
lt0 <- b * g * exp(g * t) |
| 318 | 2x |
gt0 <- (g - p * exp(-c * t)) * clbr_sld(t, b, g, c, p) |
| 319 | 2x |
ifelse(t >= 0, gt0, lt0) |
| 320 |
} |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include Grid.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' @rdname Quant-Dev |
|
| 7 |
.QuantityGeneratorSubject <- setClass( |
|
| 8 |
"QuantityGeneratorSubject", |
|
| 9 |
contains = "QuantityGenerator", |
|
| 10 |
slots = c( |
|
| 11 |
"times" = "numeric", |
|
| 12 |
"subjects" = "character_or_NULL" |
|
| 13 |
) |
|
| 14 |
) |
|
| 15 | ||
| 16 | ||
| 17 |
#' @rdname Quant-Dev |
|
| 18 |
QuantityGeneratorSubject <- function(times, subjects = NULL) {
|
|
| 19 | 94x |
.QuantityGeneratorSubject( |
| 20 | 94x |
times = times, |
| 21 | 94x |
subjects = subjects |
| 22 |
) |
|
| 23 |
} |
|
| 24 | ||
| 25 | ||
| 26 |
setValidity( |
|
| 27 |
"QuantityGeneratorSubject", |
|
| 28 |
function(object) {
|
|
| 29 |
if (length(object@times) != length(object@subjects)) {
|
|
| 30 |
return("Length of `times` and `subjects` must be equal")
|
|
| 31 |
} |
|
| 32 |
return(TRUE) |
|
| 33 |
} |
|
| 34 |
) |
|
| 35 | ||
| 36 | ||
| 37 |
#' @rdname as_stan_list.QuantityGenerator |
|
| 38 |
#' @export |
|
| 39 |
as_stan_list.QuantityGeneratorSubject <- function(object, data, ...) {
|
|
| 40 | 33x |
assert_that( |
| 41 | 33x |
is(data, "DataJoint") |
| 42 |
) |
|
| 43 | 33x |
ret <- list() |
| 44 | 33x |
data_list <- as.list(data) |
| 45 | 33x |
ret[["gq_subject_index"]] <- data_list$subject_to_index[as.character( |
| 46 | 33x |
object@subjects |
| 47 |
)] |
|
| 48 | 33x |
ret[["gq_n_quant"]] <- length(object@subjects) |
| 49 | 33x |
ret[["gq_times"]] <- object@times |
| 50 | ||
| 51 |
# dummy pop indexes in order for stan code to actualy compile. In this setting |
|
| 52 |
# this matrix isn't actually used so doesn't matter what these values are |
|
| 53 |
# but don't want to have to burden individual longitudinal models with the |
|
| 54 |
# conditional logic to check if they are generating population quantities or not |
|
| 55 | 33x |
ret[["gq_long_pop_arm_index"]] <- rep(1, ret[["gq_n_quant"]]) |
| 56 | 33x |
ret[["gq_long_pop_study_index"]] <- rep(1, ret[["gq_n_quant"]]) |
| 57 | ||
| 58 |
# Sanity checks |
|
| 59 | 33x |
assert_that( |
| 60 | 33x |
length(ret[["gq_times"]]) == ret[["gq_n_quant"]], |
| 61 | 33x |
all(object@subjects %in% names(data_list$subject_to_index)) |
| 62 |
) |
|
| 63 | 33x |
return(ret) |
| 64 |
} |
| 1 |
#' @include Grid.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' @rdname Grid-Dev |
|
| 7 |
.GridPopulation <- setClass( |
|
| 8 |
"GridPopulation", |
|
| 9 |
contains = "Grid", |
|
| 10 |
slots = c( |
|
| 11 |
"times" = "numeric_or_NULL", |
|
| 12 |
"newdata" = "data.frame_or_NULL" |
|
| 13 |
) |
|
| 14 |
) |
|
| 15 | ||
| 16 |
#' @rdname Grid-Functions |
|
| 17 |
#' @export |
|
| 18 |
GridPopulation <- function(times = NULL, newdata = NULL) {
|
|
| 19 | 7x |
.GridPopulation( |
| 20 | 7x |
times = times, |
| 21 | 7x |
newdata = newdata |
| 22 |
) |
|
| 23 |
} |
|
| 24 | ||
| 25 |
setValidity( |
|
| 26 |
"GridPopulation", |
|
| 27 |
function(object) {
|
|
| 28 |
if (!is.null(object@newdata) && anyDuplicated(names(object@newdata))) {
|
|
| 29 |
return("Column names in `newdata` must be unique")
|
|
| 30 |
} |
|
| 31 |
TRUE |
|
| 32 |
} |
|
| 33 |
) |
|
| 34 | ||
| 35 |
#' @rdname Quant-Dev |
|
| 36 |
#' @export |
|
| 37 |
as.QuantityGenerator.GridPopulation <- function( |
|
| 38 |
object, |
|
| 39 |
data, |
|
| 40 |
model = NULL, |
|
| 41 |
... |
|
| 42 |
) {
|
|
| 43 | 9x |
assert_class(data, "DataJoint") |
| 44 | 9x |
data_list <- as.list(data) |
| 45 | 9x |
validate_time_grid(object@times) |
| 46 | ||
| 47 | 9x |
n_times <- length(object@times) |
| 48 | 9x |
subject_vars <- extractVariableNames(data@subject) |
| 49 | 9x |
subject_data <- as.data.frame(harmonise(data@subject)) |
| 50 | 9x |
longitudinal_model <- if (is(model, "JointModel")) {
|
| 51 | 9x |
model@longitudinal |
| 52 |
} else {
|
|
| 53 | ! |
model |
| 54 |
} |
|
| 55 | ||
| 56 | 9x |
if (!is.null(object@newdata)) {
|
| 57 | 3x |
profiles <- object@newdata |
| 58 |
} else {
|
|
| 59 | 6x |
required_covariates <- required_longitudinal_covs(longitudinal_model) |
| 60 | 6x |
additional_covariates <- setdiff( |
| 61 | 6x |
required_covariates, |
| 62 | 6x |
c(subject_vars$arm, subject_vars$study) |
| 63 |
) |
|
| 64 | 6x |
assert_that( |
| 65 | 6x |
length(additional_covariates) == 0, |
| 66 | 6x |
msg = paste0( |
| 67 | 6x |
"`GridPopulation(newdata = ...)` must define the additional ", |
| 68 | 6x |
"longitudinal covariates: ", |
| 69 | 6x |
paste(additional_covariates, collapse = ", ") |
| 70 |
) |
|
| 71 |
) |
|
| 72 | ||
| 73 | 5x |
population_indexes <- match( |
| 74 | 5x |
paste(data_list$pop_arm_index, data_list$pop_study_index), |
| 75 | 5x |
paste(data_list$subject_arm_index, data_list$subject_study_index) |
| 76 |
) |
|
| 77 | 5x |
profiles <- subject_data[population_indexes, , drop = FALSE] |
| 78 |
} |
|
| 79 | ||
| 80 | 8x |
required_columns <- unique(c( |
| 81 | 8x |
subject_vars$arm, |
| 82 | 8x |
subject_vars$study, |
| 83 | 8x |
required_longitudinal_covs(longitudinal_model) |
| 84 |
)) |
|
| 85 | 8x |
missing_columns <- setdiff(required_columns, names(profiles)) |
| 86 | 8x |
assert_that( |
| 87 | 8x |
length(missing_columns) == 0, |
| 88 | 8x |
msg = sprintf( |
| 89 | 8x |
"`GridPopulation(newdata = ...)` is missing: %s", |
| 90 | 8x |
paste(missing_columns, collapse = ", ") |
| 91 |
) |
|
| 92 |
) |
|
| 93 | ||
| 94 | 7x |
n_quant <- nrow(profiles) |
| 95 | 7x |
expanded_indexes <- rep(seq_len(n_quant), n_times) |
| 96 | 7x |
expanded_profiles <- profiles[expanded_indexes, , drop = FALSE] |
| 97 | 7x |
rownames(expanded_profiles) <- NULL |
| 98 | ||
| 99 | 7x |
QuantityGeneratorPopulation( |
| 100 | 7x |
times = rep(object@times, each = n_quant), |
| 101 | 7x |
arms = as.character(expanded_profiles[[subject_vars$arm]]), |
| 102 | 7x |
studies = as.character(expanded_profiles[[subject_vars$study]]), |
| 103 | 7x |
newdata = expanded_profiles |
| 104 |
) |
|
| 105 |
} |
|
| 106 | ||
| 107 |
#' @rdname Quant-Dev |
|
| 108 |
#' @export |
|
| 109 |
as.QuantityCollapser.GridPopulation <- function( |
|
| 110 |
object, |
|
| 111 |
data, |
|
| 112 |
model = NULL, |
|
| 113 |
... |
|
| 114 |
) {
|
|
| 115 | 3x |
assert_class(data, "DataJoint") |
| 116 | 3x |
generator <- as.QuantityGenerator(object, data, model = model) |
| 117 | 3x |
subject_vars <- extractVariableNames(data@subject) |
| 118 | 3x |
profile_columns <- if (!is.null(object@newdata)) {
|
| 119 | 1x |
names(object@newdata) |
| 120 |
} else {
|
|
| 121 | 2x |
c(subject_vars$arm, subject_vars$study) |
| 122 |
} |
|
| 123 | 3x |
groups <- vapply( |
| 124 | 3x |
seq_len(nrow(generator@newdata)), |
| 125 | 3x |
function(index) {
|
| 126 | 754x |
values <- vapply( |
| 127 | 754x |
generator@newdata[index, profile_columns, drop = FALSE], |
| 128 | 754x |
as.character, |
| 129 | 754x |
character(1) |
| 130 |
) |
|
| 131 | 754x |
paste(sprintf("%s=%s", names(values), values), collapse = "; ")
|
| 132 |
}, |
|
| 133 | 3x |
character(1) |
| 134 |
) |
|
| 135 | 3x |
QuantityCollapser( |
| 136 | 3x |
times = generator@times, |
| 137 | 3x |
groups = groups, |
| 138 | 3x |
indexes = as.list(seq_along(generator@times)) |
| 139 |
) |
|
| 140 |
} |
|
| 141 | ||
| 142 | ||
| 143 |
#' @export |
|
| 144 |
#' |
|
| 145 |
#' @returns This is just a placeholder which will error when used. |
|
| 146 |
as.list.GridPopulation <- function(x, data, ...) {
|
|
| 147 | ! |
stop("`as.list()` is not implemented for `GridPopulation` objects")
|
| 148 |
} |
|
| 149 | ||
| 150 | ||
| 151 |
#' @rdname coalesceGridTime |
|
| 152 |
#' @export |
|
| 153 |
coalesceGridTime.GridPopulation <- function(object, times, ...) {
|
|
| 154 | 2x |
if (is.null(object@times)) {
|
| 155 | ! |
object <- GridPopulation( |
| 156 | ! |
times = times, |
| 157 | ! |
newdata = object@newdata |
| 158 |
) |
|
| 159 |
} |
|
| 160 | 2x |
object |
| 161 |
} |
| 1 |
#' @include LongitudinalModel.R |
|
| 2 |
#' @include StanModule.R |
|
| 3 |
#' @include generics.R |
|
| 4 |
#' @include ParameterList.R |
|
| 5 |
#' @include Parameter.R |
|
| 6 |
#' @include Link.R |
|
| 7 |
NULL |
|
| 8 | ||
| 9 | ||
| 10 |
#' `LongitudinalClaretBruno` |
|
| 11 |
#' |
|
| 12 |
#' This class extends the general [`LongitudinalModel`] class for using the |
|
| 13 |
#' Claret-Bruno model for the longitudinal outcome. |
|
| 14 |
#' |
|
| 15 |
#' @section Available Links: |
|
| 16 |
#' - [`linkDSLD()`] |
|
| 17 |
#' - [`linkTTG()`] |
|
| 18 |
#' - [`linkIdentity()`] |
|
| 19 |
#' - [`linkGrowth()`] |
|
| 20 |
#' @exportClass LongitudinalClaretBruno |
|
| 21 |
.LongitudinalClaretBruno <- setClass( |
|
| 22 |
Class = "LongitudinalClaretBruno", |
|
| 23 |
contains = "LongitudinalModel" |
|
| 24 |
) |
|
| 25 | ||
| 26 | ||
| 27 |
#' @rdname LongitudinalClaretBruno-class |
|
| 28 |
#' |
|
| 29 |
#' @typed mu_b: Prior |
|
| 30 |
#' for the mean population baseline sld value. |
|
| 31 |
#' @typed mu_g: Prior |
|
| 32 |
#' for the mean population growth rate. |
|
| 33 |
#' @typed mu_c: Prior |
|
| 34 |
#' for the mean population resistance rate. |
|
| 35 |
#' @typed mu_p: Prior |
|
| 36 |
#' for the mean population growth inhibition |
|
| 37 |
#' |
|
| 38 |
#' @typed omega_b: Prior |
|
| 39 |
#' for the population standard deviation for the baseline sld value. |
|
| 40 |
#' @typed omega_g: Prior |
|
| 41 |
#' for the population standard deviation for the growth rate. |
|
| 42 |
#' @typed omega_c: Prior |
|
| 43 |
#' for the population standard deviation for the resistance rate. |
|
| 44 |
#' @typed omega_p: Prior |
|
| 45 |
#' for the population standard deviation for the growth inhibition. |
|
| 46 |
#' |
|
| 47 |
#' @typed sigma: Prior |
|
| 48 |
#' for the variance of the longitudinal values. |
|
| 49 |
#' |
|
| 50 |
#' @typed centred: logical |
|
| 51 |
#' whether to use the centred parameterization. |
|
| 52 |
#' @typed scaled_variance: logical |
|
| 53 |
#' whether the variance should be scaled by the expected value, corresponding to a multiplicative model. |
|
| 54 |
#' As a default, the variance is not scaled by the expected value, corresponding to an additive model. |
|
| 55 |
#' (See the "Statistical Specifications" vignette for more details.) |
|
| 56 |
#' |
|
| 57 |
#' @export |
|
| 58 |
#' |
|
| 59 |
#' @returns A `LongitudinalClaretBruno` object. |
|
| 60 |
#' |
|
| 61 |
#' @examples |
|
| 62 |
#' LongitudinalClaretBruno() |
|
| 63 |
LongitudinalClaretBruno <- function( |
|
| 64 |
mu_b = prior_normal(log(60), 0.5), |
|
| 65 |
mu_g = prior_normal(log(1), 0.5), |
|
| 66 |
mu_c = prior_normal(log(0.4), 0.5), |
|
| 67 |
mu_p = prior_normal(log(2), 0.5), |
|
| 68 | ||
| 69 |
omega_b = prior_lognormal(log(0.2), 0.5), |
|
| 70 |
omega_g = prior_lognormal(log(0.2), 0.5), |
|
| 71 |
omega_c = prior_lognormal(log(0.2), 0.5), |
|
| 72 |
omega_p = prior_lognormal(log(0.2), 0.5), |
|
| 73 | ||
| 74 |
sigma = prior_lognormal(log(0.1), 0.5), |
|
| 75 | ||
| 76 |
scaled_variance = FALSE, |
|
| 77 |
centred = FALSE |
|
| 78 |
) {
|
|
| 79 | 17x |
sf_model <- StanModule(decorated_render( |
| 80 | 17x |
.x = read_stan("lm-claret-bruno/model.stan"),
|
| 81 | 17x |
scaled_variance = scaled_variance, |
| 82 | 17x |
centred = centred |
| 83 |
)) |
|
| 84 | ||
| 85 |
# Apply constraints |
|
| 86 | 17x |
omega_b <- set_limits(omega_b, lower = getOption("jmpost.double_eps"))
|
| 87 | 17x |
omega_g <- set_limits(omega_g, lower = getOption("jmpost.double_eps"))
|
| 88 | 17x |
omega_c <- set_limits(omega_c, lower = getOption("jmpost.double_eps"))
|
| 89 | 17x |
omega_p <- set_limits(omega_p, lower = getOption("jmpost.double_eps"))
|
| 90 | 17x |
sigma <- set_limits(sigma, lower = getOption("jmpost.double_eps"))
|
| 91 | ||
| 92 | 17x |
parameters <- list( |
| 93 | 17x |
Parameter(name = "lm_clbr_mu_b", prior = mu_b, size = "n_studies"), |
| 94 | 17x |
Parameter(name = "lm_clbr_mu_g", prior = mu_g, size = "n_arms"), |
| 95 | 17x |
Parameter(name = "lm_clbr_mu_c", prior = mu_c, size = "n_arms"), |
| 96 | 17x |
Parameter(name = "lm_clbr_mu_p", prior = mu_p, size = "n_arms"), |
| 97 | ||
| 98 | 17x |
Parameter( |
| 99 | 17x |
name = "lm_clbr_omega_b", |
| 100 | 17x |
prior = omega_b, |
| 101 | 17x |
size = "n_studies" |
| 102 |
), |
|
| 103 | 17x |
Parameter(name = "lm_clbr_omega_g", prior = omega_g, size = "n_arms"), |
| 104 | 17x |
Parameter(name = "lm_clbr_omega_c", prior = omega_c, size = "n_arms"), |
| 105 | 17x |
Parameter(name = "lm_clbr_omega_p", prior = omega_p, size = "n_arms"), |
| 106 | ||
| 107 | 17x |
Parameter(name = "lm_clbr_sigma", prior = sigma, size = 1) |
| 108 |
) |
|
| 109 | ||
| 110 | 17x |
assert_flag(centred) |
| 111 | 17x |
parameters_extra <- if (centred) {
|
| 112 | 2x |
list( |
| 113 | 2x |
Parameter( |
| 114 | 2x |
name = "lm_clbr_ind_b", |
| 115 | 2x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 116 | 2x |
median(mu_b), |
| 117 | 2x |
median(omega_b) |
| 118 | 2x |
)), lower = getOption("jmpost.double_eps")),
|
| 119 | 2x |
size = "n_subjects" |
| 120 |
), |
|
| 121 | 2x |
Parameter( |
| 122 | 2x |
name = "lm_clbr_ind_g", |
| 123 | 2x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 124 | 2x |
median(mu_g), |
| 125 | 2x |
median(omega_g) |
| 126 | 2x |
)), lower = getOption("jmpost.double_eps")),
|
| 127 | 2x |
size = "n_subjects" |
| 128 |
), |
|
| 129 | 2x |
Parameter( |
| 130 | 2x |
name = "lm_clbr_ind_c", |
| 131 | 2x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 132 | 2x |
median(mu_c), |
| 133 | 2x |
median(omega_c) |
| 134 | 2x |
)), lower = getOption("jmpost.double_eps")),
|
| 135 | 2x |
size = "n_subjects" |
| 136 |
), |
|
| 137 | 2x |
Parameter( |
| 138 | 2x |
name = "lm_clbr_ind_p", |
| 139 | 2x |
prior = set_limits(prior_init_only(prior_lognormal( |
| 140 | 2x |
median(mu_p), |
| 141 | 2x |
median(omega_p) |
| 142 | 2x |
)), lower = getOption("jmpost.double_eps")),
|
| 143 | 2x |
size = "n_subjects" |
| 144 |
) |
|
| 145 |
) |
|
| 146 |
} else {
|
|
| 147 | 15x |
list( |
| 148 | 15x |
Parameter( |
| 149 | 15x |
name = "lm_clbr_eta_b", |
| 150 | 15x |
prior = prior_std_normal(), |
| 151 | 15x |
size = "n_subjects" |
| 152 |
), |
|
| 153 | 15x |
Parameter( |
| 154 | 15x |
name = "lm_clbr_eta_g", |
| 155 | 15x |
prior = prior_std_normal(), |
| 156 | 15x |
size = "n_subjects" |
| 157 |
), |
|
| 158 | 15x |
Parameter( |
| 159 | 15x |
name = "lm_clbr_eta_c", |
| 160 | 15x |
prior = prior_std_normal(), |
| 161 | 15x |
size = "n_subjects" |
| 162 |
), |
|
| 163 | 15x |
Parameter( |
| 164 | 15x |
name = "lm_clbr_eta_p", |
| 165 | 15x |
prior = prior_std_normal(), |
| 166 | 15x |
size = "n_subjects" |
| 167 |
) |
|
| 168 |
) |
|
| 169 |
} |
|
| 170 | 17x |
parameters <- append(parameters, parameters_extra) |
| 171 | ||
| 172 | 17x |
x <- LongitudinalModel( |
| 173 | 17x |
name = "Claret-Bruno", |
| 174 | 17x |
stan = merge( |
| 175 | 17x |
sf_model, |
| 176 | 17x |
StanModule("lm-claret-bruno/functions.stan")
|
| 177 |
), |
|
| 178 | 17x |
parameters = do.call(ParameterList, parameters), |
| 179 | 17x |
scaled_variance = scaled_variance |
| 180 |
) |
|
| 181 | 17x |
.LongitudinalClaretBruno(x) |
| 182 |
} |
|
| 183 | ||
| 184 | ||
| 185 |
#' @export |
|
| 186 |
#' |
|
| 187 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 188 |
enableLink.LongitudinalClaretBruno <- function(object, ...) {
|
|
| 189 | 2x |
object@stan <- merge( |
| 190 | 2x |
object@stan, |
| 191 | 2x |
StanModule("lm-claret-bruno/link.stan")
|
| 192 |
) |
|
| 193 | 2x |
object |
| 194 |
} |
|
| 195 | ||
| 196 |
#' @export |
|
| 197 |
#' |
|
| 198 |
#' @returns A `LinkComponent` object. |
|
| 199 |
linkDSLD.LongitudinalClaretBruno <- function( |
|
| 200 |
prior = prior_normal(0, 2), |
|
| 201 |
model, |
|
| 202 |
... |
|
| 203 |
) {
|
|
| 204 | 2x |
LinkComponent( |
| 205 | 2x |
key = "link_dsld", |
| 206 | 2x |
stan = StanModule("lm-claret-bruno/link_dsld.stan"),
|
| 207 | 2x |
prior = prior |
| 208 |
) |
|
| 209 |
} |
|
| 210 | ||
| 211 |
#' @export |
|
| 212 |
#' |
|
| 213 |
#' @returns A `LinkComponent` object. |
|
| 214 |
linkTTG.LongitudinalClaretBruno <- function( |
|
| 215 |
prior = prior_normal(0, 2), |
|
| 216 |
model, |
|
| 217 |
... |
|
| 218 |
) {
|
|
| 219 | 1x |
LinkComponent( |
| 220 | 1x |
key = "link_ttg", |
| 221 | 1x |
stan = StanModule("lm-claret-bruno/link_ttg.stan"),
|
| 222 | 1x |
prior = prior |
| 223 |
) |
|
| 224 |
} |
|
| 225 | ||
| 226 |
#' @export |
|
| 227 |
#' |
|
| 228 |
#' @returns A `LinkComponent` object. |
|
| 229 |
linkIdentity.LongitudinalClaretBruno <- function( |
|
| 230 |
prior = prior_normal(0, 2), |
|
| 231 |
model, |
|
| 232 |
... |
|
| 233 |
) {
|
|
| 234 | ! |
LinkComponent( |
| 235 | ! |
key = "link_identity", |
| 236 | ! |
stan = StanModule("lm-claret-bruno/link_identity.stan"),
|
| 237 | ! |
prior = prior |
| 238 |
) |
|
| 239 |
} |
|
| 240 | ||
| 241 |
#' @export |
|
| 242 |
#' |
|
| 243 |
#' @returns A `LinkComponent` object. |
|
| 244 |
linkGrowth.LongitudinalClaretBruno <- function( |
|
| 245 |
prior = prior_normal(0, 2), |
|
| 246 |
model, |
|
| 247 |
... |
|
| 248 |
) {
|
|
| 249 | 1x |
LinkComponent( |
| 250 | 1x |
key = "link_growth", |
| 251 | 1x |
stan = StanModule("lm-claret-bruno/link_growth.stan"),
|
| 252 | 1x |
prior = prior |
| 253 |
) |
|
| 254 |
} |
|
| 255 | ||
| 256 |
#' @rdname getPredictionNames |
|
| 257 |
#' @export |
|
| 258 |
getPredictionNames.LongitudinalClaretBruno <- function(object, ...) {
|
|
| 259 | ! |
c("b", "g", "c", "p")
|
| 260 |
} |
|
| 261 | ||
| 262 |
#' @export |
|
| 263 |
#' |
|
| 264 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 265 |
enableGQ.LongitudinalClaretBruno <- function(object, ...) {
|
|
| 266 | 2x |
StanModule("lm-claret-bruno/quantities.stan")
|
| 267 |
} |
|
| 268 | ||
| 269 | ||
| 270 |
#' @rdname getRandomEffectsNames |
|
| 271 |
#' @export |
|
| 272 |
getRandomEffectsNames.LongitudinalGSF <- function(object, ...) {
|
|
| 273 |
c( |
|
| 274 |
"b" = "lm_clbr_ind_b", |
|
| 275 |
"g" = "lm_clbr_ind_g", |
|
| 276 |
"c" = "lm_clbr_ind_c", |
|
| 277 |
"p" = "lm_clbr_ind_p" |
|
| 278 |
) |
|
| 279 |
} |
| 1 |
#' @include DataJoint.R |
|
| 2 |
#' @include Quantities.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' Re-used documentation for `LongitudinalQuantities` |
|
| 7 |
#' |
|
| 8 |
#' @typed x: LongitudinalQuantities |
|
| 9 |
#' longitudinal quantities. |
|
| 10 |
#' @typed object: LongitudinalQuantities |
|
| 11 |
#' longitudinal quantities. |
|
| 12 |
#' @param ... not used. |
|
| 13 |
#' |
|
| 14 |
#' @keywords internal |
|
| 15 |
#' @name LongitudinalQuantities-Shared |
|
| 16 |
NULL |
|
| 17 | ||
| 18 | ||
| 19 |
#' `LongitudinalQuantities` Object & Constructor Function |
|
| 20 |
#' |
|
| 21 |
#' Constructor function to generate a `LongitudinalQuantities` object. |
|
| 22 |
#' |
|
| 23 |
#' @details |
|
| 24 |
#' Note that unlike [`SurvivalQuantities`], [`LongitudinalQuantities`] does not support |
|
| 25 |
#' group aggregation. |
|
| 26 |
#' |
|
| 27 |
#' @slot quantities (`Quantities`)\cr The sampled quantities. Should contain 1 element per |
|
| 28 |
#' element of `group` |
|
| 29 |
#' |
|
| 30 |
#' @slot data (`DataJoint`)\cr Survival and Longitudinal Data. |
|
| 31 |
#' |
|
| 32 |
#' @family LongitudinalQuantities |
|
| 33 |
#' @name LongitudinalQuantities-class |
|
| 34 |
#' @export LongitudinalQuantities |
|
| 35 |
#' |
|
| 36 |
#' @examples |
|
| 37 |
#' \dontrun{
|
|
| 38 |
#' LongitudinalQuantities(fit, GridEven(length.out = 25)) |
|
| 39 |
#' } |
|
| 40 |
.LongitudinalQuantities <- setClass( |
|
| 41 |
"LongitudinalQuantities", |
|
| 42 |
slots = c( |
|
| 43 |
"quantities" = "Quantities", |
|
| 44 |
"data" = "DataJoint" |
|
| 45 |
) |
|
| 46 |
) |
|
| 47 | ||
| 48 |
#' @typed object: JointModelSamples |
|
| 49 |
#' samples as drawn from a Joint Model. |
|
| 50 |
#' |
|
| 51 |
#' @typed grid: Grid |
|
| 52 |
#' object that specifies which subjects and time points to calculate the |
|
| 53 |
#' quantities for. See [Grid-Functions]. |
|
| 54 |
#' @rdname LongitudinalQuantities-class |
|
| 55 |
#' |
|
| 56 |
#' @returns A `LongitudinalQuantities` object. |
|
| 57 |
LongitudinalQuantities <- function( |
|
| 58 |
object, |
|
| 59 |
grid |
|
| 60 |
) {
|
|
| 61 | 14x |
assert_class(object, "JointModelSamples") |
| 62 | 14x |
assert_class(grid, "Grid") |
| 63 | 14x |
assert_that( |
| 64 | 14x |
!is(grid, "GridPrediction"), |
| 65 | 14x |
msg = "`GridPrediction` objects are not supported for `LongitudinalQuantities`" |
| 66 |
) |
|
| 67 | ||
| 68 | 14x |
time_grid <- seq( |
| 69 | 14x |
from = 0, |
| 70 | 14x |
to = max(as.list(object@data)[["tumour_time"]]), |
| 71 | 14x |
length = 201 |
| 72 |
) |
|
| 73 | ||
| 74 | 14x |
grid <- coalesceGridTime(grid, time_grid) |
| 75 | ||
| 76 | 14x |
gq <- generateQuantities( |
| 77 | 14x |
object, |
| 78 | 14x |
generator = as.QuantityGenerator( |
| 79 | 14x |
grid, |
| 80 | 14x |
object@data, |
| 81 | 14x |
model = object@model |
| 82 |
), |
|
| 83 | 14x |
type = "longitudinal" |
| 84 |
) |
|
| 85 | ||
| 86 | 14x |
quantities_raw <- extract_quantities(gq, type = "lm_identity") |
| 87 | ||
| 88 | 14x |
collapser <- as.QuantityCollapser( |
| 89 | 14x |
grid, |
| 90 | 14x |
object@data, |
| 91 | 14x |
model = object@model |
| 92 |
) |
|
| 93 | 14x |
quantities <- collapse_quantities(quantities_raw, collapser) |
| 94 | ||
| 95 | 14x |
.LongitudinalQuantities( |
| 96 | 14x |
quantities = Quantities( |
| 97 | 14x |
quantities, |
| 98 | 14x |
groups = collapser@groups, |
| 99 | 14x |
times = collapser@times |
| 100 |
), |
|
| 101 | 14x |
data = object@data |
| 102 |
) |
|
| 103 |
} |
|
| 104 | ||
| 105 | ||
| 106 |
#' `as.data.frame` |
|
| 107 |
#' |
|
| 108 |
#' @typed x: LongitudinalQuantities |
|
| 109 |
#' longitudinal quantities. |
|
| 110 |
#' @param ... not used. |
|
| 111 |
#' @family LongitudinalQuantities |
|
| 112 |
#' @export |
|
| 113 |
#' |
|
| 114 |
#' @returns A `data.frame` representation of the object. |
|
| 115 |
as.data.frame.LongitudinalQuantities <- function(x, ...) {
|
|
| 116 | 1x |
as.data.frame(x@quantities) |
| 117 |
} |
|
| 118 | ||
| 119 | ||
| 120 |
#' summary |
|
| 121 |
#' |
|
| 122 |
#' @description |
|
| 123 |
#' This method returns a `data.frame` of the longitudinal quantities. |
|
| 124 |
#' |
|
| 125 |
#' @typed conf.level: numeric |
|
| 126 |
#' confidence level of the interval. |
|
| 127 |
#' @inheritParams LongitudinalQuantities-Shared |
|
| 128 |
#' |
|
| 129 |
#' @family LongitudinalQuantities |
|
| 130 |
#' @family summary |
|
| 131 |
#' @export |
|
| 132 |
#' |
|
| 133 |
#' @returns A `data.frame` containing posterior summary statistics. |
|
| 134 |
summary.LongitudinalQuantities <- function( |
|
| 135 |
object, |
|
| 136 |
conf.level = 0.95, |
|
| 137 |
... |
|
| 138 |
) {
|
|
| 139 | 12x |
summary(object@quantities, conf.level = conf.level) |
| 140 |
} |
|
| 141 | ||
| 142 | ||
| 143 |
#' Longitudinal Plot |
|
| 144 |
#' |
|
| 145 |
#' Internal plotting function to create longitudinal plots |
|
| 146 |
#' This function predominately exists to extract core logic into its own function |
|
| 147 |
#' to enable easier unit testing. |
|
| 148 |
#' |
|
| 149 |
#' @typed data: data.frame |
|
| 150 |
#' summary statistics for longitudinal |
|
| 151 |
#' value estimates. See details. |
|
| 152 |
#' @typed data_obs: data.frame |
|
| 153 |
#' real observed values to be |
|
| 154 |
#' overlaid for reference. See details. |
|
| 155 |
#' @typed add_ci: logical |
|
| 156 |
#' Should confidence intervals be added? Default = `TRUE`. |
|
| 157 |
#' @details |
|
| 158 |
#' |
|
| 159 |
#' ## `data` |
|
| 160 |
#' Should contain the following columns: |
|
| 161 |
#' - `time` (`numeric`) \cr time point for the summary statistic. |
|
| 162 |
#' - `group` (`character`) \cr the group in which the observation belongs to. |
|
| 163 |
#' - `median` (`numeric`) \cr the median value for the summary statistic. |
|
| 164 |
#' - `upper` (`numeric`) \cr the upper 95% CI for the summary statistic. |
|
| 165 |
#' - `lower` (`numeric`) \cr the lower 95% CI for the summary statistic. |
|
| 166 |
#' |
|
| 167 |
#' ## `data_obs` |
|
| 168 |
#' Should contain the following columns: |
|
| 169 |
#' - `time` (`numeric`) \cr the time at which the observed value occurred. |
|
| 170 |
#' - `Yob` (`numeric`) \cr the real observed value. |
|
| 171 |
#' - `group` (`character`) \cr which group the event belongs to, should correspond to |
|
| 172 |
#' values in `data$group`. |
|
| 173 |
#' @keywords internal |
|
| 174 |
#' |
|
| 175 |
#' @returns A `ggplot` object. |
|
| 176 |
longitudinal_plot <- function( |
|
| 177 |
data, |
|
| 178 |
data_obs = NULL, |
|
| 179 |
add_ci = FALSE |
|
| 180 |
) {
|
|
| 181 | 4x |
p <- ggplot() + |
| 182 | 4x |
geom_line(aes(x = .data$time, y = .data$median), data = data) + |
| 183 | 4x |
xlab(expression(t)) + |
| 184 | 4x |
ylab(expression(y)) + |
| 185 | 4x |
facet_wrap(~group) + |
| 186 | 4x |
theme_bw() |
| 187 | ||
| 188 | 4x |
if (add_ci) {
|
| 189 | 2x |
p <- p + |
| 190 | 2x |
geom_ribbon( |
| 191 | 2x |
aes(x = .data$time, ymin = .data$lower, ymax = .data$upper), |
| 192 | 2x |
data = data, |
| 193 | 2x |
alpha = 0.3 |
| 194 |
) |
|
| 195 |
} |
|
| 196 | ||
| 197 | 4x |
if (!is.null(data_obs)) {
|
| 198 | 4x |
p <- p + geom_point(aes(x = .data$time, y = .data$Yob), data = data_obs) |
| 199 |
} |
|
| 200 | 4x |
return(p) |
| 201 |
} |
|
| 202 | ||
| 203 | ||
| 204 |
#' Automatic Plotting for `LongitudinalQuantities` |
|
| 205 |
#' |
|
| 206 |
#' @typed conf.level: numeric |
|
| 207 |
#' confidence level of the interval. If values of `FALSE`, |
|
| 208 |
#' `NULL` or `0` are provided then confidence regions will not be added to the plot. |
|
| 209 |
#' @inheritParams LongitudinalQuantities-Shared |
|
| 210 |
#' |
|
| 211 |
#' @family LongitudinalQuantities |
|
| 212 |
#' @family autoplot |
|
| 213 |
#' @export |
|
| 214 |
#' |
|
| 215 |
#' @returns A `ggplot` object. |
|
| 216 |
autoplot.LongitudinalQuantities <- function(object, conf.level = 0.95, ...) {
|
|
| 217 | 2x |
include_ci <- !is.null(conf.level) && |
| 218 | 2x |
is.numeric(conf.level) && |
| 219 | 2x |
conf.level > 0 |
| 220 |
# If CI aren't needed supply a default 0.95 to summary function as it needs |
|
| 221 |
# a value to be specified to work. |
|
| 222 | 2x |
conf.level <- if (include_ci) conf.level else 0.95 |
| 223 | 2x |
data_sum <- summary(object, conf.level = conf.level) |
| 224 | 2x |
data_obs <- extract_observed_values(object@data) |
| 225 | 2x |
assert_that( |
| 226 | 2x |
"group" %in% names(data_sum), |
| 227 | 2x |
"subject" %in% names(data_obs) |
| 228 |
) |
|
| 229 | 2x |
data_obs$group <- data_obs$subject |
| 230 | 2x |
data_obs <- data_obs[data_obs$group %in% data_sum$group, ] |
| 231 | 2x |
longitudinal_plot( |
| 232 | 2x |
data = data_sum, |
| 233 | 2x |
data_obs = data_obs, |
| 234 | 2x |
add_ci = include_ci |
| 235 |
) |
|
| 236 |
} |
|
| 237 | ||
| 238 | ||
| 239 |
#' @rdname show-object |
|
| 240 |
#' @export |
|
| 241 |
setMethod( |
|
| 242 |
f = "show", |
|
| 243 |
signature = "LongitudinalQuantities", |
|
| 244 |
definition = function(object) {
|
|
| 245 | 2x |
template <- c( |
| 246 | 2x |
"LongitudinalQuantities Object:", |
| 247 | 2x |
" # of samples = %d", |
| 248 | 2x |
" # of quantities = %d" |
| 249 |
) |
|
| 250 | 2x |
string <- sprintf( |
| 251 | 2x |
paste(template, collapse = "\n"), |
| 252 | 2x |
nrow(object@quantities), |
| 253 | 2x |
ncol(object@quantities) |
| 254 |
) |
|
| 255 | 2x |
cat("\n", string, "\n\n")
|
| 256 |
} |
|
| 257 |
) |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include Grid.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' @rdname Quant-Dev |
|
| 6 |
.QuantityGeneratorPrediction <- setClass( |
|
| 7 |
"QuantityGeneratorPrediction", |
|
| 8 |
contains = "QuantityGenerator", |
|
| 9 |
slots = c( |
|
| 10 |
"times" = "numeric", |
|
| 11 |
"newdata" = "data.frame", |
|
| 12 |
"params" = "list" |
|
| 13 |
) |
|
| 14 |
) |
|
| 15 | ||
| 16 | ||
| 17 |
#' @rdname Quant-Dev |
|
| 18 |
QuantityGeneratorPrediction <- function(times, newdata = NULL, params = NULL) {
|
|
| 19 | 7x |
.QuantityGeneratorPrediction( |
| 20 | 7x |
times = times, |
| 21 | 7x |
newdata = newdata, |
| 22 | 7x |
params = params |
| 23 |
) |
|
| 24 |
} |
|
| 25 |
setValidity( |
|
| 26 |
"QuantityGeneratorPrediction", |
|
| 27 |
function(object) {
|
|
| 28 |
if (length(object@times) != nrow(object@newdata)) {
|
|
| 29 |
return("Length of `times` and `newdata` must be equal")
|
|
| 30 |
} |
|
| 31 |
return(TRUE) |
|
| 32 |
} |
|
| 33 |
) |
|
| 34 | ||
| 35 | ||
| 36 |
#' @rdname as_stan_list.QuantityGenerator |
|
| 37 |
#' @export |
|
| 38 |
as_stan_list.QuantityGeneratorPrediction <- function(object, data, model, ...) {
|
|
| 39 | 3x |
assert_that( |
| 40 | 3x |
is(data, "DataJoint") |
| 41 |
) |
|
| 42 | 3x |
ret <- list() |
| 43 | 3x |
data_list <- as_stan_list(data) |
| 44 | ||
| 45 | 3x |
ret[["gq_times"]] <- object@times |
| 46 | 3x |
ret[["gq_n_quant"]] <- length(object@times) |
| 47 | ||
| 48 |
# Get a list of which longutidunal parameters need to be defined based |
|
| 49 |
# on the selected longitudinal model |
|
| 50 | 3x |
par_names <- getPredictionNames(model@longitudinal) |
| 51 | 3x |
for (nam in par_names) {
|
| 52 | 8x |
assert_that( |
| 53 | 8x |
nam %in% names(object@params), |
| 54 | 8x |
msg = sprintf("Parameter '%s' not found in `params`", nam)
|
| 55 |
) |
|
| 56 | 8x |
assert_that( |
| 57 | 8x |
is.numeric(object@params[[nam]]), |
| 58 | 8x |
msg = sprintf("Parameter '%s' must be numeric", nam)
|
| 59 |
) |
|
| 60 | 8x |
assert_that( |
| 61 | 8x |
length(object@params[[nam]]) == 1, |
| 62 | 8x |
msg = sprintf("Parameter '%s' must be length 1", nam)
|
| 63 |
) |
|
| 64 |
} |
|
| 65 | ||
| 66 | 3x |
par_vals <- object@params[par_names] |
| 67 | 3x |
if (length(par_vals) == 0) {
|
| 68 | 1x |
par_vals <- 0 |
| 69 | 1x |
par_names <- "null_model" |
| 70 |
} |
|
| 71 | ||
| 72 | 3x |
ret[["gq_n_par"]] <- length(par_names) |
| 73 | ||
| 74 |
# Replicate the longitudinal parameters so the same parameter values are used |
|
| 75 |
# for all observations that are being predicted |
|
| 76 | 3x |
ret[["gq_link_function_inputs"]] <- matrix( |
| 77 | 3x |
rep(unlist(par_vals), each = ret[["gq_n_quant"]]), |
| 78 | 3x |
ncol = ret[["gq_n_par"]], |
| 79 | 3x |
nrow = ret[["gq_n_quant"]] |
| 80 |
) |
|
| 81 | ||
| 82 |
# Create design matrix from new data ensuring that it has the same |
|
| 83 |
# structure as the original design matrix |
|
| 84 | 3x |
ret[["gq_os_cov_design"]] <- mirror_design_matrix( |
| 85 | 3x |
data@survival, |
| 86 | 3x |
object@newdata |
| 87 |
) |
|
| 88 | ||
| 89 |
# dummy pop indexes in order for stan code to actualy compile. In this setting |
|
| 90 |
# this matrix isn't actually used so doesn't matter what these values are |
|
| 91 |
# but don't want to have to burden individual longitudinal models with the |
|
| 92 |
# conditional logic to check if they are generating population quantities or not |
|
| 93 | 2x |
ret[["gq_long_pop_arm_index"]] <- rep(1, ret[["gq_n_quant"]]) |
| 94 | 2x |
ret[["gq_long_pop_study_index"]] <- rep(1, ret[["gq_n_quant"]]) |
| 95 | ||
| 96 |
# Sanity checks |
|
| 97 | 2x |
assert_that( |
| 98 | 2x |
nrow(ret[["gq_os_cov_design"]]) == ret[["gq_n_quant"]], |
| 99 | 2x |
ncol(ret[["gq_os_cov_design"]]) == data_list[["p_os_cov_design"]], |
| 100 | 2x |
all(!is.na(ret[["gq_link_function_inputs"]])) |
| 101 |
) |
|
| 102 | ||
| 103 | 2x |
return(ret) |
| 104 |
} |
| 1 |
.onAttach <- function(libname, pkgname) {
|
|
| 2 | 4x |
if (!is_cmdstanr_available()) {
|
| 3 | ! |
packageStartupMessage( |
| 4 | ! |
"jmpost uses cmdstanr for compiling and sampling from models, but it does not seem to be installed.\n", |
| 5 | ! |
"To install:\n", |
| 6 | ! |
"install.packages(\"cmdstanr\", repos = c(\"https://stan-dev.r-universe.dev/\", getOption(\"repos\")))" |
| 7 |
) |
|
| 8 | 4x |
} else if (is.null(cmdstanr::cmdstan_version(error_on_NA = FALSE))) {
|
| 9 | ! |
possible_paths <- unique(c( |
| 10 | ! |
cmdstanr::cmdstan_default_install_path(), |
| 11 | ! |
Sys.getenv("CMDSTAN"),
|
| 12 | ! |
Sys.getenv("CMDSTAN_PATH"),
|
| 13 | ! |
"/root/.cmdstan", |
| 14 | ! |
"~/.cmdstan" |
| 15 |
)) |
|
| 16 | ! |
possible_paths <- possible_paths[dir.exists(possible_paths)] |
| 17 | ||
| 18 | ! |
if (length(possible_paths)) {
|
| 19 | ! |
for (try_path in possible_paths) {
|
| 20 | ! |
new_path <- tryCatch( |
| 21 | ! |
suppressMessages(cmdstanr::set_cmdstan_path(try_path)), |
| 22 | ! |
warning = function(w) NULL, |
| 23 | ! |
error = function(e) NULL |
| 24 |
) |
|
| 25 |
} |
|
| 26 | ! |
if (!is.null(new_path)) {
|
| 27 | ! |
packageStartupMessage("CmdStan path set to: ", new_path)
|
| 28 |
} |
|
| 29 |
} else {
|
|
| 30 | ! |
packageStartupMessage( |
| 31 | ! |
"jmpost could not identify CmdStan path. Please use cmdstanr::set_cmdstan_path()" |
| 32 |
) |
|
| 33 |
} |
|
| 34 |
} |
|
| 35 | 4x |
invisible() |
| 36 |
} |
|
| 37 | ||
| 38 |
.onLoad <- function(...) {
|
|
| 39 | ! |
set_options() |
| 40 | ! |
s3_register("cmdstanr::as.CmdStanMCMC", "JointModelSamples")
|
| 41 |
} |
|
| 42 | ||
| 43 |
# This only exists to silence the false positive R CMD CHECK warning about |
|
| 44 |
# importing but not using the posterior package. posterior is a dependency |
|
| 45 |
# of rcmdstan that we use a lot implicitly. Also we link to their documentation |
|
| 46 |
# pages in ours |
|
| 47 |
.never_run <- function() {
|
|
| 48 | ! |
posterior::as_draws() |
| 49 |
} |
| 1 |
#' @include SimLongitudinalRandomSlope.R |
|
| 2 |
#' @include LongitudinalRandomSlopeCov.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Simulate a random-slope longitudinal model with covariates |
|
| 6 |
#' |
|
| 7 |
#' @exportClass SimLongitudinalRandomSlopeCov |
|
| 8 |
.SimLongitudinalRandomSlopeCov <- setClass( |
|
| 9 |
"SimLongitudinalRandomSlopeCov", |
|
| 10 |
contains = "SimLongitudinal", |
|
| 11 |
slots = c( |
|
| 12 |
mu_formula = "formula", |
|
| 13 |
slope_mu_formula = "formula", |
|
| 14 |
slope_sigma_formula = "formula", |
|
| 15 |
mu_parametrization = "character", |
|
| 16 |
slope_mu_parametrization = "character", |
|
| 17 |
slope_sigma_parametrization = "character", |
|
| 18 |
mu_intercept = "numeric", |
|
| 19 |
mu_coefficients = "numeric", |
|
| 20 |
slope_mu_intercept = "numeric", |
|
| 21 |
slope_mu_coefficients = "numeric", |
|
| 22 |
slope_sigma_intercept = "numeric", |
|
| 23 |
slope_sigma_coefficients = "numeric", |
|
| 24 |
sigma = "numeric", |
|
| 25 |
link_dsld = "numeric", |
|
| 26 |
link_identity = "numeric", |
|
| 27 |
scaled_variance = "logical" |
|
| 28 |
) |
|
| 29 |
) |
|
| 30 | ||
| 31 |
#' Construct a random-slope simulator with subject-level covariates |
|
| 32 |
#' |
|
| 33 |
#' @inheritParams LongitudinalRandomSlopeCov |
|
| 34 |
#' @typed times: numeric |
|
| 35 |
#' Observation times. |
|
| 36 |
#' @typed mu_intercept: numeric |
|
| 37 |
#' Intercept for the subject intercept predictor. |
|
| 38 |
#' @typed mu_coefficients: numeric |
|
| 39 |
#' Covariate coefficients for the subject intercept predictor. |
|
| 40 |
#' @typed slope_mu_intercept: numeric |
|
| 41 |
#' Intercept for the mean slope predictor. |
|
| 42 |
#' @typed slope_mu_coefficients: numeric |
|
| 43 |
#' Covariate coefficients for the mean slope predictor. |
|
| 44 |
#' @typed slope_sigma_intercept: numeric |
|
| 45 |
#' Intercept for the slope standard deviation predictor. |
|
| 46 |
#' @typed slope_sigma_coefficients: numeric |
|
| 47 |
#' Covariate coefficients for the slope standard deviation predictor. |
|
| 48 |
#' @typed sigma: numeric |
|
| 49 |
#' Observation-error standard deviation. |
|
| 50 |
#' @typed link_dsld: numeric |
|
| 51 |
#' Coefficient for the DSLD association link. |
|
| 52 |
#' @typed link_identity: numeric |
|
| 53 |
#' Coefficient for the identity association link. |
|
| 54 |
#' |
|
| 55 |
#' @rdname SimLongitudinalRandomSlopeCov-class |
|
| 56 |
#' @returns A `SimLongitudinalRandomSlopeCov` object. |
|
| 57 |
#' @export |
|
| 58 |
SimLongitudinalRandomSlopeCov <- function( |
|
| 59 |
times = c(-100, -50, 0, 50, 100, 150, 250, 350, 450, 550), |
|
| 60 |
mu_formula = ~study, |
|
| 61 |
slope_mu_formula = ~arm, |
|
| 62 |
slope_sigma_formula = ~arm, |
|
| 63 |
mu_parametrization = "linear", |
|
| 64 |
slope_mu_parametrization = "linear", |
|
| 65 |
slope_sigma_parametrization = "log-linear", |
|
| 66 |
mu_intercept = 50, |
|
| 67 |
mu_coefficients = numeric(), |
|
| 68 |
slope_mu_intercept = 0.01, |
|
| 69 |
slope_mu_coefficients = numeric(), |
|
| 70 |
slope_sigma_intercept = log(0.5), |
|
| 71 |
slope_sigma_coefficients = numeric(), |
|
| 72 |
sigma = 2, |
|
| 73 |
link_dsld = 0, |
|
| 74 |
link_identity = 0, |
|
| 75 |
scaled_variance = FALSE |
|
| 76 |
) {
|
|
| 77 | 9x |
.SimLongitudinalRandomSlopeCov( |
| 78 | 9x |
times = times, |
| 79 | 9x |
mu_formula = .validate_covariate_formula(mu_formula, "mu_formula"), |
| 80 | 9x |
slope_mu_formula = .validate_covariate_formula( |
| 81 | 9x |
slope_mu_formula, |
| 82 | 9x |
"slope_mu_formula" |
| 83 |
), |
|
| 84 | 9x |
slope_sigma_formula = .validate_covariate_formula( |
| 85 | 9x |
slope_sigma_formula, |
| 86 | 9x |
"slope_sigma_formula" |
| 87 |
), |
|
| 88 | 9x |
mu_parametrization = .validate_covariate_parametrization( |
| 89 | 9x |
mu_parametrization, |
| 90 | 9x |
"mu_parametrization" |
| 91 |
), |
|
| 92 | 9x |
slope_mu_parametrization = .validate_covariate_parametrization( |
| 93 | 9x |
slope_mu_parametrization, |
| 94 | 9x |
"slope_mu_parametrization" |
| 95 |
), |
|
| 96 | 9x |
slope_sigma_parametrization = .validate_covariate_parametrization( |
| 97 | 9x |
slope_sigma_parametrization, |
| 98 | 9x |
"slope_sigma_parametrization", |
| 99 | 9x |
scale = TRUE |
| 100 |
), |
|
| 101 | 9x |
mu_intercept = mu_intercept, |
| 102 | 9x |
mu_coefficients = mu_coefficients, |
| 103 | 9x |
slope_mu_intercept = slope_mu_intercept, |
| 104 | 9x |
slope_mu_coefficients = slope_mu_coefficients, |
| 105 | 9x |
slope_sigma_intercept = slope_sigma_intercept, |
| 106 | 9x |
slope_sigma_coefficients = slope_sigma_coefficients, |
| 107 | 9x |
sigma = sigma, |
| 108 | 9x |
link_dsld = link_dsld, |
| 109 | 9x |
link_identity = link_identity, |
| 110 | 9x |
scaled_variance = scaled_variance |
| 111 |
) |
|
| 112 |
} |
|
| 113 | ||
| 114 |
#' Normalise simulation coefficients to a design matrix |
|
| 115 |
#' |
|
| 116 |
#' An empty vector represents zero coefficients. For convenience, a full set of |
|
| 117 |
#' dummy coefficients may be supplied when its reference-level coefficient is |
|
| 118 |
#' zero; that leading zero is removed to match reference coding. |
|
| 119 |
#' |
|
| 120 |
#' @param coefficients Numeric vector of simulation coefficients. |
|
| 121 |
#' @param n Required number of design-matrix coefficients. |
|
| 122 |
#' @param argument Name of the user-facing argument, used in error messages. |
|
| 123 |
#' |
|
| 124 |
#' @keywords internal |
|
| 125 |
#' @returns A numeric coefficient vector of length `n`. |
|
| 126 |
.simulation_coefficients <- function(coefficients, n, argument) {
|
|
| 127 | 41x |
if (length(coefficients) == 0) {
|
| 128 | 24x |
return(rep(0, n)) |
| 129 |
} |
|
| 130 | 17x |
if (length(coefficients) == n + 1 && coefficients[[1]] == 0) {
|
| 131 | 13x |
return(coefficients[-1]) |
| 132 |
} |
|
| 133 | 4x |
assert_that( |
| 134 | 4x |
length(coefficients) == n, |
| 135 | 4x |
msg = sprintf( |
| 136 | 4x |
"`%s` must have length %d (or length %d starting with zero)", |
| 137 | 4x |
argument, |
| 138 | 4x |
n, |
| 139 | 4x |
n + 1 |
| 140 |
) |
|
| 141 |
) |
|
| 142 | 3x |
coefficients |
| 143 |
} |
|
| 144 | ||
| 145 |
#' Evaluate a covariate predictor in R |
|
| 146 |
#' |
|
| 147 |
#' @param design Numeric subject-level design matrix. |
|
| 148 |
#' @param intercept Numeric scalar predictor intercept. |
|
| 149 |
#' @param coefficients Numeric vector with one coefficient per design column. |
|
| 150 |
#' @param parametrization Character scalar naming the predictor |
|
| 151 |
#' parametrization. |
|
| 152 |
#' |
|
| 153 |
#' @keywords internal |
|
| 154 |
#' @returns A numeric vector with one predicted value per design row. |
|
| 155 |
.covariate_predictor_r <- function( |
|
| 156 |
design, |
|
| 157 |
intercept, |
|
| 158 |
coefficients, |
|
| 159 |
parametrization |
|
| 160 |
) {
|
|
| 161 | 50x |
linear_term <- drop(design %*% coefficients) |
| 162 |
switch( |
|
| 163 | 50x |
parametrization, |
| 164 | 27x |
linear = intercept + linear_term, |
| 165 | 1x |
proportional = intercept * (1 + linear_term), |
| 166 | 1x |
exponential = intercept^linear_term, |
| 167 | 21x |
`log-linear` = exp(intercept + linear_term) |
| 168 |
) |
|
| 169 |
} |
|
| 170 | ||
| 171 |
#' @rdname sampleSubjects |
|
| 172 |
#' @export |
|
| 173 |
sampleSubjects.SimLongitudinalRandomSlopeCov <- function(object, subjects_df) {
|
|
| 174 | 5x |
assert_that( |
| 175 | 5x |
nrow(subjects_df) == length(unique(subjects_df[["subject"]])), |
| 176 | 5x |
msg = "The number of rows in `subjects_df` should equal the number of unique subjects" |
| 177 |
) |
|
| 178 | ||
| 179 | 5x |
mu_design <- .covariate_design_matrix( |
| 180 | 5x |
object@mu_formula, |
| 181 | 5x |
subjects_df, |
| 182 | 5x |
"mu_formula" |
| 183 |
) |
|
| 184 | 5x |
slope_mu_design <- .covariate_design_matrix( |
| 185 | 5x |
object@slope_mu_formula, |
| 186 | 5x |
subjects_df, |
| 187 | 5x |
"slope_mu_formula" |
| 188 |
) |
|
| 189 | 5x |
slope_sigma_design <- .covariate_design_matrix( |
| 190 | 5x |
object@slope_sigma_formula, |
| 191 | 5x |
subjects_df, |
| 192 | 5x |
"slope_sigma_formula" |
| 193 |
) |
|
| 194 | ||
| 195 | 5x |
mu_coefficients <- .simulation_coefficients( |
| 196 | 5x |
object@mu_coefficients, |
| 197 | 5x |
ncol(mu_design), |
| 198 | 5x |
"mu_coefficients" |
| 199 |
) |
|
| 200 | 5x |
slope_mu_coefficients <- .simulation_coefficients( |
| 201 | 5x |
object@slope_mu_coefficients, |
| 202 | 5x |
ncol(slope_mu_design), |
| 203 | 5x |
"slope_mu_coefficients" |
| 204 |
) |
|
| 205 | 5x |
slope_sigma_coefficients <- .simulation_coefficients( |
| 206 | 5x |
object@slope_sigma_coefficients, |
| 207 | 5x |
ncol(slope_sigma_design), |
| 208 | 5x |
"slope_sigma_coefficients" |
| 209 |
) |
|
| 210 | ||
| 211 | 5x |
intercept <- .covariate_predictor_r( |
| 212 | 5x |
mu_design, |
| 213 | 5x |
object@mu_intercept, |
| 214 | 5x |
mu_coefficients, |
| 215 | 5x |
object@mu_parametrization |
| 216 |
) |
|
| 217 | 5x |
slope_mu <- .covariate_predictor_r( |
| 218 | 5x |
slope_mu_design, |
| 219 | 5x |
object@slope_mu_intercept, |
| 220 | 5x |
slope_mu_coefficients, |
| 221 | 5x |
object@slope_mu_parametrization |
| 222 |
) |
|
| 223 | 5x |
slope_sigma <- .covariate_predictor_r( |
| 224 | 5x |
slope_sigma_design, |
| 225 | 5x |
object@slope_sigma_intercept, |
| 226 | 5x |
slope_sigma_coefficients, |
| 227 | 5x |
object@slope_sigma_parametrization |
| 228 |
) |
|
| 229 | 5x |
assert_that( |
| 230 | 5x |
all(slope_sigma > 0), |
| 231 | 5x |
msg = "The slope standard-deviation predictor must be positive for every subject" |
| 232 |
) |
|
| 233 | ||
| 234 | 5x |
subjects_df |> |
| 235 | 5x |
dplyr::mutate( |
| 236 | 5x |
intercept = intercept, |
| 237 | 5x |
slope_ind = stats::rnorm( |
| 238 | 5x |
dplyr::n(), |
| 239 | 5x |
mean = slope_mu, |
| 240 | 5x |
sd = slope_sigma |
| 241 |
) |
|
| 242 |
) |
|
| 243 |
} |
|
| 244 | ||
| 245 |
#' @rdname sampleObservations |
|
| 246 |
#' @export |
|
| 247 |
sampleObservations.SimLongitudinalRandomSlopeCov <- function(object, times_df) {
|
|
| 248 | 7x |
times_df |> |
| 249 | 7x |
dplyr::mutate( |
| 250 | 7x |
sld_mu = .data$intercept + .data$slope_ind * .data$time, |
| 251 | 7x |
sld_sd = ifelse( |
| 252 | 7x |
object@scaled_variance, |
| 253 | 7x |
pmax(.data$sld_mu * object@sigma, .Machine$double.eps), |
| 254 | 7x |
object@sigma |
| 255 |
), |
|
| 256 | 7x |
sld = stats::rnorm(dplyr::n(), .data$sld_mu, .data$sld_sd), |
| 257 | 7x |
log_haz_link = object@link_dsld * |
| 258 | 7x |
.data$slope_ind + |
| 259 | 7x |
object@link_identity * .data$sld_mu |
| 260 |
) |
|
| 261 |
} |
|
| 262 | ||
| 263 |
#' @rdname as_print_string |
|
| 264 |
#' @exportS3Method NULL |
|
| 265 |
as_print_string.SimLongitudinalRandomSlopeCov <- function(object, ...) {
|
|
| 266 | ! |
"SimLongitudinalRandomSlopeCov" |
| 267 |
} |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include Link.R |
|
| 3 |
#' @include LongitudinalModel.R |
|
| 4 |
#' @include SurvivalModel.R |
|
| 5 |
#' @include StanModule.R |
|
| 6 |
#' @include StanModel.R |
|
| 7 |
#' @include Parameter.R |
|
| 8 |
NULL |
|
| 9 | ||
| 10 | ||
| 11 |
#' @rdname getParameters |
|
| 12 |
#' @export |
|
| 13 |
#' |
|
| 14 |
#' @returns A `ParameterList` object. |
|
| 15 |
#' |
|
| 16 |
#' @examples |
|
| 17 |
#' getParameters(LongitudinalGSF()) |
|
| 18 |
getParameters.default <- function(object, ...) {
|
|
| 19 | 84x |
if (missing(object) || is.null(object)) {
|
| 20 | 84x |
return(NULL) |
| 21 |
} |
|
| 22 | ! |
stop(sprintf( |
| 23 | ! |
"No default method implemented for getParameters(<%s>)", |
| 24 | ! |
typeof(object) |
| 25 |
)) |
|
| 26 |
} |
|
| 27 | ||
| 28 | ||
| 29 |
# merge ---- |
|
| 30 | ||
| 31 |
## merge-StanModel,NULL ---- |
|
| 32 | ||
| 33 |
#' @rdname merge |
|
| 34 |
#' |
|
| 35 |
#' @returns A merged object of the same class as `x` and `y`. |
|
| 36 |
#' |
|
| 37 |
#' @examples |
|
| 38 |
#' x <- ParameterList(Parameter(prior_normal(0, 1), "x")) |
|
| 39 |
#' y <- ParameterList(Parameter(prior_normal(0, 1), "y")) |
|
| 40 |
#' merge(x, y) |
|
| 41 |
setMethod( |
|
| 42 |
"merge", |
|
| 43 |
signature = c("StanModel", "NULL"),
|
|
| 44 | ! |
definition = function(x, y, ...) x@stan |
| 45 |
) |
|
| 46 | ||
| 47 |
## merge-NULL,StanModel ---- |
|
| 48 | ||
| 49 |
#' @rdname merge |
|
| 50 |
setMethod( |
|
| 51 |
"merge", |
|
| 52 |
signature = c("NULL", "StanModel"),
|
|
| 53 | ! |
definition = function(x, y, ...) y@stan |
| 54 |
) |
|
| 55 | ||
| 56 |
## merge-StanModule,NULL ---- |
|
| 57 | ||
| 58 |
#' @rdname merge |
|
| 59 |
setMethod( |
|
| 60 |
"merge", |
|
| 61 |
signature = c("StanModule", "NULL"),
|
|
| 62 | ! |
definition = function(x, y, ...) x |
| 63 |
) |
|
| 64 | ||
| 65 |
## merge-NULL,StanModule ---- |
|
| 66 | ||
| 67 |
#' @rdname merge |
|
| 68 |
setMethod( |
|
| 69 |
"merge", |
|
| 70 |
signature = c("NULL", "StanModule"),
|
|
| 71 | ! |
definition = function(x, y, ...) y |
| 72 |
) |
|
| 73 | ||
| 74 |
## merge-ParameterList,NULL ---- |
|
| 75 | ||
| 76 |
#' @rdname merge |
|
| 77 |
setMethod( |
|
| 78 |
"merge", |
|
| 79 |
signature = c("ParameterList", "NULL"),
|
|
| 80 | 166x |
definition = function(x, y, ...) x |
| 81 |
) |
|
| 82 | ||
| 83 |
## merge-NULL,ParameterList ---- |
|
| 84 | ||
| 85 |
#' @rdname merge |
|
| 86 |
setMethod( |
|
| 87 |
"merge", |
|
| 88 |
signature = c("NULL", "ParameterList"),
|
|
| 89 | 19x |
definition = function(x, y, ...) y |
| 90 |
) |
|
| 91 | ||
| 92 |
## merge-NULL,NULL ---- |
|
| 93 | ||
| 94 |
#' @rdname merge |
|
| 95 |
setMethod( |
|
| 96 |
"merge", |
|
| 97 |
signature = c("NULL", "NULL"),
|
|
| 98 | ! |
definition = function(x, y, ...) NULL |
| 99 |
) |
| 1 |
#' @include SurvivalModel.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 | ||
| 5 |
#' `SurvivalGamma` |
|
| 6 |
#' |
|
| 7 |
#' This class extends the general [`SurvivalModel`] class for using the |
|
| 8 |
#' Gamma survival model. |
|
| 9 |
#' |
|
| 10 |
#' @exportClass SurvivalGamma |
|
| 11 |
.SurvivalGamma <- setClass( |
|
| 12 |
Class = "SurvivalGamma", |
|
| 13 |
contains = "SurvivalModel" |
|
| 14 |
) |
|
| 15 | ||
| 16 |
# SurvivalGamma-constructors ---- |
|
| 17 | ||
| 18 |
#' @rdname SurvivalGamma-class |
|
| 19 |
#' |
|
| 20 |
#' @typed k: Prior |
|
| 21 |
#' for the shape `k`. |
|
| 22 |
#' @typed theta: Prior |
|
| 23 |
#' for the scale `theta`. |
|
| 24 |
#' @typed beta: Prior |
|
| 25 |
#' for covariates coefficients `beta`. |
|
| 26 |
#' |
|
| 27 |
#' @export |
|
| 28 |
#' |
|
| 29 |
#' @returns A `SurvivalGamma` object. |
|
| 30 |
#' |
|
| 31 |
#' @examples |
|
| 32 |
#' SurvivalGamma() |
|
| 33 |
SurvivalGamma <- function( |
|
| 34 |
k = prior_gamma(2, 0.5), |
|
| 35 |
theta = prior_gamma(2, 0.5), |
|
| 36 |
beta = prior_normal(0, 2) |
|
| 37 |
) {
|
|
| 38 | 4x |
k <- set_limits(k, lower = getOption("jmpost.double_eps"))
|
| 39 | 4x |
theta <- set_limits(theta, lower = getOption("jmpost.double_eps"))
|
| 40 | ||
| 41 | 4x |
.SurvivalGamma( |
| 42 | 4x |
SurvivalModel( |
| 43 | 4x |
name = "Gamma", |
| 44 | 4x |
stan = StanModule(x = "sm-gamma/model.stan"), |
| 45 | 4x |
parameters = ParameterList( |
| 46 | 4x |
Parameter(name = "sm_gamma_k", prior = k, size = 1), |
| 47 | 4x |
Parameter(name = "sm_gamma_theta", prior = theta, size = 1), |
| 48 | 4x |
Parameter( |
| 49 | 4x |
name = "beta_os_cov", |
| 50 | 4x |
prior = beta, |
| 51 | 4x |
size = "p_os_cov_design" |
| 52 |
) |
|
| 53 |
) |
|
| 54 |
) |
|
| 55 |
) |
|
| 56 |
} |
| 1 |
#' @include Grid.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' @rdname Grid-Dev |
|
| 7 |
.GridPrediction <- setClass( |
|
| 8 |
"GridPrediction", |
|
| 9 |
contains = "Grid", |
|
| 10 |
slots = c( |
|
| 11 |
"times" = "numeric_or_NULL", |
|
| 12 |
"newdata" = "data.frame", |
|
| 13 |
"params" = "list" |
|
| 14 |
) |
|
| 15 |
) |
|
| 16 |
#' @rdname Grid-Functions |
|
| 17 |
#' @export |
|
| 18 |
GridPrediction <- function(times = NULL, newdata, params = list()) {
|
|
| 19 | 7x |
.GridPrediction( |
| 20 | 7x |
times = times, |
| 21 | 7x |
params = params, |
| 22 | 7x |
newdata = newdata |
| 23 |
) |
|
| 24 |
} |
|
| 25 |
setValidity( |
|
| 26 |
"GridPrediction", |
|
| 27 |
function(object) {
|
|
| 28 |
for (param in names(object@params)) {
|
|
| 29 |
if (length(object@params[[param]]) != 1) {
|
|
| 30 |
return(sprintf("Parameter '%s' must be length 1", param))
|
|
| 31 |
} |
|
| 32 |
} |
|
| 33 |
if (length(object@params) != length(names(object@params))) {
|
|
| 34 |
return("All elements of `params` must be named")
|
|
| 35 |
} |
|
| 36 |
if (!is.null(object@newdata[["..new_subject.."]])) {
|
|
| 37 |
return( |
|
| 38 |
"`newdata` must not contain a column named '..new_subject..'" |
|
| 39 |
) |
|
| 40 |
} |
|
| 41 |
return(TRUE) |
|
| 42 |
} |
|
| 43 |
) |
|
| 44 | ||
| 45 | ||
| 46 |
#' @rdname Quant-Dev |
|
| 47 |
#' @export |
|
| 48 |
as.QuantityGenerator.GridPrediction <- function(object, data, ...) {
|
|
| 49 | 6x |
assert_class(data, "DataJoint") |
| 50 | 6x |
data_list <- as.list(data) |
| 51 | 6x |
validate_time_grid(object@times) |
| 52 | ||
| 53 | 6x |
n_times <- length(object@times) |
| 54 | 6x |
n_obs <- nrow(object@newdata) |
| 55 | 6x |
newdata <- object@newdata |
| 56 | 6x |
newdata[["..new_subject.."]] <- sprintf( |
| 57 | 6x |
"new_subject_%i", |
| 58 | 6x |
seq_len(nrow(newdata)) |
| 59 |
) |
|
| 60 | ||
| 61 | 6x |
QuantityGeneratorPrediction( |
| 62 | 6x |
times = rep(object@times, each = n_obs), |
| 63 | 6x |
newdata = replicate(newdata, n = n_times, simplify = FALSE) |> |
| 64 | 6x |
dplyr::bind_rows(), |
| 65 | 6x |
params = object@params |
| 66 |
) |
|
| 67 |
} |
|
| 68 | ||
| 69 |
#' @rdname Quant-Dev |
|
| 70 |
#' @export |
|
| 71 |
as.QuantityCollapser.GridPrediction <- function(object, data, ...) {
|
|
| 72 | 2x |
assert_class(data, "DataJoint") |
| 73 | 2x |
data_list <- as.list(data) |
| 74 | 2x |
generator <- as.QuantityGenerator(object, data) |
| 75 | 2x |
QuantityCollapser( |
| 76 | 2x |
times = generator@times, |
| 77 | 2x |
groups = generator@newdata[["..new_subject.."]], |
| 78 | 2x |
indexes = as.list(seq_along(generator@times)) |
| 79 |
) |
|
| 80 |
} |
|
| 81 | ||
| 82 | ||
| 83 |
#' @export |
|
| 84 |
#' |
|
| 85 |
#' @returns This is just a placeholder which will error when used. |
|
| 86 |
as.list.GridPrediction <- function(x, data, ...) {
|
|
| 87 | ! |
stop("`as.list()` is not implemented for `GridPrediction` objects")
|
| 88 |
} |
|
| 89 | ||
| 90 | ||
| 91 |
#' @rdname coalesceGridTime |
|
| 92 |
#' @export |
|
| 93 |
coalesceGridTime.GridPrediction <- function(object, times, ...) {
|
|
| 94 | 4x |
if (is.null(object@times)) {
|
| 95 | ! |
object <- GridPrediction( |
| 96 | ! |
times = times, |
| 97 | ! |
newdata = object@newdata, |
| 98 | ! |
params = object@params |
| 99 |
) |
|
| 100 |
} |
|
| 101 | 4x |
object |
| 102 |
} |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include utilities.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Re-used documentation for `DataSurvival` |
|
| 6 |
#' |
|
| 7 |
#' @typed object: DataSurvival |
|
| 8 |
#' Survival Data. |
|
| 9 |
#' @typed x: DataSurvival |
|
| 10 |
#' Survival Data. |
|
| 11 |
#' @param ... Not Used. |
|
| 12 |
#' |
|
| 13 |
#' @name DataSurvival-Shared |
|
| 14 |
#' @keywords internal |
|
| 15 |
NULL |
|
| 16 | ||
| 17 | ||
| 18 |
# DataSurvival-class ---- |
|
| 19 | ||
| 20 |
#' Survival Data Object and Constructor Function |
|
| 21 |
#' |
|
| 22 |
#' The [`DataSurvival`] class handles the processing of the survival data |
|
| 23 |
#' for fitting a [`JointModel`]. |
|
| 24 |
#' |
|
| 25 |
#' @slot data (`data.frame`)\cr See Arguments for details. |
|
| 26 |
#' @slot formula (`formula`)\cr See Arguments for details. |
|
| 27 |
#' |
|
| 28 |
#' @family DataObjects |
|
| 29 |
#' @family DataSurvival |
|
| 30 |
#' @exportClass DataSurvival |
|
| 31 |
#' @export DataSurvival |
|
| 32 |
.DataSurvival <- setClass( |
|
| 33 |
Class = "DataSurvival", |
|
| 34 |
representation = list( |
|
| 35 |
data = "data.frame", |
|
| 36 |
formula = "formula" |
|
| 37 |
) |
|
| 38 |
) |
|
| 39 | ||
| 40 |
#' @typed data: data.frame |
|
| 41 |
#' the observed time-to-event data. |
|
| 42 |
#' @typed formula: formula |
|
| 43 |
#' of the form `Surv(time, event) ~ cov1 + cov2 + ...`. |
|
| 44 |
#' See [survival::Surv()] for more details, though note that this package only supports right censoring. |
|
| 45 |
#' @rdname DataSurvival-class |
|
| 46 |
#' |
|
| 47 |
#' @returns A `DataSurvival` object. |
|
| 48 |
#' |
|
| 49 |
#' @examples |
|
| 50 |
#' DataSurvival(os_data, Surv(os_time, os_event) ~ age + sex) |
|
| 51 |
DataSurvival <- function(data, formula) {
|
|
| 52 | 68x |
.DataSurvival( |
| 53 | 68x |
data = remove_missing_rows(data, formula), |
| 54 | 68x |
formula = formula |
| 55 |
) |
|
| 56 |
} |
|
| 57 | ||
| 58 |
setValidity( |
|
| 59 |
"DataSurvival", |
|
| 60 |
method = function(object) {
|
|
| 61 |
dat <- object@data |
|
| 62 |
x <- extractVariableNames(object) |
|
| 63 |
dnames <- names(dat) |
|
| 64 |
if (nrow(object@data) == 0) {
|
|
| 65 |
return("`data` should not have 0 rows")
|
|
| 66 |
} |
|
| 67 |
if (length(x$frm) != 3) {
|
|
| 68 |
return("`formula` should be a 2 sided formula")
|
|
| 69 |
} |
|
| 70 |
LHS <- as.character(x$frm[[2]][[1]]) |
|
| 71 |
lhs_surv <- identical(LHS, "Surv") |
|
| 72 |
lhs_colon_surv <- identical(LHS, c("::", "survival", "Surv"))
|
|
| 73 |
if (!(lhs_surv || lhs_colon_surv)) {
|
|
| 74 |
return("The LHS of `formula` should be a call to survival::Surv()")
|
|
| 75 |
} |
|
| 76 |
for (v in c(x$time, x$event)) {
|
|
| 77 |
if (!v %in% dnames) {
|
|
| 78 |
return(sprintf("Variable %s is not in `data`", x$subject))
|
|
| 79 |
} |
|
| 80 |
} |
|
| 81 |
return(TRUE) |
|
| 82 |
} |
|
| 83 |
) |
|
| 84 | ||
| 85 | ||
| 86 |
#' @inheritParams DataSurvival-Shared |
|
| 87 |
#' @inherit extractVariableNames description title |
|
| 88 |
#' |
|
| 89 |
#' @returns |
|
| 90 |
#' A list with the following named elements: |
|
| 91 |
#' - `frm` (`formula`)\cr a symbolic description of the survival model to be fitted |
|
| 92 |
#' - `time` (`character`)\cr The name of the variable containing the event time |
|
| 93 |
#' - `event` (`character`) \cr The name of the variable containing the event status |
|
| 94 |
#' @export |
|
| 95 |
#' @family DataSurvival |
|
| 96 |
#' @family extractVariableNames |
|
| 97 |
#' @keywords internal |
|
| 98 |
extractVariableNames.DataSurvival <- function(object) {
|
|
| 99 | 429x |
list( |
| 100 | 429x |
frm = object@formula, |
| 101 | 429x |
time = as.character(object@formula[[2]][[2]]), |
| 102 | 429x |
event = as.character(object@formula[[2]][[3]]) |
| 103 |
) |
|
| 104 |
} |
|
| 105 | ||
| 106 | ||
| 107 |
#' `DataSurvival` -> `data.frame` |
|
| 108 |
#' |
|
| 109 |
#' @inheritParams DataSurvival-Shared |
|
| 110 |
#' |
|
| 111 |
#' @description |
|
| 112 |
#' Converts a [`DataSurvival`] object into a `data.frame`. |
|
| 113 |
#' The subject variable is cast to factor. |
|
| 114 |
#' @family DataSurvival |
|
| 115 |
#' @export |
|
| 116 |
#' |
|
| 117 |
#' @returns A `data.frame` representation of the object. |
|
| 118 |
as.data.frame.DataSurvival <- function(x, ...) {
|
|
| 119 | 422x |
x <- x@data |
| 120 | 422x |
rownames(x) <- NULL |
| 121 | 422x |
x |
| 122 |
} |
|
| 123 | ||
| 124 | ||
| 125 |
#' @rdname as_stan_list.DataObject |
|
| 126 |
#' @family DataSurvival |
|
| 127 |
#' @param df (`data.frame`)\cr The data frame to be used for generating the design matrix. |
|
| 128 |
#' @param vars (`list`)\cr The list of variable names to be used for generating the design matrix. |
|
| 129 |
#' @export |
|
| 130 |
model.matrix.DataSurvival <- function( |
|
| 131 |
object, |
|
| 132 |
df = as.data.frame(object), |
|
| 133 |
vars = extractVariableNames(object), |
|
| 134 |
... |
|
| 135 |
) {
|
|
| 136 | 345x |
design_mat <- stats::model.matrix(vars$frm, data = df) |
| 137 | 345x |
remove_index <- grep("(Intercept)", colnames(design_mat), fixed = TRUE)
|
| 138 | 345x |
if (length(remove_index) > 0) {
|
| 139 | 343x |
design_mat <- design_mat[, -remove_index, drop = FALSE] |
| 140 |
} |
|
| 141 | 345x |
rownames(design_mat) <- NULL |
| 142 | 345x |
design_mat |
| 143 |
} |
|
| 144 | ||
| 145 |
#' Extract Covariate Names for Survival Data |
|
| 146 |
#' |
|
| 147 |
#' @typed object: DataSurvival |
|
| 148 |
#' Survival data object to extract covariate names from. |
|
| 149 |
#' @param ... not used. |
|
| 150 |
#' |
|
| 151 |
#' @family covariates |
|
| 152 |
#' @export |
|
| 153 |
#' |
|
| 154 |
#' @returns A character vector containing the covariate names. |
|
| 155 |
covariates.DataSurvival <- function(object, ...) {
|
|
| 156 | 13x |
design_mat <- model.matrix(object, ...) |
| 157 | 13x |
colnames(design_mat) |
| 158 |
} |
|
| 159 | ||
| 160 |
#' @rdname as_stan_list.DataObject |
|
| 161 |
#' @family DataSurvival |
|
| 162 |
#' @export |
|
| 163 |
as_stan_list.DataSurvival <- function(object, ...) {
|
|
| 164 | 331x |
df <- as.data.frame(object) |
| 165 | 331x |
vars <- extractVariableNames(object) |
| 166 | ||
| 167 | 331x |
design_mat <- model.matrix(object, df = df, vars = vars) |
| 168 | ||
| 169 |
# Parameters for efficient integration of hazard function -> survival function |
|
| 170 | 331x |
gh_parameters <- statmod::gauss.quad( |
| 171 | 331x |
n = getOption("jmpost.gauss_quad_n"),
|
| 172 | 331x |
kind = "legendre" |
| 173 |
) |
|
| 174 | ||
| 175 | 331x |
model_data <- list( |
| 176 | 331x |
n_subject_event = sum(df[[vars$event]]), |
| 177 | 331x |
subject_event_index = which(df[[vars$event]] == 1), |
| 178 | 331x |
event_times = df[[vars$time]], |
| 179 | 331x |
p_os_cov_design = ncol(design_mat), |
| 180 | 331x |
os_cov_design = design_mat, |
| 181 | 331x |
n_nodes = length(gh_parameters$nodes), |
| 182 | 331x |
nodes = gh_parameters$nodes, |
| 183 | 331x |
weights = gh_parameters$weights |
| 184 |
) |
|
| 185 | 331x |
return(model_data) |
| 186 |
} |
|
| 187 | ||
| 188 |
#' @rdname as_stan_list.DataObject |
|
| 189 |
#' @export |
|
| 190 |
as.list.DataSurvival <- function(x, ...) {
|
|
| 191 | 2x |
as_stan_list(x, ...) |
| 192 |
} |
|
| 193 | ||
| 194 |
#' @rdname harmonise |
|
| 195 |
#' @exportS3Method NULL |
|
| 196 |
harmonise.DataSurvival <- function(object, subject_var, subject_ord, ...) {
|
|
| 197 | 31x |
data <- as.data.frame(object) |
| 198 | ||
| 199 | 31x |
assert_string(subject_var, na.ok = FALSE) |
| 200 | 31x |
assert_character(subject_ord, any.missing = FALSE) |
| 201 | 31x |
assert_that( |
| 202 | 31x |
subject_var %in% names(data), |
| 203 | 31x |
msg = sprintf( |
| 204 | 31x |
"Subject variable `%s` not found in `survival`", |
| 205 | 31x |
subject_var |
| 206 |
) |
|
| 207 |
) |
|
| 208 | 30x |
assert_that( |
| 209 | 30x |
all(data[[subject_var]] %in% subject_ord), |
| 210 | 30x |
msg = "There are subjects in `survival` that are not present in `subjects`" |
| 211 |
) |
|
| 212 | 29x |
assert_that( |
| 213 | 29x |
all(subject_ord %in% data[[subject_var]]), |
| 214 | 29x |
msg = "There are subjects in `subjects` that are not present in `survival`" |
| 215 |
) |
|
| 216 | ||
| 217 | 28x |
data[[subject_var]] <- factor( |
| 218 | 28x |
as.character(data[[subject_var]]), |
| 219 | 28x |
levels = subject_ord |
| 220 |
) |
|
| 221 | ||
| 222 | 28x |
data_ord <- data[order(data[[subject_var]]), ] |
| 223 | ||
| 224 | 28x |
DataSurvival( |
| 225 | 28x |
data = data_ord, |
| 226 | 28x |
formula = object@formula |
| 227 |
) |
|
| 228 |
} |
|
| 229 | ||
| 230 | ||
| 231 |
#' `DataSurvival` -> Printable `Character` |
|
| 232 |
#' |
|
| 233 |
#' Converts [`DataSurvival`] object into a printable string. |
|
| 234 |
#' @inheritParams DataSurvival-Shared |
|
| 235 |
#' @family DataSurvival |
|
| 236 |
#' @typed indent: numeric |
|
| 237 |
#' how much white space to prefix the print string with. |
|
| 238 |
#' @keywords internal |
|
| 239 |
#' @export |
|
| 240 |
#' |
|
| 241 |
#' @returns A character vector suitable for printing. |
|
| 242 |
as_print_string.DataSurvival <- function(object, indent = 1, ...) {
|
|
| 243 | 3x |
template <- c( |
| 244 | 3x |
"Survival-Data Object:", |
| 245 | 3x |
" # of Rows = %d", |
| 246 | 3x |
" # of Columns = %d", |
| 247 | 3x |
" # of Events = %d", |
| 248 | 3x |
" Formula = %s" |
| 249 |
) |
|
| 250 | 3x |
pad <- rep(" ", indent) |> paste(collapse = "")
|
| 251 | 3x |
template_padded <- paste(pad, template) |
| 252 | 3x |
vars <- extractVariableNames(object) |
| 253 | 3x |
sprintf( |
| 254 | 3x |
paste(template_padded, collapse = "\n"), |
| 255 | 3x |
nrow(object@data), |
| 256 | 3x |
ncol(object@data), |
| 257 | 3x |
sum(object@data[[vars$event]]), |
| 258 | 3x |
Reduce(paste, deparse(vars$frm)) |
| 259 |
) |
|
| 260 |
} |
|
| 261 | ||
| 262 |
#' @rdname show-object |
|
| 263 |
#' @export |
|
| 264 |
setMethod( |
|
| 265 |
f = "show", |
|
| 266 |
signature = "DataSurvival", |
|
| 267 |
definition = function(object) {
|
|
| 268 | 1x |
string <- as_print_string(object) |
| 269 | 1x |
cat("\n", string, "\n\n")
|
| 270 |
} |
|
| 271 |
) |
|
| 272 | ||
| 273 |
#' Build design matrix for prediction data |
|
| 274 |
#' |
|
| 275 |
#' @description |
|
| 276 |
#' This function takes a `DataSurvival` object and a `data.frame` object and generates |
|
| 277 |
#' a design matrix for the `data.frame` that has the identical structure to the |
|
| 278 |
#' design matrix of the `DataSurvival` object. |
|
| 279 |
#' |
|
| 280 |
#' This is used for predicting new data using a model that was trained on a different |
|
| 281 |
#' original data source |
|
| 282 |
#' |
|
| 283 |
#' @typed olddata: DataSurvival |
|
| 284 |
#' The original data to be used as a template for the new data |
|
| 285 |
#' @typed newdata: data.frame |
|
| 286 |
#' The new data to be used to generate the design matrix |
|
| 287 |
#' @importFrom stats .checkMFClasses terms delete.response model.frame model.matrix |
|
| 288 |
#' @importFrom survival coxph |
|
| 289 |
#' @keywords internal |
|
| 290 |
#' |
|
| 291 |
#' @returns A model matrix whose columns match the original design matrix. |
|
| 292 |
mirror_design_matrix <- function(olddata, newdata) {
|
|
| 293 | 7x |
frm <- as_formula(olddata) |
| 294 |
# Dummy model to generate a bunch of meta information that we can use to |
|
| 295 |
# re-construct the design matrix |
|
| 296 | 7x |
model <- coxph(data = as.data.frame(olddata), formula = frm) |
| 297 | 7x |
model_terms <- delete.response(terms(model)) |
| 298 | 7x |
model_frame <- model.frame( |
| 299 | 7x |
model_terms, |
| 300 | 7x |
newdata, |
| 301 | 7x |
xlev = model$xlevels |
| 302 |
) |
|
| 303 | 4x |
if (!is.null(data_classes <- attr(model_terms, "dataClasses"))) {
|
| 304 | 4x |
.checkMFClasses(data_classes, model_frame) |
| 305 |
} |
|
| 306 | 3x |
design_mat <- model.matrix( |
| 307 | 3x |
model_terms, |
| 308 | 3x |
model_frame, |
| 309 | 3x |
contrasts.arg = model$contrasts |
| 310 |
) |
|
| 311 | 3x |
remove_index <- grep("(Intercept)", colnames(design_mat), fixed = TRUE)
|
| 312 | 3x |
design_mat <- design_mat[, -remove_index, drop = FALSE] |
| 313 | 3x |
rownames(design_mat) <- NULL |
| 314 | 3x |
design_mat |
| 315 |
} |
|
| 316 | ||
| 317 | ||
| 318 |
#' @export |
|
| 319 |
#' |
|
| 320 |
#' @returns A `formula` object. |
|
| 321 |
as_formula.DataSurvival <- function(x, ...) {
|
|
| 322 | 7x |
vars <- extractVariableNames(x) |
| 323 | 7x |
vars$frm |
| 324 |
} |
| 1 |
#' Simulating Joint Longitudinal and Time-to-Event Data |
|
| 2 |
#' |
|
| 3 |
#' @typed design: list |
|
| 4 |
#' a list of [`SimGroup`] objects. See details. |
|
| 5 |
#' @typed longitudinal: SimLongitudinal |
|
| 6 |
#' object specifying how to simulate the longitudinal data |
|
| 7 |
#' @typed survival: SimSurvival |
|
| 8 |
#' object specifying how to simulate the survival data |
|
| 9 |
#' @typed .silent: flag |
|
| 10 |
#' whether to suppress info messages |
|
| 11 |
#' |
|
| 12 |
#' @slot longitudinal (`data.frame`)\cr the simulated longitudinal data. |
|
| 13 |
#' @slot survival (`data.frame`)\cr the simulated survival data. |
|
| 14 |
#' |
|
| 15 |
#' @details |
|
| 16 |
#' |
|
| 17 |
#' The `design` argument is used to specify how many distinct groups should be simulated |
|
| 18 |
#' including key information such as the number of subjects within the group as well as |
|
| 19 |
#' which treatment arm and study the group belongs to. The `design` argument should be a |
|
| 20 |
#' list of [`SimGroup`] objects e.g. |
|
| 21 |
#' ``` |
|
| 22 |
#' design = list( |
|
| 23 |
#' SimGroup(n = 50, study = "Study-1", arm = "Arm-A"), |
|
| 24 |
#' SimGroup(n = 50, study = "Study-1", arm = "Arm-B") |
|
| 25 |
#' ) |
|
| 26 |
#' ``` |
|
| 27 |
#' |
|
| 28 |
#' @name SimJointData-class |
|
| 29 |
#' @exportClass SimJointData |
|
| 30 |
#' |
|
| 31 |
#' @examples |
|
| 32 |
#' set.seed(1) |
|
| 33 |
#' SimJointData( |
|
| 34 |
#' design = list(SimGroup(3, "A", "Study 1")), |
|
| 35 |
#' longitudinal = SimLongitudinalRandomSlope( |
|
| 36 |
#' times = c(0, 10), |
|
| 37 |
#' slope_mu = 0.01, |
|
| 38 |
#' slope_sigma = 0.5 |
|
| 39 |
#' ), |
|
| 40 |
#' survival = SimSurvivalExponential(lambda = 1 / 300) |
|
| 41 |
#' ) |
|
| 42 |
.SimJointData <- setClass( |
|
| 43 |
"SimJointData", |
|
| 44 |
slots = list( |
|
| 45 |
longitudinal = "data.frame", |
|
| 46 |
survival = "data.frame" |
|
| 47 |
) |
|
| 48 |
) |
|
| 49 | ||
| 50 | ||
| 51 |
#' @rdname SimJointData-class |
|
| 52 |
#' @export |
|
| 53 |
#' |
|
| 54 |
#' @returns A `SimJointData` object. |
|
| 55 |
SimJointData <- function( |
|
| 56 |
design = list( |
|
| 57 |
SimGroup(n = 50, study = "Study-1", arm = "Arm-A"), |
|
| 58 |
SimGroup(n = 50, study = "Study-1", arm = "Arm-B") |
|
| 59 |
), |
|
| 60 |
longitudinal, |
|
| 61 |
survival, |
|
| 62 |
.silent = FALSE |
|
| 63 |
) {
|
|
| 64 | 25x |
assert( |
| 65 | 25x |
all(vapply(design, \(x) is(x, "SimGroup"), logical(1))), |
| 66 | 25x |
msg = "All elements of `design` must be of class `SimGroup`" |
| 67 |
) |
|
| 68 | ||
| 69 | 25x |
hazard_evaluation_info <- hazardWindows(survival) |
| 70 | ||
| 71 | 25x |
n_group <- vapply(design, function(x) x@n, numeric(1)) |
| 72 | 25x |
arms <- vapply(design, function(x) x@arm, character(1)) |
| 73 | 25x |
studies <- vapply(design, function(x) x@study, character(1)) |
| 74 | 25x |
n_subjects <- sum(n_group) |
| 75 | 25x |
n_times <- length(hazard_evaluation_info$midpoint) |
| 76 | ||
| 77 | 25x |
sprintf_string <- paste0( |
| 78 | 25x |
"subject_%0", |
| 79 | 25x |
ceiling(log(n_subjects, 10)) + 1, |
| 80 | 25x |
"i" |
| 81 |
) |
|
| 82 | ||
| 83 | 25x |
baseline <- dplyr::tibble( |
| 84 | 25x |
subject = sprintf(sprintf_string, seq_len(n_subjects)) |
| 85 |
) |> |
|
| 86 | 25x |
dplyr::mutate( |
| 87 | 25x |
arm = factor(rep(arms, times = n_group), levels = unique(arms)) |
| 88 |
) |> |
|
| 89 | 25x |
dplyr::mutate( |
| 90 | 25x |
study = factor( |
| 91 | 25x |
rep(studies, times = n_group), |
| 92 | 25x |
levels = unique(studies) |
| 93 |
) |
|
| 94 |
) |
|
| 95 | ||
| 96 | 25x |
os_baseline <- sampleSubjects(survival, subjects_df = baseline) |
| 97 | 25x |
lm_baseline <- sampleSubjects(longitudinal, subjects_df = baseline) |
| 98 | ||
| 99 | 25x |
lm_dat_no_obvs <- lapply( |
| 100 | 25x |
longitudinal@times, |
| 101 | 25x |
\(time) {
|
| 102 | 4560x |
baseline[["time"]] <- time |
| 103 | 4560x |
baseline |
| 104 |
} |
|
| 105 |
) |> |
|
| 106 | 25x |
dplyr::bind_rows() |> |
| 107 | 25x |
dplyr::left_join(lm_baseline, by = c("subject", "study", "arm"))
|
| 108 | ||
| 109 | 25x |
lm_dat <- sampleObservations(longitudinal, lm_dat_no_obvs) |
| 110 | ||
| 111 | 25x |
hazard_eval_df <- dplyr::tibble( |
| 112 | 25x |
subject = rep(lm_baseline$subject, each = n_times), |
| 113 | 25x |
arm = rep(lm_baseline$arm, each = n_times), |
| 114 | 25x |
study = rep(lm_baseline$study, each = n_times), |
| 115 | 25x |
midpoint = rep( |
| 116 | 25x |
as.double(hazard_evaluation_info$midpoint), |
| 117 | 25x |
times = n_subjects |
| 118 |
), |
|
| 119 | 25x |
time = rep(as.double(hazard_evaluation_info$upper), times = n_subjects), |
| 120 | 25x |
width = rep(as.double(hazard_evaluation_info$width), times = n_subjects) |
| 121 |
) |
|
| 122 | ||
| 123 | 25x |
lm_link_dat <- sampleObservations( |
| 124 | 25x |
longitudinal, |
| 125 | 25x |
dplyr::left_join( |
| 126 | 25x |
hazard_eval_df, |
| 127 | 25x |
lm_baseline, |
| 128 | 25x |
by = c("subject", "study", "arm")
|
| 129 |
) |
|
| 130 | 25x |
)[, c( |
| 131 | 25x |
"subject", |
| 132 | 25x |
"study", |
| 133 | 25x |
"arm", |
| 134 | 25x |
"log_haz_link", |
| 135 | 25x |
"time", |
| 136 | 25x |
"width", |
| 137 | 25x |
"midpoint" |
| 138 |
)] |
|
| 139 | ||
| 140 | 25x |
os_eval_df <- lm_link_dat |> |
| 141 | 25x |
dplyr::left_join(os_baseline, by = c("subject", "study", "arm"))
|
| 142 | ||
| 143 | 25x |
withCallingHandlers( |
| 144 | 25x |
os_dat <- sampleObservations(survival, os_eval_df), |
| 145 | 25x |
message = function(e) {
|
| 146 | 9x |
if (!.silent) {
|
| 147 | ! |
message(e) |
| 148 |
} |
|
| 149 | 9x |
invokeRestart("muffleMessage")
|
| 150 |
} |
|
| 151 |
) |
|
| 152 | ||
| 153 | 25x |
lm_dat2 <- lm_dat |> |
| 154 | 25x |
dplyr::left_join( |
| 155 | 25x |
dplyr::select(os_dat, "subject", os_time = "time"), |
| 156 | 25x |
by = "subject" |
| 157 |
) |> |
|
| 158 | 25x |
dplyr::mutate(observed = (.data$time <= .data$os_time)) |> |
| 159 | 25x |
dplyr::arrange(dplyr::pick(c("subject", "time")))
|
| 160 | ||
| 161 | 25x |
assert_that( |
| 162 | 25x |
length(unique(os_dat$subject)) == length(os_dat$subject), |
| 163 | 25x |
length(os_dat$subject) == n_subjects, |
| 164 | 25x |
all(os_dat$time >= 0), |
| 165 | 25x |
all(os_dat$event %in% c(0, 1)), |
| 166 | 25x |
msg = "Assumptions for the Survival data are not met (please report this issue)" |
| 167 |
) |
|
| 168 | ||
| 169 | 25x |
assert_that( |
| 170 | 25x |
nrow(lm_dat2) == n_subjects * length(longitudinal@times), |
| 171 | 25x |
length(unique(lm_dat2$subject)) == n_subjects, |
| 172 | 25x |
msg = "Assumptions for the Longitudinal data are not met (please report this issue)" |
| 173 |
) |
|
| 174 | ||
| 175 | 25x |
return( |
| 176 | 25x |
.SimJointData( |
| 177 | 25x |
survival = os_dat, |
| 178 | 25x |
longitudinal = lm_dat2[, c( |
| 179 | 25x |
"subject", |
| 180 | 25x |
"arm", |
| 181 | 25x |
"study", |
| 182 | 25x |
"time", |
| 183 | 25x |
"sld", |
| 184 | 25x |
"observed" |
| 185 |
)] |
|
| 186 |
) |
|
| 187 |
) |
|
| 188 |
} |
|
| 189 | ||
| 190 |
#' @rdname show-object |
|
| 191 |
#' @export |
|
| 192 |
setMethod( |
|
| 193 |
f = "show", |
|
| 194 |
signature = "SimJointData", |
|
| 195 |
definition = function(object) {
|
|
| 196 | 1x |
x <- sprintf("\nA SimJointData Object\n\n")
|
| 197 | 1x |
cat(x) |
| 198 | 1x |
return(object) |
| 199 |
} |
|
| 200 |
) |
|
| 201 | ||
| 202 | ||
| 203 |
#' Add PFS events at Tumour Progression to Data |
|
| 204 |
#' |
|
| 205 |
#' Adds new columns `pfs_time` and `pfs_event` based on observed changes to SLD. |
|
| 206 |
#' |
|
| 207 |
#' @typed object: SimJointData |
|
| 208 |
#' where the PFS events should be added. |
|
| 209 |
#' @typed relative_threshold: number |
|
| 210 |
#' a multiplicative threshold for the change in SLD compared to the `min(SLD)`. |
|
| 211 |
#' Default is 1.2 meaning a 20% increase. |
|
| 212 |
#' @typed absolute_threshold: number |
|
| 213 |
#' an absolute threshold for the change in SLD compared to the minimum. |
|
| 214 |
#' Default is 5. |
|
| 215 |
#' @typed from_time: number |
|
| 216 |
#' Ignore observations before this time for determining SLD minimum. |
|
| 217 |
#' @typed observed_after: logical |
|
| 218 |
#' If `FALSE` set longitudinal observations after the progression time to |
|
| 219 |
#' `observed = FALSE` |
|
| 220 |
#' @details |
|
| 221 |
#' Both thresholds must be met for a progression to be declared. |
|
| 222 |
#' |
|
| 223 |
#' @export |
|
| 224 |
#' @examples |
|
| 225 |
#' data <- SimJointData( |
|
| 226 |
#' survival = SimSurvivalExponential(lambda = 1/10), |
|
| 227 |
#' longitudinal = SimLongitudinalSteinFojo() |
|
| 228 |
#' ) |
|
| 229 |
#' data <- add_pfs(data) |
|
| 230 |
#' data@survival # now has pfs_time and pfs_event columns |
|
| 231 |
#' |
|
| 232 |
#' @returns The input `SimJointData` object with progression-free survival columns |
|
| 233 |
#' `pfs_time` and `pfs_event` added. |
|
| 234 |
add_pfs <- function( |
|
| 235 |
object, |
|
| 236 |
relative_threshold = 1.2, |
|
| 237 |
absolute_threshold = 5, |
|
| 238 |
from_time = 0, |
|
| 239 |
observed_after = FALSE |
|
| 240 |
) {
|
|
| 241 | 3x |
assert_class(object, "SimJointData") |
| 242 | ||
| 243 | 3x |
pd_times <- object@longitudinal |> |
| 244 | 3x |
dplyr::filter(.data$time >= from_time) |> |
| 245 | 3x |
dplyr::mutate( |
| 246 | 3x |
min_sld = cummin(.data$sld), |
| 247 | 3x |
is_pd = .data$sld >= |
| 248 | 3x |
pmax( |
| 249 | 3x |
.data$min_sld * relative_threshold, |
| 250 | 3x |
.data$min_sld + absolute_threshold |
| 251 |
) & |
|
| 252 | 3x |
.data$observed, |
| 253 | 3x |
pd_time = min(.data$time[.data$is_pd], Inf), |
| 254 | 3x |
.by = "subject" |
| 255 |
) |> |
|
| 256 | 3x |
dplyr::select("subject", "pd_time") |>
|
| 257 | 3x |
dplyr::slice_head(by = "subject") |
| 258 | ||
| 259 | 3x |
if (isFALSE(observed_after)) {
|
| 260 | 2x |
object@longitudinal <- object@longitudinal |> |
| 261 | 2x |
dplyr::left_join(pd_times, by = "subject") |> |
| 262 | 2x |
dplyr::mutate( |
| 263 | 2x |
observed = dplyr::if_else( |
| 264 | 2x |
.data$time > .data$pd_time, |
| 265 | 2x |
FALSE, |
| 266 | 2x |
.data$observed |
| 267 |
), |
|
| 268 | 2x |
pd_time = NULL |
| 269 |
) |
|
| 270 |
} |
|
| 271 | ||
| 272 | 3x |
object@survival <- |
| 273 | 3x |
object@survival |> |
| 274 | 3x |
dplyr::left_join(pd_times, by = "subject") |> |
| 275 | 3x |
dplyr::mutate( |
| 276 | 3x |
pfs_time = pmin(.data$time, .data$pd_time, na.rm = TRUE), |
| 277 | 3x |
pfs_event = dplyr::if_else( |
| 278 | 3x |
.data$pfs_time < .data$time, |
| 279 | 3x |
1, |
| 280 | 3x |
.data$event |
| 281 |
), |
|
| 282 | 3x |
pd_time = NULL |
| 283 |
) |
|
| 284 | 3x |
object |
| 285 |
} |
|
| 286 | ||
| 287 | ||
| 288 |
#' Cut Study Data |
|
| 289 |
#' @typed object: SimJointData |
|
| 290 |
#' where the cut times should be applied. |
|
| 291 |
#' @typed cut_time: numeric |
|
| 292 |
#' A vector of cut off times, either length 1 for all patients or |
|
| 293 |
#' `nrow(object@survival)` for a time per patient. |
|
| 294 |
#' @details |
|
| 295 |
#' All observations after this time are remove. Survival is censored at this time and any longitudinal |
|
| 296 |
#' values are removed. |
|
| 297 |
#' @export |
|
| 298 |
#' @examples |
|
| 299 |
#' data <- SimJointData( |
|
| 300 |
#' survival = SimSurvivalExponential(lambda = 1/10), |
|
| 301 |
#' longitudinal = SimLongitudinalSteinFojo() |
|
| 302 |
#' ) |
|
| 303 |
#' data <- cut_data(data, 5) |
|
| 304 |
#' data@survival |
|
| 305 |
#' # Now max time is 5 |
|
| 306 |
#' max(data@survival$time) |
|
| 307 |
#' |
|
| 308 |
#' @returns The input `SimJointData` object restricted to the requested follow-up time. |
|
| 309 |
cut_data <- function(object, cut_time) {
|
|
| 310 | 3x |
assert_class(object, "SimJointData") |
| 311 | 3x |
check_len <- if (length(cut_time) > 1) nrow(object@survival) else 1 |
| 312 | 3x |
assert_numeric(cut_time, lower = 0, len = check_len) |
| 313 | ||
| 314 | 2x |
object@survival <- object@survival |> |
| 315 | 2x |
dplyr::mutate( |
| 316 | 2x |
cut_time = cut_time, |
| 317 | 2x |
event = dplyr::if_else(.data$time < .data$cut_time, .data$event, 0), |
| 318 | 2x |
time = pmin(.data$cut_time, .data$time) |
| 319 |
) |
|
| 320 | 2x |
if (all(c("pfs_event", "pfs_time") %in% colnames(object@survival))) {
|
| 321 | ! |
object@survival <- object@survival |> |
| 322 | ! |
dplyr::mutate( |
| 323 | ! |
pfs_event = dplyr::if_else( |
| 324 | ! |
.data$pfs_time < .data$cut_time, |
| 325 | ! |
.data$pfs_event, |
| 326 | ! |
0 |
| 327 |
), |
|
| 328 | ! |
pfs_time = pmin(.data$cut_time, .data$pfs_time) |
| 329 |
) |
|
| 330 |
} |
|
| 331 | ||
| 332 | 2x |
object@longitudinal <- object@longitudinal |> |
| 333 | 2x |
dplyr::left_join( |
| 334 | 2x |
dplyr::select(object@survival, "subject", "cut_time"), |
| 335 | 2x |
by = "subject" |
| 336 |
) |> |
|
| 337 | 2x |
dplyr::filter(.data$time <= .data$cut_time) |> |
| 338 | 2x |
dplyr::mutate(cut_time = NULL) |
| 339 | ||
| 340 | 2x |
object@survival$cut_time <- NULL |
| 341 | ||
| 342 | 2x |
object |
| 343 |
} |
| 1 |
#' @include StanModule.R |
|
| 2 |
#' @include LongitudinalModel.R |
|
| 3 |
#' @include ParameterList.R |
|
| 4 |
#' @include LinkComponent.R |
|
| 5 |
#' @include Prior.R |
|
| 6 |
NULL |
|
| 7 | ||
| 8 | ||
| 9 |
#' `Link` Function Arguments |
|
| 10 |
#' |
|
| 11 |
#' This exists just to contain all the common arguments for [`Link`] methods. |
|
| 12 |
#' |
|
| 13 |
#' @typed x: Link |
|
| 14 |
#' a link object. |
|
| 15 |
#' @typed object: Link |
|
| 16 |
#' a link object. |
|
| 17 |
#' @param ... Not Used. |
|
| 18 |
#' |
|
| 19 |
#' @name Link-Shared |
|
| 20 |
#' @keywords internal |
|
| 21 |
NULL |
|
| 22 | ||
| 23 | ||
| 24 |
#' `Link` |
|
| 25 |
#' |
|
| 26 |
#' @slot components (`list`)\cr a list of [`LinkComponent`] or [`PromiseLinkComponent`] objects. |
|
| 27 |
#' @slot resolved (`logical`)\cr indicates if all the `components` have been resolved. |
|
| 28 |
#' |
|
| 29 |
#' @typed ...: "[`LinkComponent`] or [`PromiseLinkComponent`]" |
|
| 30 |
#' an arbitrary number of link components. |
|
| 31 |
#' |
|
| 32 |
#' @description |
|
| 33 |
#' Simple container class to enable the use of multiple link components in a joint model. |
|
| 34 |
#' Note that the constructor of this object is idempotent e.g. `Link(Link(x)) == Link(x)` |
|
| 35 |
#' |
|
| 36 |
#' @examples |
|
| 37 |
#' Link( |
|
| 38 |
#' linkDSLD(), |
|
| 39 |
#' linkTTG() |
|
| 40 |
#' ) |
|
| 41 |
#' |
|
| 42 |
#' @family Link |
|
| 43 |
#' @name Link-class |
|
| 44 |
#' @exportClass Link |
|
| 45 |
.Link <- setClass( |
|
| 46 |
Class = "Link", |
|
| 47 |
slots = list( |
|
| 48 |
components = "list", |
|
| 49 |
resolved = "logical" |
|
| 50 |
) |
|
| 51 |
) |
|
| 52 | ||
| 53 |
#' @rdname Link-class |
|
| 54 |
#' @export |
|
| 55 |
#' |
|
| 56 |
#' @returns A `Link` object. |
|
| 57 |
Link <- function(...) {
|
|
| 58 | 291x |
components <- list(...) |
| 59 | ||
| 60 |
# If the input is already a Link object, return it (e.g. implement |
|
| 61 |
# a constructor that is idempotent) |
|
| 62 | 291x |
if (length(components) == 1 && is(components[[1]], "Link")) {
|
| 63 | 116x |
return(components[[1]]) |
| 64 |
} |
|
| 65 | ||
| 66 | 175x |
.Link( |
| 67 | 175x |
components = components, |
| 68 | 175x |
resolved = !any(vapply( |
| 69 | 175x |
components, |
| 70 | 175x |
\(x) is(x, "PromiseLinkComponent"), |
| 71 | 175x |
logical(1) |
| 72 |
)) |
|
| 73 |
) |
|
| 74 |
} |
|
| 75 | ||
| 76 | ||
| 77 |
#' Resolve any promises |
|
| 78 |
#' |
|
| 79 |
#' Loops over all components and ensures that any [`PromiseLinkComponent`] objects |
|
| 80 |
#' are resolved to [`LinkComponent`] objects. |
|
| 81 |
#' |
|
| 82 |
#' @typed object: Link |
|
| 83 |
#' a link object. |
|
| 84 |
#' @typed model: LongitudinalModel |
|
| 85 |
#' the model object. |
|
| 86 |
#' @param ... Not Used. |
|
| 87 |
#' |
|
| 88 |
#' @export |
|
| 89 |
#' |
|
| 90 |
#' @returns A `Link` object with all promised components resolved. |
|
| 91 |
resolvePromise.Link <- function(object, model, ...) {
|
|
| 92 | 134x |
if (length(object) == 0) {
|
| 93 | 101x |
return(object) |
| 94 |
} |
|
| 95 | 33x |
assert_that( |
| 96 | 33x |
is(model, "LongitudinalModel"), |
| 97 | 33x |
msg = "model must be of class `LongitudinalModel`" |
| 98 |
) |
|
| 99 | 33x |
do.call(Link, lapply(object@components, resolvePromise, model = model)) |
| 100 |
} |
|
| 101 | ||
| 102 | ||
| 103 |
setValidity( |
|
| 104 |
Class = "Link", |
|
| 105 |
method = function(object) {
|
|
| 106 |
for (i in object@components) {
|
|
| 107 |
if (!(is(i, "LinkComponent") || is(i, "PromiseLinkComponent"))) {
|
|
| 108 |
return( |
|
| 109 |
"All components must be of class `LinkComponent` or `PromiseLinkComponent`" |
|
| 110 |
) |
|
| 111 |
} |
|
| 112 |
} |
|
| 113 | ||
| 114 |
contains_promise <- any( |
|
| 115 |
vapply( |
|
| 116 |
object@components, |
|
| 117 |
\(x) is(x, "PromiseLinkComponent"), |
|
| 118 |
logical(1) |
|
| 119 |
) |
|
| 120 |
) |
|
| 121 |
if (contains_promise && object@resolved) {
|
|
| 122 |
return("Object cannot be resolved if it contains promises")
|
|
| 123 |
} |
|
| 124 | ||
| 125 |
if (length(object@resolved) > 1) {
|
|
| 126 |
return("The `resolved` slot must be a logical scalar")
|
|
| 127 |
} |
|
| 128 |
return(TRUE) |
|
| 129 |
} |
|
| 130 |
) |
|
| 131 | ||
| 132 | ||
| 133 |
#' `Link` -> `StanModule` |
|
| 134 |
#' |
|
| 135 |
#' Converts a [`Link`] object to a [`StanModule`] object |
|
| 136 |
#' |
|
| 137 |
#' @inheritParams Link-Shared |
|
| 138 |
#' |
|
| 139 |
#' @family Link |
|
| 140 |
#' @family as.StanModule |
|
| 141 |
#' @export |
|
| 142 |
#' |
|
| 143 |
#' @returns A `StanModule` object. |
|
| 144 |
as.StanModule.Link <- function(object, ...) {
|
|
| 145 | 144x |
if (length(object@components) == 0) {
|
| 146 | 108x |
return(StanModule("base/link_none.stan"))
|
| 147 |
} |
|
| 148 | ||
| 149 | 36x |
keys <- vapply( |
| 150 | 36x |
object@components, |
| 151 | 36x |
function(x) x@key, |
| 152 | 36x |
character(1) |
| 153 |
) |
|
| 154 | ||
| 155 | 36x |
base_stan <- StanModule( |
| 156 | 36x |
decorated_render( |
| 157 | 36x |
.x = read_stan("base/link.stan"),
|
| 158 | 36x |
items = as.list(keys) |
| 159 |
) |
|
| 160 |
) |
|
| 161 | ||
| 162 | 36x |
stan_list <- lapply( |
| 163 | 36x |
object@components, |
| 164 | 36x |
as.StanModule |
| 165 |
) |
|
| 166 | ||
| 167 | 36x |
stan <- Reduce( |
| 168 | 36x |
merge, |
| 169 | 36x |
append(base_stan, stan_list) |
| 170 |
) |
|
| 171 | 36x |
return(stan) |
| 172 |
} |
|
| 173 | ||
| 174 | ||
| 175 |
#' `Link` -> `list` |
|
| 176 |
#' |
|
| 177 |
#' @inheritParams Link-Shared |
|
| 178 |
#' |
|
| 179 |
#' @description |
|
| 180 |
#' Returns a named list where each element of the list corresponds |
|
| 181 |
#' to a Stan modelling block e.g. `data`, `model`, etc. |
|
| 182 |
#' |
|
| 183 |
#' @family Link |
|
| 184 |
#' @export |
|
| 185 |
#' |
|
| 186 |
#' @returns A named `list` representation of the object. |
|
| 187 |
as.list.Link <- function(x, ...) {
|
|
| 188 | 143x |
as.list(as.StanModule(x, ...)) |
| 189 |
} |
|
| 190 | ||
| 191 | ||
| 192 |
#' @export |
|
| 193 |
#' @rdname getParameters |
|
| 194 |
getParameters.Link <- function(object, ...) {
|
|
| 195 | 133x |
parameters_list <- lapply( |
| 196 | 133x |
object@components, |
| 197 | 133x |
getParameters, |
| 198 |
... |
|
| 199 |
) |
|
| 200 | 133x |
Reduce( |
| 201 | 133x |
merge, |
| 202 | 133x |
parameters_list |
| 203 |
) |
|
| 204 |
} |
|
| 205 | ||
| 206 | ||
| 207 |
#' @rdname initialValues |
|
| 208 |
#' @export |
|
| 209 |
initialValues.Link <- function(object, ...) {
|
|
| 210 | ! |
unlist( |
| 211 | ! |
lapply(object@components, initialValues), |
| 212 | ! |
recursive = FALSE |
| 213 |
) |
|
| 214 |
} |
|
| 215 | ||
| 216 | ||
| 217 |
#' `Link` -> `list` |
|
| 218 |
#' |
|
| 219 |
#' @inheritParams Link-Shared |
|
| 220 |
#' |
|
| 221 |
#' @description |
|
| 222 |
#' Returns the number of link components within the [`Link`] object |
|
| 223 |
#' |
|
| 224 |
#' @family Link |
|
| 225 |
#' @export |
|
| 226 |
#' |
|
| 227 |
#' @returns A single integer giving the number of link components. |
|
| 228 |
length.Link <- function(x) {
|
|
| 229 | 277x |
length(x@components) |
| 230 |
} |
|
| 231 | ||
| 232 | ||
| 233 |
#' @export |
|
| 234 |
#' |
|
| 235 |
#' @returns A character vector suitable for printing. |
|
| 236 |
as_print_string.Link <- function(object, ...) {
|
|
| 237 | 10x |
if (length(object) == 0) {
|
| 238 | 4x |
return("\nNo Link")
|
| 239 |
} |
|
| 240 | ||
| 241 | 6x |
strings <- vapply(object@components, as_print_string, character(1)) |
| 242 | ||
| 243 | 6x |
paste( |
| 244 | 6x |
c( |
| 245 | 6x |
"\nLink with the following components/parameters:", |
| 246 | 6x |
paste0(" ", strings)
|
| 247 |
), |
|
| 248 | 6x |
collapse = "\n" |
| 249 |
) |
|
| 250 |
} |
|
| 251 | ||
| 252 | ||
| 253 |
#' @rdname show-object |
|
| 254 |
#' @export |
|
| 255 |
setMethod( |
|
| 256 |
f = "show", |
|
| 257 |
signature = "Link", |
|
| 258 |
definition = function(object) {
|
|
| 259 | 5x |
cat(paste0(as_print_string(object), "\n")) |
| 260 |
} |
|
| 261 |
) |
| 1 |
#' @include SimLongitudinal.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Simulate Longitudinal Data from a GSF Model |
|
| 6 |
#' |
|
| 7 |
#' @typed times: numeric |
|
| 8 |
#' the times to generate observations at. |
|
| 9 |
#' @typed sigma: number |
|
| 10 |
#' the variance of the longitudinal values. |
|
| 11 |
#' @typed mu_s: numeric |
|
| 12 |
#' the mean shrinkage rates. |
|
| 13 |
#' @typed mu_g: numeric |
|
| 14 |
#' the mean growth rates. |
|
| 15 |
#' @typed mu_b: numeric |
|
| 16 |
#' the mean baseline values. |
|
| 17 |
#' @typed mu_phi: numeric |
|
| 18 |
#' the logit-scale location of the proportion of cells affected by the |
|
| 19 |
#' treatment. |
|
| 20 |
#' @typed omega_b: number |
|
| 21 |
#' the baseline value standard deviation. |
|
| 22 |
#' @typed omega_s: number |
|
| 23 |
#' the shrinkage rate standard deviation. |
|
| 24 |
#' @typed omega_g: number |
|
| 25 |
#' the growth rate standard deviation. |
|
| 26 |
#' @typed omega_phi: number |
|
| 27 |
#' the logit-scale standard deviation of the proportion of cells affected by |
|
| 28 |
#' the treatment. |
|
| 29 |
#' @typed link_dsld: number |
|
| 30 |
#' the link coefficient for the derivative contribution. |
|
| 31 |
#' @typed link_ttg: number |
|
| 32 |
#' the link coefficient for the time-to-growth contribution. |
|
| 33 |
#' @typed link_identity: number |
|
| 34 |
#' the link coefficient for the SLD Identity contribution. |
|
| 35 |
#' @typed link_growth: number |
|
| 36 |
#' the link coefficient for the log-growth parameter contribution. |
|
| 37 |
#' @typed link_shrinkage: number |
|
| 38 |
#' the link coefficient for the log-shrinkage parameter contribution. |
|
| 39 |
#' @typed scaled_variance: logical |
|
| 40 |
#' whether the variance should be scaled by the expected value |
|
| 41 |
#' (see the "Statistical Specifications" vignette for more details) |
|
| 42 |
#' |
|
| 43 |
#' @slot sigma (`numeric`)\cr See arguments. |
|
| 44 |
#' @slot mu_s (`numeric`)\cr See arguments. |
|
| 45 |
#' @slot mu_g (`numeric`)\cr See arguments. |
|
| 46 |
#' @slot mu_b (`numeric`)\cr See arguments. |
|
| 47 |
#' @slot mu_phi (`numeric`)\cr See arguments. |
|
| 48 |
#' @slot omega_b (`numeric`)\cr See arguments. |
|
| 49 |
#' @slot omega_s (`numeric`)\cr See arguments. |
|
| 50 |
#' @slot omega_g (`numeric`)\cr See arguments. |
|
| 51 |
#' @slot omega_phi (`numeric`)\cr See arguments. |
|
| 52 |
#' @slot link_dsld (`numeric`)\cr See arguments. |
|
| 53 |
#' @slot link_ttg (`numeric`)\cr See arguments. |
|
| 54 |
#' @slot link_identity (`numeric`)\cr See arguments. |
|
| 55 |
#' @slot link_growth (`numeric`)\cr See arguments. |
|
| 56 |
#' @slot link_shrinkage (`numeric`)\cr See arguments. |
|
| 57 |
#' @slot scaled_variance (`logical`)\cr See arguments. |
|
| 58 |
#' @family SimLongitudinal |
|
| 59 |
#' @name SimLongitudinalGSF-class |
|
| 60 |
#' @exportClass SimLongitudinalGSF |
|
| 61 |
#' |
|
| 62 |
#' @examples |
|
| 63 |
#' SimLongitudinalGSF(times = c(0, 30, 60)) |
|
| 64 |
.SimLongitudinalGSF <- setClass( |
|
| 65 |
"SimLongitudinalGSF", |
|
| 66 |
contains = "SimLongitudinal", |
|
| 67 |
slots = c( |
|
| 68 |
sigma = "numeric", |
|
| 69 |
mu_s = "numeric", |
|
| 70 |
mu_g = "numeric", |
|
| 71 |
mu_b = "numeric", |
|
| 72 |
mu_phi = "numeric", |
|
| 73 |
omega_b = "numeric", |
|
| 74 |
omega_s = "numeric", |
|
| 75 |
omega_g = "numeric", |
|
| 76 |
omega_phi = "numeric", |
|
| 77 |
link_dsld = "numeric", |
|
| 78 |
link_ttg = "numeric", |
|
| 79 |
link_identity = "numeric", |
|
| 80 |
link_growth = "numeric", |
|
| 81 |
link_shrinkage = "numeric", |
|
| 82 |
scaled_variance = "logical" |
|
| 83 |
) |
|
| 84 |
) |
|
| 85 | ||
| 86 |
#' @rdname SimLongitudinalGSF-class |
|
| 87 |
#' @export |
|
| 88 |
#' |
|
| 89 |
#' @returns A `SimLongitudinalGSF` object. |
|
| 90 |
SimLongitudinalGSF <- function( |
|
| 91 |
times = c(-100, -50, 0, 50, 100, 150, 250, 350, 450, 550) / 365, |
|
| 92 |
sigma = 0.01, |
|
| 93 |
mu_s = log(c(0.6, 0.4)), |
|
| 94 |
mu_g = log(c(0.25, 0.35)), |
|
| 95 |
mu_b = log(60), |
|
| 96 |
mu_phi = qlogis(c(0.4, 0.6)), |
|
| 97 |
omega_b = 0.2, |
|
| 98 |
omega_s = 0.2, |
|
| 99 |
omega_g = 0.2, |
|
| 100 |
omega_phi = 0.2, |
|
| 101 |
link_dsld = 0, |
|
| 102 |
link_ttg = 0, |
|
| 103 |
link_identity = 0, |
|
| 104 |
link_growth = 0, |
|
| 105 |
link_shrinkage = 0, |
|
| 106 |
scaled_variance = FALSE |
|
| 107 |
) {
|
|
| 108 | 11x |
if (length(omega_b) == 1) {
|
| 109 | 11x |
omega_b <- rep(omega_b, length(mu_b)) |
| 110 |
} |
|
| 111 | 11x |
if (length(omega_s) == 1) {
|
| 112 | 11x |
omega_s <- rep(omega_s, length(mu_s)) |
| 113 |
} |
|
| 114 | 11x |
if (length(omega_g) == 1) {
|
| 115 | 11x |
omega_g <- rep(omega_g, length(mu_g)) |
| 116 |
} |
|
| 117 | 11x |
if (length(omega_phi) == 1) {
|
| 118 | 11x |
omega_phi <- rep(omega_phi, length(mu_phi)) |
| 119 |
} |
|
| 120 | ||
| 121 | 11x |
.SimLongitudinalGSF( |
| 122 | 11x |
times = times, |
| 123 | 11x |
sigma = sigma, |
| 124 | 11x |
mu_s = mu_s, |
| 125 | 11x |
mu_g = mu_g, |
| 126 | 11x |
mu_b = mu_b, |
| 127 | 11x |
mu_phi = mu_phi, |
| 128 | 11x |
omega_b = omega_b, |
| 129 | 11x |
omega_s = omega_s, |
| 130 | 11x |
omega_g = omega_g, |
| 131 | 11x |
omega_phi = omega_phi, |
| 132 | 11x |
link_dsld = link_dsld, |
| 133 | 11x |
link_ttg = link_ttg, |
| 134 | 11x |
link_identity = link_identity, |
| 135 | 11x |
link_growth = link_growth, |
| 136 | 11x |
link_shrinkage = link_shrinkage, |
| 137 | 11x |
scaled_variance = scaled_variance |
| 138 |
) |
|
| 139 |
} |
|
| 140 | ||
| 141 | ||
| 142 |
setValidity( |
|
| 143 |
"SimLongitudinalGSF", |
|
| 144 |
function(object) {
|
|
| 145 |
par_lengths <- c( |
|
| 146 |
length(object@mu_s), |
|
| 147 |
length(object@mu_g), |
|
| 148 |
length(object@mu_phi) |
|
| 149 |
) |
|
| 150 |
if (length(unique(par_lengths)) != 1) {
|
|
| 151 |
return( |
|
| 152 |
"The parameters `mu_s`, `mu_g` and `mu_phi` must have the same length." |
|
| 153 |
) |
|
| 154 |
} |
|
| 155 | ||
| 156 |
pairs <- list( |
|
| 157 |
"omega_b" = "mu_b", |
|
| 158 |
"omega_s" = "mu_s", |
|
| 159 |
"omega_g" = "mu_g", |
|
| 160 |
"omega_phi" = "mu_phi" |
|
| 161 |
) |
|
| 162 |
for (i in seq_along(pairs)) {
|
|
| 163 |
omega <- slot(object, names(pairs)[[i]]) |
|
| 164 |
mu <- slot(object, pairs[[i]]) |
|
| 165 |
if (!(length(omega) == length(mu))) {
|
|
| 166 |
return( |
|
| 167 |
sprintf( |
|
| 168 |
"`%s` must be length 1 or the same length as `%s`", |
|
| 169 |
omega, |
|
| 170 |
mu |
|
| 171 |
) |
|
| 172 |
) |
|
| 173 |
} |
|
| 174 |
} |
|
| 175 | ||
| 176 |
len_1_pars <- c( |
|
| 177 |
"sigma", |
|
| 178 |
"link_dsld", |
|
| 179 |
"link_ttg", |
|
| 180 |
"link_identity", |
|
| 181 |
"link_growth", |
|
| 182 |
"link_shrinkage" |
|
| 183 |
) |
|
| 184 |
for (par in len_1_pars) {
|
|
| 185 |
if (length(slot(object, par)) != 1) {
|
|
| 186 |
return(sprintf( |
|
| 187 |
"The `%s` parameter must be a length 1 numeric.", |
|
| 188 |
par |
|
| 189 |
)) |
|
| 190 |
} |
|
| 191 |
} |
|
| 192 | ||
| 193 |
return(TRUE) |
|
| 194 |
} |
|
| 195 |
) |
|
| 196 | ||
| 197 |
#' @rdname as_print_string |
|
| 198 |
#' @exportS3Method NULL |
|
| 199 |
as_print_string.SimLongitudinalGSF <- function(object, ...) {
|
|
| 200 | 1x |
return("SimLongitudinalGSF")
|
| 201 |
} |
|
| 202 | ||
| 203 |
#' @rdname sampleObservations |
|
| 204 |
#' @export |
|
| 205 |
sampleObservations.SimLongitudinalGSF <- function(object, times_df) {
|
|
| 206 | 19x |
times_df |> |
| 207 | 19x |
dplyr::mutate( |
| 208 | 19x |
mu_sld = gsf_sld( |
| 209 | 19x |
.data$time, |
| 210 | 19x |
.data$psi_b, |
| 211 | 19x |
.data$psi_s, |
| 212 | 19x |
.data$psi_g, |
| 213 | 19x |
.data$psi_phi |
| 214 |
), |
|
| 215 | 19x |
dsld = gsf_dsld( |
| 216 | 19x |
.data$time, |
| 217 | 19x |
.data$psi_b, |
| 218 | 19x |
.data$psi_s, |
| 219 | 19x |
.data$psi_g, |
| 220 | 19x |
.data$psi_phi |
| 221 |
), |
|
| 222 | 19x |
ttg = gsf_ttg( |
| 223 | 19x |
.data$time, |
| 224 | 19x |
.data$psi_b, |
| 225 | 19x |
.data$psi_s, |
| 226 | 19x |
.data$psi_g, |
| 227 | 19x |
.data$psi_phi |
| 228 |
), |
|
| 229 | 19x |
sld_sd = ifelse( |
| 230 | 19x |
object@scaled_variance, |
| 231 | 19x |
.data$mu_sld * object@sigma, |
| 232 | 19x |
object@sigma |
| 233 |
), |
|
| 234 | 19x |
sld = stats::rnorm(dplyr::n(), .data$mu_sld, .data$sld_sd), |
| 235 | 19x |
log_haz_link = (object@link_dsld * .data$dsld) + |
| 236 | 19x |
(object@link_ttg * .data$ttg) + |
| 237 | 19x |
(object@link_identity * .data$mu_sld) + |
| 238 | 19x |
(object@link_growth * log(.data$psi_g)) + |
| 239 | 19x |
(object@link_shrinkage * log(.data$psi_s)) |
| 240 |
) |
|
| 241 |
} |
|
| 242 | ||
| 243 | ||
| 244 |
#' @rdname sampleSubjects |
|
| 245 |
#' @export |
|
| 246 |
sampleSubjects.SimLongitudinalGSF <- function(object, subjects_df) {
|
|
| 247 | 10x |
assert_that( |
| 248 | 10x |
is.factor(subjects_df$study), |
| 249 | 10x |
is.factor(subjects_df$arm), |
| 250 | 10x |
length(levels(subjects_df$study)) == length(object@mu_b), |
| 251 | 10x |
length(levels(subjects_df$arm)) == length(object@mu_s), |
| 252 | 10x |
length(levels(subjects_df$arm)) == length(object@mu_g), |
| 253 | 10x |
length(levels(subjects_df$arm)) == length(object@mu_phi) |
| 254 |
) |
|
| 255 | ||
| 256 | 10x |
res <- subjects_df |> |
| 257 | 10x |
dplyr::distinct(.data$subject, .data$arm, .data$study) |> |
| 258 | 10x |
dplyr::mutate( |
| 259 | 10x |
study_idx = as.numeric(.data$study), |
| 260 | 10x |
arm_idx = as.numeric(.data$arm), |
| 261 | 10x |
psi_b = stats::rlnorm( |
| 262 | 10x |
dplyr::n(), |
| 263 | 10x |
object@mu_b[.data$study_idx], |
| 264 | 10x |
object@omega_b[.data$study_idx] |
| 265 |
), |
|
| 266 | 10x |
psi_s = stats::rlnorm( |
| 267 | 10x |
dplyr::n(), |
| 268 | 10x |
object@mu_s[.data$arm_idx], |
| 269 | 10x |
object@omega_s[.data$arm_idx] |
| 270 |
), |
|
| 271 | 10x |
psi_g = stats::rlnorm( |
| 272 | 10x |
dplyr::n(), |
| 273 | 10x |
object@mu_g[.data$arm_idx], |
| 274 | 10x |
object@omega_g[.data$arm_idx] |
| 275 |
), |
|
| 276 | 10x |
psi_phi_logit = stats::rnorm( |
| 277 | 10x |
dplyr::n(), |
| 278 | 10x |
object@mu_phi[.data$arm_idx], |
| 279 | 10x |
object@omega_phi[.data$arm_idx] |
| 280 |
), |
|
| 281 | 10x |
psi_phi = stats::plogis(.data$psi_phi_logit) |
| 282 |
) |
|
| 283 | 10x |
res[, c("subject", "arm", "study", "psi_b", "psi_s", "psi_g", "psi_phi")]
|
| 284 |
} |
|
| 285 | ||
| 286 | ||
| 287 |
## sim_lm_gsf ---- |
|
| 288 | ||
| 289 |
#' Generalized Stein-Fojo Functionals |
|
| 290 |
#' |
|
| 291 |
#' @typed time: numeric |
|
| 292 |
#' time grid. |
|
| 293 |
#' @typed b: number |
|
| 294 |
#' baseline. |
|
| 295 |
#' @typed s: number |
|
| 296 |
#' shrinkage. |
|
| 297 |
#' @typed g: number |
|
| 298 |
#' growth. |
|
| 299 |
#' @typed phi: number |
|
| 300 |
#' shrinkage proportion. |
|
| 301 |
#' |
|
| 302 |
#' @returns The function results. |
|
| 303 |
#' |
|
| 304 |
#' @keywords internal |
|
| 305 |
gsf_sld <- function(time, b, s, g, phi) {
|
|
| 306 | 26x |
phi <- dplyr::if_else(time >= 0, phi, 0) |
| 307 | 26x |
b * (phi * exp(-s * time) + (1 - phi) * exp(g * time)) |
| 308 |
} |
|
| 309 | ||
| 310 | ||
| 311 |
#' @rdname gsf_sld |
|
| 312 |
gsf_ttg <- function(time, b, s, g, phi) {
|
|
| 313 | 23x |
t1 <- (log(s * phi / (g * (1 - phi))) / (g + s)) |
| 314 | 23x |
t1[t1 <= 0] <- 0 |
| 315 | 23x |
return(t1) |
| 316 |
} |
|
| 317 | ||
| 318 | ||
| 319 |
#' @rdname gsf_sld |
|
| 320 |
gsf_dsld <- function(time, b, s, g, phi) {
|
|
| 321 | 23x |
phi <- dplyr::if_else(time >= 0, phi, 0) |
| 322 | 23x |
t1 <- (1 - phi) * g * exp(g * time) |
| 323 | 23x |
t2 <- phi * s * exp(-s * time) |
| 324 | 23x |
return(b * (t1 - t2)) |
| 325 |
} |
| 1 |
#' jmpost settings |
|
| 2 |
#' |
|
| 3 |
#' @description |
|
| 4 |
#' Define settings that modify the behaviour of the `jmpost` package |
|
| 5 |
#' |
|
| 6 |
#' Each of the following are the name of options that can be set via: |
|
| 7 |
#' ``` |
|
| 8 |
#' options(<option_name> = <value>) |
|
| 9 |
#' ``` |
|
| 10 |
#' |
|
| 11 |
#' ## `jmpost.prior_shrinkage` |
|
| 12 |
#' |
|
| 13 |
#' Default = `0.5` |
|
| 14 |
#' |
|
| 15 |
#' By default all initial values are drawn as random sample from the respective prior |
|
| 16 |
#' distribution with a shrinkage factor towards the mean. That is: |
|
| 17 |
#' ``` |
|
| 18 |
#' initial_value = prior_mean * prior_shrinkage + (1 - prior_shrinkage) * prior_sample |
|
| 19 |
#' ``` |
|
| 20 |
#' This setting controls the shrinkage factor. A value of 0 means no shrinkage (i.e. |
|
| 21 |
#' pure random draw) whilst a value of 1 means the initial value is just the mean. |
|
| 22 |
#' |
|
| 23 |
#' ## `jmpost.cache_dir` |
|
| 24 |
#' |
|
| 25 |
#' Default = `tempfile()` |
|
| 26 |
#' |
|
| 27 |
#' Directory to store compiled stan models in. If not set, a temporary directory is used for |
|
| 28 |
#' the given R session. Can also be set via the environment variable `JMPOST_CACHE_DIR`. |
|
| 29 |
#' |
|
| 30 |
#' |
|
| 31 |
#' |
|
| 32 |
#' ## `jmpost.gauss_quad_n` |
|
| 33 |
#' |
|
| 34 |
#' Default = 15 |
|
| 35 |
#' |
|
| 36 |
#' In most cases the survival function of the joint model does not have a closed form |
|
| 37 |
#' and as such it is calculated by integrating the hazard function. `jmpost` estimates this |
|
| 38 |
#' via Gaussian Quadrature, in particular it uses [`statmod::gauss.quad`] with |
|
| 39 |
#' `kind = "legendre"` to create the nodes and weights. |
|
| 40 |
#' |
|
| 41 |
#' This option specifies the `n` argument in the call to [`statmod::gauss.quad`]. In general |
|
| 42 |
#' higher values of `n` lead to better accuracy of the approximation but at the cost of |
|
| 43 |
#' increased computational time. |
|
| 44 |
#' |
|
| 45 |
#' ## `jmpost.double_eps` and `jmpost.double_neg_eps` |
|
| 46 |
#' |
|
| 47 |
#' Default = `0` and `sqrt(.Machine$double.neg.eps)` |
|
| 48 |
#' |
|
| 49 |
#' These are small floating-point numbers `x` which are used in parameter |
|
| 50 |
#' definitions as lower boundaries to prevent numerical issues with the MCMC |
|
| 51 |
#' sampler in the beginning of the sampling process. |
|
| 52 |
#' |
|
| 53 |
#' @examples |
|
| 54 |
#' \dontrun{
|
|
| 55 |
#' options(jmpost.prior_shrinkage = 0.5) |
|
| 56 |
#' } |
|
| 57 |
#' @name jmpost-settings |
|
| 58 |
#' |
|
| 59 |
#' @returns Invisibly returns `NULL` after initializing the package options. |
|
| 60 |
set_options <- function() {
|
|
| 61 | ! |
cache_dir <- Sys.getenv("JMPOST_CACHE_DIR")
|
| 62 | ||
| 63 | ! |
if (cache_dir == "" || is.null(cache_dir)) {
|
| 64 | ! |
cache_dir <- tempfile() |
| 65 |
} |
|
| 66 | ||
| 67 | ! |
current_opts <- names(options()) |
| 68 | ! |
jmpost_opts <- list( |
| 69 | ! |
jmpost.cache_dir = cache_dir, |
| 70 | ! |
jmpost.prior_shrinkage = 0.5, |
| 71 | ! |
jmpost.gauss_quad_n = 15, |
| 72 | ! |
jmpost.double_eps = 0, |
| 73 | ! |
jmpost.double_neg_eps = sqrt(.Machine$double.neg.eps) |
| 74 |
) |
|
| 75 | ! |
for (opt in names(jmpost_opts)) {
|
| 76 | ! |
if (!opt %in% current_opts) {
|
| 77 | ! |
options(jmpost_opts[opt]) |
| 78 |
} |
|
| 79 |
} |
|
| 80 | ! |
invisible() |
| 81 |
} |
| 1 |
#' @include generics.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 | ||
| 5 |
#' Standard Links |
|
| 6 |
#' |
|
| 7 |
#' @typed prior: Prior |
|
| 8 |
#' A [`Prior`] object. |
|
| 9 |
#' @typed model: LongitudinalModel |
|
| 10 |
#' A [`LongitudinalModel`] object. |
|
| 11 |
#' @param ... Not used. |
|
| 12 |
#' |
|
| 13 |
#' @description |
|
| 14 |
#' These functions are used to enable the use of the corresponding link function between |
|
| 15 |
#' the survival and longitudinal models in a joint model. Note that the exact implementation |
|
| 16 |
#' of the link function is model specific, see |
|
| 17 |
#' \code{vignette("Statistical Specifications", package = "jmpost")} for more details.
|
|
| 18 |
#' |
|
| 19 |
#' @name standard-link-user |
|
| 20 |
#' |
|
| 21 |
#' @examples |
|
| 22 |
#' linkNone() |
|
| 23 |
#' linkDSLD() |
|
| 24 |
#' linkTTG(prior_normal(0, 1), model = LongitudinalGSF()) |
|
| 25 |
NULL |
|
| 26 | ||
| 27 | ||
| 28 |
#' @describeIn standard-link-user No link (fit the survival and longitudinal models independently) |
|
| 29 |
#' @export |
|
| 30 |
#' |
|
| 31 |
#' @returns A `Link` or `LinkComponent` object representing the requested association. |
|
| 32 |
linkNone <- function() {
|
|
| 33 | 1x |
Link() |
| 34 |
} |
|
| 35 | ||
| 36 | ||
| 37 |
#' @describeIn standard-link-user Time to growth link |
|
| 38 |
#' @export |
|
| 39 |
linkTTG <- function(prior, model = PromiseLongitudinalModel(), ...) {
|
|
| 40 | 15x |
UseMethod("linkTTG", model)
|
| 41 |
} |
|
| 42 |
#' @export |
|
| 43 |
#' |
|
| 44 |
#' @returns A `PromiseLinkComponent` object. |
|
| 45 |
linkTTG.PromiseLongitudinalModel <- function( |
|
| 46 |
prior = prior_normal(0, 2), |
|
| 47 |
model, |
|
| 48 |
... |
|
| 49 |
) {
|
|
| 50 | 7x |
PromiseLinkComponent(fun = linkTTG, prior = prior, key = "link_ttg") |
| 51 |
} |
|
| 52 |
#' @export |
|
| 53 |
#' |
|
| 54 |
#' @returns This method always raises an error for unsupported models. |
|
| 55 |
linkTTG.default <- function(prior, model, ...) {
|
|
| 56 | 1x |
stop(sprintf( |
| 57 | 1x |
"Method `linkTTG` is not available for `%s`", |
| 58 | 1x |
class(model)[[1]] |
| 59 |
)) |
|
| 60 |
} |
|
| 61 | ||
| 62 | ||
| 63 |
#' @describeIn standard-link-user Derivative of the SLD over time link |
|
| 64 |
#' @export |
|
| 65 |
linkDSLD <- function(prior, model = PromiseLongitudinalModel(), ...) {
|
|
| 66 | 61x |
UseMethod("linkDSLD", model)
|
| 67 |
} |
|
| 68 |
#' @export |
|
| 69 |
#' |
|
| 70 |
#' @returns A `PromiseLinkComponent` object. |
|
| 71 |
linkDSLD.PromiseLongitudinalModel <- function( |
|
| 72 |
prior = prior_normal(0, 2), |
|
| 73 |
model, |
|
| 74 |
... |
|
| 75 |
) {
|
|
| 76 | 32x |
PromiseLinkComponent(fun = linkDSLD, prior = prior, key = "link_dsld") |
| 77 |
} |
|
| 78 |
#' @export |
|
| 79 |
#' |
|
| 80 |
#' @returns This method always raises an error for unsupported models. |
|
| 81 |
linkDSLD.default <- function(prior, model, ...) {
|
|
| 82 | ! |
stop(sprintf( |
| 83 | ! |
"Method `linkDSLD` is not available for `%s`", |
| 84 | ! |
class(model)[[1]] |
| 85 |
)) |
|
| 86 |
} |
|
| 87 | ||
| 88 | ||
| 89 |
#' @describeIn standard-link-user Current SLD value link |
|
| 90 |
#' @export |
|
| 91 |
linkIdentity <- function(prior, model = PromiseLongitudinalModel(), ...) {
|
|
| 92 | 15x |
UseMethod("linkIdentity", model)
|
| 93 |
} |
|
| 94 |
#' @export |
|
| 95 |
#' |
|
| 96 |
#' @returns A `PromiseLinkComponent` object. |
|
| 97 |
linkIdentity.PromiseLongitudinalModel <- function( |
|
| 98 |
prior = prior_normal(0, 2), |
|
| 99 |
model, |
|
| 100 |
... |
|
| 101 |
) {
|
|
| 102 | 7x |
PromiseLinkComponent( |
| 103 | 7x |
fun = linkIdentity, |
| 104 | 7x |
prior = prior, |
| 105 | 7x |
key = "link_identity" |
| 106 |
) |
|
| 107 |
} |
|
| 108 |
#' @export |
|
| 109 |
#' |
|
| 110 |
#' @returns This method always raises an error for unsupported models. |
|
| 111 |
linkIdentity.default <- function(prior, model, ...) {
|
|
| 112 | ! |
stop(sprintf( |
| 113 | ! |
"Method `linkIdentity` is not available for `%s`", |
| 114 | ! |
class(model)[[1]] |
| 115 |
)) |
|
| 116 |
} |
|
| 117 | ||
| 118 | ||
| 119 |
#' @describeIn standard-link-user Growth Parameter link |
|
| 120 |
#' @export |
|
| 121 |
linkGrowth <- function(prior, model = PromiseLongitudinalModel(), ...) {
|
|
| 122 | 20x |
UseMethod("linkGrowth", model)
|
| 123 |
} |
|
| 124 |
#' @export |
|
| 125 |
#' |
|
| 126 |
#' @returns A `PromiseLinkComponent` object. |
|
| 127 |
linkGrowth.PromiseLongitudinalModel <- function( |
|
| 128 |
prior = prior_normal(0, 2), |
|
| 129 |
model, |
|
| 130 |
... |
|
| 131 |
) {
|
|
| 132 | 10x |
PromiseLinkComponent(fun = linkGrowth, prior = prior, key = "link_growth") |
| 133 |
} |
|
| 134 |
#' @export |
|
| 135 |
#' |
|
| 136 |
#' @returns This method always raises an error for unsupported models. |
|
| 137 |
linkGrowth.default <- function(prior, model, ...) {
|
|
| 138 | ! |
stop(sprintf( |
| 139 | ! |
"Method `linkGrowth` is not available for `%s`", |
| 140 | ! |
class(model)[[1]] |
| 141 |
)) |
|
| 142 |
} |
|
| 143 | ||
| 144 | ||
| 145 |
#' @describeIn standard-link-user Shrinkage Parameter link |
|
| 146 |
#' @export |
|
| 147 |
linkShrinkage <- function(prior, model = PromiseLongitudinalModel(), ...) {
|
|
| 148 | 12x |
UseMethod("linkShrinkage", model)
|
| 149 |
} |
|
| 150 |
#' @export |
|
| 151 |
#' |
|
| 152 |
#' @returns A `PromiseLinkComponent` object. |
|
| 153 |
linkShrinkage.PromiseLongitudinalModel <- function( |
|
| 154 |
prior = prior_normal(0, 2), |
|
| 155 |
model, |
|
| 156 |
... |
|
| 157 |
) {
|
|
| 158 | 6x |
PromiseLinkComponent( |
| 159 | 6x |
fun = linkShrinkage, |
| 160 | 6x |
prior = prior, |
| 161 | 6x |
key = "link_shrinkage" |
| 162 |
) |
|
| 163 |
} |
|
| 164 |
#' @export |
|
| 165 |
#' |
|
| 166 |
#' @returns This method always raises an error for unsupported models. |
|
| 167 |
linkShrinkage.default <- function(prior, model, ...) {
|
|
| 168 | ! |
stop(sprintf( |
| 169 | ! |
"Method `linkShrinkage` is not available for `%s`", |
| 170 | ! |
class(model)[[1]] |
| 171 |
)) |
|
| 172 |
} |
| 1 |
#' @include StanModel.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
# LongitudinalModel-class ---- |
|
| 5 | ||
| 6 |
#' `LongitudinalModel` |
|
| 7 |
#' |
|
| 8 |
#' This class extends the general [`StanModel`] class to comprise the longitudinal |
|
| 9 |
#' model specification. |
|
| 10 |
#' |
|
| 11 |
#' @slot scaled_variance logical scalar, whether the variance should be scaled |
|
| 12 |
#' by the expected value, corresponding to a multiplicative model. If not, |
|
| 13 |
#' then an additive error model is used. |
|
| 14 |
#' |
|
| 15 |
#' @exportClass LongitudinalModel |
|
| 16 |
.LongitudinalModel <- setClass( |
|
| 17 |
Class = "LongitudinalModel", |
|
| 18 |
contains = "StanModel", |
|
| 19 |
slots = c( |
|
| 20 |
scaled_variance = "logical" |
|
| 21 |
) |
|
| 22 |
) |
|
| 23 | ||
| 24 |
# LongitudinalModel-constructors ---- |
|
| 25 | ||
| 26 |
#' @rdname LongitudinalModel-class |
|
| 27 |
#' |
|
| 28 |
#' @inheritParams stanmodel_arguments |
|
| 29 |
#' @typed scaled_variance: flag |
|
| 30 |
#' whether the variance should be scaled |
|
| 31 |
#' by the expected value, corresponding to a multiplicative model. If not, |
|
| 32 |
#' then an additive error model is used. |
|
| 33 |
#' |
|
| 34 |
#' @export |
|
| 35 |
#' |
|
| 36 |
#' @returns A `LongitudinalModel` object. |
|
| 37 |
#' |
|
| 38 |
#' @examples |
|
| 39 |
#' LongitudinalModel(scaled_variance = FALSE) |
|
| 40 |
LongitudinalModel <- function( |
|
| 41 |
stan = StanModule(), |
|
| 42 |
parameters = ParameterList(), |
|
| 43 |
name = "<Unnamed>", |
|
| 44 |
scaled_variance = NA, |
|
| 45 |
... |
|
| 46 |
) {
|
|
| 47 | 176x |
assert_flag(scaled_variance) |
| 48 | 176x |
base_stan <- read_stan("base/longitudinal.stan")
|
| 49 | ||
| 50 | 176x |
stan_full <- decorated_render( |
| 51 | 176x |
.x = base_stan, |
| 52 | 176x |
stan = add_missing_stan_blocks(as.list(stan)) |
| 53 |
) |
|
| 54 | ||
| 55 | 176x |
.LongitudinalModel( |
| 56 | 176x |
StanModel( |
| 57 | 176x |
stan = StanModule(stan_full), |
| 58 | 176x |
parameters = parameters, |
| 59 | 176x |
name = name, |
| 60 |
... |
|
| 61 |
), |
|
| 62 | 176x |
scaled_variance = scaled_variance |
| 63 |
) |
|
| 64 |
} |
|
| 65 | ||
| 66 |
#' Create longitudinal-model-specific Stan data |
|
| 67 |
#' |
|
| 68 |
#' @param model A [`LongitudinalModel`] object. |
|
| 69 |
#' @param subject A [`DataSubject`] object. |
|
| 70 |
#' |
|
| 71 |
#' @keywords internal |
|
| 72 |
#' @returns A named list of model-specific Stan data components. |
|
| 73 |
longitudinal_model_stan_data <- function(model, subject) {
|
|
| 74 | 90x |
UseMethod("longitudinal_model_stan_data")
|
| 75 |
} |
|
| 76 | ||
| 77 |
#' @rdname longitudinal_model_stan_data |
|
| 78 |
#' @keywords internal |
|
| 79 |
#' @export |
|
| 80 |
longitudinal_model_stan_data.default <- function(model, subject) {
|
|
| 81 | 60x |
list() |
| 82 |
} |
|
| 83 | ||
| 84 |
#' Extract covariate names for longitudinal model parameters |
|
| 85 |
#' |
|
| 86 |
#' Uses the subject-level design matrices prepared for fitting the model. |
|
| 87 |
#' Covariate names therefore include factor contrasts, transformations, and |
|
| 88 |
#' interactions, in the same order as the corresponding coefficients. |
|
| 89 |
#' Covariates are taken from subject data, rather than repeated measurements |
|
| 90 |
#' in a [`DataLongitudinal`] object. |
|
| 91 |
#' |
|
| 92 |
#' The default longitudinal-model method recognises design matrices named |
|
| 93 |
#' `<name>_design` paired with declared parameters named `<name>_coefficients`. |
|
| 94 |
#' Custom longitudinal models using another convention can provide their own |
|
| 95 |
#' `covariates()` method with the same return structure. |
|
| 96 |
#' |
|
| 97 |
#' @typed object: LongitudinalModel |
|
| 98 |
#' the longitudinal model specification. |
|
| 99 |
#' @typed data: DataSubject |
|
| 100 |
#' the subject data used to construct the model's design matrices. |
|
| 101 |
#' @param ... not used. |
|
| 102 |
#' |
|
| 103 |
#' @returns A named list of character vectors, keyed by coefficient parameter |
|
| 104 |
#' name. Intercept-only predictors have an empty character vector. Models |
|
| 105 |
#' without covariate predictors, such as [`LongitudinalSteinFojo`], return |
|
| 106 |
#' an empty list. |
|
| 107 |
#' |
|
| 108 |
#' @family covariates |
|
| 109 |
#' @export |
|
| 110 |
#' |
|
| 111 |
#' @examples |
|
| 112 |
#' subject <- DataSubject( |
|
| 113 |
#' data.frame( |
|
| 114 |
#' id = c("1", "2"),
|
|
| 115 |
#' arm = c("A", "B"),
|
|
| 116 |
#' study = "Study 1", |
|
| 117 |
#' age = c(50, 60) |
|
| 118 |
#' ), |
|
| 119 |
#' "id", "arm", "study" |
|
| 120 |
#' ) |
|
| 121 |
#' model <- LongitudinalSteinFojoCov(mu_g_formula = ~ arm + age) |
|
| 122 |
#' covariates(model, data = subject)$lm_sfc_mu_g_coefficients |
|
| 123 |
#' covariates(LongitudinalSteinFojo(), data = subject) |
|
| 124 |
covariates.LongitudinalModel <- function(object, data, ...) {
|
|
| 125 | 32x |
assert_class(data, "DataSubject") |
| 126 | 31x |
stan_data <- longitudinal_model_stan_data(object, subject = data) |
| 127 | 30x |
if (is.null(names(stan_data))) {
|
| 128 | 9x |
return(list()) |
| 129 |
} |
|
| 130 |
# Covariate predictors pair <name>_design with <name>_coefficients. |
|
| 131 | 21x |
design_names <- names(stan_data)[endsWith(names(stan_data), "_design")] |
| 132 | 21x |
parameters <- sub("_design$", "_coefficients", design_names)
|
| 133 | 21x |
selected <- parameters %in% names(getParameters(object)) |
| 134 | 21x |
designs <- stan_data[design_names[selected]] |
| 135 | 21x |
stats::setNames( |
| 136 | 21x |
lapply(designs, function(design) as.character(colnames(design))), |
| 137 | 21x |
parameters[selected] |
| 138 |
) |
|
| 139 |
} |
|
| 140 | ||
| 141 |
#' @export |
|
| 142 |
#' |
|
| 143 |
#' @returns A character vector suitable for printing. |
|
| 144 |
as_print_string.LongitudinalModel <- function(object, ...) {
|
|
| 145 | 12x |
string <- sprintf( |
| 146 | 12x |
"\n%s Longitudinal Model (%s error) with parameters:\n%s\n\n", |
| 147 | 12x |
object@name, |
| 148 | 12x |
if (is.na(object@scaled_variance)) {
|
| 149 |
# Should not happen but just to be robust against this case. |
|
| 150 | ! |
"unknown" |
| 151 | 12x |
} else if (object@scaled_variance) {
|
| 152 | ! |
"multiplicative" |
| 153 |
} else {
|
|
| 154 | 12x |
"additive" |
| 155 |
}, |
|
| 156 | 12x |
paste(" ", as_print_string(object@parameters)) |>
|
| 157 | 12x |
paste(collapse = "\n") |
| 158 |
) |
|
| 159 | 12x |
return(string) |
| 160 |
} |
| 1 |
#' @include generics.R |
|
| 2 |
#' @include Grid.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' @rdname Quant-Dev |
|
| 6 |
.QuantityGeneratorPopulation <- setClass( |
|
| 7 |
"QuantityGeneratorPopulation", |
|
| 8 |
contains = "QuantityGenerator", |
|
| 9 |
slots = c( |
|
| 10 |
"times" = "numeric", |
|
| 11 |
"studies" = "character_or_NULL", |
|
| 12 |
"arms" = "character_or_NULL", |
|
| 13 |
"newdata" = "data.frame_or_NULL" |
|
| 14 |
) |
|
| 15 |
) |
|
| 16 | ||
| 17 | ||
| 18 |
#' @rdname Quant-Dev |
|
| 19 |
QuantityGeneratorPopulation <- function( |
|
| 20 |
times, |
|
| 21 |
studies = NULL, |
|
| 22 |
arms = NULL, |
|
| 23 |
newdata = NULL |
|
| 24 |
) {
|
|
| 25 | 16x |
.QuantityGeneratorPopulation( |
| 26 | 16x |
times = times, |
| 27 | 16x |
studies = studies, |
| 28 | 16x |
arms = arms, |
| 29 | 16x |
newdata = newdata |
| 30 |
) |
|
| 31 |
} |
|
| 32 | ||
| 33 | ||
| 34 |
setValidity( |
|
| 35 |
"QuantityGeneratorPopulation", |
|
| 36 |
function(object) {
|
|
| 37 |
if (length(object@times) != length(object@arms)) {
|
|
| 38 |
return("Length of `times` and `arms` must be equal")
|
|
| 39 |
} |
|
| 40 |
if (length(object@times) != length(object@studies)) {
|
|
| 41 |
return("Length of `times` and `studies` must be equal")
|
|
| 42 |
} |
|
| 43 |
if ( |
|
| 44 |
!is.null(object@newdata) && |
|
| 45 |
length(object@times) != nrow(object@newdata) |
|
| 46 |
) {
|
|
| 47 |
return("Length of `times` and rows in `newdata` must be equal")
|
|
| 48 |
} |
|
| 49 |
return(TRUE) |
|
| 50 |
} |
|
| 51 |
) |
|
| 52 | ||
| 53 | ||
| 54 |
#' @rdname as_stan_list.QuantityGenerator |
|
| 55 |
#' @export |
|
| 56 |
as_stan_list.QuantityGeneratorPopulation <- function( |
|
| 57 |
object, |
|
| 58 |
data, |
|
| 59 |
model = NULL, |
|
| 60 |
... |
|
| 61 |
) {
|
|
| 62 | 4x |
assert_that( |
| 63 | 4x |
is(data, "DataJoint") |
| 64 |
) |
|
| 65 | 4x |
ret <- list() |
| 66 | 4x |
data_list <- as.list(data) |
| 67 | 4x |
ret[["gq_times"]] <- object@times |
| 68 | 4x |
ret[["gq_n_quant"]] <- length(object@arms) |
| 69 | 4x |
ret[["gq_long_pop_arm_index"]] <- data_list$arm_to_index[object@arms] |
| 70 | 4x |
ret[["gq_long_pop_study_index"]] <- data_list$study_to_index[object@studies] |
| 71 | ||
| 72 |
# Sanity checks |
|
| 73 | 4x |
assert_that( |
| 74 | 4x |
length(ret[["gq_long_pop_arm_index"]]) == |
| 75 | 4x |
length(ret[["gq_long_pop_study_index"]]), |
| 76 | 4x |
length(ret[["gq_long_pop_study_index"]]) == length(ret[["gq_times"]]), |
| 77 | 4x |
length(ret[["gq_long_pop_study_index"]]) == ret[["gq_n_quant"]], |
| 78 | 4x |
all(!is.na(ret[["gq_long_pop_arm_index"]])), |
| 79 | 4x |
all(!is.na(ret[["gq_long_pop_study_index"]])) |
| 80 |
) |
|
| 81 | ||
| 82 | 4x |
longitudinal_model <- if (is(model, "JointModel")) {
|
| 83 | 4x |
model@longitudinal |
| 84 |
} else {
|
|
| 85 | ! |
model |
| 86 |
} |
|
| 87 | 4x |
ret <- append( |
| 88 | 4x |
ret, |
| 89 | 4x |
gq_population_stan_data( |
| 90 | 4x |
object, |
| 91 | 4x |
model = longitudinal_model, |
| 92 | 4x |
data = data |
| 93 | 4x |
)$data |
| 94 |
) |
|
| 95 | 4x |
return(ret) |
| 96 |
} |
|
| 97 | ||
| 98 | ||
| 99 |
#' @rdname gq_population_stan_data |
|
| 100 |
#' @method gq_population_stan_data QuantityGeneratorPopulation |
|
| 101 |
#' @exportS3Method gq_population_stan_data QuantityGeneratorPopulation |
|
| 102 |
gq_population_stan_data.QuantityGeneratorPopulation <- function( |
|
| 103 |
object, |
|
| 104 |
model, |
|
| 105 |
data = NULL, |
|
| 106 |
... |
|
| 107 |
) {
|
|
| 108 |
# Note: We need to specify `model` here as second argument in order |
|
| 109 |
# to dispatch the appropriate method based on the class of `model`. |
|
| 110 | 15x |
UseMethod("gq_population_stan_data", model)
|
| 111 |
} |
| 1 |
#' @include Grid.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' @rdname Grid-Dev |
|
| 6 |
.GridEvent <- setClass( |
|
| 7 |
"GridEvent", |
|
| 8 |
contains = "Grid", |
|
| 9 |
slots = c( |
|
| 10 |
"subjects" = "character_or_NULL" |
|
| 11 |
) |
|
| 12 |
) |
|
| 13 | ||
| 14 | ||
| 15 |
#' @rdname Grid-Functions |
|
| 16 |
#' @export |
|
| 17 |
GridEvent <- function(subjects = NULL) {
|
|
| 18 | 1x |
.GridEvent( |
| 19 | 1x |
subjects = subjects |
| 20 |
) |
|
| 21 |
} |
|
| 22 | ||
| 23 |
#' @rdname Quant-Dev |
|
| 24 |
#' @export |
|
| 25 |
as.QuantityGenerator.GridEvent <- function(object, data, ...) {
|
|
| 26 | 3x |
assert_class(data, "DataJoint") |
| 27 | 3x |
assert_that( |
| 28 | 3x |
!is.null(data@survival), |
| 29 | 3x |
msg = "Survival data must have been provided to `DataJoint()` in order to use `GridEvent()`" |
| 30 |
) |
|
| 31 | 2x |
data_list <- as.list(data) |
| 32 | 2x |
subjects <- unlist(as.list(object, data = data), use.names = FALSE) |
| 33 | 2x |
event_times <- data_list$event_times[data_list$subject_to_index[subjects]] |
| 34 | 2x |
QuantityGeneratorSubject( |
| 35 | 2x |
times = event_times, |
| 36 | 2x |
subjects = subjects |
| 37 |
) |
|
| 38 |
} |
|
| 39 | ||
| 40 |
#' @rdname Quant-Dev |
|
| 41 |
#' @export |
|
| 42 |
as.QuantityCollapser.GridEvent <- function(object, data, ...) {
|
|
| 43 | 1x |
generator <- as.QuantityGenerator(object, data) |
| 44 | 1x |
QuantityCollapser( |
| 45 | 1x |
times = generator@times, |
| 46 | 1x |
groups = generator@subjects, |
| 47 | 1x |
indexes = as.list(seq_along(generator@times)) |
| 48 |
) |
|
| 49 |
} |
|
| 50 | ||
| 51 |
#' @export |
|
| 52 |
#' |
|
| 53 |
#' @returns A named `list` representation of the object. |
|
| 54 |
as.list.GridEvent <- function(x, data, ...) {
|
|
| 55 | 2x |
subjects_to_list(x@subjects, data) |
| 56 |
} |
| 1 |
#' @include SurvivalModel.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
# SurvivalExponential-class ---- |
|
| 5 | ||
| 6 |
#' `SurvivalExponential` |
|
| 7 |
#' |
|
| 8 |
#' This class extends the general [`SurvivalModel`] class for using the |
|
| 9 |
#' exponential survival model. |
|
| 10 |
#' |
|
| 11 |
#' @exportClass SurvivalExponential |
|
| 12 |
.SurvivalExponential <- setClass( |
|
| 13 |
Class = "SurvivalExponential", |
|
| 14 |
contains = "SurvivalModel" |
|
| 15 |
) |
|
| 16 | ||
| 17 |
# SurvivalExponential-constructors ---- |
|
| 18 | ||
| 19 |
#' @rdname SurvivalExponential-class |
|
| 20 |
#' |
|
| 21 |
#' @typed lambda: Prior |
|
| 22 |
#' for the exponential rate `lambda`. |
|
| 23 |
#' @typed beta: Prior |
|
| 24 |
#' for covariates coefficients `beta`. |
|
| 25 |
#' |
|
| 26 |
#' @export |
|
| 27 |
#' |
|
| 28 |
#' @returns A `SurvivalExponential` object. |
|
| 29 |
#' |
|
| 30 |
#' @examples |
|
| 31 |
#' SurvivalExponential() |
|
| 32 |
SurvivalExponential <- function( |
|
| 33 |
lambda = prior_gamma(2, 5), |
|
| 34 |
beta = prior_normal(0, 2) |
|
| 35 |
) {
|
|
| 36 | 31x |
lambda <- set_limits( |
| 37 | 31x |
lambda, |
| 38 | 31x |
lower = getOption("jmpost.double_eps"),
|
| 39 | 31x |
upper = 8000 |
| 40 |
) |
|
| 41 | 31x |
.SurvivalExponential( |
| 42 | 31x |
SurvivalModel( |
| 43 | 31x |
name = "Exponential", |
| 44 | 31x |
stan = StanModule("sm-exponential/model.stan"),
|
| 45 | 31x |
parameters = ParameterList( |
| 46 | 31x |
Parameter(name = "sm_exp_lambda", prior = lambda, size = 1), |
| 47 | 31x |
Parameter( |
| 48 | 31x |
name = "beta_os_cov", |
| 49 | 31x |
prior = beta, |
| 50 | 31x |
size = "p_os_cov_design" |
| 51 |
) |
|
| 52 |
) |
|
| 53 |
) |
|
| 54 |
) |
|
| 55 |
} |
| 1 |
#' @include SimLongitudinalSteinFojo.R |
|
| 2 |
#' @include LongitudinalSteinFojoCov.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Simulate a Stein-Fojo longitudinal model with covariates |
|
| 6 |
#' |
|
| 7 |
#' @exportClass SimLongitudinalSteinFojoCov |
|
| 8 |
.SimLongitudinalSteinFojoCov <- setClass( |
|
| 9 |
"SimLongitudinalSteinFojoCov", |
|
| 10 |
contains = "SimLongitudinal", |
|
| 11 |
slots = c( |
|
| 12 |
mu_b_formula = "formula", |
|
| 13 |
omega_b_formula = "formula", |
|
| 14 |
mu_s_formula = "formula", |
|
| 15 |
omega_s_formula = "formula", |
|
| 16 |
mu_g_formula = "formula", |
|
| 17 |
omega_g_formula = "formula", |
|
| 18 |
mu_b_parametrization = "character", |
|
| 19 |
omega_b_parametrization = "character", |
|
| 20 |
mu_s_parametrization = "character", |
|
| 21 |
omega_s_parametrization = "character", |
|
| 22 |
mu_g_parametrization = "character", |
|
| 23 |
omega_g_parametrization = "character", |
|
| 24 |
mu_b_intercept = "numeric", |
|
| 25 |
mu_b_coefficients = "numeric", |
|
| 26 |
omega_b_intercept = "numeric", |
|
| 27 |
omega_b_coefficients = "numeric", |
|
| 28 |
mu_s_intercept = "numeric", |
|
| 29 |
mu_s_coefficients = "numeric", |
|
| 30 |
omega_s_intercept = "numeric", |
|
| 31 |
omega_s_coefficients = "numeric", |
|
| 32 |
mu_g_intercept = "numeric", |
|
| 33 |
mu_g_coefficients = "numeric", |
|
| 34 |
omega_g_intercept = "numeric", |
|
| 35 |
omega_g_coefficients = "numeric", |
|
| 36 |
sigma = "numeric", |
|
| 37 |
link_dsld = "numeric", |
|
| 38 |
link_ttg = "numeric", |
|
| 39 |
link_identity = "numeric", |
|
| 40 |
link_growth = "numeric", |
|
| 41 |
link_shrinkage = "numeric", |
|
| 42 |
scaled_variance = "logical" |
|
| 43 |
) |
|
| 44 |
) |
|
| 45 | ||
| 46 |
#' Construct a covariate Stein-Fojo simulator |
|
| 47 |
#' |
|
| 48 |
#' @inheritParams LongitudinalSteinFojoCov |
|
| 49 |
#' @typed times: numeric |
|
| 50 |
#' Observation times. |
|
| 51 |
#' @typed mu_b_intercept: numeric |
|
| 52 |
#' Intercept for the baseline mean predictor. |
|
| 53 |
#' @typed mu_b_coefficients: numeric |
|
| 54 |
#' Covariate coefficients for the baseline mean predictor. |
|
| 55 |
#' @typed omega_b_intercept: numeric |
|
| 56 |
#' Intercept for the baseline standard deviation predictor. |
|
| 57 |
#' @typed omega_b_coefficients: numeric |
|
| 58 |
#' Covariate coefficients for the baseline standard deviation predictor. |
|
| 59 |
#' @typed mu_s_intercept: numeric |
|
| 60 |
#' Intercept for the shrinkage mean predictor. |
|
| 61 |
#' @typed mu_s_coefficients: numeric |
|
| 62 |
#' Covariate coefficients for the shrinkage mean predictor. |
|
| 63 |
#' @typed omega_s_intercept: numeric |
|
| 64 |
#' Intercept for the shrinkage standard deviation predictor. |
|
| 65 |
#' @typed omega_s_coefficients: numeric |
|
| 66 |
#' Covariate coefficients for the shrinkage standard deviation predictor. |
|
| 67 |
#' @typed mu_g_intercept: numeric |
|
| 68 |
#' Intercept for the growth mean predictor. |
|
| 69 |
#' @typed mu_g_coefficients: numeric |
|
| 70 |
#' Covariate coefficients for the growth mean predictor. |
|
| 71 |
#' @typed omega_g_intercept: numeric |
|
| 72 |
#' Intercept for the growth standard deviation predictor. |
|
| 73 |
#' @typed omega_g_coefficients: numeric |
|
| 74 |
#' Covariate coefficients for the growth standard deviation predictor. |
|
| 75 |
#' @typed sigma: numeric |
|
| 76 |
#' Observation-error standard deviation. |
|
| 77 |
#' @typed link_dsld: numeric |
|
| 78 |
#' Coefficient for the DSLD association link. |
|
| 79 |
#' @typed link_ttg: numeric |
|
| 80 |
#' Coefficient for the TTG association link. |
|
| 81 |
#' @typed link_identity: numeric |
|
| 82 |
#' Coefficient for the identity association link. |
|
| 83 |
#' @typed link_growth: numeric |
|
| 84 |
#' Coefficient for the growth association link. |
|
| 85 |
#' @typed link_shrinkage: numeric |
|
| 86 |
#' Coefficient for the shrinkage association link. |
|
| 87 |
#' |
|
| 88 |
#' @rdname SimLongitudinalSteinFojoCov-class |
|
| 89 |
#' @returns A `SimLongitudinalSteinFojoCov` object. |
|
| 90 |
#' @export |
|
| 91 |
SimLongitudinalSteinFojoCov <- function( |
|
| 92 |
times = c(-100, -50, 0, 50, 100, 150, 250, 350, 450, 550) / 365, |
|
| 93 |
mu_b_formula = ~study, |
|
| 94 |
omega_b_formula = ~study, |
|
| 95 |
mu_s_formula = ~arm, |
|
| 96 |
omega_s_formula = ~arm, |
|
| 97 |
mu_g_formula = ~arm, |
|
| 98 |
omega_g_formula = ~arm, |
|
| 99 |
mu_b_parametrization = "linear", |
|
| 100 |
omega_b_parametrization = "log-linear", |
|
| 101 |
mu_s_parametrization = "linear", |
|
| 102 |
omega_s_parametrization = "log-linear", |
|
| 103 |
mu_g_parametrization = "linear", |
|
| 104 |
omega_g_parametrization = "log-linear", |
|
| 105 |
mu_b_intercept = log(60), |
|
| 106 |
mu_b_coefficients = numeric(), |
|
| 107 |
omega_b_intercept = log(0.2), |
|
| 108 |
omega_b_coefficients = numeric(), |
|
| 109 |
mu_s_intercept = log(0.5), |
|
| 110 |
mu_s_coefficients = numeric(), |
|
| 111 |
omega_s_intercept = log(0.2), |
|
| 112 |
omega_s_coefficients = numeric(), |
|
| 113 |
mu_g_intercept = log(0.3), |
|
| 114 |
mu_g_coefficients = numeric(), |
|
| 115 |
omega_g_intercept = log(0.2), |
|
| 116 |
omega_g_coefficients = numeric(), |
|
| 117 |
sigma = 0.01, |
|
| 118 |
link_dsld = 0, |
|
| 119 |
link_ttg = 0, |
|
| 120 |
link_identity = 0, |
|
| 121 |
link_growth = 0, |
|
| 122 |
link_shrinkage = 0, |
|
| 123 |
scaled_variance = FALSE |
|
| 124 |
) {
|
|
| 125 | 6x |
.SimLongitudinalSteinFojoCov( |
| 126 | 6x |
times = times, |
| 127 | 6x |
mu_b_formula = .validate_covariate_formula(mu_b_formula, "mu_b_formula"), |
| 128 | 6x |
omega_b_formula = .validate_covariate_formula(omega_b_formula, "omega_b_formula"), |
| 129 | 6x |
mu_s_formula = .validate_covariate_formula(mu_s_formula, "mu_s_formula"), |
| 130 | 6x |
omega_s_formula = .validate_covariate_formula(omega_s_formula, "omega_s_formula"), |
| 131 | 6x |
mu_g_formula = .validate_covariate_formula(mu_g_formula, "mu_g_formula"), |
| 132 | 6x |
omega_g_formula = .validate_covariate_formula(omega_g_formula, "omega_g_formula"), |
| 133 | 6x |
mu_b_parametrization = .validate_covariate_parametrization(mu_b_parametrization, "mu_b_parametrization"), |
| 134 | 6x |
omega_b_parametrization = .validate_covariate_parametrization( |
| 135 | 6x |
omega_b_parametrization, |
| 136 | 6x |
"omega_b_parametrization", |
| 137 | 6x |
scale = TRUE |
| 138 |
), |
|
| 139 | 6x |
mu_s_parametrization = .validate_covariate_parametrization(mu_s_parametrization, "mu_s_parametrization"), |
| 140 | 6x |
omega_s_parametrization = .validate_covariate_parametrization( |
| 141 | 6x |
omega_s_parametrization, |
| 142 | 6x |
"omega_s_parametrization", |
| 143 | 6x |
scale = TRUE |
| 144 |
), |
|
| 145 | 6x |
mu_g_parametrization = .validate_covariate_parametrization(mu_g_parametrization, "mu_g_parametrization"), |
| 146 | 6x |
omega_g_parametrization = .validate_covariate_parametrization( |
| 147 | 6x |
omega_g_parametrization, |
| 148 | 6x |
"omega_g_parametrization", |
| 149 | 6x |
scale = TRUE |
| 150 |
), |
|
| 151 | 6x |
mu_b_intercept = mu_b_intercept, |
| 152 | 6x |
mu_b_coefficients = mu_b_coefficients, |
| 153 | 6x |
omega_b_intercept = omega_b_intercept, |
| 154 | 6x |
omega_b_coefficients = omega_b_coefficients, |
| 155 | 6x |
mu_s_intercept = mu_s_intercept, |
| 156 | 6x |
mu_s_coefficients = mu_s_coefficients, |
| 157 | 6x |
omega_s_intercept = omega_s_intercept, |
| 158 | 6x |
omega_s_coefficients = omega_s_coefficients, |
| 159 | 6x |
mu_g_intercept = mu_g_intercept, |
| 160 | 6x |
mu_g_coefficients = mu_g_coefficients, |
| 161 | 6x |
omega_g_intercept = omega_g_intercept, |
| 162 | 6x |
omega_g_coefficients = omega_g_coefficients, |
| 163 | 6x |
sigma = sigma, |
| 164 | 6x |
link_dsld = link_dsld, |
| 165 | 6x |
link_ttg = link_ttg, |
| 166 | 6x |
link_identity = link_identity, |
| 167 | 6x |
link_growth = link_growth, |
| 168 | 6x |
link_shrinkage = link_shrinkage, |
| 169 | 6x |
scaled_variance = scaled_variance |
| 170 |
) |
|
| 171 |
} |
|
| 172 | ||
| 173 |
#' @rdname sampleSubjects |
|
| 174 |
#' @export |
|
| 175 |
sampleSubjects.SimLongitudinalSteinFojoCov <- function(object, subjects_df) {
|
|
| 176 | 1x |
assert_that( |
| 177 | 1x |
nrow(subjects_df) == length(unique(subjects_df[["subject"]])), |
| 178 | 1x |
msg = "The number of rows in `subjects_df` should equal the number of unique subjects" |
| 179 |
) |
|
| 180 | 1x |
parameter_names <- c("mu_b", "omega_b", "mu_s", "omega_s", "mu_g", "omega_g")
|
| 181 | 1x |
predicted <- lapply(parameter_names, function(name) {
|
| 182 | 6x |
design <- .covariate_design_matrix( |
| 183 | 6x |
slot(object, paste0(name, "_formula")), |
| 184 | 6x |
subjects_df, |
| 185 | 6x |
paste0(name, "_formula") |
| 186 |
) |
|
| 187 | 6x |
coefficients <- .simulation_coefficients( |
| 188 | 6x |
slot(object, paste0(name, "_coefficients")), |
| 189 | 6x |
ncol(design), |
| 190 | 6x |
paste0(name, "_coefficients") |
| 191 |
) |
|
| 192 | 6x |
.covariate_predictor_r( |
| 193 | 6x |
design, |
| 194 | 6x |
slot(object, paste0(name, "_intercept")), |
| 195 | 6x |
coefficients, |
| 196 | 6x |
slot(object, paste0(name, "_parametrization")) |
| 197 |
) |
|
| 198 |
}) |
|
| 199 | 1x |
names(predicted) <- parameter_names |
| 200 | 1x |
for (name in c("omega_b", "omega_s", "omega_g")) {
|
| 201 | 3x |
assert_that( |
| 202 | 3x |
all(predicted[[name]] > 0), |
| 203 | 3x |
msg = sprintf("The %s predictor must be positive for every subject", name)
|
| 204 |
) |
|
| 205 |
} |
|
| 206 | ||
| 207 | 1x |
subjects_df |> |
| 208 | 1x |
dplyr::mutate( |
| 209 | 1x |
psi_b = stats::rlnorm(dplyr::n(), predicted$mu_b, predicted$omega_b), |
| 210 | 1x |
psi_s = stats::rlnorm(dplyr::n(), predicted$mu_s, predicted$omega_s), |
| 211 | 1x |
psi_g = stats::rlnorm(dplyr::n(), predicted$mu_g, predicted$omega_g) |
| 212 |
) |
|
| 213 |
} |
|
| 214 | ||
| 215 |
#' @rdname sampleObservations |
|
| 216 |
#' @export |
|
| 217 |
sampleObservations.SimLongitudinalSteinFojoCov <- function(object, times_df) {
|
|
| 218 | 1x |
times_df |> |
| 219 | 1x |
dplyr::mutate( |
| 220 | 1x |
mu_sld = sf_sld(.data$time, .data$psi_b, .data$psi_s, .data$psi_g), |
| 221 | 1x |
dsld = sf_dsld(.data$time, .data$psi_b, .data$psi_s, .data$psi_g), |
| 222 | 1x |
ttg = sf_ttg(.data$time, .data$psi_b, .data$psi_s, .data$psi_g), |
| 223 | 1x |
sld_sd = ifelse( |
| 224 | 1x |
object@scaled_variance, |
| 225 | 1x |
pmax(.data$mu_sld * object@sigma, .Machine$double.eps), |
| 226 | 1x |
object@sigma |
| 227 |
), |
|
| 228 | 1x |
sld = stats::rnorm(dplyr::n(), .data$mu_sld, .data$sld_sd), |
| 229 | 1x |
log_haz_link = (object@link_dsld * .data$dsld) + |
| 230 | 1x |
(object@link_ttg * .data$ttg) + |
| 231 | 1x |
(object@link_identity * .data$mu_sld) + |
| 232 | 1x |
(object@link_growth * log(.data$psi_g)) + |
| 233 | 1x |
(object@link_shrinkage * log(.data$psi_s)) |
| 234 |
) |
|
| 235 |
} |
|
| 236 | ||
| 237 |
#' @rdname as_print_string |
|
| 238 |
#' @exportS3Method NULL |
|
| 239 |
as_print_string.SimLongitudinalSteinFojoCov <- function(object, ...) {
|
|
| 240 | ! |
"SimLongitudinalSteinFojoCov" |
| 241 |
} |
| 1 |
#' Abstract Simulation Class for Longitudinal Data |
|
| 2 |
#' |
|
| 3 |
#' @typed times: numeric |
|
| 4 |
#' the times to generate observations at. |
|
| 5 |
#' |
|
| 6 |
#' @description |
|
| 7 |
#' This class exists to be extended by other classes that simulate longitudinal data. |
|
| 8 |
#' It is not intended to be used directly. |
|
| 9 |
#' @name SimLongitudinal-class |
|
| 10 |
#' @family SimLongitudinal |
|
| 11 |
#' @exportClass SimLongitudinal |
|
| 12 |
#' |
|
| 13 |
#' @examples |
|
| 14 |
#' SimLongitudinal(times = c(0, 30, 60)) |
|
| 15 |
.SimLongitudinal <- setClass( |
|
| 16 |
"SimLongitudinal", |
|
| 17 |
slots = list( |
|
| 18 |
times = "numeric" |
|
| 19 |
) |
|
| 20 |
) |
|
| 21 | ||
| 22 |
#' @rdname SimLongitudinal-class |
|
| 23 |
#' @export |
|
| 24 |
#' |
|
| 25 |
#' @returns A `SimLongitudinal` object. |
|
| 26 |
SimLongitudinal <- function(times = seq(0, 100, 50)) {
|
|
| 27 | ! |
.SimLongitudinal(times = times) |
| 28 |
} |
|
| 29 | ||
| 30 | ||
| 31 |
#' @rdname show-object |
|
| 32 |
#' @export |
|
| 33 |
setMethod( |
|
| 34 |
f = "show", |
|
| 35 |
signature = "SimLongitudinal", |
|
| 36 |
definition = function(object) {
|
|
| 37 | 4x |
x <- sprintf("\nA %s Object\n\n", as_print_string(object))
|
| 38 | 4x |
cat(x) |
| 39 | 4x |
return(object) |
| 40 |
} |
|
| 41 |
) |
|
| 42 | ||
| 43 |
#' @rdname as_print_string |
|
| 44 |
#' @exportS3Method NULL |
|
| 45 |
as_print_string.SimLongitudinal <- function(object, ...) {
|
|
| 46 | ! |
return("SimLongitudinal")
|
| 47 |
} |
| 1 |
#' Promise |
|
| 2 |
#' |
|
| 3 |
#' Abstract class for promise objects to inherit off of |
|
| 4 |
#' @aliases Promise |
|
| 5 |
#' @exportClass Promise |
|
| 6 |
.Promise <- setClass("Promise")
|
|
| 7 | ||
| 8 | ||
| 9 |
#' Promise of a `LongitudinalModel` |
|
| 10 |
#' |
|
| 11 |
#' An object that promises to resolve to a [`LongitudinalModel`] object. |
|
| 12 |
#' |
|
| 13 |
#' @exportClass PromiseLongitudinalModel |
|
| 14 |
.PromiseLongitudinalModel <- setClass( |
|
| 15 |
"PromiseLongitudinalModel", |
|
| 16 |
contains = "Promise" |
|
| 17 |
) |
|
| 18 | ||
| 19 |
#' @rdname PromiseLongitudinalModel-class |
|
| 20 |
#' @export |
|
| 21 |
#' |
|
| 22 |
#' @returns A `PromiseLongitudinalModel` object. |
|
| 23 |
#' |
|
| 24 |
#' @examples |
|
| 25 |
#' PromiseLongitudinalModel() |
|
| 26 |
PromiseLongitudinalModel <- function() {
|
|
| 27 | 62x |
.PromiseLongitudinalModel() |
| 28 |
} |
|
| 29 | ||
| 30 | ||
| 31 |
#' Promise of a `LinkComponent` |
|
| 32 |
#' |
|
| 33 |
#' An object that promises to resolve to a [`LinkComponent`] object. |
|
| 34 |
#' Inheriting from [`Promise`] and [`LinkComponent`]. |
|
| 35 |
#' |
|
| 36 |
#' @slot fun (`function`) \cr a function that returns a `LinkComponent`. See details. |
|
| 37 |
#' |
|
| 38 |
#' @typed fun: function |
|
| 39 |
#' a function that returns a `LinkComponent`. See details. |
|
| 40 |
#' @inheritParams LinkComponent |
|
| 41 |
#' |
|
| 42 |
#' @details |
|
| 43 |
#' |
|
| 44 |
#' The `fun` slot should be a function of signature `function(prior, model)` and should return |
|
| 45 |
#' a [`LinkComponent`] object. An error will be thrown if the returned [`LinkComponent`] object |
|
| 46 |
#' does not have the same `key` slot value as the original `PromiseLinkComponent`. |
|
| 47 |
#' |
|
| 48 |
#' @exportClass PromiseLinkComponent |
|
| 49 |
.PromiseLinkComponent <- setClass( |
|
| 50 |
"PromiseLinkComponent", |
|
| 51 |
contains = c("Promise", "LinkComponent"),
|
|
| 52 |
slots = list( |
|
| 53 |
fun = "function" |
|
| 54 |
) |
|
| 55 |
) |
|
| 56 | ||
| 57 |
#' @rdname PromiseLinkComponent-class |
|
| 58 |
#' @export |
|
| 59 |
#' |
|
| 60 |
#' @returns A `PromiseLinkComponent` object. |
|
| 61 |
#' |
|
| 62 |
#' @examples |
|
| 63 |
#' promise <- PromiseLinkComponent(linkDSLD, prior_normal(0, 2), "link_dsld") |
|
| 64 |
#' resolvePromise(promise, LongitudinalGSF()) |
|
| 65 |
PromiseLinkComponent <- function(fun, prior, key) {
|
|
| 66 | 64x |
.PromiseLinkComponent( |
| 67 | 64x |
fun = fun, |
| 68 | 64x |
stan = StanModule(), |
| 69 | 64x |
parameters = ParameterList(Parameter( |
| 70 | 64x |
name = key, |
| 71 | 64x |
prior = prior, |
| 72 | 64x |
size = 1 |
| 73 |
)), |
|
| 74 | 64x |
key = key |
| 75 |
) |
|
| 76 |
} |
|
| 77 | ||
| 78 | ||
| 79 |
#' @export |
|
| 80 |
#' |
|
| 81 |
#' @returns A `StanModule` object. |
|
| 82 |
as.StanModule.PromiseLinkComponent <- function(object, model, ...) {
|
|
| 83 | 1x |
resolved_object <- resolvePromise(object, model = model) |
| 84 | 1x |
as.StanModule(resolved_object, ...) |
| 85 |
} |
|
| 86 | ||
| 87 |
#' Resolve a `PromiseLinkComponent` |
|
| 88 |
#' |
|
| 89 |
#' Resolves a [`PromiseLinkComponent`] object to a [`LinkComponent`] object. |
|
| 90 |
#' An error will be thrown if the returned [`LinkComponent`] object |
|
| 91 |
#' does not have the same `key` slot value as the original [`PromiseLinkComponent`]. |
|
| 92 |
#' |
|
| 93 |
#' @typed object: PromiseLinkComponent |
|
| 94 |
#' the promise to resolve |
|
| 95 |
#' @typed model: LongitudinalModel |
|
| 96 |
#' the model to resolve the promise with |
|
| 97 |
#' @param ... Not used. |
|
| 98 |
#' @typedreturn LinkComponent |
|
| 99 |
#' the resolved `LinkComponent` object |
|
| 100 |
#' |
|
| 101 |
#' @export |
|
| 102 |
#' |
|
| 103 |
#' @returns The resolved `LinkComponent` object. |
|
| 104 |
resolvePromise.PromiseLinkComponent <- function(object, model, ...) {
|
|
| 105 | 57x |
x <- object@fun( |
| 106 | 57x |
prior = object@parameters@parameters[[1]]@prior, |
| 107 | 57x |
model = model |
| 108 |
) |
|
| 109 | 57x |
assert_that( |
| 110 | 57x |
is(x, "LinkComponent"), |
| 111 | 57x |
msg = "Resolved `PromiseLinkComponent` did not produce a `LinkComponent` object" |
| 112 |
) |
|
| 113 | 57x |
assert_that( |
| 114 | 57x |
names(object) == names(x), |
| 115 | 57x |
msg = paste( |
| 116 | 57x |
"Resolved `PromiseLinkComponent` did not produce a `LinkComponent` object", |
| 117 | 57x |
"with the same key as the promise" |
| 118 |
) |
|
| 119 |
) |
|
| 120 | 56x |
x |
| 121 |
} |
| 1 |
#' @include LongitudinalModel.R |
|
| 2 |
#' @include Link.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
# LongitudinalRandomSlope-class ---- |
|
| 6 | ||
| 7 |
#' `LongitudinalRandomSlope` |
|
| 8 |
#' |
|
| 9 |
#' This class extends the general [`LongitudinalModel`] class for using the |
|
| 10 |
#' random slope linear model for the longitudinal outcome. |
|
| 11 |
#' |
|
| 12 |
#' @section Available Links: |
|
| 13 |
#' - [`linkDSLD()`] |
|
| 14 |
#' - [`linkIdentity()`] |
|
| 15 |
#' @exportClass LongitudinalRandomSlope |
|
| 16 |
.LongitudinalRandomSlope <- setClass( |
|
| 17 |
Class = "LongitudinalRandomSlope", |
|
| 18 |
contains = "LongitudinalModel" |
|
| 19 |
) |
|
| 20 | ||
| 21 | ||
| 22 |
# LongitudinalRandomSlope-constructors ---- |
|
| 23 | ||
| 24 |
#' @rdname LongitudinalRandomSlope-class |
|
| 25 |
#' |
|
| 26 |
#' @typed intercept: Prior |
|
| 27 |
#' for the `intercept`. |
|
| 28 |
#' @typed slope_mu: Prior |
|
| 29 |
#' for the population slope `slope_mu` (one per arm). |
|
| 30 |
#' @typed slope_sigma: Prior |
|
| 31 |
#' for the random slope standard deviation `slope_sigma` (one per arm). |
|
| 32 |
#' @typed sigma: Prior |
|
| 33 |
#' for the variance of the longitudinal values `sigma`. |
|
| 34 |
#' @typed scaled_variance: logical |
|
| 35 |
#' whether the variance should be scaled by the expected value, corresponding to a multiplicative model. |
|
| 36 |
#' As a default, the variance is not scaled by the expected value, corresponding to an additive model. |
|
| 37 |
#' (See the "Statistical Specifications" vignette for more details.) |
|
| 38 |
#' |
|
| 39 |
#' @export |
|
| 40 |
#' |
|
| 41 |
#' @returns A `LongitudinalRandomSlope` object. |
|
| 42 |
#' |
|
| 43 |
#' @examples |
|
| 44 |
#' LongitudinalRandomSlope() |
|
| 45 |
LongitudinalRandomSlope <- function( |
|
| 46 |
intercept = prior_normal(30, 10), |
|
| 47 |
slope_mu = prior_normal(1, 3), |
|
| 48 |
slope_sigma = prior_lognormal(0, 1.5), |
|
| 49 |
sigma = prior_lognormal(0, 1.5), |
|
| 50 |
scaled_variance = FALSE |
|
| 51 |
) {
|
|
| 52 | 38x |
stan <- StanModule(decorated_render( |
| 53 | 38x |
.x = read_stan("lm-random-slope/model.stan"),
|
| 54 | 38x |
scaled_variance = scaled_variance |
| 55 |
)) |
|
| 56 | ||
| 57 |
# Apply constriants |
|
| 58 | 38x |
sigma <- set_limits(sigma, lower = getOption("jmpost.double_eps"))
|
| 59 | 38x |
slope_sigma <- set_limits(slope_sigma, lower = getOption("jmpost.double_eps"))
|
| 60 | ||
| 61 | 38x |
.LongitudinalRandomSlope( |
| 62 | 38x |
LongitudinalModel( |
| 63 | 38x |
name = "Random Slope", |
| 64 | 38x |
stan = stan, |
| 65 | 38x |
scaled_variance = scaled_variance, |
| 66 | 38x |
parameters = ParameterList( |
| 67 | 38x |
Parameter( |
| 68 | 38x |
name = "lm_rs_intercept", |
| 69 | 38x |
prior = intercept, |
| 70 | 38x |
size = "n_studies" |
| 71 |
), |
|
| 72 | 38x |
Parameter( |
| 73 | 38x |
name = "lm_rs_slope_mu", |
| 74 | 38x |
prior = slope_mu, |
| 75 | 38x |
size = "n_arms" |
| 76 |
), |
|
| 77 | 38x |
Parameter( |
| 78 | 38x |
name = "lm_rs_slope_sigma", |
| 79 | 38x |
prior = slope_sigma, |
| 80 | 38x |
size = "n_arms" |
| 81 |
), |
|
| 82 | 38x |
Parameter(name = "lm_rs_sigma", prior = sigma, size = 1), |
| 83 | 38x |
Parameter( |
| 84 | 38x |
name = "lm_rs_ind_rnd_slope", |
| 85 | 38x |
prior = prior_init_only(prior_normal( |
| 86 | 38x |
median(slope_mu), |
| 87 | 38x |
median(slope_sigma) |
| 88 |
)), |
|
| 89 | 38x |
size = "n_subjects" |
| 90 |
) |
|
| 91 |
) |
|
| 92 |
) |
|
| 93 |
) |
|
| 94 |
} |
|
| 95 | ||
| 96 | ||
| 97 |
#' @export |
|
| 98 |
#' |
|
| 99 |
#' @returns A `StanModule` object containing the generated-quantities code. |
|
| 100 |
enableGQ.LongitudinalRandomSlope <- function(object, ...) {
|
|
| 101 | 31x |
StanModule("lm-random-slope/quantities.stan")
|
| 102 |
} |
|
| 103 | ||
| 104 |
#' @export |
|
| 105 |
#' |
|
| 106 |
#' @returns The longitudinal model with its link-related Stan code enabled. |
|
| 107 |
enableLink.LongitudinalRandomSlope <- function(object, ...) {
|
|
| 108 | 8x |
object@stan <- merge( |
| 109 | 8x |
object@stan, |
| 110 | 8x |
StanModule("lm-random-slope/link.stan")
|
| 111 |
) |
|
| 112 | 8x |
object |
| 113 |
} |
|
| 114 | ||
| 115 | ||
| 116 |
#' @export |
|
| 117 |
#' |
|
| 118 |
#' @returns A `LinkComponent` object. |
|
| 119 |
linkDSLD.LongitudinalRandomSlope <- function( |
|
| 120 |
prior = prior_normal(0, 2), |
|
| 121 |
model, |
|
| 122 |
... |
|
| 123 |
) {
|
|
| 124 | 9x |
LinkComponent( |
| 125 | 9x |
key = "link_dsld", |
| 126 | 9x |
stan = StanModule("lm-random-slope/link_dsld.stan"),
|
| 127 | 9x |
prior = prior |
| 128 |
) |
|
| 129 |
} |
|
| 130 | ||
| 131 |
#' @export |
|
| 132 |
#' |
|
| 133 |
#' @returns A `LinkComponent` object. |
|
| 134 |
linkIdentity.LongitudinalRandomSlope <- function( |
|
| 135 |
prior = prior_normal(0, 2), |
|
| 136 |
model, |
|
| 137 |
... |
|
| 138 |
) {
|
|
| 139 | 3x |
LinkComponent( |
| 140 | 3x |
key = "link_identity", |
| 141 | 3x |
stan = StanModule("lm-random-slope/link_identity.stan"),
|
| 142 | 3x |
prior = prior |
| 143 |
) |
|
| 144 |
} |
|
| 145 | ||
| 146 |
#' @export |
|
| 147 |
#' |
|
| 148 |
#' @returns A `LinkComponent` object. |
|
| 149 |
linkGrowth.LongitudinalRandomSlope <- function( |
|
| 150 |
prior = prior_normal(0, 2), |
|
| 151 |
model, |
|
| 152 |
... |
|
| 153 |
) {
|
|
| 154 | ! |
LinkComponent( |
| 155 | ! |
key = "link_growth", |
| 156 | ! |
stan = StanModule("lm-random-slope/link_growth.stan"),
|
| 157 | ! |
prior = prior |
| 158 |
) |
|
| 159 |
} |
|
| 160 | ||
| 161 |
#' @rdname getPredictionNames |
|
| 162 |
#' @export |
|
| 163 |
getPredictionNames.LongitudinalRandomSlope <- function(object, ...) {
|
|
| 164 | 1x |
c("intercept", "slope")
|
| 165 |
} |
|
| 166 | ||
| 167 | ||
| 168 |
#' @rdname getRandomEffectsNames |
|
| 169 |
#' @export |
|
| 170 |
getRandomEffectsNames.LongitudinalRandomSlope <- function(object, ...) {
|
|
| 171 | ! |
c("slope" = "lm_rs_ind_rnd_slope")
|
| 172 |
} |
| 1 |
#' Quantity Grid Specification |
|
| 2 |
#' |
|
| 3 |
#' @typed subjects: "`character` or `NULL`" |
|
| 4 |
#' vector of subjects to extract quantities for. |
|
| 5 |
#' If `NULL` will default to all subjects within the dataset. |
|
| 6 |
#' |
|
| 7 |
#' @typed times: "`numeric` or `NULL`" |
|
| 8 |
#' vector of time points to extract quantities at. |
|
| 9 |
#' If `NULL` will default to 201 evenly spaced timepoints between 0 and either the max |
|
| 10 |
#' observation time (for [`LongitudinalQuantities`]) or max event time (for [`SurvivalQuantities`]). |
|
| 11 |
#' |
|
| 12 |
#' @typed groups: list |
|
| 13 |
#' named list of subjects to extract quantities for. See Group Specification. |
|
| 14 |
#' |
|
| 15 |
#' @typed spec: list |
|
| 16 |
#' named list of subjects to extract quantities for. The names of each |
|
| 17 |
#' element should be the required subjects with the element itself being a numeric vector of timepoints |
|
| 18 |
#' to generate the quantity at. |
|
| 19 |
#' |
|
| 20 |
#' @typed length.out: numeric |
|
| 21 |
#' number of evenly spaced timepoints to generate quantities at. |
|
| 22 |
#' |
|
| 23 |
#' @typed newdata: data.frame |
|
| 24 |
#' new data to generate quantities for. For `GridPrediction()`, it must |
|
| 25 |
#' contain the same columns and factor levels as the original |
|
| 26 |
#' [`DataSurvival`] object. For a covariate-based `GridPopulation()` |
|
| 27 |
#' prediction, one row defines each population profile and contains the arm, |
|
| 28 |
#' study, and all variables used by the longitudinal population-mean |
|
| 29 |
#' predictors. |
|
| 30 |
#' |
|
| 31 |
#' @typed params: list |
|
| 32 |
#' named list of parameters to fix the longitudinal model parameters at when |
|
| 33 |
#' predicting survival quantities. See [`getPredictionNames()`] for the required parameters. |
|
| 34 |
#' |
|
| 35 |
#' @description |
|
| 36 |
#' These functions are used to specify which subjects and timepoints should be generated |
|
| 37 |
#' when calculating quantities via [`SurvivalQuantities`] and [`LongitudinalQuantities`]. |
|
| 38 |
#' |
|
| 39 |
#' @details |
|
| 40 |
#' |
|
| 41 |
#' - `GridFixed()` is used to specify a fixed set of timepoints to generate quantities at for |
|
| 42 |
#' all the specified subjects. |
|
| 43 |
#' |
|
| 44 |
#' - `GridGrouped()` is similar to `GridFixed()` but allows for groupwise averaging |
|
| 45 |
#' (see Group Specification). |
|
| 46 |
#' |
|
| 47 |
#' - `GridObserved()` generates quantities at the observed longitudinal timepoints for each |
|
| 48 |
#' subject. |
|
| 49 |
#' |
|
| 50 |
#' - `GridManual()` allows for individual timepoint specification for each subject. |
|
| 51 |
#' |
|
| 52 |
#' - `GridEven()` generates quantities for each subject at N evenly spaced timepoints |
|
| 53 |
#' between each subjects first and last longitudinal observations. |
|
| 54 |
#' |
|
| 55 |
#' - `GridEvent()` generates one quantity for each subject at their event/censor time |
|
| 56 |
#' as indicated by the `time` variable in the survival dataset. |
|
| 57 |
#' |
|
| 58 |
#' - `GridPopulation()` generates longitudinal model quantities based on the |
|
| 59 |
#' population parameters at the specified time points. By default it generates |
|
| 60 |
#' one set for each distinct combination of `arm` and `study` in the fitted |
|
| 61 |
#' [`DataSubject`] object. For covariate-based longitudinal models with |
|
| 62 |
#' additional predictors, supply one population profile per row through |
|
| 63 |
#' `newdata`; this keeps arm, study, and all additional covariates together. |
|
| 64 |
#' |
|
| 65 |
#' - `GridPrediction()` generates survival quantities based on any user-defined values at the |
|
| 66 |
#' specified time points. This is useful for generating quantities for a new dataset |
|
| 67 |
#' on specific longitudinal model parameters. See [`getPredictionNames()`] to determine which |
|
| 68 |
#' longitudinal model parameters need to be defined for a given longitudinal model. |
|
| 69 |
#' |
|
| 70 |
#' @section Group Specification: |
|
| 71 |
#' For `GridGrouped()`, `groups` must be a named list of character vectors. Each element of the list |
|
| 72 |
#' must be a character vector of the subjects that will form the group where the element name |
|
| 73 |
#' is the corresponding name of the group. For example if the goal was to create two groups |
|
| 74 |
#' named `Group-1` and `Group-2` which are composed of the subjects `sub-1`, `sub-2` and |
|
| 75 |
#' `sub-3`, `sub-4` respectively then this would be specified as: |
|
| 76 |
#' ``` |
|
| 77 |
#' GridGrouped( |
|
| 78 |
#' groups = list( |
|
| 79 |
#' "Group-1" = c("sub-1", "sub-2"),
|
|
| 80 |
#' "Group-2" = c("sub-3", "sub-4")
|
|
| 81 |
#' ) |
|
| 82 |
#' ) |
|
| 83 |
#' ``` |
|
| 84 |
#' @seealso [`SurvivalQuantities`], [`LongitudinalQuantities`] |
|
| 85 |
#' @name Grid-Functions |
|
| 86 |
#' |
|
| 87 |
#' @examples |
|
| 88 |
#' GridFixed(subjects = c("subject 1", "subject 2"), times = c(0, 10))
|
|
| 89 |
#' GridGrouped(groups = list(control = c("subject 1", "subject 2")))
|
|
| 90 |
#' GridEven(subjects = c("subject 1", "subject 2"), length.out = 20)
|
|
| 91 |
#' GridObserved() |
|
| 92 |
#' GridEvent() |
|
| 93 |
#' GridPopulation(times = seq(0, 100, by = 10)) |
|
| 94 |
#' GridPopulation( |
|
| 95 |
#' times = seq(0, 100, by = 10), |
|
| 96 |
#' newdata = data.frame(study = "Study 1", arm = "A", age = 60) |
|
| 97 |
#' ) |
|
| 98 |
#' GridManual(list("subject 1" = c(0, 5), "subject 2" = c(0, 10)))
|
|
| 99 |
#' GridPrediction(times = c(0, 10), newdata = data.frame(age = 60)) |
|
| 100 |
NULL |
|
| 101 | ||
| 102 | ||
| 103 |
#' Grid Developer Notes |
|
| 104 |
#' |
|
| 105 |
#' @description |
|
| 106 |
#' Developer details for implementing / extending `Grid` objects for defining |
|
| 107 |
#' generated quantities for [`SurvivalQuantities`] and [`LongitudinalQuantities`]. |
|
| 108 |
#' |
|
| 109 |
#' @slot subjects (`character` or `NULL`)\cr vector of subjects to extract quantities for. |
|
| 110 |
#' If `NULL` will default to all subjects within the dataset. |
|
| 111 |
#' @slot times (`numeric` or `NULL`)\cr vector of time points to extract quantities at. |
|
| 112 |
#' If `NULL` will default to 201 evenly spaced timepoints between 0 and either the max |
|
| 113 |
#' @slot groups (`list`)\cr named list of subjects to extract quantities for. See details. |
|
| 114 |
#' |
|
| 115 |
#' @details |
|
| 116 |
#' All grid classes must inherit from the abstract `Grid` class. |
|
| 117 |
#' All grid classes must provide `as.QuantityGenerator(object, data)` and |
|
| 118 |
#' `as.QuantityCollapser(object, data)` methods where `data` is a [`DataJoint`] object. |
|
| 119 |
#' These methods must return a `QuantityGenerator` and `QuantityCollapser` object respectively. |
|
| 120 |
#' The `QuantityGenerator` object specifies unique subject/timepoint combinations that samples |
|
| 121 |
#' should be generated at. |
|
| 122 |
#' The `QuantityCollapser` object specifies how to combine these generated samples |
|
| 123 |
#' to form the desired quantities. |
|
| 124 |
#' As an example say we want to generate grouped samples for the groups `Group-1` and `Group-2` |
|
| 125 |
#' which consist of the subjects `sub-1`, `sub-2` and `sub-3`, `sub-4` respectively at two time points |
|
| 126 |
#' `10` and `20`. We can achieve this as follows: |
|
| 127 |
#' ``` |
|
| 128 |
#' QuantityGenerator( |
|
| 129 |
#' times = c(10, 10, 10, 10, 20, 20, 20, 20), |
|
| 130 |
#' subjects = c("sub-1" "sub-2", "sub-3", "sub-4", "sub-1" "sub-2", "sub-3", "sub-4")
|
|
| 131 |
#' ) |
|
| 132 |
#' QuantityCollapser( |
|
| 133 |
#' times = c(10, 20, 10 , 20), |
|
| 134 |
#' groups = c("Group-1", "Group-1", "Group-2", "Group-2"),
|
|
| 135 |
#' indexes = list(c(1, 2), c(5, 6), c(3, 4), c(7, 8)) |
|
| 136 |
#' ) |
|
| 137 |
#' ``` |
|
| 138 |
#' For population based quantities use the `arms` and `studies` arguments of `QuantityGenerator` |
|
| 139 |
#' instead of `subjects`. |
|
| 140 |
#' |
|
| 141 |
#' @inheritSection Grid-Functions Group Specification |
|
| 142 |
#' |
|
| 143 |
#' @keywords internal |
|
| 144 |
#' @seealso `Quant-Dev` |
|
| 145 |
#' @name Grid-Dev |
|
| 146 |
NULL |
|
| 147 | ||
| 148 | ||
| 149 |
#' Quantity Developer Notes |
|
| 150 |
#' |
|
| 151 |
#' @description |
|
| 152 |
#' Developer details for `QuantityX` objects/methods. This page just outlines the arguments |
|
| 153 |
#' and slots of these objects/methods. For the full implementation details please see [Grid-Dev] |
|
| 154 |
#' |
|
| 155 |
#' @slot times (`numeric`)\cr See Arguments for details. |
|
| 156 |
#' @slot subjects (`character`)\cr See Arguments for details. |
|
| 157 |
#' @slot groups (`character`)\cr See Arguments for details. |
|
| 158 |
#' @slot indexes (`list`)\cr See Arguments for details. |
|
| 159 |
#' |
|
| 160 |
#' @typed times: numeric |
|
| 161 |
#' vector of time points to extract quantities at. |
|
| 162 |
#' @typed subjects: character |
|
| 163 |
#' vector of subjects to extract quantities for. |
|
| 164 |
#' @typed groups: character |
|
| 165 |
#' vector of labels to apply to the generated quantities. |
|
| 166 |
#' @typed indexes: list |
|
| 167 |
#' list of indexes that specify which observations from a |
|
| 168 |
#' `QuantityGenerator` should be combined to form the desired quantities. |
|
| 169 |
#' |
|
| 170 |
#' @typed object: Grid |
|
| 171 |
#' object to convert to a `QuantityGenerator` or `QuantityCollapser`. |
|
| 172 |
#' @typed data: DataJoint |
|
| 173 |
#' Survival and Longitudinal Data. |
|
| 174 |
#' @typed model: "`JointModel` or `NULL`" |
|
| 175 |
#' optional fitted-model specification used for model-specific population |
|
| 176 |
#' profiles. |
|
| 177 |
#' @typed newdata: "`data.frame` or `NULL`" |
|
| 178 |
#' population profiles carried by a population quantity generator. |
|
| 179 |
#' @param ... Not currently used. |
|
| 180 |
#' |
|
| 181 |
#' @details |
|
| 182 |
#' The `as.QuantityGenerator` must return a `QuantityGenerator` object. |
|
| 183 |
#' The `as.QuantityCollapser` must return a `QuantityCollapser` object. |
|
| 184 |
#' @keywords internal |
|
| 185 |
#' @name Quant-Dev |
|
| 186 |
NULL |
|
| 187 | ||
| 188 | ||
| 189 |
#' @rdname Grid-Dev |
|
| 190 |
.Grid <- setClass("Grid")
|
|
| 191 | ||
| 192 | ||
| 193 |
#' @rdname Quant-Dev |
|
| 194 |
.QuantityGenerator <- setClass( |
|
| 195 |
"QuantityGenerator" |
|
| 196 |
) |
|
| 197 | ||
| 198 |
#' `QuantityGenerator` -> `list` |
|
| 199 |
#' |
|
| 200 |
#' @description |
|
| 201 |
#' Just a placeholder which will error when used. |
|
| 202 |
#' |
|
| 203 |
#' @typed object: QuantityGenerator |
|
| 204 |
#' object to convert to a list. |
|
| 205 |
#' @typed data: DataJoint |
|
| 206 |
#' Survival and Longitudinal Data. |
|
| 207 |
#' @typed model: "`JointModel` or `NULL`" |
|
| 208 |
#' optional model used to add model-specific generated-quantities data. |
|
| 209 |
#' @param ... Not currently used. |
|
| 210 |
#' @keywords internal |
|
| 211 |
#' @export |
|
| 212 |
#' |
|
| 213 |
#' @returns Will always throw an error. |
|
| 214 |
as_stan_list.QuantityGenerator <- function(object, data, ...) {
|
|
| 215 | ! |
stop("as_stan_list.QuantityGenerator not implemented")
|
| 216 |
} |
|
| 217 | ||
| 218 | ||
| 219 |
#' @rdname Quant-Dev |
|
| 220 |
.QuantityCollapser <- setClass( |
|
| 221 |
"QuantityCollapser", |
|
| 222 |
slots = c( |
|
| 223 |
"times" = "numeric", |
|
| 224 |
"groups" = "character", |
|
| 225 |
"indexes" = "list" |
|
| 226 |
) |
|
| 227 |
) |
|
| 228 |
#' @rdname Quant-Dev |
|
| 229 |
QuantityCollapser <- function(times, groups, indexes) {
|
|
| 230 | 54x |
.QuantityCollapser( |
| 231 | 54x |
times = times, |
| 232 | 54x |
groups = groups, |
| 233 | 54x |
indexes = indexes |
| 234 |
) |
|
| 235 |
} |
|
| 236 | ||
| 237 |
setValidity( |
|
| 238 |
"QuantityCollapser", |
|
| 239 |
function(object) {
|
|
| 240 |
if ( |
|
| 241 |
length(object@times) != length(object@groups) || |
|
| 242 |
length(object@times) != length(object@indexes) |
|
| 243 |
) {
|
|
| 244 |
return("Length of `times`, `groups`, and `indexes` must be equal")
|
|
| 245 |
} |
|
| 246 |
} |
|
| 247 |
) |
|
| 248 | ||
| 249 |
#' @export |
|
| 250 |
#' |
|
| 251 |
#' @returns A single integer giving the number of collapsed quantities. |
|
| 252 |
length.QuantityCollapser <- function(x) {
|
|
| 253 | 80x |
length(x@indexes) |
| 254 |
} |
|
| 255 | ||
| 256 | ||
| 257 |
#' Expand and Validate Subjects |
|
| 258 |
#' |
|
| 259 |
#' @typed subjects: character |
|
| 260 |
#' vector of subjects that should exist in `data` |
|
| 261 |
#' @typed data: DataJoint |
|
| 262 |
#' Survival and Longitudinal Data. |
|
| 263 |
#' |
|
| 264 |
#' @description |
|
| 265 |
#' If `subjects` is `NULL` this will return a named list of all subjects in `data`. |
|
| 266 |
#' Else it will return `subjects` as a named list ensuring that all subjects exist in `data`. |
|
| 267 |
#' |
|
| 268 |
#' @keywords internal |
|
| 269 |
#' |
|
| 270 |
#' @returns A named list grouping the requested subject identifiers. |
|
| 271 |
subjects_to_list <- function(subjects = NULL, data) {
|
|
| 272 | 74x |
data_list <- as.list(data) |
| 273 | 74x |
subjects_exp <- if (is.null(subjects)) {
|
| 274 | 16x |
subs <- as.list(names(data_list$subject_to_index)) |
| 275 | 16x |
names(subs) <- names(data_list$subject_to_index) |
| 276 | 16x |
subs |
| 277 |
} else {
|
|
| 278 | 58x |
subs <- as.list(subjects) |
| 279 | 58x |
names(subs) <- subjects |
| 280 | 58x |
subs |
| 281 |
} |
|
| 282 | 74x |
subjects_exp_vec <- unlist(subjects_exp, use.names = FALSE) |
| 283 | 74x |
assert_that( |
| 284 | 74x |
identical(subjects_exp_vec, unique(subjects_exp_vec)), |
| 285 | 74x |
msg = "All subject names must be unique" |
| 286 |
) |
|
| 287 | 74x |
assert_that( |
| 288 | 74x |
all(subjects_exp_vec %in% names(data_list$subject_to_index)), |
| 289 | 74x |
msg = "Not all subjects exist within the data object" |
| 290 |
) |
|
| 291 | 73x |
subjects_exp |
| 292 |
} |
| 1 |
#' @include SimLongitudinal.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' Simulate Longitudinal Data from a Random Slope Model |
|
| 6 |
#' |
|
| 7 |
#' @typed times: numeric |
|
| 8 |
#' the times to generate observations at. |
|
| 9 |
#' @typed intercept: numeric |
|
| 10 |
#' the mean baseline value for each study. |
|
| 11 |
#' @typed slope_mu: numeric |
|
| 12 |
#' the population slope for each treatment arm. |
|
| 13 |
#' @typed slope_sigma: numeric |
|
| 14 |
#' the random slope standard deviation for each treatment arm. |
|
| 15 |
#' @typed sigma: number |
|
| 16 |
#' the variance of the longitudinal values. |
|
| 17 |
#' @typed link_dsld: number |
|
| 18 |
#' the link coefficient for the DSLD contribution. |
|
| 19 |
#' @typed link_identity: number |
|
| 20 |
#' the link coefficient for the identity contribution. |
|
| 21 |
#' @typed scaled_variance: logical |
|
| 22 |
#' whether the variance should be scaled by the expected value |
|
| 23 |
#' (see the "Statistical Specifications" vignette for more details) |
|
| 24 |
#' |
|
| 25 |
#' @slot intercept (`numeric`)\cr See arguments. |
|
| 26 |
#' @slot slope_mu (`numeric`)\cr See arguments. |
|
| 27 |
#' @slot slope_sigma (`numeric`)\cr See arguments. |
|
| 28 |
#' @slot sigma (`numeric`)\cr See arguments. |
|
| 29 |
#' @slot link_dsld (`numeric`)\cr See arguments. |
|
| 30 |
#' @slot link_identity (`numeric`)\cr See arguments. |
|
| 31 |
#' @slot scaled_variance (`logical`)\cr See arguments. |
|
| 32 |
#' |
|
| 33 |
#' @family SimLongitudinal |
|
| 34 |
#' @name SimLongitudinalRandomSlope-class |
|
| 35 |
#' @exportClass SimLongitudinalRandomSlope |
|
| 36 |
#' |
|
| 37 |
#' @examples |
|
| 38 |
#' SimLongitudinalRandomSlope(times = c(0, 30, 60)) |
|
| 39 |
.SimLongitudinalRandomSlope <- setClass( |
|
| 40 |
"SimLongitudinalRandomSlope", |
|
| 41 |
contains = "SimLongitudinal", |
|
| 42 |
slots = c( |
|
| 43 |
intercept = "numeric", |
|
| 44 |
slope_mu = "numeric", |
|
| 45 |
slope_sigma = "numeric", |
|
| 46 |
sigma = "numeric", |
|
| 47 |
link_dsld = "numeric", |
|
| 48 |
link_identity = "numeric", |
|
| 49 |
scaled_variance = "logical" |
|
| 50 |
) |
|
| 51 |
) |
|
| 52 | ||
| 53 |
#' @rdname SimLongitudinalRandomSlope-class |
|
| 54 |
#' @export |
|
| 55 |
#' |
|
| 56 |
#' @returns A `SimLongitudinalRandomSlope` object. |
|
| 57 |
SimLongitudinalRandomSlope <- function( |
|
| 58 |
times = c(-100, -50, 0, 50, 100, 150, 250, 350, 450, 550), |
|
| 59 |
intercept = 50, |
|
| 60 |
slope_mu = c(0.01, 0.03), |
|
| 61 |
slope_sigma = c(0.5, 0.6), |
|
| 62 |
sigma = 2, |
|
| 63 |
link_dsld = 0, |
|
| 64 |
link_identity = 0, |
|
| 65 |
scaled_variance = FALSE |
|
| 66 |
) {
|
|
| 67 | 18x |
if (length(slope_sigma) == 1) {
|
| 68 | 4x |
slope_sigma <- rep(slope_sigma, length(slope_mu)) |
| 69 |
} |
|
| 70 | ||
| 71 | 18x |
.SimLongitudinalRandomSlope( |
| 72 | 18x |
times = times, |
| 73 | 18x |
intercept = intercept, |
| 74 | 18x |
slope_mu = slope_mu, |
| 75 | 18x |
slope_sigma = slope_sigma, |
| 76 | 18x |
sigma = sigma, |
| 77 | 18x |
link_dsld = link_dsld, |
| 78 | 18x |
link_identity = link_identity, |
| 79 | 18x |
scaled_variance = scaled_variance |
| 80 |
) |
|
| 81 |
} |
|
| 82 | ||
| 83 |
#' @rdname as_print_string |
|
| 84 |
#' @exportS3Method NULL |
|
| 85 |
as_print_string.SimLongitudinalRandomSlope <- function(object, ...) {
|
|
| 86 | 1x |
return("SimLongitudinalRandomSlope")
|
| 87 |
} |
|
| 88 | ||
| 89 |
#' @rdname sampleObservations |
|
| 90 |
#' @export |
|
| 91 |
sampleObservations.SimLongitudinalRandomSlope <- function(object, times_df) {
|
|
| 92 | 32x |
times_df |> |
| 93 | 32x |
dplyr::mutate( |
| 94 | 32x |
sld_mu = .data$intercept + .data$slope_ind * .data$time, |
| 95 | 32x |
sld_sd = ifelse( |
| 96 | 32x |
object@scaled_variance, |
| 97 | 32x |
pmax(.data$sld_mu * object@sigma, .Machine$double.eps), |
| 98 | 32x |
object@sigma |
| 99 |
), |
|
| 100 | 32x |
sld = stats::rnorm(dplyr::n(), .data$sld_mu, .data$sld_sd), |
| 101 | 32x |
log_haz_link = object@link_dsld * |
| 102 | 32x |
.data$slope_ind + |
| 103 | 32x |
object@link_identity * .data$sld_mu |
| 104 |
) |
|
| 105 |
} |
|
| 106 | ||
| 107 |
#' @rdname sampleSubjects |
|
| 108 |
#' @export |
|
| 109 |
sampleSubjects.SimLongitudinalRandomSlope <- function(object, subjects_df) {
|
|
| 110 | 17x |
assert_that( |
| 111 | 17x |
is.factor(subjects_df[["study"]]), |
| 112 | 17x |
is.factor(subjects_df[["arm"]]) |
| 113 |
) |
|
| 114 | ||
| 115 | 17x |
assert_that( |
| 116 | 17x |
length(object@slope_mu) == nlevels(subjects_df[["arm"]]), |
| 117 | 17x |
msg = "`length(slope_mu)` should be equal to the number of arms" |
| 118 |
) |
|
| 119 | ||
| 120 | 17x |
assert_that( |
| 121 | 17x |
length(object@slope_sigma) == nlevels(subjects_df[["arm"]]), |
| 122 | 17x |
msg = "`length(slope_sigma)` should be equal to the number of arms" |
| 123 |
) |
|
| 124 | ||
| 125 | 17x |
assert_that( |
| 126 | 17x |
length(object@intercept) == nlevels(subjects_df[["study"]]), |
| 127 | 17x |
msg = "`length(intercept)` should be equal to the number of studies" |
| 128 |
) |
|
| 129 | ||
| 130 | 17x |
assert_that( |
| 131 | 17x |
nrow(subjects_df) == length(unique(subjects_df[["subject"]])), |
| 132 | 17x |
msg = "The number of rows in `subjects_df` should be equal to the number of unique subjects" |
| 133 |
) |
|
| 134 | ||
| 135 | 17x |
subjects_df |> |
| 136 | 17x |
dplyr::mutate(intercept = object@intercept[as.numeric(.data$study)]) |> |
| 137 | 17x |
dplyr::mutate( |
| 138 | 17x |
slope_ind = stats::rnorm( |
| 139 | 17x |
n = dplyr::n(), |
| 140 | 17x |
mean = object@slope_mu[as.numeric(.data$arm)], |
| 141 | 17x |
sd = object@slope_sigma[as.numeric(.data$arm)] |
| 142 |
) |
|
| 143 |
) |
|
| 144 |
} |
| 1 |
#' @include SurvivalModel.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
# SurvivalLogLogistic-class ---- |
|
| 5 | ||
| 6 |
#' `SurvivalLogLogistic` |
|
| 7 |
#' |
|
| 8 |
#' This class extends the general [`SurvivalModel`] class for using the |
|
| 9 |
#' log-logistic survival model. |
|
| 10 |
#' |
|
| 11 |
#' @exportClass SurvivalLogLogistic |
|
| 12 |
.SurvivalLogLogistic <- setClass( |
|
| 13 |
Class = "SurvivalLogLogistic", |
|
| 14 |
contains = "SurvivalModel" |
|
| 15 |
) |
|
| 16 | ||
| 17 |
# SurvivalLogLogistic-constructors ---- |
|
| 18 | ||
| 19 |
#' @rdname SurvivalLogLogistic-class |
|
| 20 |
#' |
|
| 21 |
#' @typed a: Prior |
|
| 22 |
#' Prior distribution for the scale parameter `a`. |
|
| 23 |
#' @typed b: Prior |
|
| 24 |
#' Prior distribution for the shape parameter `b`. |
|
| 25 |
#' @typed beta: Prior |
|
| 26 |
#' Prior distribution for covariates coefficients `beta`. |
|
| 27 |
#' |
|
| 28 |
#' @export |
|
| 29 |
#' |
|
| 30 |
#' @returns A `SurvivalLogLogistic` object. |
|
| 31 |
#' |
|
| 32 |
#' @examples |
|
| 33 |
#' SurvivalLogLogistic() |
|
| 34 |
SurvivalLogLogistic <- function( |
|
| 35 |
a = prior_lognormal(log(0.1), 5), |
|
| 36 |
b = prior_gamma(2, 5), |
|
| 37 |
beta = prior_normal(0, 2) |
|
| 38 |
) {
|
|
| 39 | 9x |
a <- set_limits(a, lower = getOption("jmpost.double_eps"))
|
| 40 | 9x |
b <- set_limits(b, lower = getOption("jmpost.double_eps"))
|
| 41 | ||
| 42 | 9x |
.SurvivalLogLogistic( |
| 43 | 9x |
SurvivalModel( |
| 44 | 9x |
name = "Log-Logistic", |
| 45 | 9x |
stan = StanModule("sm-loglogistic/model.stan"),
|
| 46 | 9x |
parameters = ParameterList( |
| 47 | 9x |
Parameter(name = "sm_loglogis_a", prior = a, size = 1), |
| 48 | 9x |
Parameter(name = "sm_loglogis_b", prior = b, size = 1), |
| 49 | 9x |
Parameter( |
| 50 | 9x |
name = "beta_os_cov", |
| 51 | 9x |
prior = beta, |
| 52 | 9x |
size = "p_os_cov_design" |
| 53 |
) |
|
| 54 |
) |
|
| 55 |
) |
|
| 56 |
) |
|
| 57 |
} |
| 1 |
#' @include Grid.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
#' @rdname Grid-Dev |
|
| 6 |
.GridObserved <- setClass( |
|
| 7 |
"GridObserved", |
|
| 8 |
contains = "Grid", |
|
| 9 |
slots = c( |
|
| 10 |
"subjects" = "character_or_NULL" |
|
| 11 |
) |
|
| 12 |
) |
|
| 13 | ||
| 14 | ||
| 15 |
#' @rdname Grid-Functions |
|
| 16 |
#' @export |
|
| 17 |
GridObserved <- function(subjects = NULL) {
|
|
| 18 | 2x |
.GridObserved( |
| 19 | 2x |
subjects = subjects |
| 20 |
) |
|
| 21 |
} |
|
| 22 | ||
| 23 |
#' @rdname Quant-Dev |
|
| 24 |
#' @export |
|
| 25 |
as.QuantityGenerator.GridObserved <- function(object, data, ...) {
|
|
| 26 | 4x |
assert_class(data, "DataJoint") |
| 27 | 4x |
data_list <- as.list(data) |
| 28 | 4x |
subjects <- unlist(as.list(object, data = data), use.names = FALSE) |
| 29 | 4x |
unique_visits <- tapply( |
| 30 | 4x |
data_list$tumour_time, |
| 31 | 4x |
data_list$subject_tumour_index, |
| 32 | 4x |
unique |
| 33 |
) |
|
| 34 | 4x |
subject_visits <- unique_visits[data_list$subject_to_index[subjects]] |
| 35 | 4x |
visit_lengths <- vapply(subject_visits, length, numeric(1)) |
| 36 | 4x |
QuantityGeneratorSubject( |
| 37 | 4x |
times = unlist(subject_visits, use.names = FALSE), |
| 38 | 4x |
subjects = rep(subjects, visit_lengths) |
| 39 |
) |
|
| 40 |
} |
|
| 41 | ||
| 42 |
#' @rdname Quant-Dev |
|
| 43 |
#' @export |
|
| 44 |
as.QuantityCollapser.GridObserved <- function(object, data, ...) {
|
|
| 45 | 2x |
generator <- as.QuantityGenerator(object, data) |
| 46 | ||
| 47 | 2x |
QuantityCollapser( |
| 48 | 2x |
times = generator@times, |
| 49 | 2x |
groups = generator@subjects, |
| 50 | 2x |
indexes = as.list(seq_along(generator@times)) |
| 51 |
) |
|
| 52 |
} |
|
| 53 | ||
| 54 |
#' @export |
|
| 55 |
#' |
|
| 56 |
#' @returns A named `list` representation of the object. |
|
| 57 |
as.list.GridObserved <- function(x, data, ...) {
|
|
| 58 | 4x |
subjects_to_list(x@subjects, data) |
| 59 |
} |
| 1 |
#' @include StanModule.R |
|
| 2 |
#' @include LongitudinalModel.R |
|
| 3 |
#' @include ParameterList.R |
|
| 4 |
#' @include generics.R |
|
| 5 |
#' @include Prior.R |
|
| 6 |
NULL |
|
| 7 | ||
| 8 | ||
| 9 |
#' `LinkComponent` Function Arguments |
|
| 10 |
#' |
|
| 11 |
#' This exists to contain all the common arguments for [`LinkComponent`] methods. |
|
| 12 |
#' |
|
| 13 |
#' @typed stan: StanModule |
|
| 14 |
#' Stan code. |
|
| 15 |
#' @typed x: LinkComponent |
|
| 16 |
#' a link component. |
|
| 17 |
#' @typed object: LinkComponent |
|
| 18 |
#' a link component. |
|
| 19 |
#' @param ... Not Used. |
|
| 20 |
#' |
|
| 21 |
#' @name LinkComponent-Shared |
|
| 22 |
#' @keywords internal |
|
| 23 |
NULL |
|
| 24 | ||
| 25 | ||
| 26 |
#' `LinkComponent` |
|
| 27 |
#' |
|
| 28 |
#' @slot stan (`StanModule`)\cr See Arguments. |
|
| 29 |
#' @slot name (`character`)\cr See Arguments. |
|
| 30 |
#' @slot parameters (`ParameterList`)\cr The parameter specification. |
|
| 31 |
#' |
|
| 32 |
#' @typed stan: StanModule |
|
| 33 |
#' Stan code. See Details. |
|
| 34 |
#' @typed prior: Prior |
|
| 35 |
#' The prior for the scaling coeficient. |
|
| 36 |
#' @typed key: character |
|
| 37 |
#' Link identifier. See Details. |
|
| 38 |
#' |
|
| 39 |
#' @details |
|
| 40 |
#' |
|
| 41 |
#' This object provides key information needed to construct a link contribution in the |
|
| 42 |
#' survival model based on the parameters of the longitudinal model. |
|
| 43 |
#' |
|
| 44 |
#' Each link component defines a stan function of the longitudinal model parameters which is |
|
| 45 |
#' multiplied by a model coefficient and added to the survival models hazard function. |
|
| 46 |
#' |
|
| 47 |
#' For full details about the specification of a `LinkComponent` please see |
|
| 48 |
#' \code{vignette("extending-jmpost", package = "jmpost")}.
|
|
| 49 |
#' |
|
| 50 |
#' @inheritParams stanmodel_arguments |
|
| 51 |
#' @family LinkComponent |
|
| 52 |
#' @name LinkComponent-class |
|
| 53 |
#' @exportClass Link |
|
| 54 |
#' |
|
| 55 |
#' @examples |
|
| 56 |
#' LinkComponent( |
|
| 57 |
#' stan = StanModule(), |
|
| 58 |
#' prior = prior_normal(0, 1), |
|
| 59 |
#' key = "custom_link" |
|
| 60 |
#' ) |
|
| 61 |
.LinkComponent <- setClass( |
|
| 62 |
Class = "LinkComponent", |
|
| 63 |
slots = list( |
|
| 64 |
"stan" = "StanModule", |
|
| 65 |
"parameters" = "ParameterList", |
|
| 66 |
"key" = "character" |
|
| 67 |
) |
|
| 68 |
) |
|
| 69 | ||
| 70 | ||
| 71 |
#' @rdname LinkComponent-class |
|
| 72 |
#' @export |
|
| 73 |
#' |
|
| 74 |
#' @returns A `LinkComponent` object. |
|
| 75 |
LinkComponent <- function(stan, prior, key, ...) {
|
|
| 76 | 64x |
.LinkComponent( |
| 77 | 64x |
stan = stan, |
| 78 | 64x |
key = key, |
| 79 | 64x |
parameters = ParameterList(Parameter( |
| 80 | 64x |
name = key, |
| 81 | 64x |
prior = prior, |
| 82 | 64x |
size = 1 |
| 83 |
)), |
|
| 84 |
... |
|
| 85 |
) |
|
| 86 |
} |
|
| 87 | ||
| 88 | ||
| 89 |
#' @family LinkComponent |
|
| 90 |
#' @rdname getParameters |
|
| 91 |
#' @export |
|
| 92 |
getParameters.LinkComponent <- function(object, ...) {
|
|
| 93 | 55x |
object@parameters |
| 94 |
} |
|
| 95 | ||
| 96 | ||
| 97 |
#' @family LinkComponent |
|
| 98 |
#' @rdname initialValues |
|
| 99 |
#' @export |
|
| 100 |
initialValues.LinkComponent <- function(object, n_chains, ...) {
|
|
| 101 | ! |
initialValues(object@parameters, n_chains) |
| 102 |
} |
|
| 103 | ||
| 104 | ||
| 105 |
#' `LinkComponent` -> `StanModule` |
|
| 106 |
#' |
|
| 107 |
#' Converts a [`LinkComponent`] object to a [`StanModule`] object |
|
| 108 |
#' |
|
| 109 |
#' @inheritParams LinkComponent-Shared |
|
| 110 |
#' |
|
| 111 |
#' @family LinkComponent |
|
| 112 |
#' @family as.StanModule |
|
| 113 |
#' @export |
|
| 114 |
#' |
|
| 115 |
#' @returns A `StanModule` object. |
|
| 116 |
as.StanModule.LinkComponent <- function(object, ...) {
|
|
| 117 | 67x |
object@stan |
| 118 |
} |
|
| 119 | ||
| 120 | ||
| 121 |
#' `LinkComponent` -> `list` |
|
| 122 |
#' |
|
| 123 |
#' @inheritParams LinkComponent-Shared |
|
| 124 |
#' |
|
| 125 |
#' @description |
|
| 126 |
#' Returns a named list where each element of the list corresponds |
|
| 127 |
#' to a Stan modelling block e.g. `data`, `model`, etc. |
|
| 128 |
#' |
|
| 129 |
#' @family LinkComponent |
|
| 130 |
#' @export |
|
| 131 |
#' |
|
| 132 |
#' @returns A named `list` representation of the object. |
|
| 133 |
as.list.LinkComponent <- function(x, ...) {
|
|
| 134 | ! |
stan <- as.StanModule(x, ...) |
| 135 | ! |
as.list(stan) |
| 136 |
} |
|
| 137 | ||
| 138 |
#' @family LinkComponent |
|
| 139 |
#' @export |
|
| 140 |
#' |
|
| 141 |
#' @returns A character vector suitable for printing. |
|
| 142 |
as_print_string.LinkComponent <- function(object, ...) {
|
|
| 143 | 13x |
as_print_string(object@parameters) |
| 144 |
} |
|
| 145 | ||
| 146 | ||
| 147 |
#' @rdname show-object |
|
| 148 |
#' @export |
|
| 149 |
setMethod( |
|
| 150 |
f = "show", |
|
| 151 |
signature = "LinkComponent", |
|
| 152 |
definition = function(object) {
|
|
| 153 | 3x |
cat( |
| 154 | 3x |
paste0( |
| 155 | 3x |
"\nLinkComponent with parameter:\n ", |
| 156 | 3x |
as_print_string(object), |
| 157 | 3x |
"\n\n" |
| 158 |
) |
|
| 159 |
) |
|
| 160 |
} |
|
| 161 |
) |
|
| 162 | ||
| 163 |
#' @family LinkComponent |
|
| 164 |
#' @export |
|
| 165 |
#' |
|
| 166 |
#' @returns A character vector containing the component's parameter names. |
|
| 167 |
names.LinkComponent <- function(x, ...) {
|
|
| 168 | 114x |
names(x@parameters) |
| 169 |
} |
| 1 |
#' @include Grid.R |
|
| 2 |
#' @include generics.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 | ||
| 6 |
#' @rdname Grid-Dev |
|
| 7 |
.GridFixed <- setClass( |
|
| 8 |
"GridFixed", |
|
| 9 |
contains = "Grid", |
|
| 10 |
slots = c( |
|
| 11 |
"subjects" = "character_or_NULL", |
|
| 12 |
"times" = "numeric_or_NULL" |
|
| 13 |
) |
|
| 14 |
) |
|
| 15 | ||
| 16 |
#' @rdname Grid-Functions |
|
| 17 |
#' @export |
|
| 18 |
GridFixed <- function(subjects = NULL, times = NULL) {
|
|
| 19 | 46x |
.GridFixed( |
| 20 | 46x |
subjects = subjects, |
| 21 | 46x |
times = times |
| 22 |
) |
|
| 23 |
} |
|
| 24 | ||
| 25 |
#' @rdname Quant-Dev |
|
| 26 |
#' @export |
|
| 27 |
as.QuantityGenerator.GridFixed <- function(object, data, ...) {
|
|
| 28 | 62x |
assert_class(data, "DataJoint") |
| 29 | 62x |
data_list <- as.list(data) |
| 30 | 62x |
subjects <- unlist(as.list(object, data = data), use.names = FALSE) |
| 31 | ||
| 32 | 62x |
validate_time_grid(object@times) |
| 33 | 62x |
subject_times <- expand.grid( |
| 34 | 62x |
subject = subjects, |
| 35 | 62x |
time = object@times, |
| 36 | 62x |
stringsAsFactors = FALSE |
| 37 |
) |
|
| 38 | ||
| 39 | 62x |
QuantityGeneratorSubject( |
| 40 | 62x |
times = subject_times$time, |
| 41 | 62x |
subjects = subject_times$subject |
| 42 |
) |
|
| 43 |
} |
|
| 44 | ||
| 45 |
#' @rdname Quant-Dev |
|
| 46 |
#' @export |
|
| 47 |
as.QuantityCollapser.GridFixed <- function(object, data, ...) {
|
|
| 48 | 21x |
generator <- as.QuantityGenerator(object, data) |
| 49 | 21x |
QuantityCollapser( |
| 50 | 21x |
times = generator@times, |
| 51 | 21x |
groups = generator@subjects, |
| 52 | 21x |
indexes = as.list(seq_along(generator@times)) |
| 53 |
) |
|
| 54 |
} |
|
| 55 | ||
| 56 | ||
| 57 |
#' @export |
|
| 58 |
#' |
|
| 59 |
#' @returns A named `list` representation of the object. |
|
| 60 |
as.list.GridFixed <- function(x, data, ...) {
|
|
| 61 | 62x |
subjects_to_list(x@subjects, data) |
| 62 |
} |
|
| 63 | ||
| 64 |
#' @rdname coalesceGridTime |
|
| 65 |
#' @export |
|
| 66 |
coalesceGridTime.GridFixed <- function(object, times, ...) {
|
|
| 67 | 22x |
if (is.null(object@times)) {
|
| 68 | 6x |
object <- GridFixed( |
| 69 | 6x |
subjects = object@subjects, |
| 70 | 6x |
times = times |
| 71 |
) |
|
| 72 |
} |
|
| 73 | 22x |
object |
| 74 |
} |
| 1 |
#' Define Simulation Group |
|
| 2 |
#' |
|
| 3 |
#' Specifies a simulation group to be used by [`SimJointData()`]. |
|
| 4 |
#' |
|
| 5 |
#' @typed n: numeric |
|
| 6 |
#' number of subjects in the group. |
|
| 7 |
#' @typed arm: character |
|
| 8 |
#' treatment arm. |
|
| 9 |
#' @typed study: character |
|
| 10 |
#' study name. |
|
| 11 |
#' |
|
| 12 |
#' @slot n (`numeric`)\cr See arguments. |
|
| 13 |
#' @slot arm (`character`)\cr See arguments. |
|
| 14 |
#' @slot study (`character`)\cr See arguments. |
|
| 15 |
#' |
|
| 16 |
#' @examples |
|
| 17 |
#' SimGroup(n = 50, arm = "Arm-A", study = "Study-1") |
|
| 18 |
#' @name SimGroup-class |
|
| 19 |
#' @exportClass SimGroup |
|
| 20 |
.SimGroup <- setClass( |
|
| 21 |
"SimGroup", |
|
| 22 |
slots = c( |
|
| 23 |
n = "numeric", |
|
| 24 |
arm = "character", |
|
| 25 |
study = "character" |
|
| 26 |
) |
|
| 27 |
) |
|
| 28 | ||
| 29 |
#' @export |
|
| 30 |
#' @rdname SimGroup-class |
|
| 31 |
#' |
|
| 32 |
#' @returns A `SimGroup` object. |
|
| 33 |
SimGroup <- function(n, arm, study) {
|
|
| 34 | 58x |
.SimGroup( |
| 35 | 58x |
n = n, |
| 36 | 58x |
arm = arm, |
| 37 | 58x |
study = study |
| 38 |
) |
|
| 39 |
} |
|
| 40 | ||
| 41 | ||
| 42 |
setValidity( |
|
| 43 |
"SimGroup", |
|
| 44 |
function(object) {
|
|
| 45 |
if (length(object@n) != 1) {
|
|
| 46 |
return("`n` must be a length 1 integer")
|
|
| 47 |
} |
|
| 48 |
if (length(object@arm) != 1) {
|
|
| 49 |
return("`arm` must be a length 1 string")
|
|
| 50 |
} |
|
| 51 |
if (length(object@study) != 1) {
|
|
| 52 |
return("`study` must be a length 1 string")
|
|
| 53 |
} |
|
| 54 |
if (any(object@n < 1) || any(object@n %% 1 != 0)) {
|
|
| 55 |
return("`n` must be positive integer")
|
|
| 56 |
} |
|
| 57 |
return(TRUE) |
|
| 58 |
} |
|
| 59 |
) |
| 1 |
#' @include StanModule.R |
|
| 2 |
#' @include ParameterList.R |
|
| 3 |
#' @include generics.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 |
#' `StanModel` Function Arguments |
|
| 7 |
#' |
|
| 8 |
#' The documentation lists all the conventional arguments for wrappers around |
|
| 9 |
#' [StanModel()]. |
|
| 10 |
#' |
|
| 11 |
#' @typed stan: StanModule |
|
| 12 |
#' code containing the model-specific Stan code specification. |
|
| 13 |
#' @typed parameters: ParameterList |
|
| 14 |
#' the parameter declaration and prior specification. |
|
| 15 |
#' @typed parameter: ParameterList |
|
| 16 |
#' the (single) parameter declaration and prior specification. |
|
| 17 |
#' @typed name: character |
|
| 18 |
#' display name for the model object. |
|
| 19 |
#' @param ... additional arguments for [StanModel()]. |
|
| 20 |
#' |
|
| 21 |
#' @name stanmodel_arguments |
|
| 22 |
#' @keywords internal |
|
| 23 |
NULL |
|
| 24 | ||
| 25 |
# StanModel-class ---- |
|
| 26 | ||
| 27 |
#' Stan Model Object and Constructor Function |
|
| 28 |
#' |
|
| 29 |
#' @slot stan (`StanModule`)\cr See Arguments. |
|
| 30 |
#' @slot parameters (`ParameterList`)\cr See Arguments. |
|
| 31 |
#' @slot name (`character`)\cr display name for the model object. |
|
| 32 |
#' |
|
| 33 |
#' @export StanModel |
|
| 34 |
#' @exportClass StanModel |
|
| 35 |
#' @family StanModel |
|
| 36 |
.StanModel <- setClass( |
|
| 37 |
Class = "StanModel", |
|
| 38 |
slots = list( |
|
| 39 |
"stan" = "StanModule", |
|
| 40 |
"parameters" = "ParameterList", |
|
| 41 |
"name" = "character" |
|
| 42 |
) |
|
| 43 |
) |
|
| 44 | ||
| 45 |
# StanModel-constructor ---- |
|
| 46 | ||
| 47 |
#' @inheritParams stanmodel_arguments |
|
| 48 |
#' @rdname StanModel-class |
|
| 49 |
#' |
|
| 50 |
#' @returns A `StanModel` object. |
|
| 51 |
#' |
|
| 52 |
#' @examples |
|
| 53 |
#' StanModel(StanModule(), ParameterList(), name = "Example model") |
|
| 54 |
StanModel <- function(stan, parameters, name = "<Unnamed>") {
|
|
| 55 | 254x |
.StanModel( |
| 56 | 254x |
stan = stan, |
| 57 | 254x |
parameters = parameters, |
| 58 | 254x |
name = name |
| 59 |
) |
|
| 60 |
} |
|
| 61 | ||
| 62 |
# as.list-StanModel ---- |
|
| 63 | ||
| 64 |
#' `StanModel` -> `list` |
|
| 65 |
#' @description |
|
| 66 |
#' Returns a named list where each element of the list corresponds |
|
| 67 |
#' to a Stan modelling block e.g. `data`, `model`, etc. |
|
| 68 |
#' @typed x: StanModel |
|
| 69 |
#' A Stan Model |
|
| 70 |
#' @param ... Not Used. |
|
| 71 |
#' @family StanModel |
|
| 72 |
#' @export |
|
| 73 |
#' |
|
| 74 |
#' @returns A named `list` representation of the object. |
|
| 75 |
as.list.StanModel <- function(x, ...) {
|
|
| 76 | 225x |
as.list(x@stan) |
| 77 |
} |
|
| 78 | ||
| 79 |
# getParameters-StanModel ---- |
|
| 80 | ||
| 81 |
#' @rdname getParameters |
|
| 82 |
#' @export |
|
| 83 | 208x |
getParameters.StanModel <- function(object, ...) object@parameters |
| 84 | ||
| 85 | ||
| 86 |
#' @export |
|
| 87 |
#' |
|
| 88 |
#' @returns A character vector suitable for printing. |
|
| 89 |
as_print_string.StanModel <- function(object, ...) {
|
|
| 90 | 1x |
string <- sprintf( |
| 91 | 1x |
"\n%s Model Object with parameters:\n%s\n\n", |
| 92 | 1x |
object@name, |
| 93 | 1x |
paste(" ", as_print_string(object@parameters)) |>
|
| 94 | 1x |
paste(collapse = "\n") |
| 95 |
) |
|
| 96 | 1x |
return(string) |
| 97 |
} |
|
| 98 | ||
| 99 |
#' @rdname show-object |
|
| 100 |
#' @export |
|
| 101 |
setMethod( |
|
| 102 |
f = "show", |
|
| 103 |
signature = "StanModel", |
|
| 104 |
definition = function(object) {
|
|
| 105 | 20x |
cat(as_print_string(object)) |
| 106 |
} |
|
| 107 |
) |
|
| 108 | ||
| 109 |
#' @rdname initialValues |
|
| 110 |
#' @export |
|
| 111 |
initialValues.StanModel <- function(object, n_chains, ...) {
|
|
| 112 | 28x |
initialValues(object@parameters, n_chains) |
| 113 |
} |
| 1 |
#' @include StanModel.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
# SurvivalModel-class ---- |
|
| 5 | ||
| 6 |
#' `SurvivalModel` |
|
| 7 |
#' |
|
| 8 |
#' This class extends the general [`StanModel`] class to comprise the survival |
|
| 9 |
#' model specification. |
|
| 10 |
#' |
|
| 11 |
#' @exportClass SurvivalModel |
|
| 12 |
.SurvivalModel <- setClass( |
|
| 13 |
Class = "SurvivalModel", |
|
| 14 |
contains = "StanModel" |
|
| 15 |
) |
|
| 16 | ||
| 17 |
# SurvivalModel-constructors ---- |
|
| 18 | ||
| 19 |
#' @rdname SurvivalModel-class |
|
| 20 |
#' |
|
| 21 |
#' @inheritParams stanmodel_arguments |
|
| 22 |
#' |
|
| 23 |
#' @export |
|
| 24 |
#' |
|
| 25 |
#' @returns A `SurvivalModel` object. |
|
| 26 |
#' |
|
| 27 |
#' @examples |
|
| 28 |
#' SurvivalModel() |
|
| 29 |
SurvivalModel <- function( |
|
| 30 |
stan = StanModule(), |
|
| 31 |
parameters = ParameterList(), |
|
| 32 |
name = "<Unnamed>", |
|
| 33 |
... |
|
| 34 |
) {
|
|
| 35 | 77x |
base_stan <- read_stan("base/survival.stan")
|
| 36 | 77x |
stan_full <- decorated_render( |
| 37 | 77x |
.x = base_stan, |
| 38 | 77x |
stan = add_missing_stan_blocks(as.list(stan)) |
| 39 |
) |
|
| 40 | 77x |
.SurvivalModel( |
| 41 | 77x |
StanModel( |
| 42 | 77x |
name = name, |
| 43 | 77x |
stan = StanModule(stan_full), |
| 44 | 77x |
parameters = parameters, |
| 45 |
... |
|
| 46 |
) |
|
| 47 |
) |
|
| 48 |
} |
|
| 49 | ||
| 50 |
#' @export |
|
| 51 |
#' |
|
| 52 |
#' @returns A character vector suitable for printing. |
|
| 53 |
as_print_string.SurvivalModel <- function(object, ...) {
|
|
| 54 | 15x |
string <- sprintf( |
| 55 | 15x |
"\n%s Survival Model with parameters:\n%s\n\n", |
| 56 | 15x |
object@name, |
| 57 | 15x |
paste(" ", as_print_string(object@parameters)) |>
|
| 58 | 15x |
paste(collapse = "\n") |
| 59 |
) |
|
| 60 | 15x |
return(string) |
| 61 |
} |
| 1 |
#' @include SurvivalModel.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
# SurvivalWeibullPH-class ---- |
|
| 5 | ||
| 6 |
#' `SurvivalWeibullPH` |
|
| 7 |
#' |
|
| 8 |
#' This class extends the general [`SurvivalModel`] class for using the |
|
| 9 |
#' Weibull proportional hazards survival model. |
|
| 10 |
#' |
|
| 11 |
#' @exportClass SurvivalWeibullPH |
|
| 12 |
.SurvivalWeibullPH <- setClass( |
|
| 13 |
Class = "SurvivalWeibullPH", |
|
| 14 |
contains = "SurvivalModel" |
|
| 15 |
) |
|
| 16 | ||
| 17 |
# SurvivalWeibullPH-constructors ---- |
|
| 18 | ||
| 19 |
#' @rdname SurvivalWeibullPH-class |
|
| 20 |
#' |
|
| 21 |
#' @typed lambda: Prior |
|
| 22 |
#' for the scale `lambda`. |
|
| 23 |
#' @typed gamma: Prior |
|
| 24 |
#' for the shape `gamma`. |
|
| 25 |
#' @typed beta: Prior |
|
| 26 |
#' for covariates coefficients `beta`. |
|
| 27 |
#' |
|
| 28 |
#' @export |
|
| 29 |
#' |
|
| 30 |
#' @returns A `SurvivalWeibullPH` object. |
|
| 31 |
#' |
|
| 32 |
#' @examples |
|
| 33 |
#' SurvivalWeibullPH() |
|
| 34 |
SurvivalWeibullPH <- function( |
|
| 35 |
lambda = prior_gamma(2, 0.5), |
|
| 36 |
gamma = prior_gamma(2, 0.5), |
|
| 37 |
beta = prior_normal(0, 2) |
|
| 38 |
) {
|
|
| 39 | 33x |
lambda <- set_limits(lambda, lower = getOption("jmpost.double_eps"))
|
| 40 | 33x |
gamma <- set_limits(gamma, lower = getOption("jmpost.double_eps"))
|
| 41 | ||
| 42 | 33x |
.SurvivalWeibullPH( |
| 43 | 33x |
SurvivalModel( |
| 44 | 33x |
name = "Weibull-PH", |
| 45 | 33x |
stan = StanModule(x = "sm-weibull-ph/model.stan"), |
| 46 | 33x |
parameters = ParameterList( |
| 47 | 33x |
Parameter( |
| 48 | 33x |
name = "sm_weibull_ph_lambda", |
| 49 | 33x |
prior = lambda, |
| 50 | 33x |
size = 1 |
| 51 |
), |
|
| 52 | 33x |
Parameter( |
| 53 | 33x |
name = "sm_weibull_ph_gamma", |
| 54 | 33x |
prior = gamma, |
| 55 | 33x |
size = 1 |
| 56 |
), |
|
| 57 | 33x |
Parameter( |
| 58 | 33x |
name = "beta_os_cov", |
| 59 | 33x |
prior = beta, |
| 60 | 33x |
size = "p_os_cov_design" |
| 61 |
) |
|
| 62 |
) |
|
| 63 |
) |
|
| 64 |
) |
|
| 65 |
} |