-
-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathcov_struct.R
More file actions
465 lines (436 loc) · 13 KB
/
cov_struct.R
File metadata and controls
465 lines (436 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
#' Covariance Type Database
#'
#' An internal constant for covariance type information.
#'
#' @format A data frame with 5 variables and one record per covariance type:
#'
#' \describe{
#' \item{name}{
#' The long-form name of the covariance structure type
#' }
#' \item{abbr}{
#' The abbreviated name of the covariance structure type
#' }
#' \item{habbr}{
#' The abbreviated name of the heterogeneous version of a covariance
#' structure type (The abbreviated name (`abbr`) with a trailing `"h"` if
#' the structure has a heterogeneous implementation or `NA` otherwise).
#' }
#' \item{heterogeneous}{
#' A logical value indicating whether the covariance structure has a
#' heterogeneous counterpart.
#' }
#' \item{spatial}{
#' A logical value indicating whether the covariance structure is spatial.
#' }
#' }
#'
#' @keywords internal
COV_TYPES <- local({ # nolint
type <- function(name, abbr, habbr, heterogeneous, spatial) {
args <- as.list(match.call()[-1])
do.call(data.frame, args)
}
as.data.frame(
col.names = names(formals(type)),
rbind(
type("unstructured", "us", NA, FALSE, FALSE),
type("Toeplitz", "toep", "toeph", TRUE, FALSE),
type("auto-regressive order one", "ar1", "ar1h", TRUE, FALSE),
type("ante-dependence", "ad", "adh", TRUE, FALSE),
type("compound symmetry", "cs", "csh", TRUE, FALSE),
type("spatial exponential", "sp_exp", NA, FALSE, TRUE),
type("spatial Gaussian", "sp_gau", NA, FALSE, TRUE)
)
)
})
#' Covariance Types
#'
#' @description `r lifecycle::badge("stable")`
#'
#' @param form (`character`)\cr covariance structure type name form. One or
#' more of `"name"`, `"abbr"` (abbreviation), or `"habbr"` (heterogeneous
#' abbreviation).
#' @param filter (`character`)\cr covariance structure type filter. One or
#' more of `"heterogeneous"` or `"spatial"`.
#'
#' @return A character vector of accepted covariance structure type names and
#' abbreviations.
#'
#' @section Abbreviations for Covariance Structures:
#'
#' ## Common Covariance Structures:
#'
#' \tabular{clll}{
#'
#' \strong{Structure}
#' \tab \strong{Description}
#' \tab \strong{Parameters}
#' \tab \strong{\eqn{(i, j)} element}
#' \cr
#'
#' ad
#' \tab Ante-dependence
#' \tab \eqn{m}
#' \tab \eqn{\sigma^{2}\prod_{k=i}^{j-1}\rho_{k}}
#' \cr
#'
#' adh
#' \tab Heterogeneous ante-dependence
#' \tab \eqn{2m-1}
#' \tab \eqn{\sigma_{i}\sigma_{j}\prod_{k=i}^{j-1}\rho_{k}}
#' \cr
#'
#' ar1
#' \tab First-order auto-regressive
#' \tab \eqn{2}
#' \tab \eqn{\sigma^{2}\rho^{\left \vert {i-j} \right \vert}}
#' \cr
#'
#' ar1h
#' \tab Heterogeneous first-order auto-regressive
#' \tab \eqn{m+1}
#' \tab \eqn{\sigma_{i}\sigma_{j}\rho^{\left \vert {i-j} \right \vert}}
#' \cr
#'
#' cs
#' \tab Compound symmetry
#' \tab \eqn{2}
#' \tab \eqn{\sigma^{2}\left[ \rho I(i \neq j)+I(i=j) \right]}
#' \cr
#'
#' csh
#' \tab Heterogeneous compound symmetry
#' \tab \eqn{m+1}
#' \tab \eqn{\sigma_{i}\sigma_{j}\left[ \rho I(i \neq j)+I(i=j) \right]}
#' \cr
#'
#' toep
#' \tab Toeplitz
#' \tab \eqn{m}
#' \tab \eqn{\sigma_{\left \vert {i-j} \right \vert +1}}
#' \cr
#'
#' toeph
#' \tab Heterogeneous Toeplitz
#' \tab \eqn{2m-1}
#' \tab \eqn{\sigma_{i}\sigma_{j}\rho_{\left \vert {i-j} \right \vert}}
#' \cr
#'
#' us
#' \tab Unstructured
#' \tab \eqn{m(m+1)/2}
#' \tab \eqn{\sigma_{ij}}
#'
#' }
#'
#' where \eqn{i} and \eqn{j} denote \eqn{i}-th and \eqn{j}-th time points,
#' respectively, out of total \eqn{m} time points, \eqn{1 \leq i, j \leq m}.
#'
#' @note The **ante-dependence** covariance structure in this package refers to
#' homogeneous ante-dependence, while the ante-dependence covariance structure
#' from SAS `PROC MIXED` refers to heterogeneous ante-dependence and the
#' homogeneous version is not available in SAS.
#'
#' @note For all non-spatial covariance structures, the time variable must
#' be coded as a factor.
#'
#' ## Spatial Covariance structures:
#'
#' \tabular{clll}{
#'
#' \strong{Structure}
#' \tab \strong{Description}
#' \tab \strong{Parameters}
#' \tab \strong{\eqn{(i, j)} element}
#' \cr
#'
#' sp_exp
#' \tab spatial exponential
#' \tab \eqn{2}
#' \tab \eqn{\sigma^{2}\rho^{-d_{ij}}}
#' sp_gau
#' \tab spatial Guassian
#' \tab \eqn{2}
#' \tab \eqn{\sigma^{2}\rho^{-d_{ij}^2}}
#' }
#'
#' where \eqn{d_{ij}} denotes the Euclidean distance between time points
#' \eqn{i} and \eqn{j}.
#'
#' @family covariance types
#' @name covariance_types
#' @export
cov_types <- function(
form = c("name", "abbr", "habbr"),
filter = c("heterogeneous", "spatial")) {
form <- match.arg(form, several.ok = TRUE)
filter <- if (missing(filter)) c() else match.arg(filter, several.ok = TRUE)
df <- COV_TYPES[form][rowSums(!COV_TYPES[filter]) == 0, ]
Filter(Negate(is.na), unlist(t(df), use.names = FALSE))
}
#' Retrieve Associated Abbreviated Covariance Structure Type Name
#'
#' @param type (`string`)\cr either a full name or abbreviate covariance
#' structure type name to collapse into an abbreviated type.
#'
#' @return The corresponding abbreviated covariance type name.
#'
#' @keywords internal
cov_type_abbr <- function(type) {
row <- which(COV_TYPES == type, arr.ind = TRUE)[, 1]
COV_TYPES$abbr[row]
}
#' Retrieve Associated Full Covariance Structure Type Name
#'
#' @param type (`string`)\cr either a full name or abbreviate covariance
#' structure type name to convert to a long-form type.
#'
#' @return The corresponding abbreviated covariance type name.
#'
#' @keywords internal
cov_type_name <- function(type) {
row <- which(COV_TYPES == type, arr.ind = TRUE)[, 1]
COV_TYPES$name[row]
}
#' Produce A Covariance Identifier Passing to TMB
#'
#' @param cov (`cov_struct`)\cr a covariance structure object.
#'
#' @return A string used for method dispatch when passed to TMB.
#'
#' @keywords internal
tmb_cov_type <- function(cov) {
paste0(cov$type, if (cov$heterogeneous) "h")
}
#' Define a Covariance Structure
#'
#' @description `r lifecycle::badge("stable")`
#'
#' @param type (`string`)\cr the name of the covariance structure type to use.
#' For available options, see `cov_types()`. If a type abbreviation is used
#' that implies heterogeneity (e.g. `cph`) and no value is provided to
#' `heterogeneous`, then the heterogeneity is derived from the type name.
#' @param visits (`character`)\cr a vector of variable names to use for the
#' longitudinal terms of the covariance structure. Multiple terms are only
#' permitted for the `"spatial"` covariance type.
#' @param subject (`string`)\cr the name of the variable that encodes a subject
#' identifier.
#' @param group (`string`)\cr optionally, the name of the variable that encodes
#' a grouping variable for subjects.
#' @param heterogeneous (`flag`)\cr
#'
#' @return A `cov_struct` object.
#'
#' @examples
#' cov_struct("csh", "AVISITN", "USUBJID")
#' cov_struct("spatial", c("VISITA", "VISITB"), group = "GRP", subject = "SBJ")
#'
#' @family covariance types
#' @export
cov_struct <- function(
type = cov_types(), visits, subject, group = character(),
heterogeneous = FALSE) {
# if heterogeneous isn't provided, derive from provided type
if (missing(heterogeneous)) {
heterogeneous <- switch(type,
toeph = ,
ar1h = ,
adh = ,
csh = TRUE,
heterogeneous
)
}
# coerce all type options into abbreviated form
type <- match.arg(type)
type <- cov_type_abbr(type)
x <- structure(
list(
type = type,
heterogeneous = heterogeneous,
visits = visits,
subject = subject,
group = group
),
class = c("cov_struct", "mmrm_cov_struct", "list")
)
validate_cov_struct(x)
}
#' Reconcile Possible Covariance Structure Inputs
#'
#' @inheritParams mmrm
#'
#' @return The value `covariance` if it's provided or a covariance structure
#' derived from the provided `formula` otherwise. An error is raised of both
#' are provided.
#'
#' @keywords internal
h_reconcile_cov_struct <- function(formula = NULL, covariance = NULL) {
assert_multi_class(covariance, c("formula", "cov_struct"), null.ok = TRUE)
assert_formula(formula, null.ok = FALSE)
if (inherits(covariance, "formula")) {
covariance <- as.cov_struct(covariance)
}
if (!is.null(covariance) && length(h_extract_covariance_terms(formula)) > 0) {
stop(paste0(
"Redundant covariance structure definition in `formula` and ",
"`covariance` arguments"
))
}
if (!is.null(covariance)) {
return(covariance)
}
as.cov_struct(formula, warn_partial = FALSE)
}
#' Validate Covariance Structure Data
#'
#' Run checks against relational integrity of covariance definition
#'
#' @param x (`cov_struct`)\cr a covariance structure object.
#'
#' @return `x` if successful, or an error is thrown otherwise.
#'
#' @keywords internal
validate_cov_struct <- function(x) {
checks <- checkmate::makeAssertCollection()
with(x, {
assert_character(subject, len = 1, add = checks)
assert_logical(heterogeneous, len = 1, add = checks)
if (length(group) > 1 || length(visits) < 1) {
checks$push(
"Covariance structure must be of the form `time | (group /) subject`"
)
}
if (!type %in% cov_types(filter = "spatial") && length(visits) > 1) {
checks$push(paste(
"Non-spatial covariance structures must have a single longitudinal",
"variable"
))
}
})
reportAssertions(checks)
x
}
#' Format Covariance Structure Object
#'
#' @param x (`cov_struct`)\cr a covariance structure object.
#' @param ... Additional arguments unused.
#'
#' @return A formatted string for `x`.
#'
#' @export
format.cov_struct <- function(x, ...) {
sprintf(
"<covariance structure>\n%s%s:\n\n %s | %s%s\n",
if (x$heterogeneous) "heterogeneous " else "",
cov_type_name(x$type),
format_symbols(x$visits),
if (length(x$group) > 0) paste0(format_symbols(x$group), " / ") else "",
format_symbols(x$subject)
)
}
#' Print a Covariance Structure Object
#'
#' @param x (`cov_struct`)\cr a covariance structure object.
#' @param ... Additional arguments unused.
#'
#' @return `x` invisibly.
#'
#' @export
print.cov_struct <- function(x, ...) {
cat(format(x, ...), "\n")
invisible(x)
}
#' Coerce into a Covariance Structure Definition
#'
#' @description `r lifecycle::badge("stable")`
#'
#' @details
#' A covariance structure can be parsed from a model definition formula or call.
#' Generally, covariance structures defined using non-standard evaluation take
#' the following form:
#'
#' ```
#' type( (visit, )* visit | (group /)? subject )
#' ```
#'
#' For example, formulas may include terms such as
#'
#' ```r
#' us(time | subject)
#' cp(time | group / subject)
#' sp_exp(coord1, coord2 | group / subject)
#' ```
#'
#' Note that only spatial covariance structures may provide multiple
#' coordinates, which identify the Euclidean distance between the time points.
#'
#' @param x an object from which to derive a covariance structure. See object
#' specific sections for details.
#' @param warn_partial (`flag`)\cr whether to emit a warning when parts of the
#' formula are disregarded.
#' @param ... additional arguments unused.
#'
#' @return A [cov_struct()] object.
#'
#' @examples
#' # provide a covariance structure as a right-sided formula
#' as.cov_struct(~ csh(visit | group / subject))
#'
#' # when part of a full formula, suppress warnings using `warn_partial = FALSE`
#' as.cov_struct(y ~ x + csh(visit | group / subject), warn_partial = FALSE)
#'
#' @family covariance types
#' @export
as.cov_struct <- function(x, ...) { # nolint
UseMethod("as.cov_struct")
}
#' @export
as.cov_struct.cov_struct <- function(x, ...) {
x
}
#' @describeIn as.cov_struct
#' When provided a formula, any specialized functions are assumed to be
#' covariance structure definitions and must follow the form:
#'
#' ```
#' y ~ xs + type( (visit, )* visit | (group /)? subject )
#' ```
#'
#' Any component on the right hand side of a formula is considered when
#' searching for a covariance definition.
#'
#' @export
as.cov_struct.formula <- function(x, warn_partial = TRUE, ...) {
x_calls <- h_extract_covariance_terms(x)
if (length(x_calls) < 1) {
stop(
"Covariance structure must be specified in formula. ",
"Possible covariance structures include: ",
paste0(cov_types(c("abbr", "habbr")), collapse = ", ")
)
}
if (length(x_calls) > 1) {
cov_struct_types <- as.character(lapply(x_calls, `[[`, 1L))
stop(
"Only one covariance structure can be specified. ",
"Currently specified covariance structures are: ",
paste0(cov_struct_types, collapse = ", ")
)
}
# flatten into list of infix operators, calls and names/atomics
x <- flatten_call(x_calls[[1]])
type <- as.character(x[[1]])
x <- drop_elements(x, 1)
# take visits until "|"
n <- position_symbol(x, "|", nomatch = 0)
visits <- as.character(utils::head(x, max(n - 1, 0)))
x <- drop_elements(x, n)
# take group until "/"
n <- position_symbol(x, "/", nomatch = 0)
group <- as.character(utils::head(x, max(n - 1, 0)))
x <- drop_elements(x, n)
# remainder is subject
subject <- as.character(x)
cov_struct(type = type, visits = visits, group = group, subject = subject)
}