<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 17a
Step1:  For this example, we're going to re-create the market segmentation
versions of model 17 from the
Self Instructing Manual. (pp. 133)
Market segmentation can be used to determine whether the impact of other variables is different among population groups. The most common approach to market segmentation is for the analyst to consider sample segments which are mutually exclusive and collectively exhaustive (that is, each case is included in one and only one segment). Models are estimated for the sample associated with each segment and compared to the pooled model (all segments represented by a single model) to determine if there are statistically significant and important differences among the market segments.
In these models, we will segment the market by automobile ownership for households that have one or fewer cars. It is appealing to include a distinct segment for households with no cars since the mode choice behavior of this segment is very different from the rest of the population due to their dependence on non-automobile modes. However, the size of this segment in the dataset is too small, so it is joined with the one car group.
Step2:  We can use the query_cases method to create two separate datasets for this work,
and then attach these datasets to two models.
Step3:  We will then construct the same model stucture in each, using the 
design worked up for Model 17.
Step4:  Independent Estimation
We can estimate these models completely independently.
Step5:  To have t-statistics and $\rho^2_{\oslash}$ values, we'll also need
to prepare those for each model.
Step6:  We can generate a side-by-side summary of the 
two models using the joint_parameter_summary function.
Step7:  Joint Estimation
Suppose we think completely independent estimation is 
inappropriate, and that while these two market segments behave
differently with respect to some attributes of the choice, they
are also the same on some attributes.  In that case, we'll need
to use joint estimation -- that is, estimating all the parameters
of both models simultaneously.
We'll assume that any parameter that appears in both models with 
the same name is actually the same parameter (with the same value).
As configured so far, that's all the parameters.  But we can edit
one of the models so that the parameter names differ where we
want them to, using reformat_param.
Suppose we want different parameters for all the ca level of
service variables.  We can change every parameter in the entire
utility_ca linear function with a simple formatted replacement.
Step8:  To change just a selection of parameters, there's a regular
expression approach using different arguments to reformat_param. 
Here, we'll replace all the parameters with 'ASC' in them to
be a segment-specific value.
Step9:  Then, we can put our models together into a ModelGroup for
joint estimation.
Step10:  The estimation interface for a ModelGroup is the 
same as that for a regular model
Step11:  To review the estimation results, we can use ordering,
parameter_summary, and estimation_statistics as usual.
<ASSISTANT_TASK:>
Python Code:
# TEST
import larch.numba as lx
import larch
import pandas as pd
pd.set_option("display.max_columns", 999)
pd.set_option('expand_frame_repr', False)
pd.set_option('display.precision', 3)
larch._doctest_mode_ = True
Explanation: 17a: Market Segmentatation
End of explanation
import larch.numba as lx
d = lx.examples.MTC(format='dataset')
Explanation: For this example, we're going to re-create the market segmentation
versions of model 17 from the
Self Instructing Manual. (pp. 133)
Market segmentation can be used to determine whether the impact of other variables is different among population groups. The most common approach to market segmentation is for the analyst to consider sample segments which are mutually exclusive and collectively exhaustive (that is, each case is included in one and only one segment). Models are estimated for the sample associated with each segment and compared to the pooled model (all segments represented by a single model) to determine if there are statistically significant and important differences among the market segments.
In these models, we will segment the market by automobile ownership for households that have one or fewer cars. It is appealing to include a distinct segment for households with no cars since the mode choice behavior of this segment is very different from the rest of the population due to their dependence on non-automobile modes. However, the size of this segment in the dataset is too small, so it is joined with the one car group.
End of explanation
d1 = d.dc.query_cases("numveh <= 1")
d2 = d.dc.query_cases("numveh > 1")
m1 = lx.Model(d1, title="Cars<=1")
m2 = lx.Model(d2, title="Cars>=2")
Explanation: We can use the query_cases method to create two separate datasets for this work,
and then attach these datasets to two models.
End of explanation
from larch import P, X
for m in (m1,m2):
    m.availability_var = 'avail'
    m.choice_ca_var = 'chose'
    m.utility_ca = (
        + X("totcost/hhinc") * P("costbyincome")
        + X("tottime * (altnum <= 4)") * P("motorized_time")
        + X("tottime * (altnum >= 5)") * P("nonmotorized_time")
        + X("ovtt/dist * (altnum <= 4)") * P("motorized_ovtbydist")
    )
    for a in [4,5,6]:
        m.utility_co[a] += X("hhinc") * P("hhinc#{}".format(a))
    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)
        ) 
    m.ordering = (
        ('LOS', ".*cost.*", ".*time.*", ".*dist.*",),
        ('Zonal', "wkcbd.*", "wkempden.*",),
        ('Household', "hhinc.*", "vehbywrk.*",),
        ('ASCs', "ASC.*",),
    )
Explanation: We will then construct the same model stucture in each, using the 
design worked up for Model 17.
End of explanation
r1 = m1.maximize_loglike()
r2 = m2.maximize_loglike()
# TEST
from pytest import approx
assert r1.loglike == approx(-1049.2796388550328)
assert r2.loglike == approx(-2296.667143538367)
assert r1.n_cases == 1221
assert r2.n_cases == 3808
assert 'success' in r1.message.lower()
assert 'success' in r2.message.lower()
Explanation: Independent Estimation
We can estimate these models completely independently.
End of explanation
for m in (m1,m2):
    m.calculate_parameter_covariance()
    m.loglike_null()
