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

Control Function

For fine control, mmrm_control() is provided. This function allows the user to choose the adjustment method for the degrees of freedom and the coefficients covariance matrix, specify optimization routines, number of cores to be used on Unix systems for trying several optimizers in parallel, provide a vector of starting parameter values, decide the action to be taken when the defined design matrix is singular, not drop unobserved visit levels. For example:

mmrm_control(
  method = "Kenward-Roger",
  optimizer = c("L-BFGS-B", "BFGS"),
  n_cores = 2,
  start = c(0, 1, 1, 0, 1, 0),
  accept_singular = FALSE,
  drop_visit_levels = FALSE
)

Note that this control list can either be passed via the control argument to mmrm, or selected controls can be directly specified in the mmrm call. We will see this below.

Starting Values

The starting values will affect the optimization result. A better starting value usually can make the optimization more efficient. In mmrm we provide two starting value functions, one is std_start and the other is emp_start. std_start will try to use the identity matrix as the covariance, however there are convergence problems for ar1 and ar1h if the identity matrix is provided, thus for these two covariance structures we use \(\rho=0.5\) instead. emp_start will try to use the empirical covariance matrix of the residuals of the ordinary least squares model as the starting value for unstructured covariance structure. If some timepoints are missing from data, identity matrix will be used for that submatrix. The correlation between existing and non-existing timepoints are set to 0.

As the starting values will affect the result, please be cautious on choosing the starting values.

Example of Default Starting Value Fails

Here we provide an example where the std_start fails. In the following code chunk, we will create a dummy dataset for mmrm analysis.

gen_data <- function(
    n = 100,
    mu = -100 / 52,
    delta = 50 / 52,
    mua = 2000,
    sigmaa = 300,
    sigmab = 60,
    corab = 0.2,
    sigma = 10,
    times = c(0, 2, 6, 12, 24, 36, 52, 70, 88, 104)) {
  nt <- length(times)
  out <- data.frame(
    pts = rep(seq_len(n * 2), each = nt),
    trt = rep(c("Treatment", "Placebo"), rep(n * nt, 2)),
    time = rep(times, n * 2)
  )

  covab <- corab * sigmaa * sigmab # cov between a and b
  cov <- matrix(c(sigmaa^2, covab, covab, sigmab^2), ncol = 2) # Cov matrix for the slope and intercept
  si <- rbind(
    MASS::mvrnorm(n, mu = c(mua, mu + delta), Sigma = cov),
    MASS::mvrnorm(n, mu = c(mua, mu + delta), Sigma = cov)
  )
  idx <- rep(seq_len(n * 2), each = nt)
  out$fev1 <- si[idx, 1] + si[idx, 2] * times + rnorm(n * nt * 2, sd = sigma)
  out$trt <- factor(out$trt)
  out$time <- factor(out$time)
  out$pts <- factor(out$pts)
  return(out)
}
set.seed(123)
out <- gen_data()

In the generated data, the variance is not in the same scale across visits.

vapply(split(out$fev1, out$time), sd, FUN.VALUE = 1)
##         0         2         6        12        24        36        52        70 
##  278.6079  319.0589  482.4172  799.9107 1491.1440 2194.5776 3140.0768 4204.9355 
##        88       104 
## 5272.6041 6221.2195

Using emp_start as the starting value, mmrm will converge fast.

mmrm(fev1 ~ trt * time + us(time | pts), data = out, start = emp_start)
## mmrm fit
## 
## Formula:     fev1 ~ trt * time + us(time | pts)
## Data:        out (used 2000 observations from 200 subjects with maximum 10 
## timepoints)
## Covariance:  unstructured (55 variance parameters)
## Inference:   REML
## Deviance:    19154.63
## 
## Coefficients: 
##          (Intercept)         trtTreatment                time2 
##         1962.6980059           11.3831958            0.2130684 
##                time6               time12               time24 
##           -1.6901336           -1.1984278           -7.3954489 
##               time36               time52               time70 
##          -11.4078896          -15.8040921          -22.5556525 
##               time88              time104   trtTreatment:time2 
##          -28.2068896          -33.6608068            6.0436315 
##   trtTreatment:time6  trtTreatment:time12  trtTreatment:time24 
##           27.3686373           49.4246567          107.3638488 
##  trtTreatment:time36  trtTreatment:time52  trtTreatment:time70 
##          161.4310444          233.6438342          316.7387101 
##  trtTreatment:time88 trtTreatment:time104 
##          397.9895966          471.8871913 
## 
## Model Inference Optimization:
## Converged with code 0 and message: convergence: rel_reduction_of_f <= factr*epsmch

However, if we use std_start, there will be convergence problems. We can also force a specific optimization algorithm and add control details, here e.g. choosing nlminb with increased maximum number of function evaluations and iterations.

