Skip to content

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 spec and are used only in presentation tables.

None
dems_data object | None

Separate panel-level data joined by spec.ids.panel. Columns may be used in utility and membership designs and must not overlap choice-data columns.

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
def fit(
    data: object,
    spec: LCLSpec,
    *,
    options: Options | None = None,
    fit_options: FitOptions | None = None,
    optimization_options: OptimizationOptions | None = None,
    inference: InferenceOptions | None = None,
    diagnostics: DiagnosticsOptions | None = None,
    variable_labels: _Mapping[str, str] | None = None,
    dems_data: object | None = None,
    progress_callback: _Callable[[dict[str, object]], None] | None = None,
) -> LCLResults:
    """Fit a latent-class conditional-logit model from an :class:`LCLSpec`.

    Parameters
    ----------
    data : object
        Long-format choice data.
    spec : LCLSpec
        Declarative model specification.
    options : Options | None, optional
        Complete configuration bundle. Do not combine with individual option arguments.
    fit_options : FitOptions | None, optional
        EM algorithm options.
    optimization_options : OptimizationOptions | None, optional
        M-step optimizer options.
    inference : InferenceOptions | None, optional
        Covariance and standard-error options.
    diagnostics : DiagnosticsOptions | None, optional
        Diagnostic thresholds and switches.
    variable_labels : Mapping[str, str] | None, optional
        Optional display labels for raw model/DataFrame variable names.  Labels
        supplement any labels stored on ``spec`` and are used only in
        presentation tables.
    dems_data : object | None, optional
        Separate panel-level data joined by ``spec.ids.panel``. Columns may be
        used in utility and membership designs and must not overlap choice-data columns.
    progress_callback : callable | None, optional
        Receives dictionaries describing hardware, starts, EM steps, polishing,
        and completion.

    Returns
    -------
    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.
    """
    model = LatentClassConditionalLogit(spec=spec)
    return model.fit(
        data=data,
        options=options,
        fit_options=fit_options,
        optimization_options=optimization_options,
        inference=inference,
        diagnostics=diagnostics,
        variable_labels=variable_labels,
        dems_data=dems_data,
        progress_callback=progress_callback,
    )

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 utility_formula provides the utility design.

None
membership Sequence[str] | None

Panel-level variables for class-membership probabilities. Omit when membership_formula provides the demographic design.

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 "choice ~ cost + C(mode)". A right-hand-side-only utility formula is permitted when the choice column is supplied by :class:ChoiceIds.

None
membership_formula str | None

Right-hand-side Formulaic string for the class-membership demographic regression, such as "~ income + C(segment)".

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 "C(segment)[T.high]".

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
def __post_init__(self) -> None:
    """Validate internal consistency."""
    _require_integer(self.classes, "LCLSpec.classes")
    if self.classes < 2:
        raise ValueError("LCLSpec.classes must be at least 2.")
    _validate_design_arguments(
        self.utility, self.utility_formula, self.membership, self.membership_formula
    )
    if self.utility_formula is None and not self.utility:
        raise ValueError("LCLSpec requires either utility variables or a formula.")
    if len(self.negative_constraints) > 1:
        raise NotImplementedError(
            "The current latent-class estimator supports one negative "
            "coefficient constraint. Multiple constraints can be added once "
            "the optimizer is generalized beyond a single numeraire row."
        )

constraint_rows()

Return serializable constraint metadata.

Source code in src/lcl/spec.py
def constraint_rows(self) -> list[dict[str, object]]:
    """Return serializable constraint metadata."""
    return constraint_summary_rows(self.negative_constraints)

summary_lines()

Return a compact, human-readable specification summary.

Source code in src/lcl/spec.py
def summary_lines(self) -> list[str]:
    """Return a compact, human-readable specification summary."""
    lines = [
        "Latent-class conditional logit",
        f"Classes: {self.classes}",
        f"Panel id: {self.ids.panel}",
        f"Case id: {self.ids.case}",
        f"Alternative id: {self.ids.alt}",
        f"Choice column: {self.ids.choice}",
        "",
        "Utility variables:",
    ]
    if self.utility_formula is not None:
        lines.append(f"  formula: {self.utility_formula}")
    else:
        for variable in self.utility or []:
            suffix = ""
            for constraint in self.negative_constraints:
                if constraint.variable == variable:
                    suffix = f" [negative, min_abs={constraint.min_abs:g}]"
            label = self._display_variable(variable)
            lines.append(f"  {label}{suffix}")
    lines.append("")
    lines.append("Class-membership variables:")
    if self.membership:
        lines.extend(
            f"  {self._display_variable(variable)}" for variable in self.membership
        )
    elif self.membership_formula is not None:
        lines.append(f"  formula: {self.membership_formula}")
    else:
        lines.append("  none")
    return lines

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 coefficient <= -min_abs.

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 near_zero_numeraire_threshold. The warn_near_zero_numeraire switch still controls whether the diagnostic has warning status.