Explanation: To have t-statistics and $\rho^2_{\oslash}$ values, we'll also need
to prepare those for each model.
End of explanation
from larch.util.summary import joint_parameter_summary
joint_parameter_summary([m1,m2])
Explanation: We can generate a side-by-side summary of the 
two models using the joint_parameter_summary function.
End of explanation
m2.utility_ca = m2.utility_ca.reformat_param('{}_2Cars')
Explanation: Joint Estimation
Suppose we think completely independent estimation is 
inappropriate, and that while these two market segments behave
differently with respect to some attributes of the choice, they
are also the same on some attributes.  In that case, we'll need
to use joint estimation -- that is, estimating all the parameters
of both models simultaneously.
We'll assume that any parameter that appears in both models with 
the same name is actually the same parameter (with the same value).
As configured so far, that's all the parameters.  But we can edit
one of the models so that the parameter names differ where we
want them to, using reformat_param.
Suppose we want different parameters for all the ca level of
service variables.  We can change every parameter in the entire
utility_ca linear function with a simple formatted replacement.
End of explanation
for a in m2.utility_co:
    m2.utility_co[a] = m2.utility_co[a].reformat_param(
        pattern='(ASC)', repl='ASC_2Cars'
    )
Explanation: To change just a selection of parameters, there's a regular
expression approach using different arguments to reformat_param. 
Here, we'll replace all the parameters with 'ASC' in them to
be a segment-specific value.
End of explanation
mg = lx.ModelGroup([m1,m2])
# TEST
assert mg.loglike('null') == approx(-7309.600971749634)
Explanation: Then, we can put our models together into a ModelGroup for
joint estimation.
End of explanation
mg.set_cap()
rg = mg.maximize_loglike()
mg.calculate_parameter_covariance()
rg
# TEST
assert rg.loglike == approx(-3406.7232213364787)
assert rg.n_cases == 5029
Explanation: The estimation interface for a ModelGroup is the 
same as that for a regular model: first maximize_loglike,
then calculate_parameter_covariance if desired. We can also
use many other Model commands, e.g. set_cap to bound our 
parameters to finite values.
End of explanation
mg.ordering = (
    ('LOS', ".*cost.*", ".*time.*", ".*dist.*",),
    ('Zonal', "wkcbd.*", "wkempden.*",),
    ('Household', "hhinc.*", "vehbywrk.*",),
    ('ASCs', "ASC.*",),
)
mg.parameter_summary()
# TEST
import re, platform
mash = lambda x: re.sub('\\s+', ' ', str(x)).strip()
if platform.system() != "Windows":
    assert mash(mg.parameter_summary().data) == mash('''
                                        Value    Std Err  t Stat Signif  Null Value
Category  Parameter                                                                
LOS       costbyincome                -0.0174     0.0117   -1.49                0.0
          costbyincome_2Cars           -0.102     0.0154   -6.60    ***         0.0
          motorized_time              -0.0196    0.00567   -3.46    ***         0.0
          motorized_time_2Cars        -0.0195    0.00496   -3.94    ***         0.0
          nonmotorized_time           -0.0442    0.00776   -5.69    ***         0.0
          nonmotorized_time_2Cars     -0.0471    0.00868   -5.43    ***         0.0
          motorized_ovtbydist          -0.100     0.0237   -4.23    ***         0.0
          motorized_ovtbydist_2Cars    -0.187     0.0309   -6.05    ***         0.0
Zonal     wkcbd_Bike                    0.460      0.364    1.27                0.0
          wkcbd_SR2                     0.233      0.124    1.88                0.0
          wkcbd_SR3+                     1.04      0.192    5.42    ***         0.0
          wkcbd_Transit                  1.27      0.168    7.53    ***         0.0
          wkcbd_Walk                    0.100      0.250    0.40                0.0
          wkempden_Bike               0.00137    0.00124    1.11                0.0
          wkempden_SR2                0.00132   0.000392    3.36    ***         0.0
          wkempden_SR3+               0.00184   0.000461    3.99    ***         0.0
          wkempden_Transit            0.00274   0.000358    7.64    ***         0.0
          wkempden_Walk               0.00248   0.000730    3.39    ***         0.0
Household hhinc#4                    -0.00101    0.00208   -0.48                0.0
          hhinc#5                    -0.00366    0.00527   -0.70                0.0
          hhinc#6                    -0.00232    0.00331   -0.70                0.0
          vehbywrk_Bike                -0.255      0.290   -0.88                0.0
          vehbywrk_SR                  -0.314     0.0740   -4.24    ***         0.0
          vehbywrk_Transit             -0.638      0.132   -4.82    ***         0.0
          vehbywrk_Walk                -0.395      0.198   -1.99      *         0.0
ASCs      ASC_2Cars_Bike                -2.94      0.629   -4.67    ***         0.0
          ASC_2Cars_SR2                 -1.91      0.127  -15.06    ***         0.0
          ASC_2Cars_SR3+                -3.53      0.173  -20.43    ***         0.0
          ASC_2Cars_Transit             -1.47      0.320   -4.60    ***         0.0
          ASC_2Cars_Walk                -1.00      0.517   -1.94                0.0
          ASC_Bike                      -1.39      0.439   -3.17     **         0.0
          ASC_SR2                       -1.58      0.126  -12.57    ***         0.0
          ASC_SR3+                      -3.26      0.206  -15.84    ***         0.0
          ASC_Transit                  -0.747      0.271   -2.76     **         0.0
          ASC_Walk                      0.314      0.416    0.75                0.0
''')
mg.estimation_statistics()
Explanation: To review the estimation results, we can use ordering,
parameter_summary, and estimation_statistics as usual.
End of explanation
<END_TASK>