Sophisticated models in emmeans

Russ Lenth

2018-07-17

This vignette gives a few examples of the use of the emmeans package to analyze other than the basic types of models provided by the stats package. Emphasis here is placed on accessing the optional capabilities that are typically not needed for the more basic models. A reference for all supported models is provided in the “models” vignette.

Contents

  1. Linear mixed models (lmer)
    1. System options for lmerMod models
  2. Models with offsets
  3. Ordinal models
  4. Models fitted using MCMC methods
  5. Reference for supported models

Index of all vignette topics

Linear mixed models (lmer)

Linear mixed models are really important in statistics. Emphasis here is placed on those fitted using lme4::lmer(), but emmeans also supports other mixed-model packages such as nlme.

To illustrate, consider the Oats dataset in the nlme package. It has the results of a balanced split-plot experiment: experimental blocks are divided into plots that are randomly assigned to oat varieties, and the plots are subdivided into subplots that are randomly assigned to amounts of nitrogen within each plot. We will consider a linear mixed model for these data, excluding interaction (which is justified in this case). For sake of illustration, we will exclude a few observations.

Oats.lmer <- lme4::lmer(yield ~ Variety + factor(nitro) + (1|Block/Variety),
                        data = nlme::Oats, subset = -c(1,2,3,5,8,13,21,34,55))

Let’s look at the EMMs for nitro:

Oats.emm.n <- emmeans(Oats.lmer, "nitro")
Oats.emm.n
##  nitro    emmean       SE   df  lower.CL  upper.CL
##    0.0  78.89207 7.294379 7.78  61.98930  95.79484
##    0.2  97.03425 7.136271 7.19  80.25029 113.81822
##    0.4 114.19816 7.136186 7.19  97.41454 130.98179
##    0.6 124.06857 7.070235 6.95 107.32795 140.80919
## 
## Results are averaged over the levels of: Variety 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95

You will notice that the degrees of freedom are fractional: that is due to the fact that whole-plot and subplot variations are combined when standard errors are estimated. Different degrees-of-freedom methods are available. By default, the Kenward-Roger method is used, and that’s why you see a message about the pbkrtest package being loaded, as it implements that method. We may specify a different degrees-of-freedom method via the optional argument lmer.df:

emmeans(Oats.lmer, "nitro", lmer.df = "satterthwaite")
##  nitro    emmean       SE   df  lower.CL  upper.CL
##    0.0  78.89207 7.280608 7.28  61.81122  95.97292
##    0.2  97.03425 7.128706 6.72  80.03161 114.03690
##    0.4 114.19816 7.128885 6.72  97.19535 131.20098
##    0.6 124.06857 7.067304 6.49 107.08857 141.04857
## 
## Results are averaged over the levels of: Variety 
## Degrees-of-freedom method: satterthwaite 
## Confidence level used: 0.95

This latest result uses the Satterthwaite method, which is implemented in the lmerTest package. Note that, with this method, not only are the degrees of freedom slightly different, but so are the standard errors. That is because the Kenward-Roger method also entails making a bias adjustment to the covariance matrix of the fixed effects; that is the principal difference between the methods. A third possibility is "asymptotic":

emmeans(Oats.lmer, "nitro", lmer.df = "asymptotic")
##  nitro    emmean       SE  df asymp.LCL asymp.UCL
##    0.0  78.89207 7.280608 Inf  64.62234   93.1618
##    0.2  97.03425 7.128706 Inf  83.06225  111.0063
##    0.4 114.19816 7.128885 Inf 100.22580  128.1705
##    0.6 124.06857 7.067304 Inf 110.21691  137.9202
## 
## Results are averaged over the levels of: Variety 
## Degrees-of-freedom method: asymptotic 
## Confidence level used: 0.95

This just sets all the degrees of freedom to Inf – that’s emmeans’s way of using z statistics rather than t statistics. The asymptotic methods tend to make confidence intervals a bit too narrow and P values a bit too low; but they involve much, much less computation. Note that the SEs are the same as obtained using the Satterthwaite method.

