This vignette demonstrates an example of how to use the
logitr()
function to estimate mixed logit (MXL) models with
preference space and WTP space utility parameterizations.
In mixed logit models, parameters are assumed to follow a particular
distribution. This is implemented in logitr
with the
randPars
argument, which should be a named vector defining
which distribution to use for each random parameter. In the example
below, we set randPars = c(feat = 'n', brand = 'n')
so that
feat
and brand
are normally distributed. Mixed
logit models will estimate a mean and standard deviation of the
underlying normal distribution for each random coefficient.
The current version of the package includes the following distributions:
"n"
"ln"
"cn"
Note that log-normal or zero-censored normal parameters force positivity, so when using these you may need to use the negative of a value. For example, the “price” coefficient is typically negative, so if modeling “price” with a log-normal or zero-censored normal distribution you should convert “price” to the negative of price in the data prior to estimating the model.
This example uses the yogurt data set from Jain et al. (1994). The data set contains 2,412 choice observations from a series of yogurt purchases by a panel of 100 households in Springfield, Missouri, over a roughly two-year period. The data were collected by optical scanners and contain information about the price, brand, and a “feature” variable, which identifies whether a newspaper advertisement was shown to the customer. There are four brands of yogurt: Yoplait, Dannon, Weight Watchers, and Hiland, with market shares of 34%, 40%, 23% and 3%, respectively.
In the utility models described below, the data variables are represented as follows:
Symbol | Variable |
---|---|
\(p\) | The price in US dollars. |
\(x_{j}^{\mathrm{Feat}}\) | Dummy variable for whether the newspaper advertisement was shown to the customer. |
\(x_{j}^{\mathrm{Hiland}}\) | Dummy variable for the “Highland” brand. |
\(x_{j}^{\mathrm{Weight}}\) | Dummy variable for the “Weight Watchers” brand. |
\(x_{j}^{\mathrm{Yoplait}}\) | Dummy variable for the “Yoplait” brand. |
This example will estimate the following mixed logit model in the preference space:
\[\begin{equation} u_{j} = \alpha p_{j} + \beta_1 x_{j}^{\mathrm{Feat}} + \beta_2 x_{j}^{\mathrm{Hiland}} + \beta_3 x_{j}^{\mathrm{Weight}} + \beta_4 x_{j}^{\mathrm{Yoplait}} + \varepsilon_{j} \label{eq:mnlPrefExample} \end{equation}\]
where the parameters \(\alpha\), \(\beta_1\), \(\beta_2\), \(\beta_3\), and \(\beta_4\) have units of utility. In the example below, we will model \(\beta_1\), \(\beta_2\), \(\beta_3\), and \(\beta_4\) as normally distributed across the population. As a result, the model will estimate a mean and standard deviation for each of these coefficients.
Note that since the yogurt
data has a panel structure
(i.e. multiple choice observations for each respondent), it is necessary
to set the panelID
argument to the id
variable, which identifies the individual. This will use the panel
version of the log-likelihood (see Train
2009 chapter 6, section 6.7 for details).
Finally, as with WTP space models, it is recommended to use a
multistart search for mixed logit models as they are non-convex. This is
implemented in the example below by setting
numMultiStarts = 10
:
library("logitr")
<- logitr(
mxl_pref data = yogurt,
outcome = 'choice',
obsID = 'obsID',
panelID = 'id',
pars = c('price', 'feat', 'brand'),
randPars = c(feat = 'n', brand = 'n'),
numMultiStarts = 10
)
#> Running multistart...
#> Iterations: 10
#> Cores: 3
#> Done!
Print a summary of the results:
summary(mxl_pref)
#> =================================================
#>
#> Model estimated on: Thu Jun 16 05:48:23 2022
#>
#> Using logitr version: 0.6.1
#>
#> Call:
#> logitr(data = yogurt, outcome = "choice", obsID = "obsID", pars = c("price",
#> "feat", "brand"), randPars = c(feat = "n", brand = "n"),
#> panelID = "id", numMultiStarts = 10)
#>
#> Frequencies of alternatives:
#> 1 2 3 4
#> 0.402156 0.029436 0.229270 0.339138
#>
#> Summary Of Multistart Runs:
#> Log Likelihood Iterations Exit Status
#> 1 -1266.550 34 3
#> 2 -1280.044 65 3
#> 3 -1248.998 39 3
#> 4 -1267.858 43 3
#> 5 -1239.295 52 3
#> 6 -1300.132 36 3
#> 7 -1250.922 46 3
#> 8 -1289.385 41 3
#> 9 -1252.705 43 3
#> 10 -1303.013 45 3
#>
#> Use statusCodes() to view the meaning of each status code
#>
#> Exit Status: 3, Optimization stopped because ftol_rel or ftol_abs was reached.
#>
#> Model Type: Mixed Logit
#> Model Space: Preference
#> Model Run: 5 of 10
#> Iterations: 52
#> Elapsed Time: 0h:0m:3s
#> Algorithm: NLOPT_LD_LBFGS
#> Weights Used?: FALSE
#> Panel ID: id
#> Robust? FALSE
#>
#> Model Coefficients:
#> Estimate Std. Error z-value Pr(>|z|)
#> price -0.448906 0.040004 -11.2216 < 2.2e-16 ***
#> feat 0.776342 0.193533 4.0114 6.036e-05 ***
#> brandhiland -6.393908 0.523611 -12.2112 < 2.2e-16 ***
#> brandweight -3.660216 0.306889 -11.9268 < 2.2e-16 ***
#> brandyoplait 1.127390 0.204158 5.5221 3.349e-08 ***
#> sd_feat 0.566220 0.225256 2.5137 0.01195 *
#> sd_brandhiland -3.201333 0.372903 -8.5849 < 2.2e-16 ***
#> sd_brandweight 4.093423 0.232198 17.6290 < 2.2e-16 ***
#> sd_brandyoplait 3.258847 0.219937 14.8172 < 2.2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Log-Likelihood: -1239.2945922
#> Null Log-Likelihood: -3343.7419990
#> AIC: 2496.5891845
#> BIC: 2548.6831000
#> McFadden R2: 0.6293690
#> Adj McFadden R2: 0.6266774
#> Number of Observations: 2412.0000000
#>
#> Summary of 10k Draws for Random Coefficients:
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> feat -Inf 0.3940471 0.7759089 0.7755488 1.1576404 Inf
#> brandhiland -Inf -8.5515625 -6.3928805 -6.3909395 -4.2335128 Inf
#> brandweight -Inf -6.4232959 -3.6635735 -3.6665318 -0.9030747 Inf
#> brandyoplait -Inf -1.0752246 1.1218102 1.1190157 3.3194987 Inf
The above summary table prints summaries of the estimated
coefficients as well as a summary table of the distribution of 10,000
population draws for each normally-distributed model coefficient. The
results show that the feat
attribute has a significant
standard deviation coefficient, suggesting that there is considerable
heterogeneity across the population for this attribute. In contrast, the
brand
coefficients have small and insignificant standard
deviation coefficients.
Compute the WTP implied from the preference space model:
<- wtp(mxl_pref, scalePar = "price")
wtp_mxl_pref
wtp_mxl_pref#> Estimate Std. Error z-value Pr(>|z|)
#> scalePar 0.448906 0.039865 11.2607 < 2.2e-16 ***
#> feat 1.729408 0.502648 3.4406 0.0005804 ***
#> brandhiland -14.243309 1.388362 -10.2591 < 2.2e-16 ***
#> brandweight -8.153634 0.975137 -8.3615 < 2.2e-16 ***
#> brandyoplait 2.511415 0.412994 6.0810 1.194e-09 ***
#> sd_feat 1.261333 0.503097 2.5071 0.0121714 *
#> sd_brandhiland -7.131410 0.957027 -7.4516 9.215e-14 ***
#> sd_brandweight 9.118663 0.945261 9.6467 < 2.2e-16 ***
#> sd_brandyoplait 7.259531 0.767964 9.4530 < 2.2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
This example will estimate the following mixed logit model in the WTP space:
\[\begin{equation} u_{j} = \lambda ( \omega_1 x_{j}^{\mathrm{Feat}} + \omega_2 x_{j}^{\mathrm{Hiland}} + \omega_3 x_{j}^{\mathrm{Weight}} + \omega_4 x_{j}^{\mathrm{Yoplait}} - p_{j}) + \varepsilon_{j} \label{eq:mnlWtpExample} \end{equation}\]
where the parameters \(\omega_1\), \(\omega_2\), \(\omega_3\), and \(\omega_4\) have units of dollars and \(\lambda\) is the scale parameter. In the example below, we will model \(\omega_1\), \(\omega_2\), \(\omega_3\), and \(\omega_4\) as normally distributed across the population. Note that this is a slightly different assumption than in the preference space model. In the WTP space, we are assuming that the WTP for these features is normally-distributed (as opposed to the preference space model where the utility coefficients are assumed to follow a normal distribution).
In the example below, we also use a 10-iteration multistart. We also set the starting values for the first iteration to the computed WTP from the preference space model:
<- logitr(
mxl_wtp data = yogurt,
outcome = 'choice',
obsID = 'obsID',
panelID = 'id',
pars = c('feat', 'brand'),
scalePar = 'price',
randPars = c(feat = 'n', brand = 'n'),
numMultiStarts = 10,
startVals = wtp_mxl_pref$Estimate
)
#> Running multistart...
#> Iterations: 10
#> Cores: 3
#> NOTE: Using user-provided starting values for first iteration
#> Done!
Print a summary of the results:
summary(mxl_wtp)
#> =================================================
#>
#> Model estimated on: Thu Jun 16 05:48:57 2022
#>
#> Using logitr version: 0.6.1
#>
#> Call:
#> logitr(data = yogurt, outcome = "choice", obsID = "obsID", pars = c("feat",
#> "brand"), scalePar = "price", randPars = c(feat = "n", brand = "n"),
#> panelID = "id", startVals = wtp_mxl_pref$Estimate, numMultiStarts = 10)
#>
#> Frequencies of alternatives:
#> 1 2 3 4
#> 0.402156 0.029436 0.229270 0.339138
#>
#> Summary Of Multistart Runs:
#> Log Likelihood Iterations Exit Status
#> 1 -1239.294 115 3
#> 2 -1285.949 100 3
#> 3 -1258.974 89 3
#> 4 -1286.371 66 3
#> 5 -1261.622 96 3
#> 6 -1250.922 70 3
#> 7 -1260.216 63 3
#> 8 -1255.878 57 3
#> 9 -1261.053 68 3
#> 10 -1263.875 77 3
#>
#> Use statusCodes() to view the meaning of each status code
#>
#> Exit Status: 3, Optimization stopped because ftol_rel or ftol_abs was reached.
#>
#> Model Type: Mixed Logit
#> Model Space: Willingness-to-Pay
#> Model Run: 1 of 10
#> Iterations: 115
#> Elapsed Time: 0h:0m:10s
#> Algorithm: NLOPT_LD_LBFGS
#> Weights Used?: FALSE
#> Panel ID: id
#> Robust? FALSE
#>
#> Model Coefficients:
#> Estimate Std. Error z-value Pr(>|z|)
#> scalePar 0.448572 0.039996 11.2155 < 2.2e-16 ***
#> feat 1.729654 0.491729 3.5175 0.0004356 ***
#> brandhiland -14.227286 1.366031 -10.4151 < 2.2e-16 ***
#> brandweight -8.171795 0.956028 -8.5477 < 2.2e-16 ***
#> brandyoplait 2.505569 0.407526 6.1482 7.834e-10 ***
#> sd_feat 1.265524 0.497617 2.5432 0.0109851 *
#> sd_brandhiland -7.119721 0.944786 -7.5358 4.863e-14 ***
#> sd_brandweight 9.131179 0.923738 9.8850 < 2.2e-16 ***
#> sd_brandyoplait 7.270999 0.753003 9.6560 < 2.2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Log-Likelihood: -1239.2940197
#> Null Log-Likelihood: -3343.7419990
#> AIC: 2496.5880393
#> BIC: 2548.6819000
#> McFadden R2: 0.6293691
#> Adj McFadden R2: 0.6266775
#> Number of Observations: 2412.0000000
#>
#> Summary of 10k Draws for Random Coefficients:
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> feat -Inf 0.8752105 1.728687 1.727882 2.581872 Inf
#> brandhiland -Inf -19.0258810 -14.225002 -14.220685 -9.422597 Inf
#> brandweight -Inf -14.3353845 -8.179285 -8.185884 -2.021454 Inf
#> brandyoplait -Inf -2.4088088 2.493120 2.486885 7.396507 Inf
If you want to compare the WTP from the two different model spaces,
use the wtpCompare()
function:
wtpCompare(mxl_pref, mxl_wtp, scalePar = 'price')
#> pref wtp difference
#> scalePar 0.4489061 0.4485716 -0.00033443
#> feat 1.7294076 1.7296537 0.00024604
#> brandhiland -14.2433089 -14.2272860 0.01602288
#> brandweight -8.1536341 -8.1717952 -0.01816109
#> brandyoplait 2.5114154 2.5055686 -0.00584683
#> sd_feat 1.2613332 1.2655242 0.00419104
#> sd_brandhiland -7.1314099 -7.1197213 0.01168860
#> sd_brandweight 9.1186633 9.1311788 0.01251553
#> sd_brandyoplait 7.2595309 7.2709992 0.01146829
#> logLik -1239.2945922 -1239.2940197 0.00057257
Note that the WTP will not necessarily be the same between preference space and WTP space MXL models. This is because the distributional assumptions in MXL models imply different distributions on WTP depending on the model space. See Train and Weeks (2005) and Sonnier, Ainslie, and Otter (2007) for details on this topic.