This vignette provides an overview of the bayesMeanScale package, which is designed to compute model predictions, marginal effects, and comparisons of marginal effects for several different fixed-effect generalized linear models fit using the rstanarm package (https://mc-stan.org/rstanarm/). In particular, these statistics are computed on the mean scale rather than the link scale for easier interpretation. For example, rather than working on the log-odds scale for a logistic regression, we focus on the probability scale.

To get to the mean scale, bayesMeanScale takes a random sample with replacement from the joint posterior distribution. Then, this matrix is multiplied by the adjusted data matrix (adjusted according to your values of interest). Finally, the inverse link function is applied to transform the predictions to the mean scale.

Predictions are computed by holding one or more explanatory variables fixed at particular values and either averaging over the rows of the data (average marginal predictions) or holding all other covariates at their means (marginal predictions at the mean). Marginal effects can also be calculated by averaging over the data (average marginal effect) or holding covariates at their means (marginal effect at the mean).

The third workhorse function of the package compares marginal effects against each other. This is particularly useful for testing non-linear interaction effects such as those that appear in generalized linear models that do not use the identity link.

Predictions

Average marginal predictions

The examples below use the wells data from the rstanarm package. Use ?rstanarm::wells to view the documentation on this dataset.

For average marginal predictions, the goal is to get predictions at important settings of one or more of the model explanatory variables. These predictions are then averaged over the rows of the data.

lapply(c('bayesMeanScale', 'rstanarm', 'flextable', 'magrittr', 'MASS'), function(x) base::library(x, character.only=T))
# Simulate the data #

modelData       <- rstanarm::wells
modelData$assoc <- ifelse(modelData$assoc==1, 'Y', 'N')

binomialModel <- stan_glm(switch ~ dist*educ + arsenic + I(arsenic^2) + assoc, 
                          data    = modelData, 
                          family  = binomial, 
                          refresh = 0)
bayesPredsF(binomialModel, 
            at = list(arsenic = c(.82, 1.3, 2.2), assoc=c("Y", "N")))
##  arsenic assoc   mean  lower  upper
##     0.82     Y 0.4528 0.4222 0.4828
##     1.30     Y 0.5341 0.5064 0.5617
##     2.20     Y 0.6557 0.6250 0.6864
##     0.82     N 0.4843 0.4562 0.5132
##     1.30     N 0.5653 0.5415 0.5889
##     2.20     N 0.6835 0.6585 0.7092

The output contains the unique values for the “at” variables, the posterior means, and the lower and upper bounds of the credible intervals.

Marginal predictions at the mean

For marginal predictions at the mean, the goal is essentially the same except that we want to hold the covariates at their means. In the example below, all explanatory variables except for “arsenic” are held at their means for the computation. Since “assoc” is a discrete variable, we hold it at the proportion of cases that equaled “Y”.

bayesPredsF(binomialModel, 
            at       = list(arsenic = c(.82, 1.3, 2.2), assoc=c("Y", "N")), 
            at_means = T)
##  arsenic assoc   mean  lower  upper
##     0.82     Y 0.4483 0.4146 0.4767
##     1.30     Y 0.5335 0.5048 0.5638
##     2.20     Y 0.6610 0.6289 0.6935
##     0.82     N 0.4799 0.4507 0.5077
##     1.30     N 0.5649 0.5381 0.5877
##     2.20     N 0.6889 0.6600 0.7151

The results are slightly different than the average marginal predictions. From a computational standpoint, setting “at_means” to “TRUE” makes for a substantially faster computation. For relatively small models, this speed advantage is likely trivial, but it can make a noticeable difference when working with big data models.

Average marginal predictions for count probabilities

You can also get the predictions for the count probabilities from a Poisson or negative binomial model. Here, rather than looking at the rate, or mean, parameter, we investigate the probabilities of particular counts. This is an effective approach for summarizing count models in more depth.

crabs <- read.table("https://users.stat.ufl.edu/~aa/cat/data/Crabs.dat", header=T)

poissonModel  <- stan_glm(sat ~ weight + width, 
                          data    = crabs, 
                          family  = poisson, 
                          refresh = 0)

bayesCountPredsF(poissonModel,
                 counts = c(0,1,2),
                 at     = list(weight=c(2,3,4)))
##  weight count   mean  lower  upper
##       2     0 0.1107 0.0772 0.1491
##       3     0 0.0334 0.0133 0.0583
##       4     0 0.0089 0.0000 0.0326
##       2     1 0.2377 0.1895 0.2828
##       3     1 0.1077 0.0553 0.1535
##       4     1 0.0342 0.0004 0.1013
##       2     2 0.2602 0.2381 0.2707
##       3     2 0.1799 0.1358 0.2213
##       4     2 0.0722 0.0019 0.1689

Marginal effects

Average marginal effects

The concept of average marginal effects is straightforward. For discrete changes, we simply take two posterior distributions of average marginal predictions and subtract one from the other. In the example below, we see that the average expected probability of switching wells for families that had an arsenic level of 2.2 is roughly 27 percentage points greater than for a family that had an arsenic level of .82.

binomialAME <- bayesMargEffF(binomialModel,
                             marginal_effect = 'arsenic',
                             start_value     = 2.2,
                             end_value       = .82)

binomialAME
##  marg_effect start_val end_val   mean  lower  upper
##      arsenic       2.2    0.82 0.2683 0.2149 0.3136
head(binomialAME$diffDraws)
##         diff   comparison marg_effect
##        <num>       <char>      <char>
## 1: 0.2194958 2.2 vs. 0.82     arsenic
## 2: 0.2263655 2.2 vs. 0.82     arsenic
## 3: 0.3009543 2.2 vs. 0.82     arsenic
## 4: 0.2933862 2.2 vs. 0.82     arsenic
## 5: 0.2507658 2.2 vs. 0.82     arsenic
## 6: 0.2291384 2.2 vs. 0.82     arsenic

Also note that we can access the posterior distribution of the marginal effects. This can be useful for graphing purposes and to describe the marginal effect distributions in more detail.

As an alternative to computing some discrete change in arsenic, we can approximate the average instantaneous rate of change by supplying “instantaneous” as our value for the “start_value” and “end_value” arguments.

binomialAMEInstant <- bayesMargEffF(binomialModel,
                                    marginal_effect = 'arsenic',
                                    start_value     = 'instantaneous',
                                    end_value       = 'instantaneous')

binomialAMEInstant
##  marg_effect     start_val       end_val   mean  lower upper
##      arsenic instantaneous instantaneous 0.2006 0.1595 0.244

We can also compute multiple marginal effects. When doing so, it is necessary to specify the start and end values in a list.

bayesMargEffF(binomialModel,
              marginal_effect = c('arsenic', 'dist'),
              start_value     = list(2.2, 64.041),
              end_value       = list(.82, 21.117))
##  marg_effect start_val end_val    mean   lower   upper
##      arsenic     2.200   0.820  0.2684  0.2166  0.3152
##         dist    64.041  21.117 -0.0969 -0.1165 -0.0789

The “at” argument allows the user to specify particular values for one or more covariates, and they must be specified in a list. The example below specifies at values for “educ” given that we have an interaction between “dist” and “educ” in the model. If there is an interaction effect on the mean (probability) scale, we would expect the marginal effect of “dist” to be different at various levels of “educ.” The final section will cover how to test these marginal effects against each other.

binomialAMEInteraction <- bayesMargEffF(binomialModel,
                                        marginal_effect = 'dist',
                                        start_value     = 'instantaneous',
                                        end_value       = 'instantaneous',
                                        at              = list(educ=c(0, 5, 8)))

binomialAMEInteraction
##  educ marg_effect     start_val       end_val    mean   lower   upper
##     0        dist instantaneous instantaneous -0.0033 -0.0039 -0.0026
##     5        dist instantaneous instantaneous -0.0022 -0.0027 -0.0018
##     8        dist instantaneous instantaneous -0.0015 -0.0021 -0.0010

Average marginal effects for count probabilities

You can also get the marginal effects for the count probabilities from a Poisson or negative binomial model.

countMarg <- bayesCountMargEffF(poissonModel,
                                counts          = c(0,1,2),
                                marginal_effect = 'width',
                                start_value     = 25,
                                end_value       = 20,
                                at              = list(weight=c(2,3,4)))

countMarg
##  weight count marg_effect start_val end_val    mean   lower  upper
##       2     0       width        25      20 -0.0701 -0.2095 0.0567
##       3     0       width        25      20 -0.0520 -0.1704 0.0113
##       4     0       width        25      20 -0.0316 -0.1326 0.0012
##       2     1       width        25      20 -0.0466 -0.1198 0.0631
##       3     1       width        25      20 -0.0652 -0.1503 0.0324
##       4     1       width        25      20 -0.0563 -0.1683 0.0041
##       2     2       width        25      20  0.0178 -0.0081 0.0715
##       3     2       width        25      20 -0.0206 -0.0658 0.0576
##       4     2       width        25      20 -0.0483 -0.1158 0.0096

Marginal effects at the mean

Marginal effects at the mean compute the differences while holding the covariates at their means. Like the marginal predictions at the mean, specifying “at_means=TRUE” allows for a much faster computation than specifying “at_means=F”.

binomialMEMInteraction <- bayesMargEffF(binomialModel,
                                        marginal_effect = 'dist',
                                        start_value     = 64.041,
                                        end_value       = 21.117,
                                        at              = list(educ=c(0, 5, 8)),
                                        at_means        = T)

binomialMEMInteraction
##  educ marg_effect start_val end_val    mean   lower   upper
##     0        dist    64.041  21.117 -0.1527 -0.1854 -0.1167
##     5        dist    64.041  21.117 -0.1002 -0.1220 -0.0802
##     8        dist    64.041  21.117 -0.0692 -0.0934 -0.0434

Comparing marginal effects

After computing multiple marginal effects for a model, you might like to compare them against one another. The “bayesMargCompareF” function calculates tests for all the unique pairs of marginal effects that you computed with “bayesMargEffF”. In the example below, we are able to investigate the interaction effect between “dist” and “educ”. We see that the marginal effect of “dist” is meaningfully different at different levels of “educ”.

bayesMargCompareF(binomialAMEInteraction)
##  educ1 educ2                         marg_effect   mean lower  upper
##      5     0 dist (Instantaneous rate of change) 0.0011 5e-04 0.0016
##      8     0 dist (Instantaneous rate of change) 0.0017 8e-04 0.0025
##      8     5 dist (Instantaneous rate of change) 0.0007 3e-04 0.0010

You can also compare the marginal effects on count probabilities, shown in the example below.

bayesMargCompareF(countMarg)
##  weight1 weight2 count       marg_effect    mean   lower  upper
##        3       2     0 width (25 vs. 20)  0.0181 -0.0363 0.0464
##        4       2     0 width (25 vs. 20)  0.0385 -0.0468 0.0961
##        4       3     0 width (25 vs. 20)  0.0204 -0.0096 0.0466
##        3       2     1 width (25 vs. 20) -0.0186 -0.0530 0.0048
##        4       2     1 width (25 vs. 20) -0.0096 -0.0773 0.0337
##        4       3     1 width (25 vs. 20)  0.0089 -0.0331 0.0396
##        3       2     2 width (25 vs. 20) -0.0384 -0.0860 0.0337
##        4       2     2 width (25 vs. 20) -0.0662 -0.1507 0.0058
##        4       3     2 width (25 vs. 20) -0.0277 -0.0806 0.0143

Proportional odds models

The examples below use the housing data from the MASS package. Use ?MASS::housing to view the documentation on this dataset.

We can also compute predictions, marginal effects, and comparisons of marginal effects for proportional odds models. Posterior means and credible intervals are computed with respect to each outcome of the response variable.

propOddsModel <- stan_polr(Sat ~ Infl + Type, 
                           data    = housing, 
                           prior   = rstanarm::R2(0.2, 'mean'),
                           refresh = 0)

bayesOrdinalPredsF(propOddsModel, 
                   at = list(Type=c("Tower", "Apartment")))
##       Type outcome   mean  lower  upper
##      Tower     Low 0.3418 0.2524 0.4281
##  Apartment     Low 0.3473 0.1634 0.5331
##      Tower  Medium 0.3195 0.3025 0.3358
##  Apartment  Medium 0.3097 0.2675 0.3352
##      Tower    High 0.3387 0.2518 0.4287
##  Apartment    High 0.3431 0.1683 0.5353
propOddsMarg <- bayesOrdinalMargEffF(propOddsModel, 
                                     marginal_effect = "Infl", 
                                     start_value     = "Low", 
                                     end_value       = "High",  
                                     at              = list(Type=c("Tower", "Apartment")))

propOddsMarg
##  outcome      Type marg_effect start_val end_val    mean   lower  upper
##      Low     Tower        Infl       Low    High -0.0063 -0.1722 0.1351
##      Low Apartment        Infl       Low    High -0.0053 -0.1672 0.1309
##   Medium     Tower        Infl       Low    High  0.0082 -0.0005 0.0341
##   Medium Apartment        Infl       Low    High  0.0062 -0.0235 0.0514
##     High     Tower        Infl       Low    High -0.0019 -0.1526 0.1452
##     High Apartment        Infl       Low    High -0.0009 -0.1617 0.1415
bayesMargCompareF(propOddsMarg)
##      Type1 Type2 outcome         marg_effect    mean   lower  upper
##  Apartment Tower     Low Infl (Low vs. High)  0.0009 -0.0173 0.0242
##  Apartment Tower  Medium Infl (Low vs. High) -0.0019 -0.0362 0.0360
##  Apartment Tower    High Infl (Low vs. High)  0.0010 -0.0171 0.0240

List of models currently supported


Class

Family

Links

stanreg

beta

logit; probit; cloglog

stanreg

binomial

logit; probit; cloglog

stanreg

Gamma

inverse; log; identity

stanreg

gaussian

identity

stanreg

neg_binomial_2

identity; log; sqrt

stanreg

poisson

identity; log; sqrt

stanreg; polr

binomial

logit; probit; cloglog

References

Agresti, Alan. 2013. Categorical Data Analysis. Third Edition. New York: Wiley

Long, J. Scott and Jeremy Freese. 2001. “Predicted Probabilities for Count Models.” Stata Journal 1(1): 51-57.

Long, J. Scott and Sarah A. Mustillo. 2018. “Using Predictions and Marginal Effects to Compare Groups in Regression Models for Binary Outcomes.” Sociological Methods & Research 50(3): 1284-1320.

Mize, Trenton D. 2019. “Best Practices for Estimating, Interpreting, and Presenting Non-linear Interaction Effects.” Sociological Science 6: 81-117.