10. Nested logit model normalized from bottom

Bayesian estimation of a nested logit model where the normalization is done at the

bottom level.

Michel Bierlaire, EPFL Mon Nov 03 2025, 20:07:02

from pathlib import Path

from IPython.core.display_functions import display

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,
)

import biogeme.biogeme_logging as blog
from biogeme.bayesian_estimation import (
    BayesianResults,
    BayesianResultsSummary,
    get_pandas_estimated_parameters,
)
from biogeme.biogeme import BIOGEME
from biogeme.expressions import Beta
from biogeme.models import lognested_mev_mu
from biogeme.nests import NestsForNestedLogit, OneNestForNestedLogit

logger = blog.get_screen_logger(level=blog.INFO)
logger.info('Example b10nested_bottom.py')
Example b10nested_bottom.py

The scale parameters must stay away from zero. We define a small but positive lower bound

POSITIVE_LOWER_BOUND = 1.0e-5

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, 0, 0)
b_cost = Beta('b_cost', 0, None, 0, 0)

This is the scale parameter of the choice model. It is usually normalized to one. In this example, we normalize the nest parameter instead, and estimate the scale parameter for the model.

scale_parameter = Beta('scale_parameter', 0.5, POSITIVE_LOWER_BOUND, 1.0, 0)

Definition of the utility functions

v_train = asc_train + b_time * TRAIN_TT_SCALED + b_cost * TRAIN_COST_SCALED
v_swissmetro = asc_sm + b_time * SM_TT_SCALED + b_cost * SM_COST_SCALED
v_car = asc_car + b_time * CAR_TT_SCALED + b_cost * CAR_CO_SCALED

Associate utility functions with the numbering of alternatives.

v = {1: v_train, 2: v_swissmetro, 3: v_car}

Associate the availability conditions with the alternatives.

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

Definition of nests. Only the non trivial nests must be defined. A trivial nest is a nest containing exactly one alternative. The nest parameter is normalized to 1.

nest_parameter = 1.0
existing = OneNestForNestedLogit(
    nest_param=nest_parameter, list_of_alternatives=[1, 3], name='existing'
)

nests = NestsForNestedLogit(choice_set=list(v), tuple_of_nests=(existing,))
The following elements do not appear in any nest and are assumed each to be alone in a separate nest: {2}. If it is not the intention, check the assignment of alternatives to nests.

Definition of the model. This is the contribution of each observation to the log likelihood function. The choice model is a nested logit, with availability conditions, where the scale parameter mu is explicitly involved.

log_probability = lognested_mev_mu(v, av, nests, CHOICE, scale_parameter)

Create the Biogeme object.

the_biogeme = BIOGEME(database, log_probability)
the_biogeme.model_name = 'b10_nested_bottom'
Biogeme parameters read from biogeme.toml.

Estimate the posterior distribution of the parameters, or read the results if already available.

yaml_file = Path('saved_results') / f'{the_biogeme.model_name}.yaml'
try:
    summary_results = BayesianResultsSummary.from_yaml_file(filename=yaml_file)
except FileNotFoundError:
    results: BayesianResults = the_biogeme.bayesian_estimation()
    summary_results = results.to_summary()
print(summary_results.short_summary())
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:00.323492
Posterior predictive log-likelihood (sum of log mean p)  -5234.08
Expected log-likelihood E[log L(Y|θ)]                    -5239.43
Best-draw log-likelihood (posterior upper bound)         -5236.94
LOO (Leave-One-Out Cross-Validation)                     -5244.88
LOO Standard Error                                       62.49
Effective number of parameters (p_LOO)                   10.80

Present the parameter estimates in a pandas table.

pandas_results = get_pandas_estimated_parameters(
    estimation_results=summary_results,
)
display(pandas_results)
              Name  Value (mean)  ...   ESS (bulk)   ESS (tail)
0        asc_train     -1.056517  ...  2983.359140  3567.481626
1          asc_car     -0.347840  ...  3081.463560  3409.419691
2           b_time     -1.850810  ...  3923.110568  4445.632886
3           b_cost     -1.767597  ...  3905.582600  4542.012141
4  scale_parameter      0.484927  ...  3625.135474  3963.390778

[5 rows x 12 columns]

Report the variables stored in the Bayesian estimation results.

display(summary_results.report_stored_variables())
             group           variable                dims            shape
0    constant_data          CAR_AV_SP               [obs]           [6768]
1    constant_data      CAR_CO_SCALED               [obs]           [6768]
2    constant_data      CAR_TT_SCALED               [obs]           [6768]
3    constant_data             CHOICE               [obs]           [6768]
4    constant_data              SM_AV               [obs]           [6768]
5    constant_data     SM_COST_SCALED               [obs]           [6768]
6    constant_data       SM_TT_SCALED               [obs]           [6768]
7    constant_data        TRAIN_AV_SP               [obs]           [6768]
8    constant_data  TRAIN_COST_SCALED               [obs]           [6768]
9    constant_data    TRAIN_TT_SCALED               [obs]           [6768]
10  log_likelihood            _choice  [chain, draw, obs]  [4, 2000, 6768]
11       posterior            asc_car       [chain, draw]        [4, 2000]
12       posterior          asc_train       [chain, draw]        [4, 2000]
13       posterior             b_cost       [chain, draw]        [4, 2000]
14       posterior             b_time       [chain, draw]        [4, 2000]
15       posterior           log_like  [chain, draw, obs]  [4, 2000, 6768]
16       posterior    scale_parameter       [chain, draw]        [4, 2000]
17           prior            asc_car       [chain, draw]        [1, 2000]
18           prior          asc_train       [chain, draw]        [1, 2000]
19           prior             b_cost       [chain, draw]        [1, 2000]
20           prior             b_time       [chain, draw]        [1, 2000]
21           prior           log_like  [chain, draw, obs]  [1, 2000, 6768]
22           prior    scale_parameter       [chain, draw]        [1, 2000]
23    sample_stats    acceptance_rate       [chain, draw]        [4, 2000]
24    sample_stats          diverging       [chain, draw]        [4, 2000]
25    sample_stats             energy       [chain, draw]        [4, 2000]
26    sample_stats                 lp       [chain, draw]        [4, 2000]
27    sample_stats            n_steps       [chain, draw]        [4, 2000]
28    sample_stats          step_size       [chain, draw]        [4, 2000]
29    sample_stats         tree_depth       [chain, draw]        [4, 2000]

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

Gallery generated by Sphinx-Gallery