.. currentmodule:: larch

================================
101: Swissmetro MNL Mode Choice
================================

.. testsetup:: *

	import larch
	import os
	import pandas
	pandas.set_option("display.max_columns", 999)
	pandas.set_option('expand_frame_repr', False)
	pandas.set_option('precision', 3)
	larch._doctest_mode_ = True

This example is a mode choice model built using the Swissmetro example dataset.
First we can create a Model object:

.. testcode::

	m = larch.Model()

We can attach a title to the model. The title does not affect the calculations
as all; it is merely used in various output report styles.

.. testcode::

	m.title = "swissmetro example 01 (simple logit)"

We need to identify the availability and choice variables.
The Swissmetro dataset, as with all Biogeme data, is only
in `co` format, so we must define alternative
availability as an expression for each alternative, using a
dictionary to map alternative codes and expressions.

.. testcode::

	m.availability_co_vars = {
	    1: "TRAIN_AV * (SP!=0)",
	    2: "SM_AV",
	    3: "CAR_AV * (SP!=0)",
	}

In the Swissmetro example dataset, as in many discrete choice
modeling applications, there is one and only one chosen
alternative for each case, so the choices can be described
as a single expression that evaluates to the code of the
chosen alternative.

.. testcode::

	m.choice_co_code = 'CHOICE'

We will also write utility functions for each alternative.
Since the data is only in `co` format, we must use only the
`utility_co` form for the utility functions.

.. testcode::

	from larch.roles import P,X
	m.utility_co[1] = P("ASC_TRAIN")
	m.utility_co[2] = 0
	m.utility_co[3] = P("ASC_CAR")
	m.utility_co[1] += X("TRAIN_TT") * P("B_TIME")
	m.utility_co[2] += X("SM_TT") * P("B_TIME")
	m.utility_co[3] += X("CAR_TT") * P("B_TIME")
	m.utility_co[1] += X("TRAIN_CO*(GA==0)") * P("B_COST")
	m.utility_co[2] += X("SM_CO*(GA==0)") * P("B_COST")
	m.utility_co[3] += X("CAR_CO") * P("B_COST")

Larch will find all the parameters in the model, but we'd like to output them in
a rational order.  We can use the ordering method to do this:

.. testcode::

	m.ordering = [
		("ASCs", 'ASC.*',),
		("LOS", 'B_.*',),
	]

Now we can prepare the data, which is available in the data warehouse that
comes with Larch.

.. testcode::

	from larch.data_warehouse import example_file
	import pandas
	raw_data = pandas.read_csv(example_file('swissmetro.csv.gz'))

The swissmetro example models exclude some observations.  We will use pandas
to identify the observations we would like to keep.

.. testcode::

	keep = raw_data.eval("PURPOSE in (1,3) and CHOICE != 0")
	selected_data = raw_data[keep]

When you've created the data you need, you can pass the dataframe to
the larch.DataFrames constructor.  Since the *swissmetro* data is in
``idco`` format, we'll need to explicitly identify the alternative
codes as well.

.. testcode::

	dfs = larch.DataFrames(selected_data, alt_codes=[1,2,3])

You might notice we have not carefully constructed this object to
include only the relevant data or the various simple transformations
used in the utility definition above.  Larch can do this itself, if
you assign this DataFrames not as the actual set of data used in model
estimation, but rather as the `dataservice` that can be used as the
source to create these computational arrays.

.. testcode::

	m.dataservice = dfs

We can estimate the models and check the results match up with those given by Biogeme:

.. doctest::
	:options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF

	>>> m.load_data()
	>>> m.maximize_loglike(method='SLSQP')
	┣ ...Optimization terminated successfully...
	>>> m.loglike()
	-5331.252...
	>>> m.calculate_parameter_covariance()
	>>> m.pf.loc['B_TIME','value']
	-0.01277...
	>>> m.pf.loc['B_COST','value']
	-0.01083...
	>>> m.pf.loc['ASC_TRAIN','value']
	-0.701...
	>>> m.pf.loc['ASC_CAR','value']
	-0.1546...



.. doctest::
	:options: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_NDIFF

	>>> print(m.pfo()[['value','std_err','t_stat','robust_std_err','robust_t_stat']])  # parameter frame, ordered
	                    value    std_err  t_stat  robust_std_err  robust_t_stat
	Category Parameter
	ASCs     ASC_CAR   -0.155  4.324e-02  -3.576       5.816e-02         -2.65...
	         ASC_TRAIN -0.701  5.487e-02 -12.778       8.256e-02         -8.49...
	LOS      B_COST    -0.011  5.183e-04 -20.910       6.823e-04        -15.88...
	         B_TIME    -0.013  5.688e-04 -22.465       1.043e-03        -12.25...


.. tip::

	If you want access to the model in this example without worrying about assembling all the code blocks
	together on your own, you can load a read-to-estimate copy like this::

		m = larch.example(101)

