Common usage

A minimal call of mmrm(), consisting of only formula and data arguments will produce an object of class mmrm, mmrm_fit, and mmrm_tmb. Here we fit a mmrm model with us (unstructured) covariance structure specified, as well as the defaults of reml = TRUE and optimizer = 'automatic'.

fit <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data
)

Printing the object will show you output which should be familiar to anyone who has used any popular modeling functions such as stats::lm(), stats::glm(), glmmTMB::glmmTMB(), and lme4::nlmer(). From this print out we see the function call, the data used, the covariance structure with number of variance parameters, as well as the likelihood method, and model deviance achieved. Additionally the user is provided a printout of the estimated coefficients and the model convergence information.

print(fit)
#> mmrm fit
#> 
#> Formula:     FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID)
#> Data:        fev_data (used 537 observations from 197 subjects with maximum 4 
#> timepoints)
#> Covariance:  unstructured (10 variance parameters)
#> Method:      REML
#> Deviance:    3387.373
#> 
#> Coefficients: 
#>                   (Intercept) RACEBlack or African American 
#>                   30.96769899                    1.50464863 
#>                     RACEWhite                      ARMCDTRT 
#>                    5.61309565                    3.77555734 
#>                    AVISITVIS2                    AVISITVIS3 
#>                    4.82858803                   10.33317002 
#>                    AVISITVIS4           ARMCDTRT:AVISITVIS2 
#>                   15.05255715                   -0.01737409 
#>           ARMCDTRT:AVISITVIS3           ARMCDTRT:AVISITVIS4 
#>                   -0.66753189                    0.63094392 
#> 
#> Model Inference Optimization:
#> Converged with code 0 and message: convergence: rel_reduction_of_f <= factr*epsmch

Common customizations

From the high-level mmrm() interface, common changes to the default function call can be specified.

REML or ML

Users can specify if REML should be used (default) or if ML should be used in optimization.

fit_ml <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data,
  reml = FALSE
)

print(fit_ml)
#> mmrm fit
#> 
#> Formula:     FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID)
#> Data:        fev_data (used 537 observations from 197 subjects with maximum 4 
#> timepoints)
#> Covariance:  unstructured (10 variance parameters)
#> Method:      ML
#> Deviance:    3397.934
#> 
#> Coefficients: 
#>                   (Intercept) RACEBlack or African American 
#>                    30.9663423                     1.5086851 
#>                     RACEWhite                      ARMCDTRT 
#>                     5.6133151                     3.7761037 
#>                    AVISITVIS2                    AVISITVIS3 
#>                     4.8270155                    10.3353319 
#>                    AVISITVIS4           ARMCDTRT:AVISITVIS2 
#>                    15.0487715                    -0.0156154 
#>           ARMCDTRT:AVISITVIS3           ARMCDTRT:AVISITVIS4 
#>                    -0.6663598                     0.6317222 
#> 
#> Model Inference Optimization:
#> Converged with code 0 and message: convergence: rel_reduction_of_f <= factr*epsmch

Optimizer

Users can specify which optimizer should be used, changing from the default of automatic selection, which starts with L-BFGS-B and proceeds through the other choices if optimization fails to converge. Other choices are BFGS, CG, and nlminb.

L-BFGS-B, BFGS and CG are all implemented with stats::optim() and the Hessian is not used, while nlminb is using `stats::nlminb() which in turn uses both the gradient and the Hessian for the optimization.

fit_opt <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data,
  optimizer = "BFGS"
)

print(fit_opt)
#> mmrm fit
#> 
#> Formula:     FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID)
#> Data:        fev_data (used 537 observations from 197 subjects with maximum 4 
#> timepoints)
#> Covariance:  unstructured (10 variance parameters)
#> Method:      REML
#> Deviance:    3387.373
#> 
#> Coefficients: 
#>                   (Intercept) RACEBlack or African American 
#>                   30.96769004                    1.50467449 
#>                     RACEWhite                      ARMCDTRT 
#>                    5.61310489                    3.77554263 
#>                    AVISITVIS2                    AVISITVIS3 
#>                    4.82858562                   10.33317692 
#>                    AVISITVIS4           ARMCDTRT:AVISITVIS2 
#>                   15.05257072                   -0.01735093 
#>           ARMCDTRT:AVISITVIS3           ARMCDTRT:AVISITVIS4 
#>                   -0.66751927                    0.63095827 
#> 
#> Model Inference Optimization:
#> Converged with code 0 and message:

Covariance Structure

Covariance structures supported by the mmrm are being continuously developed. For a complete list and description please visit the vignette in the package website. Below we see the function call for homogeneous compound symmetry (cs).

fit_cs <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + cs(AVISIT | USUBJID),
  data = fev_data,
  reml = FALSE
)

print(fit_cs)
#> mmrm fit
#> 
#> Formula:     FEV1 ~ RACE + ARMCD * AVISIT + cs(AVISIT | USUBJID)
#> Data:        fev_data (used 537 observations from 197 subjects with maximum 4 
#> timepoints)
#> Covariance:  compound symmetry (2 variance parameters)
#> Method:      ML
#> Deviance:    3536.989
#> 
#> Coefficients: 
#>                   (Intercept) RACEBlack or African American 
#>                    31.4207077                     0.5357237 
#>                     RACEWhite                      ARMCDTRT 
#>                     5.4546329                     3.4305212 
#>                    AVISITVIS2                    AVISITVIS3 
#>                     4.8326353                    10.2395076 
#>                    AVISITVIS4           ARMCDTRT:AVISITVIS2 
#>                    15.0672680                     0.2801641 
#>           ARMCDTRT:AVISITVIS3           ARMCDTRT:AVISITVIS4 
#>                    -0.5894964                     0.7939750 
#> 
#> Model Inference Optimization:
#> Converged with code 0 and message: convergence: rel_reduction_of_f <= factr*epsmch

Weighting

Users can perform weighted MMRM by specifying a numeric vector weights with positive values.

fit_wt <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data,
  weights = fev_data$WEIGHT
)

print(fit_wt)
#> mmrm fit
#> 
#> Formula:     FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID)
#> Data:        fev_data (used 537 observations from 197 subjects with maximum 4 
#> timepoints)
#> Weights:     fev_data$WEIGHT
#> Covariance:  unstructured (10 variance parameters)
#> Method:      REML
#> Deviance:    3476.526
#> 
#> Coefficients: 
#>                   (Intercept) RACEBlack or African American 
#>                   31.20065229                    1.18452837 
#>                     RACEWhite                      ARMCDTRT 
#>                    5.36525917                    3.39695951 
#>                    AVISITVIS2                    AVISITVIS3 
#>                    4.85890820                   10.03942420 
#>                    AVISITVIS4           ARMCDTRT:AVISITVIS2 
#>                   14.79354054                    0.03418184 
#>           ARMCDTRT:AVISITVIS3           ARMCDTRT:AVISITVIS4 
#>                    0.01308088                    0.86701567 
#> 
#> Model Inference Optimization:
#> Converged with code 0 and message: convergence: rel_reduction_of_f <= factr*epsmch

Grouped Covariance Structure

Grouped covariance structures are supported by themmrm package. Covariance matrices for each group are identically structured (unstructured, compound symmetry, etc) but the estimates are allowed to vary across groups. We use the form cs(time | group / subject) to specify the group variable.

Here is an example of how we use ARMCD as group variable.

fit_cs <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + cs(AVISIT | ARMCD / USUBJID),
  data = fev_data,
  reml = FALSE
)

print(VarCorr(fit_cs))
#> $PBO
#>           VIS1      VIS2      VIS3      VIS4
#> VIS1 37.823638  3.601296  3.601296  3.601296
#> VIS2  3.601296 37.823638  3.601296  3.601296
#> VIS3  3.601296  3.601296 37.823638  3.601296
#> VIS4  3.601296  3.601296  3.601296 37.823638
#> 
#> $TRT
#>          VIS1     VIS2     VIS3     VIS4
#> VIS1 49.58110 10.98112 10.98112 10.98112
#> VIS2 10.98112 49.58110 10.98112 10.98112
#> VIS3 10.98112 10.98112 49.58110 10.98112
#> VIS4 10.98112 10.98112 10.98112 49.58110

We can see that the estimated covariance matrices are different in different ARMCD groups.

Extraction of model features

Similar to model objects created in other packages, components of mmrm and mmrm_tmb objects can be accessed with standard methods. Additionally, component() is provided to allow deeper and more precise access for those interested in digging through model output. Complete documentation of standard model output methods supported for mmrm_tmb objects can be found at the package website.

Summary extraction

The summary method for mmrm objects provides easy access to frequently needed model components.

fit <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data
)

fit_summary <- summary(fit)

From this summary object, you can easily retrieve the coefficients table.

fit_summary$coefficients
#>                                  Estimate Std. Error       df     t value
#> (Intercept)                   30.96769899  0.8293349 187.9132 37.34040185
#> RACEBlack or African American  1.50464863  0.6206596 169.9454  2.42427360
#> RACEWhite                      5.61309565  0.6630909 158.8700  8.46504747
#> ARMCDTRT                       3.77555734  1.0762774 146.2690  3.50797778
#> AVISITVIS2                     4.82858803  0.8017144 143.6593  6.02282805
#> AVISITVIS3                    10.33317002  0.8224414 155.6572 12.56401918
#> AVISITVIS4                    15.05255715  1.3128602 138.3916 11.46546844
#> ARMCDTRT:AVISITVIS2           -0.01737409  1.1291645 138.3926 -0.01538668
#> ARMCDTRT:AVISITVIS3           -0.66753189  1.1865359 158.2106 -0.56258887
#> ARMCDTRT:AVISITVIS4            0.63094392  1.8507884 129.6377  0.34090549
#>                                   Pr(>|t|)
#> (Intercept)                   7.122406e-89
#> RACEBlack or African American 1.638725e-02
#> RACEWhite                     1.605553e-14
#> ARMCDTRT                      6.001485e-04
#> AVISITVIS2                    1.366921e-08
#> AVISITVIS3                    1.927523e-25
#> AVISITVIS4                    8.242709e-22
#> ARMCDTRT:AVISITVIS2           9.877459e-01
#> ARMCDTRT:AVISITVIS3           5.745112e-01
#> ARMCDTRT:AVISITVIS4           7.337266e-01

Other model parameters and metadata available in the summary object is as follows:

str(fit_summary)
#> List of 13
#>  $ cov_type        : chr "us"
#>  $ reml            : logi TRUE
#>  $ n_groups        : int 1
#>  $ n_theta         : int 10
#>  $ n_subjects      : int 197
#>  $ n_timepoints    : int 4
#>  $ n_obs           : int 537
#>  $ beta_vcov       : num [1:10, 1:10] 0.688 -0.207 -0.163 -0.569 -0.422 ...
#>   ..- attr(*, "dimnames")=List of 2
#>   .. ..$ : chr [1:10] "(Intercept)" "RACEBlack or African American" "RACEWhite" "ARMCDTRT" ...
#>   .. ..$ : chr [1:10] "(Intercept)" "RACEBlack or African American" "RACEWhite" "ARMCDTRT" ...
#>  $ varcor          : num [1:4, 1:4] 40.73 14.27 5.14 13.53 14.27 ...
#>   ..- attr(*, "dimnames")=List of 2
#>   .. ..$ : chr [1:4] "VIS1" "VIS2" "VIS3" "VIS4"
#>   .. ..$ : chr [1:4] "VIS1" "VIS2" "VIS3" "VIS4"
#>  $ coefficients    : num [1:10, 1:5] 30.97 1.5 5.61 3.78 4.83 ...
#>   ..- attr(*, "dimnames")=List of 2
#>   .. ..$ : chr [1:10] "(Intercept)" "RACEBlack or African American" "RACEWhite" "ARMCDTRT" ...
#>   .. ..$ : chr [1:5] "Estimate" "Std. Error" "df" "t value" ...
#>  $ n_singular_coefs: int 0
#>  $ aic_list        :List of 4
#>   ..$ AIC     : num 3407
#>   ..$ BIC     : num 3440
#>   ..$ logLik  : num -1694
#>   ..$ deviance: num 3387
#>  $ call            : language fit_mmrm(formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),      data = "fev_data", weights = weights| __truncated__
#>  - attr(*, "class")= chr "summary.mmrm"

Other components

Specific model quantities not supported by methods can be retrieved with the component() function. The default will output all supported components.

For example, a user may want information about convergence:

component(fit, name = c("convergence", "evaluations", "conv_message"))
#> $convergence
#> [1] 0
#> 
#> $evaluations
#> function gradient 
#>       17       17 
#> 
#> $conv_message
#> [1] "CONVERGENCE: REL_REDUCTION_OF_F <= FACTR*EPSMCH"

or the original low-level call:

component(fit, name = "call")
#> fit_mmrm(formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | 
#>     USUBJID), data = "fev_data", weights = weights, reml = reml, 
#>     control = control)

the user could also ask for all provided components by not specifying the name argument.

component(fit)

Lower level functions

Low-level mmrm

The lower level function which is called by mmrm() is fit_mmrm(). This function is exported and can be used directly. It is similar to mmrm() but lacks some post-processing and support for Satterthwaite d.f. calculations. However, it exposes an argument for fine control over optimization which may be needed by some users.

fit_mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data,
  weights = rep(1, nrow(fev_data)),
  reml = TRUE,
  control = mmrm_control()
)
#> mmrm fit
#> 
#> Formula:     FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID)
#> Data:        fev_data (used 537 observations from 197 subjects with maximum 4 
#> timepoints)
#> Covariance:  unstructured (10 variance parameters)
#> Method:      REML
#> Deviance:    3387.373
#> 
#> Coefficients: 
#>                   (Intercept) RACEBlack or African American 
#>                   30.96769000                    1.50467592 
#>                     RACEWhite                      ARMCDTRT 
#>                    5.61310611                    3.77554333 
#>                    AVISITVIS2                    AVISITVIS3 
#>                    4.82858471                   10.33317565 
#>                    AVISITVIS4           ARMCDTRT:AVISITVIS2 
#>                   15.05257417                   -0.01735434 
#>           ARMCDTRT:AVISITVIS3           ARMCDTRT:AVISITVIS4 
#>                   -0.66752150                    0.63095366 
#> 
#> Model Inference Optimization:
#> Converged with code 0 and message: both x-convergence and relative convergence (5)

For fine control of optimization routine, mmrm_control() is provided. This function allows the user to specify optimization routine with optimizer, pass arguments to that optimizer with optimizer_args, provide a list of control parameters with optimizer_control, provide a list of starting parameter values with start, and decide the action to be taken when the defined design matrix is singular with accept_singular.

mmrm_control(
  optimizer = stats::nlminb,
  optimizer_args = list(upper = Inf, lower = 0),
  optimizer_control = list(),
  start = c(0, 1, 1, 0, 1, 0),
  accept_singular = FALSE
)

Hypothesis testing

This package supports estimation of one- and multi-dimensional contrasts (t-test and F-test calculation) with the df_1d() and df_md() functions. Both functions utilize Satterthwaite’s method for the calculation of test degrees of freedom.

One-dimensional contrasts

Compute the test of a one-dimensional (vector) contrast for a mmrm object with Satterthwaite degrees of freedom.

fit <- mmrm(
  formula = FEV1 ~ RACE + SEX + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data
)

contrast <- numeric(length(component(fit, "beta_est")))
contrast[3] <- 1

df_1d(fit, contrast)
#> $est
#> [1] 5.643565
#> 
#> $se
#> [1] 0.6656093
#> 
#> $df
#> [1] 157.1382
#> 
#> $t_stat
#> [1] 8.478795
#> 
#> $p_val
#> [1] 1.564869e-14

Multi-dimensional contrasts

Compute the test of a multi-dimensional (matrix) contrast for a mmrm object with Satterthwaite degrees of freedom.

fit <- mmrm(
  formula = FEV1 ~ RACE + SEX + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data
)

contrast <- matrix(data = 0, nrow = 2, ncol = length(component(fit, "beta_est")))
contrast[1, 2] <- contrast[2, 3] <- 1

df_md(fit, contrast)
#> $num_df
#> [1] 2
#> 
#> $denom_df
#> [1] 165.5553
#> 
#> $f_stat
#> [1] 36.91143
#> 
#> $p_val
#> [1] 5.544575e-14

Support for emmeans

This package includes methods that allow mmrm objects to be used with the emmeans package. emmeans computes estimated marginal means (also called least-square means) for the coefficients of the MMRM.

fit <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data
)

if (require(emmeans)) {
  emmeans(fit, ~ ARMCD | AVISIT)
}
#> Loading required package: emmeans
#> AVISIT = VIS1:
#>  ARMCD emmean    SE  df lower.CL upper.CL
#>  PBO     33.3 0.757 149     31.8     34.8
#>  TRT     37.1 0.764 144     35.6     38.6
#> 
#> AVISIT = VIS2:
#>  ARMCD emmean    SE  df lower.CL upper.CL
#>  PBO     38.2 0.608 150     37.0     39.4
#>  TRT     41.9 0.598 146     40.7     43.1
#> 
#> AVISIT = VIS3:
#>  ARMCD emmean    SE  df lower.CL upper.CL
#>  PBO     43.7 0.462 131     42.8     44.6
#>  TRT     46.8 0.507 130     45.8     47.8
#> 
#> AVISIT = VIS4:
#>  ARMCD emmean    SE  df lower.CL upper.CL
#>  PBO     48.4 1.189 134     46.0     50.7
#>  TRT     52.8 1.188 133     50.4     55.1
#> 
#> Results are averaged over the levels of: RACE 
#> Confidence level used: 0.95

Acknowledgments

The mmrm package is based on previous work internal in Roche, namely the tern and tern.mmrm packages which were based on lme4. The work done in the rbmi package has been important since it used glmmTMB for fitting MMRMs.

We would like to thank Ben Bolker from the glmmTMB team for multiple discussions when we tried to get Satterthwaite degrees of freedom implemented with glmmTMB (see https://github.com/glmmTMB/glmmTMB/blob/satterthwaite_df/glmmTMB/vignettes/satterthwaite_unstructured_example2.Rmd). Also Ben helped significantly with an example showing how to use TMB for a random effect vector (https://github.com/bbolker/tmb-case-studies/tree/master/vectorMixed).