mmrm(
  fev1 ~ trt * time + us(time | pts),
  data = out,
  start = std_start,
  optimizer = "nlminb",
  optimizer_control = list(eval.max = 1000, iter.max = 1000)
)

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
)
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)
## Inference:   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 four optimizers, which starts with L-BFGS-B and proceeds through the other choices if optimization fails to converge. Other choices are BFGS, CG, nlminb and other user-defined custom optimizers.

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 (by default but can be switch off) for the optimization.

fit_opt <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data,
  optimizer = "BFGS"
)
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)
## Inference:   REML
## Deviance:    3387.373
## 
## Coefficients: 
##                   (Intercept) RACEBlack or African American 
##                    30.9676894                     1.5046748 
##                     RACEWhite                      ARMCDTRT 
##                     5.6131058                     3.7755440 
##                    AVISITVIS2                    AVISITVIS3 
##                     4.8285859                    10.3331765 
##                    AVISITVIS4           ARMCDTRT:AVISITVIS2 
##                    15.0525711                    -0.0173539 
##           ARMCDTRT:AVISITVIS3           ARMCDTRT:AVISITVIS4 
##                    -0.6675207                     0.6309568 
## 
## Model Inference Optimization:
## Converged with code 0 and message: No message provided.

Covariance Structure

Covariance structures supported by the mmrm are being continuously developed. For a complete list and description please visit the covariance vignette. 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
)
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)
## Inference:   ML
## Deviance:    3536.989
## 
## Coefficients: 
##                   (Intercept) RACEBlack or African American 
##                    31.4207078                     0.5357236 
##                     RACEWhite                      ARMCDTRT 
##                     5.4546329                     3.4305211 
##                    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

The time points have to be unique for each subject. That is, there cannot be time points with multiple observations for any subject. The rationale is that these observations would need to be correlated, but it is not possible within the currently implemented covariance structure framework to do that correctly. Moreover, for non-spatial covariance structures, the time variable must be coded as a factor.

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
)
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)
## Inference:   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
)
VarCorr(fit_cs)
## $PBO
##           VIS1      VIS2      VIS3      VIS4
## VIS1 37.822313  3.600996  3.600996  3.600996
## VIS2  3.600996 37.822313  3.600996  3.600996
## VIS3  3.600996  3.600996 37.822313  3.600996
## VIS4  3.600996  3.600996  3.600996 37.822313
## 
## $TRT
##          VIS1     VIS2     VIS3     VIS4
## VIS1 49.58031 10.98097 10.98097 10.98097
## VIS2 10.98097 49.58031 10.98097 10.98097
## VIS3 10.98097 10.98097 49.58031 10.98097
## VIS4 10.98097 10.98097 10.98097 49.58031

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

Adjustment Method

In additional to the residual and Between-Within degrees of freedom, both Satterthwaite and Kenward-Roger adjustment methods are available. The default is Satterthwaite adjustment of the degrees of freedom. To use e.g. the Kenward-Roger adjustment of the degrees of freedom as well as the coefficients covariance matrix, use the method argument:

A list of all allowed method is

  1. “Kenward-Roger”
  2. “Satterthwaite”
  3. “Residual”
  4. “Between-Within”
fit_kr <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data,
  method = "Kenward-Roger"
)

Note that this requires reml = TRUE, i.e. Kenward-Roger adjustment is not possible when using maximum likelihood inference. While this adjustment choice is not visible in the print() result of the fitted model (because the initial model fit is not affected by the choice of the adjustment method), looking at the summary we see the method and the correspondingly adjusted standard errors and degrees of freedom:

summary(fit_kr)
## 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:      Kenward-Roger
## Vcov Method: Kenward-Roger
## Inference:   REML
## 
## Model selection criteria:
##      AIC      BIC   logLik deviance 
##   3407.4   3440.2  -1693.7   3387.4 
## 
## Coefficients: 
##                                Estimate Std. Error        df t value Pr(>|t|)
## (Intercept)                    30.96770    0.83335 187.91000  37.160  < 2e-16
## RACEBlack or African American   1.50465    0.62901 169.95000   2.392  0.01784
## RACEWhite                       5.61310    0.67139 158.87000   8.360 2.98e-14
## ARMCDTRT                        3.77556    1.07910 146.27000   3.499  0.00062
## AVISITVIS2                      4.82859    0.80408 143.66000   6.005 1.49e-08
## AVISITVIS3                     10.33317    0.82303 155.66000  12.555  < 2e-16
## AVISITVIS4                     15.05256    1.30180 138.39000  11.563  < 2e-16
## ARMCDTRT:AVISITVIS2            -0.01737    1.13154 138.39000  -0.015  0.98777
## ARMCDTRT:AVISITVIS3            -0.66753    1.18714 158.21000  -0.562  0.57470
## ARMCDTRT:AVISITVIS4             0.63094    1.83319 129.64000   0.344  0.73127
##                                  
## (Intercept)                   ***
## RACEBlack or African American *  
## RACEWhite                     ***
## ARMCDTRT                      ***
## AVISITVIS2                    ***
## AVISITVIS3                    ***
## AVISITVIS4                    ***
## ARMCDTRT:AVISITVIS2              
## ARMCDTRT:AVISITVIS3              
## ARMCDTRT:AVISITVIS4              
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Covariance estimate:
##         VIS1    VIS2    VIS3    VIS4
## VIS1 40.7335 14.2740  5.1411 13.5288
## VIS2 14.2740 26.2243  2.6391  7.3219
## VIS3  5.1411  2.6391 14.9497  1.0341
## VIS4 13.5288  7.3219  1.0341 95.6006

