I am analysing results from a Best-Worst Scaling using a Latent Class Model with two classes (for now). We estimate separate attribute, attribute level parameters for both classes as well as separat scale paramaters (for the scale of the error variance of the worst vote relative to the best vote). We found a negative scale paramater for one of the classes which we are unsure how to proceed with.
The model builds on the simultanous or MaxDiff model (using the code of the apollo online example). We've estimated a Mixed Logit before and found large between-respondent heterogeneity for some of the coefficients (and then hoped to be able to reveal more information on voting patterns through the LCM). The scale parameter in the MXL was mu_worst=0.67. In the 2-class LCM, we got a similar value for one of the classes, but a significant negative scale paramter of mu_worst=-0.10 for the other (see code and results below). I would not have been to surprised about a class/group with a zero scale paramter, which I would have been interpret as a random worst choice or some kind of worst-non attendance. But I am unsure how to interpret with the the negative value.
Since the model is unconstrained, is it effectively dragging the coefficient to zero? Should we constrain the paramter ("mu_worst_1>0, mu_worst_2>0")?
Thank you and best regards
Dominik
Code:
Code: Select all
# ################################################################# #
#### DEFINE MODEL PARAMETERS ####
# ################################################################# #
apollo_beta = c(
# --- Class 1 (RUM) ---
# attribute constants (asc_busfreq_1 fixed as reference)
asc_busfreq_1 = 0,
asc_mobhub_1 = -0.1,
asc_shareserv_1 = -0.1,
asc_facil_1 = -0.1,
asc_parkpol_1 = -0.1,
asc_caraccess_1 = -0.1,
# busfreq (base level = 2: "3 times/h")
b_busfreq_15min_1 = -0.1,
b_busfreq_30min_1 = -0.1,
b_busfreq_60min_1 = -0.1,
# mobhub (base level = 5: "only stop")
b_mobhub_lockers_1 = -0.1,
b_mobhub_charging_1 = -0.1,
b_mobhub_rental_1 = -0.1,
# shareserv (base level = 11: "pooling")
b_shareserv_shuttle_1 = -0.1,
b_shareserv_hailing_1 = -0.1,
b_shareserv_sharing_1 = -0.1,
# facil (base level = 13: "EV charge price")
b_facil_EVchargefree_1 = -0.1,
b_facil_EVhotelprice_1 = -0.1,
b_facil_EVhotelfree_1 = -0.1,
# parkpol (base level = 17: "1.5 EUR/h")
b_parkpol_3_1 = -0.1,
b_parkpol_6_1 = -0.1,
b_parkpol_12_1 = -0.1,
# caraccess (base level = 21: "restricted")
b_caraccess_nocar_park0_1 = -0.1,
b_caraccess_nocar_park12.5_1 = -0.1,
b_caraccess_nocar_park25_1 = -0.1,
# --- Class 2 (RUM) ---
asc_busfreq_2 = 0,
asc_mobhub_2 = 0.2,
asc_shareserv_2 = 0.2,
asc_facil_2 = 0.2,
asc_parkpol_2 = 0.2,
asc_caraccess_2 = 0.2,
b_busfreq_15min_2 = 0.2,
b_busfreq_30min_2 = 0.2,
b_busfreq_60min_2 = 0.2,
b_mobhub_lockers_2 = 0.2,
b_mobhub_charging_2 = 0.2,
b_mobhub_rental_2 = 0.2,
b_shareserv_shuttle_2 = 0.2,
b_shareserv_hailing_2 = 0.2,
b_shareserv_sharing_2 = 0.2,
b_facil_EVchargefree_2 = 0.2,
b_facil_EVhotelprice_2 = 0.2,
b_facil_EVhotelfree_2 = 0.2,
b_parkpol_3_2 = 0.2,
b_parkpol_6_2 = 0.2,
b_parkpol_12_2 = 0.2,
b_caraccess_nocar_park0_2 = 0.2,
b_caraccess_nocar_park12.5_2 = 0.2,
b_caraccess_nocar_park25_2 = 0.2,
# --- Separate scale parameter ---
mu_worst_1 = 1,
mu_worst_2 = 1,
# --- Class membership (logit) ---
delta_1 = 0,
delta_2 = 0
)
apollo_fixed = c("asc_busfreq_1", "asc_busfreq_2", "delta_2")
# ################################################################# #
#### DEFINE LATENT CLASS PARAMETERS ####
# ################################################################# #
apollo_lcPars = function(apollo_beta, apollo_inputs) {
lcpars = list()
# Map each attribute parameter to its per-class values (list of length = nClasses)
lcpars[["asc_busfreq"]] = list(asc_busfreq_1, asc_busfreq_2)
lcpars[["asc_mobhub"]] = list(asc_mobhub_1, asc_mobhub_2)
lcpars[["asc_shareserv"]] = list(asc_shareserv_1, asc_shareserv_2)
lcpars[["asc_facil"]] = list(asc_facil_1, asc_facil_2)
lcpars[["asc_parkpol"]] = list(asc_parkpol_1, asc_parkpol_2)
lcpars[["asc_caraccess"]] = list(asc_caraccess_1, asc_caraccess_2)
lcpars[["b_busfreq_15min"]] = list(b_busfreq_15min_1, b_busfreq_15min_2)
lcpars[["b_busfreq_30min"]] = list(b_busfreq_30min_1, b_busfreq_30min_2)
lcpars[["b_busfreq_60min"]] = list(b_busfreq_60min_1, b_busfreq_60min_2)
lcpars[["b_mobhub_lockers"]] = list(b_mobhub_lockers_1, b_mobhub_lockers_2)
lcpars[["b_mobhub_charging"]] = list(b_mobhub_charging_1, b_mobhub_charging_2)
lcpars[["b_mobhub_rental"]] = list(b_mobhub_rental_1, b_mobhub_rental_2)
lcpars[["b_shareserv_shuttle"]] = list(b_shareserv_shuttle_1, b_shareserv_shuttle_2)
lcpars[["b_shareserv_hailing"]] = list(b_shareserv_hailing_1, b_shareserv_hailing_2)
lcpars[["b_shareserv_sharing"]] = list(b_shareserv_sharing_1, b_shareserv_sharing_2)
lcpars[["b_facil_EVchargefree"]] = list(b_facil_EVchargefree_1, b_facil_EVchargefree_2)
lcpars[["b_facil_EVhotelprice"]] = list(b_facil_EVhotelprice_1, b_facil_EVhotelprice_2)
lcpars[["b_facil_EVhotelfree"]] = list(b_facil_EVhotelfree_1, b_facil_EVhotelfree_2)
lcpars[["b_parkpol_3"]] = list(b_parkpol_3_1, b_parkpol_3_2)
lcpars[["b_parkpol_6"]] = list(b_parkpol_6_1, b_parkpol_6_2)
lcpars[["b_parkpol_12"]] = list(b_parkpol_12_1, b_parkpol_12_2)
lcpars[["b_caraccess_nocar_park0"]] = list(b_caraccess_nocar_park0_1, b_caraccess_nocar_park0_2)
lcpars[["b_caraccess_nocar_park12.5"]] = list(b_caraccess_nocar_park12.5_1, b_caraccess_nocar_park12.5_2)
lcpars[["b_caraccess_nocar_park25"]] = list(b_caraccess_nocar_park25_1, b_caraccess_nocar_park25_2)
lcpars[["mu_worst"]] = list(mu_worst_1, mu_worst_2)
# Class membership (delta_2 = 0 fixed as reference)
V = list()
V[["class_1"]] = delta_1
V[["class_2"]] = delta_2
classAlloc_settings = list(
classes = c(class_1 = 1, class_2 = 2),
avail = 1,
utilities = V
)
lcpars[["pi_values"]] = apollo_classAlloc(classAlloc_settings)
return(lcpars)
}
# ################################################################# #
#### GROUP AND VALIDATE INPUTS ####
# ################################################################# #
apollo_inputs = apollo_validateInputs()
# ################################################################# #
#### DEFINE MODEL AND LIKELIHOOD FUNCTION ####
# ################################################################# #
apollo_probabilities = function(apollo_beta, apollo_inputs, functionality = "estimate") {
### Attach inputs and detach after function exit
apollo_attach(apollo_beta, apollo_inputs)
on.exit(apollo_detach(apollo_beta, apollo_inputs))
### Create list of probabilities P
P = list()
### Pre-build the 30 best-!=worst paired alternative codes (shared across classes)
alts = list()
for (best in 1:6) for (worst in 1:6) if (best != worst)
alts[[paste0("alt_b", best, "_w", worst)]] = 10*best + worst
choiceVar = 10 * best_attr + worst_attr
### Loop over latent classes
for (s in 1:2) {
### Effects coding constraints using class-s parameters
b_busfreq_3times = -b_busfreq_15min[[s]] - b_busfreq_30min[[s]] - b_busfreq_60min[[s]]
b_mobhub_onlystop = -b_mobhub_lockers[[s]] - b_mobhub_charging[[s]] - b_mobhub_rental[[s]]
b_shareserv_pooling = -b_shareserv_shuttle[[s]] - b_shareserv_hailing[[s]] - b_shareserv_sharing[[s]]
b_facil_EVchargeprice = -b_facil_EVchargefree[[s]] - b_facil_EVhotelprice[[s]] - b_facil_EVhotelfree[[s]]
b_parkpol_1.5 = -b_parkpol_3[[s]] - b_parkpol_6[[s]] - b_parkpol_12[[s]]
b_caraccess_restr = -b_caraccess_nocar_park0[[s]] - b_caraccess_nocar_park12.5[[s]] - b_caraccess_nocar_park25[[s]]
### Attribute-level utilities for class s
V = list()
V[["alt1"]] = asc_busfreq[[s]] + b_busfreq_15min[[s]]*(bus_freq==1) + b_busfreq_3times*(bus_freq==2) + b_busfreq_30min[[s]]*(bus_freq==3) + b_busfreq_60min[[s]]*(bus_freq==4)
V[["alt2"]] = asc_mobhub[[s]] + b_mobhub_onlystop*(mob_hub==5) + b_mobhub_lockers[[s]]*(mob_hub==6) + b_mobhub_charging[[s]]*(mob_hub==7) + b_mobhub_rental[[s]]*(mob_hub==8)
V[["alt3"]] = asc_shareserv[[s]] + b_shareserv_shuttle[[s]]*(share_serv==9) + b_shareserv_hailing[[s]]*(share_serv==10) + b_shareserv_pooling*(share_serv==11) + b_shareserv_sharing[[s]]*(share_serv==12)
V[["alt4"]] = asc_facil[[s]] + b_facil_EVchargeprice*(facil==13) + b_facil_EVchargefree[[s]]*(facil==14) + b_facil_EVhotelprice[[s]]*(facil==15) + b_facil_EVhotelfree[[s]]*(facil==16)
V[["alt5"]] = asc_parkpol[[s]] + b_parkpol_1.5*(park_pol==17) + b_parkpol_3[[s]]*(park_pol==18) + b_parkpol_6[[s]]*(park_pol==19) + b_parkpol_12[[s]]*(park_pol==20)
V[["alt6"]] = asc_caraccess[[s]] + b_caraccess_restr*(car_access==21) + b_caraccess_nocar_park0[[s]]*(car_access==22) + b_caraccess_nocar_park12.5[[s]]*(car_access==23) + b_caraccess_nocar_park25[[s]]*(car_access==24)
### Paired-alternative utilities for class s
utils = list()
for (b in 1:6) for (w in 1:6) if (b != w) {
nm = paste0("alt_b", b, "_w", w)
utils[[nm]] = V[[paste0("alt", b)]] - mu_worst[[s]] * V[[paste0("alt", w)]]
}
### MNL choice probability for this class
mnl_settings = list(
alternatives = unlist(alts),
choiceVar = choiceVar,
utilities = utils,
componentName = paste0("Class_", s)
)
P[[paste0("Class_", s)]] = apollo_mnl(mnl_settings, functionality)
### Panel product: multiply across tasks for the same respondent
P[[paste0("Class_", s)]] = apollo_panelProd(P[[paste0("Class_", s)]], apollo_inputs, functionality)
}
### Latent class combination (pi_values provided by apollo_lcPars via apollo_attach)
lc_settings = list(inClassProb = P, classProb = pi_values)
P[["model"]] = apollo_lc(lc_settings, apollo_inputs, functionality)
### Prepare and return outputs of function
P = apollo_prepareProb(P, apollo_inputs, functionality)
return(P)
}
# ################################################################# #
#### MODEL ESTIMATION ####
# ################################################################# #
model = apollo_estimate(apollo_beta, apollo_fixed, apollo_probabilities, apollo_inputs)
Code: Select all
Model name : BWScase2_simultan_LC_baseline_2c_scaled
Model description : Best-Worst Scaling Case 2 latent class model:
to model heterogenous voting behavior
Both classes have class-specific utility parameters.
Model run at : 2026-06-18 22:55:31.72068
Estimation method : bgw
Estimation diagnosis : Relative function convergence
Optimisation diagnosis : Maximum found
hessian properties : Negative definite
maximum eigenvalue : -16.421932
reciprocal of condition number : 0.00713948
Number of individuals : 1199
Number of rows in database : 9592
Number of modelled outcomes : 9592
Number of cores used : 8
Model without mixing
LL(start) : -32725.26
LL (whole model) at equal shares, LL(0) : -32624.29
LL (whole model) at observed shares, LL(C) : -31264.99
LL(final, whole model) : -29721.55
Rho-squared vs equal shares : 0.089
Adj.Rho-squared vs equal shares : 0.0875
Rho-squared vs observed shares : 0.0494
Adj.Rho-squared vs observed shares : 0.0497
AIC : 59541.1
BIC : 59892.37
LL(0,Class_1) : -32624.29
LL(final,Class_1) : -34873.08
LL(0,Class_2) : -32624.29
LL(final,Class_2) : -33162.64
Estimated parameters : 49
Time taken (hh:mm:ss) : 00:07:44.78
pre-estimation : 00:00:42.14
estimation : 00:01:43.13
post-estimation : 00:05:19.51
Iterations : 47
Unconstrained optimisation.
Estimates:
Estimate s.e. t.rat.(0) p(2-sided) t.rat(1) p(2-sided)
asc_busfreq_1 0.00000 NA NA NA NA NA
asc_mobhub_1 -0.72143 0.04837 -14.915126 0.000000 -35.589 0.000000
asc_shareserv_1 -1.17491 0.07983 -14.717072 0.000000 -27.243 0.000000
asc_facil_1 -1.33438 0.11558 -11.545291 0.000000 -20.197 0.000000
asc_parkpol_1 -2.17150 0.12388 -17.529295 0.000000 -25.602 0.000000
asc_caraccess_1 -1.67670 0.07877 -21.285293 0.000000 -33.980 0.000000
...
mu_worst_1 0.80148 0.05737 13.970362 0.000000 -3.460 5.3950e-04
mu_worst_2 -0.10034 0.04534 -2.212857 0.026907 -24.267 0.000000
delta_1 -0.16442 0.09688 -1.697220 0.089655 -12.019 0.000000
delta_2 0.00000 NA NA NA NA NA
Rob.s.e. Rob.t.rat.(0) p(2-sided) Rob.t.rat.(1) p(2-sided)
asc_busfreq_1 NA NA NA NA NA
asc_mobhub_1 0.22235 -3.244541 0.001176 -7.7419 9.770e-15
asc_shareserv_1 0.46984 -2.500665 0.012396 -4.6291 3.673e-06
asc_facil_1 0.76029 -1.755092 0.079244 -3.0704 0.002138
asc_parkpol_1 0.64751 -3.353606 7.9766e-04 -4.8980 9.683e-07
asc_caraccess_1 0.37112 -4.517978 6.243e-06 -7.2125 5.491e-13
...
mu_worst_1 0.10106 7.930360 2.220e-15 -1.9643 0.049498
mu_worst_2 0.27263 -0.368042 0.712842 -4.0360 5.436e-05
delta_1 0.49737 -0.330586 0.740957 -2.3411 0.019225
delta_2 NA NA NA NA NA
Summary of class allocation for model component :
Mean prob.
Class_1 0.4590
Class_2 0.5410