{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "74eb2486",
   "metadata": {},
   "source": [
    "\n",
    "File: 04-elasticities_solution.py\n",
    "\n",
    "\n",
    "Michel Bierlaire\n",
    "\n",
    "Sun Aug 03 2025, 16:06:29\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21fd6430",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "import pickle\n",
    "\n",
    "from IPython.core.display_functions import display\n",
    "from biogeme.biogeme import BIOGEME\n",
    "from biogeme.expressions import Derive, Expression\n",
    "from biogeme.models import logit, loglogit\n",
    "from biogeme.results_processing import (\n",
    "    EstimationResults,\n",
    "    get_pandas_estimated_parameters,\n",
    ")\n",
    "\n",
    "from optima_specification import v_base\n",
    "from optima_variables import (\n",
    "    Choice,\n",
    "    CostCarCHF,\n",
    "    MarginalCostPT,\n",
    "    TimeCar,\n",
    "    TimePT,\n",
    "    database,\n",
    "    distance_km,\n",
    "    normalizedWeight,\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "091593d2",
   "metadata": {},
   "source": [
    "The objective of this laboratory is to use an estimated choice model to calculate elasticities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac96d51c",
   "metadata": {},
   "outputs": [],
   "source": [
    "logprob = loglogit(v_base, None, Choice)\n",
    "model_name = 'optima_base'\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c9524bf",
   "metadata": {},
   "source": [
    "If the model has already been estimated, we read the results from file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "46883bdc",
   "metadata": {},
   "outputs": [],
   "source": [
    "biogeme = BIOGEME(database, logprob)\n",
    "biogeme.model_name = model_name\n",
    "results: EstimationResults = biogeme.estimate(run_bootstrap=True, recycle=True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb9c78b1",
   "metadata": {},
   "source": [
    "General statistics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "edc99e00",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(results.short_summary())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27bf676f",
   "metadata": {},
   "source": [
    "Estimated parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e7c48662",
   "metadata": {},
   "outputs": [],
   "source": [
    "display(get_pandas_estimated_parameters(results))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dacbc333",
   "metadata": {},
   "source": [
    "# Choice probabilities"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a7c64bc",
   "metadata": {},
   "outputs": [],
   "source": [
    "prob_PT: Expression = logit(v_base, None, 0)\n",
    "prob_CAR: Expression = logit(v_base, None, 1)\n",
    "prob_SM: Expression = logit(v_base, None, 2)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2f7b674",
   "metadata": {},
   "source": [
    "# Disaggregate elasticities"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2219d9d3",
   "metadata": {},
   "source": [
    "The direct point elasticity of travel time for public transportation is defined as\n",
    "$$ E_\\text{time}^{P_n(\\text{PT})} = \\frac{\\partial P_n(\\text{PT})}{\\partial \\text{time}}\n",
    "\\frac{\\text{time}}{P_n(\\text{PT})}.$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b6da5708",
   "metadata": {},
   "outputs": [],
   "source": [
    "direct_elas_pt_time = Derive(prob_PT, 'TimePT') * TimePT / prob_PT\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c12a6994",
   "metadata": {},
   "source": [
    "The direct point elasticity of travel cost for public transportation is defined as\n",
    "$$ E_\\text{cost}^{P_n(\\text{PT})} = \\frac{\\partial P_n(\\text{PT})}{\\partial \\text{cost}}\n",
    "\\frac{\\text{cost}}{P_n(\\text{PT})}.$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5db10759",
   "metadata": {},
   "outputs": [],
   "source": [
    "direct_elas_pt_cost = Derive(prob_PT, 'MarginalCostPT') * MarginalCostPT / prob_PT\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99c2d911",
   "metadata": {},
   "source": [
    "The direct point elasticity of travel time for car is defined as\n",
    "$$ E_\\text{time}^{P_n(\\text{car})} = \\frac{\\partial P_n(\\text{car})}{\\partial \\text{time}}\n",
    "\\frac{\\text{time}}{P_n(\\text{car})}.$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b020a89",
   "metadata": {},
   "outputs": [],
   "source": [
    "direct_elas_car_time = Derive(prob_CAR, 'TimeCar') * TimeCar / prob_CAR\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22eb2ad8",
   "metadata": {},
   "source": [
    "The direct point elasticity of travel cost for car is defined as\n",
    "$$ E_\\text{cost}^{P_n(\\text{car})} = \\frac{\\partial P_n(\\text{car})}{\\partial \\text{cost}}\n",
    "\\frac{\\text{cost}}{P_n(\\text{car})}.$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b190cce4",
   "metadata": {},
   "outputs": [],
   "source": [
    "direct_elas_car_cost = Derive(prob_CAR, 'CostCarCHF') * CostCarCHF / prob_CAR\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08119543",
   "metadata": {},
   "source": [
    "The direct point elasticity of distance for slow modes is defined as\n",
    "$$ E_\\text{distance}^{P_n(\\text{SM})} = \\frac{\\partial P_n(\\text{SM})}{\\partial \\text{distance}}\n",
    "\\frac{\\text{distance}}{P_n(\\text{SM})}.$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dea2f6aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "direct_elas_sm_dist = Derive(prob_SM, 'distance_km') * distance_km / prob_SM\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "441dbc16",
   "metadata": {},
   "source": [
    "Simulate the formulas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1699be4a",
   "metadata": {},
   "outputs": [],
   "source": [
    "simulate = {\n",
    "    'weight': normalizedWeight,\n",
    "    'Prob. car': prob_CAR,\n",
    "    'Prob. public transportation': prob_PT,\n",
    "    'Prob. slow modes': prob_SM,\n",
    "    'direct_elas_pt_time': direct_elas_pt_time,\n",
    "    'direct_elas_pt_cost': direct_elas_pt_cost,\n",
    "    'direct_elas_car_time': direct_elas_car_time,\n",
    "    'direct_elas_car_cost': direct_elas_car_cost,\n",
    "    'direct_elas_sm_dist': direct_elas_sm_dist,\n",
    "    'Marginal cost PT': MarginalCostPT,\n",
    "}\n",
    "\n",
    "filename = 'elasticities.pickle'\n",
    "try:\n",
    "    with open(filename, 'rb') as f:\n",
    "        simulated_values = pickle.load(f)\n",
    "        print(f'Elasticities read from {filename}')\n",
    "except FileNotFoundError:\n",
    "    biosim = BIOGEME(database, simulate)\n",
    "    simulated_values = biosim.simulate(results.get_beta_values())\n",
    "    print(f'Elasticities calculated and saved in {filename}')\n",
    "    with open(filename, 'wb') as f:\n",
    "        pickle.dump(simulated_values, f)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c17a2b2b",
   "metadata": {},
   "source": [
    "We first look at individual 0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a8f55195",
   "metadata": {},
   "outputs": [],
   "source": [
    "display(simulated_values.iloc[0])\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06a1b46e",
   "metadata": {},
   "source": [
    "- This individual does not pay for public transportation. Therefore, the elasticity with respect to the cost of public transportation is 0.\n",
    "- The elasticity of travel time for public transportation is quite low. If the travel time increases by 1%, the probability to choose that mode decreases by 0.12%. It means that it will drop from 0.4750 down to 0.4745. When the value of the elasticity is less than one, it is common to say that the demand is \"inelastic\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9088730d",
   "metadata": {},
   "source": [
    "Probability for public transportation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d36166ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "prob_pt = simulated_values.loc[simulated_values.index[0], 'Prob. public transportation']\n",
    "print(f'Prob. public transportation: {prob_pt:.3g}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c29bb7f9",
   "metadata": {},
   "source": [
    "Direct elasticity wrt travel time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2f009a19",
   "metadata": {},
   "outputs": [],
   "source": [
    "elast_pt_time = simulated_values.loc[simulated_values.index[0], 'direct_elas_pt_time']\n",
    "print(f'Direct elasticity wrt PT travel time: {elast_pt_time:.3g}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88433283",
   "metadata": {},
   "source": [
    "Probability if travel time increases by 1%."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca53b0c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_prob_pt = prob_pt * (100 + elast_pt_time) / 100\n",
    "print(f'Probability when travel time increases by 1%: {new_prob_pt:.3g}')\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ff5a2c9",
   "metadata": {},
   "source": [
    "- The elasticity of distance for slow modes is quite high. If the individual considers a trip that is 1% longer in\n",
    "distance, the probability to choose that mode decreases by 5.50%. As it is larger than 1, it is common to say that\n",
    "the demand is \"elastic\". However, as the initial probability to choose that mode is small, the absolute change is\n",
    "not large."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0614671",
   "metadata": {},
   "source": [
    "Probability for slow modes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "830e254b",
   "metadata": {},
   "outputs": [],
   "source": [
    "prob_sm = simulated_values.loc[simulated_values.index[0], 'Prob. slow modes']\n",
    "print(f'Prob. slow modes: {prob_sm:.3g}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4a065bb",
   "metadata": {},
   "source": [
    "Elasticity wrt distance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3e07b7e",
   "metadata": {},
   "outputs": [],
   "source": [
    "elast_sm_dist = simulated_values.loc[simulated_values.index[0], 'direct_elas_sm_dist']\n",
    "print(f'Direct elasticity wrt distance: {elast_sm_dist:.3g}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "411ea91c",
   "metadata": {},
   "source": [
    "Probability if distance increases."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77d00c93",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_prob_sm = prob_sm * (100 + elast_sm_dist) / 100\n",
    "print(f'Probability when distance increases by 1%: {new_prob_sm:.3g}')\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e82fcf9",
   "metadata": {},
   "source": [
    "We now look at individual 2."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "480f89af",
   "metadata": {},
   "outputs": [],
   "source": [
    "display(simulated_values.iloc[2])\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f16a880",
   "metadata": {},
   "source": [
    "- We note that the elasticity of travel cost for public transportation is larger than one (in absolute value).\n",
    "If the travel cost increases by 1%, the probability to choose public transportation decreases by 1.63%. It means that it will drop from 10.95% down to 10.77%. When the value of the elasticity is larger than one, it is common to say that the demand is \"elastic\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29556074",
   "metadata": {},
   "source": [
    "Probability for public transportation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8cee1f73",
   "metadata": {},
   "outputs": [],
   "source": [
    "prob_pt = simulated_values.loc[simulated_values.index[2], 'Prob. public transportation']\n",
    "print(f'Prob. public transportation: {prob_pt:.3g}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79d0d287",
   "metadata": {},
   "source": [
    "Elasticity wrt cost."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8f0b474",
   "metadata": {},
   "outputs": [],
   "source": [
    "elast_pt_cost = simulated_values.loc[simulated_values.index[2], 'direct_elas_pt_cost']\n",
    "print(f'Direct elasticity wrt cost: {elast_pt_cost:.3g}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21d26956",
   "metadata": {},
   "source": [
    "Probability if cost increases by 1%"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "368effcd",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_prob_pt = prob_pt * (100 + elast_pt_cost) / 100\n",
    "print(f'Probability when cost increases by 1%: {new_prob_pt:.3g}')\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8bcf6b76",
   "metadata": {},
   "source": [
    "# Aggregate elasticities"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df2cf604",
   "metadata": {},
   "source": [
    "Aggregate elasticities capture the relative change at the level of the market shares. They can be derived from\n",
    "disaggregate elasticities using the following formula:\n",
    "$$E^{\\widehat{population_shares}(i)}_{x_{jk}}  =\\frac{1}{\\sum_{\\ell=1}^{N} \\omega_\\ell P_\\ell(i)}\\sum_{n=1}^{N}\\omega_n\n",
    "P_n(i) E^{P_n(i)}_{x_{jnk}}.$$\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60c54845",
   "metadata": {},
   "source": [
    "We calculate the aggregate elasticities for travel cost and travel time for public transportation."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d624b8be",
   "metadata": {},
   "source": [
    "Numerator for cost."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7fb1ecab",
   "metadata": {},
   "outputs": [],
   "source": [
    "simulated_values['numerator_pt_cost'] = (\n",
    "    simulated_values['weight']\n",
    "    * simulated_values['Prob. public transportation']\n",
    "    * simulated_values['direct_elas_pt_cost']\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92dc2894",
   "metadata": {},
   "source": [
    "Numerator for travel time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d04eb2ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "simulated_values['numerator_pt_time'] = (\n",
    "    simulated_values['weight']\n",
    "    * simulated_values['Prob. public transportation']\n",
    "    * simulated_values['direct_elas_pt_time']\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "520a4c73",
   "metadata": {},
   "source": [
    "Denominator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64b594a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "simulated_values['denominator_pt'] = (\n",
    "    simulated_values['weight'] * simulated_values['Prob. public transportation']\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52d1a1ce",
   "metadata": {},
   "source": [
    "Aggregate elasticity wrt cost"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "933c8e9e",
   "metadata": {},
   "outputs": [],
   "source": [
    "agg_elast_pt_cost = (\n",
    "    simulated_values['numerator_pt_cost'].sum()\n",
    "    / simulated_values['denominator_pt'].sum()\n",
    ")\n",
    "print(f'Aggregate elasticity wrt cost: {agg_elast_pt_cost:.3g}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b531833",
   "metadata": {},
   "source": [
    "Aggregate elasticity wrt travel time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "60fa95c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "agg_elast_pt_time = (\n",
    "    simulated_values['numerator_pt_time'].sum()\n",
    "    / simulated_values['denominator_pt'].sum()\n",
    ")\n",
    "print(f'Aggregate elasticity wrt travel time: {agg_elast_pt_time:.3g}')\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba26993b",
   "metadata": {},
   "source": [
    "We note that the demand is inelastic for both those indicators, as they are less than one (in absolute value)."
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 5
}