Comparisons and contrasts are pretty much the same as with other models. As nitro has quantitative levels, we might want to test polynomial contrasts:

contrast(Oats.emm.n, "poly")
##  contrast    estimate        SE    df t.ratio p.value
##  linear    152.693407 15.577738 43.23   9.802  <.0001
##  quadratic  -8.271775  6.948552 44.19  -1.190  0.2402
##  cubic      -6.315230 15.205089 42.75  -0.415  0.6800
## 
## Results are averaged over the levels of: Variety

The interesting thing here is that the degrees of freedom are much larger than they are for the EMMs. The reason is because nitro within-plot factor, so inter-plot variations have little role in estimating contrasts among nitro levels. On the other hand, Variety is a whole-plot factor, and there is not much of a bump in degrees of freedom for comparisons:

emmeans(Oats.lmer, pairwise ~ Variety)
## $emmeans
##  Variety        emmean       SE   df lower.CL upper.CL
##  Golden Rain 105.24081 7.531718 8.46 88.03704 122.4446
##  Marvellous  108.46951 7.482632 8.28 91.31571 125.6233
##  Victory      96.93446 7.641645 8.81 79.59011 114.2788
## 
## Results are averaged over the levels of: nitro 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast                  estimate       SE   df t.ratio p.value
##  Golden Rain - Marvellous -3.228698 6.553848 9.56  -0.493  0.8764
##  Golden Rain - Victory     8.306351 6.707935 9.80   1.238  0.4595
##  Marvellous - Victory     11.535049 6.670488 9.80   1.729  0.2431
## 
## Results are averaged over the levels of: nitro 
## P value adjustment: tukey method for comparing a family of 3 estimates

System options for lmerMod models

The computation required to compute the adjusted covariance matrix and degrees of freedom may become cumbersome. Some user options (i.e., emm_options() calls) make it possible to streamline these computations through default methods and limitations on them. First, the option lmer.df, which may have values of "kenward-roger", "satterthwaite", or "asymptotic" (partial matches are OK!) specifies the default degrees-of-freedom method.

The options disable.pbkrtest and disable.lmerTest may be TRUE or FALSE, and comprise another way of controlling which method is used (e.g., the Kenward-Roger method will not be used if get_emm_option("disable.pbkrtest") == TRUE). Finally, the options pbkrtest.limit and lmerTest.limit, which should be set to numeric values, enable the given package conditionally on whether the number of data rows does not exceed the given limit. The factory default is 3000 for both limits.

Back to Contents

Models with offsets

If a model is fitted and its formula includes an offset() term, then by default, the offset is computed and included in the reference grid. To illustrate, consider a hypothetical dataset on insurance claims (used as an example in SAS’s documentation). There are classes of cars of varying counts (n), sizes (size), and age (age), and we record the number of insurance claims (claims). We fit a Poisson model to claims as a function of size and age. An offset of log(n) is included so that n functions as an “exposure” variable.

ins <- data.frame(
    n = c(500, 1200, 100, 400, 500, 300),
    size = factor(rep(1:3,2), labels = c("S","M","L")),
    age = factor(rep(1:2, each = 3)),
    claims = c(42, 37, 1, 101, 73, 14))
ins.glm <- glm(claims ~ size + age + offset(log(n)), 
               data = ins, family = "poisson")

First, let’s look at the reference grid obtained by default:

ref_grid(ins.glm)
## 'emmGrid' object with variables:
##     size = S, M, L
##     age = 1, 2
##     n = 500
## Transformation: "log"

Note that n is included in the reference grid and that its average value of 500 is used for all predictions. Thus, if we obtain EMMs for, say, age, these are results are based on a pool of 500 cars:

emmeans(ins.glm, "size", type = "response")
##  size     rate       SE  df asymp.LCL asymp.UCL
##  S    69.26111 6.250440 Inf 58.032750  82.66198
##  M    34.64335 3.342971 Inf 28.673568  41.85604
##  L    11.86512 3.136904 Inf  7.066934  19.92108
## 
## Results are averaged over the levels of: age 
## Confidence level used: 0.95 
## Intervals are back-transformed from the log scale

