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.
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.
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.
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
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
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 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
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
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
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 |
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.