The probit model assigns alternative \(j\) at occasion \(t\) of decider \(n\) the latent utility \(U_{ntj} = X_{ntj}^\top \beta_n +
\epsilon_{ntj}\) with the coefficient vector \(\beta_n\). A model with fixed coefficients
sets \(\beta_n = \beta\) for all
deciders and thereby assumes that all deciders weigh price, time, and
comfort in the same way. This is rarely true: some travelers react
mainly to the fare, others to travel time, and some households would pay
a premium for a local electricity supplier while others would not.
RprobitB lets coefficients differ between deciders in
two ways, which can be combined: random coefficients follow a continuous
distribution over the population, and latent classes divide the deciders
into groups. Both are requested through arguments of fit().
Oelschläger (2026) treats the methodological
background. Each variant is first estimated on simulated data, where the
estimates can be compared with the parameters that generated them, and
then applied to data of the mlogit package (Croissant
2020).
Each term named in random_effects receives a separate
coefficient for every decider, drawn from a population distribution
whose parameters the sampler estimates together with the other
parameters. Such hierarchical models capture continuous preference
heterogeneity and allow inference about the coefficients of individual
deciders (Allenby and
Rossi 1998). The population distribution is normal on a
latent scale: the random coefficients of decider \(n\) are \(\beta_n
\sim \mathrm{N}(\mu, \Omega)\) with the mean vector \(\mu\) and the covariance matrix \(\Omega\), reported as
mu[<effect>] and
Omega[<effect>,<effect>]. The mixing
distribution of an effect decides how its latent normal variable enters
the utility. An unnamed vector requests correlated normal effects. A
named vector selects the mixing distribution of each term as
"<covariate>" = "<distribution>", where
"ASC" names the alternative-specific constants:
| Value | Distribution | Coefficient |
|---|---|---|
"cn" |
correlated normal | any sign |
"n" |
uncorrelated normal | any sign |
"cln" |
correlated log-normal | positive |
"ln" |
uncorrelated log-normal | positive |
"cln-" |
correlated log-normal | negative |
"ln-" |
uncorrelated log-normal | negative |
The six values combine two choices. The first is the shape of the
distribution. A normal coefficient can take any value, which suits an
attribute that some deciders like and others dislike. A log-normal
coefficient is the exponential of a normal variable and therefore always
positive, or, with the trailing minus, always negative. It suits an
attribute whose sign is fixed by theory: a higher price does not raise
the utility of any decider. Log-normal effects are estimated on the
latent normal scale, so mu, Omega, and the
individual draws refer to the normal variable whose exponential enters
the utility. The second choice is whether an effect is correlated with
the other correlated effects. Deciders who value travel time may also
value comfort, and the c prefix adds the covariance between
such effects to the model. An uncorrelated effect has its own variance
but no covariance with any other effect, which appears as zeros in
Omega.
The following demonstration combines both choices. The price coefficient is negative log-normal and uncorrelated. Travel time and comfort receive correlated normal effects with a positive covariance.
mixing <- fit(
choice ~ price + time + comfort | 0,
random_effects = c(price = "ln-", time = "cn", comfort = "cn"),
n_deciders = 100,
n_occasions = 10,
dgp_parameters = list(
beta = c(price = -1, time = -0.8, comfort = 0.5),
Omega = rbind(c(0.25, 0, 0), c(0, 0.4, 0.2), c(0, 0.2, 0.3))
),
iterations = 4000,
warmup = 2000,
thin = 2,
chains = 1,
save_individual_draws = TRUE
)
summary(mixing)
#> Bayesian probit choice model
#> Formula: choice ~ price + time + comfort | 0 | 0
#> Samples: 1000 retained per chain, 1 chain
#> variable dgp mean mode sd rhat ess_bulk
#> mu[price] -1.00 -0.956 -0.946 0.1284 1.02 36.0
#> mu[time] -0.80 -0.921 -0.897 0.1045 1.02 66.2
#> mu[comfort] 0.50 0.563 0.561 0.0872 1.01 93.1
#> Omega[price,price] 0.25 0.286 0.227 0.1082 1.04 31.4
#> Omega[time,time] 0.40 0.580 0.547 0.1469 1.05 23.3
#> Omega[time,comfort] 0.20 0.281 0.251 0.0971 1.00 70.0
#> Omega[comfort,comfort] 0.30 0.374 0.381 0.1000 1.02 27.8The dgp column lists the parameters that generated the
data. For the price coefficient, mu[price] is the mean of
the latent normal variable, so the coefficient that enters the utility
is minus its exponential and negative for every decider.
time and comfort share the estimated
covariance Omega[time,comfort], while price
has no covariance entry with either of them.
interpret() converts the coefficients into trade-offs.
For a random effect it uses the coefficient of the median decider, which
for the log-normal price is minus the exponential of
mu[price]:
interpret(mixing, reference = "price")
#> 1 `time` compensates -2.41 `price` (95% interval -3.38 to -1.75)
#> 1 `comfort` compensates 1.47 `price` (95% interval 0.985 to 2.13)The dgp_parameters set the latent mean of the price
effect to -1, so the true median price coefficient is
-exp(-1), about -0.37. Dividing the true time
and comfort coefficients by it gives the true trade-offs, with which the
posterior means above can be compared.
The panel structure makes individual coefficients estimable.
coef(level = "individual") returns the posterior mean
coefficient of every decider, on the latent normal scale for log-normal
effects. The histogram shows the price coefficient of each of the 100
deciders, all negative as the log-normal specification enforces, with
the left tail containing the deciders who react most strongly to a
higher price.
individual <- coef(mixing, level = "individual")
head(individual)
#> price time comfort
#> 1 -0.9435903 -0.7202750 0.8751589
#> 2 -1.1755543 -1.2226296 -0.4385831
#> 3 -0.8668353 -0.2364210 0.8752388
#> 4 -0.7618339 -0.3089142 1.1401193
#> 5 -0.6839710 0.1314405 1.1817297
#> 6 -1.0070474 -1.9981351 -0.4826107
price <- -exp(individual[, "price"])
hist(
price,
breaks = 30, col = "grey85", border = "white",
main = "", xlab = "price coefficient of a decider"
)The Electricity data of the mlogit
package come from a stated choice experiment in which 361 US households
chose 8 to 12 times among four hypothetical suppliers. The suppliers
differ in price (pf), contract length (cl),
whether the supplier is local (loc) or well-known
(wk), and whether it offers time-of-day (tod)
or seasonal (seas) rates (Huber and Train 2001). The attribute
columns end in the supplier number without a delimiter, pf1
to pf4, so an underscore is inserted first, and the choice
occasions of a household are numbered in the order of the rows.
Contract length and locality receive correlated normal random
coefficients, the other attributes one coefficient for all households.
Fixing the price coefficient to -1 identifies the scale and
expresses every other coefficient in cents per kWh, that is, directly as
a willingness to pay. The fit uses the first 100 households to keep the
computation short.
data("Electricity", package = "mlogit")
names(Electricity) <- sub("([a-z]+)([1-4])$", "\\1_\\2", names(Electricity))
Electricity$occasion <- ave(Electricity$id, Electricity$id, FUN = seq_along)
households <- Electricity[Electricity$id %in% unique(Electricity$id)[1:100], ]
electricity <- fit(
choice ~ pf + cl + loc + wk + tod + seas | 0,
data = households,
random_effects = c("cl", "loc"),
column_decider = "id",
column_occasion = "occasion",
scale = c(pf = -1),
iterations = 4000,
warmup = 2000,
thin = 2,
chains = 1,
save_individual_draws = TRUE,
progress = FALSE
)
summary(electricity)
#> Bayesian probit choice model
#> Formula: choice ~ pf + cl + loc + wk + tod + seas | 0 | 0
#> Samples: 1000 retained per chain, 1 chain
#> variable mean mode sd rhat ess_bulk
#> beta[wk] 1.638 1.6481 0.1413 1.000 187.2
#> beta[tod] -9.002 -9.0171 0.1560 1.001 140.1
#> beta[seas] -9.260 -9.2902 0.1574 1.007 124.2
#> mu[cl] -0.283 -0.3051 0.0645 1.003 404.5
#> mu[loc] 2.023 1.9833 0.2401 0.999 295.5
#> Omega[cl,cl] 0.304 0.2795 0.0717 1.005 133.7
#> Omega[cl,loc] 0.108 0.0941 0.1168 1.006 212.1
#> Omega[loc,loc] 2.286 2.0095 0.6530 1.003 179.3
#> Sigma[2,2] 7.601 7.1518 1.4440 1.027 38.3
#> Sigma[2,3] 2.377 2.2962 0.8535 1.000 32.5
#> Sigma[3,3] 5.526 4.8822 1.1887 1.001 110.9
#> Sigma[2,4] 3.955 3.6995 1.1866 1.065 28.4
#> Sigma[3,4] 1.652 1.4400 0.7695 1.018 45.3
#> Sigma[4,4] 6.139 5.8729 1.4809 1.020 53.6Because the price coefficient is fixed, interpret()
reports the coefficients directly as willingness to pay in cents per
kWh, with credible intervals:
interpret(electricity, effects = c("cl", "loc", "wk"))
#> 1 `cl` compensates -0.283 `pf` (95% interval -0.413 to -0.149)
#> 1 `loc` compensates 2.02 `pf` (95% interval 1.57 to 2.53)
#> 1 `wk` compensates 1.64 `pf` (95% interval 1.39 to 1.93)On average, households would accept a price about 2 cents per kWh higher for a local supplier, and would need a price about 0.28 cents lower for each additional year of contract length.
A single normal distribution is a strong assumption about the
distribution of preferences in a population. There may be commuters and
leisure travelers, or households that focus on price and households that
focus on service. latent_class_effects names the effects
that differ between classes latent classes. Every decider
belongs to exactly one class. The class weights \(w_1, \dots, w_K\) are the probabilities of
membership and sum to one, and the class allocation of every decider is
a latent variable that the sampler draws together with the parameters.
Naming a random effect in latent_class_effects replaces its
normal distribution by a finite mixture of normals, which yields the
latent-class mixed multinomial probit model of Oelschläger and Bauer (2021). Random effects that are
not named keep one distribution for all deciders, and a named
coefficient that is not random takes one value per class and does not
vary within it, as in the classical latent class model (Kamakura and Russell
1989; Greene and Hensher 2003).
class_update selects how the number of classes is
treated:
class_update = "fixed" when exactly \(K\) substantively meaningful classes are
assumed;class_update = "sparse" when classes is a
generous upper bound and redundant classes should empty;class_update = "dirichlet_process" for a
Dirichlet-process mixture whose number of occupied classes is itself
random;class_update = "weight_based" for the split, remove,
and merge heuristic.All four updates are demonstrated on one simulated data set with two
classes. The fixed-class fit simulates the data; the other three are
refits through update(), which reuses the simulated
data.
For class_update = "fixed", classes = K
fixes the number of classes. The class weights have the prior \((w_1,\ldots,w_K)\sim\operatorname{Dirichlet}(\delta,\ldots,\delta)\),
where the default class_concentration = 1 is uniform on the
weight simplex. The class labels are arbitrary: swapping them leaves the
likelihood unchanged, so the sampler may swap them during a run.
RprobitB therefore relabels the retained draws after
sampling, so that every draw uses the same labels (Dahl 2006; Papastamoulis and Iliopoulos
2010), and numbers the classes by decreasing weight.
The following demonstration uses eight occasions per decider and two
well-separated classes: a majority with coefficients centered at
-1 and a minority centered at 2.
mixture <- fit(
choice ~ x | 0,
random_effects = "x",
latent_class_effects = "x",
classes = 2,
n_deciders = 100,
n_occasions = 8,
save_individual_draws = TRUE,
dgp_parameters = list(
beta = list(c(x = -1), c(x = 2)),
Omega = list(matrix(0.2), matrix(0.2)),
weights = c(0.6, 0.4)
),
iterations = 1500,
chains = 2,
progress = FALSE
)
summary(mixture, variables = c(
"weight[1]", "weight[2]", "mu[x,1]", "mu[x,2]",
"Omega[x,x,1]", "Omega[x,x,2]"
))
#> Bayesian probit choice model
#> Formula: choice ~ x | 0 | 0
#> Samples: 750 retained per chain, 2 chains
#>
#> variable dgp mean mode sd rhat ess_bulk
#> weight[1] 0.6 0.642 0.647 0.050 1.00 736.8
#> weight[2] 0.4 0.358 0.353 0.050 1.00 736.8
#> mu[x,1] -1.0 -0.952 -0.923 0.111 1.06 62.5
#> mu[x,2] 2.0 1.726 1.648 0.233 1.08 23.8
#> Omega[x,x,1] 0.2 0.211 0.166 0.122 1.02 48.0
#> Omega[x,x,2] 0.2 0.338 0.188 0.263 1.11 13.7latent_class_diagnostics() returns the posterior
distribution of the number of occupied classes, the membership
probabilities of the deciders after relabeling, and the co-clustering
matrix. Its entries are the posterior probabilities that two deciders
belong to the same class:
class_diagnostics <- latent_class_diagnostics(mixture)
class_diagnostics$occupancy
#> n_classes probability
#> 1 2 1
class_diagnostics$membership[1:6, ]
#> class_1 class_2
#> 1 0.9953333 0.004666667
#> 2 1.0000000 0.000000000
#> 3 0.9973333 0.002666667
#> 4 0.0000000 1.000000000
#> 5 0.0000000 1.000000000
#> 6 0.9986667 0.001333333
class_diagnostics$co_clustering[1:6, 1:6]
#> 1 2 3 4 5 6
#> 1 1.000000000 0.9953333 0.992666667 0.004666667 0.004666667 0.994000000
#> 2 0.995333333 1.0000000 0.997333333 0.000000000 0.000000000 0.998666667
#> 3 0.992666667 0.9973333 1.000000000 0.002666667 0.002666667 0.996000000
#> 4 0.004666667 0.0000000 0.002666667 1.000000000 1.000000000 0.001333333
#> 5 0.004666667 0.0000000 0.002666667 1.000000000 1.000000000 0.001333333
#> 6 0.994000000 0.9986667 0.996000000 0.001333333 0.001333333 1.000000000Do the train travelers of the vignette Get
started with RprobitB all trade time against money at the same rate,
or are there classes with different values of time? The fit below uses
the first 60 travelers, fixes the price coefficient to -1,
and gives the time coefficient two class-specific values through
latent_class_effects without a random effect, so each class
has its own value of travel time and the remaining coefficients are
common to both classes.
data("Train", package = "mlogit")
Train$price_A <- Train$price_A / 100 / 2.20371
Train$price_B <- Train$price_B / 100 / 2.20371
Train$time_A <- Train$time_A / 60
Train$time_B <- Train$time_B / 60
train_small <- Train[Train$id %in% unique(Train$id)[1:60], ]
train_classes <- fit(
choice ~ price + time + change + factor(comfort) | 0,
data = train_small,
latent_class_effects = "time",
classes = 2,
column_decider = "id",
column_occasion = "choiceid",
scale = c(price = -1),
iterations = 1500,
chains = 2,
progress = FALSE
)
summary(train_classes, variables = c(
"weight[1]", "weight[2]", "beta[time,1]", "beta[time,2]"
))
#> Bayesian probit choice model
#> Formula: choice ~ price + time + change + factor(comfort) | 0 | 0
#> Samples: 750 retained per chain, 2 chains
#>
#> variable mean mode sd rhat ess_bulk
#> weight[1] 0.752 0.758 0.0857 1.02 63.6
#> weight[2] 0.248 0.242 0.0857 1.02 63.6
#> beta[time,1] -2.702 -2.973 1.0179 1.02 72.6
#> beta[time,2] -18.709 -16.929 4.5048 1.03 35.8With the price coefficient fixed at -1, the
class-specific time coefficients are values of travel time in euro per
hour, which interpret() reports class by class:
time_by_class <- interpret(train_classes, effects = "time")
time_by_class
#> Class 1: 1 `time` compensates -2.7 `price` (95% interval -4.64 to -0.623)
#> Class 2: 1 `time` compensates -18.7 `price` (95% interval -33 to -12.9)The larger class values an hour at about 3 euro, the smaller class at about 19 euro. The smaller class chooses the faster trip almost regardless of its price.
Oelschläger and Bauer (2021) presents a weight-based
update scheme for latent class analysis. Every buffer
warmup iterations, it removes the smallest class if its weight is below
epsmin, splits the largest class if its weight is above
epsmax, or merges the closest pair of classes if the
distance of their means is below deltamin, at most one
operation in this order. weight_based_control overrides the
defaults of these constants, and max_classes bounds the
splitting. These dimension changes correspond to no prior on the number
of classes, and they stop after warmup, so the reported
n_classes is the outcome of a search, not a posterior
distribution.
This refit and the two in the following subsections change the class
update, so summary() has no dgp column for
them. The helper recover_classes() provides the comparison
with the true values instead. The classes are numbered by decreasing
weight, so the first class should be the majority with weight
0.6 and mean -1, and the second class the
minority with weight 0.4 and mean 2. The
helper puts the posterior means of these four variables and the most
probable number of occupied classes beside the true values. Applied to
the fit with two fixed classes, it gives the reference for the
refits:
recover_classes <- function(x) {
variables <- c("weight[1]", "weight[2]", "mu[x,1]", "mu[x,2]")
occupancy <- latent_class_diagnostics(x)$occupancy
data.frame(
variable = c("n_classes", variables),
dgp = c(2, 0.6, 0.4, -1, 2),
estimate = round(c(
occupancy$n_classes[which.max(occupancy$probability)],
coef(x)[variables]
), 2),
row.names = NULL
)
}
recover_classes(mixture)
#> variable dgp estimate
#> 1 n_classes 2.0 2.00
#> 2 weight[1] 0.6 0.64
#> 3 weight[2] 0.4 0.36
#> 4 mu[x,1] -1.0 -0.95
#> 5 mu[x,2] 2.0 1.73The weight-based refit is compared in the same way:
weight_based <- update(mixture, class_update = "weight_based")
recover_classes(weight_based)
#> variable dgp estimate
#> 1 n_classes 2.0 2.00
#> 2 weight[1] 0.6 0.63
#> 3 weight[2] 0.4 0.37
#> 4 mu[x,1] -1.0 -0.95
#> 5 mu[x,2] 2.0 1.90The run ends with the two classes that generated the data, and their weights and means agree closely with those of the fixed fit.
A sparse finite mixture fixes a generous upper bound \(K\), permits empty classes, and places the
symmetric Dirichlet prior with a small concentration \(e_0\) on the weights, which favors emptying
redundant classes (Rousseau and Mengersen 2011; Frühwirth-Schnatter and
Malsiner-Walli 2019). The default
class_concentration = c(shape = 1, rate = 200) is the gamma
hyperprior \(e_0\sim\operatorname{Gamma}(1,200)\) with
mean 0.005. Under a fixed \(e_0\), the prior expected number of
occupied classes among \(N\) deciders
is
\[ K\left[1- \frac{\Gamma(Ke_0)\,\Gamma((K-1)e_0+N)} {\Gamma((K-1)e_0)\,\Gamma(Ke_0+N)}\right], \]
which translates \(e_0\) into a statement about the number of classes. At the prior mean \(e_0 = 0.005\), with \(K = 6\) and the 100 deciders of the simulated data, the prior expects close to a single occupied class. The refit sets the upper bound to six classes for the two-class data.
sparse <- update(mixture, classes = 6, class_update = "sparse")
summary(sparse)
#> Bayesian probit choice model
#> Formula: choice ~ x | 0 | 0
#> Samples: 750 retained per chain, 2 chains
#>
#> variable occupied mean mode sd rhat ess_bulk
#> weight[1] 1.00000 0.64853 0.65088 0.04906 1.00 398.8
#> weight[2] 1.00000 0.34974 0.35131 0.04999 1.00 357.3
#> weight[3] 0.01867 0.04568 0.01316 0.05625 NA NA
#> weight[4] 0.01400 0.04428 0.02009 0.04498 NA NA
#> weight[5] 0.00133 0.00777 0.00200 0.00819 NA NA
#> mu[x,1] 1.00000 -0.94376 -0.92041 0.10470 1.01 101.4
#> mu[x,2] 1.00000 1.80321 1.80700 0.28163 1.11 14.9
#> mu[x,3] 0.01867 0.21099 0.86381 1.22663 NA NA
#> mu[x,4] 0.01400 0.66316 0.00170 1.40181 NA NA
#> mu[x,5] 0.00133 0.38135 -0.64276 1.45400 NA NA
#> Omega[x,x,1] 1.00000 0.21250 0.14544 0.10665 1.04 59.1
#> Omega[x,x,2] 1.00000 0.42390 0.19726 0.44729 1.10 15.5
#> Omega[x,x,3] 0.01867 0.71721 0.24689 0.86957 NA NA
#> Omega[x,x,4] 0.01400 0.42503 0.22908 0.38434 NA NA
#> Omega[x,x,5] 0.00133 0.23504 0.10206 0.18880 NA NA
#> class_concentration 1.00000 0.00811 0.00386 0.00520 1.14 14.0
#> n_classes 1.00000 2.03400 2.00000 0.18129 1.02 88.4
latent_class_diagnostics(sparse)$occupancy
#> n_classes probability
#> 1 2 0.966
#> 2 3 0.034
recover_classes(sparse)
#> variable dgp estimate
#> 1 n_classes 2.0 2.00
#> 2 weight[1] 0.6 0.65
#> 3 weight[2] 0.4 0.35
#> 4 mu[x,1] -1.0 -0.94
#> 5 mu[x,2] 2.0 1.80Although the prior favors a single class, the posterior concentrates
on two occupied classes, whose weights and means are close to the true
values. When the number of classes varies, a class exists only in part
of the draws, and the occupied column of
summary() reports this share.
For class_update = "dirichlet_process", the precision
\(\alpha\) controls the prior tendency
to open new classes, with the default \(\alpha\sim\operatorname{Gamma}(2,4)\) of
mean 0.5. Conditional on a fixed \(\alpha\), the prior expects \(\sum_{i=1}^{N}\alpha/(\alpha+i-1)\)
occupied classes among \(N\) deciders,
which for \(\alpha = 0.5\) and 100
deciders is about three. RprobitB updates \(\alpha\) with the augmentation of Escobar and West (1995) and the allocations with the
algorithm of Neal (2000). max_classes caps the
number of classes the sampler may open, and fit() warns
when the draws reach the cap.
dynamic <- update(
mixture, class_update = "dirichlet_process", max_classes = 15,
iterations = 1000
)
summary(dynamic)
#> Bayesian probit choice model
#> Formula: choice ~ x | 0 | 0
#> Samples: 500 retained per chain, 2 chains
#>
#> variable occupied mean mode sd rhat ess_bulk
#> weight[1] 1.000 0.6162 0.6399 0.05061 1.08 23.4
#> weight[2] 1.000 0.3196 0.3530 0.05394 1.05 37.1
#> weight[3] 0.739 0.0522 0.0185 0.04794 NA NA
#> weight[4] 0.418 0.0411 0.0122 0.05132 NA NA
#> weight[5] 0.211 0.0273 0.0103 0.02813 NA NA
#> weight[6] 0.094 0.0201 0.0107 0.01562 NA NA
#> weight[7] 0.041 0.0139 0.0100 0.00666 NA NA
#> weight[8] 0.013 0.0138 0.0100 0.00506 NA NA
#> weight[9] 0.004 0.0100 0.0100 0.00000 NA NA
#> weight[10] 0.001 0.0100 0.0100 NA NA NA
#> mu[x,1] 1.000 -0.9541 -0.9353 0.10484 1.09 18.2
#> mu[x,2] 1.000 1.8161 1.8702 0.26389 1.09 33.7
#> mu[x,3] 0.739 -0.2260 1.5488 2.55053 NA NA
#> mu[x,4] 0.418 -0.5404 -0.7682 2.23122 NA NA
#> mu[x,5] 0.211 0.5182 -0.6997 2.82037 NA NA
#> mu[x,6] 0.094 0.4110 -0.5243 2.79613 NA NA
#> mu[x,7] 0.041 1.3673 0.4289 2.29924 NA NA
#> mu[x,8] 0.013 -0.4197 0.4193 3.10556 NA NA
#> mu[x,9] 0.004 1.8442 1.7016 2.97785 NA NA
#> mu[x,10] 0.001 2.4817 2.4817 NA NA NA
#> Omega[x,x,1] 1.000 0.1907 0.1293 0.08694 1.13 20.0
#> Omega[x,x,2] 1.000 0.3680 0.1879 0.29598 1.08 28.7
#> Omega[x,x,3] 0.739 1.0103 0.2429 2.55221 NA NA
#> Omega[x,x,4] 0.418 1.0205 0.2769 1.72494 NA NA
#> Omega[x,x,5] 0.211 1.0638 0.2956 1.97809 NA NA
#> Omega[x,x,6] 0.094 0.7327 0.2879 1.06701 NA NA
#> Omega[x,x,7] 0.041 0.7324 0.2946 0.88532 NA NA
#> Omega[x,x,8] 0.013 0.7130 0.3192 0.85467 NA NA
#> Omega[x,x,9] 0.004 0.6714 0.3993 0.49658 NA NA
#> Omega[x,x,10] 0.001 0.9626 0.9626 NA NA NA
#> class_concentration 1.000 0.5294 0.4154 0.29634 1.02 104.1
#> n_classes 1.000 3.5210 3.0000 1.40482 1.06 30.3
latent_class_diagnostics(dynamic)$occupancy
#> n_classes probability
#> 1 2 0.261
#> 2 3 0.321
#> 3 4 0.207
#> 4 5 0.117
#> 5 6 0.053
#> 6 7 0.028
#> 7 8 0.009
#> 8 9 0.003
#> 9 10 0.001
recover_classes(dynamic)
#> variable dgp estimate
#> 1 n_classes 2.0 3.00
#> 2 weight[1] 0.6 0.62
#> 3 weight[2] 0.4 0.32
#> 4 mu[x,1] -1.0 -0.95
#> 5 mu[x,2] 2.0 1.82The occupancy distribution is wider than under the sparse finite prior and assigns most of its probability to three or more classes. This reflects the prior, which expects about three occupied classes for 100 deciders. The two largest classes nevertheless match the two true groups, because the superfluous classes contain only a few deciders.
Both mechanisms also work for ordered and ranked responses, which the
vignette Model
specification and variants introduces. The following demonstration
uses a simulated panel of rankings: 100 deciders order three
alternatives five times each, with a coefficient that varies normally
around -1.
ranked_random <- fit(
rank ~ x | 0,
choice_type = "ranked",
random_effects = "x",
n_deciders = 100,
n_occasions = 5,
n_alternatives = 3,
dgp_parameters = list(beta = c(x = -1), Omega = matrix(0.3)),
iterations = 2000,
chains = 1,
progress = FALSE
)
summary(ranked_random)
#> Bayesian probit choice model
#> Formula: rank ~ x | 0 | 0
#> Samples: 1000 retained per chain, 1 chain
#> variable dgp mean mode sd rhat ess_bulk
#> mu[x] -1.000 -1.009 -0.974 0.0860 1.08 25.7
#> Omega[x,x] 0.300 0.287 0.246 0.0729 1.09 17.5
#> Sigma[B,C] 0.292 0.325 0.325 0.0695 1.01 117.9
#> Sigma[C,C] 0.737 0.635 0.613 0.1159 1.01 121.3The summary shows the population mean and variance beside their true
values. Ordered responses accept the same arguments, and latent classes
are requested in the same way as above, with
latent_class_effects and classes.
The vignette Posterior prediction uses the individual coefficients to compute choice probabilities for each decider in the fit. The vignette Bayesian model evaluation shows how to decide whether random coefficients improve a model.