However, many users would like to ignore the offset for this kind of model, because then the estimates we obtain are rates per unit value of the (logged) offset. This may be accomplished by specifying an offset parameter in the call:

emmeans(ins.glm, "size", type = "response", offset = 0)
##  size       rate          SE  df  asymp.LCL  asymp.UCL
##  S    0.13852223 0.012500880 Inf 0.11606550 0.16532396
##  M    0.06928671 0.006685942 Inf 0.05734714 0.08371208
##  L    0.02373023 0.006273808 Inf 0.01413387 0.03984217
## 
## Results are averaged over the levels of: age 
## Confidence level used: 0.95 
## Intervals are back-transformed from the log scale

You may verify that the above estimates are 1/500th of the previous ones. You may also verify that the above results are identical to those obtained by setting n equal to 1:

emmeans(ins.glm, "size", type = "response", at = list(n = 1))

However, those who use these types of models will be more comfortable directly setting the offset to zero.

By the way, you may set some other reference value for the rates. For example, if you want estimates of claims per 100 cars, simply use (results not shown):

emmeans(ins.glm, "size", type = "response", offset = log(100))

Back to Contents

Ordinal models

Ordinal-response models comprise an example where several options are available for obtaining EMMs. To illustrate, consider the wine data in the ordinal package. The response is a rating of bitterness on a five-point scale. we will consider a probit model in two factors during fermentation: temp (temperature) and contact (contact with grape skins), with the judge making the rating as a scale predictor:

require("ordinal")
## Loading required package: ordinal
wine.clm <- clm(rating ~ temp + contact, scale = ~ judge,
                data = wine, link = "probit")

(in earlier modeling, we found little interaction between the factors.) Here are the EMMs for each factor using default options:

emmeans(wine.clm, list(pairwise ~ temp, pairwise ~ contact))
## $`emmeans of temp`
##  temp     emmean        SE  df  asymp.LCL  asymp.UCL
##  cold -0.8838870 0.2898157 Inf -1.4519155 -0.3158586
##  warm  0.6011128 0.2246028 Inf  0.1608993  1.0413263
## 
## Results are averaged over the levels of: contact, judge 
## Confidence level used: 0.95 
## 
## $`pairwise differences of temp`
##  contrast     estimate        SE  df z.ratio p.value
##  cold - warm -1.073545 0.4215306 Inf  -2.547  0.0109
## 
## Results are averaged over the levels of: contact, judge 
## 
## $`emmeans of contact`
##  contact     emmean        SE  df   asymp.LCL   asymp.UCL
##  no      -0.6143569 0.2983133 Inf -1.19904027 -0.02967357
##  yes      0.3315827 0.2014363 Inf -0.06322522  0.72639062
## 
## Results are averaged over the levels of: temp, judge 
## Confidence level used: 0.95 
## 
## $`pairwise differences of contact`
##  contrast   estimate        SE  df z.ratio p.value
##  no - yes -0.6838446 0.3038508 Inf  -2.251  0.0244
## 
## Results are averaged over the levels of: temp, judge

These results are on the “latent” scale; the idea is that there is a continuous random variable (in this case normal, due to the probit link) having a mean that depends on the predictors; and that the ratings are a discretization of the latent variable based on a fixed set of cut points (which are estimated). In this particular example, we also have a scale model that says that the variance of the latent variable depends on the judges. The latent results are quite a bit like those for measurement data, making them easy to interpret. The only catch is that they are not uniquely defined: we could apply a linear transformation to them, and the same linear transformation to the cut points, and the results would be the same.

The clm function actually fits the model using an ordinary probit model but with different intercepts for each cut point. We can get detailed information for this model by specifying mode = "linear.predictor":

tmp <- ref_grid(wine.clm, mode = "lin")
tmp
## 'emmGrid' object with variables:
##     temp = cold, warm
##     contact = no, yes
##     judge = 1, 2, 3, 4, 5, 6, 7, 8, 9
##     cut = multivariate response levels: 1|2, 2|3, 3|4, 4|5
## Transformation: "probit"

