{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "30a2d59e",
   "metadata": {},
   "source": [
    "File 02-logit_swissmetro.py\n",
    "\n",
    "\n",
    "\n",
    "Michel Bierlaire, EPFL\n",
    "\n",
    "Sat Aug 02 2025, 17:29:00\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca7dbdb2",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "import pandas as pd\n",
    "from IPython.core.display_functions import display\n",
    "from biogeme.biogeme import BIOGEME\n",
    "from biogeme.database import Database\n",
    "from biogeme.expressions import Beta, Variable\n",
    "from biogeme.models import loglogit\n",
    "from biogeme.results_processing import (\n",
    "    EstimationResults,\n",
    "    get_pandas_estimated_parameters,\n",
    ")\n",
    "from biogeme.segmentation import DiscreteSegmentationTuple, segmented_beta\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e46e87cd",
   "metadata": {},
   "source": [
    "The objective of this exercise is to introduce variations of the choice set across observations, and to investigate\n",
    "interactions of socio-economic characteristics and the model parameters."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "768d9b4e",
   "metadata": {},
   "source": [
    "We use the Swissmetro data, available as\n",
    "[transp-or.epfl.ch/data/swissmetro.dat](http://transp-or.epfl.ch/data/swissmetro.dat). The description of the data\n",
    "is available [here](http://transp-or.epfl.ch/documents/technicalReports/CS_SwissmetroDescription.pdf). We advise to\n",
    "download the data in your local directory."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "816e8df9",
   "metadata": {},
   "source": [
    "We  read the data and import them into Biogeme's database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3508dc3e",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('swissmetro.dat', sep='\\t')\n",
    "database = Database('swissmetro', df)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "febc829b",
   "metadata": {},
   "source": [
    "We identify the variables that will enter the model specification."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "258ccc87",
   "metadata": {},
   "outputs": [],
   "source": [
    "SM_CO = Variable('SM_CO')\n",
    "TRAIN_CO = Variable('TRAIN_CO')\n",
    "CAR_CO = Variable('CAR_CO')\n",
    "SM_TT = Variable('SM_TT')\n",
    "TRAIN_TT = Variable('TRAIN_TT')\n",
    "CAR_TT = Variable('CAR_TT')\n",
    "\n",
    "TRAIN_HE = Variable('TRAIN_HE')\n",
    "SM_HE = Variable('SM_HE')\n",
    "\n",
    "SP = Variable('SP')\n",
    "GA = Variable('GA')\n",
    "PURPOSE = Variable('PURPOSE')\n",
    "\n",
    "CHOICE = Variable('CHOICE')\n",
    "TRAIN_AV = Variable('TRAIN_AV')\n",
    "SM_AV = Variable('SM_AV')\n",
    "CAR_AV = Variable('CAR_AV')\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2236f653",
   "metadata": {},
   "source": [
    "We keep only data corresponding to commuters (`PURPOSE == 1`) and business trips (`PURPOSE == 3`), and remove\n",
    "observations where the choice is incorrectly coded (`CHOICE == 0`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c103d699",
   "metadata": {},
   "outputs": [],
   "source": [
    "exclude = ((PURPOSE != 1) * (PURPOSE != 3) + (CHOICE == 0)) > 0\n",
    "database.remove(exclude)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e20a7925",
   "metadata": {},
   "source": [
    "Definition of new variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9d75a2c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "SM_COST = SM_CO * (GA == 0)\n",
    "TRAIN_COST = TRAIN_CO * (GA == 0)\n",
    "CAR_AV_SP = CAR_AV * (SP != 0)\n",
    "TRAIN_AV_SP = TRAIN_AV * (SP != 0)\n",
    "TRAIN_TT_SCALED = TRAIN_TT / 100\n",
    "TRAIN_COST_SCALED = TRAIN_COST / 100\n",
    "SM_TT_SCALED = SM_TT / 100\n",
    "SM_COST_SCALED = SM_COST / 100\n",
    "CAR_TT_SCALED = CAR_TT / 100\n",
    "CAR_CO_SCALED = CAR_CO / 100\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86b0b316",
   "metadata": {},
   "source": [
    "Parameters to be estimated"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c259df82",
   "metadata": {},
   "outputs": [],
   "source": [
    "asc_car = Beta('asc_car', 0, None, None, 0)\n",
    "asc_train = Beta('asc_train', 0, None, None, 0)\n",
    "b_time = Beta('b_time', 0, None, None, 0)\n",
    "b_cost = Beta('b_cost', 0, None, None, 0)\n",
    "b_headway = Beta('B_HEADWAY', 0, None, None, 0)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8768517d",
   "metadata": {},
   "source": [
    "Definition of the utility functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "69c4cad5",
   "metadata": {},
   "outputs": [],
   "source": [
    "v_train = (\n",
    "    asc_train\n",
    "    + b_time * TRAIN_TT_SCALED\n",
    "    + b_cost * TRAIN_COST_SCALED\n",
    "    + b_headway * TRAIN_HE\n",
    ")\n",
    "v_swissmetro = b_time * SM_TT_SCALED + b_cost * SM_COST_SCALED + b_headway * SM_HE\n",
    "v_car = asc_car + b_time * CAR_TT_SCALED + b_cost * CAR_CO_SCALED\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97f3e5f7",
   "metadata": {},
   "source": [
    "Associate utility functions with the numbering of alternatives"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "973c58d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "v = {1: v_train, 2: v_swissmetro, 3: v_car}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ee6d449",
   "metadata": {},
   "source": [
    "The choice set is different from one observation to the next. In order to characterize it, we associate\n",
    "availability conditions with each alternative. These conditions are defined in the data file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43fbd9e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "av = {1: TRAIN_AV_SP, 2: SM_AV, 3: CAR_AV_SP}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99f483d3",
   "metadata": {},
   "source": [
    "Definition of the model. This is the contribution of each\n",
    "observation to the log likelihood function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "85ab554d",
   "metadata": {},
   "outputs": [],
   "source": [
    "logprob = loglogit(v, av, CHOICE)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbd4322d",
   "metadata": {},
   "source": [
    "Create the Biogeme object"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a9479dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "biogeme = BIOGEME(database, logprob)\n",
    "biogeme.model_name = 'logit_swissmetro_base'\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ba42379",
   "metadata": {},
   "source": [
    "Calculate the null log likelihood for reporting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa8fb7de",
   "metadata": {},
   "outputs": [],
   "source": [
    "null_log_likelihood = biogeme.calculate_null_loglikelihood(av)\n",
    "display(null_log_likelihood)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3686357",
   "metadata": {},
   "source": [
    "Estimate the parameters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "119a159d",
   "metadata": {},
   "outputs": [],
   "source": [
    "results: EstimationResults = biogeme.estimate()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2495f930",
   "metadata": {},
   "source": [
    "Summary statistics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b1a3757",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(results.short_summary())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e62e5b27",
   "metadata": {},
   "source": [
    "Get the results in a pandas table"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a20efbe",
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_results = get_pandas_estimated_parameters(estimation_results=results)\n",
    "display(pandas_results)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b7f9abe",
   "metadata": {},
   "source": [
    "In order to capture the  heterogeneity of the taste parameters, the population can be segmented using socio-economic\n",
    "characteristics, and a different parameter associated with each segment."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3cd5e44b",
   "metadata": {},
   "source": [
    "Suppose that we want to create a segmentation based on trip purpose. There are only two trip purposes in our sample:\n",
    "commuters (`PURPOSE == 1`) and business trips (`PURPOSE == 3`). We define the segmentation using a tuple with two\n",
    "entries: a Biogeme variable, and a mapping between the value of the variable and the name of the corresponding\n",
    "segment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9344176f",
   "metadata": {},
   "outputs": [],
   "source": [
    "purpose_segmentation = DiscreteSegmentationTuple(\n",
    "    variable=PURPOSE, mapping={1: 'commuter', 3: 'business'}\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc8b81ed",
   "metadata": {},
   "source": [
    "If we want to define a parameter for each segment, we use the function `segment_parameter`that takes two arguments:\n",
    "a parameters, and a list of segmentation objects, as defined above (in this example, the list contains only one\n",
    "segmentation)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c3f8060",
   "metadata": {},
   "outputs": [],
   "source": [
    "b_time_car = Beta('b_time_car', 0, None, None, 0)\n",
    "segmented_b_time_car = segmented_beta(b_time_car, [purpose_segmentation])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "342e6a5b",
   "metadata": {},
   "source": [
    "Now, the expression `segmented_B_TIME_CAR` can be included in the utility function like a regular parameter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3fe48b8c",
   "metadata": {},
   "outputs": [],
   "source": [
    "v_car = asc_car + segmented_b_time_car * CAR_TT_SCALED + b_cost * CAR_CO_SCALED\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f7d9b7cc",
   "metadata": {},
   "source": [
    "Investigate now segmentations based on:\n",
    "- luggage,\n",
    "- groups (current rail users, or current ar users),\n",
    "- GA (yearly subscription).\n",
    "\n",
    "Note that the GA segmentation is special, as the cost of public transportation (`TRAIN` or `SM`) is zero for those\n",
    "who own a GA. Therefore, there is no way to estimate a cost parameter for that segment.\n",
    "\n",
    "Investigate also alternative specific parameters."
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 5
}
