In the ideal world the "unstructured" pattern of covariance should save us from complex, hard-to-guess-apriori considerations about the expected residual cov. structure. If only it always converged :)
If it fails to converge, there are some options to account for it in our SAPs:
-
we can pre-specify the path of "fallback", like: US -> ADH, -> AD -> AR1 H or Toeplitz (stationary-M-dependent(m)) or spatial -> CSH -> CS
-
guess the covariance structure a priori, based on some prior investigations, literature, common sense. People often assume just AR1 or SP(POW) [for unequal visits/timepoints].
-
follow the least-resistance line or not care by specifying CS. Seems legit :) Nobody will argue "hey, you ignored the within-subject covariance", easy to fit, easy to talk about, compliant with tons of literature. Only it can be far from reality and even farther from any reasonable scientific justification for the strong assumption that "all subjects' organisms respond with the same rate" (approximately translates to random-intercept LMM, if we enforce only non-negative covariances).
-
some specify automated selection methods, e.g. based on AIC/BIC/AICC (or QIC/AGPC/GHYC/etc. in GEE). BTW, I found mmrm:::AIC.mmrm_tmb() and mmrm:::BIC.mmrm_tmb() but missed AICC.
In either case, some people (including myself) are curious about the autocorrelation and look at ACF and/or (semi)variogram to check if and how each structure helped (or messed): 1) acf(residuals(model, type="response")) then 2) acf(residuals(model, type="normalized"))
For nlme::gls() we can use either:
acf(residuals(model, type="normalized")), but it loses the information about clusters (subjects)
layout(1:2)
acf(resid(ma, type = "response"))
acf(resid(ma, type = "normalized"))

plot(ACF(ma, form=~1|PatientId, resType = "response"), grid = TRUE), which preserves the number of timepoints (visits)
gridExtra::grid.arrange(plot(ACF(ma, form=~1|PatientId,resType = "response"), alpha = .05),
plot(ACF(ma, form=~1|PatientId,resType = "normalized"), alpha = .05))

Optionally also:
gridExtra::grid.arrange(
plot(Variogram(ma, resType = "response"), smooth = FALSE, sigma = ma$sigma, ylim = c(0, 1.1*ma$sigma)),
plot(Variogram(ma, resType = "normalized"), smooth = FALSE, sigma = ma$sigma, ylim = c(0, 1.1*ma$sigma))
)

For the mmrm we can still use acf(residuals(model, type="normalized")), but, as mentioned previously, it loses the information about clusters (subjects). OK, if used just for some very rough exploration, it can be so. Some patterns will be visible anyway.
For semi-variogram I can employ the nlme::gls() followed by its Variogram() function with appropriate type of residuals ("response" for the raw, "normalized" accounting for the covariance), which isn't very much convenient in terms of translating correlation + weight terms in gls() into mmrm's us/ar1/csh/cs, etc.
If it can be done better, than why not :)
For example, my assessments look like:
$ICs
struct AIC BIC
------------------------ --------- ---------
1) unstructured 821.482 868.7004
2) ante-dependence het. 846.7565 871.49
3) ante-dependence 845.5287 859.0197
4) AR(1) het. 845.9508 861.6902
5) AR(1) 847.4463 851.9432
6) exchangeable het. 867.0957 882.8352
7) exchangeable 869.382 873.879
$ACF_plot

PS: just a very minor thing - for xIC comparisons it would be helpful to have "ind()" (independence) structure in mmrm. You know, something like nlme::gls(correlation = NULL, weights = varIdent(form =~1|Visit)). Just a technical "sugar". Just saying :)
In the ideal world the "unstructured" pattern of covariance should save us from complex, hard-to-guess-apriori considerations about the expected residual cov. structure. If only it always converged :)
If it fails to converge, there are some options to account for it in our SAPs:
we can pre-specify the path of "fallback", like: US -> ADH, -> AD -> AR1 H or Toeplitz (stationary-M-dependent(m)) or spatial -> CSH -> CS
guess the covariance structure a priori, based on some prior investigations, literature, common sense. People often assume just AR1 or SP(POW) [for unequal visits/timepoints].
follow the least-resistance line or not care by specifying CS. Seems legit :) Nobody will argue "hey, you ignored the within-subject covariance", easy to fit, easy to talk about, compliant with tons of literature. Only it can be far from reality and even farther from any reasonable scientific justification for the strong assumption that "all subjects' organisms respond with the same rate" (approximately translates to random-intercept LMM, if we enforce only non-negative covariances).
some specify automated selection methods, e.g. based on AIC/BIC/AICC (or QIC/AGPC/GHYC/etc. in GEE). BTW, I found
mmrm:::AIC.mmrm_tmb()andmmrm:::BIC.mmrm_tmb()but missed AICC.In either case, some people (including myself) are curious about the autocorrelation and look at ACF and/or (semi)variogram to check if and how each structure helped (or messed): 1)
acf(residuals(model, type="response"))then 2)acf(residuals(model, type="normalized"))For nlme::gls() we can use either:
acf(residuals(model, type="normalized")), but it loses the information about clusters (subjects)plot(ACF(ma, form=~1|PatientId, resType = "response"), grid = TRUE), which preserves the number of timepoints (visits)Optionally also:
For the mmrm we can still use
acf(residuals(model, type="normalized")), but, as mentioned previously, it loses the information about clusters (subjects). OK, if used just for some very rough exploration, it can be so. Some patterns will be visible anyway.For semi-variogram I can employ the nlme::gls() followed by its Variogram() function with appropriate type of residuals ("response" for the raw, "normalized" accounting for the covariance), which isn't very much convenient in terms of translating correlation + weight terms in gls() into mmrm's us/ar1/csh/cs, etc.
If it can be done better, than why not :)
For example, my assessments look like:
PS: just a very minor thing - for xIC comparisons it would be helpful to have "ind()" (independence) structure in mmrm. You know, something like nlme::gls(correlation = NULL, weights = varIdent(form =~1|Visit)). Just a technical "sugar". Just saying :)