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, gradient_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.
Use separate utility_formula and membership_formula fields for formula-based
designs. LCLSpec is immutable, so it can be reused safely across fitting and
cross-validation.
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 |
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
|
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 structural coefficient. The forward
transform is |
1e-5
|
units
|
str | None
|
Optional human-readable units for summaries and audit reports. |
None
|
warn_below
|
float | None
|
Optional threshold used by diagnostics to flag weakly identified numeraires. |
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
forward(raw)
hessian_diag(raw)
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 understates the distance to the optimum by |
1e-8
|
score_tol
|
float
|
Stopping tolerance on the maximum absolute component of the observed-data
score, per panel. EM terminates when either criterion is met, and the
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 alone converges linearly and reliably stops short of a stationary point; the polish makes the reported optimum stationary, which is what the observed information and the sandwich covariance assume. 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 convergence checks. |
1
|
starts
|
int
|
Number of independent EM starts. The start with the highest final log likelihood is kept. |
1
|
start_method
|
str
|
Strategy used to build starting values. |
"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.
__post_init__()
Validate EM and multi-start settings.
Source code in src/lcl/options.py
lcl.options.OptimizationOptions(maxiter=75, newton_decrement_tol=DEFAULT_NEWTON_DECREMENT_TOL, hessian_damping=0.0, max_step_norm=1000.0, initial_trust_radius=1.0, line_search_maxiter=40, accept_any_decrease=False, gradient_tol=DEFAULT_NEWTON_DECREMENT_TOL)
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 |
DEFAULT_NEWTON_DECREMENT_TOL
|
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.
__post_init__()
Resolve the deprecated alias and validate supported settings.
Source code in src/lcl/options.py
lcl.options.InferenceOptions(covariance='clustered', cluster='panel', finite_sample_correction=True, skip=False)
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
|
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. |
1e-3
|
large_coefficient_threshold
|
float
|
Absolute coefficient magnitude above which the large-coefficient warning fires. |
25.0
|
lcl.options.Options(fit=FitOptions(), optimization=OptimizationOptions(), inference=InferenceOptions(), diagnostics=DiagnosticsOptions())
dataclass
Complete configuration shared by all model-fitting entry points.