Important: Read this before posting to this forum

  1. This forum is for questions related to the use of Apollo. We will answer some general choice modelling questions too, where appropriate, and time permitting. We cannot answer questions about how to estimate choice models with other software packages.
  2. There is a very detailed manual for Apollo available at http://www.ApolloChoiceModelling.com/manual.html. This contains detailed descriptions of the various Apollo functions, and numerous examples are available at http://www.ApolloChoiceModelling.com/examples.html. In addition, help files are available for all functions, using e.g. ?apollo_mnl
  3. Before asking a question on the forum, users are kindly requested to follow these steps:
    1. Check that the same issue has not already been addressed in the forum - there is a search tool.
    2. Ensure that the correct syntax has been used. For any function, detailed instructions are available directly in Apollo, e.g. by using ?apollo_mnl for apollo_mnl
    3. Check the frequently asked questions section on the Apollo website, which discusses some common issues/failures. Please see http://www.apollochoicemodelling.com/faq.html
    4. Make sure that R is using the latest official release of Apollo.
  4. If the above steps do not resolve the issue, then users should follow these steps when posting a question:
    1. provide full details on the issue, including the entire code and output, including any error messages
    2. posts will not immediately appear on the forum, but will be checked by a moderator first. We check the forum at least twice a week. It may thus take a couple of days for your post to appear and before we reply. There is no need to submit the post multiple times.

Negative scale coefficient in MaxDiff / Case 2 BWS LC model

Ask questions about the results reported after estimation. If the output includes errors, please include your model code if possible.
Post Reply
dominik_uibk
Posts: 1
Joined: 08 Jul 2026, 11:13

Negative scale coefficient in MaxDiff / Case 2 BWS LC model

Post by dominik_uibk »

Dear Apollo team,

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)
Results:

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
dpalma
Posts: 233
Joined: 24 Apr 2020, 17:54

Re: Negative scale coefficient in MaxDiff / Case 2 BWS LC model

Post by dpalma »

Hi Dominik,

While I am no expert in Best-Worst, my limited experience with it is that a wrong scale parameter simply indicates that the model formulation is not supported by the data. This would be similar to obtaining an out-of-range nest parameter in a nested logit, which simply means that the nesting structure is not supported by the data.

I would try:
  • Different starting values. You can try using apollo_searchStart for this.
  • Different utility functions formulation
  • Different parameters for the best and worst utilities. Sometimes preferences for the best and worst alternatives are different for some attributes.
I hope this helps.

Best wishes,
David
Post Reply