The math behind choicer: mixed logit
Source:vignettes/articles/mixed_logit_math.Rmd
mixed_logit_math.RmdThis document provides a detailed mathematical description of the
mixed logit (random coefficients logit) model as implemented in
src/mxlogit.cpp.
Table of Contents
- Notation
- Model Definition
- Log-Likelihood Function
- Gradient Computation
- Hessian Computation
- Implementation Details
- Elasticity Computation
- Diversion Ratios
- BLP Contraction Mapping
- Choice-Based Sampling and WESML Weighting
- Willingness to Pay
- Consumer Surplus and the Simulated Logsum
- Goodness of Fit
- Conditional (Individual-Level) Tastes
Notation
| Symbol | Description |
|---|---|
| Index for choice situations | |
| Index for alternatives available to individual | |
| Index for simulation draws | |
| The alternative chosen by individual | |
| Weight for individual | |
| Row vector of covariates with fixed coefficients () | |
| Row vector of covariates with random coefficients () | |
| Fixed coefficient vector () | |
| Mean parameters for random coefficients () | |
| Lower-triangular Cholesky factor () | |
| Covariance matrix of the latent Gaussian deviations (and of realized coefficients only in the all-normal case) | |
| Alternative-specific constant (ASC) for alternative | |
| Standard-normal integration draw for choice situation and draw () | |
| Latent Gaussian random-coefficient deviation | |
Index for decision makers (respondents) when person_col
groups situations into a panel;
is the number of decision makers |
|
| Index for the choice situations belonging to decision maker ; is their count | |
| Posterior weight of draw given decision maker ’s observed choices ($$2.6) |
Sections 2–12 are written per choice situation
,
the default likelihood unit. §1.6 introduces the decision maker
as an optional coarser unit; §2.6, §3.5 and §4.5 give the corresponding
panel likelihood, score and Hessian. Without person_col,
and
for every
,
so
and
index the same choice situation and every panel formula collapses to its
§2–4 counterpart.
1. Model Definition
1.1 Utility Specification
The utility that individual derives from alternative under simulation draw is:
where: - captures the systematic utility from fixed coefficients - captures the mean contribution of random coefficients - captures the individual-specific random deviation - is the alternative-specific constant (the first inside ASC is normalized without an outside option; with an outside option all inside ASCs are free)
1.2 Random Coefficient Structure
Random coefficients are generated through a Cholesky decomposition structure:
This implies:
For finite parameter values the exponentiated diagonal makes nonsingular, so is positive definite (positive semidefinite in the limiting sense as a diagonal element tends to zero). Here is the covariance of the latent Gaussian deviation . It is the covariance of the realized coefficient vector only when all entries are normal. After any componentwise log-normal transformation, the realized coefficient covariance is a nonlinear function of and is not .
1.3 Distribution Transformations
The implementation supports two distributions for each random coefficient :
Normal Distribution (rc_dist[k] = 0):
Log-Normal Distribution
(rc_dist[k] = 1):
Note: This is a shifted log-normal parameterization: , which differs from the textbook parameterization .
When rc_mean = FALSE,
for every distribution. Thus a normal random coefficient is centered at
zero, while a log-normal random coefficient is
and has median one. Correlation is introduced on the latent-normal scale
by the rows of
;
componentwise transformations then induce a generally non-Gaussian joint
coefficient distribution.
1.4 Cholesky Parameterization
For a full covariance matrix (rc_correlation = TRUE),
the lower-triangular matrix
is parameterized as:
where: - Diagonal elements: (ensures positivity) - Off-diagonal elements: for (unconstrained)
For diagonal-only covariance
(rc_correlation = FALSE):
The parameter vector for has length: - Full: - Diagonal:
1.5 Scaling the Optimizer’s Coordinates
Motivation. scale_vars changes how the
optimizer moves, never what it maximizes. L-BFGS starts each iteration
from a single scalar multiple of the identity as its curvature, so it is
slow, and can fail, when the log-likelihood’s curvature differs by
orders of magnitude across parameters. Two things spread it. Units: a
price in thousands next to a 0/1 dummy gives their coefficients
curvatures far apart. The model: the constant of a small alternative (a
school with few applicants, a rarely chosen hospital) has far less
curvature than a large one’s, and the Cholesky entries of
vary as well. Column scales reach the first; only the curvature itself
reaches the second.
A change of coordinates. The kernels evaluate the model at , on the data as supplied. The optimizer works on , with
The objective in its coordinates is
,
with gradient
:
the map is affine and diagonal,
,
so the chain rule has no second-order term and the Hessian in
is
.
The maximizer is the same:
.
Start values and bounds are given in natural units and mapped by
,
which keeps their order. scale_vars = "bhhh" is the default
of run_mxlogit() and run_mnlogit();
"none" bypasses the map entirely.
Column scales ("sd", "mad",
"iqr"). Let
and
be the columns’ scales. The map
| Parameter | ||
|---|---|---|
| 0 | ||
| (normal RC) | 0 | |
| (Cholesky diagonal, stored as ) | 1 | |
| (Cholesky off-diagonal, ) | 0 | |
| 1 | 0 |
makes
the log-likelihood of the design with every column divided by its scale:
,
and
implies
,
row
of
(on the log diagonal, an additive shift; the scale is keyed on the row
,
not the column
).
The data are never divided: the optimizer sees the scaled problem
through the map. "sd" uses the sample standard deviation;
"mad" (stats::mad,
)
and "iqr" (stats::IQR(x) / 1.349) are
SD-equivalent under normality and preferable when outliers dominate a
column’s SD.
Why centering is omitted. Without an outside option, subtracting a column’s mean from every alternative shifts every utility in a situation by the same amount, which cancels from the softmax; the same argument applies draw by draw to . With an implicit outside option fixed at utility zero, an inside-only shift does not cancel against the outside: a fixed- shift could be offset only by shifting every inside ASC, and a random- shift cannot generally be absorbed by fixed ASCs. So the column-scale map corresponds to dividing the data by their scales, never to centering them.
Log-normal coefficients. For a log-normal coefficient
(,
rc_dist[k] == 1), dividing the column by
has no closed form in
,
because the two exponentials cannot share a scale factor; the
column-scale map leaves those parameters at
,
.
BHHH scales ("bhhh"). With
the BHHH (outer product of gradients) matrix at the start values
,
from the unit scores
and the fit’s weights,
for every parameter, the constants, the Cholesky entries and
log-normal coefficients included. The diagonal of
is then within a factor of two of one, for entries neither floored nor
clamped: insofar as
tracks the curvature, the optimizer starts from curvatures near one in
those coordinates. One extra gradient pass returns
,
accumulated beside the gradient (the full matrix is never formed). A
that is not finite, or below
times the median of the finite positive ones, carries no information at
and takes the median’s scale; scales are clamped to
.
Powers of two make the map exact:
round-trips without rounding, bounds included. The scales are fixed for
the whole run, because L-BFGS’s line search and stored curvature pairs
assume one fixed function of
.
A start near the estimate (theta_init) gives
closer to the information at the optimum. The scales fix the optimizer’s
metric, not a start that is wrong in a column’s units; the default start
is set in the columns’ units (below).
The default start. With
theta_init = NULL the start is zero for
,
,
the off-diagonal
and the constants, and on the diagonal
for a normal random coefficient, where
is the typical step of column
:
the distance between its values for a column with two (a dummy, a
two-level attribute), the mean of
over the other rows when one value
fills more than half of the column (a mostly-zero column), and its
standard deviation otherwise. At the start, a step of
in
moves the utility by a random amount with standard deviation one (the
logit error’s is
).
For a dummy with a share
of ones the standard deviation,
,
would understate the step its coefficient multiplies, which is 1; with
one of
alternatives marked per situation,
,
it would start
about
times higher. For a column far from zero with an implicit outside
option, the contrast with the outside good is the level
itself, so a root mean square might seem the step; on a census-style
design with a price of mean 100 and standard deviation 5 it put the
start at the true heterogeneity and took 169 evaluations, against 104
from the standard deviation (the level errs high, below). A column with
no variation (a step below
of its level) or a step that is not finite starts at
,
and steps are clamped to
.
A default start outside lower/upper is moved
to the nearest bound, with a message.
Rescaling the column,
,
gives the same model with row
of
(and
,
with rc_mean = TRUE) divided by
,
its
shifted by
;
is multiplied by
,
so this start transforms with it. So does the map of "sd"
(and of "mad" and "iqr"), whose shift
puts
at
,
a constant that does not depend on the units
(
under "sd" for a column whose step is its standard
deviation): in exact arithmetic the optimizer’s problem is then the same
in any units. Under "bhhh", a parameter divided by
has its score multiplied by
,
so
is multiplied by
and
(before rounding, for entries neither floored nor clamped) divided by
:
is unchanged up to the power-of-two rounding.
,
shifted by
,
keeps its score and its scale, so
is translated by
,
which L-BFGS’s steps do not see; only nloptr’s relative step test
(xtol_rel,
by default) does.
From
in each column’s own units (the 0.2.x default), no choice of
scale_vars reached the maximum of a census-style design
with columns rescaled by
or
in 2,000 likelihood evaluations; from this start "bhhh"
reaches it in 54, against 56 on the original units. The level, one, errs
high on purpose.
is fixed at the start, and while
is small the simulated likelihood depends on it only at second order
(the draws have mean zero, or nearly): the score in
is
,
so
is
and
at the start, while the curvature in
grows like
as the optimizer raises it. A start with too little heterogeneity
therefore yields a scale too generous for where the optimizer goes: from
one design reported convergence 554 log-likelihood units short.
A log-normal coefficient starts at : there is the standard deviation of the logarithm of the coefficient’s random part, which has no units. That random part, , has median one in any units, so no start makes a log-normal column’s units irrelevant: its covariate belongs in units where the coefficient is of order one.
Variance. The variance is computed at , in natural units, as for an unscaled fit: with the information (the negated Hessian of the log-likelihood), the BHHH variance at , or the sandwich of §9.3 (with the meat, its weights squared). A scaled fit’s natural information can be ill-conditioned for the reason it was scaled, so the matrix being inverted (, or the BHHH matrix) is equilibrated:
and for the sandwich
,
with
from
.
The products with
are exact (powers of two), so only the inversion rounds (for the
sandwich, also the products with the meat and their symmetrization), on
a matrix whose diagonal is within a factor of two of one in magnitude.
On one test design the natural bread had condition number
and the equilibrated one
;
inverted raw, the robust variance was off by
relative, equilibrated by
.
The magnitude of the diagonal is used so that an information that is not
positive definite away from the optimum is equilibrated too, and its
negative variances reported. Post-hoc vcov(fit, type = )
uses the same route; an unscaled fit keeps the raw inversion.
The other models. run_mnlogit() and
run_nestlogit() take the same map: column scales reach the
coefficients only
(
on
;
the constants and the nest parameters keep
),
and "bhhh" reaches every parameter, the nest parameters
included, whose lower bound is mapped with the start values.
run_nestlogit() keeps "none" as its default:
the BHHH scales are fixed at
,
and nest parameters that end far from their start can leave them
misleading.
1.6 Likelihood Unit: Cross-Section or Panel
By default
indexes a choice situation, and the likelihood
integrates a separate coefficient distribution for each situation — the
right model when tastes are redrawn every situation, or each decision
maker is observed once. Passing person_col to
run_mxlogit() / prepare_mxl_data() switches
the likelihood unit from the choice situation to the
decision maker
(Revelt and Train 1998): the
choice situations belonging to
then share one taste draw
,
and the likelihood integrates their joint probability,
in place of the per-situation
used elsewhere in this
document (§2.2–2.3). id_col must still identify choice
situations uniquely across the data set — situation ids do not restart
within person. person_col = NULL (the default) makes every
situation its own decision maker,
,
and recovers the cross-sectional likelihood exactly, so every formula in
§2–§9 written per situation
applies unchanged, read with
.
On panel data, imposing
on genuinely repeated tasks (i.e., fitting cross-sectionally) maximizes
a composite (pseudo-) likelihood: each situation’s
marginal probability is still correctly specified under
,
so the estimator stays consistent for
,
provided the per-situation marginals identify
,
but it is statistically inefficient — it never uses the fact that the
same
generated a person’s repeated choices, which is exactly the variation
that identifies
most sharply (the marginals identify it too, but only through the shape
of the mixture). Cluster-robust standard errors
(cluster_col=, or vcov(fit, type = "cluster");
§9.5) repair the inference for the resulting score dependence
but leave the likelihood unchanged; §2.6, §3.5 and §4.5 give the panel
likelihood, score and Hessian that use the repetition directly.
run_hmnlogit() is the Bayesian counterpart of the panel
likelihood, adding partially-pooled alternative effects and posterior
rather than asymptotic inference.
2. Log-Likelihood Function
2.1 Choice Probability
For a given draw , the probability that individual chooses alternative follows the logit formula:
If an outside option is included, it is normalized to .
2.2 Simulated Probability
Since the random coefficients are unobserved, we integrate over their distribution using simulation:
where is the alternative chosen by individual .
2.4 Log-Sum-Exp Trick for Numerical Stability
To avoid numerical overflow/underflow, the implementation uses:
Probability computation: Before computing , subtract :
-
Log-probability averaging: Use log-sum-exp to compute :
Applied iteratively across draws . With
person_col(§2.6), each draw’s log-probability is first summed across a decision maker’s situations, , and only then does log-sum-exp run over draws to obtain . Log-probability of the chosen alternative: each term of is formed directly in log space, as the chosen utility minus the log-sum-exp of the choice set, never as the logarithm of , which turns subnormal beyond a utility gap of about 708 and underflows to zero beyond about 745 (the multinomial logit kernels use the same form). Neither a single choice probability nor a product of many is ever formed outside log space, so and are finite, and exact, whenever the utilities are finite, however poorly the parameter vector fits the observed choices.
2.5 Statistical Properties of the Finite-Draw Criterion
For independent pseudo-random draws, is an unbiased simulator of the exact mixed-logit probability. Its logarithm is not: Jensen’s inequality gives when the simulator is nondegenerate, and a second-order expansion gives the familiar criterion bias. Under the regularity conditions in Hajivassiliou and Ruud (1994), Lee (1995), Gouriéroux and Monfort (1996), and Train (2009, Ch. 10), the classical pseudo-random MSL benchmarks are:
- Fixed : MSL is inconsistent — the simulation bias does not vanish as .
- with (at any rate): MSL is consistent.
- rising faster than : MSL is asymptotically equivalent to exact maximum likelihood — simulation bias is and simulation variance is negligible under the maintained draw scheme.
choicer uses a Halton sequence rather than independent
pseudo-random draws (§5.4), so the first sentence above is not a literal
property of its deterministic store-mode simulator, and the
pseudo-random rate conditions should not be quoted as an exact theorem
for this implementation. Quasi-Monte Carlo often reduces integration
error markedly at a given
(Bhat 2003), but its error depends on integrand smoothness, dimension,
sequence construction, and randomization. The operational implication is
to treat
and, for generated scrambled draws, the seed as part of the numerical
specification; increase
until estimates, the Cholesky block, the objective, and standard errors
stabilize. mc_asymptotics() supplies Monte Carlo
diagnostics rather than a proof that a particular
finite-
fit satisfies exact-ML asymptotics.
2.6 Panel Likelihood
With person_col, §1.6 replaces the per-situation
likelihood by the per-decision-maker one. Writing
for the log-probability of decision maker
’s
choices at draw
— a sum, over the situations sharing that draw, of the situation-level
log-probabilities of §2.1 — the simulated log-likelihood is
with
a decision-maker weight, constant across
’s
situations — a requirement of this panel objective, not of §9.1–§9.2’s
WESML weights, which vary with the alternative a person chose and can
therefore differ across their situations; WESML is accordingly not
supported with person_col. Define the posterior
draw weight
the simulated posterior probability that draw is decision maker ’s taste, given the choices they made — computed via the max-shifted log-sum-exp of §2.4, so it never forms the product of many situation probabilities outside log space. §3.5 and §4.5 write the panel score and Hessian in terms of . Setting collapses to and to , the per-draw share already implicit in §2.3’s , so the panel and cross-sectional log-likelihoods agree termwise.
3. Gradient Computation
3.1 General Formula
Define for draw the log-probability of the chosen alternative:
The gradient of this with respect to utility is:
3.2 Per-Draw Gradient
Define as the gradient of utility with respect to parameters. The per-draw gradient is:
Vectorized implementation. Define the logit residual vector with elements . The per-block gradient components collapse to matrix-vector products — computed once per draw rather than looped over alternatives:
| Block | Scalar sum | Vectorized form |
|---|---|---|
where
(Wt_diff in the code) is computed once and shared across
the
and
blocks. The
block uses an irregular alt-index scatter and is kept as a loop.
Code reference: src/mxlogit.cpp
3.3 Gradient of Simulated Log-Likelihood
Using the identity for the derivative of a logarithm of a sum:
The full gradient is:
Code reference: src/mxlogit.cpp
3.4 Utility Derivatives by Parameter Block
3.4.2 Random Coefficient Means ()
For Normal distribution:
For Log-Normal distribution:
Both cases are captured by dmu_final_dmu(p)
(
for normal,
for log-normal). Summing over alternatives:
.
Code reference: src/mxlogit.cpp
3.4.3 Cholesky Parameters ()
Let denote the parameter for (where ).
Step 1: Derivative of with respect to :
Step 2: Derivative of with respect to :
Note: , so only the term with contributes.
Step 3: Derivative of transformed :
For Normal distribution:
For Log-Normal distribution:
Step 4: Chain rule for utility derivative:
Step 5: Summing over alternatives using :
For the diagonal case, all
parameters are computed simultaneously as
Wt_diff % dgamma_final_dgamma % L.diag() % eta_i_s. For the
full correlated case,
Wt_p = Wt_diff(p) * dgamma_final_dgamma(p) is factored out
per row
.
Code reference: src/mxlogit.cpp
3.4.4 Alternative-Specific Constants ()
where (normalized) if no outside option, or all for are free if outside option is included. The sum over alternatives is a simple scatter (one non-zero contribution per alternative), implemented as a loop.
Code reference: src/mxlogit.cpp
3.5 Panel Score and Posterior Draw Weights
With person_col, the per-draw score of §3.2 is first
summed across a decision maker’s situations,
(each
the situation-level score of §3.2–§3.4), and then averaged over draws
with the posterior weight of §2.6 in place of the per-draw probability
share of §3.3:
This is §3.3’s with replaced by and replaced by the situation-summed ; recovers §3.3 termwise. The block decomposition of §3.4 (the // BLAS collapse via , and the scatter) is unchanged within a situation.
Implementation. The double sum over situations and
draws needs no explicit loop over
:
mxl_unit_simulate() already stacks the logit residual
of every row of every situation belonging to unit
into one
matrix DiffW
(
= stacked alternative rows across all of
’s
situations), because
is a single per-draw weight shared by every situation in the unit.
then gives the
,
and
blocks directly, exactly as §3.2’s vectorized table builds them from
— the situation sum is absorbed into the row-stacking, and the draw sum
into the matrix-vector product, in a single pass.
DiffW is formed
draws at a time,
,
with
unless the two
matrices (DiffW and
)
would exceed a small per-thread budget (4 MiB, sized to stay in cache):
a unit is split into batches (of sizes differing by at most one draw)
only when
,
i.e. for a long panel, a choice set of hundreds of alternatives or a
very large
,
and a thread’s memory then grows with
rather than
.
Each batch is folded in before the next one overwrites it.
accumulates by a streaming max shift: the batch’s weights
are taken relative to the largest
seen so far. The running sum is rescaled by
whenever
rises, then divided by
at the end, the same denominator used to normalize
.
Keeping the maximum separate from this denominator avoids losing
normalization accuracy through subtraction of a large-magnitude
log-sum-exp. Batching changes
only by rounding (about one rounding per batch), and a unit in one batch
computes exactly the unbatched values. The
block cannot collapse through
alone: its per-draw derivative couples the residuals with that draw’s
(§3.4.3), so the draw sum has to happen after that coupling;
each batch writes its columns of
(,
small whatever
is), and mxl_unit_score_cont() then forms
(;
a situation-stacked dgemm for the inner product, then a
draw-block dgemm against
),
and reads
’s
score off
.
(The Hessian, §4.5, needs an explicit per-situation loop because its
outer-product terms cannot be pre-summed across situations before being
squared.)
Code reference: mxl_unit_simulate(),
mxl_unit_score_cont() (the continuous block, with the
collapse), mxl_unit_score() and
mxl_unit_score_local() (the delta block, over every
parameter or the unit’s own, §4.4.6) in src/mxlogit.cpp,
shared by mxl_loglik_gradient_parallel,
mxl_scores_parallel, mxl_bhhh_parallel and
mxl_cluster_meat_parallel.
4. Hessian Computation
4.1 General Formula
Define: - — the gradient for individual - — the per-draw gradient - — the per-draw Hessian
The Hessian of the simulated log-probability for individual is:
The full Hessian is:
Code reference: src/mxlogit.cpp
4.2 Per-Draw Hessian
The per-draw Hessian has the form:
where is the Hessian of utility.
In the code, this is computed as: - sum_Pzz
- sum_Pz
- sum_diff_H_V
Then:
Code reference: src/mxlogit.cpp
4.3 Second Derivatives of Utility
Most second derivatives are zero since utility is linear in most parameters. Non-zero second derivatives arise from:
4.3.1 Mean Parameters () — Log-Normal Only
For log-normal distribution:
Code reference: src/mxlogit.cpp
4.3.2 Cholesky Diagonal Parameters ()
Since :
For normal distribution:
For log-normal distribution (combining chain rule):
where .
Code reference: src/mxlogit.cpp
4.3.3 Cholesky Cross-Derivatives Within a Row
Let with and define
Distinct Cholesky parameters affect the same transformed coefficient only when they belong to the same row . Their cross derivative is
where for a normal coefficient and for a log-normal coefficient. Hence the cross derivative is zero for normal coefficients and equals for log-normal coefficients. This expression also covers a diagonal/off-diagonal pair (say ); restricting both indices to be off-diagonal would omit terms that the C++ Hessian includes. Cross derivatives across different rows of are zero. The separate shifted term implies no – cross derivative.
Code reference: src/mxlogit.cpp
4.4 Implementation Tricks in mxl_hessian_parallel
The subsections below describe the computational restructuring used
by mxl_hessian_parallel. They cover (i) a block
decomposition of the score vector and Hessian terms, (ii) buffer
accumulation that eliminates the per-draw dense matrix allocation, (iii)
BLAS-3 batching of two outer-product sums, (iv) the symmetry strategy
used for final assembly, and (v) the compact block of the parameters a
unit’s likelihood involves, in which (i)–(iv) run, and how the blocks
are accumulated into the result.
A reader who has never seen the C++ can understand what the function does and why the result is numerically equivalent to the per-draw naive form (§4.1–4.2) from these sections alone.
4.4.1 Parameter Block Layout and Score Vector Decomposition
The parameter vector is partitioned into a continuous block of length and a delta block (ASCs) of length :
The boundary index
is stored as idx_delta_start in the code.
The per-draw score vector inherits this partition: . The continuous sub-vector is
where is the continuous-block utility gradient for alternative at draw . The delta sub-vector scatters logit residuals into the ASC index for each alternative.
Define also: - (the cc weighted-score sum, plus a -length delta counterpart ), - (, symmetric), and its cd () and dd (diagonal -vector) sub-blocks.
4.4.2 Block Buffer Accumulation — Identity C
Two of the three terms in (§4.2) are linear in the draw weight :
Their draw-weighted sum can therefore be accumulated directly into per-individual buffer matrices without constructing the full dense matrix at each draw:
Each buffer is updated at the end of draw
with a single scalar-times-matrix addition
(buf += P_s * block_s), replacing the original path that
assembled the full
and then computed
hess_term1_numerator += P_s * (g_is * g_isᵀ + H_is).
The block structure of buf_Pzz minimizes memory by
storing only the three structurally distinct sub-blocks:
| Buffer | Shape | Content | Notes |
|---|---|---|---|
buf_Pzz_cc |
Symmetric | ||
buf_Pzz_cd |
Rectangular; not symmetric | ||
buf_Pzz_dd |
-vector | Diagonal only | |
buf_diff_HV_cc |
Symmetric; cc block only ( has no delta rows) |
All four buffers are thread-private scratch, zeroed at the start of each individual . In the code the cd and dd buffers hold only the constants that ’s likelihood involves (§4.4.6), so they grow only to the largest such block their thread meets.
4.4.3 BLAS-3 Batching of Outer-Product Sums — Identities A and B
The third term in — — is not linear in , so it cannot go into a linear buffer. Instead, together with the OPG term , it is handled by reformulating both weighted outer-product sums as a single BLAS level-3 matrix multiply.
Identity A (OPG batching). With the normalized draw weights , , and the weighted mean score , define the matrix with columns
Then
Proof.
,
because
.
The identity requires only
,
which holds because
is a softmax probability (guaranteed by stable_softmax). It
folds the OPG term and the final
of the Hessian (§4.4.5) into one product, and the centered form is also
the numerically stable one. The implementation normalizes max-shifted
exponentials directly, then subtracts the highest-weight draw’s score
before computing the weighted mean of the deviations. This prevents a
large common score from amplifying rounding in the weights into spurious
covariance.
Identity B (sum-Pz outer-product batching). Define the matrix with columns
Then
by the same expansion without the centering. The linear buffers of §4.4.2 are likewise accumulated with in place of , which divides them by once and for all.
Combined BLAS-3 form. Concatenating and into and forming one matrix product gives
because
and the block multiply sums over
columns with no
-
cross term. This is the form implemented in
mxl_hessian_parallel: G_stash and
F_stash are filled column-by-column during the
-loop,
and opg_pz accumulates
and
as two separate
rank-
BLAS level-3 updates rather than one join_rows product
(§4.5 explains why: with person_col,
is squared and added once per situation while
accumulates across situations and is squared once per unit; the two
coincide here because §4.4 is the single-situation,
case). The result is
in cache-friendly level-3 kernels.
Floating-point precision. Changing the summation
order (per-draw increments vs. one level-3 multiply) is a reassociation
of additions that is exact in real arithmetic. In floating-point the
results differ only by rounding. Across 10 equivalence scenarios
sweeping all combinations of rc_dist,
rc_correlation, and rc_mean, the worst-case
element-wise deviation from the per-draw form was
,
several orders of magnitude below the
tolerance enforced by the package’s equivalence tests.
4.4.4 Optional Per-Draw Batching of sum_Pzz_cc —
Identity D
Within each draw
,
the cc sub-block of sum_Pzz accumulates per-alternative
rank-1 updates:
This can equivalently be expressed as a single matrix product:
where
and
counts the active (non-outside-option) alternatives. One
dsyrk call per draw over
columns would replace
individual dsyr rank-1 updates. The identity holds by the
same
argument as Identities A and B.
Implementation choice. The implementation retains
the per-alternative rank-1 accumulation
(sum_Pzz_cc += P_a * (zc_a * zc_a.t())). Both forms are
correct; the result flows into buf_Pzz_cc via the Identity
C scalar-times-matrix step at the end of each draw. Identity D is
recorded here to establish that the batched form is available without
changing any result; the per-alternative form is kept for
auditability.
4.4.5 Final Assembly and Symmetry
After the
-loop
for individual
,
let
, with the weights of §4.4.3. The
Hessian
is assembled
block-by-block into hess_t1 (the code builds it in place in
opg_pz, reading each block before overwriting it):
The cd block is rectangular
()
and not symmetric, so both the cd and dc sub-matrices are written
explicitly into hess_t1. The normalized draw weight
replaces a separate division by
,
and the centered
of Identity A already carries
,
matching §4.5’s Louis-identity assembly:
Symmetry strategy.
is exactly symmetric in floating point, not only in exact arithmetic:
and
are symmetric products, buf_Pzz_cc and
buf_diff_HV_cc are accumulated as full symmetric matrices
(the per-alternative loop writes both
and
for off-diagonal
-
entries in sum_diff_H_V_cc), and the dc block is written as
the transpose of the cd block. So only the upper triangle of
is added into the result, which is mirrored once after the parallel
region (§4.4.6); the mirror is the sum the lower triangle would have
accumulated.
4.4.6 Compact Blocks and Accumulation
A likelihood unit’s score and Hessian are zero outside the continuous
block and the constants of the alternatives the unit faced (unit
:
a decision maker, or a choice situation in the cross-section, §1.6). Let
be the number of free ASCs among the alternatives in
’s
choice situations (the normalized reference has none without an outside
option, and the outside option has none), and
the number in situation
.
Everything in §4.4.1–§4.4.5 runs in
’s
compact block of
local parameters, the continuous parameters first and then
’s
free ASCs in increasing global order, so that each
-sized
piece there is restricted to the constants
’s
likelihood involves. Each situation has its own slots as well: the
per-situation, per-draw pieces (sum_Pzz_cd,
sum_Pzz_dd,
)
are formed on its
parameters and added into
’s
slots, and an alternative listed twice in a situation adds into one
slot, as the dense scatter did. The product
then costs
per unit and each
costs
per situation, instead of
:
on a school-census design with 2,620 parameters, a student’s likelihood
involves 59 of them on average.
is added into the result at its global indices, upper triangle only (§4.4.5). With one thread the blocks are added into the result itself. With several, each thread adds into its own packed upper triangle while the threads’ triangles, doubles each, fit in 2 GiB together (up to about 7,000 parameters at eleven threads), and the triangles are added up in thread order. Above that the threads share the result: each keeps private only the rows of the continuous parameters, which every unit touches, and adds the ASC block atomically. The two differ only in the order of the additions. Per-thread triangles are kept where they fit because they need no atomic additions: at census scale with eleven threads, the clustered meat of 200,000 students in 2,000 clusters (§9.5) took 1.37 s with them and 1.82 s with a shared result, and the BHHH matrix of 200,000 students 1.23 s and 1.32 s; the Hessian took the same time either way, and only with two draws, where accumulation dominates, was the shared result faster (the BHHH matrix in 0.47 s against 0.52 s). The BHHH kernel’s rank-one updates (§4.1’s score) use the same blocks and accumulators, and the cluster meat’s update with each cluster’s summed weighted scores (§9.5) the union of its units’ blocks.
Non-finite terms spread as the dense products spread them: a
non-finite row of
or of the centered
makes the rows and columns of the parameters concerned NaN
in the result
(),
and a non-finite weight makes every entry outside the unit’s block
NaN
(;
NA for a missing weight), or, with several such units,
every entry outside the intersection of their blocks.
4.5 Panel Hessian: the Louis (1982) Identity and Its Two-Pass Assembly
With person_col, §4.1’s per-situation Hessian
generalizes to decision maker
by the Louis (1982) missing-information identity, treating the draw
as missing data and
’s
choices as the observed data:
with as in §3.5 and the situation-summed per-draw Hessian of §4.2. Because is already normalized (, §2.6), this is directly, with no separate division step — the same normalized form as §4.4.5’s (defined there). Setting makes §4.4.5’s formula the special case of the one above, term by term.
Two passes per decision maker. Unlike the score
(§3.5), the Hessian cannot be read off the situation-stacked residual
matrix alone: its outer-product terms must be squared within a
situation before being summed across situations, since summing residuals
across situations first and squaring once would cross-multiply different
situations’
vectors — an identity the algebra does not license.
mxl_hessian_parallel therefore runs two passes per unit,
sharing the same draw loop that computes
(§2.6):
-
Pass 1 is
mxl_unit_simulate()without residuals: it yields (the per-draw log-likelihoods of the unit’s choices), their log-sum-exp over draws, and — exactly as in §3.5. -
Pass 2 loops situations
outer, draws
inner, repeating §4.4’s per-alternative block accumulation with
everywhere §4.4 used
.
A unit whose draws pass 1 had to split into batches
()
does not keep the
matrix
:
each situation’s
rows of it are formed in turn, so a thread’s memory grows with the
largest choice set rather than with the unit:
-
Identity C (linear pieces):
buf_Pzz_cc/cd/ddandbuf_diff_HV_ccaccumulate and at every pair in the unit — linear accumulation does not care whether it is summed within or across situations. -
Identity B (
F_stash, the term): column is overwritten each situation with , and , formed on the situation’s slots (§4.4.6), is added intoopg_pzonce per situation, immediately after that situation’s -loop — situations’ outer products are added, never combined before squaring: . -
Identity A (
G_stash, the score outer product): column instead accumulates across every situation in the unit, and only after the situation loop ends is it centered at , scaled by and squared once: with — the situation sum must happen before squaring here, the opposite order from Identity B, because it is the person’s total score that enters the outer product, not each situation’s on its own. Since the sum to one, this is the Louis identity’s , computed without its cancellation. In code,g_barfirst holds a highest-weight draw’s score, which is subtracted from every column; it then holds the weighted mean of those deviations, subtracted in turn. This is equivalent to centering at while avoiding amplification of weight-rounding error by a large common score.
-
Identity C (linear pieces):
Final assembly mirrors §4.4.5’s block-by-block combination
(hess_t1 = opg_pz - buf_Pzz + buf_diff_HV, cc/cd/dc/dd),
whose centered Identity A already carries the Louis identity’s
,
and closes by adding the upper triangle of
hess_t1
into the result (§4.4.6) — no
step, because
is already a normalized weight (§2.6), as in §4.4.5. Setting
collapses pass 2 to a single situation per unit: F_stash
(holding
)
and G_stash (holding
)
remain different vectors, but with only one situation to loop over, F’s
per-situation overwrite and G’s cross-situation
accumulate produce the same result, and the two-pass structure
reduces exactly to §4.4’s single-situation accumulation.
The non-finite-likelihood guard. Pass 1 skips a unit
only when its log-sum-exp is non-finite,
!std::isfinite(lse). Because every
is formed in log space (§2.4), lse is finite whenever the
unit’s utilities are, however poorly the unit is fit; the guard fires
only for utilities that overflow, where the unit’s log-likelihood is
undefined. It replaces the former
threshold of §5.4, which could also discard a unit that was merely very
poorly fit under the current parameter vector. See §5.4 and
NEWS.md.
Code reference: mxl_hessian_parallel() in src/mxlogit.cpp.
5. Implementation Details
5.1 Parameter Vector Structure
The full parameter vector is organized as:
| Block | Indices | Length | Description |
|---|---|---|---|
| Fixed coefficients | |||
(if rc_mean=TRUE) |
Random coefficient means | ||
| next block | or | Cholesky parameters | |
| remaining | , , or | Free ASCs |
The ASC length is zero when use_asc = FALSE,
without an outside option, and
with an outside option. The low-level parser requires at least one
fixed-coefficient column
(),
even when the substantive focus is entirely on random coefficients.
5.2 Delta Method for Variance Parameters
When reporting results, the Cholesky parameters are transformed to variance matrix elements .
The Jacobian
is computed analytically in jacobian_vech_Sigma().
For element :
This follows from the product rule applied to : , where is the matrix with a single non-zero entry .
The variance-covariance matrix of is then:
Code reference: src/mxlogit.cpp
5.3 OpenMP Parallelization
The implementation parallelizes over likelihood units (§1.6) using
OpenMP, with dynamic scheduling for load balancing
(#pragma omp for schedule(dynamic)): - The log-likelihood
and gradient keep thread-local accumulators, combined in a
#pragma omp critical section. - The Hessian and BHHH
kernels add each unit’s compact block, and the cluster meat each
cluster’s, into per-thread packed upper triangles, added up in thread
order after the parallel region, or into a shared result when those
would exceed 2 GiB (§4.4.6). - Before allocating, each estimation kernel
compares an upper estimate of its working memory, per thread and shared,
with the machine’s physical memory (or
options(choicer.max_memory)), and stops with an error
naming the number of threads that would fit instead of running out of
memory inside the parallel region.
5.4 Practical Notes
Negated Objectives:
mxl_loglik_gradient_parallelreturns and ;mxl_hessian_parallelreturns . The BHHH kernel instead returns the positive-semidefinite information approximation .ASC Identification: When
include_outside_option = FALSE, the first inside alternative’s ASC () is fixed to zero for identification. Wheninclude_outside_option = TRUE, all inside ASCs are free parameters (the outside option with serves as the reference).Gradient vectorization: The per-draw gradient is computed without a per-alternative loop. After forming , the and / blocks are evaluated with two BLAS dgemv calls ( and ). The delta block uses an irregular scatter and remains a loop.
Exact draw layout and source: In both modes, likelihood unit (§1.6: a decision maker under
person_col, a choice situation otherwise) receives the contiguous global indices for ; dimension uses the th prime base and the resulting uniform is mapped through the standard-normal inverse CDF. Indraws = "store",randtoolbox::halton(n = U*S, dim = K_w, normal = TRUE)is materialized as a cube during estimation, one block per unit ( decision makers underperson_col, or choice situations otherwise). It is filled a block of units at a time withhalton(start = ), which returns exactly those points. Store mode needs , the largest starting indexhalton()accepts. The prediction family (predict(),elasticities(),diversion_ratios(),blp(),logsum(),consumer_surplus(), andgof()throughpredict()) integrates over one block per choice situation instead ( blocks for a panel fit, not ), which it forms on the fly from the same points of the sequence (§5.5), with no limit on for up to 128 random coefficients. The fitted object retains onlydraws_infometadata, not the cube; the estimation-type functions (vcov(type = ),wesml_vcov(),conditional_tastes()) rebuild it. This mode is deterministic and has no seed or scramble. Indraws = "generate",HaltonGencomputes the radical inverse on demand.scramble = "none"uses identity permutations and is tested against therandtoolboxsequence.scramble = "permuted"(the default) constructs one independently seeded Fisher–Yates permutation for every (dimension, digit-position) pair and shares that table across sequence indices. Evaluation stops after the last nonzero base- digit, so trailing zero digits remain unchanged. This is not Owen nested-uniform scrambling: the digit permutation does not depend on the preceding digit prefix, and the construction does not make each point marginally uniform. Consequently it carries no standard RQMC unbiasedness, variance-rate, or replicate-based error-estimation guarantee. The historical API value"owen"is a deprecated alias for"permuted"; old fitted objects carrying that serialized label remain readable. The result is deterministic conditional on the seed and independent of OpenMP scheduling. Numerical sensitivity should be assessed by increasing and, for permuted draws, varying the seed. Generate mode is available only through the convenience data workflow; the advancedinput_dataworkflow requires an expliciteta_drawscube.Finite-value guards: The likelihood/gradient kernel replaces a non-finite global negated objective by and a zero gradient, allowing the optimizer’s line search to backtrack; isolated non-finite gradient entries are zeroed. The Hessian contributes a likelihood unit — decision maker under
person_col, choice situation otherwise (§1.6, §4.5) — only when its log-sum-exp is finite. Since every is the chosen utility minus a log-sum-exp (§2.4), , and the objective are finite whenever the utilities are, however poorly the parameter vector fits the observed choices: the sentinel, the Hessian’s skip and theNAconditional tastes (§13) all fire only when the utilities themselves overflow (for instance an exploding Cholesky diagonal during a line search), where the log-likelihood is undefined. (Earlier releases skipped a situation whenever , which could also discard a situation that was merely very poorly fit rather than genuinely zero-probability, biasing the reported Hessian and its standard errors; and until the log-space form of §2.4, a single choice probability that underflowed to zero, at a utility gap beyond about 745, made the whole objective the sentinel with a zero gradient, while subnormal probabilities, at gaps beyond about 708, lost precision. SeeNEWS.md.) Relatedly, the posterior draw weights (§2.6) that both the score (§3.5) and the Hessian (§4.5) are built from are computed in log space via log-sum-exp rather than as a ratio of raw probabilities, so they stay finite and accurate even when the product over situations falls far below the smallest double — the earlier raw-probability construction could return an identically zero gradient at a genuinely poorly-fit point, a false stationary point for the optimizer. These are numerical failure guards, not changes to the mathematical criterion in its regular finite region.
5.5 Data Layout
- has rows; situation occupies rows (64-bit offsets in the kernels).
-
may be row-aligned with
or have one row per global inside alternative; the kernels either read
the situation’s block of rows or gather rows by the global 1-based
alt_idx. The convenience R workflow constructs the row-aligned form. -
choice_idxis a 1-based local row position within each situation block, or 0 for an implicit outside choice.alt_idxis instead the global alternative ID used to gather ASCs and alternative-level rows. - Stored draws have dimensions
,
one block per likelihood unit
(
decision makers under
person_col, choice situations otherwise). Generate-mode kernels receive an empty placeholder cube plusgen_seed,gen_scramble, andgen_S. - Prediction kernels read one block per choice situation: situation
gets points
of the Halton sequence. With stored draws the prediction-type functions
(
predict(),logsum(),elasticities(),diversion_ratios(),blp(), and through themconsumer_surplus()andgof()) never build that cube (thoughblp()may keep the draws across its iterations, §8.4): they pass an empty placeholder andgen_scramble = 2, and each thread forms its situation’s block on the fly fromHaltonGen’s identity-permutation uniforms, which arerandtoolbox::halton()’s points, mapped to normals by R’sqnorm()(R::qnorm, libR’s ownqnorm5), asget_halton_normals()maps them. The draws, and so the results, are the cube’s bit for bit (per situation at any thread count, and sums over situations at one thread; at more threads their rounding depends on the order in which threads take the situations, as it always did) whererandtoolboxand choicer are compiled with the same floating-point contraction: both add each digit’s term to the radical inverse in the same order, and agree bit for bit when both fuse those multiply-adds or neither does, as on CRAN’s platforms. Otherwise under 1% of the uniforms, all of bases 5 and up, differ in their last bit. The on-the-fly generator covers up to 128 random coefficients; beyond them the functions build the cube.
6. Elasticity Computation
6.1 Elasticity Definitions
The elasticity measures the percentage change in choice probability with respect to a percentage change in an attribute. For the mixed logit model, we compute elasticities using simulated probabilities.
Simulated Probability:
6.2 Elasticity for Fixed Coefficients (X Variables)
For a variable in the design matrix with fixed coefficient :
Own-Elasticity (elasticity of with respect to ):
Using the chain rule and averaging over draws:
Cross-Elasticity (elasticity of with respect to where ):
6.3 Elasticity for Random Coefficients (W Variables)
For a variable in the design matrix with random coefficient, the effective coefficient for individual and draw is:
where: - For normal distribution: , - For log-normal distribution: ,
Own-Elasticity:
Cross-Elasticity:
6.4 Weighted Average of Individual Elasticities
The implementation computes weighted average individual probability elasticities:
This is not generally the elasticity of an aggregate share under a common market-level perturbation. Rows index responding alternatives and columns perturbed alternatives. The kernel sets a contribution to zero when and divides the accumulated matrix by total weight over all situations; with ordinary finite utilities, the threshold is only a numerical guard.
Code reference: mxlogit.cpp:mxl_elasticities_parallel
7. Diversion Ratios
7.1 Definition
The attribute-based diversion ratio from alternative to alternative with respect to attribute is
where is the aggregate share. It is the fraction of demand lost by that is captured by when a marginal change in ’s attribute reduces .
7.2 MNL Special Case (β cancels)
For multinomial logit, the per-individual derivatives of the choice probability are
Letting , the aggregate share derivatives are
Because is a scalar constant, it pulls out of both weighted sums. Forming the ratio,
with and cancelling. The result is independent of which attribute is perturbed — this is a special property of MNL.
7.3 MXL: β Does Not Cancel
For mixed logit the realized coefficient on a random variable for individual at draw is
with the same distribution transforms as in §6.3. The derivatives evaluated at draw are
Integrating over draws gives and similarly for the cross term. Aggregating over individuals, the diversion ratio is
Because varies across , it cannot be pulled out of the sums and does not cancel. The MXL diversion ratio therefore depends on the perturbed variable. For a variable with a fixed coefficient the dependence again vanishes ( is a constant scalar); for a random-coefficient variable it does not.
7.4 Properties
Column-sum identity. For each , . This follows from , which holds inside the draw sum and is preserved through the same weight on both sides.
Sign of
.
If
is constant negative (e.g. a fixed price coefficient), both numerator
and denominator change sign together and the ratio is unchanged — DR is
non-negative. With a random coefficient of mixed sign across
,
terms can partially cancel; the aggregate ratio is still well-defined
but can be numerically sensitive. The implementation guards against this
with an abs(denominator) > 1e-15 check.
Implementation. Cross-products and must be accumulated inside the per-draw loop, multiplied by at that point, and only then averaged across draws and weighted by . Computing or -weighted averages computed outside the draw loop is biased.
Code reference: mxlogit.cpp:mxl_diversion_ratios_parallel
8. BLP Contraction Mapping
8.1 Problem Statement
Given observed market shares , find the ASC parameters such that the model-predicted shares match the observed shares:
where is the predicted market share for alternative computed using simulated probabilities.
8.2 Simulated Market Shares
The predicted market share for alternative is:
where:
and is the probability for individual , alternative , and draw , which depends on through the utility specification.
8.3 Contraction Mapping Algorithm
The Berry–Levinsohn–Pakes fixed-point iteration is:
This can be written in vector form as:
The target must be feasible and strictly positive. The kernel checks positivity of the inside target and predicted shares but does not verify that the full target sums to one.
8.4 Implementation Details
-
No outside option.
deltamay be the full length- vector or the free length- block; the latter is padded with a leading zero. The first element is subtracted initially and after every update, and the returned vector has length with its baseline entry equal to zero. -
Outside option.
deltahas length and contains inside ASCs only. The outside utility remains zero, the update uses only the inside components of target and predicted shares, and no inside ASC is re-centered. A coherent target outside share is matched only indirectly because all shares sum to one. -
Stopping. The residual is the largest absolute
inside-delta change. On reaching
max_iter, the kernel prints a warning and returns the last completed iterate.
The public blp.choicer_mxl() method resolves the fitted
draw metadata through .mxl_pred_draws(): store-mode fits
have the kernel form the plain-Halton points on the fly (§5.5), while
generate-mode fits pass the original seed, permutation mode, and
to the contraction kernel. Share inversion therefore draws from the same
deterministic Halton sequence as the fit, one block per choice situation
(for a cross-sectional fit, the estimation draws themselves; a panel
fit’s estimation draws are per decision maker instead, §5.5). Neither
the draws nor
change across iterations, so blp() (through the internal
mxl_blp_contraction_cached()) keeps each situation’s
,
with its log-normal rows exponentiated, from the first evaluation of the
shares on, when the draws are formed on the fly (stored draws with up to
128 random coefficients, or generated ones), the
blocks take at most keep_draws_bytes
(
bytes; 4 GiB by default) and the allocation succeeds; otherwise every
evaluation forms them again. The kept blocks are the values each
evaluation would compute, so the shares, and the returned
,
are the same either way. The exported mxl_blp_contraction()
keeps nothing.
Code reference: mxlogit.cpp:mxl_blp_contraction
9. Choice-Based Sampling and WESML Weighting
9.1 Endogenous Stratified (Choice-Based) Sampling
In random (exogenous) sampling each choice situation is drawn independently of its outcome. Under choice-based (endogenous stratified) sampling the strata are defined by the chosen alternative , and situations are drawn with stratum-specific frequencies – for example, deliberately oversampling the few individuals who chose a rare alternative. Let
Maximum simulated likelihood applied naively to such a sample is generally inconsistent for the population parameters. (A known special case: in a pure multinomial logit with a full set of alternative-specific constants, choice-based sampling biases only the constants; in general – and for the mixed logit – both the slopes and the constants are affected.)
9.2 The WESML Weighted Log-Likelihood
Manski and Lerman (1977) restore consistency by weighting each situation’s contribution by the ratio of population to sample share of its chosen alternative,
which yields the weighted simulated log-likelihood already defined in $$2.3,
The maximizer is invariant to multiplying every by a common positive constant (the objective is merely rescaled), so the weights may be normalized to mean 1 without changing the estimates.
9.3 Robust (Sandwich) Variance: the Meat
WESML is a weighted M-estimator solving , where is the per-situation score ($$3) and its Hessian ($$4). Its robust (Huber–White) asymptotic variance is the sandwich
The weight enters the bread linearly, but the meat carries the weight squared: the contribution of situation to the estimating equation is , so the variance of that contribution involves .
This is exactly why the two naive variances are wrong under weighting:
- the inverse-Hessian assumes the information-matrix equality , which fails once the are non-degenerate;
- the ordinary BHHH/OPG uses the weight to the first power, not the second.
A consistency check: rescaling all weights by a constant sends and , so – the sandwich is invariant to the weight scale, whereas is not.
9.4 Scope: Robust vs. Design-Based Variance
The estimator implemented here is the robust weighted-M-estimator (Huber–White) variance, whose meat is uncentered. This is the asymptotic variance under variable-probability sampling (each population unit sampled independently with a stratum-dependent probability). Under a fixed-quota design – a prescribed number drawn from each choice stratum, i.e. standard stratified sampling – the design-based variance centers the scores within strata and may be smaller; that stratum-centered (design-based) estimator is out of scope here. See Manski & McFadden (1981) for the design-based treatment and Cosslett (1981) for the asymptotically efficient conditional-maximum-likelihood alternative.
9.5 Implementation and Cluster-Robust Extension
The variance routes never form the matrix of the units’ weight-free scores: two million decision makers by 2,620 parameters would take 42 GB. With the bread , the robust meat is the BHHH kernel with squared weights, summed over each unit’s compact block (§4.4.6),
and vcov(type = "bhhh") is the same kernel with weights
.
The matrices are combined as
,
and one set of draws, stored or generated from the fit’s draw metadata,
serves both. For clusters
,
the meat
is mxl_cluster_meat_parallel: the units are sorted by
cluster and cut into chunks of similar numbers of rows, about four per
thread; each cluster’s weighted scores are summed over the parameters
they touch, and a cluster that a chunk boundary cuts is summed in parts,
added after the parallel region, so a few large clusters still keep
every thread busy. There is no finite-cluster correction.
mxl_scores_parallel (internal; no variance route of the
mixed logit calls it) still returns
for small problems and refuses a matrix above 2 GiB. Clustering changes
inference, not the estimand of whichever likelihood was fit (§1.6):
cross-sectional by default, or the panel likelihood of §2.6 under
person_col, where cluster labels must nest decision makers.
It is only §9.1–§9.2’s WESML weighting that is defined solely for the
cross-sectional likelihood:
is a weight that varies with the alternative chosen in situation
,
whereas the panel objective of §2.6 requires one weight
per decision maker, constant across their situations, so a choice-based
weight cannot enter it — run_mxlogit(..., person_col = )
therefore rejects choice-based-sampling provenance. The sandwich
variance of §9.3 and its cluster-robust extension here in §9.5 carry no
such restriction: both are built from whichever likelihood unit’s score
was actually fit (a decision maker under person_col, a
choice situation otherwise), so they apply to panel fits too, with
cluster labels then nesting decision makers exactly as above. A scaled
fit’s variance is computed in natural units and inverted equilibrated
($$
delta method ($$5.2) applies on top, exactly as for any stored
vcov. User-facing entry points:
wesml_weights() and sample_by_choice() build
the weights; run_mxlogit(..., se_method = "sandwich")
estimates with the robust variance; wesml_vcov() returns it
post hoc.
Code reference: mxl_bhhh_parallel(),
mxl_cluster_meat_parallel() and
mxl_scores_parallel() in src/mxlogit.cpp;
compute_sandwich_vcov(),
.assemble_score_vcov(), .mxl_score_meat() and
(MNL, NL) .score_meat() in R/classes.R;
wesml_weights() / sample_by_choice() in R/sampling.R.
10. Willingness to Pay
10.1 Fixed Coefficients
When the price variable has a fixed coefficient (a column of ), the WTP for any fixed attribute is the usual marginal rate of substitution, with the same analytic delta-method SE as in the MNL case (§8 of the MNL document):
10.2 Random Coefficients
For a random attribute coefficient the WTP is itself a random variable , and a scalar summary must be chosen. The summaries reported follow the package’s parameterization (§1.3):
Normal RC with estimated mean
(rc_mean = TRUE, rc_dist[k] = 0):
has mean
,
so the mean WTP is
with the linear-ratio gradient of §10.1 applied to .
Shifted log-normal RC with estimated location
(rc_mean = TRUE, rc_dist[k] = 1):
.
Since
is monotone in the normal draw
and the median of
is
,
the population median is
and the median WTP is
The median is preferred to the mean, with , because the mean is highly sensitive to the estimated variance (a thin right tail dominates it).
Shifted log-normal RC without location
(rc_mean = FALSE, rc_dist[k] = 1):
has median exactly 1, so the median WTP is
with
— uncertainty comes solely from
.
Normal RC without mean
(rc_mean = FALSE, rc_dist[k] = 0): the mean is
0 by construction; no WTP row is reported.
10.3 Random Price Coefficients Are Rejected
The implementation requires price_var to be a
fixed-coefficient column of
and rejects every random price coefficient. For a normally distributed
denominator this is substantively important: positive density
arbitrarily close to zero generally makes
diverge. That no-moment argument does not cover every
possible mixing distribution—for example, inverse moments of a
sign-constrained log-normal can be finite—but wtp() still
does not simulate the joint ratio distribution, account for its
correlation, or report its quantiles. Thus the rejection is both a
specification guard for normal price mixing and an implementation
boundary for other distributions. WTP-space estimation (Train and Weeks
2005) is not implemented in choicer v0.2.0.
Code reference: R/wtp.R
11. Consumer Surplus and the Simulated Logsum
11.1 Expected Logsum
Conditional on a coefficient realization
,
expected maximum utility over the iid type-I extreme-value errors is
,
where
is Euler’s constant. As in the other package methods,
logsum() omits this scenario-invariant additive constant.
The mixed-logit logsum integrates over the mixing distribution:
simulated over the fit’s Halton sequence, one block per choice situation (regenerated from the stored draw metadata; for a cross-sectional fit, the estimation draws themselves; a panel fit’s estimation draws are per decision maker instead), with the outside option contributing in each draw’s sum when present, and the max-subtraction trick applied per draw.
Order of operations matters. The log-sum-exp function is convex, so by Jensen’s inequality
with strict inequality when the draws induce non-common variation in
utilities. A nondegenerate
alone is not sufficient for strictness: if the random contribution is
the same additive shift for every available alternative, log-sum-exp is
affine in that direction and equality holds. A dedicated C++ kernel
(mxl_logsum) computes the per-draw logsum and then averages
across draws.
11.2 Expected Consumer Surplus
With a fixed price coefficient (the marginal utility of income is then nonrandom):
A random price coefficient is rejected for the capability and specification reasons in §10.3; the divergence argument applies directly to normal price mixing but not to every sign-constrained distribution. As in MNL, the formula assumes no income effects. The routine does not enforce ; the economic interpretation as positive marginal utility of income requires the analyst to verify that sign. CS levels inherit the ASC normalization, so only differences across counterfactual scenarios are meaningful. The implementation reports point estimates for MXL (the delta-method SE of mean CS is currently provided for MNL only); Krinsky–Robb resampling is a practical interval method.
Code reference: R/surplus.R;
kernel in src/mxlogit.cpp
(mxl_logsum)
12. Goodness of Fit
The measures are model-agnostic and identical to §10 of the MNL document, computed from the simulated log-likelihood and simulated choice probabilities:
- McFadden pseudo R²: , adjusted , with counting all estimated parameters (, , Cholesky block, ASCs).
- Equal-shares null (default; exact for unbalanced choice sets and weights): .
- Market-shares null (constants-only closed form): , where is the common weight, valid only for identical choice-set composition and uniform weights. The constants-only model contains no random coefficients, so the MNL closed form applies unchanged.
- Hit rate: weighted share of choice situations whose observed choice has the highest simulated probability ; with an outside option, the kernel returns the simulated outside probability directly and the outside good competes for the maximum.
Code reference: R/gof.R
13. Conditional (Individual-Level) Tastes
conditional_tastes() recovers, for each likelihood unit,
the mean and standard deviation of the random coefficients
conditional on the choices observed — the same posterior draw
weights
that build the panel score and Hessian (§2.6, §3.5, §4.5), now applied
to the realized taste draws themselves rather than to the score:
with the taste at draw on the scale it enters utility (§1.1–1.3), the conditional mean above, and the sequence of choices observed across ’s situations (Revelt and Train 2000; Train 2009, ch. 11). defines the same object for the default cross-sectional fit, one choice situation at a time; a single choice reveals comparatively little, so its conditional mean sits closer to the population mean than a panel decision maker’s typically does.
Not
itself.
is the average taste among decision makers who would make
’s
choices, not
’s
own realized
— it shrinks toward the population mean, more so the fewer situations
contributes. Consequently the variance across decision makers
of the conditional means understates
:
by the law of total variance,
so the revealed share
(-weighted
variance of the conditional means) and the residual share
(-weighted
average of the conditional variances) should sum to about one. These are
numerical checks, not specification tests: for normal random
coefficients with free means, both this identity and
are the likelihood’s first-order conditions for
and
— the population mean and variance are exactly the
-weighted
mean and variance the estimating equations set to zero at
— so they hold at a converged maximum whatever the true taste
distribution. A departure points to too few draws
or an unconverged
;
when a coefficient’s mean is fixed at zero (rc_mean = FALSE
and no fixed coefficient on the same variable), a mean_cond
far from zero can also flag that restriction. For log-normal
coefficients the variance identity holds on the underlying normal scale
,
not the utility scale
reported here, so expect departures of a few percent in either direction
at moderate
.
print.choicer_tastes() reports these alongside
model_mean/model_sd (the moments of
itself, §1.2–1.3) and mean_cond/sd_cond (the
weighted mean and SD of the conditional means across units).
Implementation.
mxl_conditional_tastes_parallel() reuses
mxl_unit_load() and mxl_unit_simulate() (§3.5)
to obtain
per unit, then forms
over the unit’s
realized-deviation block
(the same
values already used to build
,
§1.1) and adds back the mean block,
;
the SD uses the analogous weighted second moment of
around
.
Both are matrix-vector products — no second pass is needed, since
neither quantity is an outer product. Units whose log-likelihood is
non-finite — possible only when their utilities overflow (§4.5, §5.4) —
return NA and are dropped from the population summary.
Code reference: mxl_conditional_tastes_parallel() in
src/mxlogit.cpp;
R/conditional_tastes.R.
References
- Train, K. E. (2009). Discrete Choice Methods with Simulation. Cambridge University Press.
- Revelt, D., & Train, K. (1998). Mixed logit with repeated choices: households’ choices of appliance efficiency level. Review of Economics and Statistics, 80(4), 647-657.
- Revelt, D., & Train, K. (2000). Customer-specific taste parameters and mixed logit: households’ choice of electricity supplier. Working Paper E00-274, Department of Economics, University of California, Berkeley.
- Louis, T. A. (1982). Finding the observed information matrix when using the EM algorithm. Journal of the Royal Statistical Society: Series B, 44(2), 226-233.
- Train, K., & Weeks, M. (2005). Discrete choice models in preference space and willingness-to-pay space. In R. Scarpa & A. Alberini (Eds.), Applications of Simulation Methods in Environmental and Resource Economics (pp. 1-16). Springer.
- McFadden, D., & Train, K. (2000). Mixed MNL models for discrete response. Journal of Applied Econometrics, 15(5), 447-470.
- Berry, S., Levinsohn, J., & Pakes, A. (1995). Automobile prices in market equilibrium. Econometrica, 63(4), 841-890.
- Bhat, C. R. (2003). Simulation estimation of mixed discrete choice models using randomized and scrambled Halton sequences. Transportation Research Part B, 37(9), 837-855.
- Gouriéroux, C., & Monfort, A. (1996). Simulation-Based Econometric Methods. Oxford University Press.
- Hajivassiliou, V. A., & Ruud, P. A. (1994). Classical estimation methods for LDV models using simulation. In R. F. Engle & D. L. McFadden (Eds.), Handbook of Econometrics, Vol. 4 (pp. 2383-2441). North-Holland.
- Lee, L.-F. (1995). Asymptotic bias in simulated maximum likelihood estimation of discrete choice models. Econometric Theory, 11(3), 437-483.
- Manski, C. F., & Lerman, S. R. (1977). The estimation of choice probabilities from choice based samples. Econometrica, 45(8), 1977-1988.
- Manski, C. F., & McFadden, D. (1981). Alternative estimators and sample designs for discrete choice analysis. In Structural Analysis of Discrete Data with Econometric Applications. MIT Press.
- Cosslett, S. R. (1981). Maximum likelihood estimator for choice-based samples. Econometrica, 49(5), 1289-1316.
- Wooldridge, J. M. (2010). Econometric Analysis of Cross Section and Panel Data (2nd ed.), Section 13.8. MIT Press.