17: MTC Expanded MNL Mode Choice#
For this example, we’re going to re-create model 17 from the Self Instructing Manual. (pp. 128)
import larch as lx
lx.__version__
'6.0.37.dev32+gdab7641'
d = lx.examples.MTC()
m = lx.Model(d, compute_engine="numba")
We will use the usual choice and availability variables.
m.availability_ca_var = "avail"
m.choice_ca_var = "chose"
from larch import P, X
m.utility_ca = (
+X("totcost/hhinc") * P("costbyincome")
+ X("tottime * (altid <= 4)") * P("motorized_time")
+ X("tottime * (altid >= 5)") * P("nonmotorized_time")
+ X("ovtt/dist * (altid <= 4)") * P("motorized_ovtbydist")
)
The “totcost/hhinc” data is computed once as a new variable when loading the model data. The same applies for tottime filtered by motorized modes (we harness the convenient fact that all the motorized modes have identifying numbers 4 or less), and “ovtt/dist”.
for a in [4, 5, 6]:
m.utility_co[a] += X("hhinc") * P(f"hhinc#{a}")
Since the model we want to create groups together DA, SR2 and SR3+ jointly as reference alternatives with respect to income, we can simply omit all of these alternatives from the block that applies to hhinc.
For vehicles per worker, the preferred model include a joint parameter on SR2 and SR3+, but not including DA and not fixed at zero. Here we might use a shadow_parameter (also called an alias in some places), which allows us to specify one or more parameters that are simply a fixed proportion of another parameter. For example, we can say that vehbywrk_SR2 will be equal to vehbywrk_SR.
for i in d["alt_names"][1:3]:
name = str(i.values)
a = int(i.altid)
m.utility_co[a] += (
+X("vehbywrk") * P("vehbywrk_SR")
+ X("wkccbd+wknccbd") * P("wkcbd_" + name)
+ X("wkempden") * P("wkempden_" + name)
+ P("ASC_" + name)
)
for i in d["alt_names"][3:]:
name = str(i.values)
a = int(i.altid)
m.utility_co[a] += (
+X("vehbywrk") * P("vehbywrk_" + name)
+ X("wkccbd+wknccbd") * P("wkcbd_" + name)
+ X("wkempden") * P("wkempden_" + name)
+ P("ASC_" + name)
)
We didn’t explicitly define our parameters first, which is fine; Larch will find them in the utility functions (or elsewhere in more complex models). But they may be found in a weird order that is hard to read in reports. We can define an ordering scheme by assigning to the parameter_groups attribute, like this:
m.ordering = (
(
"LOS",
".*cost.*",
".*time.*",
".*dist.*",
),
(
"Zonal",
"wkcbd.*",
"wkempden.*",
),
(
"Household",
"hhinc.*",
"vehbywrk.*",
),
(
"ASCs",
"ASC.*",
),
)
m.set_cap(25)
Each item in parameter_ordering is a tuple, with a label and one or more regular expressions, which will be compared against all the parameter names. Any names that match will be pulled out and put into the reporting order sequentially. Thus if a parameter name would match more than one regex, it will appear in the ordering only for the first match.
Having created this model, we can then estimate it:
result = m.maximize_loglike(stderr=True, options={"maxiter": 1000, "ftol": 1e-10})
Iteration 106 [Optimization terminated successfully]
Best LL = -3444.1861692625935
value | best | initvalue | minimum | maximum | nullvalue | holdfast | |
---|---|---|---|---|---|---|---|
param_name | |||||||
ASC_Bike | -1.626 | -1.626 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
ASC_SR2 | -1.808 | -1.808 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
ASC_SR3+ | -3.433 | -3.433 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
ASC_Transit | -0.689 | -0.689 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
ASC_Walk | 0.073 | 0.073 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
costbyincome | -0.052 | -0.052 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
hhinc#4 | -0.005 | -0.005 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
hhinc#5 | -0.009 | -0.009 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
hhinc#6 | -0.006 | -0.006 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
motorized_ovtbydist | -0.133 | -0.133 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
motorized_time | -0.020 | -0.020 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
nonmotorized_time | -0.046 | -0.046 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
vehbywrk_Bike | -0.703 | -0.703 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
vehbywrk_SR | -0.316 | -0.316 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
vehbywrk_Transit | -0.945 | -0.945 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
vehbywrk_Walk | -0.722 | -0.722 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkcbd_Bike | 0.499 | 0.499 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkcbd_SR2 | 0.259 | 0.259 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkcbd_SR3+ | 1.067 | 1.067 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkcbd_Transit | 1.310 | 1.310 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkcbd_Walk | 0.096 | 0.096 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkempden_Bike | 0.002 | 0.002 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkempden_SR2 | 0.002 | 0.002 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkempden_SR3+ | 0.002 | 0.002 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkempden_Transit | 0.003 | 0.003 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
wkempden_Walk | 0.003 | 0.003 | 0.0 | -25.0 | 25.0 | 0.0 | 0 |
Note we have set the convergence tolerance to be excessively strict here to ensure that the optimization results remain steady across platforms with different default settings.
m.parameter_summary()
Value | Std Err | t Stat | Signif | Null Value | ||
---|---|---|---|---|---|---|
Category | Parameter | |||||
LOS | costbyincome | -0.0524 | 0.0104 | -5.03 | *** | 0.00 |
motorized_time | -0.0202 | 0.00381 | -5.29 | *** | 0.00 | |
nonmotorized_time | -0.0455 | 0.00577 | -7.89 | *** | 0.00 | |
motorized_ovtbydist | -0.133 | 0.0196 | -6.76 | *** | 0.00 | |
Zonal | wkcbd_Bike | 0.499 | 0.360 | 1.38 | 0.00 | |
wkcbd_SR2 | 0.259 | 0.123 | 2.10 | * | 0.00 | |
wkcbd_SR3+ | 1.07 | 0.191 | 5.58 | *** | 0.00 | |
wkcbd_Transit | 1.31 | 0.166 | 7.90 | *** | 0.00 | |
wkcbd_Walk | 0.0960 | 0.252 | 0.38 | 0.00 | ||
wkempden_Bike | 0.00191 | 0.00122 | 1.57 | 0.00 | ||
wkempden_SR2 | 0.00158 | 0.000390 | 4.04 | *** | 0.00 | |
wkempden_SR3+ | 0.00226 | 0.000452 | 5.00 | *** | 0.00 | |
wkempden_Transit | 0.00313 | 0.000361 | 8.68 | *** | 0.00 | |
wkempden_Walk | 0.00290 | 0.000742 | 3.91 | *** | 0.00 | |
Household | hhinc#4 | -0.00531 | 0.00198 | -2.68 | ** | 0.00 |
hhinc#5 | -0.00866 | 0.00515 | -1.68 | 0.00 | ||
hhinc#6 | -0.00600 | 0.00315 | -1.91 | 0.00 | ||
vehbywrk_Bike | -0.703 | 0.258 | -2.72 | ** | 0.00 | |
vehbywrk_SR | -0.316 | 0.0666 | -4.75 | *** | 0.00 | |
vehbywrk_Transit | -0.945 | 0.118 | -7.99 | *** | 0.00 | |
vehbywrk_Walk | -0.722 | 0.169 | -4.26 | *** | 0.00 | |
ASCs | ASC_Bike | -1.63 | 0.427 | -3.81 | *** | 0.00 |
ASC_SR2 | -1.81 | 0.106 | -17.04 | *** | 0.00 | |
ASC_SR3+ | -3.43 | 0.152 | -22.61 | *** | 0.00 | |
ASC_Transit | -0.689 | 0.248 | -2.78 | ** | 0.00 | |
ASC_Walk | 0.0727 | 0.348 | 0.21 | 0.00 |