None

__post_init__()

Validate constraint settings.

Source code in src/lcl/constraints.py
def __post_init__(self) -> None:
    """Validate constraint settings."""
    if not math.isfinite(self.min_abs) or self.min_abs <= 0:
        raise ValueError("NegativeCoefficient.min_abs must be finite and positive.")
    if self.warn_below is not None and (
        not math.isfinite(self.warn_below) or self.warn_below <= 0
    ):
        raise ValueError(
            "NegativeCoefficient.warn_below must be finite and positive."
        )

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
def bind(self, variable: str) -> "NegativeCoefficient":
    """Return a copy tied to ``variable``.

    Parameters
    ----------
    variable : str
        Variable name from a specification mapping.

    Returns
    -------
    NegativeCoefficient
        A constraint with a concrete variable name.
    """
    if self.variable is not None and self.variable != variable:
        raise ValueError(
            "NegativeCoefficient variable mismatch: "
            f"{self.variable!r} != {variable!r}."
        )
    return replace(self, variable=variable)

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 r, the estimated remaining ascent after the latest iterate is change * r / (1 - r). Normalizing by the panel count keeps the tolerance's meaning fixed as the sample grows.

1e-8
score_tol float

Stopping tolerance on the maximum absolute component of the observed-data score, per panel, used for the final public converged flag. It is checked after EM and optional polishing; it does not stop EM early.

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" is supported.

