Specification & options
Define a model with LCLSpec, then estimate it with
lcl.fit. The four focused option objects can be collected in one
Options bundle shared by every fitting entry point.
import lcl
from lcl import FitOptions, InferenceOptions, OptimizationOptions, Options
results = lcl.fit(
data,
spec,
options=Options(
fit=FitOptions(seed=42, starts=3, max_em_iter=500),
optimization=OptimizationOptions(maxiter=75, newton_decrement_tol=1e-5),
inference=InferenceOptions(covariance="clustered"),
),
)
The legacy fit_options=, optimization_options=, inference=, and
diagnostics= keywords remain available, but do not mix them with options=;
ambiguous partial merges raise an error. See API contracts and compatibility
for configuration precedence, which sections each estimator uses, and array ordering.
Use separate utility_formula and membership_formula fields for formula-based
designs. LCLSpec has frozen top-level fields and can be reused across fitting
and cross-validation. Treat its nested variable lists and mappings as read-only;
use dataclasses.replace to derive another specification.
Boundary inference options (0.1.42)
inference = InferenceOptions(
covariance="clustered", boundary="projected",
boundary_draws=2048, boundary_seed=0,
)
results = lcl.fit(data, spec, inference=inference)
summary = results.beta_summary()
| Option | Default | Contract |
|---|---|---|
boundary |
"strict" |
Require an interior estimate and full positive-definite information; "conditional" instead fixes numerically binding prices for covariance; "projected" additionally estimates boundary-aware mean/SD uncertainty in beta_summary(). |
boundary_draws |
2048 |
Integer, at least 100; Gaussian simulation draws for summaries, with no model refits. |
boundary_seed |
0 |
Nonnegative integer; repeated summary calls use a cached result. |
These modes are LCL-only. ConditionalLogit rejects "conditional" and
"projected". With skip=True, covariance remains unavailable; structural KKT
checking of binding prices still runs. The price constraint and optimizer settings
are unchanged. boundary="projected" does not authorize normal Wald intervals or
extend projected inference to prediction/WTP methods.
See the result-field contracts, runnable tutorial, and literature and assumptions.
Fitting
lcl.fit(data, spec, *, options=None, fit_options=None, optimization_options=None, inference=None, diagnostics=None, variable_labels=None, dems_data=None, progress_callback=None)
Fit a latent-class conditional-logit model from an :class:LCLSpec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
Long-format choice data. |
required |
spec
|
LCLSpec
|
Declarative model specification. |
required |
options
|
Options | None
|
Complete configuration bundle. Do not combine with individual option arguments. |
None
|
fit_options
|
FitOptions | None
|
EM algorithm options. |
None
|
optimization_options
|
OptimizationOptions | None
|
M-step optimizer options. |
None
|
inference
|
InferenceOptions | None
|
Covariance and standard-error options. |
None
|
diagnostics
|
DiagnosticsOptions | None
|
Diagnostic thresholds and switches. |
None
|
variable_labels
|
Mapping[str, str] | None
|
Optional display labels for raw model/DataFrame variable names. Labels
supplement any labels stored on |
None
|
dems_data
|
object | None
|
Separate panel-level data joined by |
None
|
progress_callback
|
callable | None
|
Receives dictionaries describing hardware, starts, EM steps, polishing, and completion. |
None
|
Returns:
| Type | Description |
|---|---|
LCLResults
|
Fitted latent-class results. |
Notes
Latent-class estimation does not accept case or survey weights. Use
:class:~lcl.conditional_logit.ConditionalLogit when weighting is required.
Source code in src/lcl/__init__.py
Model specification
lcl.LCLSpec(ids, utility=None, membership=None, classes=2, constraints=None, utility_formula=None, membership_formula=None, variable_labels=None)
dataclass
Declarative latent-class conditional-logit specification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
ChoiceIds
|
Identifier and choice columns for the long-format dataset. |
required |
utility
|
Sequence[str] | None
|
Alternative-specific variables in the utility specification. Omit when
|
None
|
membership
|
Sequence[str] | None
|
Panel-level variables for class-membership probabilities. Omit when
|
None
|
classes
|
int
|
Number of latent classes. |
2
|
constraints
|
mapping or sequence
|
Coefficient constraints. The current estimation engine supports one negative coefficient, typically a price, cost, or travel-time numeraire. |
None
|
utility_formula
|
str | None
|
Formulaic string for the alternative-specific utility specification, such
as |
None
|
membership_formula
|
str | None
|
Right-hand-side Formulaic string for the class-membership demographic
regression, such as |
None
|
variable_labels
|
Mapping[str, str] | None
|
Optional mapping from raw DataFrame/model variable names to human-readable
labels used in printed coefficient and WTP/tradeoff tables. Exact
Formulaic-expanded names may also be labeled directly; otherwise labels
for raw categorical columns are reused for terms such as
|
None
|
negative_constraint
property
Return the single negative constraint, if present.
negative_constraints
property
Return normalized negative-coefficient constraints.
numeraire
property
Return the constrained variable used as the numeraire.
numeraire_min_abs
property
Return the numeraire floor implied by the specification.
__post_init__()
Validate internal consistency.
Source code in src/lcl/spec.py
constraint_rows()
summary_lines()
Return a compact, human-readable specification summary.
Source code in src/lcl/spec.py
lcl.ChoiceIds(alt, case, panel, choice)
dataclass
Column names identifying a long-format choice dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alt
|
str
|
Alternative identifier column. |
required |
case
|
str
|
Choice-situation identifier column. |
required |
panel
|
str
|
Decision-maker or panel identifier column. |
required |
choice
|
str
|
Boolean or binary chosen-alternative indicator column. |
required |
lcl.NegativeCoefficient(variable=None, min_abs=DEFAULT_NEGATIVE_MIN_ABS, units=None, warn_below=None)
dataclass
Constrain a coefficient to be strictly negative.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variable
|
str | None
|
Name of the variable being constrained. It may be omitted when the object is supplied in a mapping keyed by variable name. |
None
|
min_abs
|
float
|
Minimum absolute magnitude of the coefficient, enforced as
|
1e-5
|
units
|
str | None
|
Optional human-readable units for summaries and audit reports. |
None
|
warn_below
|
float | None
|
Optional LCL diagnostic threshold overriding the general
|
None
|
__post_init__()
Validate constraint settings.
Source code in src/lcl/constraints.py
bind(variable)
Return a copy tied to variable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variable
|
str
|
Variable name from a specification mapping. |
required |
Returns:
| Type | Description |
|---|---|
NegativeCoefficient
|
A constraint with a concrete variable name. |
Source code in src/lcl/constraints.py
Options
lcl.options.FitOptions(seed=0, max_em_iter=2000, em_tol=1e-08, score_tol=0.0001, polish=True, polish_maxiter=25, num_devices=device_count(), check_interval=1, starts=1, start_method='panel_partition')
dataclass
Latent-class EM and multi-start settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
int
|
Base seed for the panel partition used to build starting values. |
0
|
max_em_iter
|
int
|
Maximum number of EM recursions. |
2000
|
em_tol
|
float
|
Stopping tolerance on the Aitken-extrapolated log-likelihood change per
panel. Because EM converges linearly, the raw iteration-to-iteration
change can understate the distance to the optimum. For an observed rate
|
1e-8
|
score_tol
|
float
|
Stopping tolerance on the maximum absolute component of the observed-data
score, per panel, used for the final public |
1e-4
|
polish
|
bool
|
Run safeguarded Newton steps on the observed-data log likelihood after EM, using the exact analytic score and Hessian. EM may stop before reaching a stationary point; polishing attempts to close this gap before covariance estimation. A polish step is kept only if it does not decrease the log likelihood. |
True
|
polish_maxiter
|
int
|
Maximum number of observed-data Newton iterations. |
25
|
num_devices
|
int
|
Number of JAX devices across which class-specific M-steps are sharded. |
device_count()
|
check_interval
|
int
|
Number of EM recursions between Aitken stopping checks. History and progress callbacks are still updated on every recursion. |
1
|
starts
|
int
|
Number of independent EM starts. The start with the highest final log likelihood is kept. |
1
|
start_method
|
str
|
Compatibility setting; only |
"panel_partition"
|
Notes
The class is frozen so a configuration can be hashed and used as a static argument to a cached JIT-compiled M-step.
Polishing uses polish_maxiter and a fixed Newton decrement tolerance of
1e-10. The M-step iteration budget and decrement tolerance come from
:class:OptimizationOptions; its remaining solver settings also apply to
polishing. The final score criterion depends on the scale of the predictors
and certifies approximate stationarity, not a global maximum.
__post_init__()
Validate EM and multi-start settings.
Source code in src/lcl/options.py
lcl.options.OptimizationOptions(maxiter=75, newton_decrement_tol=None, hessian_damping=0.0, max_step_norm=1000.0, initial_trust_radius=1.0, line_search_maxiter=40, accept_any_decrease=False, gradient_tol=None)
dataclass
Safeguarded exact-Newton settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
maxiter
|
int
|
Maximum number of Newton iterations. |
75
|
newton_decrement_tol
|
float
|
Stopping tolerance on the Newton decrement
:math: |
1e-5
|
hessian_damping
|
float
|
Initial diagonal shift used only when the undamped Cholesky solve does not produce a finite descent direction. |
0.0
|
max_step_norm
|
float
|
Upper bound on the adaptive trust radius, in the local curvature metric. |
1000.0
|
initial_trust_radius
|
float
|
Starting trust radius, in the local curvature metric. It expands or contracts with agreement between the quadratic model and the objective. |
1.0
|
line_search_maxiter
|
int
|
Maximum number of Armijo backtracking steps per Newton iteration. |
40
|
accept_any_decrease
|
bool
|
Accept a finite step that merely decreases the objective when the stricter Armijo sufficient-decrease rule is not met. |
False
|
gradient_tol
|
float
|
Deprecated alias for |
None
|
Notes
The class is frozen so a configuration can be hashed and used as a static
argument to a cached JIT-compiled M-step. Use :func:dataclasses.replace
to derive a modified configuration.
Resolve the compatibility alias without storing duplicate tolerances.
Source code in src/lcl/options.py
gradient_tol
property
Read the resolved Newton tolerance under its deprecated spelling.
__post_init__()
Validate supported settings.
Source code in src/lcl/options.py
lcl.options.InferenceOptions(covariance='clustered', cluster='panel', finite_sample_correction=True, skip=False, boundary='strict', boundary_draws=2048, boundary_seed=0)
dataclass
Covariance and standard-error settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
covariance
|
str
|
One of |
"clustered"
|
cluster
|
str | None
|
Grouping used when |
"panel"
|
finite_sample_correction
|
bool
|
Apply the |
True
|
skip
|
bool
|
Skip covariance estimation entirely and return a matrix of |
False
|
boundary
|
str
|
For LCL models, |
"strict"
|
boundary_draws
|
int
|
Number of small Gaussian/quadratic-program draws for summary inference. Independent draws cover the weak price constraints and any zero-spread class coefficients. Gaussian residual variances are integrated exactly. When neither is selected, summary SEs equal the delta method without Monte Carlo error and the draws are unused. |
2048
|
boundary_seed
|
int
|
Seed for reproducible boundary summary inference. |
0
|
cluster_column
property
Return the data column naming a coarser cluster, if any.
clusters_at_panel
property
Report whether clustering uses the panel identifier itself.
__post_init__()
Normalize and validate covariance settings.
Source code in src/lcl/options.py
lcl.options.DiagnosticsOptions(check_separation=True, check_collinearity=True, warn_near_zero_numeraire=True, warn_large_coefficients=True, separation_threshold=1e-08, near_zero_numeraire_threshold=0.001, large_coefficient_threshold=25.0)
dataclass
Diagnostic switches and warning thresholds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
check_separation
|
bool
|
Report whether any demographic cell has a class-membership probability pinned at zero. When one does, the membership coefficients for that cell are unbounded and the observed information is singular in that direction, which is the usual cause of an otherwise puzzling rank deficiency. |
True
|
check_collinearity
|
bool
|
Report the rank and conditioning of the observed information. |
True
|
warn_near_zero_numeraire
|
bool
|
Warn when a class's numeraire coefficient sits near its floor, which makes every willingness-to-pay ratio for that class unstable. |
True
|
warn_large_coefficients
|
bool
|
Warn on implausibly large utility coefficients. |
True
|
separation_threshold
|
float
|
Membership probability at or below which a cell counts as separated. |
1e-8
|
near_zero_numeraire_threshold
|
float
|
Numeraire magnitude below which the near-zero warning fires, unless the
specification supplies a constraint-specific |
1e-3
|
large_coefficient_threshold
|
float
|
Absolute coefficient magnitude above which the large-coefficient warning fires. |
25.0
|
__post_init__()
Reject non-finite or out-of-range warning thresholds.
Source code in src/lcl/options.py
lcl.options.Options(fit=FitOptions(), optimization=OptimizationOptions(), inference=InferenceOptions(), diagnostics=DiagnosticsOptions())
dataclass
Configuration bundle accepted by all model-fitting entry points.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fit
|
FitOptions
|
EM and multi-start settings, used by LCL and cross-validation. |
FitOptions()
|
optimization
|
OptimizationOptions
|
Solver settings used by both estimators. |
OptimizationOptions()
|
inference
|
InferenceOptions
|
Covariance settings used by both estimators. |
InferenceOptions()
|
diagnostics
|
DiagnosticsOptions
|
LCL diagnostic switches and warning thresholds. Conditional logit uses
|
DiagnosticsOptions()
|
Notes
Conditional logit has no EM stage and does not use fit. Do not combine
options= with individual option arguments. Mutable inference and diagnostic
settings are copied on fit, so later edits cannot change a fitted result.