{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4266467e",
   "metadata": {},
   "source": [
    "\n",
    "File: 01-logit_airline.py\n",
    "\n",
    "\n",
    "Michel Bierlaire\n",
    "\n",
    "Sat Aug 02 2025, 17:19:33\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "84235b06",
   "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 boxcox, loglogit, piecewise_formula\n",
    "from biogeme.results_processing import (\n",
    "    EstimationResults,\n",
    "    get_pandas_estimated_parameters,\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9ad6289",
   "metadata": {},
   "source": [
    "The goal of this computer session is to estimate a logit model with more than two alternatives, and to investigate\n",
    "several specifications."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "defe193c",
   "metadata": {},
   "source": [
    "We are using stated preference data for the choice of an airline itinerary. The data set is available as\n",
    "[transp-or.epfl.ch/data/airline.dat](http://transp-or.epfl.ch/data/airline.dat), and its description is\n",
    "available [here](http://transp-or.epfl.ch/documents/technicalReports/CS_AirlineDescription.pdf)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8811204e",
   "metadata": {},
   "source": [
    "We recommend to download the dataset in your local directory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3edc682",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('airline.dat', sep='\\t')\n",
    "display(df)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3446864c",
   "metadata": {},
   "source": [
    "Import the database for Biogeme."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "db31ed55",
   "metadata": {},
   "outputs": [],
   "source": [
    "database = Database('airline', df)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "add947f5",
   "metadata": {},
   "source": [
    "We identify the columns that will be used as variable in our model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "70e7654b",
   "metadata": {},
   "outputs": [],
   "source": [
    "ArrivalTimeHours_1 = Variable('ArrivalTimeHours_1')\n",
    "ArrivalTimeHours_2 = Variable('ArrivalTimeHours_2')\n",
    "ArrivalTimeHours_3 = Variable('ArrivalTimeHours_3')\n",
    "BestAlternative_1 = Variable('BestAlternative_1')\n",
    "BestAlternative_2 = Variable('BestAlternative_2')\n",
    "BestAlternative_3 = Variable('BestAlternative_3')\n",
    "\n",
    "q11_DepartureOrArrivalIsImportant = Variable('q11_DepartureOrArrivalIsImportant')\n",
    "q12_IdealDepTime = Variable('q12_IdealDepTime')\n",
    "q13_IdealArrTime = Variable('q13_IdealArrTime')\n",
    "DepartureTimeMins_1 = Variable('DepartureTimeMins_1')\n",
    "DepartureTimeMins_2 = Variable('DepartureTimeMins_2')\n",
    "DepartureTimeMins_3 = Variable('DepartureTimeMins_3')\n",
    "ArrivalTimeMins_1 = Variable('ArrivalTimeMins_1')\n",
    "ArrivalTimeMins_2 = Variable('ArrivalTimeMins_2')\n",
    "ArrivalTimeMins_3 = Variable('ArrivalTimeMins_3')\n",
    "\n",
    "Fare_1 = Variable('Fare_1')\n",
    "Fare_2 = Variable('Fare_2')\n",
    "Fare_3 = Variable('Fare_3')\n",
    "Legroom_1 = Variable('Legroom_1')\n",
    "Legroom_2 = Variable('Legroom_2')\n",
    "Legroom_3 = Variable('Legroom_3')\n",
    "TripTimeHours_1 = Variable('TripTimeHours_1')\n",
    "TripTimeHours_2 = Variable('TripTimeHours_2')\n",
    "TripTimeHours_3 = Variable('TripTimeHours_3')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69ca5cd0",
   "metadata": {},
   "source": [
    "We define new variables from existing ones."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be3ab534",
   "metadata": {},
   "source": [
    "The chosen alternative"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "712db466",
   "metadata": {},
   "outputs": [],
   "source": [
    "chosen_alternative = (\n",
    "    (BestAlternative_1 * 1) + (BestAlternative_2 * 2) + (BestAlternative_3 * 3)\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1740c9b",
   "metadata": {},
   "source": [
    "Sensitivity to departure or arrival time (binary variable)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab42e3ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "departure_time_sensitive = q11_DepartureOrArrivalIsImportant == 1\n",
    "arrival_time_sensitive = q11_DepartureOrArrivalIsImportant == 2\n",
    "missing_sensitive = (q11_DepartureOrArrivalIsImportant != 1) * (\n",
    "    q11_DepartureOrArrivalIsImportant != 2\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9bc2c35",
   "metadata": {},
   "source": [
    "Desired departure and arrival time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eeaa4216",
   "metadata": {},
   "outputs": [],
   "source": [
    "desired_departure_time = q12_IdealDepTime\n",
    "desired_arrival_time = q13_IdealArrTime\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6f6b06a",
   "metadata": {},
   "source": [
    "The scheduled delay measures the difference between the desired and the actual value for the departure or\n",
    "arrival time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7885971e",
   "metadata": {},
   "outputs": [],
   "source": [
    "scheduled_delay_1 = departure_time_sensitive * (\n",
    "    DepartureTimeMins_1 - desired_departure_time\n",
    ") + arrival_time_sensitive * (ArrivalTimeMins_1 - desired_arrival_time)\n",
    "scheduled_delay_2 = departure_time_sensitive * (\n",
    "    DepartureTimeMins_2 - desired_departure_time\n",
    ") + arrival_time_sensitive * (ArrivalTimeMins_2 - desired_arrival_time)\n",
    "scheduled_delay_3 = departure_time_sensitive * (\n",
    "    DepartureTimeMins_3 - desired_departure_time\n",
    ") + arrival_time_sensitive * (ArrivalTimeMins_3 - desired_arrival_time)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfc27a24",
   "metadata": {},
   "source": [
    "We make a distinction between being earlier and later, compared to the preferred time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32211b8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "opt1_sched_delay_early = -scheduled_delay_1 * (scheduled_delay_1 < 0) / 60\n",
    "opt2_sched_delay_early = -scheduled_delay_2 * (scheduled_delay_2 < 0) / 60\n",
    "opt3_sched_delay_early = -scheduled_delay_3 * (scheduled_delay_3 < 0) / 60\n",
    "\n",
    "opt1_sched_delay_late = scheduled_delay_1 * (scheduled_delay_1 > 0) / 60\n",
    "opt2_sched_delay_late = scheduled_delay_2 * (scheduled_delay_2 > 0) / 60\n",
    "opt3_sched_delay_late = scheduled_delay_3 * (scheduled_delay_3 > 0) / 60\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38dc700d",
   "metadata": {},
   "source": [
    "# Model specification"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc6cce06",
   "metadata": {},
   "source": [
    "We are now ready to specify the choice model. We start with a simple model, that contains only generic coefficients:\n",
    "$$\\begin{align*}\n",
    "V_class_1 &= \\beta_\\text{fare}  \\text{fare}_1 + \\beta_\\text{legroom}  \\text{legroom}_1\n",
    "+ \\beta_\\text{sd\\_early} \\text{sched\\_delay\\_early}_1  + \\beta_\\text{sd\\_late} \\text{sched\\_delay\\_late}_1\n",
    "+ \\beta_\\text{time} \\text{elapsed\\_time}_1 \\\\\n",
    "V_class_2 &= \\text{cte}_2 + \\beta_\\text{fare}  \\text{fare}_2 + \\beta_\\text{legroom}  \\text{legroom}_2\n",
    "+ \\beta_\\text{sd\\_early} \\text{sched\\_delay\\_early}_2  + \\beta_\\text{sd\\_late} \\text{sched\\_delay\\_late}_2\n",
    "+ \\beta_\\text{time} \\text{elapsed\\_time}_2 \\\\\n",
    "V_3 &= \\text{cte}_3 + \\beta_\\text{fare}  \\text{fare}_3 + \\beta_\\text{legroom}  \\text{legroom}_3\n",
    "+ \\beta_\\text{sd\\_early} \\text{sched\\_delay\\_early}_3  + \\beta_\\text{sd\\_late} \\text{sched\\_delay\\_late}_3\n",
    "+ \\beta_\\text{time} \\text{elapsed\\_time}_3\n",
    "\\end{align*}$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e03c3397",
   "metadata": {},
   "source": [
    "We define the unknown parameters using the Biogeme expression `Beta`, that takes 5 parameters:\n",
    "\n",
    "- the name of the parameter (it is advised to use the exact same name for the corresponding Python variable),\n",
    "- the starting value for the estimation (usually, 0),\n",
    "- a lower bound on the value of the coefficient, or `None` for no bound,\n",
    "- an upper bound, or `None`for no bound,\n",
    "- a parameter that is 1 if the value of the parameter must be fixed to its starting value, and 0 if it has to be\n",
    "estimated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7dfc6279",
   "metadata": {},
   "outputs": [],
   "source": [
    "constant_2 = Beta('constant_2', 0, None, None, 0)\n",
    "constant_3 = Beta('constant_3', 0, None, None, 0)\n",
    "beta_fare = Beta('beta_fare', 0, None, None, 0)\n",
    "beta_legroom = Beta('beta_legroom', 0, None, None, 0)\n",
    "beta_schedule_delay_early = Beta('beta_schedule_delay_early', 0, None, None, 0)\n",
    "beta_schedule_delay_late = Beta('beta_schedule_delay_late', 0, None, None, 0)\n",
    "beta_elapsed_time = Beta('beta_elapsed_time', 0, None, None, 0)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd842d19",
   "metadata": {},
   "source": [
    "We now write the utility functions:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1aa0c53c",
   "metadata": {},
   "source": [
    "Utility functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8704eedf",
   "metadata": {},
   "outputs": [],
   "source": [
    "opt1 = (\n",
    "    beta_fare * Fare_1\n",
    "    + beta_legroom * Legroom_1\n",
    "    + beta_schedule_delay_early * opt1_sched_delay_early\n",
    "    + beta_schedule_delay_late * opt1_sched_delay_late\n",
    "    + beta_elapsed_time * TripTimeHours_1\n",
    ")\n",
    "opt2 = (\n",
    "    constant_2\n",
    "    + beta_fare * Fare_2\n",
    "    + beta_legroom * Legroom_2\n",
    "    + beta_schedule_delay_early * opt2_sched_delay_early\n",
    "    + beta_schedule_delay_late * opt2_sched_delay_late\n",
    "    + beta_elapsed_time * TripTimeHours_2\n",
    ")\n",
    "opt3 = (\n",
    "    constant_3\n",
    "    + beta_fare * Fare_3\n",
    "    + beta_legroom * Legroom_3\n",
    "    + beta_schedule_delay_early * opt3_sched_delay_early\n",
    "    + beta_schedule_delay_late * opt3_sched_delay_late\n",
    "    + beta_elapsed_time * TripTimeHours_3\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc875b75",
   "metadata": {},
   "source": [
    "The choice model is logit. No need to code the formula. An efficient implementation is available from Biogeme."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b764444",
   "metadata": {},
   "source": [
    "We associate each utility function with the id of the corresponding alternative. It must be consistent with the\n",
    "definition of the chosen alternative."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5e05a608",
   "metadata": {},
   "outputs": [],
   "source": [
    "V = {1: opt1, 2: opt2, 3: opt3}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4adc525",
   "metadata": {},
   "source": [
    "We obtain the choice model, that is a logit model. As Biogeme requests the contribution of each observation to the\n",
    "log likelihood function, we calculate the log of the logit model."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff10098e",
   "metadata": {},
   "source": [
    "The documentation of the function can be obtained with the `help`function. See also the\n",
    "[online documentation](http://biogeme.epfl.ch/sphinx/index.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f39c776",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(loglogit)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d13cb10",
   "metadata": {},
   "source": [
    "We obtain the formula for the log of the probability, that is the contribution of each individual to the\n",
    "log-likelihood function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "496cff49",
   "metadata": {},
   "outputs": [],
   "source": [
    "logprob = loglogit(V, None, chosen_alternative)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17e71220",
   "metadata": {},
   "source": [
    "We initialize Biogeme with this expression, and the database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4028b7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "biogeme = BIOGEME(database, logprob)\n",
    "biogeme.model_name = 'logit_airline_base'\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d230c8b",
   "metadata": {},
   "source": [
    "# Estimation of the parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "964dbbe9",
   "metadata": {},
   "outputs": [],
   "source": [
    "results: EstimationResults = biogeme.estimate()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81d2dff8",
   "metadata": {},
   "source": [
    "We first display some summary information:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "529756f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(results.short_summary())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0219f922",
   "metadata": {},
   "source": [
    "Then we display the estimation results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6af88b19",
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_results = get_pandas_estimated_parameters(estimation_results=results)\n",
    "display(pandas_results)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b63fbaa6",
   "metadata": {},
   "source": [
    "The results are also available in an HTML file than can be opened in your preferred browser."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "018e18b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "display(biogeme.html_filename)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7e1f657",
   "metadata": {},
   "source": [
    "You now need to improve the model. We propose to investigate the following specifications."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0cd11f66",
   "metadata": {},
   "source": [
    "1. Make the coefficients of elapsed time alternative specific.\n",
    "2. Interact the cost coefficient with income. Not that income is not available for all observations. Therefore, we\n",
    "propose to estimate a separate coefficient for observations where income is available. For other observations,\n",
    "define $$\\beta_\\text{fare} = \\beta_\\text{fare\\_base} / \\text{Cont\\_Income}.$$\n",
    "3. Investigate nonlinear specifications for the travel time variable:\n",
    "- Consider a piecewise linear specification. This model is available with Biogeme.\n",
    "- Consider a Box-Cox transform.\n",
    "- Consider a power series of degree 2."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c46b8b99",
   "metadata": {},
   "source": [
    "Note that Biogeme already implements the functions for piecewise linear specification, and Box-Cox transform."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d257a4a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(piecewise_formula)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ddf63a85",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(boxcox)"
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 5
}