For one-dimensional contrasts as in the coefficients table above, the degrees of freedom are the same for Kenward-Roger and Satterthwaite. However, Kenward-Roger uses adjusted standard errors, hence the p-values are different.

Note that if you would like to match SAS results for an unstructured covariance model, you can use the linear Kenward-Roger approximation:

fit_kr_lin <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data,
  method = "Kenward-Roger",
  vcov = "Kenward-Roger-Linear"
)

This is due to the different parametrization of the unstructured covariance matrix, see the Kenward-Roger vignette for details.

Variance-covariance for Coefficients

There are multiple variance-covariance estimator available for the coefficients, including:

  1. “Asymptotic”
  2. “Empirical” (Cluster Robust Sandwich)
  3. “Empirical-Jackknife”
  4. “Empirical-Bias-Reduced”
  5. “Kenward-Roger”
  6. “Kenward-Roger-Linear”

Please note that, not all combinations of variance-covariance for coefficients and method of degrees of freedom are possible, e.g. “Kenward-Roger” and “Kenward-Roger-Linear” are available only when the degrees of freedom method is “Kenward-Roger”.

Details can be found in Coefficients Covariance Matrix Adjustment vignette and Weighted Least Square Empirical Covariance.

An example of using other variance-covariance is:

fit_emp <- mmrm(
  formula = FEV1 ~ RACE + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = fev_data,
  method = "Satterthwaite",
  vcov = "Empirical"
)

Keeping Unobserved Visits

Sometimes not all possible time points are observed in a given data set. When using a structured covariance matrix, e.g. with auto-regressive structure, then it can be relevant to keep the correct distance between the observed time points.

Consider the following example where we have deliberately removed the VIS3 observations from our initial example data set fev_data to obtain sparse_data. We first fit the model where we do not drop the visit level explicitly, using the drop_visit_levels = FALSE choice. Second we fit the model by default without this option.

sparse_data <- fev_data[fev_data$AVISIT != "VIS3", ]
sparse_result <- mmrm(
  FEV1 ~ RACE + ar1(AVISIT | USUBJID),
  data = sparse_data,
  drop_visit_levels = FALSE
)

dropped_result <- mmrm(
  FEV1 ~ RACE + ar1(AVISIT | USUBJID),
  data = sparse_data
)
## In AVISIT there are dropped visits: VIS3.
##  Additional attributes including contrasts are lost.
## To avoid this behavior, make sure use `drop_visit_levels = FALSE`.

We see that we get a message about the dropped visit level by default. Now we can compare the estimated correlation matrices:

cov2cor(VarCorr(sparse_result))
##            VIS1      VIS2      VIS3       VIS4
## VIS1 1.00000000 0.4051834 0.1641736 0.06652042
## VIS2 0.40518341 1.0000000 0.4051834 0.16417360
## VIS3 0.16417360 0.4051834 1.0000000 0.40518341
## VIS4 0.06652042 0.1641736 0.4051834 1.00000000
cov2cor(VarCorr(dropped_result))
##            VIS1      VIS2       VIS4
## VIS1 1.00000000 0.1468464 0.02156386
## VIS2 0.14684640 1.0000000 0.14684640
## VIS4 0.02156386 0.1468464 1.00000000

We see that when using the default, second result, we just drop the VIS3 from the covariance matrix. As a consequence, we model the correlation between VIS2 and VIS4 the same as the correlation between VIS1 and VIS2. Hence we get a smaller correlation estimate here compared to the first result, which includes VIS3 explicitly.

Fine control over design matrix

In some scenarios, we may want to control how the design matrix is constructed in finer detail. This can be especially of interest if there are factor levels not present in the current dataset, which nonetheless are valid levels that should not result in later errors (for example, when using predict functions). In the example below, mmrm might not run if only one factor level is available.