Note that this reference grid involves an additional constructed predictor named cut that accounts for the different intercepts in the model. Let’s obtain EMMs for temp on the linear-predictor scale:

emmeans(tmp, "temp")
##  temp     emmean        SE  df  asymp.LCL  asymp.UCL
##  cold  0.8838870 0.2898157 Inf  0.3158586  1.4519155
##  warm -0.6011128 0.2246028 Inf -1.0413263 -0.1608993
## 
## Results are averaged over the levels of: contact, judge, cut 
## Results are given on the probit (not the response) scale. 
## Confidence level used: 0.95

These are just the negatives of the latent results obtained earlier (the sign is changed to make the comparisons go the right direction). Closely related to this is mode = "cum.prob" and mode = "exc.prob", which simply transform the linear predictor to cumulative probabilities and exceedance (1 - cumulative) probabilities. These modes give us access to the details of the fitted model but are cumbersome to use for describing results. When they can become useful is when you want to work in terms of a particular cut point. Let’s look at temp again in terms of the probability that the rating will be at least 4:

emmeans(wine.clm, ~ temp, mode = "exc.prob", at = list(cut = "3|4"))
##  temp   exc.prob         SE  df  asymp.LCL asymp.UCL
##  cold 0.07477754 0.03183196 Inf 0.01238804 0.1371670
##  warm 0.40688371 0.07056873 Inf 0.26857154 0.5451959
## 
## Results are averaged over the levels of: contact, judge 
## Confidence level used: 0.95

There are yet more modes! With mode = "prob", we obtain estimates of the probability distribution of each rating. Its reference grid includes a factor with the same name as the model response – in this case rating. We usually want to use that as the primary factor, and the factors of interest as by variables:

emmeans(wine.clm, ~ rating | temp, mode = "prob")
## temp = cold:
##  rating       prob         SE  df    asymp.LCL  asymp.UCL
##  1      0.12922075 0.06252864 Inf  0.006666872 0.25177462
##  2      0.48768408 0.07051118 Inf  0.349484709 0.62588345
##  3      0.30831763 0.05942035 Inf  0.191855896 0.42477937
##  4      0.05766120 0.02378461 Inf  0.011044211 0.10427818
##  5      0.01711634 0.01265193 Inf -0.007680992 0.04191368
## 
## temp = warm:
##  rating       prob         SE  df    asymp.LCL  asymp.UCL
##  1      0.01561393 0.01287141 Inf -0.009613574 0.04084144
##  2      0.14730458 0.04475283 Inf  0.059590654 0.23501851
##  3      0.43019777 0.06274049 Inf  0.307228674 0.55316687
##  4      0.26845558 0.06251632 Inf  0.145925842 0.39098532
##  5      0.13842813 0.05061119 Inf  0.039232030 0.23762424
## 
## Results are averaged over the levels of: contact, judge 
## Confidence level used: 0.95

Using mode = "mean.class" obtains the average of these probability distributions as probabilities of the integers 1–5:

emmeans(wine.clm, "temp", mode = "mean.class")
##  temp mean.class        SE  df asymp.LCL asymp.UCL
##  cold   2.345768 0.1438341 Inf  2.063859  2.627678
##  warm   3.366779 0.1462579 Inf  3.080119  3.653440
## 
## Results are averaged over the levels of: contact, judge 
## Confidence level used: 0.95

And there is a mode for the scale model too. In this example, the scale model involves only judges, and that is the only factor in the grid:

summary(ref_grid(wine.clm, mode = "scale"), type = "response")
##  judge  response        SE  df
##  1     1.0000000 0.0000000 Inf
##  2     1.0429804 0.5696650 Inf
##  3     1.0526798 0.4811605 Inf
##  4     0.7103256 0.3356562 Inf
##  5     0.6632397 0.3009780 Inf
##  6     0.7582403 0.3411929 Inf
##  7     1.0707070 0.5861427 Inf
##  8     0.2409019 0.1791135 Inf
##  9     0.5331235 0.3110024 Inf

Judge 8’s ratings don’t vary much, relative to the others. The scale model is in terms of log(SD). Again, these are not uniquely identifiable, and the first level’s estimate is set to log(1) = 0. so, actually, each estimate shown is a comparison with judge 1.