"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
def __post_init__(self) -> None:
    """Validate EM and multi-start settings."""
    for name in (
        "seed",
        "max_em_iter",
        "polish_maxiter",
        "num_devices",
        "check_interval",
        "starts",
    ):
        _require_integer(getattr(self, name), name)
    for name in ("em_tol", "score_tol"):
        if not math.isfinite(getattr(self, name)):
            raise ValueError(f"{name} must be finite.")
    if self.seed < 0:
        raise ValueError("seed must be nonnegative.")
    if self.em_tol <= 0:
        raise ValueError("em_tol must be positive.")
    if self.score_tol <= 0:
        raise ValueError("score_tol must be positive.")
    if self.max_em_iter < 1:
        raise ValueError("max_em_iter must be at least one.")
    if self.polish_maxiter < 0:
        raise ValueError("polish_maxiter must be nonnegative.")
    if self.check_interval <= 0:
        raise ValueError("check_interval must be positive.")
    if self.starts < 1:
        raise ValueError("starts must be at least 1.")
    if self.start_method != "panel_partition":
        raise ValueError(
            "Only start_method='panel_partition' is currently supported."
        )
    available_devices = device_count()
    if not 1 <= self.num_devices <= available_devices:
        raise ValueError(
            "num_devices must be between 1 and the number of available JAX "
            f"devices ({available_devices})."
        )

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:\lambda = \sqrt{g' H^{-1} g}, not on a raw gradient norm. The decrement is invariant to nonsingular diagonal rescaling of the parameters and approximates :math:\sqrt{2(f - f^\star)}, so a value of 1e-5 on the package's per-observation objective corresponds to roughly 5e-11 in objective units. With price bounds, this uses structural derivatives, the Newton decrement on free coordinates, and feasible displacement on active coordinates. Outward slopes at an attained upper bound satisfy KKT; inward slopes remain eligible for optimization.

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 newton_decrement_tol. Reading it returns the resolved tolerance; passing it emits a :class:DeprecationWarning. If both spellings are supplied, newton_decrement_tol takes precedence.

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
def __init__(
    self,
    maxiter: int = 75,
    newton_decrement_tol: float | None = None,
    hessian_damping: float = 0.0,
    max_step_norm: float = 1000.0,
    initial_trust_radius: float = 1.0,
    line_search_maxiter: int = 40,
    accept_any_decrease: bool = False,
    gradient_tol: float | None = None,
) -> None:
    """Resolve the compatibility alias without storing duplicate tolerances."""
    if gradient_tol is not None:
        warnings.warn(
            "OptimizationOptions.gradient_tol is deprecated; use "
            "newton_decrement_tol. The value is a Newton-decrement "
            "tolerance, not a gradient norm.",
            DeprecationWarning,
            stacklevel=2,
        )
    tolerance = newton_decrement_tol
    if tolerance is None:
        tolerance = (
            DEFAULT_NEWTON_DECREMENT_TOL if gradient_tol is None else gradient_tol
        )
    for name, value in (
        ("maxiter", maxiter),
        ("newton_decrement_tol", tolerance),
        ("hessian_damping", hessian_damping),
        ("max_step_norm", max_step_norm),
        ("initial_trust_radius", initial_trust_radius),
        ("line_search_maxiter", line_search_maxiter),
        ("accept_any_decrease", accept_any_decrease),
    ):
        object.__setattr__(self, name, value)
    self.__post_init__()

gradient_tol property

Read the resolved Newton tolerance under its deprecated spelling.

__post_init__()

Validate supported settings.

Source code in src/lcl/options.py
def __post_init__(self) -> None:
    """Validate supported settings."""
    for name in ("maxiter", "line_search_maxiter"):
        _require_integer(getattr(self, name), name)
    for name in (
        "newton_decrement_tol",
        "hessian_damping",
        "max_step_norm",
        "initial_trust_radius",
    ):
        if not math.isfinite(getattr(self, name)):
            raise ValueError(f"{name} must be finite.")
    if self.maxiter < 0:
        raise ValueError("maxiter must be nonnegative.")
    if self.newton_decrement_tol <= 0:
        raise ValueError("newton_decrement_tol must be positive.")
    if self.hessian_damping < 0:
        raise ValueError("hessian_damping must be nonnegative.")
    if self.max_step_norm <= 0:
        raise ValueError("max_step_norm must be positive.")
    if self.initial_trust_radius <= 0:
        raise ValueError("initial_trust_radius must be positive.")
    if self.line_search_maxiter < 0:
        raise ValueError("line_search_maxiter must be nonnegative.")

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", "robust", or "unadjusted". Latent-class models accept only "clustered" and "unadjusted", because the latent class is shared within a panel.

"clustered"
cluster str | None

Grouping used when covariance="clustered". "panel" clusters at the decision-maker. Any other string names a column of the estimation data holding a coarser grouping, which must be constant within each panel — a household, market, or region identifier, for instance. The value is ignored when covariance is not "clustered".

"panel"
finite_sample_correction bool

Apply the G / (G - 1) cluster multiplier, or n / (n - 1) for the unclustered sandwich, matching Stata's maximum-likelihood convention.

True
skip bool

Skip covariance estimation entirely and return a matrix of NaN.

False
boundary str

For LCL models, "conditional" estimates covariance on the free parameter subspace, holding binding negative coefficients fixed. This is conditional inference, not the nonnormal sampling distribution of an inequality-constrained estimator. "strict" requires an interior estimate and a positive-definite full information matrix. "projected" uses Gaussian critical-cone simulation for coefficient means and standard deviations in beta_summary; other inference remains conditional. Boundary modes are LCL-only. They do not repair unidentified mixtures.

"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
def __post_init__(self) -> None:
    """Normalize and validate covariance settings."""
    if self.boundary not in {"strict", "conditional", "projected"}:
        raise ValueError(
            "InferenceOptions.boundary must be 'strict', 'conditional', or 'projected'."
        )
    _require_integer(self.boundary_draws, "boundary_draws")
    _require_integer(self.boundary_seed, "boundary_seed")
    if self.boundary_draws < 100 or self.boundary_seed < 0:
        raise ValueError(
            "boundary_draws must be >=100 and boundary_seed nonnegative."
        )
    covariance = self.covariance.lower()
    if covariance in {"none", "unadjusted", "hessian"}:
        covariance = "unadjusted"
    elif covariance == "clustered":
        covariance = "clustered"
    elif covariance in {"robust", "sandwich", "huber-white"}:
        covariance = "robust"
    else:
        raise ValueError(
            "InferenceOptions.covariance must be one of 'clustered', "
            "'robust', 'sandwich', 'huber-white', 'unadjusted', or 'none'."
        )
    self.covariance = covariance
    if covariance == "clustered" and self.cluster is None:
        raise ValueError(
            "covariance='clustered' requires a cluster grouping. Pass "
            "cluster='panel' for decision-maker clustering, cluster='<column>' "
            "for a coarser grouping, or covariance='robust'/'unadjusted'."
        )
    if self.cluster is not None and not isinstance(self.cluster, str):
        raise ValueError("InferenceOptions.cluster must be a string or None.")

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 warn_below threshold.

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
def __post_init__(self) -> None:
    """Reject non-finite or out-of-range warning thresholds."""
    for name in (
        "separation_threshold",
        "near_zero_numeraire_threshold",
        "large_coefficient_threshold",
    ):
        value = getattr(self, name)
        if not math.isfinite(value) or value < 0:
            raise ValueError(f"{name} must be finite and nonnegative.")
    if self.separation_threshold > 1:
        raise ValueError("separation_threshold must be at most one.")

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 check_collinearity; membership and class-warning settings apply to LCL.

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.