diff --git a/notebooks/README_Alex.ipynb b/notebooks/README_Alex.ipynb new file mode 100644 index 0000000..9d56fda --- /dev/null +++ b/notebooks/README_Alex.ipynb @@ -0,0 +1,127 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Matching SBB and Timetable datasets \n", + "\n", + "_Note : This section summarize what is done in `hdfs_match_datasets.ipynb`_\n", + "\n", + "#### Get corresponding stop_id between two datasets \n", + "\n", + "We first look at the station names in timetable dataset. Stop_id can be given in multiple formats :\n", + "- `8502186` : the format defining the stop itself, which matches sbb `bpuic` field\n", + "\n", + "We will call the 3 next ones __Special cases__ throughout the notebook :\n", + "- `8502186:0:1` or `8502186:0:2` : The individual platforms are separated by “:”. A “platform” can also be a platform+sectors (e.g. “8500010:0:7CD”).\n", + "- `8502186P` : All the stops have a common “parent” “8500010P”.\n", + "- `8502186:0:Bfpl` : if the RBS uses it for rail replacement buses.\n", + "\n", + "source : [timetable cookbook](https://opentransportdata.swiss/en/cookbook/gtfs/), section stops.txt \n", + "\n", + "In the sbb actual_data we find equivalent to stop_id in its first format defining the station without platform information, in its `bpuic` field" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Get corresponding trip_id between two datasets \n", + "\n", + "In sbb dataset, the trip ids are defined by `FAHRT_BEZEICHNER` field and in timetable `trip_id`. We will use corresponding station_id and arrival_times in order to get corresponding trip_id. Our goal is to find a match in sbb dataset for _timetable_ trips (and not the other way around). So we will focus on getting this assymetrical correspondance table. \n", + "\n", + "The idea is to take every trip_id with more than X matches between the two datasets. We decided to use 2 as a minimum number of match needed -> this was required to be able to get InterCity / InterRegio trains, which have few stops in the 15km perimeter.\n", + "\n", + "These labels will be used to differentiate 3 different ways to compute probabilities :\n", + "- __One-to-one__ we find a clear match : we use distribution of delays on weekdays for a given trip/station_id based on all past sbb data. \n", + "- __One-to-many__ we find multiple match :\n", + " - Matches are aggregated together in the final distribution table\n", + "- __One-to-none__ we find no match : as described later, we will use delay distribution of similar trip (sharing stop_id, transport type and hour) to infer the delay." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Distributions of Delay Times per trip and station\n", + "\n", + "_Note : This summarize `hdfs_get_distributions.ipynb`_\n", + "\n", + "The goal of this chapter is to create a distribution of arrival delays for each station / trip_id pair, to be used later on to compute transfer probabilities. These are then used in McRaptor implementation, to choose the best trip according to their time but also their __probability of success__.\n", + "\n", + "#### Work from translation tables \n", + "\n", + "We will use data generated in `match_datasets.ipynb`, that matches trip_id between _timetable_ and _sbb_ dataset. We begin by looking at all trip_id that are found in both dataset with at least 5 stations in common.\n", + "\n", + "Our goal is to find a match in sbb dataset for all _timetable_ trips (and not the other way around). So we will focus on getting this assymetrical correspondance table. \n", + "\n", + "In order to do that, we need to do multiple join, as we want to join 3 tables : _sbb_ data which contains information about delays, `joined_trip_atL5_3` table which contains translation between trip_id in two datasets, and `stop_time` which contains all the unique stop_id x trip_id used for later steps.\n", + "- First, we join _sbb_ data `sbb_filt_forDelays_GeschaetzAndReal_2` with translation table `joined_trip_atL5_3` to get sbb data with information about _timetable_ trip_id. \n", + "- We can then use this _timetable_ trip_id to join this first table with `stop_time` table, using a _left_outer_ join, so that we get an idea of how many matches are found overall.\n", + "\n", + "First we load SBB data. Following cells were ran twice : once for `geschaetz` / `real` delays only, and once for `all` delays. \n", + "- `geschaetz` / `real` : load and use `/user/{}/sbb_filt_forDelays_GeschaetzAndReal_2.orc` table\n", + "- `all` : load and use `/user/{}/sbb_filt_forDelays_AllDelays.orc` table" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compute probability of transfer success from delays distributions\n", + "\n", + "_Note : This summarize `proba_functions.ipynb` and is run in local._\n", + "\n", + "To be able to compute the probability of success of a given transfert, we use the arrival delay distribution compared with the next trip departure. To be able to do that, we need delay distributions for each trip arrival to a given station. Whenever we have a clear match, we can use an __cumulative distribution function__ to compute $P(X \\leq x)$ :\n", + "\n", + "$${\\displaystyle F_{X}(x)=\\operatorname {P} (T\\leq t)=\\sum _{t_{i}\\leq t}\\operatorname {P} (T=t_{i})=\\sum _{t_{i}\\leq t}p(t_{i}).}$$\n", + "\n", + "The strategy was to rely entirely on past data we have to compute $p(t_i)$, without the need of building a model which imply making additionnal assumptions. If we have enough data for a given transfer with known trip_id x stop_id, we use the the abovementionned formula to compute each $p(t_i)$ by simply using :\n", + "\n", + "$$p(t_i) = \\frac{x_i}{\\sum x_i}$$\n", + "\n", + "with $x_i$ being the number of delays at time $t_i$ from SBB dataset.\n", + "\n", + "#### Recover missing data \n", + "\n", + "As we are using SBB data to compute delays from timetable trip_id, we may encounter problems with the translation between the two datasets (certain trip_id/stop_id have no correspondance datasets!). We may also encounter To recover missing or faulty data, the strategy is the following :\n", + "\n", + "1. If we have more than 100 data points in `real` group, we rely exclusively on its delay distribution to compute probabilities for a given transfer on a `trip_id x stop_id`.\n", + "\n", + "_Note : `real` group corresponds to arrival time with status `geschaetz` or `real`, meaning it comes from actual measurments._\n", + "\n", + "2. If we do not find enough data within `real` group, we use delay distributions in `all` group (contains all delays including `prognose` status), if there is more than 100 data points for a given `trip_id x stop_id`.\n", + "\n", + "3. If `all` group still does not have more than 100 data points, we rely on `recovery tables` to estimate delay distributions. The strategy is the following :\n", + " - As we will always know the `stop_id`, the `time` and the `transport_type`, we rely on arrival delays from aggregated values of similar transfer. \n", + " - First, we compute a table of distribution with all possible combination of `stop_id`, `time` (round to hours) and `transport_type`, and aggregate all the counts we have to compute cumulative distribution probabilities. \n", + " - Is there is less than 100 data points in one of these intersections, we use the last possibilities : a table with `transport_type` x `time` aggregate counts.\n", + " - The last values with no match are given the overall average of cumulative distribution probabilities for each `transport_type` with no limit for the minimum number of data points.\n", + "\n", + "Following this approach, we can find cumulative distribution probabilities for every combination of `trip_id x stop_id` as defined in `stop_times_df`. We will make a table with the same row order so that McRaptor can easily find their indexes. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/hdfs_get_distributions.ipynb b/notebooks/hdfs_get_distributions.ipynb index f9f1c9b..39087f9 100644 --- a/notebooks/hdfs_get_distributions.ipynb +++ b/notebooks/hdfs_get_distributions.ipynb @@ -1,1228 +1,1289 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "### Get Distributions of Delay Times per trip and station\n", "\n", "The goal of this chapter is to create a distribution of arrival delays for each station / trip_id pair, to be used later on to compute transfer probabilities. These are then used in McRaptor implementation, to choose the best trip according to their time but also their __probability of success__.\n", "\n", "
Any application without a proper name would be promptly killed.
" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 1, "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "A session has already been started. If you intend to recreate the session with new configurations, please include the -f argument.\n" - ] + "data": { + "text/html": [ + "Current session configs: {'conf': {'spark.app.name': 'lgptguys_final'}, 'kind': 'pyspark'}
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
8154application_1589299642358_2680pysparkidleLinkLink
8213application_1589299642358_2739pysparkidleLinkLink
8236application_1589299642358_2765pysparkidleLinkLink
8242application_1589299642358_2772pysparkidleLinkLink
8250application_1589299642358_2780pysparkidleLinkLink
8252application_1589299642358_2784pysparkidleLinkLink
8253application_1589299642358_2785pysparkidleLinkLink
8255application_1589299642358_2787pysparkidleLinkLink
8256application_1589299642358_2788pysparkidleLinkLink
8257application_1589299642358_2789pysparkidleLinkLink
8259application_1589299642358_2792pysparkidleLinkLink
8261application_1589299642358_2794pysparkbusyLinkLink
8262application_1589299642358_2795pysparkbusyLinkLink
8263application_1589299642358_2796pysparkidleLinkLink
8264application_1589299642358_2797pysparkidleLinkLink
8266application_1589299642358_2799pysparkidleLinkLink
8267application_1589299642358_2800sparkidleLinkLink
8268application_1589299642358_2803pysparkbusyLinkLink
8269application_1589299642358_2804pysparkidleLinkLink
8270application_1589299642358_2805pysparkidleLinkLink
8277application_1589299642358_2812pysparkbusyLinkLink
8278Nonepysparkshutting_down
8279application_1589299642358_2813pysparkbusyLinkLink
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ "%%configure\n", "{\"conf\": {\n", " \"spark.app.name\": \"lgptguys_final\"\n", "}}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Start Spark" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 2, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting Spark application\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
8280application_1589299642358_2814pysparkidleLinkLink
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SparkSession available as 'spark'.\n" + ] + }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stderr", "output_type": "stream", "text": [ "An error was encountered:\n", "unknown magic command '%spark'\n", "UnknownMagic: unknown magic command '%spark'\n", "\n" ] } ], "source": [ "# Initialization\n", "%%spark" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "An error was encountered:\n", "Variable named username not found.\n" ] } ], "source": [ "%%send_to_spark -i username -t str -n username" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Import useful libraries " ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from geopy.distance import great_circle\n", "from pyspark.sql.functions import *\n", "from pyspark.sql.types import StructType, StructField, StringType, IntegerType, LongType" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Read TimeTable data for routes / trips " ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "1407\n", "+---------------+\n", "|stop_id_general|\n", "+---------------+\n", - "| 8503078|\n", "| 8503088|\n", - "| 8589111|\n", - "| 8503376|\n", "| 8591190|\n", + "| 8591284|\n", + "| 8503078|\n", + "| 8502508|\n", "+---------------+\n", "only showing top 5 rows" ] } ], "source": [ "# Load data with stop_id of interest\n", "stop_times = spark.read.csv('data/lgpt_guys/stop_times_final_cyril.csv', header = True)\n", "stops_15km = stop_times.select(col('stop_id_general')).dropDuplicates()\n", "\n", "# print unique number of stop_id and show\n", "print stops_15km.count()\n", "stops_15km.show(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Read the [SBB actual data](https://opentransportdata.swiss/en/dataset/istdaten) in ORC format" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "sbb = spark.read.orc('/data/sbb/orc/istdaten')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Subset SBB data\n", "\n", "This notebook was ran twice to get two different set of distributions : \n", "- On one hand, delay distribution calculated only with delays having prognose status `geschaetz` or `real`. These are the distributions we use in priority whenever we have enough data in it for a given transfer.\n", "- On the other hand, delay distribution calculated with all delays from sbb, including status `prognose`, which means the delay is estimated. This is expected to be less precise, but whenever we have not enough data in `geschaetz` or `real`, this is still better than estimating ourself the delay.\n", "\n", "__Delays with geschaetz/real status__\n", "\n", "We take only stop_id in 15 km range from Zurich HB using `stop_id_general` field from _stops_15km_ file. Then we filter only `an_prognose_status` and `ab_prognose_status` set to `geschaetz` or `real`." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "10848628\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", "|fahrt_bezeichner|ankunftszeit |abfahrtszeit |an_prognose |ab_prognose |stop_id|\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", "|85:11:10:002 |03.09.2018 21:51| |03.09.2018 21:53:40| |8503000|\n", "|85:11:11:001 | |03.09.2018 06:09| |03.09.2018 06:10:22|8503000|\n", "|85:11:12:001 |03.09.2018 10:51| |03.09.2018 10:51:28| |8503000|\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", "only showing top 3 rows" ] } ], "source": [ "# Used to subset sbb table based on stop_id from stops_15km\n", "stop_id = stops_15km.select('stop_id_general').collect()\n", "stop_idx = [item.stop_id_general for item in stop_id]\n", "\n", "# Make the subset dataframe\n", "sbb_filt = sbb.filter( ( sbb['bpuic'].isin(stop_idx) ) &\\\n", " ((sbb.an_prognose_status == 'REAL') | \\\n", " (sbb.an_prognose_status == 'GESCHAETZ') | \\\n", " (sbb.ab_prognose_status == 'REAL') | \\\n", " (sbb.ab_prognose_status == 'GESCHAETZ') ) ) \\\n", " .select('fahrt_bezeichner', 'ankunftszeit', 'abfahrtszeit', \\\n", " 'an_prognose', 'ab_prognose', \\\n", " col('bpuic').alias('stop_id'))\n", "\n", "print sbb_filt.count()\n", "sbb_filt.show(3,False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Write subset table in HDFS for better performance during later usage" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# save\n", "sbb_filt.write.format(\"orc\").save(\"data/lgpt_guys/sbb_filt_forDelays_GeschaetzAndReal.orc\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "__Delay including prognose status__\n", "\n", "We take only stop_id in 15 km range from Zurich HB using `stop_id_general` field from _stops_15km_ file." ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "209398081\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", "|fahrt_bezeichner|ankunftszeit |abfahrtszeit |an_prognose |ab_prognose |stop_id|\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", "|85:11:10:002 |03.09.2018 21:51| |03.09.2018 21:53:40| |8503000|\n", "|85:11:11:001 | |03.09.2018 06:09| |03.09.2018 06:10:22|8503000|\n", "|85:11:12:001 |03.09.2018 10:51| |03.09.2018 10:51:28| |8503000|\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", "only showing top 3 rows" ] } ], "source": [ "# Used to subset sbb table based on stop_id from stops_15km\n", "stop_id = stops_15km.select('stop_id_general').collect()\n", "stop_idx = [item.stop_id_general for item in stop_id]\n", "\n", "# Make the subset dataframe\n", "sbb_filt_all = sbb.filter( sbb['bpuic'].isin(stop_idx) )\\\n", " .select('fahrt_bezeichner', 'ankunftszeit', 'abfahrtszeit', \\\n", " 'an_prognose', 'ab_prognose', \\\n", " col('bpuic').alias('stop_id'))\n", "\n", "print sbb_filt_all.count()\n", "sbb_filt_all.show(3,False)" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# save\n", "sbb_filt_all.write.format(\"orc\").save(\"data/lgpt_guys/sbb_filt_forDelays_AllDelays_2.orc\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Summary of tables writen in `data/lgpt_guys/` :\n", "- sbb_filt_forDelays_GeschaetzAndReal.orc : Use geschaetz and real, < 15km, final stops from cyril data\n", "- sbb_filt_forDelays_AllDelays_2.orc : stops < 15km, final stops from cyril data\n", "- sbb_filt_forDelays_AllDelays.orc : MISTAKE done , do NOT use this one !!\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Work from translation tables \n", "\n", "We will use data generated in `match_datasets.ipynb`, that matches trip_id between _timetable_ and _sbb_ dataset. We begin by looking at all trip_id that are found in both dataset with at least 5 stations in common.\n", "\n", "Our goal is to find a match in sbb dataset for all _timetable_ trips (and not the other way around). So we will focus on getting this assymetrical correspondance table. \n", "\n", "In order to do that, we need to do multiple join, as we want to join 3 tables : _sbb_ data which contains information about delays, `joined_trip_atL5_3` table which contains translation between trip_id in two datasets, and `stop_time` which contains all the unique stop_id x trip_id used for later steps.\n", "- First, we join _sbb_ data `sbb_filt_forDelays_GeschaetzAndReal_2` with translation table `joined_trip_atL5_3` to get sbb data with information about _timetable_ trip_id. \n", "- We can then use this _timetable_ trip_id to join this first table with `stop_time` table, using a _left_outer_ join, so that we get an idea of how many matches are found overall.\n", "\n", "First we load SBB data. Following cells were ran twice : once for `geschaetz` / `real` delays only, and once for `all` delays. \n", "- `geschaetz` / `real` : load and use `/user/{}/sbb_filt_forDelays_GeschaetzAndReal_2.orc` table\n", "- `all` : load and use `/user/{}/sbb_filt_forDelays_AllDelays.orc` table" ] }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "10848628\n", + "209398081\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", "|fahrt_bezeichner| ankunftszeit| abfahrtszeit| an_prognose| ab_prognose|stop_id|\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", - "| 85:11:10:002|12.10.2018 21:51| |12.10.2018 21:51:50| |8503000|\n", - "| 85:11:10293:004| |13.10.2018 00:25| |13.10.2018 00:26:08|8503000|\n", - "| 85:11:10293:004|13.10.2018 00:34|13.10.2018 00:35|13.10.2018 00:35:27|13.10.2018 00:36:44|8503016|\n", + "| 85:11:10:002|15.08.2018 21:51| |15.08.2018 21:54:44| |8503000|\n", + "| 85:11:11:001| |15.08.2018 06:09| |15.08.2018 06:10:13|8503000|\n", + "| 85:11:12:001|15.08.2018 10:51| |15.08.2018 10:54:39| |8503000|\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+\n", "only showing top 3 rows" ] } ], "source": [ - "# Load sbb data from /user/ folder with username \n", - "username = 'acoudray'\n", - "\n", "# Choose one table to work with \n", - "#table_delays = 'sbb_filt_forDelays_AllDelays_2'\n", - "table_delays = 'sbb_filt_forDelays_GeschaetzAndReal'\n", + "table_delays = 'sbb_filt_forDelays_AllDelays_2'\n", + "#table_delays = 'sbb_filt_forDelays_GeschaetzAndReal'\n", "\n", "# Load sbb data for a given table\n", "sbb_filt = spark.read.orc(\"data/lgpt_guys/{}.orc\".format(table_delays))\n", "\n", "# Print line count and show\n", "print(sbb_filt.count())\n", "sbb_filt.show(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then we load the translation table we made in `match_datasets` notebook. This give a table with matching trip_id between _timetable_ and _sbb_ data." ] }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "28690\n", - "+------------------------+---------------------+-----+\n", - "|trip_id |fahrt_bezeichner |count|\n", - "+------------------------+---------------------+-----+\n", - "|567.TA.26-10-j19-1.3.H |85:3849:88318-02010-1|22 |\n", - "|1412.TA.26-10-j19-1.11.R|85:3849:88213-02010-1|23 |\n", - "|89.TA.79-736-j19-1.5.H |85:773:6780-01736-1 |5 |\n", - "|984.TA.26-10-j19-1.8.R |85:3849:88474-02010-1|10 |\n", - "|514.TA.26-70-A-j19-1.3.R|85:849:58516-03070-1 |10 |\n", - "+------------------------+---------------------+-----+\n", + "518910\n", + "+--------------------------+----------------------+-----+\n", + "|trip_id |fahrt_bezeichner |count|\n", + "+--------------------------+----------------------+-----+\n", + "|2337.TA.26-14-A-j19-1.25.H|85:3849:417949-32014-1|13 |\n", + "|166.TA.26-812-j19-1.2.H |85:838:521719-26850-1 |10 |\n", + "|602.TA.26-33E-j19-1.4.H |85:849:423060-11412-1 |11 |\n", + "|502.TA.26-140-j19-1.5.R |85:807:471687-33131-1 |11 |\n", + "|799.TA.26-140-j19-1.6.H |85:807:534387-06131-1 |12 |\n", + "+--------------------------+----------------------+-----+\n", "only showing top 5 rows" ] } ], "source": [ "# Load data\n", - "joined_trip_atL5 = spark.read.csv('data/lgpt_guys/joined_trip_atL5_3.csv', header = True)\n", + "joined_trip_atL5 = spark.read.csv('data/lgpt_guys/joined_trip_atL5_4_full.csv', header = True)\n", "\n", "# Print line counts and show\n", "print joined_trip_atL5.count()\n", "joined_trip_atL5.show(5, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We first join sbb data `sbb_filt` with the translation table `joined_trip_atL5` to get trip_id in _timetable_ format on _sbb_ table." ] }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "11118291\n", + "237719057\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+-------+-----+\n", "|fahrt_bezeichner|ankunftszeit |abfahrtszeit |an_prognose |ab_prognose |stop_id|trip_id|count|\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+-------+-----+\n", - "|85:11:10:002 |12.10.2018 21:51| |12.10.2018 21:51:50| |8503000|null |null |\n", - "|85:11:10293:004 | |13.10.2018 00:25| |13.10.2018 00:26:08|8503000|null |null |\n", - "|85:11:10293:004 |13.10.2018 00:34|13.10.2018 00:35|13.10.2018 00:35:27|13.10.2018 00:36:44|8503016|null |null |\n", - "|85:11:10536:004 | |12.10.2018 20:03| |12.10.2018 20:04:20|8503000|null |null |\n", - "|85:11:10537:006 |12.10.2018 21:59| |12.10.2018 22:01:43| |8503000|null |null |\n", + "|85:11:10:002 |15.08.2018 21:51| |15.08.2018 21:54:44| |8503000|null |null |\n", + "|85:11:11:001 | |15.08.2018 06:09| |15.08.2018 06:10:13|8503000|null |null |\n", + "|85:11:12:001 |15.08.2018 10:51| |15.08.2018 10:54:39| |8503000|null |null |\n", + "|85:11:1251:003 |15.08.2018 07:00| |15.08.2018 07:02:35| |8503000|null |null |\n", + "|85:11:1252:001 |15.08.2018 21:23|15.08.2018 21:36|15.08.2018 21:25:53| |8503000|null |null |\n", "+----------------+----------------+----------------+-------------------+-------------------+-------+-------+-----+\n", "only showing top 5 rows" ] } ], "source": [ "joined_sbb = sbb_filt.join(joined_trip_atL5, on = ['fahrt_bezeichner'], how = 'left_outer')\n", "\n", "print joined_sbb.count()\n", "joined_sbb.show(5,False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The reference table we use is `stop_times`. We load it and use it as a reference in a join against sbb table which now contains trip_id in _timetable_ format." ] }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "260459\n", "+-------+----------------------+\n", "|stop_id|trip_id |\n", "+-------+----------------------+\n", "|8591371|742.TA.26-46-j19-1.8.R|\n", "|8591358|742.TA.26-46-j19-1.8.R|\n", "|8591158|742.TA.26-46-j19-1.8.R|\n", "+-------+----------------------+\n", "only showing top 3 rows" ] } ], "source": [ "# load ref table stop_times\n", "stop_times = spark.read.csv('data/lgpt_guys/stop_times_final_cyril.csv', header = True)\n", "\n", "# rename trip_id column\n", "stop_times = stop_times.select(stop_times.stop_id_general.alias('stop_id'), 'trip_id')\n", "\n", "# print line count and show \n", "print stop_times.count()\n", "stop_times.show(3, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We then make the join between our reference table and previous join containing sbb data. We can join them on `trip_id` column, which in both case corresponds to _timetable_ trip_id, and `stop_id` column." ] }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "2003073\n", - "+----------------------+-------+-----+----------------+------------+------------+-----------+-----------+\n", - "|trip_id |stop_id|count|fahrt_bezeichner|ankunftszeit|abfahrtszeit|an_prognose|ab_prognose|\n", - "+----------------------+-------+-----+----------------+------------+------------+-----------+-----------+\n", - "|1.TA.26-163-j19-1.1.R |8590688|null |null |null |null |null |null |\n", - "|1.TA.26-89-j19-1.1.R |8591209|null |null |null |null |null |null |\n", - "|10.TA.1-305-j19-1.1.R |8587018|null |null |null |null |null |null |\n", - "|10.TA.26-69-j19-1.2.H |8591122|null |null |null |null |null |null |\n", - "|10.TA.26-845-j19-1.2.H|8580879|null |null |null |null |null |null |\n", - "+----------------------+-------+-----+----------------+------------+------------+-----------+-----------+\n", + "156554643\n", + "+---------------------+-------+-----+--------------------+----------------+----------------+-------------------+-------------------+\n", + "|trip_id |stop_id|count|fahrt_bezeichner |ankunftszeit |abfahrtszeit |an_prognose |ab_prognose |\n", + "+---------------------+-------+-----+--------------------+----------------+----------------+-------------------+-------------------+\n", + "|1.TA.26-163-j19-1.1.R|8590688|5 |85:849:95054-01162-1|15.08.2018 19:54|15.08.2018 19:54|15.08.2018 19:54:29|15.08.2018 19:54:35|\n", + "|1.TA.26-163-j19-1.1.R|8590688|5 |85:849:95054-01162-1|02.02.2018 19:54|02.02.2018 19:54|02.02.2018 19:54:14|02.02.2018 19:54:20|\n", + "|1.TA.26-163-j19-1.1.R|8590688|5 |85:849:95054-01162-1|19.02.2018 19:54|19.02.2018 19:54|19.02.2018 19:54:20|19.02.2018 19:54:26|\n", + "|1.TA.26-163-j19-1.1.R|8590688|5 |85:849:95054-01162-1|24.01.2018 19:54|24.01.2018 19:54|24.01.2018 19:54:56|24.01.2018 19:55:02|\n", + "|1.TA.26-163-j19-1.1.R|8590688|5 |85:849:95054-01162-1|15.02.2018 19:54|15.02.2018 19:54|15.02.2018 19:56:26|15.02.2018 19:56:32|\n", + "+---------------------+-------+-----+--------------------+----------------+----------------+-------------------+-------------------+\n", "only showing top 5 rows" ] } ], "source": [ "# Do the \n", "stop_times_join = stop_times.join(joined_sbb, on=['trip_id', 'stop_id'], \n", " how='left_outer')\\\n", " .select('trip_id', 'stop_id', 'count',\n", " 'fahrt_bezeichner', 'ankunftszeit', 'abfahrtszeit',\n", " 'an_prognose', 'ab_prognose')\n", "\n", "print stop_times_join.count()\n", "stop_times_join.show(5, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We then compute arrival delays using the following approach : \n", "- arrival_true ( = `an_prognose`) - arrival_expected ( = `ankunftszeit`). Train being late have a positive delay and trains being ahead of schedule a negative one." ] }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "+-------+----------------------+-------------------+-------------+-------------+-------+\n", "|stop_id|trip_id |arrival_true |DiffInSeconds|DiffInMinutes|weekday|\n", "+-------+----------------------+-------------------+-------------+-------------+-------+\n", - "|8517377|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:04:56|-4 |0 |Wed |\n", - "|8502274|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:06:13|13 |0 |Wed |\n", - "|8502188|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:07:43|-17 |0 |Wed |\n", - "|8502275|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:09:45|-15 |0 |Wed |\n", - "|8502268|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:11:11|11 |0 |Wed |\n", - "|8502276|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:12:51|-9 |0 |Wed |\n", - "|8502187|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:16:15|15 |0 |Wed |\n", - "|8502277|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:17:23|23 |0 |Wed |\n", - "|8502278|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:19:43|43 |0 |Wed |\n", - "|8502186|56.TA.1-17-A-j19-1.5.H|2019-05-22 05:21:35|35 |0 |Wed |\n", + "|8517377|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:06:04|64 |1 |Wed |\n", + "|8502274|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:07:20|80 |1 |Wed |\n", + "|8502188|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:08:49|49 |0 |Wed |\n", + "|8502275|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:10:20|20 |0 |Wed |\n", + "|8502268|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:11:52|52 |0 |Wed |\n", + "|8502276|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:13:21|21 |0 |Wed |\n", + "|8502187|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:16:21|21 |0 |Wed |\n", + "|8502277|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:17:32|32 |0 |Wed |\n", + "|8502278|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:19:49|49 |0 |Wed |\n", + "|8502186|56.TA.1-17-A-j19-1.5.H|2018-08-15 05:21:54|54 |0 |Wed |\n", "+-------+----------------------+-------------------+-------------+-------------+-------+\n", "only showing top 10 rows" ] } ], "source": [ "stop_times_diff = stop_times_join.select( col(\"an_prognose\").alias(\"arrival_true\"),\\\n", " col(\"ankunftszeit\").alias(\"arrival_expected\"),\\\n", " 'trip_id', 'stop_id')\\\n", " .withColumn('arrival_true',to_timestamp(col('arrival_true'),\\\n", " format='dd.MM.yyyy HH:mm:ss'))\\\n", " .withColumn('arrival_expected',to_timestamp(col('arrival_expected'),\\\n", " format='dd.MM.yyyy HH:mm'))\\\n", " .withColumn('DiffInSeconds',col('arrival_true').cast(LongType()) - col('arrival_expected').cast(LongType()))\\\n", " .withColumn('DiffInMinutes',(col('DiffInSeconds')/60).cast('integer'))\\\n", " .select(\"stop_id\", \"trip_id\", \"arrival_true\", \"DiffInSeconds\", \"DiffInMinutes\",\\\n", " date_format('arrival_expected', 'E').alias('weekday'))\n", "\n", "# Remove Saturday and Sunday weekdays from table - show\n", "stop_times_diff = stop_times_diff.filter( (stop_times_diff.weekday != \"Sun\") & (stop_times_diff.weekday != \"Sat\") )\n", "stop_times_diff.show(10, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We get the difference between expected arrival time `ankunftszeit` and the actual arrival time `an_prognose` to compute the delay. This delay in seconds `DiffInSeconds` is then converted to a delay in minutes `DiffInMinutes` and converted to integer type. \n", "\n", "This `DiffInMinutes` is used in next step to do a pivot on the table, in order to get one column per unique value of `DiffInMinutes`. Before being able to do that, we bound the values contained in `DiffInMinutes` in the range [-1, +30] :\n", "- minimum 'delay' is -1 : it contains all arrivals ahead of schedule.\n", "- maximum delay is +30, it contains all delays > +30" ] }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "+-------+-------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", - "|stop_id|trip_id |-1 |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 |16 |17 |18 |19 |20 |21 |22 |23 |24 |25 |26 |27 |28 |29 |30 |\n", - "+-------+-------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", - "|8502273|101.TA.1-17-A-j19-1.9.R |2 |225|197|84 |57 |15 |4 |1 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "|8503087|197.TA.26-4-B-j19-1.11.R |0 |111|75 |17 |3 |2 |0 |1 |0 |0 |0 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "|8517376|197.TA.1-17-A-j19-1.16.R |0 |263|231|50 |18 |1 |0 |0 |1 |0 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "|8590271|143.TA.1-4-B-j19-1.10.H |0 |16 |7 |3 |2 |2 |0 |2 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "|8503052|238.TA.26-10-B-j19-1.10.R|0 |54 |75 |42 |21 |12 |5 |3 |3 |1 |1 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "+-------+-------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", + "+-------+-------------------------+---+----+----+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", + "|stop_id|trip_id |-1 |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 |16 |17 |18 |19 |20 |21 |22 |23 |24 |25 |26 |27 |28 |29 |30 |\n", + "+-------+-------------------------+---+----+----+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", + "|8591412|1935.TA.26-6-B-j19-1.11.R|0 |233 |169 |46 |13 |3 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", + "|8588002|63.TA.26-962-j19-1.2.H |0 |1600|1458|564|200|46 |12 |4 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |4 |\n", + "|8591416|543.TA.26-69-j19-1.3.R |1 |235 |113 |84 |16 |20 |7 |4 |3 |3 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", + "|8591279|43.TA.26-70-A-j19-1.3.R |3 |411 |252 |100|25 |3 |1 |1 |4 |13 |2 |0 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |1 |\n", + "|8591197|1285.TA.26-80-j19-1.8.R |0 |196 |185 |82 |21 |2 |3 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", + "+-------+-------------------------+---+----+----+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", "only showing top 5 rows" ] } ], "source": [ "# we bound distribution to this \n", "lower_bound = -1\n", "upped_bound = +30\n", "\n", "stop_times_bounded = stop_times_diff.withColumn('DiffInMinutes_bounded1',\\\n", " greatest(col('DiffInMinutes'), lit(lower_bound) ))\\\n", " .withColumn('DiffInMinutes_bounded2',\\\n", " least(col('DiffInMinutes_bounded1'), lit(upped_bound) ))\n", "\n", "stop_times_distribution = stop_times_bounded.groupBy('stop_id', 'trip_id')\\\n", " .pivot(\"DiffInMinutes_bounded2\").count()\\\n", " .na.fill(0)\n", "\n", "stop_times_distribution.show(5, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The last move is to compute a unique key per line, corresponding to `trip_id` x `stop_id`. " ] }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "+-------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", - "|key |-1 |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 |16 |17 |18 |19 |20 |21 |22 |23 |24 |25 |26 |27 |28 |29 |30 |\n", - "+-------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", - "|10.TA.1-11-B-j19-1.1.R__8590314|0 |2 |2 |1 |3 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "|10.TA.1-11-B-j19-1.1.R__8590317|0 |3 |2 |2 |3 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "|10.TA.1-11-B-j19-1.1.R__8594304|0 |0 |4 |4 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "|10.TA.1-11-B-j19-1.1.R__8594307|0 |1 |5 |3 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "|10.TA.1-11-B-j19-1.1.R__8594310|0 |1 |3 |4 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", - "+-------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", + "+------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", + "|key |-1 |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 |16 |17 |18 |19 |20 |21 |22 |23 |24 |25 |26 |27 |28 |29 |30 |\n", + "+------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", + "|1.TA.26-161-j19-1.1.H__8587347|0 |214|191|71 |23 |5 |0 |1 |0 |0 |1 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", + "|1.TA.26-161-j19-1.1.H__8590671|0 |82 |155|135|86 |28 |12 |5 |1 |0 |2 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", + "|1.TA.26-161-j19-1.1.H__8590677|0 |118|197|113|57 |16 |3 |1 |0 |0 |1 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", + "|1.TA.26-161-j19-1.1.H__8590678|0 |71 |140|150|88 |36 |12 |6 |1 |0 |2 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", + "|1.TA.26-161-j19-1.1.H__8590680|1 |216|163|74 |37 |9 |4 |0 |1 |0 |1 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 |\n", + "+------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n", "only showing top 5 rows" ] } ], "source": [ "stop_times_distrib_wKey = stop_times_distribution.orderBy('trip_id', 'stop_id')\\\n", " .withColumn('key2', concat(col('trip_id'), lit('__'), col('stop_id')))\\\n", " .drop('trip_id').drop('stop_id')\\\n", " .select(col('key2').alias('key'), \"*\")\\\n", " .drop('key2')\n", "\n", "stop_times_distrib_wKey.show(5, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get an idea of the number of lines we have related to the total number of unique `trip_id` x `stop_id`, we can compare it to the ref stop_time table, where each line correspond to a unique `trip_id` x `stop_id`" ] }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "reference table stop_times number of lines : 260459\n", - "distribution table number of unique keys : 5715" + "distribution table number of unique keys : 221945" ] } ], "source": [ "print \"reference table stop_times number of lines : {}\".format(stop_times.count())\n", "print \"distribution table number of unique keys : {}\".format(stop_times_distrib_wKey.select(\"key\").distinct().count())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Write tables on hdfs. Differente path depending on which table we are working with (`geschaetz`/`real` or `all`). " ] }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ - "which_distribution = 'geschaetzAndReal' # 'allDelays'\n", - "stop_times_distrib_wKey.write.csv('data/lgpt_guys/distribution_{}_4.csv'.format(which_distribution), \\\n", + "#which_distribution = 'geschaetzAndReal' # 'allDelays'\n", + "which_distribution = 'allDelays' \n", + "\n", + "stop_times_distrib_wKey.write.csv('data/lgpt_guys/distribution_{}_5.csv'.format(which_distribution), \\\n", " header = True, mode=\"overwrite\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Also write it on /user/ folder to be able to load it in local" ] }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "username = 'acoudray'\n", - "stop_times_distrib_wKey.write.csv('/user/{0}/distribution_{1}_4.csv'.format(username, which_distribution), \\\n", + "stop_times_distrib_wKey.write.csv('/user/{0}/distribution_{1}_5.csv'.format(username, which_distribution), \\\n", " header = True, mode=\"overwrite\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_Note : Last tables written_\n", "\n", - "- data/lgpt_guys/distribution_allDelays_4.csv : contains distribution delays from all SBB data including arrival time with `prognose` status. \n", - "- data/lgpt_guys/distribution_geschaetzAndReal_4.csv : contains distribution delays for arrival time with status `geschaetz` or `real` only." + "- data/lgpt_guys/distribution_allDelays_5.csv : contains distribution delays from all SBB data including arrival time with `prognose` status. Made with FULL sbb dataset.\n", + "- data/lgpt_guys/distribution_geschaetzAndReal_5.csv : contains distribution delays for arrival time with status `geschaetz` or `real` only. Made with FULL sbb dataset.\n", + "- data/lgpt_guys/distribution_allDelays_4.csv : contains distribution delays from all SBB data including arrival time with `prognose` status. Made with 13-17 May sbb dataset.\n", + "- data/lgpt_guys/distribution_geschaetzAndReal_4.csv : contains distribution delays for arrival time with status `geschaetz` or `real` only. Made with 13-17 May sbb dataset." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Use local python to make dictionnary on local" ] }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('10.TA.1-11-B-j19-1.1.R__8590314',\n", - " array([0, 2, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", + " array([ 0, 84, 54, 35, 24, 5, 3, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.1-11-B-j19-1.1.R__8590317',\n", - " array([0, 3, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", + " array([ 0, 96, 69, 36, 22, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.1-11-B-j19-1.1.R__8594304',\n", - " array([0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", + " array([ 0, 70, 78, 45, 22, 9, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.1-11-B-j19-1.1.R__8594307',\n", - " array([0, 1, 5, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", + " array([ 0, 87, 70, 43, 16, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.1-11-B-j19-1.1.R__8594310',\n", - " array([0, 1, 3, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", + " array([ 0, 77, 65, 33, 13, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.26-4-B-j19-1.1.R__8503086',\n", - " array([ 0, 67, 226, 131, 36, 7, 11, 5, 5, 3, 3, 2, 1,\n", - " 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0,\n", + " array([ 0, 134, 387, 197, 49, 9, 17, 6, 7, 4, 4, 3, 1,\n", + " 1, 1, 2, 1, 3, 2, 0, 1, 0, 0, 0, 0, 0,\n", " 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.26-4-B-j19-1.1.R__8503087',\n", - " array([ 0, 91, 204, 126, 40, 8, 10, 4, 5, 3, 3, 3, 0,\n", - " 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", + " array([ 0, 179, 340, 194, 56, 11, 14, 5, 7, 5, 4, 4, 0,\n", + " 1, 1, 2, 2, 2, 1, 2, 0, 0, 0, 0, 0, 0,\n", " 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.26-4-B-j19-1.1.R__8503088',\n", - " array([ 0, 132, 134, 90, 34, 19, 7, 4, 3, 3, 1, 4, 1,\n", - " 0, 0, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0,\n", + " array([ 1, 272, 214, 157, 52, 26, 8, 5, 4, 6, 2, 4, 1,\n", + " 0, 2, 2, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0,\n", " 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.26-4-B-j19-1.1.R__8503089',\n", - " array([ 0, 379, 58, 20, 11, 7, 8, 4, 5, 4, 0, 3, 0,\n", - " 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,\n", + " array([ 0, 652, 82, 28, 17, 13, 10, 4, 5, 4, 2, 3, 2,\n", + " 1, 0, 3, 2, 1, 0, 1, 0, 0, 0, 0, 0, 0,\n", " 0, 0, 0, 0, 0, 0])),\n", " ('10.TA.26-4-B-j19-1.1.R__8503090',\n", - " array([ 0, 72, 173, 144, 61, 20, 11, 2, 5, 4, 3, 3, 0,\n", - " 1, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0,\n", + " array([ 0, 148, 288, 225, 92, 26, 16, 3, 7, 4, 6, 3, 0,\n", + " 1, 2, 1, 2, 1, 2, 1, 1, 0, 0, 0, 0, 0,\n", " 0, 0, 0, 0, 0, 0]))]" ] }, - "execution_count": 103, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%local\n", "\n", "from hdfs3 import HDFileSystem\n", "import pandas as pd\n", "import numpy as np \n", "import pickle \n", "import gzip\n", "from itertools import islice\n", "\n", "hdfs = HDFileSystem(host='hdfs://iccluster044.iccluster.epfl.ch', port=8020, user='ebouille')\n", "\n", "username = 'acoudray'\n", - "which_distribution = 'geschaetzAndReal' # 'allDelays'\n", + "which_distribution = 'geschaetzAndReal'\n", + "#which_distribution = 'allDelays'\n", "\n", "# Load distribution file from HDFS and concatenate individual csv\n", - "distrib_files = hdfs.glob('/user/{0}/distribution_{1}_4.csv/*.csv'.format(username, which_distribution))\n", + "distrib_files = hdfs.glob('/user/{0}/distribution_{1}_5.csv/*.csv'.format(username, which_distribution))\n", "distrib = pd.DataFrame()\n", "for file in distrib_files:\n", " with hdfs.open(file) as f:\n", " distrib = distrib.append(pd.read_csv(f))\n", "distrib = distrib.set_index('key')\n", "\n", "# zip index and values to get {key : np.array()} shape \n", "d = dict(zip(distrib.index, np.array(distrib.values)))\n", "\n", "# Write it to local \n", "with gzip.open(\"../data/distributions_{0}.pkl.gz\".format(which_distribution), \"wb\") as output_file:\n", " pickle.dump(d, output_file)\n", "\n", "# Functon to take a slice from a dictionnary - head equivalent\n", "def take(n, iterable):\n", " \"Return first n items of the iterable as a list\"\n", " return list(islice(iterable, n))\n", "\n", "# display a slice of it\n", "take(10, d.items())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Analyse RAM usage and run time to get distribution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How many RAM does the dictionnary occupy when it is open ? Open pickle and calculate amount of memory occupied using _resource_ lib" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "length of dict : 246968\n", "Data size is: 106968218\n" ] } ], "source": [ "%local \n", "\n", "import pickle \n", "import gzip\n", "import sys\n", "import os\n", "import resource\n", "\n", "with gzip.open(\"../data/distributions.pickle\", \"rb\") as input_file:\n", " d = pickle.load(input_file)\n", " \n", "\n", "d['1290.TA.26-32-j19-1.12.H__8591151']\n", "print('length of dict : ',len(d))\n", "\n", "def getsizeof_r(obj):\n", " total = 0\n", " if isinstance(obj, list):\n", " for i in obj:\n", " total += getsizeof_r(i)\n", " elif isinstance(obj, dict):\n", " for k, v in obj.items():\n", " total += getsizeof_r(k) + getsizeof_r(v)\n", " else:\n", " total += sys.getsizeof(obj)\n", " return total\n", "\n", "print('Data size is: {}'.format(getsizeof_r(d)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How many time does it take to access elements in the dictionnary ?" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0 1008 405 207 95 39 25 11 5 3 0 0 0 0\n", " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0]\n", "running time to get value from key when exists : 0.0004305839538574219\n", "\n", "KEY ERROR: .26-32-j19-1.12.H__8591151 not found un distribution dictionnary\n", "running time to get error when key does NOT exists : 0.00010466575622558594\n", "\n" ] } ], "source": [ "%local\n", "\n", "import pickle \n", "import gzip\n", "import time\n", "\n", "def get_distribution(key, dico):\n", " if key in dico:\n", " print(dico[key])\n", " else:\n", " print(\"KEY ERROR: {} not found un distribution dictionnary\".format(key))\n", " \n", "with gzip.open(\"../data/distributions.pickle\", \"rb\") as input_file:\n", " d = pickle.load(input_file)\n", " \n", "this_key = '1290.TA.26-32-j19-1.12.H__8591151'\n", "\n", "start = time.time()\n", "get_distribution(this_key, d)\n", "end = time.time()\n", "print(\"running time to get value from key when exists : {}\\n\".format(end - start))\n", "\n", "start = time.time()\n", "get_distribution(this_key.replace('1290.TA',''), d)\n", "end = time.time()\n", "print(\"running time to get error when key does NOT exists : {}\\n\".format(end - start))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "when key exists we access it in $5\\cdot10^{-4}$ seconds and when it does not exists error message is displayed in $1\\cdot10^{-4}$ seconds. Should be more than enough to be called multiple time when using raptor." ] } ], "metadata": { "kernelspec": { "display_name": "PySpark", "language": "", "name": "pysparkkernel" }, "language_info": { "codemirror_mode": { "name": "python", "version": 3 }, "mimetype": "text/x-python", "name": "pyspark", "pygments_lexer": "python3" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/notebooks/hdfs_match_datasets.ipynb b/notebooks/hdfs_match_datasets.ipynb index 38dbeac..11a8fed 100644 --- a/notebooks/hdfs_match_datasets.ipynb +++ b/notebooks/hdfs_match_datasets.ipynb @@ -1,1255 +1,1052 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Match datasets \n", "\n", "### Name your spark application as `GASPAR_final` or `GROUP_NAME_final`.\n", "\n", "
Any application without a proper name would be promptly killed.
" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "Current session configs: {'conf': {'spark.app.name': 'lgptguys_final'}, 'kind': 'pyspark'}
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", - "
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
8044application_1589299642358_2564pysparkidleLinkLink
8154application_1589299642358_2680pysparkbusyLinkLink
8174application_1589299642358_2700pysparkidleLinkLink
8211application_1589299642358_2737pysparkidleLinkLink
8213application_1589299642358_2739pysparkidleLinkLink
8216application_1589299642358_2742pysparkidleLinkLink
8217application_1589299642358_2743pysparkidleLinkLink
8219application_1589299642358_2745pysparkidleLinkLink
8221application_1589299642358_2747pysparkidleLinkLink
8222application_1589299642358_2748pysparkidleLinkLink
8223application_1589299642358_2749pysparkidleLinkLink
8226application_1589299642358_2754pysparkdeadLinkLink
8230application_1589299642358_2759pysparkbusyLinkLink
8232application_1589299642358_2761pysparkidleLinkLink
8233application_1589299642358_2762pysparkidleLinkLink
8234application_1589299642358_2763pysparkbusyLinkLink
8235application_1589299642358_2764pysparkidleLinkLink
" + "IDYARN Application IDKindStateSpark UIDriver logCurrent session?8361application_1589299642358_2893pysparkidleLinkLink8374application_1589299642358_2906pysparkidleLinkLink8387application_1589299642358_2919pysparkidleLinkLink8390application_1589299642358_2922pysparkidleLinkLink8392application_1589299642358_2924pysparkidleLinkLink8393application_1589299642358_2925pysparkidleLinkLink8394application_1589299642358_2926pysparkidleLinkLink8395application_1589299642358_2927pysparkbusyLinkLink8397application_1589299642358_2929pysparkidleLinkLink8398application_1589299642358_2930pysparkidleLinkLink8400application_1589299642358_2932pysparkidleLinkLink8401application_1589299642358_2933pysparkidleLinkLink8403application_1589299642358_2935pysparkidleLinkLink8405application_1589299642358_2937pysparkidleLinkLink8407application_1589299642358_2939pysparkidleLinkLink8408application_1589299642358_2940pysparkidleLinkLink8409Nonepysparkstarting" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%configure\n", "{\"conf\": {\n", " \"spark.app.name\": \"lgptguys_final\"\n", "}}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Start Spark" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Starting Spark application\n" ] }, { "data": { "text/html": [ "\n", - "
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
8236application_1589299642358_2765pysparkidleLinkLink
" + "IDYARN Application IDKindStateSpark UIDriver logCurrent session?8410application_1589299642358_2942pysparkidleLinkLink✔" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "SparkSession available as 'spark'.\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stderr", "output_type": "stream", "text": [ "An error was encountered:\n", "unknown magic command '%spark'\n", "UnknownMagic: unknown magic command '%spark'\n", "\n" ] } ], "source": [ "# Initialization\n", "%%spark" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "An error was encountered:\n", "Variable named username not found.\n" ] } ], "source": [ "%%send_to_spark -i username -t str -n username" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Import useful libraries " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from geopy.distance import great_circle\n", "from pyspark.sql.functions import *\n", "from pyspark.sql.types import StructType, StructField, StringType, IntegerType,LongType, TimestampType" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Read TimeTable curated data\n", "\n", "contains only stops / trips in a 15km range from Zurich HB" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Load data with stop_id of interest\n", "stop_times = spark.read.csv('data/lgpt_guys/stop_times_final_cyril.csv', header = True)\n", "stops_15km = stop_times.select(col('stop_id_general')).dropDuplicates()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Read the [SBB actual data](https://opentransportdata.swiss/en/dataset/istdaten) in ORC format" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "['betriebstag', 'fahrt_bezeichner', 'betreiber_id', 'betreiber_abk', 'betreiber_name', 'produkt_id', 'linien_id', 'linien_text', 'umlauf_id', 'verkehrsmittel_text', 'zusatzfahrt_tf', 'faellt_aus_tf', 'bpuic', 'haltestellen_name', 'ankunftszeit', 'an_prognose', 'an_prognose_status', 'abfahrtszeit', 'ab_prognose', 'ab_prognose_status', 'durchfahrt_tf']" ] } ], "source": [ "sbb = spark.read.orc('/data/sbb/orc/istdaten')\n", "sbb.columns" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Subset SBB data\n", "\n", "We take only stop_id in 15 km range from Zurich HB - Then, we want to write an intermidate table to avoid doing the computation on the whole SBB dataset." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Used to subset sbb table based on stop_id from stops_15km\n", "stop_id = stops_15km.select('stop_id_general').collect()\n", "stop_idx = [item.stop_id_general for item in stop_id]\n", "\n", "# Make the subset dataframe\n", "sbb_filt = sbb.filter( sbb['bpuic'].isin(stop_idx) )\\\n", " .select('fahrt_bezeichner','haltestellen_name', 'produkt_id',\\\n", " 'ankunftszeit', 'abfahrtszeit', 'betriebstag',\\\n", " col('bpuic').alias('stop_id'))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "1341" ] } ], "source": [ "print sbb_filt.select('stop_id').distinct().count()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the number of stop_id that are found in sbb dataset based on _timetable_ dataset. 70 stop_id are missing." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_Optional : Now we want to subset to take only the schedule of May 13-17, 2019. The goal is to find trip_id that matches because they share sufficient number of stops at the same time._ " ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "1657153\n", "+----------------+-----------------+----------+----------------+----------------+-----------+-------+\n", "|fahrt_bezeichner|haltestellen_name|produkt_id|ankunftszeit |abfahrtszeit |betriebstag|stop_id|\n", "+----------------+-----------------+----------+----------------+----------------+-----------+-------+\n", "|85:11:10:001 |Zürich HB |Zug |14.05.2019 21:50| |14.05.2019 |8503000|\n", "|85:11:1007:001 |Zürich HB |Zug |14.05.2019 06:23| |14.05.2019 |8503000|\n", "|85:11:1009:001 |Zürich HB |Zug |14.05.2019 07:23| |14.05.2019 |8503000|\n", "|85:11:1011:001 |Zürich HB |Zug |14.05.2019 08:23| |14.05.2019 |8503000|\n", "|85:11:10190:001 |Zürich Flughafen |Zug |14.05.2019 22:46|14.05.2019 22:48|14.05.2019 |8503016|\n", "+----------------+-----------------+----------+----------------+----------------+-----------+-------+\n", "only showing top 5 rows" ] } ], "source": [ "sbb_subTime = sbb_filt.filter( (sbb_filt.betriebstag == '13.05.2019') | \\\n", " (sbb_filt.betriebstag == '14.05.2019') | \\\n", " (sbb_filt.betriebstag == '15.05.2019') | \\\n", " (sbb_filt.betriebstag == '16.05.2019') | \\\n", " (sbb_filt.betriebstag == '17.05.2019') )\n", "print sbb_subTime.count()\n", "sbb_subTime.show(5,False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We write the resulting subset. This is important to avoid working on the whole dataset and only on a subset of it, which makes every run much faster. " ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# save\n", "username = 'acoudray'\n", "sbb_filt.write.format(\"orc\").save(\"/user/{}/sbb_filt2.orc\".format(username))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For these are the files previously added to /user/{}/ :\n", "- `sbb_filt2.orc` : every day with stations < 15km (Cyril final version)\n", "- `sbb_filt.orc` : every day with stations < 15km\n", "- `sbb_subTime.orc` : schedule of May 13-17, 2019, stations < 15km\n", "- `sbb_subTime2.orc` : schedule of May 13-17, 2019, stations < 15km (Cyril final version)\n", "- `sbb_subTime3.orc` : schedule of May 13-17, 2019, stations < 15km (Cyril final version)\n", "- `sbb_oneday.orc` : May 13th 2019 only, stations < 15km, `linien_id` field added" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Get corresponding stop_id between two datasets \n", "\n", "We first look at the station names in timetable dataset. Stop_id can be given in multiple formats :\n", "- `8502186` : the format defining the stop itself, which matches sbb `bpuic` field\n", "\n", "We will call the 3 next ones __Special cases__ throughout the notebook :\n", "- `8502186:0:1` or `8502186:0:2` : The individual platforms are separated by “:”. A “platform” can also be a platform+sectors (e.g. “8500010:0:7CD”).\n", "- `8502186P` : All the stops have a common “parent” “8500010P”.\n", "- `8502186:0:Bfpl` : if the RBS uses it for rail replacement buses.\n", "\n", "source : [timetable cookbook](https://opentransportdata.swiss/en/cookbook/gtfs/), section stops.txt \n", "\n", "In the sbb actual_data we find equivalent to stop_id in its first format defining the station without platform information, in its `bpuic` field" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Get corresponding trip_id between two datasets \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "### Get corresponding trip_id between two datasets \n", + "\n", "In sbb dataset, the trip ids are defined by `FAHRT_BEZEICHNER` field and in timetable `trip_id`. We will use corresponding station_id and arrival_times in order to get corresponding trip_id. Our goal is to find a match in sbb dataset for _timetable_ trips (and not the other way around). So we will focus on getting this assymetrical correspondance table. \n", "\n", "These labels will be used to differentiate 3 different ways to compute probabilities :\n", "- __One-to-one__ we find a clear match : we use distribution of delays on weekdays for a given trip/station_id based on all past sbb data. \n", "- __One-to-many__ we find multiple match :\n", - " - First we double check the matches, if we have the same type of transportation for example.\n", - " - If they seem to be correct, we can merge the trips from sbb and get the merged distribution of their delays.\n", - "- __One-to-none__ we find no match : as described later, we will use delay distribution of similar trip (sharing stop_id, transport time and hour) to infer the delay." + " - Matches are aggregated together in the final distribution table\n", + "- __One-to-none__ we find no match : as described later, we will use delay distribution of similar trip (sharing stop_id, transport type and hour) to infer the delay." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "__Timetable dataset__ \n", "\n", "We first load _timetable_ with curated trip_id, in a 15km radius from Zurich HB. " ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "260459\n", "+-----------+---------------+----------------------+-------+------------+--------------+-------------+------------------------+----------------+----------------+---------------+---------------+------------+--------------------+---------+----------+--------+----------+---------------------------+\n", "|route_id |stop_id_general|trip_id |stop_id|arrival_time|departure_time|stop_sequence|stop_name |stop_lat |stop_lon |trip_headsign |trip_short_name|direction_id|departure_first_stop|route_int|stop_count|stop_int|route_desc|monotonically_increasing_id|\n", "+-----------+---------------+----------------------+-------+------------+--------------+-------------+------------------------+----------------+----------------+---------------+---------------+------------+--------------------+---------+----------+--------+----------+---------------------------+\n", "|26-46-j19-1|8591371 |742.TA.26-46-j19-1.8.R|8591371|18:06:00 |18:06:00 |13 |Zürich, Singlistrasse |47.4051109214132|8.49349016415681|Zürich, Rütihof|2524 |1 |17:48:00 |1363 |18 |879 |Bus |1632087572480 |\n", "|26-46-j19-1|8591358 |742.TA.26-46-j19-1.8.R|8591358|18:07:00 |18:07:00 |14 |Zürich, Segantinistrasse|47.4074455475966|8.48996876824257|Zürich, Rütihof|2524 |1 |17:48:00 |1363 |18 |1025 |Bus |1632087572481 |\n", "|26-46-j19-1|8591158 |742.TA.26-46-j19-1.8.R|8591158|18:08:00 |18:08:00 |15 |Zürich, Giblenstrasse |47.4107284405996|8.485953298922 |Zürich, Rütihof|2524 |1 |17:48:00 |1363 |18 |704 |Bus |1632087572482 |\n", "+-----------+---------------+----------------------+-------+------------+--------------+-------------+------------------------+----------------+----------------+---------------+---------------+------------+--------------------+---------+----------+--------+----------+---------------------------+\n", "only showing top 3 rows" ] } ], "source": [ "# Load data \n", "stop_times = spark.read.csv('data/lgpt_guys/stop_times_final_cyril.csv', header = True)\n", "stop_times.withColumn('stop_id', col('stop_id_general'))\n", "\n", "# Print number of lines and show\n", "print stop_times.count()\n", "stop_times.show(3, False)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "+----------------------+-------+------------+--------------+\n", "|trip_id |stop_id|arrival_time|departure_time|\n", "+----------------------+-------+------------+--------------+\n", "|742.TA.26-46-j19-1.8.R|8591371|06:06 |06:06 |\n", "|742.TA.26-46-j19-1.8.R|8591358|06:07 |06:07 |\n", "|742.TA.26-46-j19-1.8.R|8591158|06:08 |06:08 |\n", "|742.TA.26-46-j19-1.8.R|8576241|06:09 |06:09 |\n", "|742.TA.26-46-j19-1.8.R|8591155|06:10 |06:10 |\n", "+----------------------+-------+------------+--------------+\n", "only showing top 5 rows" ] } ], "source": [ "# Make the subset dataframe\n", "stop_times_format = stop_times\\\n", " .select('trip_id', 'stop_id', \n", " unix_timestamp(stop_times.arrival_time, 'HH:mm:ss')\\\n", " .alias('arrival_time_ut'),\\\n", " unix_timestamp(stop_times.departure_time, 'HH:mm:ss')\\\n", " .alias('departure_time_ut') )\\\n", " .select('trip_id', 'stop_id', \n", " from_unixtime('arrival_time_ut')\\\n", " .alias('arrival_time_dty'),\n", " from_unixtime('departure_time_ut')\\\n", " .alias('departure_time_dty'))\\\n", " .select('trip_id', 'stop_id', \n", " date_format('arrival_time_dty', 'hh:mm')\\\n", " .alias('arrival_time'),\n", " date_format('departure_time_dty', 'hh:mm')\\\n", " .alias('departure_time'))\n", "\n", "stop_times_format.show(5, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have reformated `arrival_time` and `departure_time` to get it in `hh:mm` format. \n", "\n", "_Removed : We explored rounding time to recover eventual mismatches in time between datasets. As it did not make a big change, we conclude it is not needed and therefore simply round to the minute (round_factor = 60 seconds)._" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We want to rely only on stop_id with a defined departure_time and arrival_time, so we remove line with null in one of these two lines " - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "stop_times_filt = stop_times_format.filter(stop_times_format.departure_time.isNotNull() & \n", - " stop_times_format.arrival_time.isNotNull() )" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "+----------------------+-------+------------+--------------+\n", - "|trip_id |stop_id|arrival_time|departure_time|\n", - "+----------------------+-------+------------+--------------+\n", - "|742.TA.26-46-j19-1.8.R|8591371|06:06 |06:06 |\n", - "|742.TA.26-46-j19-1.8.R|8591358|06:07 |06:07 |\n", - "|742.TA.26-46-j19-1.8.R|8591158|06:08 |06:08 |\n", - "|742.TA.26-46-j19-1.8.R|8576241|06:09 |06:09 |\n", - "|742.TA.26-46-j19-1.8.R|8591155|06:10 |06:10 |\n", - "+----------------------+-------+------------+--------------+\n", - "only showing top 5 rows" - ] - } - ], - "source": [ - "stop_times_filt.show(5, False)" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is an example of a single trip_id : it goes from stop to stop and has various arrival / departure along its journey. As the times are rounded, they seem to be the same. Most probably the time between stops is very short." ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "+------------------------+-------+------------+--------------+\n", "|trip_id |stop_id|arrival_time|departure_time|\n", "+------------------------+-------+------------+--------------+\n", "|813.TA.26-33-B-j19-1.6.R|8503610|08:02 |08:02 |\n", "|813.TA.26-33-B-j19-1.6.R|8591214|08:03 |08:03 |\n", "|813.TA.26-33-B-j19-1.6.R|8591344|08:04 |08:04 |\n", "|813.TA.26-33-B-j19-1.6.R|8591331|08:05 |08:05 |\n", "|813.TA.26-33-B-j19-1.6.R|8591203|08:06 |08:06 |\n", "|813.TA.26-33-B-j19-1.6.R|8591236|08:08 |08:08 |\n", "|813.TA.26-33-B-j19-1.6.R|8591038|08:09 |08:09 |\n", "|813.TA.26-33-B-j19-1.6.R|8591177|08:12 |08:12 |\n", "|813.TA.26-33-B-j19-1.6.R|8591060|08:14 |08:14 |\n", "|813.TA.26-33-B-j19-1.6.R|8594239|08:15 |08:15 |\n", "+------------------------+-------+------------+--------------+\n", "only showing top 10 rows" ] } ], "source": [ - "stop_times_filt.filter(stop_times_format['trip_id'] == '813.TA.26-33-B-j19-1.6.R').show(10,False)" + "stop_times_format.filter(stop_times_format['trip_id'] == '813.TA.26-33-B-j19-1.6.R').show(10,False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will only use first 7 characters of `stop_id` field in order to get exact match with sbb dataset. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We use time rounded to quarter hour, so that unexact match between two datasets should not be a problem." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have _timetable_ trip_id, the stop_id as defined above, and arrival/departure time. The idea is to match these information with the ones we have in sbb dataset. The stop_id should match.\n", "\n", " __SBB dataset__\n", " \n", "We will subset sbb dataset to get only the 13th of May in sbb dataset :" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1657109" - ] - } - ], + "outputs": [], "source": [ "username='acoudray'\n", "sbb_subTime = spark.read.orc(\"/user/{}/sbb_filt2.orc\".format(username))\n", - "sbb_subTime.count()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "+----------------+-----------------+----------------+----------------+-----------+-------+\n", - "|fahrt_bezeichner|haltestellen_name| ankunftszeit| abfahrtszeit|betriebstag|stop_id|\n", - "+----------------+-----------------+----------------+----------------+-----------+-------+\n", - "| 85:11:10:001| Zürich HB|17.05.2019 21:50| | 17.05.2019|8503000|\n", - "| 85:11:1007:001| Zürich HB|17.05.2019 06:23| | 17.05.2019|8503000|\n", - "| 85:11:1009:001| Zürich HB|17.05.2019 07:23| | 17.05.2019|8503000|\n", - "| 85:11:1011:001| Zürich HB|17.05.2019 08:23| | 17.05.2019|8503000|\n", - "| 85:11:10190:001| Zürich Flughafen|17.05.2019 22:46|17.05.2019 22:48| 17.05.2019|8503016|\n", - "+----------------+-----------------+----------------+----------------+-----------+-------+\n", - "only showing top 5 rows" - ] - } - ], - "source": [ + "\n", "sbb_subTime.show(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We first convert time in string format 'hh:mm', same than in timetable." ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "+----------------+-------+------------+--------------+\n", "|fahrt_bezeichner|stop_id|arrival_time|departure_time|\n", "+----------------+-------+------------+--------------+\n", - "|85:11:10:001 |8503000|09:50 |null |\n", - "|85:11:1007:001 |8503000|06:23 |null |\n", - "|85:11:1009:001 |8503000|07:23 |null |\n", - "|85:11:1011:001 |8503000|08:23 |null |\n", - "|85:11:10190:001 |8503016|10:46 |10:48 |\n", + "|85:11:10:002 |8503000|09:51 |null |\n", + "|85:11:10293:004 |8503000|null |12:25 |\n", + "|85:11:10293:004 |8503016|12:34 |12:35 |\n", + "|85:11:10536:004 |8503000|null |08:03 |\n", + "|85:11:10537:006 |8503000|09:59 |null |\n", "+----------------+-------+------------+--------------+\n", "only showing top 5 rows" ] } ], "source": [ "# Make the subset dataframe\n", "sbb_filt = sbb_subTime.select('fahrt_bezeichner', 'stop_id',\\\n", " unix_timestamp(sbb_subTime.ankunftszeit, 'dd.MM.yyyy HH:mm')\\\n", " .alias('arrival_time_ut'),\\\n", " unix_timestamp(sbb_subTime.abfahrtszeit, 'dd.MM.yyyy HH:mm')\\\n", " .alias('departure_time_ut') )\\\n", " .select('fahrt_bezeichner', 'stop_id', \n", " from_unixtime('arrival_time_ut')\\\n", " .alias('arrival_time_dty'),\n", " from_unixtime('departure_time_ut')\\\n", " .alias('departure_time_dty'))\\\n", " .select('fahrt_bezeichner', 'stop_id', \n", " date_format('arrival_time_dty', 'hh:mm')\\\n", " .alias('arrival_time'),\n", " date_format('departure_time_dty', 'hh:mm')\\\n", " .alias('departure_time'))\n", "sbb_filt.show(5, False)" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "39218" + "864437" ] } ], "source": [ "sbb_filt.select(\"fahrt_bezeichner\").distinct().count()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's have a look at a single trip_id according to sbb dataset (called `fahrt_bezeichner`). " ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "+----------------+-------+------------+--------------+\n", "|fahrt_bezeichner|stop_id|arrival_time|departure_time|\n", "+----------------+-------+------------+--------------+\n", "|85:11:10:001 |8503000|09:50 |null |\n", "|85:11:10:001 |8503000|09:50 |null |\n", "|85:11:10:001 |8503000|09:50 |null |\n", "|85:11:10:001 |8503000|09:50 |null |\n", "|85:11:10:001 |8503000|09:50 |null |\n", - "+----------------+-------+------------+--------------+" + "|85:11:10:001 |8503000|09:50 |null |\n", + "|85:11:10:001 |8503000|09:50 |null |\n", + "|85:11:10:001 |8503000|09:50 |null |\n", + "|85:11:10:001 |8503000|09:50 |null |\n", + "|85:11:10:001 |8503000|09:50 |null |\n", + "+----------------+-------+------------+--------------+\n", + "only showing top 10 rows" ] } ], "source": [ "fahrt_bezeichner_example = '85:11:10:001'\n", "sbb_filt.filter(sbb_filt['fahrt_bezeichner'] == fahrt_bezeichner_example)\\\n", " .show(10,False)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Last step : we remove line with undefined departure_time or arrival_time " - ] - }, - { - "cell_type": "code", - "execution_count": 93, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1447145\n", - "+----------------+-------+------------+--------------+\n", - "|fahrt_bezeichner|stop_id|arrival_time|departure_time|\n", - "+----------------+-------+------------+--------------+\n", - "|85:11:10190:001 |8503016|10:46 |10:48 |\n", - "|85:11:10190:001 |8503000|10:58 |11:08 |\n", - "|85:11:1255:001 |8503000|08:26 |08:37 |\n", - "|85:11:1258:001 |8503000|09:23 |09:34 |\n", - "|85:11:14017:001 |8503011|05:24 |05:24 |\n", - "+----------------+-------+------------+--------------+\n", - "only showing top 5 rows" - ] - } - ], - "source": [ - "sbb_defined = sbb_filt.filter(sbb_filt.departure_time.isNotNull() &\n", - " sbb_filt.arrival_time.isNotNull() )\n", - "\n", - "print sbb_defined.count()\n", - "sbb_defined.show(5, False)" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "__Join two datasets on stop_id and time__\n", "\n", "We can now create a joined table using `stop_time_format` and `sbb_filt_format`. We use only stop_id and times to merge the tables. The idea is then to compare the trip_id from both dataset than end up on the same line. We use join with _left_outer_ so that we can only have _null_ values on the sbb side (assymetrical join)." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "+-------+------------+----------------------+---------------------+\n", - "|stop_id|arrival_time|trip_id |fahrt_bezeichner |\n", - "+-------+------------+----------------------+---------------------+\n", - "|8591251|06:13 |734.TA.26-46-j19-1.8.R|85:849:240560-10046-1|\n", - "|8591291|06:17 |733.TA.26-46-j19-1.8.R|85:849:240522-10046-1|\n", - "|8591247|06:32 |727.TA.26-46-j19-1.8.R|85:849:240236-10046-1|\n", - "|8576240|06:38 |727.TA.26-46-j19-1.8.R|85:3849:53470-03013-1|\n", - "|8591358|06:42 |727.TA.26-46-j19-1.8.R|85:849:246541-11046-1|\n", - "|8576240|06:46 |725.TA.26-46-j19-1.8.R|85:849:59942-03080-1 |\n", - "|8591291|06:41 |720.TA.26-46-j19-1.8.R|85:849:240329-10046-1|\n", - "|8591155|06:57 |720.TA.26-46-j19-1.8.R|85:849:246857-11046-1|\n", - "|8591379|06:44 |719.TA.26-46-j19-1.8.R|85:849:246680-11046-1|\n", - "|8591226|07:24 |710.TA.26-46-j19-1.8.R|85:849:240563-10046-1|\n", - "+-------+------------+----------------------+---------------------+\n", + "+-------+------------+------------------------+---------------------+\n", + "|stop_id|arrival_time|trip_id |fahrt_bezeichner |\n", + "+-------+------------+------------------------+---------------------+\n", + "|8500926|02:46 |37.TA.26-301-j19-1.1.R |85:849:448872-15301-1|\n", + "|8502268|02:31 |187.TA.1-17-A-j19-1.16.R|85:31:646:000 |\n", + "|8502268|12:26 |225.TA.1-17-A-j19-1.17.H|85:31:937:000 |\n", + "|8502270|12:49 |184.TA.1-17-A-j19-1.16.R|85:31:640:000 |\n", + "|8502270|12:49 |184.TA.1-17-A-j19-1.16.R|85:31:940:000 |\n", + "|8502274|08:21 |209.TA.1-17-A-j19-1.17.H|85:31:621:000 |\n", + "|8502274|11:06 |32.TA.1-17-A-j19-1.5.H |85:31:591:001 |\n", + "|8502495|02:22 |185.TA.26-70-A-j19-1.3.R|85:849:40590-13184-1 |\n", + "|8502495|04:37 |625.TA.26-185-j19-1.3.R |85:849:40596-13184-1 |\n", + "|8502495|06:23 |518.TA.26-70-A-j19-1.3.R|85:849:76091-02070-1 |\n", + "+-------+------------+------------------------+---------------------+\n", "only showing top 10 rows" ] } ], "source": [ "joined_trip_table = stop_times_format.join(sbb_filt,\\\n", " on=['stop_id', 'arrival_time'], \n", " how='inner')\\\n", " .select('stop_id', 'arrival_time',\n", " 'trip_id', 'fahrt_bezeichner')\\\n", " .distinct()\n", "joined_trip_table.show(10, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the raw results of the intersection. Note that we used a `distinct()` to avoid having multiple lines corresponding to multiple days. Each line must be a unqiue combination of stop_id x trip_id, no matter which day it is. \n", "\n", "Now we can count how many stops (with same time) are shared between trip_id from _timetable_ and _sbb_ data." ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "", + "model_id": "bdc98b6fa98f45b3b63558f79d40ef2f", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "+--------------------------+----------------------+-----+\n", - "|trip_id |fahrt_bezeichner |count|\n", - "+--------------------------+----------------------+-----+\n", - "|2713.TA.26-31-j19-1.17.H |85:849:55091-29031-1 |1 |\n", - "|2436.TA.26-6-B-j19-1.26.H |85:3849:49846-03002-1 |1 |\n", - "|543.TA.1-2-A-j19-1.15.R |85:886:2214-0 |5 |\n", - "|689.TA.26-3-A-j19-1.2.H |85:3849:169312-07008-1|2 |\n", - "|151.TA.26-787-j19-1.1.R |85:773:226039-06787-1 |15 |\n", - "|260.TA.26-83-j19-1.2.H |85:849:94051-22089-1 |1 |\n", - "|557.TA.26-4-j19-1.20.R |85:3849:86436-02004-1 |14 |\n", - "|2415.TA.26-14-A-j19-1.25.H|85:849:92236-02067-1 |1 |\n", - "|586.TA.26-80-j19-1.3.H |85:849:59927-03080-1 |22 |\n", - "|243.TA.26-485-j19-1.5.R |85:773:5007-01485-1 |16 |\n", - "+--------------------------+----------------------+-----+\n", - "only showing top 10 rows" - ] } ], "source": [ - "joined_trip_filt = joined_trip_table.select(\"trip_id\", \"fahrt_bezeichner\")\\\n", + "cutoff_min_overlap = 2\n", + "\n", + "joined_trip_atL2 = joined_trip_table.select(\"trip_id\", \"fahrt_bezeichner\")\\\n", " .groupBy(\"trip_id\", \"fahrt_bezeichner\")\\\n", - " .count()\n", + " .count()\\\n", + " .filter(col('count') >= cutoff_min_overlap )\n", "\n", - "joined_trip_filt.show(10, False)" + "joined_trip_atL2.show(10, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This table indicates how many stop_id / departure_time / arrival_time were identical between `trip_id` (_timetable_ data) and `fahrt_bezeichner` (sbb data). The idea is to take every trip_id with more than X matches between the two datasets. We decided to use 3 as a minimum number of match needed." + "This table indicates how many stop_id / departure_time / arrival_time were identical between `trip_id` (_timetable_ data) and `fahrt_bezeichner` (sbb data). The idea is to take every trip_id with more than X matches between the two datasets. We decided to use 2 as a minimum number of match needed -> this was required to be able to get InterCity / InterRegio trains, which have few stops in the 15km perimeter." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try to use a threshold to only take intersection whenever there is at least X stops shared between the two trip_id version" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ - "+-------------------------+---------------------+-----+\n", - "|trip_id |fahrt_bezeichner |count|\n", - "+-------------------------+---------------------+-----+\n", - "|586.TA.26-80-j19-1.3.H |85:849:59927-03080-1 |22 |\n", - "|3630.TA.26-31-j19-1.22.R |85:849:66751-31411-1 |5 |\n", - "|226.TA.26-813-j19-1.3.H |85:838:108705-02850-1|7 |\n", - "|547.TA.26-80-j19-1.3.H |85:849:159697-02080-1|24 |\n", - "|737.TA.26-33-B-j19-1.6.R |85:849:55722-25033-1 |15 |\n", - "|623.TA.26-145-j19-1.1.R |85:807:104911-01131-1|13 |\n", - "|552.TA.26-4-j19-1.20.R |85:3849:50490-03004-1|14 |\n", - "|845.TA.26-11-A-j19-1.3.H |85:3849:53084-03011-1|22 |\n", - "|546.TA.26-136-j19-1.4.H |85:807:185966-20131-1|5 |\n", - "|89.TA.26-38-j19-1.2.H |85:849:56108-01038-1 |8 |\n", - "|1204.TA.26-75-A-j19-1.5.H|85:849:93246-02075-1 |16 |\n", - "|64.TA.26-813-j19-1.2.R |85:838:72067-03850-1 |7 |\n", - "|481.TA.26-10-j19-1.3.H |85:3849:52603-03010-1|22 |\n", - "|151.TA.26-787-j19-1.1.R |85:773:226039-06787-1|15 |\n", - "|1127.TA.26-5-B-j19-1.23.R|85:3849:50985-03005-1|11 |\n", - "|319.TA.1-2-A-j19-1.13.H |85:886:45922-0 |6 |\n", - "|1358.TA.26-2-A-j19-1.24.R|85:3849:49878-03002-1|16 |\n", - "|196.TA.26-89-j19-1.1.R |85:849:94216-22089-1 |8 |\n", - "|85.TA.26-4-B-j19-1.2.H |85:78:12575:002 |11 |\n", - "|1598.TA.26-10-j19-1.11.R |85:3849:52204-03009-1|8 |\n", - "+-------------------------+---------------------+-----+\n", + "+-------------------------+----------------------+-----+\n", + "|trip_id |fahrt_bezeichner |count|\n", + "+-------------------------+----------------------+-----+\n", + "|85.TA.26-4-B-j19-1.2.H |85:78:12575:002 |11 |\n", + "|79.TA.26-732-j19-1.1.H |85:773:293053-06731-1 |8 |\n", + "|189.TA.26-817-j19-1.3.R |85:838:647158-21850-1 |8 |\n", + "|1401.TA.26-131-j19-1.10.H|85:807:516102-01131-1 |6 |\n", + "|92.TA.26-650-j19-1.6.R |85:773:588783-15640-1 |11 |\n", + "|557.TA.26-4-j19-1.20.R |85:3849:86436-02004-1 |14 |\n", + "|305.TA.26-912-j19-1.8.H |85:849:528364-01912-1 |14 |\n", + "|516.TA.26-80-j19-1.3.H |85:849:148617-04080-1 |28 |\n", + "|571.TA.26-80-j19-1.3.H |85:849:485973-30080-1 |24 |\n", + "|243.TA.26-485-j19-1.5.R |85:773:5007-01485-1 |17 |\n", + "|217.TA.26-491-j19-1.3.R |85:773:15175-07491-1 |8 |\n", + "|64.TA.26-732-j19-1.1.H |85:773:1006-07731-1 |8 |\n", + "|3.TA.26-735-j19-1.1.H |85:773:12216-11733-1 |6 |\n", + "|3765.TA.26-8-C-j19-1.27.H|85:3849:166472-23008-1|19 |\n", + "|32.TA.26-314-j19-1.2.R |85:849:421456-61301-1 |7 |\n", + "|2080.TA.26-13-j19-1.24.H |85:3849:438872-32013-1|7 |\n", + "|1197.TA.26-759-j19-1.7.R |85:773:9751-01759-1 |21 |\n", + "|2508.TA.26-7-B-j19-1.17.H|85:3849:591887-23007-1|20 |\n", + "|516.TA.1-2-A-j19-1.16.R |85:886:110970-0 |5 |\n", + "|467.TA.26-185-j19-1.2.R |85:849:95552-01184-1 |8 |\n", + "+-------------------------+----------------------+-----+\n", "only showing top 20 rows" ] } ], "source": [ - "cutoff_min_overlap = 5\n", - "joined_trip_atL5 = joined_trip_filt.filter(col('count') >= cutoff_min_overlap )\n", - "joined_trip_atL5.show(20, False)" + "cutoff_min_overlap = 2\n", + "joined_trip_atL2 = joined_trip_filt.filter(col('count') >= cutoff_min_overlap )\n", + "joined_trip_atL2.show(20, False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Write results in csv format in general folder data/lgpt_guys" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ - "joined_trip_atL5.write.csv('data/lgpt_guys/joined_trip_atL5_3.csv', header = True, mode=\"overwrite\")" + "joined_trip_atL2.write.csv('data/lgpt_guys/joined_trip_atL2_5_full.csv', header = True, mode=\"overwrite\")" ] } ], "metadata": { "kernelspec": { "display_name": "PySpark", "language": "", "name": "pysparkkernel" }, "language_info": { "codemirror_mode": { "name": "python", "version": 3 }, "mimetype": "text/x-python", "name": "pyspark", "pygments_lexer": "python3" } }, "nbformat": 4, "nbformat_minor": 4 } diff --git a/notebooks/proba_functions.ipynb b/notebooks/proba_functions.ipynb index 188db3d..9cea583 100644 --- a/notebooks/proba_functions.ipynb +++ b/notebooks/proba_functions.ipynb @@ -1,1683 +1,1738 @@ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Compute probability of transfer success from delays distributions\n", "\n", "To be able to compute the probability of success of a given transfert, we use the arrival delay distribution compared with the next trip departure. To be able to do that, we need delay distributions for each trip arrival to a given station. Whenever we have a clear match, we can use an __cumulative distribution function__ to compute $P(X \\leq x)$ :\n", "\n", "$${\\displaystyle F_{X}(x)=\\operatorname {P} (T\\leq t)=\\sum _{t_{i}\\leq t}\\operatorname {P} (T=t_{i})=\\sum _{t_{i}\\leq t}p(t_{i}).}$$\n", "\n", "The strategy was to rely entirely on past data we have to compute $p(t_i)$, without the need of building a model which imply making additionnal assumptions. If we have enough data for a given transfer with known trip_id x stop_id, we use the the abovementionned formula to compute each $p(t_i)$ by simply using :\n", "\n", "$$p(t_i) = \\frac{x_i}{\\sum x_i}$$\n", "\n", "with $x_i$ being the number of delays at time $t_i$ from SBB dataset.\n", "\n", "### Recover missing data \n", "\n", "As we are using SBB data to compute delays from timetable trip_id, we may encounter problems with the translation between the two datasets (certain trip_id/stop_id have no correspondance datasets!). We may also encounter To recover missing or faulty data, the strategy is the following :\n", "\n", "1. If we have more than 100 data points in `real` group, we rely exclusively on its delay distribution to compute probabilities for a given transfer on a `trip_id x stop_id`.\n", "\n", "_Note : `real` group corresponds to arrival time with status `geschaetz` or `real`, meaning it comes from actual measurments._\n", "\n", "2. If we do not find enough data within `real` group, we use delay distributions in `all` group (contains all delays including `prognose` status), if there is more than 100 data points for a given `trip_id x stop_id`.\n", "\n", "3. If `all` group still does not have more than 100 data points, we rely on `recovery tables` to estimate delay distributions. The strategy is the following :\n", " - As we will always know the `stop_id`, the `time` and the `transport_type`, we rely on arrival delays from aggregated values of similar transfer. \n", " - First, we compute a table of distribution with all possible combination of `stop_id`, `time` (round to hours) and `transport_type`, and aggregate all the counts we have to compute cumulative distribution probabilities. \n", " - Is there is less than 100 data points in one of these intersections, we use the last possibilities : a table with `transport_type` x `time` aggregate counts.\n", " - The last values with no match are given the overall average of cumulative distribution probabilities for each `transport_type` with no limit for the minimum number of data points.\n", "\n", "Following this approach, we can find cumulative distribution probabilities for every combination of `trip_id x stop_id` as defined in `stop_times_df`. We will make a table with the same row order so that McRaptor can easily find their indexes. " ] }, { "cell_type": "code", - "execution_count": 212, + "execution_count": 245, "metadata": {}, "outputs": [], "source": [ "import pickle \n", "import gzip\n", "from itertools import islice\n", "import matplotlib as mlt \n", "import matplotlib.pyplot as plt\n", "import numpy as np \n", "import pandas as pd \n", "import math" ] }, { "cell_type": "code", - "execution_count": 213, + "execution_count": 246, "metadata": {}, "outputs": [], "source": [ "# Functon to take a slice from a dictionnary - head equivalent\n", "def take(n, iterable):\n", " \"Return first n items of the iterable as a list\"\n", " return list(islice(iterable, n))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Load dictionnaries of distributions" ] }, { "cell_type": "code", - "execution_count": 214, + "execution_count": 247, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "len dict_real : 5715\n", - "[('10.TA.1-11-B-j19-1.1.R__8590314', array([0, 2, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('10.TA.1-11-B-j19-1.1.R__8590317', array([0, 3, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('10.TA.1-11-B-j19-1.1.R__8594304', array([0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('10.TA.1-11-B-j19-1.1.R__8594307', array([0, 1, 5, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('10.TA.1-11-B-j19-1.1.R__8594310', array([0, 1, 3, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))]\n", - "len dict_all : 212152\n", - "[('1.TA.26-161-j19-1.1.H__8587347', array([ 0, 101, 99, 27, 10, 2, 0, 0, 0, 0, 0, 0, 0,\n", + "len dict_real : 6417\n", + "[('10.TA.1-11-B-j19-1.1.R__8590314', array([ 0, 84, 54, 35, 24, 5, 3, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('10.TA.1-11-B-j19-1.1.R__8590317', array([ 0, 96, 69, 36, 22, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('10.TA.1-11-B-j19-1.1.R__8594304', array([ 0, 70, 78, 45, 22, 9, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('10.TA.1-11-B-j19-1.1.R__8594307', array([ 0, 87, 70, 43, 16, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('10.TA.1-11-B-j19-1.1.R__8594310', array([ 0, 77, 65, 33, 13, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))]\n", + "len dict_all : 221945\n", + "[('1.TA.26-161-j19-1.1.H__8587347', array([ 0, 214, 191, 71, 23, 5, 0, 1, 0, 0, 1, 1, 0,\n", " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0])), ('1.TA.26-161-j19-1.1.H__8590671', array([ 0, 32, 72, 67, 46, 13, 6, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('1.TA.26-161-j19-1.1.H__8590677', array([ 0, 48, 94, 64, 24, 8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('1.TA.26-161-j19-1.1.H__8590678', array([ 0, 28, 62, 76, 47, 16, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), ('1.TA.26-161-j19-1.1.H__8590680', array([ 0, 97, 77, 41, 18, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))]\n" + " 0, 0, 0, 0, 0, 0])), ('1.TA.26-161-j19-1.1.H__8590671', array([ 0, 82, 155, 135, 86, 28, 12, 5, 1, 0, 2, 1, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0])), ('1.TA.26-161-j19-1.1.H__8590677', array([ 0, 118, 197, 113, 57, 16, 3, 1, 0, 0, 1, 1, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0])), ('1.TA.26-161-j19-1.1.H__8590678', array([ 0, 71, 140, 150, 88, 36, 12, 6, 1, 0, 2, 1, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0])), ('1.TA.26-161-j19-1.1.H__8590680', array([ 1, 216, 163, 74, 37, 9, 4, 0, 1, 0, 1, 1, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0]))]\n" ] } ], "source": [ "with gzip.open(\"../data/distributions_geschaetzAndReal.pkl.gz\", \"rb\") as input_file:\n", " d_real = pickle.load(input_file)\n", "\n", "with gzip.open(\"../data/distributions_allDelays.pkl.gz\", \"rb\") as input_file:\n", " d_all = pickle.load(input_file)\n", "\n", "# display a slice of it\n", "print('len dict_real : ', len(d_real))\n", "print(take(5, d_real.items()))\n", "\n", "# display a slice of it\n", "print('len dict_all : ', len(d_all))\n", "print(take(5, d_all.items()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Probability using cumulative distribution based on frequency of delays \n", "\n", "When we have __enough data__ and no ambiguity about `trip_id` and `stop_id` for a given distribution, then we can compute the probability $P(T \\leq t)$ for every $t$ (delay in minute). \n", "\n", "Let's take a __threshold of 100__ sample points (=number of time we could measure a delay) as a minimum number of points to use this approach. \n", "\n", "_How many keys in our distionnary of distribution have at least this number of samples ?_" ] }, { "cell_type": "code", - "execution_count": 123, + "execution_count": 248, "metadata": {}, "outputs": [], "source": [ "def plot_data_points_hist(dico):\n", " list_tot_points = []\n", " for key in dico:\n", " distrib = dico[key]\n", " list_tot_points.append(np.sum(distrib))\n", "\n", " tot_per_key = np.array(list_tot_points)\n", " binwidth = 100\n", " n_keys_less_than_binwidth = np.sum(np.array(tot_per_key < binwidth))\n", " perc_key_to_recover = round(100 * ( n_keys_less_than_binwidth / len(tot_per_key) ), 2)\n", " plt.figure(figsize = (10,5))\n", " plt.hist(tot_per_key, bins = range(min(tot_per_key), max(tot_per_key) + binwidth, binwidth))\n", " plt.title(\"Total number of data points per trip_id / stop_id key. N keys with less than {0} points: {1} ({2}%)\"\\\n", " .format(binwidth, n_keys_less_than_binwidth, perc_key_to_recover))\n", " plt.xlabel('n data points')\n", " plt.ylabel('n keys')\n", " return plt.show()" ] }, { "cell_type": "code", - "execution_count": 124, + "execution_count": 249, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plot_data_points_hist(d_all)" ] }, { "cell_type": "code", - "execution_count": 125, + "execution_count": 250, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plot_data_points_hist(d_real)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we generate a dictionnary with cumulative probability based on frequency of delays, for each keys in our reference dictionnary." ] }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 251, "metadata": {}, "outputs": [], "source": [ "def cumul_distri_probas_dict(dico):\n", " list_tot_points = []\n", " for key in dico:\n", " distrib = dico[key]\n", "\n", " # get total number of elements \n", " N = np.sum(distrib)\n", "\n", " # make cumulative distribution probabilities\n", " cdf_distrib = np.empty((len(distrib)), dtype=float)\n", " save_x = 0\n", " for x in range(len(distrib)):\n", " cdf_distrib[x] = float(distrib[x])/float(N) + float(save_x)/float(N)\n", " save_x += distrib[x]\n", "\n", " dico[key] = cdf_distrib\n", " return dico" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Construct recovery tables \n", "\n", "First approach is to simple sum up similar distribution to get a new distribution we can use. For that, we need to have transport type (`route_desc`), `time` (rounded to hour) and `stop_id` which are valid. We then make all combination of these tree parameters and get the associate distributions.\n", "\n", "First we need to reformat stoptimes table in order to get time rounded to the hour, correct stop_id format and type, generate `key` column using `trip_id` and `stop_id`, and droping unnecessary columns." ] }, { "cell_type": "code", "execution_count": 173, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.0%, 3.84%, 7.68%, 11.52%, 15.36%, 19.2%, 23.04%, 26.88%, 30.72%, 34.55%, 38.39%, 42.23%, 46.07%, 49.91%, 53.75%, 57.59%, 61.43%, 65.27%, 69.11%, 72.95%, 76.79%, 80.63%, 84.47%, 88.31%, 92.15%, 95.98%, 99.82%, " ] } ], "source": [ "with open(\"../data/stop_times_df_cyril.pkl\", \"rb\") as input_file:\n", " stoptimes = pickle.load(input_file)\n", "\n", "# Use stop_id_general as stop_id \n", "stoptimes['stop_id'] = stoptimes['stop_id_general'].apply(str)\n", "\n", "# Set same stoptimes index as distribution dict \n", "stoptimes['key'] = stoptimes['trip_id'] + '__' + stoptimes['stop_id']\n", "stoptimes = stoptimes.set_index('key')\n", "\n", "stoptimes = stoptimes[['trip_id','stop_id', 'route_desc', 'arrival_time', 'departure_time']]\n", "\n", "list_hours = []\n", "size_stop_times = stoptimes.shape[0]\n", "for x in range(size_stop_times):\n", " if (x % 10000) == 0 :\n", " print('{}%'.format(round(100*x/size_stop_times,2)), end = ', ')\n", " \n", " arr_time_hour = pd.to_datetime(stoptimes.iloc[x,:]['arrival_time']).hour\n", " if math.isnan(arr_time_hour): # if arrival is NaT, use departure time\n", " arr_time_hour = pd.to_datetime(stoptimes.iloc[x,:]['departure_time']).hour\n", " list_hours.append(int(arr_time_hour))\n", " \n", "stoptimes['hour'] = list_hours\n", "stoptimes = stoptimes.drop(columns=['trip_id', 'arrival_time', 'departure_time'])\n", "\n", "# Write this pickle to avoid re-running this above code all the time\n", "with gzip.open(\"../data/stop_times_wHour.pkl\", \"wb\") as output_file:\n", " pickle.dump(stoptimes, output_file) \n", " " ] }, { "cell_type": "code", - "execution_count": 215, + "execution_count": 270, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "(16673, 32)\n" + "(16895, 32)\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
0123456789...22232425262728293031
stop_idhourroute_desc
85009267Bus031449681108563826181640100002...0000000000
8Bus4276370011561221095400000037...00000100000
9Bus240721073311452016660000001...0000000002
10Bus0112041219947175110042200...0000000000
\n", "

4 rows × 32 columns

\n", "
" ], "text/plain": [ - " 0 1 2 3 4 5 6 7 8 9 ... 22 23 \\\n", - "stop_id hour route_desc ... \n", - "8500926 7 Bus 0 31 4 4 0 1 0 0 0 0 ... 0 0 \n", - " 8 Bus 4 27 5 4 0 0 0 0 0 0 ... 0 0 \n", - " 9 Bus 0 16 6 6 0 0 0 0 0 0 ... 0 0 \n", - " 10 Bus 0 11 5 1 1 0 0 2 0 0 ... 0 0 \n", + " 0 1 2 3 4 5 6 7 8 9 ... 22 \\\n", + "stop_id hour route_desc ... \n", + "8500926 7 Bus 49 681 108 56 38 26 18 16 4 2 ... 0 \n", + " 8 Bus 63 700 115 61 22 10 9 5 3 7 ... 0 \n", + " 9 Bus 2 407 210 73 31 14 5 2 0 1 ... 0 \n", + " 10 Bus 0 204 121 99 47 17 5 4 2 2 ... 0 \n", "\n", - " 24 25 26 27 28 29 30 31 \n", - "stop_id hour route_desc \n", - "8500926 7 Bus 0 0 0 0 0 0 0 0 \n", - " 8 Bus 0 0 0 0 0 0 0 0 \n", - " 9 Bus 0 0 0 0 0 0 0 2 \n", - " 10 Bus 0 0 0 0 0 0 0 0 \n", + " 23 24 25 26 27 28 29 30 31 \n", + "stop_id hour route_desc \n", + "8500926 7 Bus 0 0 0 0 0 0 0 0 0 \n", + " 8 Bus 0 0 0 1 0 0 0 0 0 \n", + " 9 Bus 0 0 0 0 0 0 0 0 2 \n", + " 10 Bus 0 0 0 0 0 0 0 0 0 \n", "\n", "[4 rows x 32 columns]" ] }, - "execution_count": 215, + "execution_count": 270, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with gzip.open(\"../data/stop_times_wHour.pkl\", \"rb\") as input_file:\n", " stoptimes = pickle.load(input_file)\n", "\n", "distrib_df = pd.DataFrame(d_all).transpose()\n", "stoptimes_df = pd.DataFrame(stoptimes)\n", "\n", "recovery_df = distrib_df.join(stoptimes_df)\n", "list_bins = [x for x in range(32)]\n", "\n", "recovery_df = recovery_df.groupby(['stop_id','hour', 'route_desc'])[list_bins].apply(lambda x : x.astype(float).sum())\n", "recovery_df = recovery_df.astype('int')\n", "\n", "print(recovery_df.shape)\n", "recovery_df.head(4)" ] }, { "cell_type": "code", - "execution_count": 217, + "execution_count": 271, "metadata": {}, "outputs": [], "source": [ "def plot_df_missing(df, max_bin = 10000):\n", " tot_per_key = np.array(df.sum(axis=1)).astype('int')\n", " binwidth = 100\n", " n_keys_less_than_binwidth = np.sum(np.array(tot_per_key < binwidth))\n", " perc_key_to_recover = round(100 * ( n_keys_less_than_binwidth / len(tot_per_key) ), 2)\n", " plt.figure(figsize = (10,5))\n", " plt.hist(tot_per_key, bins = range(min(tot_per_key), max_bin + binwidth, binwidth))\n", " plt.title(\"Total number of data points per stop_id / hour key. N keys with less than {0} points: {1} ({2}%)\"\\\n", " .format(binwidth, n_keys_less_than_binwidth, perc_key_to_recover))\n", " plt.xlabel('n data points')\n", " plt.ylabel('n keys')\n", " return plt.show()" ] }, { "cell_type": "code", - "execution_count": 218, + "execution_count": 272, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plot_df_missing(recovery_df)" ] }, - { - "cell_type": "code", - "execution_count": 232, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "MultiIndex([('8500926', 7, 'Bus'),\n", - " ('8500926', 8, 'Bus'),\n", - " ('8500926', 9, 'Bus'),\n", - " ('8500926', 10, 'Bus'),\n", - " ('8500926', 11, 'Bus'),\n", - " ('8500926', 12, 'Bus'),\n", - " ('8500926', 13, 'Bus'),\n", - " ('8500926', 14, 'Bus'),\n", - " ('8500926', 15, 'Bus'),\n", - " ('8500926', 16, 'Bus'),\n", - " ...\n", - " ('8596113', 10, 'Bus'),\n", - " ('8596113', 11, 'Bus'),\n", - " ('8596113', 12, 'Bus'),\n", - " ('8596113', 13, 'Bus'),\n", - " ('8596113', 14, 'Bus'),\n", - " ('8596113', 15, 'Bus'),\n", - " ('8596113', 16, 'Bus'),\n", - " ('8596113', 17, 'Bus'),\n", - " ('8596113', 18, 'Bus'),\n", - " ('8596113', 19, 'Bus')],\n", - " names=['stop_id', 'hour', 'route_desc'], length=16673)" - ] - }, - "execution_count": 232, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "recovery_df.index" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Make second recovery table\n", "\n", "Here only taking combination of `transport_type x hour`" ] }, { "cell_type": "code", - "execution_count": 220, + "execution_count": 259, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(39, 32)\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", "
0123456789...22232425262728293031
hourroute_desc
7Bus103621209665752197299952115061468282191711631678338983812041073322539805102193238779915730573998394182294313943...6030504773035531211013422772823032373322282721906931
S-Bahn9483056393524962273089013134123367242333197805105802914053717813751517922501176812640...161314531000263430119925733
Tram1009010698173616091227964587219451893645692539151328177277359594192831593011449847608227131192267063954...6860632258449441237190176209941581197461733004
8Bus938698118965305330729013727965330342171841210373620036788350761222694741040781451125208801106285574403360520332...2820402822123331359753552802772001812462733362206591
\n", "

4 rows × 32 columns

\n", "
" ], "text/plain": [ - " 0 1 2 3 4 5 6 7 \\\n", - "hour route_desc \n", - "7 Bus 10362 1209665 752197 299952 115061 46828 21917 11631 \n", - " S-Bahn 94830 56393 52496 22730 8901 3134 1233 672 \n", - " Tram 10090 1069817 361609 122796 45872 19451 8936 4569 \n", - "8 Bus 9386 981189 653053 307290 137279 65330 34217 18412 \n", + " 0 1 2 3 4 5 6 \\\n", + "hour route_desc \n", + "7 Bus 38120 4107332 2539805 1021932 387799 157305 73998 \n", + " S-Bahn 97805 105802 91405 37178 13751 5179 2250 \n", + " Tram 28177 2773595 941928 315930 114498 47608 22713 \n", + "8 Bus 36788 3507612 2269474 1040781 451125 208801 106285 \n", + "\n", + " 7 8 9 ... 22 23 24 25 26 27 28 \\\n", + "hour route_desc ... \n", + "7 Bus 39418 22943 13943 ... 342 277 282 303 237 332 228 \n", + " S-Bahn 1176 812 640 ... 26 34 30 11 9 9 2 \n", + " Tram 11922 6706 3954 ... 190 176 209 94 158 119 74 \n", + "8 Bus 57440 33605 20332 ... 355 280 277 200 181 246 273 \n", "\n", - " 8 9 ... 22 23 24 25 26 27 28 29 30 31 \n", - "hour route_desc ... \n", - "7 Bus 6783 3898 ... 60 30 50 47 7 30 35 53 12 1101 \n", - " S-Bahn 423 331 ... 16 13 14 5 3 1 0 0 0 9 \n", - " Tram 2539 1513 ... 68 60 63 22 58 44 9 4 4 1237 \n", - "8 Bus 10373 6200 ... 28 20 40 28 22 12 33 31 35 975 \n", + " 29 30 31 \n", + "hour route_desc \n", + "7 Bus 272 190 6931 \n", + " S-Bahn 5 7 33 \n", + " Tram 61 73 3004 \n", + "8 Bus 336 220 6591 \n", "\n", "[4 rows x 32 columns]" ] }, - "execution_count": 220, + "execution_count": 259, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with gzip.open(\"../data/stop_times_wHour.pkl\", \"rb\") as input_file:\n", " stoptimes = pickle.load(input_file)\n", " \n", "distrib_df = pd.DataFrame(d_all).transpose()\n", "distrib_to_rm = np.array(distrib_df.iloc[:,range(11)].sum(axis=1) == 11) # missing trips\n", "distrib_df = distrib_df.iloc[~distrib_to_rm,:]\n", "\n", "stoptimes_df = pd.DataFrame(stoptimes)\n", "\n", "recovery_df2 = distrib_df.join(stoptimes_df)\n", "list_bins = [x for x in range(32)]\n", "\n", "recovery_df2 = recovery_df2.groupby(['hour', 'route_desc'])[list_bins].apply(lambda x : x.astype(float).sum())\n", "recovery_df2 = recovery_df2.astype('int')\n", "print(recovery_df2.shape)\n", "recovery_df2.head(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Third recovery table \n", "\n", "Takes aggregated transport type distributions to make cumulative probabilities over minutes of delays" ] }, { "cell_type": "code", - "execution_count": 221, + "execution_count": 260, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(3, 32)\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", "
0123456789...22232425262728293031
route_desc
Bus9623210744574703401631398601302715566890276485149750872505545036326836683792237362401057613843886861928373951454516108303852193705...8967476946674884514265273791071958124984453144063942419435173662319263646
S-Bahn9373536430614839951779376585924168968948203006204697839011055628261223003721087614103117883917057314066...100946952342027614183180115877356493642246
Tram89309982531136580451303349508626218972102448517822955018004247447254977569524532337192412894515537342624231354007763847291...679597563513375350227179188122591986195317941508127813211018102186733074
\n", "

3 rows × 32 columns

\n", "
" ], "text/plain": [ - " 0 1 2 3 4 5 6 \\\n", - "route_desc \n", - "Bus 96232 10744574 7034016 3139860 1302715 566890 276485 \n", - "S-Bahn 937353 643061 483995 177937 65859 24168 9689 \n", - "Tram 89309 9825311 3658045 1303349 508626 218972 102448 \n", + " 0 1 2 3 4 5 6 \\\n", + "route_desc \n", + "Bus 363268 36683792 23736240 10576138 4388686 1928373 951454 \n", + "S-Bahn 978390 1105562 826122 300372 108761 41031 17883 \n", + "Tram 247447 25497756 9524532 3371924 1289451 553734 262423 \n", "\n", - " 7 8 9 ... 22 23 24 25 26 27 28 29 \\\n", - "route_desc ... \n", - "Bus 149750 87250 55450 ... 896 747 694 667 488 451 426 527 \n", - "S-Bahn 4820 3006 2046 ... 100 94 69 52 34 20 27 6 \n", - "Tram 51782 29550 18004 ... 679 597 563 513 375 350 227 179 \n", + " 7 8 9 ... 22 23 24 25 26 27 \\\n", + "route_desc ... \n", + "Bus 516108 303852 193705 ... 5812 4984 4531 4406 3942 4194 \n", + "S-Bahn 9170 5731 4066 ... 183 180 115 87 73 56 \n", + "Tram 135400 77638 47291 ... 1986 1953 1794 1508 1278 1321 \n", "\n", - " 30 31 \n", - "route_desc \n", - "Bus 379 10719 \n", - "S-Bahn 14 73 \n", - "Tram 188 12259 \n", + " 28 29 30 31 \n", + "route_desc \n", + "Bus 3517 3662 3192 63646 \n", + "S-Bahn 49 36 42 246 \n", + "Tram 1018 1021 867 33074 \n", "\n", "[3 rows x 32 columns]" ] }, - "execution_count": 221, + "execution_count": 260, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with gzip.open(\"../data/stop_times_wHour.pkl\", \"rb\") as input_file:\n", " stoptimes = pickle.load(input_file)\n", " \n", "distrib_df = pd.DataFrame(d_all).transpose()\n", "\n", "stoptimes_df = pd.DataFrame(stoptimes)\n", "\n", "recovery_df3 = distrib_df.join(stoptimes_df)\n", "list_bins = [x for x in range(32)]\n", "\n", "recovery_df3 = recovery_df3.groupby(['route_desc'])[list_bins].apply(lambda x : x.astype(float).sum())\n", "recovery_df3 = recovery_df3.astype('int')\n", "print(recovery_df3.shape)\n", "recovery_df3.head(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Overall aggregate distribution (used when it fails)" ] }, { "cell_type": "code", - "execution_count": 229, + "execution_count": 261, "metadata": {}, "outputs": [], "source": [ "last_chance_distrib = np.array(recovery_df3.sum(axis=0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Reconstruct cumulative distribution probabilities from multiple distributions to recover data with few/missing points \n", "\n", "At this point, we have 2 dictionnaries of distributions and 3 recovery dataframes :\n", "\n", " - `d_real` : contains delay distribution for each keys in form `trip_id + __ + stop_id` calculated from delays with status `geschaetz` or `real` in sbb datasets.\n", " - `d_all` : contains delay distributions for each keys in form `trip_id + __ + stop_id`. No filter was applied on status (contains `geschaetz`, `real` __and__ `prognose` = evaluated delay).\n", " - `recovery_df` : contains aggregated delay distributions for each combination of `stop_id`, `route_desc` (transport type) and `hour` (time rounded to hour). \n", " - `recovery_df2` : contains aggregated delay distributions for each combination of `route_desc` (transport type) and `hour` (time rounded to hour). \n", " - `recovery_df3` : contains aggregated delay distributions for `route_desc` (transport type) \n", " - `last_chance_distrib` : contains aggregated delay from all lines\n", " \n", "We will now use these in order to reconstruct the final table with $P(T\\leq t_i)$ for each time points between -1 and +30, using a cumulative probability function as mentionned above." ] }, { "cell_type": "code", - "execution_count": 237, + "execution_count": 276, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.0%, 3.84%, 7.68%, 11.52%, 15.36%, 19.2%, 23.04%, 26.88%, 30.72%, 34.55%, 38.39%, 42.23%, 46.07%, 49.91%, 53.75%, 57.59%, 61.43%, 65.27%, 69.11%, 72.95%, 76.79%, 80.63%, 84.47%, 88.31%, 92.15%, 95.98%, 99.82%, " ] } ], "source": [ "###################### MAKE CUMULATIVE PROBABILITY TABLE #######################\n", "\n", "# Load stop_time table, to use its order as a template for our final table \n", "with open(\"../data/stop_times_df_cyril.pkl\", \"rb\") as input_file:\n", " stoptimes = pickle.load(input_file)\n", " \n", + "# declare empty variables \n", + "size_stop_times = stoptimes.shape[0]\n", + "n_fail = n_real = n_all = n_recov1 = n_recov2 = n_recov3 = i = 0\n", + "all_distrib, all_transport_type, all_hours, all_keys, all_failure_keys, all_data_origin = \\\n", + " ([] for i in range(6))\n", + "\n", + "# Get useful indexes\n", "stop_id_idx = stoptimes.columns.get_loc(\"stop_id_general\")\n", "trip_id_idx = stoptimes.columns.get_loc(\"trip_id\")\n", "transport_type_idx = stoptimes.columns.get_loc(\"route_desc\")\n", "\n", - "summary_df = pd.DataFrame(columns = ['key', 'key_int', 'trip_id', 'stop_id', 'transport_type', 'hour', 'distribution'])\n", - "n_fail = 0\n", - "size_stop_times = stoptimes.shape[0]\n", - "n_real = 0\n", - "n_all = 0\n", - "n_recov1 = 0\n", - "n_recov2 = 0\n", - "n_recov3 = 0\n", - "all_distrib = []\n", - "all_transport_type = []\n", - "all_hours = []\n", - "all_keys = []\n", - "\n", - "i = 0\n", "for index, row in stoptimes.iterrows():\n", " \n", " trip_id = row[trip_id_idx]\n", " stop_id = str(row[stop_id_idx])\n", " transport_type = row[transport_type_idx]\n", " key = str(trip_id) + '__' + str(stop_id)\n", "\n", " # Compute rounded hour using arrival if possible - recover with departure\n", " hour = pd.to_datetime(stoptimes.loc[index]['arrival_time']).hour\n", " if math.isnan(hour): # if arrival is NaT, use departure time\n", " hour = pd.to_datetime(stoptimes.loc[index]['departure_time']).hour\n", " \n", " distrib = np.zeros(31)\n", " keep_trying = True\n", " \n", " # 1) try d_real to get distribution from measured delays\n", " if key in d_real:\n", " distrib = d_real[key]\n", " sum_distrib = np.sum(distrib)\n", " if sum_distrib > 100 :\n", " #summary_df.loc[index, 'distribution'] = distrib\n", " all_distrib.append(distrib)\n", " keep_trying = False \n", " n_real += 1\n", + " all_data_origin.append('d_real')\n", " \n", " # 2) try d_all to get distribution from measured + estimated delays\n", " if keep_trying and key in d_all:\n", " distrib = d_all[key]\n", " sum_distrib = np.sum(distrib)\n", " if sum_distrib > 100 :\n", " #summary_df.loc[index, 'distribution'] = distrib\n", " all_distrib.append(distrib)\n", " keep_trying = False\n", " n_all += 1\n", + " all_data_origin.append('d_all')\n", + "\n", " \n", " # 3) try first recovery table using stop_id, transport_type and hour\n", " if keep_trying and (stop_id, hour, transport_type) in recovery_df.index:\n", " distrib = np.array(recovery_df.loc[(stop_id, hour, transport_type)])\n", " sum_distrib = np.sum(distrib)\n", " if sum_distrib > 100 :\n", " #summary_df.loc[index, 'distribution'] = distrib\n", " all_distrib.append(distrib)\n", " keep_trying = False \n", " n_recov1 += 1\n", + " all_data_origin.append('recov_tab1')\n", " \n", " # 4) use second recovery table using transport_type and hour \n", " if keep_trying and (hour, transport_type) in recovery_df2.index:\n", " distrib = np.array(recovery_df2.loc[(hour, transport_type)])\n", " sum_distrib = np.sum(distrib)\n", " if sum_distrib > 100 :\n", " #summary_df.loc[index, 'distribution'] = distrib\n", " all_distrib.append(distrib)\n", " keep_trying = False \n", " n_recov2 += 1\n", + " all_data_origin.append('recov_tab2')\n", " \n", " # 5) use third recovery table using transport_type only \n", " if keep_trying and (transport_type) in recovery_df3.index:\n", " distrib = np.array(recovery_df3.loc[(transport_type)])\n", " sum_distrib = np.sum(distrib)\n", " #summary_df.loc[index, 'distribution'] = distrib\n", " all_distrib.append(distrib)\n", " keep_trying = False \n", " n_recov3 += 1\n", - " \n", + " all_data_origin.append('recov_tab3')\n", + "\n", " # Record results in summary\n", " all_keys.append(key)\n", " all_transport_type.append(transport_type)\n", " all_hours.append(hour)\n", "\n", " # save number of failure for recovery\n", " if keep_trying:\n", " #print('fail{}'.format(index), end = ', ')\n", " all_distrib.append(last_chance_distrib)\n", + " all_failure_keys.append(key)\n", " n_fail += 1 \n", + " all_data_origin.append('fail')\n", " \n", " # print progression \n", " if (index % 10000) == 0 :\n", " print('{}%'.format(round(100*index/size_stop_times,2)), end = ', ')" ] }, { "cell_type": "code", - "execution_count": 238, + "execution_count": 277, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.barh(np.array(['real / geschaetz','real / geschaetz / prognose','recov_tab1 (stop_id x hour x type)','recov_tab2 (hour x type)','recov_tab3 (type)', 'fail'])[::-1],\\\n", " np.array([n_real,n_all,n_recov1,n_recov2, n_recov3, n_fail])[::-1])\n", "plt.title('where the distribution data come from')\n", "plt.show()" ] }, { "cell_type": "code", - "execution_count": 239, + "execution_count": 279, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", " \n", " \n", "
keyhourtransport_typedistributionorigin
02064.TA.26-13-j19-1.24.H__85762407Tram[6, 155, 15, 3, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, ...[15, 445, 31, 4, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,...d_all
12064.TA.26-13-j19-1.24.H__85913537Tram[0, 154, 18, 7, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, ...[1, 442, 41, 11, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,...d_all
22064.TA.26-13-j19-1.24.H__85910397Tram[0, 140, 31, 8, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, ...[0, 415, 68, 11, 2, 0, 0, 1, 0, 0, 1, 0, 0, 0,...d_all
32064.TA.26-13-j19-1.24.H__85911217Tram[0, 138, 33, 8, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, ...[0, 403, 79, 12, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0,...d_all
42064.TA.26-13-j19-1.24.H__85914177Tram[0, 134, 29, 13, 3, 0, 0, 1, 0, 0, 0, 0, 1, 0,...[0, 388, 79, 24, 4, 1, 0, 1, 0, 0, 0, 0, 1, 0,...d_all
\n", "
" ], "text/plain": [ " key hour transport_type \\\n", "0 2064.TA.26-13-j19-1.24.H__8576240 7 Tram \n", "1 2064.TA.26-13-j19-1.24.H__8591353 7 Tram \n", "2 2064.TA.26-13-j19-1.24.H__8591039 7 Tram \n", "3 2064.TA.26-13-j19-1.24.H__8591121 7 Tram \n", "4 2064.TA.26-13-j19-1.24.H__8591417 7 Tram \n", "\n", - " distribution \n", - "0 [6, 155, 15, 3, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, ... \n", - "1 [0, 154, 18, 7, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, ... \n", - "2 [0, 140, 31, 8, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, ... \n", - "3 [0, 138, 33, 8, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, ... \n", - "4 [0, 134, 29, 13, 3, 0, 0, 1, 0, 0, 0, 0, 1, 0,... " + " distribution origin \n", + "0 [15, 445, 31, 4, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,... d_all \n", + "1 [1, 442, 41, 11, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,... d_all \n", + "2 [0, 415, 68, 11, 2, 0, 0, 1, 0, 0, 1, 0, 0, 0,... d_all \n", + "3 [0, 403, 79, 12, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0,... d_all \n", + "4 [0, 388, 79, 24, 4, 1, 0, 1, 0, 0, 0, 0, 1, 0,... d_all " ] }, - "execution_count": 239, + "execution_count": 279, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "summary_df = pd.DataFrame([all_keys, all_hours, all_transport_type, all_distrib],\\\n", - " index = ['key','hour','transport_type','distribution']).transpose()\n", + "summary_df = pd.DataFrame([all_keys, all_hours, all_transport_type, all_distrib, all_data_origin],\\\n", + " index = ['key','hour','transport_type','distribution', 'origin']).transpose()\n", "summary_df.head()" ] }, + { + "cell_type": "code", + "execution_count": 309, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " trip_id stop_id_general\n", + "167848 35.TA.40-5-Y-j19-1.33.H 8503000\n", + "167849 35.TA.40-5-Y-j19-1.33.H 8503016\n", + "167886 8.TA.40-5-Y-j19-1.8.H 8503000\n", + "167887 8.TA.40-5-Y-j19-1.8.H 8503016\n", + "167920 16.TA.40-5-Y-j19-1.16.H 8503000\n", + "167921 16.TA.40-5-Y-j19-1.16.H 8503016\n", + "251450 18.TA.40-4-Y-j19-1.16.H 8503016\n", + "251451 18.TA.40-4-Y-j19-1.16.H 8503000\n" + ] + } + ], + "source": [ + "print(stoptimes.iloc[np.array(stoptimes['route_desc'] == 'Eurocity'),:][['trip_id','stop_id_general']])" + ] + }, + { + "cell_type": "code", + "execution_count": 311, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
keyhourtransport_typedistributionorigin
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: [key, hour, transport_type, distribution, origin]\n", + "Index: []" + ] + }, + "execution_count": 311, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "summary_df.loc[np.array(summary_df['key'] == '1326.TA.26-8-C-j19-1.8.R'),:]" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "Write down summary table used to make final distribution" ] }, { "cell_type": "code", "execution_count": 240, "metadata": {}, "outputs": [], "source": [ "with gzip.open(\"../data/join_distribution_all.pkl.gz\", \"wb\") as out_file:\n", " pickle.dump(summary_df, out_file)" ] }, { "cell_type": "code", "execution_count": 241, "metadata": {}, "outputs": [], "source": [ "list_all_rows = []\n", "for index, row in summary_df.iterrows():\n", " distrib = np.array(row['distribution'])\n", " \n", " # get total number of elements \n", " N = np.sum(distrib)\n", " \n", " # make cumulative distribution probabilities\n", " cdf_distrib = np.empty((len(distrib)), dtype=float)\n", " save_x = 0\n", " for x in range(len(distrib)):\n", " cdf_distrib[x] = float(distrib[x])/float(N) + float(save_x)/float(N)\n", " save_x += distrib[x]\n", " \n", " list_all_rows.append(cdf_distrib)" ] }, { "cell_type": "code", "execution_count": 242, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0.03314917, 0.88950276, 0.97237569, 0.98895028, 0.98895028,\n", " 0.98895028, 0.99447514, 0.99447514, 0.99447514, 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. ],\n", " [0. , 0.85082873, 0.95027624, 0.98895028, 0.98895028,\n", " 0.98895028, 0.98895028, 0.99447514, 0.99447514, 0.99447514,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. ],\n", " [0. , 0.77348066, 0.94475138, 0.98895028, 0.98895028,\n", " 0.98895028, 0.98895028, 0.99447514, 0.99447514, 0.99447514,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. ],\n", " [0. , 0.76243094, 0.94475138, 0.98895028, 0.98895028,\n", " 0.98895028, 0.98895028, 0.99447514, 0.99447514, 0.99447514,\n", " 0.99447514, 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. ],\n", " [0. , 0.74033149, 0.90055249, 0.97237569, 0.98895028,\n", " 0.98895028, 0.98895028, 0.99447514, 0.99447514, 0.99447514,\n", " 0.99447514, 0.99447514, 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. , 1. , 1. , 1. ,\n", " 1. , 1. ]])" ] }, "execution_count": 242, "metadata": {}, "output_type": "execute_result" } ], "source": [ "final_df = pd.DataFrame(list_all_rows)\n", "final_df.index = summary_df.index\n", "final_np = final_df.to_numpy()\n", "final_np[0:5,:]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Last check if all indexes corresponds to `stoptimes` indexes :" ] }, { "cell_type": "code", "execution_count": 243, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 243, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum(np.array(final_df.index == stoptimes.index)) == stoptimes.shape[0]" ] }, { "cell_type": "code", "execution_count": 244, "metadata": {}, "outputs": [], "source": [ "# write recovery table \n", "with gzip.open(\"../data/join_distribution_cumulative_p_3.pkl.gz\", \"wb\") as output_file:\n", " pickle.dump(final_np, output_file)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Poisson cumulative distribution\n", "\n", "The Poisson distribution is popular for modeling the number of times an event occurs in an interval of time or space. We modeled a poisson distribution for delays assuming parameter $k$ is the time in minutes (as it was done [here](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0126137), formulas $(4),(5),(6)$).\n", "\n", "A discrete random variable X is said to have a Poisson distribution with parameter λ > 0, if, for k = 0, 1, 2, ..., the probability mass function of X is given by:\n", "\n", "$${\\displaystyle \\!f(k;\\lambda )=\\Pr(X=k)={\\frac {\\lambda ^{k}e^{-\\lambda }}{k!}},}$$\n", "where\n", "\n", "e is Euler's number (e = 2.71828...)\n", "k! is the factorial of k.\n", "The positive real number λ is equal to the expected value of X __and__ to its variance.\n", "\n", "$${\\displaystyle \\lambda =\\operatorname {E} (X)=\\operatorname {Var} (X)}$$\n", "\n", "We can approximate E[𝑋]∼$\\mu_i$ for our data $X_i$, if we assume the sample $X_i$ of size N follow the distribution of $X$ meaning $X_i$∼$X$.\n", "\n", "Poisson-related __assumptions__ :\n", "- $k$ is the __delay time in minutes__ and can take values 0, 1, 2, ... (strictly positive and discrete)\n", "- We assume our sampling $X_i$ of $X$ is good enough to approximate E[X] ~ $\\mu_i$\n", "- The occurrence of one event does not affect probability of others. That is, events occur independently.\n", " - __We assume being late one day is not affecting the delay of the day after__ \n", "- The average rate at which events occur is independent of any occurrences. For simplicity, this is usually assumed to be constant, but may in practice vary with time.\n", " - __we assumes delays occurs with a constant rate over time__\n", "- Two events cannot occur at exactly the same instant\n", "\n", "We made a function `poisson_proba` that takes a `trip_id`, a `stop_id`, an `arrival time` and a `departure time` and a dictionnary {key : distribution} to compute a __probability to be at least 2 minutes before departure of next trip__. \n", "\n", "We make a few __assumptions__ on our side :\n", "- We assume that if we have less than 2 minutes for the transfer, we miss it.\n", "- We assume the next train is on time.\n", "- As for poisson distribution $k$ is strictly positive, we assume trains ahead of schedule were on time ($k=0$)\n", "\n", "\n", "_Question we should address :_\n", "- _Is the poisson a reasonable approximation of the binomial distribution in our case ?_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's first test the poisson distribution and compare it with our distribution to see how well it fits the data. We will compute $Pr(X = k)$ for each values of k and look at the shape of the poisson distribution compared to the shape of our scaled data. Then, we will compare $\\sum_{k=0}^T Pr(X = k)$ with the cumulative distribution function which directly gives $Pr(k \\leq X)$" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "An error was encountered:\n", "Invalid status code '404' from http://iccluster044.iccluster.epfl.ch:8998/sessions/6821 with error payload: \"Session '6821' not found.\"\n" ] } ], "source": [ "################################# POISSON FIT TEST #########################################\n", "\n", "# to do .. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here are all the functions needed to calculate probability of success for a given transfer. We need the `trip_id`, `stop_id`, `departure_time`, `arrival_time` and dictionnary `d` (pickled load at the beginning of the cell) to be able to compute a probability of success with following function : \n", "\n", "`poisson_proba(trip_id, stop_id, arrival_time, departure_time, d)`" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "lambda (expectation given distribution): 1.0194769059543685 \n", "\n", "Probability of success for transfer time = 13.0 minutes : 0.999999999994185\n" ] } ], "source": [ "%local\n", "################################# POISSON FUNCTIONS ########################################\n", "\n", "import pickle \n", "import gzip\n", "import time\n", "import math \n", "import datetime\n", "import time\n", "from scipy.stats import poisson\n", "\n", "# Load dictionnary\n", "with gzip.open(\"../data/distributions.pickle\", \"rb\") as input_file:\n", " d = pickle.load(input_file)\n", "\n", "# Load dictionnary\n", "with open(\"../data/stop_times_array.pkl\", \"rb\") as input_file:\n", " times = pickle.load(input_file)\n", "\n", "# we take two exemple time in format numpy.datetime64\n", "arr_time = times[4][1]\n", "dep_time = times[0][1]\n", "\n", "# Load distribution in dictinonary given a key\n", "def get_distrib(key, dico):\n", " if key in dico:\n", " return dico[key]\n", " else:\n", " raise ValueError(\"KEY ERROR: {} not found un distribution dictionnary\".format(key))\n", " \n", "# Evaluate lambda parameter assuming it is equal to average \n", "def evaluate_lamda(distrib):\n", " # First calculate total number of measures N\n", " N = 0 # by starting at -1 we ignore trains ahead of schedule\n", " for x in distrib:\n", " N += x\n", "\n", " lambda_p = 0 # expectation - we want to calculate it\n", " t = -1 # time = index - 1\n", "\n", " for x in distrib:\n", " if t>0:\n", " lambda_p += t*x\n", " t += 1\n", "\n", " # calculate lambda - the expectation of x\n", " if N > 0:\n", " lambda_p /= N \n", " print('lambda (expectation given distribution): ',lambda_p, '\\n')\n", " return lambda_p\n", " else : \n", " raise ValueError(\"ERROR : {} distribution has 0 counts\".format(key))\n", " #print('Returning 1 to avoid later problem... \\n')\n", " return 1\n", "\n", "# process time given as string in format 'hh:mm' - not needed\n", "def process_time_str(str_time):\n", " x = time.strptime(str_time,'%H:%M')\n", " return datetime.timedelta(hours=x.tm_hour,minutes=x.tm_min,seconds=x.tm_sec).total_seconds()\n", "\n", "# Calculate transfer time given two times in string format 'hh:mm'\n", "def get_transfer_time(arr_time, dep_time, delta=2.0):\n", " diff_time_min = (arr_time - dep_time).astype('timedelta64[m]') / np.timedelta64(1, 'm')\n", " return diff_time_min - delta\n", "\n", "# Calculate poisson probability of success for a given transfert \n", "# for a given trip_id, stop_id, arrival/departure times and dict\n", "def poisson_proba(trip_id, stop_id, arr_time, dep_time, dico):\n", " # Generate key from trip_id / stop_id \n", " key = str(trip_id) + '__' + str(stop_id[0:7]) # 7 first char to be sbb-compatible\n", "\n", " # Get distribution from dictionnary\n", " distrib = get_distrib(key, dico)\n", " \n", " # Calculate transfer time at disposal \n", " T = get_transfer_time(arr_time, dep_time)\n", " \n", " # Get lambda value to calculate proba\n", " lambda_p = evaluate_lamda(distrib)\n", "\n", " # Get proba\n", " if T > 2:\n", " poisson_p = poisson.cdf(T, lambda_p)\n", " else : \n", " poisson_p = 0.0 # if we have less than 2 minutes, we miss it\n", " \n", " print('Probability of success for transfer time = {} minutes : '.format(T),poisson_p)\n", " return poisson_p\n", "\n", "# Mock exemple of probability calculations with given inputs\n", "trip_id = '1286.TA.26-32-j19-1.12.H'\n", "stop_id = '8591184'\n", "\n", "# we take two exemple time from stop_times_array in format numpy.datetime64\n", "arr_time = times[3][1]\n", "dep_time = times[0][1]\n", "\n", "Pr = poisson_proba(trip_id, stop_id, arr_time, dep_time, d)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" } }, "nbformat": 4, "nbformat_minor": 4 }