7. Latent class model

Bayesian estimation of a discrete mixture of logit (or latent class model).

Michel Bierlaire, EPFL Mon Nov 03 2025, 13:36:51

from IPython.core.display_functions import display

from biogeme.bayesian_estimation import BayesianResults, get_pandas_estimated_parameters
from biogeme.biogeme import BIOGEME
from biogeme.expressions import Beta, log
from biogeme.models import logit

See the data processing script: Data preparation for Swissmetro.

from swissmetro_data import (
    CAR_AV_SP,
    CAR_CO_SCALED,
    CAR_TT_SCALED,
    CHOICE,
    SM_AV,
    SM_COST_SCALED,
    SM_TT_SCALED,
    TRAIN_AV_SP,
    TRAIN_COST_SCALED,
    TRAIN_TT_SCALED,
    database,
)

Parameters to be estimated.

asc_car = Beta('asc_car', 0, None, None, 0)
asc_train = Beta('asc_train', 0, None, None, 0)
asc_sm = Beta('asc_sm', 0, None, None, 1)
b_time = Beta('b_time', 0, None, None, 0)
b_cost = Beta('b_cost', 0, None, None, 0)

Class membership probability.

prob_class1 = Beta('prob_class1', 0.5, 0, 1, 0)
prob_class2 = 1 - prob_class1

Definition of the utility functions for latent_old class 1, where the time coefficient is zero.

v_train_class_1 = asc_train + b_cost * TRAIN_COST_SCALED
v_swissmetro_class_1 = asc_sm + b_cost * SM_COST_SCALED
v_car_class_1 = asc_car + b_cost * CAR_CO_SCALED

Associate utility functions with the numbering of alternatives.

v_class_1 = {1: v_train_class_1, 2: v_swissmetro_class_1, 3: v_car_class_1}

Definition of the utility functions for latent_old class 2, whete the time coefficient is estimated.

v_train_class_2 = asc_train + b_time * TRAIN_TT_SCALED + b_cost * TRAIN_COST_SCALED
v_swissmetro_class_2 = asc_sm + b_time * SM_TT_SCALED + b_cost * SM_COST_SCALED
v_car_class_2 = asc_car + b_time * CAR_TT_SCALED + b_cost * CAR_CO_SCALED

Associate utility functions with the numbering of alternatives.

v_class_2 = {1: v_train_class_2, 2: v_swissmetro_class_2, 3: v_car_class_2}

Associate the availability conditions with the alternatives.

av = {1: TRAIN_AV_SP, 2: SM_AV, 3: CAR_AV_SP}

The choice model is a discrete mixture of logit, with availability conditions

choice_probability_class_1 = logit(v_class_1, av, CHOICE)
choice_probability_class_2 = logit(v_class_2, av, CHOICE)
prob = (
    prob_class1 * choice_probability_class_1 + prob_class2 * choice_probability_class_2
)
log_probability = log(prob)

Create the Biogeme object

the_biogeme = BIOGEME(database, log_probability)
the_biogeme.model_name = 'b07_discrete_mixture'

Estimate the parameters.

try:
    results = BayesianResults.from_netcdf(
        filename=f'saved_results/{the_biogeme.model_name}.nc'
    )
except FileNotFoundError:
    results = the_biogeme.bayesian_estimation()
load finished in 4350 ms (4.35 s)
print(results.short_summary())
posterior_predictive_loglike finished in 248 ms
expected_log_likelihood finished in 11 ms
best_draw_log_likelihood finished in 11 ms
waic_res finished in 618 ms
waic finished in 618 ms
loo_res finished in 7493 ms (7.49 s)
loo finished in 7493 ms (7.49 s)
Sample size                                              6768
Sampler                                                  NUTS
Number of chains                                         4
Number of draws per chain                                2000
Total number of draws                                    8000
Acceptance rate target                                   0.9
Run time                                                 0:01:20.750559
Posterior predictive log-likelihood (sum of log mean p)  -5208.04
Expected log-likelihood E[log L(Y|θ)]                    -5211.01
Best-draw log-likelihood (posterior upper bound)         -5208.55
WAIC (Widely Applicable Information Criterion)           -5213.98
WAIC Standard Error                                      53.21
Effective number of parameters (p_WAIC)                  5.94
LOO (Leave-One-Out Cross-Validation)                     -5213.98
LOO Standard Error                                       53.21
Effective number of parameters (p_LOO)                   5.94
pandas_results = get_pandas_estimated_parameters(estimation_results=results)
display(pandas_results)
          Name  Value (mean)  ...   ESS (bulk)   ESS (tail)
0    asc_train     -0.399679  ...  4426.559689  5037.338577
1       b_cost     -1.265343  ...  5089.817814  4912.904953
2      asc_car      0.123068  ...  4415.769041  4980.176025
3       b_time     -2.807006  ...  4300.831069  4195.683113
4  prob_class1      0.251723  ...  4565.556656  4841.224547

[5 rows x 12 columns]

Total running time of the script: (0 minutes 35.683 seconds)

Gallery generated by Sphinx-Gallery