ex_data <- fev_data[!(fev_data$RACE == "White" & fev_data$SEX == "Male"),]
fit_subgroup1 <- mmrm(
  formula = FEV1 ~ SEX + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = ex_data[ex_data$RACE != "White",],
  vcov = "Asymptotic"
)
fit_subgroup2 <- mmrm(
  formula = FEV1 ~ SEX + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = ex_data[ex_data$RACE == "White",],
  vcov = "Asymptotic"
)
## Some factor levels are dropped due to singular design matrix: SEX
## Error in `contrasts<-`:
## ! contrasts can be applied only to factors with 2 or more levels
emmeans::emmeans(fit_subgroup1, ~ARMCD|AVISIT, data=fev_data, weights="proportional")
## mmrm() registered as emmeans extension
## AVISIT = VIS1:
##  ARMCD emmean    SE    df lower.CL upper.CL
##  PBO     31.8 0.812 106.9     30.2     33.4
##  TRT     35.2 0.916 106.1     33.4     37.0
## 
## AVISIT = VIS2:
##  ARMCD emmean    SE    df lower.CL upper.CL
##  PBO     36.9 0.671 106.8     35.5     38.2
##  TRT     40.4 0.720 104.2     38.9     41.8
## 
## AVISIT = VIS3:
##  ARMCD emmean    SE    df lower.CL upper.CL
##  PBO     41.9 0.515  94.6     40.9     43.0
##  TRT     44.9 0.631  95.2     43.6     46.1
## 
## AVISIT = VIS4:
##  ARMCD emmean    SE    df lower.CL upper.CL
##  PBO     46.3 1.340  96.0     43.6     48.9
##  TRT     50.9 1.470  95.4     47.9     53.8
## 
## Results are averaged over the levels of: SEX 
## Confidence level used: 0.95
emmeans::emmeans(fit_subgroup2, ~ARMCD|AVISIT, data=fev_data, weights="proportional")
## Error:
## ! object 'fit_subgroup2' not found

By setting the contrasts argument specifically, just like in lm(), we can avoid this error:

contr_mat <- contr.sum(nlevels(ex_data$SEX))
rownames(contr_mat) <- levels(fev_data$SEX)
contr_mat
##        [,1]
## Male      1
## Female   -1

Now the subgroup analysis code will run correctly, even when emmeans averages over the baseline values that aren’t subgroup-specific:

fit_subgroup1 <- mmrm(
  formula = FEV1 ~ SEX + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = ex_data[ex_data$RACE == "White",],
  vcov = "Asymptotic",
  contrasts = list(SEX = contr_mat)
)
fit_subgroup2 <- mmrm(
  formula = FEV1 ~ SEX + ARMCD * AVISIT + us(AVISIT | USUBJID),
  data = ex_data[ex_data$RACE != "White",],
  vcov = "Asymptotic",
  contrasts = list(SEX = contr_mat)
)

emmeans::emmeans(fit_subgroup1, ~ARMCD|AVISIT, data=fev_data, weights="proportional")
## AVISIT = VIS1:
##  ARMCD emmean SE df asymp.LCL asymp.UCL
##  PBO   nonEst NA NA        NA        NA
##  TRT   nonEst NA NA        NA        NA
## 
## AVISIT = VIS2:
##  ARMCD emmean SE df asymp.LCL asymp.UCL
##  PBO   nonEst NA NA        NA        NA
##  TRT   nonEst NA NA        NA        NA
## 
## AVISIT = VIS3:
##  ARMCD emmean SE df asymp.LCL asymp.UCL
##  PBO   nonEst NA NA        NA        NA
##  TRT   nonEst NA NA        NA        NA
## 
## AVISIT = VIS4:
##  ARMCD emmean SE df asymp.LCL asymp.UCL
##  PBO   nonEst NA NA        NA        NA
##  TRT   nonEst NA NA        NA        NA
## 
## Results are averaged over the levels of: SEX 
## Confidence level used: 0.95
emmeans::emmeans(fit_subgroup2, ~ARMCD|AVISIT, data=fev_data, weights="proportional")
## AVISIT = VIS1:
##  ARMCD emmean    SE    df lower.CL upper.CL
##  PBO     31.8 0.812 106.9     30.2     33.4
##  TRT     35.2 0.916 106.1     33.4     37.0
## 
## AVISIT = VIS2:
##  ARMCD emmean    SE    df lower.CL upper.CL
##  PBO     36.9 0.671 106.8     35.5     38.2
##  TRT     40.4 0.720 104.2     38.9     41.8
## 
## AVISIT = VIS3:
##  ARMCD emmean    SE    df lower.CL upper.CL
##  PBO     41.9 0.515  94.6     40.9     43.0
##  TRT     44.9 0.631  95.2     43.6     46.1
## 
## AVISIT = VIS4:
##  ARMCD emmean    SE    df lower.CL upper.CL
##  PBO     46.3 1.340  96.0     43.6     48.9
##  TRT     50.9 1.470  95.4     47.9     53.8
## 
## Results are averaged over the levels of: SEX 
## Confidence level used: 0.95