Nested logit model

Example of a nested logit model.

author:

Michel Bierlaire, EPFL

date:

Tue Oct 24 13:37:32 2023

from biogeme import biogeme_logging as blog
import biogeme.biogeme as bio
from biogeme import models
from biogeme.expressions import Beta
from biogeme.nests import OneNestForNestedLogit, NestsForNestedLogit

See the data processing script: Data preparation for Swissmetro.

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

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

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)
MU = Beta('MU', 1, 1, 10, 0)

Definition of the utility functions.

V1 = ASC_TRAIN + B_TIME * TRAIN_TT_SCALED + B_COST * TRAIN_COST_SCALED
V2 = ASC_SM + B_TIME * SM_TT_SCALED + B_COST * SM_COST_SCALED
V3 = ASC_CAR + B_TIME * CAR_TT_SCALED + B_COST * CAR_CO_SCALED

Associate utility functions with the numbering of alternatives.

V = {1: V1, 2: V2, 3: V3}

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. In this example, we create a nest for the existing modes, that is train (1) and car (3).

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

nests = NestsForNestedLogit(choice_set=list(V), tuple_of_nests=(existing,))

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.

logprob = models.lognested(V, av, nests, CHOICE)

Create the Biogeme object.

the_biogeme = bio.BIOGEME(database, logprob)
the_biogeme.modelName = "b09nested"
File biogeme.toml has been parsed.

Calculate the null log likelihood for reporting.

the_biogeme.calculateNullLoglikelihood(av)
-6964.662979191462

Estimate the parameters

results = the_biogeme.estimate()
*** Initial values of the parameters are obtained from the file __b09nested.iter
Cannot read file __b09nested.iter. Statement is ignored.
Optimization algorithm: hybrid Newton/BFGS with simple bounds [simple_bounds]
** Optimization: Newton with trust region for simple bounds
Iter.         ASC_CAR       ASC_TRAIN          B_COST          B_TIME              MU     Function    Relgrad   Radius      Rho
    0             0.1           -0.75              -1            -0.8             1.5      5.4e+03      0.082       10     0.92   ++
    1           -0.22           -0.28           -0.82           -0.86             2.2      5.3e+03      0.076       10     0.44    +
    2           -0.17           -0.52            -0.7            -0.7             2.7      5.3e+03      0.023       10     0.72    +
    3           -0.17           -0.52            -0.7            -0.7             2.7      5.3e+03      0.023     0.84     -4.6    -
    4           -0.14           -0.52           -0.87           -0.92             1.8      5.2e+03      0.006     0.84     0.63    +
    5           -0.16           -0.51           -0.87           -0.91               2      5.2e+03     0.0014      8.4      1.1   ++
    6           -0.17           -0.51           -0.86            -0.9             2.1      5.2e+03    8.1e-05       84        1   ++
    7           -0.17           -0.51           -0.86            -0.9             2.1      5.2e+03    2.8e-07       84        1   ++
Results saved in file b09nested.html
Results saved in file b09nested.pickle
print(results.short_summary())
Results for model b09nested
Nbr of parameters:              5
Sample size:                    6768
Excluded data:                  3960
Null log likelihood:            -6964.663
Final log likelihood:           -5236.9
Likelihood ratio test (null):           3455.526
Rho square (null):                      0.248
Rho bar square (null):                  0.247
Akaike Information Criterion:   10483.8
Bayesian Information Criterion: 10517.9
pandas_results = results.getEstimatedParameters()
pandas_results
Value Rob. Std err Rob. t-test Rob. p-value
ASC_CAR -0.167155 0.054529 -3.065428 2.173586e-03
ASC_TRAIN -0.511948 0.079114 -6.471047 9.732637e-11
B_COST -0.856667 0.060035 -14.269453 0.000000e+00
B_TIME -0.898666 0.107112 -8.389947 0.000000e+00
MU 2.054056 0.164201 12.509383 0.000000e+00


We calculate the correlation between the error terms of the alternatives.

corr = nests.correlation(
    parameters=results.getBetaValues(),
    alternatives_names={1: 'Train', 2: 'Swissmetro', 3: 'Car'},
)
print(corr)
            Train  Swissmetro  Car
Train         1.0         0.0  0.0
Swissmetro    0.0         1.0  0.0
Car           0.0         0.0  1.0

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

Gallery generated by Sphinx-Gallery