Back to Contents

Models fitted using MCMC methods

To illustrate emmeans’s support for models fitted using MCMC methods, consider the example_model available in the rstanarm package. The example concerns CBPP, a serious disease of cattle in Ethiopia. A generalized linear mixed model was fitted to the data using the code below. We subsequently obtain its reference grid in the usual way.

example_model <- rstanarm::stan_glmer(
    cbind(incidence, size - incidence) ~ size + period + (1|herd),
    data = lme4::cbpp, family = binomial,
    chains = 2, cores = 1, seed = 12345, iter = 500)
cbpp.rg <- ref_grid(example_model)

Here is the structure of the reference grid:

cbpp.rg
## 'emmGrid' object with variables:
##     size = 15.036
##     period = 1, 2, 3, 4
## Transformation: "logit"

And, again in the usual way, we can obtain EMMs:

period.emm <- emmeans(cbpp.rg, "period")
period.emm
##  period    emmean lower.HPD  upper.HPD
##  1      -1.430450 -1.959630 -0.8940016
##  2      -2.394991 -3.132075 -1.8225853
##  3      -2.515846 -3.190710 -1.8616529
##  4      -2.967635 -3.884121 -1.9977081
## 
## Results are given on the logit (not the response) scale. 
## HPD interval probability: 0.95

The summary for EMMs of Bayesian models shows the median of the posterior distribution of each estimate, along with highest posterior density (HPD) intervals. Under the hood, the posterior sample of parameter estimates is used to compute a corresponding sample of posterior EMMs, and it is those that are summarized. (Technical note: the summary is actually rerouted to the hpd.summary() function, and it loads the coda package if it is not already loaded.

We can access the posterior EMMs via the as.mcmc method for emmGrid objects. This gives us an object of class mcmc (defined in the coda package), which can be summarized and explored as we please.

require("coda")
## Loading required package: coda
## Warning: package 'coda' was built under R version 3.5.1
summary(as.mcmc(period.emm))
## 
## Iterations = 1:250
## Thinning interval = 1 
## Number of chains = 2 
## Sample size per chain = 250 
## 
## 1. Empirical mean and standard deviation for each variable,
##    plus standard error of the mean:
## 
##            Mean     SD Naive SE Time-series SE
## period 1 -1.435 0.2825  0.01264        0.01771
## period 2 -2.393 0.3374  0.01509        0.01620
## period 3 -2.516 0.3409  0.01524        0.01540
## period 4 -2.979 0.4634  0.02072        0.02793
## 
## 2. Quantiles for each variable:
## 
##            2.5%    25%    50%    75%  97.5%
## period 1 -1.969 -1.618 -1.430 -1.262 -0.903
## period 2 -3.122 -2.609 -2.395 -2.165 -1.746
## period 3 -3.199 -2.739 -2.516 -2.286 -1.870
## period 4 -3.890 -3.254 -2.968 -2.698 -2.003

Note that as.mcmc will actually produce an mcmc.list when there is more than one chain present, as in this example. The 2.5th and 97.5th quantiles are similar, but not identical, to the 95% confidence intervals in the frequentist summary. Here is a plot of the posterior EMMs, back-transformed:

bayesplot::mcmc_areas(as.mcmc(regrid(period.emm)))

… and here are intervals for each period compared with its neighbor:

contrast(period.emm, "consec", reverse = TRUE)
##  contrast  estimate  lower.HPD upper.HPD
##  1 - 2    0.9544005  0.3749803 1.5329574
##  2 - 3    0.1332683 -0.5813314 0.9415829
##  3 - 4    0.4394573 -0.4765553 1.6714750
## 
## Results are given on the log odds ratio (not the response) scale. 
## HPD interval probability: 0.95

The only interval that excludes zero is the one that compares periods 1 and 2.

In summary, to do Bayesian analysis in the emmeans package, use the same tools that are available for other models, extract the MCMC samples using as.mcmc(), and summarize or plot from there.

Back to Contents

Index of all vignette topics