{brms} supports multinomial models with the categorical()
family, and it works well as long as the response variable has 3+ categories in it.
The {mlogit} R package allows you to fit multinomial logitistic models in a frequentist way, and if you feed it data that is structured in a specific way, it can fit a multinomial model using a binary 0/1 outcome
Here’s an example (from this earlier related question)
library(dplyr)
library(brms)
library(mlogit)
# Read in data
cbc_df <- read.csv("http://goo.gl/5xQObB",
colClasses = c(seat = "factor", price = "factor", choice="integer")) %>%
mutate(eng = factor(eng, levels = c("gas", "hyb", "elec")))
Here, resp.id
refers to 200 different respondents, ques
referrs to 15 different random sets of questions offered to each respondent, alt
refers to the different options in each set of questions. The choice
column indicates which alt
was selected. The other columns are different characteristics of the options being presented.
head(cbc_df)
#> resp.id ques alt carpool seat cargo eng price choice
#> 1 1 1 1 yes 6 2ft gas 35 0
#> 2 1 1 2 yes 8 3ft hyb 30 0
#> 3 1 1 3 yes 6 3ft gas 30 1
#> 4 1 2 1 yes 6 2ft gas 30 0
#> 5 1 2 2 yes 7 3ft gas 35 1
#> 6 1 2 3 yes 6 2ft elec 35 0
With mlogit()
, you use the binary choice
column as the response variable, but because of some adjustments to the dataset with dfidx()
, mlogit()
instead models the choice of alt
= 1, alt
= 2, or alt
= 3 within each question. You can tell it’s doing multinomial work because it finds that alts 1, 2, and 3 were selected 33% of the time (to be expected, since the options were all shuffled every time)
# Modify the data for mlogit()
cbc_df_idx <- cbc_df %>%
# mlogit() needs a column with unique question id numbers
group_by(resp.id, ques) %>%
mutate(choice.id = cur_group_id()) %>%
ungroup() %>%
# Make indexed data frame for mlogit
dfidx(
idx = list(c("choice.id", "resp.id"), "alt"),
choice = "choice",
shape = "long"
)
m1 <- mlogit(choice ~ 0 + seat + cargo + eng + price, data = cbc_df_idx)
summary(m1)
#> Call:
#> mlogit(formula = choice ~ 0 + seat + cargo + eng + price, data = #> cbc_df_idx,
#> method = "nr")
#>
#> Frequencies of alternatives:choice
#> 1 2 3
#> 0.32700 0.33467 0.33833
#>
#> ...
#>
#> Coefficients :
#> Estimate Std. Error z-value Pr(>|z|)
#> seat7 -0.535280 0.062360 -8.5837 < 2.2e-16 ***
#> seat8 -0.305840 0.061129 -5.0032 5.638e-07 ***
#> cargo3ft 0.477449 0.050888 9.3824 < 2.2e-16 ***
#> enghyb -0.811282 0.060130 -13.4921 < 2.2e-16 ***
#> engelec -1.530762 0.067456 -22.6926 < 2.2e-16 ***
#> price35 -0.913656 0.060601 -15.0765 < 2.2e-16 ***
#> price40 -1.725851 0.069631 -24.7856 < 2.2e-16 ***
#> ...
Translating this to {brms} seems like it should be relatively straightforward (again referencing this earlier question):
m2 <- brm(
bf(choice ~ 0 + seat + cargo + eng + price + (1 | resp.id)),
data = cbc_df,
family = categorical(),
prior = c(
prior(normal(0, 3), class = b, dpar = mu1),
prior(exponential(1), class = sd, dpar = mu1)
),
chains = 4, cores = 4, iter = 2000, seed = 1234,
backend = "cmdstanr", threads = threading(2)
)
The model fits just fine and the coefficients are all roughly comparable to the output from mlogit()
, but I get this message:
Only 2 levels detected so that family 'bernoulli' might be a more efficient choice.
Which is true. The choice
column here only contains 0/1 valies and it’s not technicaly multinomial.
Is there some way to get {brms} to somehow treat the response variable as multinomial with alternatives 1, 2, 3, similar to {mlogit}?
I’ve tried a few possible methods for this:
- Use the
alt
column as the response, likebf(alt ~ 0 + seat + ...)
. But this isn’t right because it doesn’t include anything about which alternative was actually chosen - Create a new column called
choice_alt
that isalt * choice
- if someone selected alternative 3, this value would be 3; if someone selected alternative 2, this value would be 2; and so on. The value is 0 if no choice was made. This works-ish, but it creates four categories (0, 1, 2, and 3) instead of 3 - Somehow pass a three-column matrix of dummy-encoded columns to brms? Like
bf(choice_matrix ~ 0 + seat + ...)
. That doesn’t work, though, because brms isn’t designed for that kind of input data - Somehow build the formula using nonlinear syntax (via this GitHub issue). This is probably the correct approach though how to implement it eludes me.
- Somehow use multivariate models and subsets? I tried this, but got an error about “Cannot use the same response variable twice in the same model”
option1 <- bf(choice | subset(alt == 1) ~ 0 + seat + cargo + eng + price + (1 | resp.id))
option2 <- bf(choice | subset(alt == 2) ~ 0 + seat + cargo + eng + price + (1 | resp.id))
option3 <- bf(choice | subset(alt == 3) ~ 0 + seat + cargo + eng + price + (1 | resp.id))
brm(
option1 + option2 + option3,
data = cbc_df,
family = categorical(),
...
)
Thanks!