Skip to content

Instantly share code, notes, and snippets.

@laowantong
Last active June 8, 2023 12:30
Show Gist options
  • Save laowantong/5a11b6c0ecdc76fc8490fd0679bcd0b0 to your computer and use it in GitHub Desktop.
Save laowantong/5a11b6c0ecdc76fc8490fd0679bcd0b0 to your computer and use it in GitHub Desktop.
Génération de la base d'itinéraires utilisée sur https://maisonrougevernet.fr/transports
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
{
"cells": [
{
"cell_type": "markdown",
"id": "3454bcd9",
"metadata": {},
"source": [
"# Base des transports"
]
},
{
"cell_type": "markdown",
"id": "84e42cb8",
"metadata": {},
"source": [
"## Initialisations"
]
},
{
"cell_type": "code",
"execution_count": 77,
"id": "8ae7dce0",
"metadata": {
"init_cell": true
},
"outputs": [],
"source": [
"import json\n",
"import re\n",
"import unicodedata\n",
"from pathlib import Path\n",
"from pprint import pprint\n",
"from itertools import groupby\n",
"import time\n",
"import os\n",
"\n",
"import pandas as pd\n",
"import requests\n",
"\n",
"import bz2\n",
"import importlib\n",
"import pickle\n",
"from collections import defaultdict\n",
"from datetime import datetime\n",
"from functools import lru_cache\n",
"\n",
"import camelot\n",
"import numpy as np\n",
"from neo4j import GraphDatabase\n",
"\n",
"import pypdfium2 as pdfium"
]
},
{
"cell_type": "code",
"execution_count": 71,
"id": "d9eb00ce",
"metadata": {
"code_folding": [
0
],
"init_cell": true
},
"outputs": [],
"source": [
"class StopExecution(Exception):\n",
" def _render_traceback_(self):\n",
" pass\n",
"\n",
"normalize = lambda s: unicodedata.normalize(\"NFKD\", s)\n",
"\n",
"OK = \"\\033[92m\"\n",
"WARNING = \"\\033[1m\\033[38;5;166m\"\n",
"FAIL = \"\\033[1m\\033[91m\"\n",
"RESET = \"\\033[0m\"\n",
"\n",
"PDF_DIR = Path(\"pdf\")\n",
"PBZ2_DIR = Path(\"pbz2\")\n",
"TEXTS_DIR = Path(\"texts\")\n",
"SEGMENTS_DIR = Path(\"segments\")\n",
"QUERIES_DIR = Path(\"queries\")\n",
"\n",
"def hhmm_to_minutes(time):\n",
" return int(time[:2]) * 60 + int(time[3:5])\n",
"\n",
"def calculate_int_duration(dep_time, arr_time):\n",
" return hhmm_to_minutes(arr_time) - hhmm_to_minutes(dep_time)\n",
"\n",
"def minutes_to_hhmm(minutes):\n",
" return \"%02d:%02d\" % divmod(minutes, 60)\n",
"\n",
"LINES_OF_INTEREST = [\n",
" \"520\",\n",
" \"521\",\n",
" \"523\",\n",
" \"525\",\n",
" \"530\",\n",
" \"540\",\n",
" \"560\",\n",
" \"561\",\n",
" \"564\",\n",
" \"train_jaune\",\n",
" \"PORT BOU - NARBONNE\",\n",
" \"PERPIGNAN - VILLEFRANCHE\",\n",
"]\n",
"SUBGRAPHS = [\n",
" [\n",
" (\"Vernet\", \"Casteil\"),\n",
" (\"Vernet\", \"Villefranche\"),\n",
" (\"Villefranche\", \"Prades\"),\n",
" (\"Prades\", \"Mosset\"),\n",
" (\"Prades\", \"Py\"),\n",
" (\"Villefranche\", \"Mont-Louis\"),\n",
" (\"Mont-Louis\", \"Bouillouses\"),\n",
" (\"Mont-Louis\", \"Formiguères\"),\n",
" (\"Prades\", \"Perpignan\"),\n",
" (\"Perpignan\", \"Collioure\"),\n",
" (\"Perpignan\", \"Céret\"),\n",
" (\"Céret\", \"Arles-sur-Tech\"),\n",
" ],\n",
" [\n",
" (\"Perpignan\", \"Prades\"),\n",
" (\"Prades\", \"Villefranche\"),\n",
" (\"Villefranche\", \"Vernet\"),\n",
" (\"Vernet\", \"Casteil\"),\n",
" (\"Prades\", \"Mosset\"),\n",
" (\"Prades\", \"Py\"),\n",
" (\"Villefranche\", \"Mont-Louis\"),\n",
" (\"Mont-Louis\", \"Bouillouses\"),\n",
" (\"Mont-Louis\", \"Formiguères\"),\n",
" # Omit the last three arcs of the main graph, which would be redundant.\n",
" ] \n",
"]\n",
"for i in range(len(SUBGRAPHS)-1, -1, -1):\n",
" SUBGRAPHS.insert(i+1, [(y, x) for (x, y) in SUBGRAPHS[i]])\n",
"CITIES = sorted(set([edge[0] for edge in SUBGRAPHS[0]] + [edge[1] for edge in SUBGRAPHS[0]]))"
]
},
{
"cell_type": "markdown",
"id": "b7445f1c",
"metadata": {},
"source": [
"## Récupérer les url des fiches horaires"
]
},
{
"cell_type": "markdown",
"id": "eda86786",
"metadata": {},
"source": [
"Plutôt que de coder en dur les url des fiches horaires en PDF (leur nom varie à chaque mise à jour), on récupère autant que possible celles-ci dans des pages d'adresse censément fixe.\n",
"\n",
"Malheureusement, il ne semble pas y avoir de page statique où récupérer les fiches horaires SNCF. Les chercher sur [cette page](https://www.ter.sncf.com/occitanie/se-deplacer/fiches-horaires) et copier leurs urls dans la constante `URLS` ci-dessous (qui contient donc aussi bien des liens directs qu'indirects vers les pdf). Les lignes concernées sont :\n",
"- Port-Bou-Perpignan-Narbonne.\n",
"- Perpignan-Villefranche-Vernet-les-Bains."
]
},
{
"cell_type": "code",
"execution_count": 72,
"id": "28c2c242",
"metadata": {
"code_folding": []
},
"outputs": [],
"source": [
"URLS = [\n",
" \"https://www.lio-occitanie.fr/horaires-et-plans/\",\n",
" \"https://www.tourisme-canigou.com/decouvrez/le-train-jaune\",\n",
" \"https://ter-fiches-horaires.sncf.fr/publish/WEB%20FH24%20PERPIGNAN%20-%20VILLEFRANCHE%20VERNET%20LES%20BAINS_2023.pdf\",\n",
" \"https://ter-fiches-horaires.sncf.fr/publish/WEB%20FH23%20PORT%20BOU%20-%20NARBONNE%20PAYSAGE_2023.pdf\",\n",
" \"https://storage.googleapis.com/is-wp-90-prod/uploads-preprod/2023/06/100522_liO_flyer_ligne564.pdf\",\n",
"]\n",
"page_text_compilation = \"\\n\".join(requests.get(url).text for url in URLS if not url.endswith(\"pdf\"))"
]
},
{
"cell_type": "code",
"execution_count": 73,
"id": "503368e0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Files to download:\n",
" 520: https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-520-032023.pdf\n",
" 521: https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-521-032023.pdf\n",
" 523: https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-523-032023.pdf\n",
" 525: https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-525-032023.pdf\n",
" 530: https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-530-032023.pdf\n",
" 540: https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-540-032023.pdf\n",
" 560: https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-560-032023.pdf\n",
" 561: https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-561-032023.pdf\n",
" 564: https://storage.googleapis.com/is-wp-90-prod/uploads-preprod/2023/06/100522_liO_flyer_ligne564.pdf\n",
" train_jaune: https://www.tourisme-canigou.com/sites/tourisme-canigou/files/content/files/horaires_train_jaune_27_mai_au_9_decembre_2023.pdf\n",
" PORT BOU - NARBONNE: https://ter-fiches-horaires.sncf.fr/publish/WEB%20FH23%20PORT%20BOU%20-%20NARBONNE%20PAYSAGE_2023.pdf\n",
" PERPIGNAN - VILLEFRANCHE: https://ter-fiches-horaires.sncf.fr/publish/WEB%20FH24%20PERPIGNAN%20-%20VILLEFRANCHE%20VERNET%20LES%20BAINS_2023.pdf\n",
"If you want to exclude some of them from downloading, extend BLACKLISTED_SUBSTRINGS.\n"
]
}
],
"source": [
"BLACKLISTED_SUBSTRINGS = [\n",
" \"PLAN\",\n",
" \"Oc22\",\n",
" \"du_2702_au_2804\",\n",
" \"2022\",\n",
" \"social\",\n",
"]\n",
"\n",
"pdf_urls = {n: \"\" for n in LINES_OF_INTEREST}\n",
"for line in pdf_urls:\n",
" for pdf_url in re.findall(fr'href=\"([^\"]*?(?<=[\\W_]){line}(?=[\\W_])[^\"]*?\\.pdf)', page_text_compilation):\n",
" for s in BLACKLISTED_SUBSTRINGS:\n",
" if re.search(rf\"\\b{s}\\b\", pdf_url):\n",
" break\n",
" else:\n",
" pdf_urls[line] = pdf_url\n",
"print(\"Files to download:\")\n",
"for (line_of_interest, pdf_url) in pdf_urls.items():\n",
" print(f\" {line_of_interest}: \", end=\"\")\n",
" if pdf_url:\n",
" print(pdf_url)\n",
" continue\n",
" for url in URLS:\n",
" if url.endswith(\".pdf\") and line_of_interest.replace(\" \", \"%20\") in url:\n",
" pdf_urls[line_of_interest] = url\n",
" print(url)\n",
" break\n",
" else:\n",
" print(f\"{WARNING}Not found.{RESET}\")\n",
"print(\"If you want to exclude some of them from downloading, extend BLACKLISTED_SUBSTRINGS.\")"
]
},
{
"cell_type": "markdown",
"id": "1553f38f",
"metadata": {},
"source": [
"## Télécharger les fiches horaires dans `pdf/`"
]
},
{
"cell_type": "code",
"execution_count": 80,
"id": "67b0c14c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"520.pdf \u001b[92malready up to date.\u001b[0m\n",
"521.pdf \u001b[92malready up to date.\u001b[0m\n",
"523.pdf \u001b[92malready up to date.\u001b[0m\n",
"525.pdf \u001b[92malready up to date.\u001b[0m\n",
"530.pdf \u001b[92malready up to date.\u001b[0m\n",
"540.pdf \u001b[92malready up to date.\u001b[0m\n",
"560.pdf \u001b[92malready up to date.\u001b[0m\n",
"561.pdf \u001b[92malready up to date.\u001b[0m\n",
"564.pdf \u001b[92malready up to date.\u001b[0m\n",
"train_jaune.pdf \u001b[92malready up to date.\u001b[0m\n",
"PORT BOU - NARBONNE.pdf \u001b[92malready up to date.\u001b[0m\n",
"PERPIGNAN - VILLEFRANCHE.pdf \u001b[92malready up to date.\u001b[0m\n"
]
}
],
"source": [
"touched = set()\n",
"for (line, pdf_url) in pdf_urls.items():\n",
" print(line, end=\".pdf \")\n",
" path = PDF_DIR / f\"{line}.pdf\"\n",
" if not pdf_url:\n",
" if path.is_file():\n",
" print(f\"{OK}already downloaded.{RESET}\")\n",
" else:\n",
" print(f\"{FAIL}No schedule!{RESET}\")\n",
" continue\n",
" response = requests.get(pdf_url)\n",
" touched.add(normalize(path.name))\n",
" if path.is_file():\n",
" if path.read_bytes() == response.content:\n",
" print(f\"{OK}already up to date.{RESET}\")\n",
" continue\n",
" path.rename(path.parent / f\"{path.stem}_old.pdf\")\n",
" print(f\"{WARNING}updated. {RESET}\", end=\"\")\n",
" else:\n",
" print(f\"{OK}created. {RESET}\", end=\"\")\n",
" path.write_bytes(response.content)\n",
" if line == 564:\n",
" print(f'{WARNING}Update the corresponding code cell in this notebook.{RESET}')\n",
" else:\n",
" print(f'{WARNING}Verify \"config.json\".{RESET}')\n",
"\n",
"untouched = [path for path in PDF_DIR.glob(\"*.pdf\") if normalize(path.name) not in touched]\n",
"if untouched:\n",
" print(f\"\\n{WARNING}You may delete the following file(s) before proceeding:{RESET}\")\n",
" for path in untouched:\n",
" print(f\" {path.name}\")\n",
" os.system(\"open pdf\")"
]
},
{
"cell_type": "markdown",
"id": "d9196775",
"metadata": {},
"source": [
"Le cas échéant, les vérifications à faire dans `\"config.json\"` sont :\n",
"- `col_headers` : copier-coller le contenu de la liste mis en forme au moment de l'extraction du texte (script suivant) ;\n",
"- `periods_to_uncheck` : 1-based indexed. Compter les colonnes dans le PDF jusqu'à arriver à celles où le symbole « • » est manquant."
]
},
{
"cell_type": "markdown",
"id": "382235c5",
"metadata": {},
"source": [
"## PyPdfium : enregistrer le texte des fiches dans `textes/`"
]
},
{
"cell_type": "code",
"execution_count": 83,
"id": "5d78cb36",
"metadata": {
"code_folding": [],
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"520: up to date.\n",
"521: up to date.\n",
"523: up to date.\n",
"525: up to date.\n",
"530: up to date.\n",
"540: up to date.\n",
"560: up to date.\n",
"561: up to date.\n",
"564: \u001b[92mupdated.\u001b[0m\n",
"train_jaune: up to date.\n",
"PORT BOU - NARBONNE: up to date.\n",
"PERPIGNAN - VILLEFRANCHE: up to date.\n"
]
}
],
"source": [
"FIND_AND_REPLACE = {\n",
" r\"\\*\": \"\",\n",
" r\"Tous\\s*\\nles\\s*\\njours\": '\"LMWJVSD\", ',\n",
" r\"Lun\\s*\\nà\\s*\\nSam\": '\"LMWJVS\", ',\n",
" r\"Lun\\s*\\nà\\s*\\nVen\": '\"LMWJV\", ',\n",
" r\"Lun\\s*\\nà\\s*\\nJeu\": '\"LMWJ\", ',\n",
" r\"Lun\\s*\\nà\\s*\\nMer\": '\"LMW\", ',\n",
" r\"Ven\\s*\\nà\\s*\\nDim\": '\"VSD\", ',\n",
" r\"Sam\\s*\\nDim\\s*\\n(et\\s*\\n)?Fêtes\": '\"SD\", ',\n",
" r\"Dim\\s*\\n(et\\s*\\n)?Fêtes\": '\"D\", ',\n",
" r\"Ven\\s*\\n(et\\s*\\n)?Sam\": '\"VS\", ',\n",
" r\"Sam\\s*\\n(et\\s*\\n)?Dim\": '\"SD\", ',\n",
" r\"Sauf\\s*\\nSam\": '\"LMWJVD\", ',\n",
" r\"(?<!-)Sam\\b\": '\"S\", ',\n",
" r\"(?<!-)Dim\\b\": '\"D\", ',\n",
" r\"(?<!-)Lun\\b\": '\"L\", ',\n",
" r\"(?<!-)Mar\\b\": '\"M\", ',\n",
" r\"(?<!-)Mer\\b\": '\"W\", ',\n",
" r\"(?<!-)Jeu\\b\": '\"J\", ',\n",
" r\"(?<!-)Ven\\b\": '\"V\", ',\n",
" r'\",\\s+\"': '\", \"',\n",
" r'(\".+\"),': r'\\n[\\1]',\n",
" r\"LàV\": \"LMWJV\",\n",
" r\"LàS\": \"LMWJVS\",\n",
" r\"LàD\": \"LMWJVSD\",\n",
" r\"ET FÊTES\": \"\",\n",
" r\"L à D\": \"LMWJVSD\",\n",
" r\"L à S\": \"LMWJVS\",\n",
" r\"L à V\": \"LMWJV\",\n",
" r\" D\\s+D \": \" D D \",\n",
" r\"Train\\s+[Jj]aune\": \"train_jaune\",\n",
" r\"\\bMe\\b\": \"W\",\n",
" \" +\": \" \",\n",
" \"Villefranche-Vernet-les-Bains\": \"Villefranche\",\n",
" \"Villefranche Gare SNCF\": \"Villefranche\",\n",
" \"Mont-Louis-La Cabanasse\": \"Mont-Louis\",\n",
" \"Perpignan Gare SNCF\": \"Perpignan\",\n",
" \"Prades Espace Multimodal .+\\nGare SNCF\": \"Prades\",\n",
" \"Prades Gare SNCF\": \"Prades\",\n",
" \"Perpignan Gare Routière .+\\nGare SNCF\": \"Perpignan\",\n",
" \"Mont Louis -\\s+Porte de France\": \"Mont-Louis\",\n",
" \"Les Bouillouses -\\s+Pla de Barres\": \"Bouillouses\",\n",
"}\n",
"\n",
"FORCE = False\n",
"\n",
"for line in LINES_OF_INTEREST:\n",
" print(f\"{line}: \", end=\"\")\n",
" text_path = TEXTS_DIR / f\"{line}.txt\"\n",
" pdf_path = PDF_DIR / f\"{line}.pdf\"\n",
" if not FORCE and text_path.is_file() and pdf_path.stat().st_mtime < text_path.stat().st_mtime:\n",
" print(\"up to date.\")\n",
" continue\n",
" reader = pdfium.PdfDocument(pdf_path)\n",
" text = \"\\n\\n<PAGE BREAK>\\n\\n\".join(reader[i].get_textpage().get_text_range() for i in (0, 1))\n",
" for (find, replace) in FIND_AND_REPLACE.items():\n",
" text = re.sub(find, replace, text)\n",
" text = re.sub(r\"(?m)(JOURS DE LA SEMAINE )((?:[LMWJVSD]+\\s+)+)\", lambda m: m[1] + re.sub(r\"(\\S+)\\s+\", r'\"\\1\", ', m[2]) + \"\\n\", text)\n",
" text_path.write_text(text)\n",
" print(f\"{OK}updated.{RESET}\")"
]
},
{
"cell_type": "markdown",
"id": "3b168709",
"metadata": {},
"source": [
"## Mettre à jour `transports.md.html`\n",
"Injecter les dates de validité et les urls de téléchargement des fiches horaires."
]
},
{
"cell_type": "code",
"execution_count": 87,
"id": "8497ec07",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<details><summary>Fiches horaires à télécharger…</summary>\n",
"\n",
"<p style=\"color: gray\">Jusqu'au 7 juillet 2023</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 540 Perpignan ⇄ Collioure](https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-540-032023.pdf)</p>\n",
"<p><span class=\"train\"></span>&nbsp; [train Perpignan ⇄ Collioure](https://ter-fiches-horaires.sncf.fr/publish/WEB%20FH23%20PORT%20BOU%20-%20NARBONNE%20PAYSAGE_2023.pdf)</p>\n",
"<p><span class=\"train\"></span>&nbsp; [train Perpignan ⇄ Villefranche](https://ter-fiches-horaires.sncf.fr/publish/WEB%20FH24%20PERPIGNAN%20-%20VILLEFRANCHE%20VERNET%20LES%20BAINS_2023.pdf)</p>\n",
"<p style=\"color: gray\">Jusqu'au 31 août 2023</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 520 Prades ⇄ Perpignan](https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-520-032023.pdf)</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 521 Perpignan ⇄ Casteil](https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-521-032023.pdf)</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 523 Mosset ⇄ Prades](https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-523-032023.pdf)</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 525 Py ⇄ Prades](https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-525-032023.pdf)</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 530 Perpignan ⇄ Arles-sur-Tech](https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-530-032023.pdf)</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 560 Mont-Louis ⇄ Perpignan](https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-560-032023.pdf)</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 561 Formiguères ⇄ Mont-Louis](https://storage.googleapis.com/is-wp-90-preprod/uploads-preprod/2023/03/66-FH-561-032023.pdf)</p>\n",
"<p style=\"color: gray\">Jusqu'au 9 décembre 2023</p>\n",
"<p><span class=\"train_jaune\"></span>&nbsp; [Train Jaune Villefranche ⇄ Mont-Louis](https://www.tourisme-canigou.com/sites/tourisme-canigou/files/content/files/horaires_train_jaune_27_mai_au_9_decembre_2023.pdf)</p>\n",
"<p style=\"color: gray\">Été 2023</p>\n",
"<p><span class=\"bus\"></span>&nbsp; [car 564 Mont-Louis ⇄ Bouillouses](https://storage.googleapis.com/is-wp-90-prod/uploads-preprod/2023/06/100522_liO_flyer_ligne564.pdf)</p>\n",
"\n",
"\u001b[92mtransports.md.html updated with new dates of validity.\u001b[0m\n"
]
}
],
"source": [
"configurations = json.loads(Path(\"config.json\").read_text())\n",
"\n",
"MONTHS = \"janvier février mars avril mai juin juillet août septembre octobre novembre décembre\".split()\n",
"\n",
"def month_name_to_number(name):\n",
" return MONTHS.index(name) + 1\n",
"\n",
"def month_number_to_name(number):\n",
" return MONTHS[number - 1]\n",
"\n",
"rex = re.compile(r\"(?i)du \\d{1,2}(?:/\\d{1,2}/| \\w+\\s)(?:20\\d{2})?\\s*au (\\d{1,2})[/ ]+(\\d{1,2}|\\w+)[/ ]+(20\\d{2})\")\n",
"\n",
"missing_lines = []\n",
"quadruples = []\n",
"for (line, d) in configurations.items():\n",
" text = (TEXTS_DIR / f\"{line}.txt\").read_text()\n",
" if (m := rex.search(text.lower())):\n",
" day = int(m[1])\n",
" month = int(m[2]) if m[2].isdigit() else month_name_to_number(m[2])\n",
" year = int(m[3])\n",
" mode = f\"[{d['mode']} {d['from']} ⇄ {d['to']}]({pdf_urls[line]})\"\n",
" quadruples.append((year, month, day, mode))\n",
" else:\n",
" missing_lines.append(line)\n",
"if missing_lines:\n",
" print(f\"{WARNING}Enable to extract validity dates in: {', '.join(missing_lines)}.{RESET}\")\n",
"quadruples.sort()\n",
"title = '<details><summary>Fiches horaires à télécharger…</summary>'\n",
"result = [title, \"\"]\n",
"for ((year, month, day), modes) in groupby(quadruples, key=lambda x: x[:3]):\n",
" result.append(f'<p style=\"color: gray\">Jusqu\\'au {day} {month_number_to_name(month)} {year}</p>')\n",
" for mode in modes:\n",
" result.append(f\"<p>{mode[3]}</p>\")\n",
"result.append(f'<p style=\"color: gray\">Été 2023</p>')\n",
"result.append(f'<p>[car 564 Mont-Louis ⇄ Bouillouses]({pdf_urls[\"564\"]})</p>')\n",
"result.append(\"\")\n",
"result = \"\\n\".join(result)\n",
"result = re.sub(r\"(\\[car)\", r'<span class=\"bus\"></span>&nbsp; \\1', result)\n",
"result = re.sub(r\"(\\[train)\", r'<span class=\"train\"></span>&nbsp; \\1', result)\n",
"result = re.sub(r\"(\\[Train Jaune)\", r'<span class=\"train_jaune\"></span>&nbsp; \\1', result)\n",
"print(result)\n",
"path = Path(\"../pages/transports.md.html\")\n",
"text = path.read_text()\n",
"(text, n) = re.subn(fr\"{title}\\n+(<p.+\\n)*\", result, text)\n",
"if n == 1:\n",
" path.write_text(text)\n",
" print(f\"{OK}transports.md.html updated with new dates of validity.{RESET}\")\n",
"else:\n",
" print(f\"{WARNING}Unable to find a paragraph to update in transports.md.html.{RESET}\")"
]
},
{
"cell_type": "markdown",
"id": "75975e83",
"metadata": {},
"source": [
"## Mettre à jour `search.js`"
]
},
{
"cell_type": "code",
"execution_count": 88,
"id": "a40bf02b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[92msearch.js updated with the current list of cities.\u001b[0m\n"
]
}
],
"source": [
"path = Path(\"../www/search.js\")\n",
"text = path.read_text()\n",
"(text, n) = re.subn(fr\"\\\\b\\(.*Perpignan.*\\)\\\\b\", fr\"\\\\b({'|'.join(CITIES)})\\\\b\", text)\n",
"if n == 2:\n",
" path.write_text(text)\n",
" print(f\"{OK}search.js updated with the current list of cities.{RESET}\")\n",
"else:\n",
" print(f\"{WARNING}Unable to find two list of cities in search.js.{RESET}\")"
]
},
{
"cell_type": "markdown",
"id": "2e35de35",
"metadata": {},
"source": [
"## Camelot : extraire les dataframes des cars dans `pbz2/`\n",
"Extraire le texte d'une fiche horaire PDF n'est pas suffisant lorsque les rangées correspondant aux arrêts retenus ne comportent pas le même nombre de valeurs. Par exemple, ligne 521, plusieurs dizaines d'autocars desservent Prades, mais seule une demi-douzaine desservent Casteil. Le texte résultant sera du genre :\n",
"```\n",
"Prades 07:54 08:00 08:46 09:00 09:48 10:00 11:49 12:00 13:28 13:10 ...\n",
"Casteil 08:23 12:23 19:45 12:45 19:23\n",
"```\n",
"Cela se produit pour la quasi-totalité des lignes d'autocars, tandis que les TER desservent toutes les gares situées. Pour les cars, on doit donc opter pour une méthode d'extraction qui préserve les alignements verticaux. La bibliothèque `camelot` permet de repérer les tableaux d'un PDF et de les convertir en _dataframes_ Pandas.\n",
"\n",
"Le traitement des lignes de car se faisant par colonne, il n'est pas nécessaire de supprimer celles qui sont incomplètes. Par exemple, dans la ligne 540, certains trains de Perpignan ne vont pas jusqu'à Collioure. Après filtrage des rangées inutiles, les colonnes correspondantes ne comportent donc que l'horaire à Perpignan, et sont ignorées dans le résultat, puisqu'on ne peut pas l'apparier avec un horaire de Collioure.\n",
"\n",
"**NB.** En cas d'erreur `Camelot: DeprecationError: PdfFileReader is deprecated` appliquer https://stackoverflow.com/a/74957139/173003 et relancer le kernel.\n",
"```\n",
"pip install 'PyPDF2<3.0'\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 89,
"id": "babb92c6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"520: up to date.\n",
"521: up to date.\n",
"523: up to date.\n",
"525: up to date.\n",
"530: up to date.\n",
"540: up to date.\n",
"560: up to date.\n",
"561: up to date.\n",
"train_jaune: up to date.\n",
"PORT BOU - NARBONNE: up to date.\n",
"PERPIGNAN - VILLEFRANCHE: up to date.\n"
]
}
],
"source": [
"for line in LINES_OF_INTEREST:\n",
" if line == \"564\":\n",
" continue # treated as a special case\n",
" print(f\"{line}: \", end=\"\")\n",
" pbz2_path = PBZ2_DIR / f\"{line}.pbz2\"\n",
" pdf_path = PDF_DIR / f\"{line}.pdf\"\n",
" if pbz2_path.is_file() and pdf_path.stat().st_mtime < pbz2_path.stat().st_mtime:\n",
" print(\"up to date.\")\n",
" continue\n",
" df = [camelot.read_pdf(str(PDF_DIR / f\"{line}.pdf\"), pages=page)[0].df for page in \"12\"]\n",
" with bz2.BZ2File(pbz2_path, \"w\") as file:\n",
" pickle.dump(df, file)\n",
" print(f\"{OK}updated.{RESET}\")"
]
},
{
"cell_type": "markdown",
"id": "d70de8a8",
"metadata": {},
"source": [
"## Extraire les segments des lignes de train dans `segments/trains.json`"
]
},
{
"cell_type": "markdown",
"id": "c5a2f768",
"metadata": {},
"source": [
"En cas d'avertissement sur le nombre de colonnes, vérifier le champ `to_delete`. C'est un dictionnaire dont les clés sont la concaténation des rangées où supprimer des colonnes, et les valeurs les index (0-based) des colonnes à supprimer. Le but est d'avoir uniquement des colonnes pleines."
]
},
{
"cell_type": "code",
"execution_count": 90,
"id": "b408c40f",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"PERPIGNAN - VILLEFRANCHE\n",
" 0. PERPIGNAN VILLEFRANCHE-VERNET LES BAINS\n",
" 1. VILLEFRANCHE-VERNET LES BAINS PERPIGNAN\n",
"PORT BOU - NARBONNE\n",
" 0. PERPIGNAN PORT-BOU\n",
" 1. PORT-BOU PERPIGNAN\n",
"train_jaune\n",
" 0. VILLEFRANCHE-VERNET-LES-BAINS LATOUR-DE-CAROL\n",
" 1. LATOUR-DE-CAROL-ENVEITG VILLEFRANCHE-VERNET-LES-BAINS\n"
]
}
],
"source": [
"configurations = json.loads(Path(\"config.json\").read_text())\n",
"segments = {}\n",
"for (line, pages) in configurations.items():\n",
" if line.isdigit():\n",
" continue\n",
" texts = (TEXTS_DIR / f\"{line}.txt\").read_text().split(\"<PAGE BREAK>\")\n",
" print(line)\n",
" for (i, config) in enumerate(configurations[line][\"pages\"]):\n",
" print(f' {i}. {config[\"expected_substring\"]}')\n",
" # Make a sanity check on the title of the page\n",
" if config[\"expected_substring\"] not in texts[i]:\n",
" print(f'Please replace configurations[\"{line}\"][\"pages\"][{i}][\"expected_substring\"] with an actual substring of the text.')\n",
" raise StopExecution\n",
" result = {\n",
" \"Jours\": [day.partition(\"/\")[0] for day in config[\"col_headers\"][1:]],\n",
" \"Note\": [config.get(\"notes\", {}).get(day.partition(\"/\")[2], \"\") for day in config[\"col_headers\"][1:]]\n",
" }\n",
" if line == \"train_jaune\":\n",
" result[\"Mode\"] = [\"Train Jaune\"] * len(config[\"col_headers\"][1:])\n",
" else:\n",
" result[\"Mode\"] = []\n",
" for match in re.findall(r\"Numéro de circulation (.+)\", texts[i]):\n",
" result[\"Mode\"].extend(re.findall(\"(?:ligne )?(\\d+)\", match))\n",
" result[\"Mode\"] = [f\"train {n}\" for n in result[\"Mode\"]]\n",
" for city in config[\"row_headers\"][2:]:\n",
" result[city] = []\n",
" for match in re.findall(fr\"{city}[- \\w]+?([\\d+. ]+)\", texts[i]):\n",
" result[city].extend(s.replace(\".\", \":\") for s in re.findall(\"\\d{1,2}\\.\\d\\d\", match))\n",
" for (row_headers, indexes_to_delete) in config.get(\"to_delete\", {}).items():\n",
" for row_header in row_headers.split():\n",
" result[row_header] = [x for (i, x) in enumerate(result[row_header]) if i not in indexes_to_delete]\n",
" expected_number_of_columns = len(result[\"Jours\"])\n",
" for (k, v) in result.items():\n",
" if k == \"Note\":\n",
" continue\n",
" if len(v) != expected_number_of_columns:\n",
" print(f\"{WARNING}Row {k} has {len(v)} columns, but {expected_number_of_columns} are expected.{RESET}\")\n",
" key = f\"{line} / {config['expected_substring']}\"\n",
" data = list(zip(*result.values()))\n",
" segments[key] = {\"columns\": list(result), \"data\": data}\n",
"(SEGMENTS_DIR / \"trains.json\").write_text(json.dumps(segments, indent=2, ensure_ascii=False));"
]
},
{
"cell_type": "markdown",
"id": "520bdd24",
"metadata": {},
"source": [
"## Extraire les segments des lignes de car dans `segments/cars.json`"
]
},
{
"cell_type": "code",
"execution_count": 91,
"id": "530e2dd3",
"metadata": {
"code_folding": [
0,
19
],
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"520: 0. PRADES > PERPIGNAN"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>...</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>SD</th>\n",
" <th>SD</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" <th>SD</th>\n",
" <th>SD</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td></td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mode</th>\n",
" <td>520</td>\n",
" <td>521</td>\n",
" <td>TRAIN</td>\n",
" <td>520</td>\n",
" <td>521</td>\n",
" <td>520</td>\n",
" <td>521</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>520</td>\n",
" <td>...</td>\n",
" <td>520</td>\n",
" <td>TRAIN</td>\n",
" <td>521</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>521</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td>04:55</td>\n",
" <td>06:01</td>\n",
" <td>06:19</td>\n",
" <td>06:25</td>\n",
" <td>06:35</td>\n",
" <td>06:40</td>\n",
" <td>07:09</td>\n",
" <td>07:16</td>\n",
" <td>08:17</td>\n",
" <td>08:40</td>\n",
" <td>...</td>\n",
" <td>17:35</td>\n",
" <td>18:22</td>\n",
" <td>19:06</td>\n",
" <td>09:18</td>\n",
" <td>11:18</td>\n",
" <td>13:18</td>\n",
" <td>13:44</td>\n",
" <td>15:18</td>\n",
" <td>17:18</td>\n",
" <td>19:17</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td>05:55</td>\n",
" <td>07:05</td>\n",
" <td>06:59</td>\n",
" <td>07:30</td>\n",
" <td>07:20</td>\n",
" <td>07:50</td>\n",
" <td>08:15</td>\n",
" <td>07:57</td>\n",
" <td>08:59</td>\n",
" <td>09:50</td>\n",
" <td>...</td>\n",
" <td>18:45</td>\n",
" <td>19:04</td>\n",
" <td>20:10</td>\n",
" <td>09:59</td>\n",
" <td>11:59</td>\n",
" <td>13:59</td>\n",
" <td>14:50</td>\n",
" <td>15:59</td>\n",
" <td>17:59</td>\n",
" <td>19:55</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5 rows × 27 columns</p>\n",
"</div>"
],
"text/plain": [
"Jours LMWJV LMWJV LMWJV LMWJV LMWJV LMWJV LMWJV LMWJVS LMWJVS \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • \n",
"Mode 520 521 TRAIN 520 521 520 521 TRAIN TRAIN \n",
"Prades 04:55 06:01 06:19 06:25 06:35 06:40 07:09 07:16 08:17 \n",
"Perpignan 05:55 07:05 06:59 07:30 07:20 07:50 08:15 07:57 08:59 \n",
"\n",
"Jours LMWJV ... LMWJV LMWJV LMWJV SD SD SD S SD \\\n",
"PS • ... • • • • • • • • \n",
"PV • ... • • • • • • • • \n",
"Mode 520 ... 520 TRAIN 521 TRAIN TRAIN TRAIN 521 TRAIN \n",
"Prades 08:40 ... 17:35 18:22 19:06 09:18 11:18 13:18 13:44 15:18 \n",
"Perpignan 09:50 ... 18:45 19:04 20:10 09:59 11:59 13:59 14:50 15:59 \n",
"\n",
"Jours SD SD \n",
"PS • • \n",
"PV • • \n",
"Mode TRAIN TRAIN \n",
"Prades 17:18 19:17 \n",
"Perpignan 17:59 19:55 \n",
"\n",
"[5 rows x 27 columns]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"520: 1. PERPIGNAN > PRADES"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>W</th>\n",
" <th>W</th>\n",
" <th>LMWJV</th>\n",
" <th>...</th>\n",
" <th>LMWJV</th>\n",
" <th>SD</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" <th>SD</th>\n",
" <th>SD</th>\n",
" <th>SD</th>\n",
" <th>D</th>\n",
" <th>D</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mode</th>\n",
" <td>TRAIN</td>\n",
" <td>520</td>\n",
" <td>520</td>\n",
" <td>TRAIN</td>\n",
" <td>520</td>\n",
" <td>TRAIN</td>\n",
" <td>521</td>\n",
" <td>520</td>\n",
" <td>521</td>\n",
" <td>520</td>\n",
" <td>...</td>\n",
" <td>521</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>521</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" <td>TRAIN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td>07:11</td>\n",
" <td>07:30</td>\n",
" <td>08:05</td>\n",
" <td>09:09</td>\n",
" <td>10:05</td>\n",
" <td>11:10</td>\n",
" <td>12:15</td>\n",
" <td>12:30</td>\n",
" <td>12:30</td>\n",
" <td>12:45</td>\n",
" <td>...</td>\n",
" <td>19:30</td>\n",
" <td>08:11</td>\n",
" <td>10:11</td>\n",
" <td>11:05</td>\n",
" <td>12:11</td>\n",
" <td>14:11</td>\n",
" <td>16:11</td>\n",
" <td>18:13</td>\n",
" <td>19:11</td>\n",
" <td>20:11</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td>07:54</td>\n",
" <td>08:46</td>\n",
" <td>09:21</td>\n",
" <td>09:48</td>\n",
" <td>11:21</td>\n",
" <td>11:49</td>\n",
" <td>13:28</td>\n",
" <td>13:20</td>\n",
" <td>13:10</td>\n",
" <td>13:55</td>\n",
" <td>...</td>\n",
" <td>20:43</td>\n",
" <td>08:55</td>\n",
" <td>10:50</td>\n",
" <td>12:18</td>\n",
" <td>12:50</td>\n",
" <td>14:50</td>\n",
" <td>16:50</td>\n",
" <td>18:52</td>\n",
" <td>19:53</td>\n",
" <td>20:50</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5 rows × 31 columns</p>\n",
"</div>"
],
"text/plain": [
"Jours LMWJV LMWJV LMWJV LMWJV LMWJV LMWJV LMWJV W W \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • \n",
"Mode TRAIN 520 520 TRAIN 520 TRAIN 521 520 521 \n",
"Perpignan 07:11 07:30 08:05 09:09 10:05 11:10 12:15 12:30 12:30 \n",
"Prades 07:54 08:46 09:21 09:48 11:21 11:49 13:28 13:20 13:10 \n",
"\n",
"Jours LMWJV ... LMWJV SD SD S SD SD SD SD \\\n",
"PS • ... • • • • • • • • \n",
"PV • ... • • • • • • • • \n",
"Mode 520 ... 521 TRAIN TRAIN 521 TRAIN TRAIN TRAIN TRAIN \n",
"Perpignan 12:45 ... 19:30 08:11 10:11 11:05 12:11 14:11 16:11 18:13 \n",
"Prades 13:55 ... 20:43 08:55 10:50 12:18 12:50 14:50 16:50 18:52 \n",
"\n",
"Jours D D \n",
"PS • • \n",
"PV • • \n",
"Mode TRAIN TRAIN \n",
"Perpignan 19:11 20:11 \n",
"Prades 19:53 20:50 \n",
"\n",
"[5 rows x 31 columns]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"521: 0. PERPIGNAN > VERNET"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>W</th>\n",
" <th>...</th>\n",
" <th>LMWJV</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td></td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mode</th>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>520</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>521</td>\n",
" <td>521</td>\n",
" <td>...</td>\n",
" <td>521</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>521</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td>07:11</td>\n",
" <td></td>\n",
" <td>07:30</td>\n",
" <td></td>\n",
" <td>09:09</td>\n",
" <td></td>\n",
" <td>11:10</td>\n",
" <td></td>\n",
" <td>12:15</td>\n",
" <td>12:30</td>\n",
" <td>...</td>\n",
" <td>19:30</td>\n",
" <td>08:11</td>\n",
" <td></td>\n",
" <td>10:11</td>\n",
" <td></td>\n",
" <td>11:05</td>\n",
" <td>16:11</td>\n",
" <td></td>\n",
" <td>18:13</td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td>07:54</td>\n",
" <td>08:00</td>\n",
" <td>08:46</td>\n",
" <td>09:00</td>\n",
" <td>09:48</td>\n",
" <td>10:00</td>\n",
" <td>11:49</td>\n",
" <td>12:00</td>\n",
" <td>13:28</td>\n",
" <td>13:10</td>\n",
" <td>...</td>\n",
" <td>20:43</td>\n",
" <td>08:55</td>\n",
" <td>09:00</td>\n",
" <td>10:50</td>\n",
" <td>11:00</td>\n",
" <td>12:18</td>\n",
" <td>16:50</td>\n",
" <td>17:00</td>\n",
" <td>18:52</td>\n",
" <td>19:00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Villefranche</th>\n",
" <td></td>\n",
" <td>08:11</td>\n",
" <td></td>\n",
" <td>09:11</td>\n",
" <td></td>\n",
" <td>10:11</td>\n",
" <td></td>\n",
" <td>12:11</td>\n",
" <td>13:42</td>\n",
" <td>13:24</td>\n",
" <td>...</td>\n",
" <td>20:57</td>\n",
" <td></td>\n",
" <td>09:11</td>\n",
" <td></td>\n",
" <td>11:11</td>\n",
" <td>12:32</td>\n",
" <td></td>\n",
" <td>17:11</td>\n",
" <td></td>\n",
" <td>19:11</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Vernet</th>\n",
" <td></td>\n",
" <td>08:19</td>\n",
" <td></td>\n",
" <td>09:19</td>\n",
" <td></td>\n",
" <td>10:19</td>\n",
" <td></td>\n",
" <td>12:19</td>\n",
" <td>13:49</td>\n",
" <td>13:32</td>\n",
" <td>...</td>\n",
" <td>21:04</td>\n",
" <td></td>\n",
" <td>09:19</td>\n",
" <td></td>\n",
" <td>11:19</td>\n",
" <td>12:39</td>\n",
" <td></td>\n",
" <td>17:19</td>\n",
" <td></td>\n",
" <td>19:19</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Casteil</th>\n",
" <td></td>\n",
" <td>08:23</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>12:23</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>...</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>12:45</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>19:23</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>8 rows × 27 columns</p>\n",
"</div>"
],
"text/plain": [
"Jours LMWJV LMWJV LMWJV LMWJV LMWJV LMWJV LMWJV LMWJV LMWJV \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • • \n",
"Mode TRAIN 522 520 522 TRAIN 522 TRAIN 522 521 \n",
"Perpignan 07:11 07:30 09:09 11:10 12:15 \n",
"Prades 07:54 08:00 08:46 09:00 09:48 10:00 11:49 12:00 13:28 \n",
"Villefranche 08:11 09:11 10:11 12:11 13:42 \n",
"Vernet 08:19 09:19 10:19 12:19 13:49 \n",
"Casteil 08:23 12:23 \n",
"\n",
"Jours W ... LMWJV SD S SD S S SD \\\n",
"PS • ... • • • • • • • \n",
"PV ... • • • • • • • \n",
"Mode 521 ... 521 TRAIN 522 TRAIN 522 521 TRAIN \n",
"Perpignan 12:30 ... 19:30 08:11 10:11 11:05 16:11 \n",
"Prades 13:10 ... 20:43 08:55 09:00 10:50 11:00 12:18 16:50 \n",
"Villefranche 13:24 ... 20:57 09:11 11:11 12:32 \n",
"Vernet 13:32 ... 21:04 09:19 11:19 12:39 \n",
"Casteil ... 12:45 \n",
"\n",
"Jours S SD S \n",
"PS • • • \n",
"PV • • • \n",
"Mode 522 TRAIN 522 \n",
"Perpignan 18:13 \n",
"Prades 17:00 18:52 19:00 \n",
"Villefranche 17:11 19:11 \n",
"Vernet 17:19 19:19 \n",
"Casteil 19:23 \n",
"\n",
"[8 rows x 27 columns]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"521: 1. VERNET > PERPIGNAN"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>...</th>\n",
" <th>LMWJV</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>SD</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td></td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mode</th>\n",
" <td>521</td>\n",
" <td>521</td>\n",
" <td>521</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>...</td>\n",
" <td>521</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>521</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" <td>522</td>\n",
" <td>TRAIN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Casteil</th>\n",
" <td></td>\n",
" <td>06:15</td>\n",
" <td>06:45</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>08:30</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>...</td>\n",
" <td></td>\n",
" <td>08:30</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>13:20</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>Vernet</th>\n",
" <td>05:41</td>\n",
" <td>06:19</td>\n",
" <td>06:49</td>\n",
" <td>07:31</td>\n",
" <td></td>\n",
" <td>08:35</td>\n",
" <td>09:31</td>\n",
" <td></td>\n",
" <td>11:31</td>\n",
" <td></td>\n",
" <td>...</td>\n",
" <td>18:46</td>\n",
" <td>08:35</td>\n",
" <td></td>\n",
" <td>10:31</td>\n",
" <td></td>\n",
" <td>13:24</td>\n",
" <td>16:31</td>\n",
" <td></td>\n",
" <td>18:31</td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>Villefranche</th>\n",
" <td>05:50</td>\n",
" <td>06:25</td>\n",
" <td>06:58</td>\n",
" <td>07:42</td>\n",
" <td></td>\n",
" <td>08:46</td>\n",
" <td>09:42</td>\n",
" <td></td>\n",
" <td>11:42</td>\n",
" <td></td>\n",
" <td>...</td>\n",
" <td>18:55</td>\n",
" <td>08:46</td>\n",
" <td></td>\n",
" <td>10:42</td>\n",
" <td></td>\n",
" <td>13:33</td>\n",
" <td>16:42</td>\n",
" <td></td>\n",
" <td>18:42</td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td>06:01</td>\n",
" <td>06:35</td>\n",
" <td>07:09</td>\n",
" <td>07:53</td>\n",
" <td>08:17</td>\n",
" <td>08:57</td>\n",
" <td>09:53</td>\n",
" <td>10:18</td>\n",
" <td>11:53</td>\n",
" <td>12:18</td>\n",
" <td>...</td>\n",
" <td>19:06</td>\n",
" <td>08:57</td>\n",
" <td>09:18</td>\n",
" <td>10:53</td>\n",
" <td>11:18</td>\n",
" <td>13:44</td>\n",
" <td>16:53</td>\n",
" <td>17:18</td>\n",
" <td>18:53</td>\n",
" <td>19:17</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td>07:05</td>\n",
" <td>07:20</td>\n",
" <td>08:15</td>\n",
" <td></td>\n",
" <td>08:59</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>10:59</td>\n",
" <td></td>\n",
" <td>12:59</td>\n",
" <td>...</td>\n",
" <td>20:10</td>\n",
" <td></td>\n",
" <td>09:59</td>\n",
" <td></td>\n",
" <td>11:59</td>\n",
" <td>14:50</td>\n",
" <td></td>\n",
" <td>17:59</td>\n",
" <td></td>\n",
" <td>19:55</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>8 rows × 25 columns</p>\n",
"</div>"
],
"text/plain": [
"Jours LMWJV LMWJV LMWJV LMWJV LMWJVS LMWJV LMWJV LMWJV LMWJV \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • \n",
"Mode 521 521 521 522 TRAIN 522 522 TRAIN 522 \n",
"Casteil 06:15 06:45 08:30 \n",
"Vernet 05:41 06:19 06:49 07:31 08:35 09:31 11:31 \n",
"Villefranche 05:50 06:25 06:58 07:42 08:46 09:42 11:42 \n",
"Prades 06:01 06:35 07:09 07:53 08:17 08:57 09:53 10:18 11:53 \n",
"Perpignan 07:05 07:20 08:15 08:59 10:59 \n",
"\n",
"Jours LMWJV ... LMWJV S SD S SD S S \\\n",
"PS • ... • • • • • • • \n",
"PV • ... • • • • • • • \n",
"Mode TRAIN ... 521 522 TRAIN 522 TRAIN 521 522 \n",
"Casteil ... 08:30 13:20 \n",
"Vernet ... 18:46 08:35 10:31 13:24 16:31 \n",
"Villefranche ... 18:55 08:46 10:42 13:33 16:42 \n",
"Prades 12:18 ... 19:06 08:57 09:18 10:53 11:18 13:44 16:53 \n",
"Perpignan 12:59 ... 20:10 09:59 11:59 14:50 \n",
"\n",
"Jours SD S SD \n",
"PS • • • \n",
"PV • • • \n",
"Mode TRAIN 522 TRAIN \n",
"Casteil \n",
"Vernet 18:31 \n",
"Villefranche 18:42 \n",
"Prades 17:18 18:53 19:17 \n",
"Perpignan 17:59 19:55 \n",
"\n",
"[8 rows x 25 columns]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"523: 0. MOSSET > PRADES"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mosset</th>\n",
" <td>05:51</td>\n",
" <td>07:22</td>\n",
" <td>08:27</td>\n",
" <td>10:27</td>\n",
" <td>13:31</td>\n",
" <td>14:27</td>\n",
" <td>16:27</td>\n",
" <td>07:06</td>\n",
" <td>12:57</td>\n",
" <td>17:22</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td>06:20</td>\n",
" <td>07:50</td>\n",
" <td>08:55</td>\n",
" <td>10:55</td>\n",
" <td>14:00</td>\n",
" <td>14:57</td>\n",
" <td>16:55</td>\n",
" <td>07:35</td>\n",
" <td>13:25</td>\n",
" <td>17:50</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Jours LMWJV LMWJV LMWJVS LMWJV LMWJV LMWJV LMWJV S S S\n",
"PS • • • • • • • • • •\n",
"PV • • • • • • • • • •\n",
"Mosset 05:51 07:22 08:27 10:27 13:31 14:27 16:27 07:06 12:57 17:22\n",
"Prades 06:20 07:50 08:55 10:55 14:00 14:57 16:55 07:35 13:25 17:50"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"523: 1. PRADES > MOSSET"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td>06:56</td>\n",
" <td>08:01</td>\n",
" <td>10:01</td>\n",
" <td>12:01</td>\n",
" <td>14:01</td>\n",
" <td>16:01</td>\n",
" <td>18:11</td>\n",
" <td>12:31</td>\n",
" <td>16:56</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mosset</th>\n",
" <td>07:22</td>\n",
" <td>08:27</td>\n",
" <td>10:27</td>\n",
" <td>12:27</td>\n",
" <td>14:27</td>\n",
" <td>16:27</td>\n",
" <td>18:37</td>\n",
" <td>12:57</td>\n",
" <td>17:22</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Jours LMWJV LMWJVS LMWJV LMWJV LMWJV LMWJV LMWJV S S\n",
"PS • • • • • • • • •\n",
"PV • • • • • • • • •\n",
"Prades 06:56 08:01 10:01 12:01 14:01 16:01 18:11 12:31 16:56\n",
"Mosset 07:22 08:27 10:27 12:27 14:27 16:27 18:37 12:57 17:22"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"525: 0. PY > PRADES"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Py</th>\n",
" <td>08:20</td>\n",
" <td>13:00</td>\n",
" <td>17:45</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td>08:53</td>\n",
" <td>13:33</td>\n",
" <td>18:18</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJVS\n",
"PS • • •\n",
"PV • • •\n",
"Py 08:20 13:00 17:45\n",
"Prades 08:53 13:33 18:18"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"525: 1. PRADES > PY"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td>07:46</td>\n",
" <td>12:21</td>\n",
" <td>17:11</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Py</th>\n",
" <td>08:20</td>\n",
" <td>12:55</td>\n",
" <td>17:45</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJVS\n",
"PS • • •\n",
"PV • • •\n",
"Prades 07:46 12:21 17:11\n",
"Py 08:20 12:55 17:45"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"530: 0. ARLES SUR TECH > PERPIGNAN"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>D</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td></td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Arles-sur-Tech</th>\n",
" <td>05:45</td>\n",
" <td>06:10</td>\n",
" <td>06:25</td>\n",
" <td></td>\n",
" <td>06:55</td>\n",
" <td></td>\n",
" <td>07:25</td>\n",
" <td>08:25</td>\n",
" <td>09:30</td>\n",
" <td>10:30</td>\n",
" <td>11:25</td>\n",
" <td>12:30</td>\n",
" <td>13:35</td>\n",
" <td>14:25</td>\n",
" <td>15:30</td>\n",
" <td>16:30</td>\n",
" <td>17:30</td>\n",
" <td>18:35</td>\n",
" <td></td>\n",
" <td>11:30</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Céret</th>\n",
" <td>06:15</td>\n",
" <td>06:40</td>\n",
" <td>06:55</td>\n",
" <td></td>\n",
" <td>07:25</td>\n",
" <td>07:15</td>\n",
" <td>07:55</td>\n",
" <td>08:55</td>\n",
" <td>10:00</td>\n",
" <td>11:00</td>\n",
" <td>11:55</td>\n",
" <td>13:00</td>\n",
" <td>14:05</td>\n",
" <td>14:55</td>\n",
" <td>16:00</td>\n",
" <td>17:00</td>\n",
" <td>18:00</td>\n",
" <td>19:05</td>\n",
" <td>18:45</td>\n",
" <td>12:00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td>07:10</td>\n",
" <td>07:40</td>\n",
" <td>07:50</td>\n",
" <td>07:20</td>\n",
" <td>08:20</td>\n",
" <td>08:05</td>\n",
" <td>08:50</td>\n",
" <td>09:45</td>\n",
" <td>10:45</td>\n",
" <td>11:45</td>\n",
" <td>12:45</td>\n",
" <td>13:45</td>\n",
" <td>14:50</td>\n",
" <td>15:45</td>\n",
" <td>16:45</td>\n",
" <td>17:45</td>\n",
" <td>18:45</td>\n",
" <td>19:50</td>\n",
" <td>19:35</td>\n",
" <td>12:45</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJVS LMWJV LMWJV LMWJV LMWJVS LMWJVS \\\n",
"PS • • • • • • • • \n",
"PV • • • • • • • \n",
"Arles-sur-Tech 05:45 06:10 06:25 06:55 07:25 08:25 \n",
"Céret 06:15 06:40 06:55 07:25 07:15 07:55 08:55 \n",
"Perpignan 07:10 07:40 07:50 07:20 08:20 08:05 08:50 09:45 \n",
"\n",
"Jours LMWJVSD LMWJVS LMWJVS LMWJVS LMWJVS LMWJVS LMWJVSD LMWJVS \\\n",
"PS • • • • • • • • \n",
"PV • • • • • • • • \n",
"Arles-sur-Tech 09:30 10:30 11:25 12:30 13:35 14:25 15:30 16:30 \n",
"Céret 10:00 11:00 11:55 13:00 14:05 14:55 16:00 17:00 \n",
"Perpignan 10:45 11:45 12:45 13:45 14:50 15:45 16:45 17:45 \n",
"\n",
"Jours LMWJVSD LMWJVS LMWJVS D \n",
"PS • • • • \n",
"PV • • • • \n",
"Arles-sur-Tech 17:30 18:35 11:30 \n",
"Céret 18:00 19:05 18:45 12:00 \n",
"Perpignan 18:45 19:50 19:35 12:45 "
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"530: 1. PERPIGNAN > ARLES SUR TECH"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>W</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>...</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>D</th>\n",
" <th>D</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td></td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td></td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td></td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td>06:25</td>\n",
" <td>07:10</td>\n",
" <td>08:10</td>\n",
" <td>09:10</td>\n",
" <td>10:10</td>\n",
" <td>12:30</td>\n",
" <td>12:10</td>\n",
" <td>12:20</td>\n",
" <td>12:45</td>\n",
" <td>13:10</td>\n",
" <td>...</td>\n",
" <td>17:10</td>\n",
" <td>17:30</td>\n",
" <td>17:45</td>\n",
" <td>17:50</td>\n",
" <td>18:10</td>\n",
" <td>18:30</td>\n",
" <td>18:45</td>\n",
" <td>19:30</td>\n",
" <td>09:10</td>\n",
" <td>11:10</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Céret</th>\n",
" <td>07:15</td>\n",
" <td>07:57</td>\n",
" <td>08:57</td>\n",
" <td>09:54</td>\n",
" <td>10:57</td>\n",
" <td></td>\n",
" <td>12:57</td>\n",
" <td>13:04</td>\n",
" <td>13:32</td>\n",
" <td>13:57</td>\n",
" <td>...</td>\n",
" <td>17:52</td>\n",
" <td>18:20</td>\n",
" <td>18:27</td>\n",
" <td>18:32</td>\n",
" <td>18:52</td>\n",
" <td>19:20</td>\n",
" <td>19:27</td>\n",
" <td>20:17</td>\n",
" <td>09:57</td>\n",
" <td>11:57</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Arles-sur-Tech</th>\n",
" <td></td>\n",
" <td>08:25</td>\n",
" <td>09:25</td>\n",
" <td>10:25</td>\n",
" <td>11:25</td>\n",
" <td></td>\n",
" <td>13:25</td>\n",
" <td>13:35</td>\n",
" <td>14:00</td>\n",
" <td>14:25</td>\n",
" <td>...</td>\n",
" <td>18:23</td>\n",
" <td></td>\n",
" <td>18:58</td>\n",
" <td></td>\n",
" <td>19:23</td>\n",
" <td></td>\n",
" <td>19:58</td>\n",
" <td>20:45</td>\n",
" <td>10:25</td>\n",
" <td>12:25</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5 rows × 24 columns</p>\n",
"</div>"
],
"text/plain": [
"Jours LMWJV LMWJVS LMWJVS LMWJVS LMWJVS W LMWJVS LMWJVS LMWJVS \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • \n",
"Perpignan 06:25 07:10 08:10 09:10 10:10 12:30 12:10 12:20 12:45 \n",
"Céret 07:15 07:57 08:57 09:54 10:57 12:57 13:04 13:32 \n",
"Arles-sur-Tech 08:25 09:25 10:25 11:25 13:25 13:35 14:00 \n",
"\n",
"Jours LMWJVS ... LMWJVSD LMWJV LMWJVS LMWJVS LMWJVS LMWJV LMWJVS \\\n",
"PS • ... • • • • • • • \n",
"PV • ... • • • • • \n",
"Perpignan 13:10 ... 17:10 17:30 17:45 17:50 18:10 18:30 18:45 \n",
"Céret 13:57 ... 17:52 18:20 18:27 18:32 18:52 19:20 19:27 \n",
"Arles-sur-Tech 14:25 ... 18:23 18:58 19:23 19:58 \n",
"\n",
"Jours LMWJVS D D \n",
"PS • • • \n",
"PV • • • \n",
"Perpignan 19:30 09:10 11:10 \n",
"Céret 20:17 09:57 11:57 \n",
"Arles-sur-Tech 20:45 10:25 12:25 \n",
"\n",
"[5 rows x 24 columns]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"540: 0. BANYULS SUR MER > PERPIGNAN"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Collioure</th>\n",
" <td></td>\n",
" <td>06:39</td>\n",
" <td></td>\n",
" <td>07:09</td>\n",
" <td>07:39</td>\n",
" <td>08:39</td>\n",
" <td>09:39</td>\n",
" <td>10:39</td>\n",
" <td>12:39</td>\n",
" <td></td>\n",
" <td>13:49</td>\n",
" <td>14:39</td>\n",
" <td>15:39</td>\n",
" <td>16:39</td>\n",
" <td>17:39</td>\n",
" <td>18:04</td>\n",
" <td>18:39</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td>07:05</td>\n",
" <td>07:40</td>\n",
" <td>07:30</td>\n",
" <td>08:15</td>\n",
" <td>08:45</td>\n",
" <td>09:45</td>\n",
" <td>10:45</td>\n",
" <td>11:45</td>\n",
" <td>13:45</td>\n",
" <td>13:30</td>\n",
" <td>14:55</td>\n",
" <td>15:45</td>\n",
" <td>16:45</td>\n",
" <td>17:45</td>\n",
" <td>18:45</td>\n",
" <td>19:10</td>\n",
" <td>19:45</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJVS LMWJVS LMWJVS LMWJVSD LMWJVS LMWJVSD LMWJVS \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • • \n",
"Collioure 06:39 07:09 07:39 08:39 09:39 10:39 12:39 \n",
"Perpignan 07:05 07:40 07:30 08:15 08:45 09:45 10:45 11:45 13:45 \n",
"\n",
"Jours LMWJVS LMWJVS LMWJVSD LMWJVS LMWJVSD LMWJVS LMWJVS LMWJVS \n",
"PS • • • • • • • • \n",
"PV • • • • • • • • \n",
"Collioure 13:49 14:39 15:39 16:39 17:39 18:04 18:39 \n",
"Perpignan 13:30 14:55 15:45 16:45 17:45 18:45 19:10 19:45 "
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"540: 1. PERPIGNAN > BANYULS SUR MER"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>D</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVSD</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>...</th>\n",
" <th>LMWJVS</th>\n",
" <th>D</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td>06:30</td>\n",
" <td>07:10</td>\n",
" <td>08:10</td>\n",
" <td>09:15</td>\n",
" <td>10:10</td>\n",
" <td>11:15</td>\n",
" <td>12:05</td>\n",
" <td>12:10</td>\n",
" <td>12:45</td>\n",
" <td>13:10</td>\n",
" <td>...</td>\n",
" <td>15:15</td>\n",
" <td>16:10</td>\n",
" <td>16:15</td>\n",
" <td>16:45</td>\n",
" <td>17:05</td>\n",
" <td>17:10</td>\n",
" <td>17:45</td>\n",
" <td>18:10</td>\n",
" <td>18:30</td>\n",
" <td>19:30</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Collioure</th>\n",
" <td>07:34</td>\n",
" <td>08:14</td>\n",
" <td>09:14</td>\n",
" <td>10:14</td>\n",
" <td>11:14</td>\n",
" <td>12:14</td>\n",
" <td></td>\n",
" <td>13:14</td>\n",
" <td>13:44</td>\n",
" <td>14:14</td>\n",
" <td>...</td>\n",
" <td>16:14</td>\n",
" <td>17:14</td>\n",
" <td>17:14</td>\n",
" <td>17:49</td>\n",
" <td></td>\n",
" <td>18:14</td>\n",
" <td>18:49</td>\n",
" <td></td>\n",
" <td>19:34</td>\n",
" <td>20:29</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>4 rows × 21 columns</p>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJVSD LMWJVS D LMWJVS LMWJVS LMWJVSD LMWJVS \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • • \n",
"Perpignan 06:30 07:10 08:10 09:15 10:10 11:15 12:05 12:10 12:45 \n",
"Collioure 07:34 08:14 09:14 10:14 11:14 12:14 13:14 13:44 \n",
"\n",
"Jours LMWJVS ... LMWJVS D LMWJVS LMWJVS LMWJVS LMWJVS LMWJVS LMWJVS \\\n",
"PS • ... • • • • • • • • \n",
"PV • ... • • • • • • • • \n",
"Perpignan 13:10 ... 15:15 16:10 16:15 16:45 17:05 17:10 17:45 18:10 \n",
"Collioure 14:14 ... 16:14 17:14 17:14 17:49 18:14 18:49 \n",
"\n",
"Jours LMWJVS LMWJVS \n",
"PS • • \n",
"PV • • \n",
"Perpignan 18:30 19:30 \n",
"Collioure 19:34 20:29 \n",
"\n",
"[4 rows x 21 columns]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"560: 0. PORTE PUYMORENS > PERPIGNAN"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>...</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>D</th>\n",
" <th>SD</th>\n",
" <th>D</th>\n",
" <th>SD</th>\n",
" <th>D</th>\n",
" <th>SD</th>\n",
" <th>D</th>\n",
" <th>SD</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mode</th>\n",
" <td>560</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>...</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mont-Louis</th>\n",
" <td>06:25</td>\n",
" <td>06:59</td>\n",
" <td></td>\n",
" <td>08:53</td>\n",
" <td></td>\n",
" <td>10:23</td>\n",
" <td></td>\n",
" <td>12:58</td>\n",
" <td></td>\n",
" <td>14:39</td>\n",
" <td>...</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>10:00</td>\n",
" <td></td>\n",
" <td>12:00</td>\n",
" <td></td>\n",
" <td>16:00</td>\n",
" <td></td>\n",
" <td>17:45</td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>Villefranche</th>\n",
" <td></td>\n",
" <td>07:44</td>\n",
" <td></td>\n",
" <td>09:38</td>\n",
" <td></td>\n",
" <td>11:08</td>\n",
" <td></td>\n",
" <td>13:43</td>\n",
" <td></td>\n",
" <td>15:24</td>\n",
" <td>...</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>10:50</td>\n",
" <td></td>\n",
" <td>12:50</td>\n",
" <td></td>\n",
" <td>16:50</td>\n",
" <td></td>\n",
" <td>18:35</td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td></td>\n",
" <td>07:54</td>\n",
" <td>08:17</td>\n",
" <td>09:48</td>\n",
" <td>10:18</td>\n",
" <td>11:18</td>\n",
" <td>12:18</td>\n",
" <td>13:53</td>\n",
" <td>14:18</td>\n",
" <td>15:34</td>\n",
" <td>...</td>\n",
" <td>19:17</td>\n",
" <td></td>\n",
" <td>11:00</td>\n",
" <td>11:18</td>\n",
" <td>13:00</td>\n",
" <td>13:18</td>\n",
" <td>17:00</td>\n",
" <td>17:18</td>\n",
" <td>18:45</td>\n",
" <td>19:17</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td></td>\n",
" <td>08:45</td>\n",
" <td>08:59</td>\n",
" <td>10:39</td>\n",
" <td>10:59</td>\n",
" <td></td>\n",
" <td>12:59</td>\n",
" <td></td>\n",
" <td>14:59</td>\n",
" <td></td>\n",
" <td>...</td>\n",
" <td>19:55</td>\n",
" <td></td>\n",
" <td>11:50</td>\n",
" <td>11:59</td>\n",
" <td>13:50</td>\n",
" <td>13:59</td>\n",
" <td>17:50</td>\n",
" <td>17:59</td>\n",
" <td>19:35</td>\n",
" <td>19:55</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>7 rows × 34 columns</p>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJVS LMWJVS LMWJV LMWJV LMWJV LMWJV LMWJV \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • • \n",
"Mode 560 560 TRAIN 560 TRAIN 560 TRAIN 560 TRAIN \n",
"Mont-Louis 06:25 06:59 08:53 10:23 12:58 \n",
"Villefranche 07:44 09:38 11:08 13:43 \n",
"Prades 07:54 08:17 09:48 10:18 11:18 12:18 13:53 14:18 \n",
"Perpignan 08:45 08:59 10:39 10:59 12:59 14:59 \n",
"\n",
"Jours LMWJV ... SD S D SD D SD D \\\n",
"PS • ... • • • • • • • \n",
"PV • ... • • • • • • • \n",
"Mode 560 ... TRAIN 560 560 TRAIN 560 TRAIN 560 \n",
"Mont-Louis 14:39 ... 10:00 12:00 16:00 \n",
"Villefranche 15:24 ... 10:50 12:50 16:50 \n",
"Prades 15:34 ... 19:17 11:00 11:18 13:00 13:18 17:00 \n",
"Perpignan ... 19:55 11:50 11:59 13:50 13:59 17:50 \n",
"\n",
"Jours SD D SD \n",
"PS • • • \n",
"PV • • • \n",
"Mode TRAIN 560 TRAIN \n",
"Mont-Louis 17:45 \n",
"Villefranche 18:35 \n",
"Prades 17:18 18:45 19:17 \n",
"Perpignan 17:59 19:35 19:55 \n",
"\n",
"[7 rows x 34 columns]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"560: 1. PERPIGNAN > PORTE PUYMORENS"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>...</th>\n",
" <th>SD</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" <th>D</th>\n",
" <th>SD</th>\n",
" <th>D</th>\n",
" <th>SD</th>\n",
" <th>D</th>\n",
" <th>SD</th>\n",
" <th>D</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>...</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mode</th>\n",
" <td>560</td>\n",
" <td>560</td>\n",
" <td>520</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>520</td>\n",
" <td>560</td>\n",
" <td>...</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>560</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Perpignan</th>\n",
" <td></td>\n",
" <td>06:50</td>\n",
" <td>08:05</td>\n",
" <td>08:30</td>\n",
" <td>11:10</td>\n",
" <td></td>\n",
" <td>13:11</td>\n",
" <td></td>\n",
" <td>14:05</td>\n",
" <td></td>\n",
" <td>...</td>\n",
" <td>12:11</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>08:30</td>\n",
" <td>12:11</td>\n",
" <td>12:15</td>\n",
" <td>14:11</td>\n",
" <td>14:15</td>\n",
" <td>18:13</td>\n",
" <td>18:20</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Prades</th>\n",
" <td></td>\n",
" <td>07:41</td>\n",
" <td>09:20</td>\n",
" <td>09:21</td>\n",
" <td>11:49</td>\n",
" <td>12:05</td>\n",
" <td>13:49</td>\n",
" <td>14:05</td>\n",
" <td>15:15</td>\n",
" <td>15:30</td>\n",
" <td>...</td>\n",
" <td>12:50</td>\n",
" <td>13:05</td>\n",
" <td></td>\n",
" <td>09:20</td>\n",
" <td>12:50</td>\n",
" <td>13:05</td>\n",
" <td>14:50</td>\n",
" <td>15:05</td>\n",
" <td>18:52</td>\n",
" <td>19:10</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Villefranche</th>\n",
" <td></td>\n",
" <td>07:51</td>\n",
" <td></td>\n",
" <td>09:34</td>\n",
" <td></td>\n",
" <td>12:15</td>\n",
" <td></td>\n",
" <td>14:15</td>\n",
" <td></td>\n",
" <td>15:49</td>\n",
" <td>...</td>\n",
" <td></td>\n",
" <td>13:15</td>\n",
" <td></td>\n",
" <td>09:25</td>\n",
" <td></td>\n",
" <td>13:10</td>\n",
" <td></td>\n",
" <td>15:10</td>\n",
" <td></td>\n",
" <td>19:15</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mont-Louis</th>\n",
" <td>06:25</td>\n",
" <td>08:38</td>\n",
" <td></td>\n",
" <td>10:21</td>\n",
" <td></td>\n",
" <td>13:02</td>\n",
" <td></td>\n",
" <td>15:02</td>\n",
" <td></td>\n",
" <td>16:36</td>\n",
" <td>...</td>\n",
" <td></td>\n",
" <td>14:02</td>\n",
" <td></td>\n",
" <td>10:15</td>\n",
" <td></td>\n",
" <td>14:00</td>\n",
" <td></td>\n",
" <td>16:00</td>\n",
" <td></td>\n",
" <td>20:05</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>7 rows × 26 columns</p>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJV LMWJVS LMWJV LMWJV LMWJV LMWJV LMWJV \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • • \n",
"Mode 560 560 520 560 TRAIN 560 TRAIN 560 520 \n",
"Perpignan 06:50 08:05 08:30 11:10 13:11 14:05 \n",
"Prades 07:41 09:20 09:21 11:49 12:05 13:49 14:05 15:15 \n",
"Villefranche 07:51 09:34 12:15 14:15 \n",
"Mont-Louis 06:25 08:38 10:21 13:02 15:02 \n",
"\n",
"Jours LMWJVS ... SD S S D SD D SD \\\n",
"PS • ... • • • • • • • \n",
"PV • ... • • • • • • • \n",
"Mode 560 ... TRAIN 560 560 560 TRAIN 560 TRAIN \n",
"Perpignan ... 12:11 08:30 12:11 12:15 14:11 \n",
"Prades 15:30 ... 12:50 13:05 09:20 12:50 13:05 14:50 \n",
"Villefranche 15:49 ... 13:15 09:25 13:10 \n",
"Mont-Louis 16:36 ... 14:02 10:15 14:00 \n",
"\n",
"Jours D SD D \n",
"PS • • • \n",
"PV • • • \n",
"Mode 560 TRAIN 560 \n",
"Perpignan 14:15 18:13 18:20 \n",
"Prades 15:05 18:52 19:10 \n",
"Villefranche 15:10 19:15 \n",
"Mont-Louis 16:00 20:05 \n",
"\n",
"[7 rows x 26 columns]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"561: 0. FORMIGUERES > MONT LOUIS"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" <th>D</th>\n",
" <th>D</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mode</th>\n",
" <td>569</td>\n",
" <td>560</td>\n",
" <td>561</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>569</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>561</td>\n",
" <td>560</td>\n",
" <td>561</td>\n",
" <td>560</td>\n",
" <td>561</td>\n",
" <td>560</td>\n",
" <td>TRAIN</td>\n",
" <td>561</td>\n",
" <td>560</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Formiguères</th>\n",
" <td>08:00</td>\n",
" <td></td>\n",
" <td>12:20</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>13:35</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>15:35</td>\n",
" <td></td>\n",
" <td>17:20</td>\n",
" <td></td>\n",
" <td>11:20</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>16:50</td>\n",
" <td></td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mont-Louis</th>\n",
" <td>08:30</td>\n",
" <td>08:53</td>\n",
" <td>12:50</td>\n",
" <td>12:58</td>\n",
" <td></td>\n",
" <td>14:05</td>\n",
" <td>14:39</td>\n",
" <td></td>\n",
" <td>16:05</td>\n",
" <td>16:18</td>\n",
" <td>17:50</td>\n",
" <td>17:58</td>\n",
" <td>11:50</td>\n",
" <td>11:58</td>\n",
" <td></td>\n",
" <td>17:20</td>\n",
" <td>17:45</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJV LMWJV LMWJV LMWJVS LMWJV LMWJV LMWJVS \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • • \n",
"Mode 569 560 561 560 TRAIN 569 560 TRAIN 561 \n",
"Formiguères 08:00 12:20 13:35 15:35 \n",
"Mont-Louis 08:30 08:53 12:50 12:58 14:05 14:39 16:05 \n",
"\n",
"Jours LMWJVS LMWJVS LMWJV S S S D D \n",
"PS • • • • • • • • \n",
"PV • • • • • • • • \n",
"Mode 560 561 560 561 560 TRAIN 561 560 \n",
"Formiguères 17:20 11:20 16:50 \n",
"Mont-Louis 16:18 17:50 17:58 11:50 11:58 17:20 17:45 "
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"561: 1. MONT LOUIS > FORMIGUERES"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Jours</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJV</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>LMWJVS</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" <th>S</th>\n",
" <th>D</th>\n",
" <th>D</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>PS</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>PV</th>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" <td>•</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mode</th>\n",
" <td>560</td>\n",
" <td>561</td>\n",
" <td>560</td>\n",
" <td>561</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>561</td>\n",
" <td>569</td>\n",
" <td>560</td>\n",
" <td>569</td>\n",
" <td>TRAIN</td>\n",
" <td>560</td>\n",
" <td>561</td>\n",
" <td>560</td>\n",
" <td>561</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Mont-Louis</th>\n",
" <td>08:38</td>\n",
" <td>08:45</td>\n",
" <td>10:21</td>\n",
" <td>10:30</td>\n",
" <td></td>\n",
" <td>13:02</td>\n",
" <td>13:05</td>\n",
" <td>14:10</td>\n",
" <td>18:03</td>\n",
" <td>18:25</td>\n",
" <td></td>\n",
" <td>11:57</td>\n",
" <td>12:05</td>\n",
" <td>10:15</td>\n",
" <td>10:25</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Formiguères</th>\n",
" <td></td>\n",
" <td>09:15</td>\n",
" <td></td>\n",
" <td>11:00</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>13:35</td>\n",
" <td>14:45</td>\n",
" <td></td>\n",
" <td>19:00</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>12:35</td>\n",
" <td></td>\n",
" <td>11:00</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Jours LMWJVS LMWJVS LMWJVS LMWJVS LMWJV LMWJV LMWJV LMWJVS LMWJVS \\\n",
"PS • • • • • • • • • \n",
"PV • • • • • • • • • \n",
"Mode 560 561 560 561 TRAIN 560 561 569 560 \n",
"Mont-Louis 08:38 08:45 10:21 10:30 13:02 13:05 14:10 18:03 \n",
"Formiguères 09:15 11:00 13:35 14:45 \n",
"\n",
"Jours LMWJVS S S S D D \n",
"PS • • • • • • \n",
"PV • • • • • • \n",
"Mode 569 TRAIN 560 561 560 561 \n",
"Mont-Louis 18:25 11:57 12:05 10:15 10:25 \n",
"Formiguères 19:00 12:35 11:00 "
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"DAYS = {\n",
" \"JOURS DE LA SEMAINE\": \"JOURS \",\n",
" \"JOURS SEMAINE\": \"JOURS \",\n",
" \"Lundi à Samedi\": \"LMWJVS\",\n",
" \"L à S\": \"LMWJVS\",\n",
" \"LàS\": \"LMWJVS\",\n",
" \"Lundi à Vendredi\": \"LMWJV\",\n",
" \"L à V\": \"LMWJV\",\n",
" \"LàV\": \"LMWJV\",\n",
" \"LàD\": \"LMWJVSD\",\n",
" \"L à D\": \"LMWJVSD\",\n",
" \"Dimanche\": \"D\",\n",
" \"Samedi\": \"S\",\n",
" \"Me\": \"W\",\n",
" \"SD\": \"SD\",\n",
" \"D\": \"D\",\n",
" \"S\": \"S\",\n",
"}\n",
"\n",
"ROW_HEADERS = {\n",
" \"Jours\": [(\"JOURS\", \"\")],\n",
" \"PS\": [(\"PÉRIODE SCOLAIRE\", \"\"), (\"PERIODE SCOLAIRE\", \"\")],\n",
" \"PV\": [(\"PETITES VACANCES\", \"\")], # or PÉRIODE DE VACANCES, since ÉTÉ is assimilated to PV\n",
" \"Mode\": [(\"Mode de transport\", \"\"), (\"MODE DE TRANSPORT\", \"\")],\n",
" \"Casteil\": [(\"CASTEIL\", \"Parking\")],\n",
" \"Collioure\": [(\"COLLIOURE\", \"Square M Banyuls\"), (\"COLLIOURE\", \"Le Glacis\")],\n",
" \"Formiguères\": [(\"FORMIGUÈRES\", \"Place\")],\n",
" \"Mont-Louis\": [(\"MONT-LOUIS\", \"Porte de France\"), (\"MONT LOUIS\", \"Porte de France\")],\n",
" \"Mosset\": [(\"MOSSET\", \"Place\")],\n",
" \"Perpignan\": [(\"PERPIGNAN\", \"G.R Bd St Assiscle\"), (\"PERPIGNAN\", \"G.R Bd St Assiscle (2)\")],\n",
" \"Prades\": [(\"PRADES\", \"Es. Multimodal Dr Salies\")],\n",
" \"Py\": [(\"PY\", \"La Paraguera\")],\n",
" \"Vernet\": [(\"VERNET-LES-BAINS\", \"La Poste\"), (\"VERNET LES BAINS\", \"La Poste\")],\n",
" \"Villefranche\": [(\"VILLEFRANCHE-DE-CONFLENT\", \"Cité\"), (\"VILLEFRANCHE/CONFLENT\", \"Cité\")],\n",
" \"Arles-sur-Tech\": [(\"ARLES-SUR-TECH\", \"Gare Routiere\")],\n",
" \"Céret\": [(\"CÉRET\", \"RP du Toreador\"), (\"SAINT-JEAN-PLA-DE-CORTS\\nCÉRET\", \"RP du Toreador\")]\n",
"}\n",
"\n",
"configurations = json.loads(Path(\"config.json\").read_text())\n",
"segments = {}\n",
"for line in LINES_OF_INTEREST:\n",
" if not line.isdigit():\n",
" continue\n",
" if line == \"564\":\n",
" continue\n",
" dataframes = pickle.load(bz2.BZ2File(PBZ2_DIR / f\"{line}.pbz2\", \"rb\"))\n",
" texts = (TEXTS_DIR / f\"{line}.txt\").read_text().split(\"<PAGE BREAK>\")\n",
" for (i, config) in enumerate(configurations[line][\"pages\"]):\n",
" print(f'\\n\\n{line}: {i}. {config[\"expected_substring\"]}', end=\"\")\n",
" \n",
" # Make a sanity check on the title of the page\n",
" if config[\"expected_substring\"] not in texts[i]:\n",
" print(f'Please replace configurations[{line}][\"pages\"][{i}][\"expected_substring\"] with an actual substring of the text.')\n",
" raise StopExecution\n",
"\n",
" df = dataframes[i].copy()\n",
" df.replace(r\" +\", \" \", regex=True, inplace=True) # suppress double spaces in all cells\n",
" \n",
" # split the top-left cell and broadcast the list onto the first row\n",
" try:\n",
" df.iloc[0] = config[\"col_headers\"]\n",
" except ValueError:\n",
" expected = len(config[\"col_headers\"])\n",
" actual = len(df.iloc[0])\n",
" print(f\"\\n{FAIL}{expected} columns are specified in configurations, but there are {actual} in the PDF!{RESET}\")\n",
" suggestions = []\n",
" for actual in df.iloc[0,0].split(\"\\n\"):\n",
" if actual in DAYS:\n",
" suggestions.append(DAYS[actual])\n",
" else:\n",
" print(f\"Please add an entry in DAYS for '{actual}'.\")\n",
" raise StopExecution\n",
" raise StopExecution\n",
" \n",
" # Broadcast the cities on empty cells when they have several stops.\n",
" df[0][2:] = df[0][2:].replace(\"\", np.NaN)\n",
" df.ffill(inplace=True)\n",
" \n",
" # Filter the dataframe by keeping only the specified rows.\n",
" df = df.set_index([0, 1]) # make the row indexes to be couples of (city, city_stop)\n",
" df = df.sort_index(level=df.index.names) # prevent a PerformanceWarning \n",
" rows_to_keep = []\n",
" for row_header in config[\"row_headers\"]:\n",
" for actual_row_header in ROW_HEADERS[row_header]:\n",
" if actual_row_header in df.index:\n",
" rows_to_keep.append(actual_row_header)\n",
" break\n",
" else:\n",
" print(f\"\\n{FAIL}No row of the table can be normalized as '{row_header}'!{RESET}\")\n",
" print(f\"Please bind '{row_header}' with one couple among:\")\n",
" pprint(list(df.index))\n",
" raise StopExecution\n",
" df = df.loc[rows_to_keep, :]\n",
" df.index = config[\"row_headers\"]\n",
" \n",
" # Camelot loses the check marks from the PDF. We check them by default. Specify unchecked ones in config.json.\n",
" periods = configurations[line][\"periods\"]\n",
" df.loc[periods] = df.loc[periods].replace(\"\", \"•\") # default value: checked\n",
" for (period, columns) in config.get(\"periods_to_uncheck\", {}).items():\n",
" for column in columns:\n",
" df.at[period, column + 1] = \"\"\n",
" \n",
" # Copy the first line in column headers and display it without this first line.\n",
" df.columns = df.iloc[0]\n",
" display(df[1:])\n",
" \n",
" # Store the transposed result\n",
" key = f\"{line} / {config['expected_substring']}\"\n",
" segments[key] = df.transpose().to_dict(\"split\")\n",
" del segments[key][\"index\"]\n",
"Path(SEGMENTS_DIR / \"cars.json\").write_text(json.dumps(segments, indent=2, ensure_ascii=False));"
]
},
{
"cell_type": "markdown",
"id": "309f027a",
"metadata": {},
"source": [
"## Extraire les segments de la ligne 564 dans `segments/car_564.json`"
]
},
{
"cell_type": "code",
"execution_count": 95,
"id": "5ecb7371",
"metadata": {},
"outputs": [],
"source": [
"text = Path(\"texts/564.txt\").read_text()\n",
"result = {}\n",
"rex = r\"(?m)fonctionnement (.+)\\n(?:.+\\n)+?(Bouillouses|Mont-Louis) (.+)\\n(Bouillouses|Mont-Louis) (.+)\"\n",
"for (days, dep_city, dep_times, arr_city, arr_times) in re.findall(rex, text):\n",
" days = days.split()\n",
" dep_times = dep_times.split()\n",
" arr_times = arr_times.split()\n",
" array = [row for row in zip(days, dep_times, arr_times) if \"-\" not in row]\n",
" note = \"pour info\"\n",
" result[f\"564 / {dep_city} > {arr_city}\"] = {\n",
" \"columns\": [\"Jours\", \"Note\", \"Mode\", \"PS\", \"PV\", dep_city, arr_city],\n",
" \"data\": [[day, \"Du 1/7 au 3/9 et week-ends<br>du 10/6 au 17/9/23.\", \"564\", \"\", \"•\", dep_time, arr_time] for (day, dep_time, arr_time) in array]\n",
" }\n",
"result = json.dumps(result, indent=2, ensure_ascii=False)\n",
"Path(\"segments/car_564.json\").write_text(result);"
]
},
{
"cell_type": "markdown",
"id": "ec6b23b2",
"metadata": {},
"source": [
"## Collecter les segments dans `segments/trains+cars.tsv`\n",
"Un segment est un tronçon d'un trajet qui ne comporte aucun arrêt intermédiaire sélectionné. Par exemple, le trajet Perpignan > Vernet est constitué des segments Perpignan > Prades, Prades > Villefranche, Villefranche > Vernet."
]
},
{
"cell_type": "code",
"execution_count": 96,
"id": "87abb1bd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"PERPIGNAN - VILLEFRANCHE / PERPIGNAN VILLEFRANCHE-VERNET LES BAINS\n",
"PERPIGNAN - VILLEFRANCHE / VILLEFRANCHE-VERNET LES BAINS PERPIGNAN\n",
"PORT BOU - NARBONNE / PERPIGNAN PORT-BOU\n",
"PORT BOU - NARBONNE / PORT-BOU PERPIGNAN\n",
"train_jaune / VILLEFRANCHE-VERNET-LES-BAINS LATOUR-DE-CAROL\n",
"train_jaune / LATOUR-DE-CAROL-ENVEITG VILLEFRANCHE-VERNET-LES-BAINS\n",
"520 / PRADES > PERPIGNAN\n",
"520 / PERPIGNAN > PRADES\n",
"521 / PERPIGNAN > VERNET\n",
"521 / VERNET > PERPIGNAN\n",
"523 / MOSSET > PRADES\n",
"523 / PRADES > MOSSET\n",
"525 / PY > PRADES\n",
"525 / PRADES > PY\n",
"530 / ARLES SUR TECH > PERPIGNAN\n",
"530 / PERPIGNAN > ARLES SUR TECH\n",
"540 / BANYULS SUR MER > PERPIGNAN\n",
"540 / PERPIGNAN > BANYULS SUR MER\n",
"560 / PORTE PUYMORENS > PERPIGNAN\n",
"560 / PERPIGNAN > PORTE PUYMORENS\n",
"561 / FORMIGUERES > MONT LOUIS\n",
"561 / MONT LOUIS > FORMIGUERES\n",
"564 / Bouillouses > Mont-Louis\n",
"564 / Mont-Louis > Bouillouses\n",
"\u001b[92m'trains+cars.tsv' updated.\u001b[0m\n"
]
}
],
"source": [
"segments = {}\n",
"for mode in (\"trains\", \"cars\", \"car_564\"):\n",
" segments.update(json.loads((SEGMENTS_DIR / f\"{mode}.json\").read_text()))\n",
"rows = set()\n",
"for (key, extract) in segments.items():\n",
" print(key)\n",
" (line, name) = key.split(\" / \")\n",
" columns = extract[\"columns\"]\n",
" data = extract[\"data\"]\n",
" for datum in data:\n",
" (dep_city, dep_time) = (None, None)\n",
" (arr_city, arr_time) = (None, None)\n",
" mode = f\"car {line}\" # default value for schedules without mode\n",
" note = \"\"\n",
" periods = []\n",
" for (column, value) in zip(columns, datum):\n",
" if column == \"Jours\":\n",
" days = value\n",
" elif column == \"Note\":\n",
" note = value\n",
" elif column in (\"PS\", \"PV\"):\n",
" periods += [column] if value else []\n",
" elif column == \"Mode\":\n",
" if value in (np.NaN, \"\", \"car\", \"CAR\"):\n",
" mode = f\"car {line}\"\n",
" elif value.isdigit():\n",
" mode = f\"car {value}\"\n",
" elif value == \"TRAIN\":\n",
" mode = \"train\"\n",
" else:\n",
" mode = value\n",
" elif re.match(r\"\\d{1,2}:\\d\\d\", value):\n",
" (arr_city, arr_time) = (column, (\"0\" + value)[-5:])\n",
" if dep_city:\n",
" if not periods:\n",
" periods = [\"PS\", \"PV\"]\n",
" rows.add((dep_city, dep_time, arr_city, arr_time, mode, tuple(periods), days, note))\n",
" (dep_city, dep_time) = (arr_city, arr_time)\n",
"result = [\n",
" \"\\t\".join([dep_city, dep_time, arr_city, arr_time, mode, period, day, note])\n",
" for (dep_city, dep_time, arr_city, arr_time, mode, periods, days, note) in sorted(rows)\n",
" for period in periods\n",
" for day in days\n",
" if mode != \"train\" # suppress trains which appear on bus schedules without circulation numbers\n",
"]\n",
"path = SEGMENTS_DIR / \"trains+cars.tsv\"\n",
"text = \"\\n\".join(result)\n",
"print(f\"{OK}'{path.name}' \", end=\"\")\n",
"if path.read_text() == text:\n",
" print(f\"already up to date.{RESET}\")\n",
"else:\n",
" path.write_text(text)\n",
" print(f\"updated.{RESET}\")"
]
},
{
"cell_type": "markdown",
"id": "cdda5e84",
"metadata": {},
"source": [
"## Neo4j : énumérer tous les trajets à partir des segments\n",
"Pré-requis : Neo4j Desktop est lancé et la base `transports` est active.\n",
"\n",
"**NB.** La phase de création du graphe est relativement lente la première fois (de l'ordre d'une minute). Elle est normalement plus rapide les fois suivantes, mais il est arrivé qu'elle devienne de plus en plus lente. Si c'est le cas, arrêter la base, quitter et relancer Neo4j. Le calcul de trajet prend de l'ordre de deux minutes."
]
},
{
"cell_type": "code",
"execution_count": 97,
"id": "4d831a78",
"metadata": {
"code_folding": [],
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Deleting nodes and arcs...\n",
"Constructing creation query from file trains+cars.tsv...\n",
"Creating nodes and arcs...\n",
"Database created in 75.76 seconds.\n",
"Calculating direction 1 trips...\n",
" PS LMWJVSD\n",
" PV LMWJVSD\n",
"Calculating direction 2 trips...\n",
" PS LMWJVSD\n",
" PV LMWJVSD\n",
"Calculating direction 3 trips...\n",
" PS LMWJVSD\n",
" PV LMWJVSD\n",
"Calculating direction 4 trips...\n",
" PS LMWJVSD\n",
" PV LMWJVSD\n",
"Found 13424 trips.\n",
"Database queried in 360.76 seconds.\n"
]
}
],
"source": [
"class DB:\n",
" def __init__(self):\n",
" self.driver = GraphDatabase.driver(\"bolt://localhost:7687\", auth=(\"neo4j\", \"kemeuv123\"))\n",
" self.driver.verify_connectivity()\n",
" self.session = self.driver.session()\n",
"\n",
" def create(self):\n",
" print(\"Deleting nodes and arcs...\")\n",
" self.session.run(\"MATCH (n) DETACH DELETE n\")\n",
" print(\"Constructing creation query from file trains+cars.tsv...\")\n",
" result = [f\"CREATE ({city[:3]}:City {{name: '{city}'}})\" for city in CITIES]\n",
" path = SEGMENTS_DIR / \"trains+cars.tsv\"\n",
" rows = [(row.split(\"\\t\") + [\"\"] * 8)[:8] for row in path.read_text().split(\"\\n\") if row]\n",
" for (direction, arcs) in enumerate(SUBGRAPHS, 1):\n",
" for (dep_city, dep_time, arr_city, arr_time, mode, period, day, note) in rows:\n",
" if (dep_city, arr_city) in arcs:\n",
" dep_time = hhmm_to_minutes(dep_time)\n",
" arr_time = hhmm_to_minutes(arr_time)\n",
" payload = {\n",
" \"<dep_time>\": dep_time,\n",
" \"<arr_time>\": arr_time,\n",
" \"<note>\": note,\n",
" \"<mode>\": mode,\n",
" \"<duration>\": arr_time - dep_time,\n",
" }\n",
" payload = json.dumps(payload, ensure_ascii=False).replace('\"<', \"\").replace('>\"', \"\")\n",
" result.append(f\"CREATE ({dep_city[:3]})-[:{period}{day}{direction} {payload}]->({arr_city[:3]})\")\n",
" result.append(\"\")\n",
" creation_query = \"\\n\".join(result)\n",
" (QUERIES_DIR / \"create.cypher\").write_text(creation_query)\n",
" print(\"Creating nodes and arcs...\")\n",
" self.session.run(creation_query)\n",
"\n",
" def trips(self):\n",
" query = (QUERIES_DIR / \"process.cypher\").read_text()\n",
" result = []\n",
" for direction in range(len(SUBGRAPHS)):\n",
" direction = str(direction + 1)\n",
" print(f\"Calculating direction {direction} trips...\")\n",
" for period in [\"PS\", \"PV\"]:\n",
" print(f\" {period} \", end =\"\")\n",
" for day in \"LMWJVSD\":\n",
" print(day, end=\"\")\n",
" q = query.replace(\"$DIR\", direction).replace(\"$DAY\", day).replace(\"$PER\", period)\n",
" result.extend(self.session.run(q))\n",
" print()\n",
" print(f\"Found {len(result)} trips.\")\n",
" return result\n",
"\n",
" def close(self):\n",
" self.session.close()\n",
" self.driver.close()\n",
"\n",
"log_path = Path(\"log/neo4j_output.tsv\")\n",
" \n",
"db = DB()\n",
"\n",
"if log_path.is_file() and (SEGMENTS_DIR / \"trains+cars.tsv\").stat().st_mtime < log_path.stat().st_mtime:\n",
" print(\"Database already created.\")\n",
"else:\n",
" starting_time = time.time()\n",
" db.create()\n",
" print(f\"Database created in {time.time() - starting_time:.2f} seconds.\")\n",
"\n",
"starting_time = time.time()\n",
"neo4j_trips = db.trips()\n",
"print(f\"Database queried in {time.time() - starting_time:.2f} seconds.\")\n",
"db.close()\n",
"\n",
"# Log the result for debugging\n",
"result = []\n",
"for trip in neo4j_trips:\n",
" row = []\n",
" for field in (\"cities\", \"dep_times\", \"arr_times\", \"day\", \"period\", \"modes\", \"notes\"):\n",
" value = trip.get(field)\n",
" if field == \"day\":\n",
" value = \"LMWJVSD\".index(value)\n",
" row.append(f\"{value}\")\n",
" result.append(\"\\t\".join(row))\n",
"log_path.write_text(\"\\n\".join(sorted(result)));"
]
},
{
"cell_type": "markdown",
"id": "056e6b89",
"metadata": {},
"source": [
"## Regrouper et dédupliquer les trajets\n",
"Un même trajet peut avoir lieu différents jours de la semaine et à différentes périodes. Cela correspond à autant de trajets distincts dans la base Neo4j. Le code ci-dessous crée deux dictionnaires permettant, pour un trajet donné, de retrouver tous les jours et toutes les périodes où il a lieu.\n",
"\n",
"Du fait du lancement de la requête de création des trajets avec deux sources/cibles, certains ont été trouvés en double. Par exemple, Le trajet Perpignan > Villefranche est trouvé aussi bien avec la source Perpignan qu'avec la cible Vernet. Par contre, Perpignan > Mont-Louis n'est trouvé qu'avec la source Perpignan. Les valeurs des dictionnaires ci-dessous étant des ensembles, le regroupement s'accompagne d'une déduplication des trajets."
]
},
{
"cell_type": "code",
"execution_count": 98,
"id": "842dcc44",
"metadata": {
"code_folding": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1317 collated and deduplicated trips.\n"
]
}
],
"source": [
"periods_by_trip = defaultdict(set)\n",
"days_by_trip = defaultdict(set)\n",
"for trip in neo4j_trips:\n",
" cities = tuple(trip.get(\"cities\"))\n",
" dep_times = tuple(trip.get(\"dep_times\"))\n",
" arr_times = tuple(trip.get(\"arr_times\"))\n",
" notes = tuple(trip.get(\"notes\"))\n",
" modes = tuple(trip.get(\"modes\"))\n",
" key = (cities, dep_times, arr_times, notes, modes)\n",
" periods_by_trip[key].add(trip.get(\"period\"))\n",
" days_by_trip[key].add(trip.get(\"day\"))\n",
"print(f\"{len(days_by_trip)} collated and deduplicated trips.\")"
]
},
{
"cell_type": "markdown",
"id": "02746189",
"metadata": {},
"source": [
"## Convertir chaque trajet en une ligne tabulée"
]
},
{
"cell_type": "markdown",
"id": "77876386",
"metadata": {},
"source": [
"Pour chacun des trajets (clés) de ce dictionnaire, on crée une instance de `Trip` avec les périodes et les jours correpondants. L'ensemble des trajets est accumulé dans une nouvelle liste `trips`."
]
},
{
"cell_type": "code",
"execution_count": 99,
"id": "d15e063f",
"metadata": {
"code_folding": [
37
]
},
"outputs": [],
"source": [
"class Trip:\n",
" \n",
" def __init__(self, cities, dep_times, arr_times, notes, modes, periods, days):\n",
" \"\"\"For each subtrip A > B > C without transfert, drop the intermediate city.\"\"\"\n",
" self.cities = [cities[0]]\n",
" self.dep_times = [dep_times[0]]\n",
" self.arr_times = []\n",
" self.modes = [modes[0]]\n",
" self.notes = [notes[0]]\n",
" for i in range(1, len(modes)):\n",
" if arr_times[i - 1] != dep_times[i] or modes[i - 1] != modes[i]:\n",
" self.cities.append(cities[i])\n",
" self.dep_times.append(dep_times[i])\n",
" self.arr_times.append(arr_times[i - 1])\n",
" self.modes.append(modes[i])\n",
" self.notes.append(notes[i])\n",
" self.cities.append(cities[-1])\n",
" self.arr_times.append(arr_times[-1])\n",
" self.periods = periods_to_human(frozenset(periods))\n",
" self.days = frozenset(days)\n",
" self.days_as_int = days_to_int(self.days)\n",
" self.arr_times = list(map(minutes_to_hhmm, self.arr_times))\n",
" self.dep_times = list(map(minutes_to_hhmm, self.dep_times))\n",
"\n",
" def get_arrow(self):\n",
" \"\"\"Return an expandable arrow for any trip having at least one transfert.\"\"\"\n",
" if len(self.modes) == 1: # no transfert\n",
" return \">\"\n",
" details = []\n",
" summary = \"<summary>></summary>\"\n",
" for i in range(1, len(self.modes)):\n",
" (arr_time, dep_time) = (self.arr_times[i-1], self.dep_times[i])\n",
" duration = calculate_int_duration(arr_time, dep_time)\n",
" if duration < 5 or (\n",
" duration < 15 and\n",
" self.cities[i] == \"Villefranche\" and\n",
" self.modes[i-1][1] + self.modes[i][1] in (\"ar\", \"ra\") # transfert between train and bus\n",
" # Example: \"car 560\"[1] + \"Train Jaune\"[1] == \"ar\"\n",
" ):\n",
" summary = '<summary class=\"short\">></summary>'\n",
" arr_time = f'<span class=\"short\">{arr_time}</span>'\n",
" dep_time = f'<span class=\"short\">{dep_time}</span>'\n",
" details.append(f\"{arr_time} {self.cities[i]} {mode_to_html(self.modes[i])} {dep_time}\")\n",
" return f\"<details>{summary} {' > '.join(details)} ></details>\"\n",
"\n",
" day_map = {\n",
" \"L\": \"lundi\",\n",
" \"M\": \"mardi\",\n",
" \"mar.\": \"mardi\",\n",
" \"W\": \"mercredi\",\n",
" \"mer.\": \"mercredi\",\n",
" \"J\": \"jeudi\",\n",
" \"V\": \"vendredi\",\n",
" \"S\": \"samedi\",\n",
" \"D\": \"dimanche\",\n",
" }\n",
" \n",
" def get_keywords(self):\n",
" keywords = set()\n",
" keywords.add(\"matin\" if self.dep_times[0] <= \"12:00\" else \"après-midi\")\n",
" if (\n",
" self.cities[0] == \"Vernet\" and self.dep_times[0] < \"10:00\"\n",
" or\n",
" self.cities[-1] == \"Vernet\" and self.dep_times[0] > \"15:00\"\n",
" ):\n",
" keywords.add(\"journée\")\n",
" if set(\"LMWJV\").intersection(self.days):\n",
" keywords.add(\"semaine\")\n",
" if set(\"SD\").intersection(self.days):\n",
" keywords.add(\"week-end\")\n",
" keywords.update((self.day_map[day] for day in self.days))\n",
" keywords.add(f\"#{self.cities[0]}\")\n",
" keywords.update(self.cities[1:-1])\n",
" keywords.add(f\"#{self.cities[-1]}\")\n",
" keywords.update(k.lower() for k in \" \".join(self.modes).split())\n",
" if not self.periods:\n",
" keywords.add(\"vacances\")\n",
" if (\n",
" \"jaune\" not in keywords\n",
" and\n",
" (self.cities[0] != \"Collioure\" or self.modes[0].startswith(\"car\"))\n",
" and\n",
" (self.cities[-1] != \"Collioure\" or self.modes[-1].startswith(\"car\"))\n",
" ):\n",
" keywords.add(\"1€\")\n",
" return \" \".join(sorted(keywords))\n",
"\n",
" def calculate_note(self):\n",
" # Warning : side effect on dep_times. Cannot be a pure getter.\n",
" self.dep_times[:] = [f\"{time}{note and '*'}\" for (time, note) in zip(self.dep_times, self.notes)]\n",
" notes = [note for (i, note) in enumerate(self.notes) if note and note not in self.notes[:i]]\n",
" self.note = re.sub(r\"\\s+\", \" \", \"<br>\".join(notes)).strip()\n",
" self.note = f\"{self.note}\" if self.note else \"\"\n",
" \n",
" def get_duration(self):\n",
" d = calculate_int_duration(self.dep_times[0], self.arr_times[-1])\n",
" return f\"{d // 60}h{d % 60:02d}\"\n",
"\n",
" def get_colored_row(self):\n",
" \"\"\"Return a couple consisting in a sorting key and a TSV representation of the trip.\"\"\"\n",
" self.calculate_note()\n",
" sorting_key = (\n",
" self.cities[0],\n",
" self.cities[-1],\n",
" self.dep_times[0],\n",
" self.days_as_int,\n",
" self.arr_times[-1],\n",
" )\n",
" label = (\n",
" f\"{self.cities[0]} {mode_to_html(self.modes[0])} {self.dep_times[0]} \" +\n",
" f\"{self.get_arrow()} \" +\n",
" f\"{self.arr_times[-1]} {self.cities[-1]} — \" +\n",
" f\"{''.join(days_to_human(self.days))}{self.periods}\" \n",
" )\n",
" keywords = self.get_keywords()\n",
" modes = \" / \".join(self.modes)\n",
" duration = self.get_duration()\n",
" note = self.note\n",
" row = \"\\t\".join([label, keywords, duration, note, modes])\n",
" return (sorting_key, row)\n",
"\n",
"\n",
"@lru_cache(maxsize=None)\n",
"def days_to_human(days):\n",
" result = []\n",
" for day in \"LMWJVSD\":\n",
" if day in days:\n",
" result.append(day)\n",
" if result == [\"W\"]:\n",
" return [\"mer.\"]\n",
" if result == [\"M\"]:\n",
" return [\"mar.\"]\n",
" return [\"M\" if day == \"W\" else day for day in result]\n",
"\n",
"@lru_cache(maxsize=None)\n",
"def periods_to_human(periods):\n",
" if periods == {\"PS\"}:\n",
" return \" (pér. scol.)\"\n",
" else:\n",
" # Handle other periods if needed\n",
" return \"\"\n",
"\n",
"@lru_cache(maxsize=None)\n",
"def mode_to_html(mode):\n",
" if mode.startswith(\"car\"):\n",
" return f'<span class=\"bus\">({mode})</span>'\n",
" elif mode == \"Train Jaune\":\n",
" return f'<span class=\"train_jaune\">({mode})</span>'\n",
" else:\n",
" return f'<span class=\"train\">({mode})</span>'\n",
"\n",
"@lru_cache(maxsize= None)\n",
"def days_to_int(days):\n",
" acc = 0\n",
" power_of_two = 1\n",
" for day in \"LMWJVSD\":\n",
" if day in days:\n",
" acc += power_of_two\n",
" power_of_two *= 2\n",
" return acc\n",
" \n",
"# Create all Trip instances and accumulate them\n",
"trips = [Trip(*map(list, k[:6]), periods_by_trip[k], days) for (k, days) in days_by_trip.items()]"
]
},
{
"cell_type": "markdown",
"id": "ed73bb99",
"metadata": {},
"source": [
"## Enregistrer les trajets résultants en TSV et les téléverser"
]
},
{
"cell_type": "code",
"execution_count": 100,
"id": "a0da2610",
"metadata": {
"code_folding": [
0
]
},
"outputs": [],
"source": [
"rows = [\"\\t\".join([\"label\", \"haystack\", \"duration\", \"note\", \"mode\"])]\n",
"sorted_trips = sorted(trip.get_colored_row() for trip in trips)\n",
"rows.extend(row for (_, row) in sorted_trips)"
]
},
{
"cell_type": "code",
"execution_count": 101,
"id": "bd0870ff",
"metadata": {},
"outputs": [],
"source": [
"# Log the resulting rows without HTML stuff (optional)\n",
"acc = []\n",
"for row in rows:\n",
" row = re.sub(r\"<span .+?</span> \", \"\", row)\n",
" row = re.sub(r\"<details><summary.*?>\", \"\", row)\n",
" row = re.sub(r\"</summary>\", \"\", row)\n",
" row = re.sub(r\"</details>\", \"\", row)\n",
" row = re.sub(r\"\\t(#.+?|haystack)\\t\", \"\\t\", row)\n",
" acc.append(row)\n",
"Path(\"log/transports.tsv\").write_text(\"\\n\".join(acc));"
]
},
{
"cell_type": "code",
"execution_count": 102,
"id": "5d740b0c",
"metadata": {
"code_folding": []
},
"outputs": [],
"source": [
"# Write the resulting rows and automagically upload them on the website\n",
"Path(\"../www/transports_db.tsv\").write_text(\"\\n\".join(rows));"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c8d274d8",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.9.13"
},
"toc": {
"base_numbering": 1,
"nav_menu": {
"height": "265px",
"width": "303px"
},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Base des transports",
"title_sidebar": "Base des transports",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "173.182px"
},
"toc_section_display": true,
"toc_window_display": true
}
},
"nbformat": 4,
"nbformat_minor": 5
}
MIT License
Copyright (c) 2023 Aristide Grange
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MATCH path = () -[arcs:$PER$DAY$DIR*]-> ()
WHERE
// Check that, on this path, no arc arrives after the next one departs
ALL(i in range(0, size(arcs)-2) WHERE arcs[i].arr_time <= arcs[i+1].dep_time)
WITH
[city in nodes(path) | city.name] AS cities,
relationships(path) AS arcs,
[i in range(0, size(arcs)-2) | arcs[i+1].dep_time - arcs[i].arr_time] as transfer_times
WITH
cities,
[arc in arcs | arc.dep_time] AS dep_times,
[arc in arcs | arc.arr_time] AS arr_times,
[arc in arcs | arc.note] AS notes,
[arc in arcs | arc.mode] AS modes,
toInteger(apoc.coll.sum([arc in arcs | arc.duration])) AS seat_duration,
size([ // Count the number of transfers
i in range(0, size(arcs)-2)
WHERE arcs[i].mode <> arcs[i+1].mode OR transfer_times[i] > 0
]) AS nb_transfers,
size([
i in range(0, size(arcs)-2)
WHERE transfer_times[i] < 5 OR
( // At Villefranche, the train and the bus are separated by a 8-minute walk. Take it into account.
cities[i+1] = 'Villefranche' AND
// "ar" = car + train, "ra" = train + car
substring(arcs[i].mode, 1, 1) + substring(arcs[i+1].mode, 1, 1) in ["ar", "ra"] AND
transfer_times[i] < 15
)
]) AS transfer_penalty,
size([
i in range(0, size(arcs)-2)
WHERE ( // Count the number of bus/train transfers (in fact, 0 or 1) at Villefranche
cities[i+1] = 'Villefranche' AND
substring(arcs[i].mode, 1, 1) + substring(arcs[i+1].mode, 1, 1) in ["ar", "ra"]
)
]) AS mode_change_at_villefranche
WITH
// Group by origin, destination and departure time
cities[0] AS origin,
cities[-1] AS destination,
dep_times[0] AS dep_time,
apoc.agg.minItems(
{ // Collect the data to be transmitted to the next step
dep_times: dep_times,
arr_times: arr_times,
notes: notes,
modes: modes,
cities: cities
},
apoc.text.format("%04d %04d %04d %04d %04d %06d", [ // Concatenate the values to make them comparable (is there a better way?)
arr_times[-1], // Minimize the arrival time and break ties by...
nb_transfers, // minimizing the number of transfers,
transfer_penalty, // minimizing the transfer penalty,
mode_change_at_villefranche, // preferring no mode change at Villefranche,
seat_duration, // minimizing the seat duration (which gives more time for transfers),
toInteger(apoc.coll.sum(dep_times)) // minimizing the sum of departure times (no other reason than stability),
// for example:
// Vernet 15:31 > Villefranche 15:42 / 15:49 > Mont-Louis 16:36 / 18:25 > Formiguères 19:00
// should be preferred to:
// Vernet 15:31 > Villefranche 15:42 / 16:53 > Mont-Louis 17:40 / 18:25 > Formiguères 19:00
// because (15:31 + 15:49 + 18:25) < (15:31 + 16:53 + 18:25).
])
).items[0] AS best_data
WITH
// Group by origin, destination and arrival time
origin,
destination,
best_data.arr_times[-1] AS arr_time,
// If there remain several trips with the same arrival time,
// keep the one with the latest departure time.
apoc.agg.maxItems(best_data, best_data.dep_times[0]).items[0] AS best_data
RETURN
best_data.cities AS cities,
best_data.dep_times AS dep_times,
best_data.arr_times AS arr_times,
best_data.notes AS notes,
best_data.modes AS modes,
"$PER" AS period,
"$DAY" AS day
UNION
// Keep the direct trips
MATCH path = () -[arcs:$PER$DAY$DIR*]-> ()
WHERE
// Check that, on this path, there is no transfer
ALL(
i in range(0, size(arcs)-2)
WHERE arcs[i].arr_time = arcs[i+1].dep_time
AND arcs[i].mode = arcs[i+1].mode
)
WITH
[city in nodes(path) | city.name] AS cities,
relationships(path) AS arcs
WITH
cities,
[arc in arcs | arc.dep_time] AS dep_times,
[arc in arcs | arc.arr_time] AS arr_times,
[arc in arcs | arc.note] AS notes,
[arc in arcs | arc.mode] AS modes
WITH
// Group by origin, destination and departure time
// This is necessary because some identical trips are referenced with small errors
// in different schedule sheets, e.g.:
// Perpignan (car 520) 08:05 > Prades 09:20 — LMMJV 1h15 car 520
// Perpignan (car 520) 08:05 > Prades 09:21 — LMMJV 1h16 car 520
cities[0] AS origin,
cities[-1] AS destination,
dep_times[0] AS dep_time,
modes[0] AS mode,
apoc.agg.minItems(
{ // Collect the data to be transmitted to the next step
dep_times: dep_times,
arr_times: arr_times,
notes: notes,
modes: modes,
cities: cities
},
// Minimize the arrival time
arr_times[-1]
).items[0] AS best_data
RETURN
best_data.cities AS cities,
best_data.dep_times AS dep_times,
best_data.arr_times AS arr_times,
best_data.notes AS notes,
best_data.modes AS modes,
"$PER" AS period,
"$DAY" AS day
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment