{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Journey Times Analysis\n",
    "\n",
    "Analyzing travel times to Bank station by different transport modes versus distance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:51.298414Z",
     "iopub.status.busy": "2026-01-28T19:53:51.298328Z",
     "iopub.status.idle": "2026-01-28T19:53:51.682527Z",
     "shell.execute_reply": "2026-01-28T19:53:51.682199Z"
    }
   },
   "outputs": [],
   "source": [
    "import polars as pl\n",
    "import matplotlib.pyplot as plt\n",
    "from math import radians, sin, cos, sqrt, atan2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:51.683859Z",
     "iopub.status.busy": "2026-01-28T19:53:51.683731Z",
     "iopub.status.idle": "2026-01-28T19:53:51.797877Z",
     "shell.execute_reply": "2026-01-28T19:53:51.797505Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total postcodes: 840000\n",
      "Columns: ['postcode', 'public_transport_easy_minutes', 'public_transport_quick_minutes', 'cycling_minutes', 'error', 'lat', 'long']\n",
      "With lat/long: 840000\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div><style>\n",
       ".dataframe > thead > tr,\n",
       ".dataframe > tbody > tr {\n",
       "  text-align: right;\n",
       "  white-space: pre-wrap;\n",
       "}\n",
       "</style>\n",
       "<small>shape: (5, 7)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>postcode</th><th>public_transport_easy_minutes</th><th>public_transport_quick_minutes</th><th>cycling_minutes</th><th>error</th><th>lat</th><th>long</th></tr><tr><td>str</td><td>i64</td><td>i64</td><td>i64</td><td>null</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>&quot;W1J 8HX&quot;</td><td>31</td><td>22</td><td>11</td><td>null</td><td>51.506738</td><td>-0.143261</td></tr><tr><td>&quot;W1J 8HS&quot;</td><td>32</td><td>23</td><td>11</td><td>null</td><td>51.507445</td><td>-0.141877</td></tr><tr><td>&quot;W1J 8HR&quot;</td><td>32</td><td>23</td><td>11</td><td>null</td><td>51.507578</td><td>-0.141757</td></tr><tr><td>&quot;W1J 8HZ&quot;</td><td>31</td><td>22</td><td>11</td><td>null</td><td>51.506578</td><td>-0.14393</td></tr><tr><td>&quot;W1J 8HP&quot;</td><td>32</td><td>23</td><td>11</td><td>null</td><td>51.507541</td><td>-0.141686</td></tr></tbody></table></div>"
      ],
      "text/plain": [
       "shape: (5, 7)\n",
       "┌──────────┬──────────────────┬──────────────────┬─────────────────┬───────┬───────────┬───────────┐\n",
       "│ postcode ┆ public_transport ┆ public_transport ┆ cycling_minutes ┆ error ┆ lat       ┆ long      │\n",
       "│ ---      ┆ _easy_minutes    ┆ _quick_minutes   ┆ ---             ┆ ---   ┆ ---       ┆ ---       │\n",
       "│ str      ┆ ---              ┆ ---              ┆ i64             ┆ null  ┆ f64       ┆ f64       │\n",
       "│          ┆ i64              ┆ i64              ┆                 ┆       ┆           ┆           │\n",
       "╞══════════╪══════════════════╪══════════════════╪═════════════════╪═══════╪═══════════╪═══════════╡\n",
       "│ W1J 8HX  ┆ 31               ┆ 22               ┆ 11              ┆ null  ┆ 51.506738 ┆ -0.143261 │\n",
       "│ W1J 8HS  ┆ 32               ┆ 23               ┆ 11              ┆ null  ┆ 51.507445 ┆ -0.141877 │\n",
       "│ W1J 8HR  ┆ 32               ┆ 23               ┆ 11              ┆ null  ┆ 51.507578 ┆ -0.141757 │\n",
       "│ W1J 8HZ  ┆ 31               ┆ 22               ┆ 11              ┆ null  ┆ 51.506578 ┆ -0.14393  │\n",
       "│ W1J 8HP  ┆ 32               ┆ 23               ┆ 11              ┆ null  ┆ 51.507541 ┆ -0.141686 │\n",
       "└──────────┴──────────────────┴──────────────────┴─────────────────┴───────┴───────────┴───────────┘"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Load the journey times data and join with arcgis for lat/long\n",
    "jt = pl.read_parquet(\"../data_sources/processed/journey_times_bank_checkpoint.parquet\")\n",
    "arcgis = pl.read_parquet(\n",
    "    \"../data_sources/arcgis_data.parquet\", columns=[\"pcds\", \"lat\", \"long\"]\n",
    ")\n",
    "\n",
    "df = jt.join(arcgis, left_on=\"postcode\", right_on=\"pcds\", how=\"left\")\n",
    "print(f\"Total postcodes: {df.height}\")\n",
    "print(f\"Columns: {df.columns}\")\n",
    "print(f\"With lat/long: {df.filter(pl.col('lat').is_not_null()).height}\")\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:51.814525Z",
     "iopub.status.busy": "2026-01-28T19:53:51.814373Z",
     "iopub.status.idle": "2026-01-28T19:53:51.943769Z",
     "shell.execute_reply": "2026-01-28T19:53:51.943463Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div><style>\n",
       ".dataframe > thead > tr,\n",
       ".dataframe > tbody > tr {\n",
       "  text-align: right;\n",
       "  white-space: pre-wrap;\n",
       "}\n",
       "</style>\n",
       "<small>shape: (5, 4)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>postcode</th><th>lat</th><th>long</th><th>distance_km</th></tr><tr><td>str</td><td>f64</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>&quot;W1J 8HX&quot;</td><td>51.506738</td><td>-0.143261</td><td>3.852557</td></tr><tr><td>&quot;W1J 8HS&quot;</td><td>51.507445</td><td>-0.141877</td><td>3.744058</td></tr><tr><td>&quot;W1J 8HR&quot;</td><td>51.507578</td><td>-0.141757</td><td>3.733327</td></tr><tr><td>&quot;W1J 8HZ&quot;</td><td>51.506578</td><td>-0.14393</td><td>3.901403</td></tr><tr><td>&quot;W1J 8HP&quot;</td><td>51.507541</td><td>-0.141686</td><td>3.729191</td></tr></tbody></table></div>"
      ],
      "text/plain": [
       "shape: (5, 4)\n",
       "┌──────────┬───────────┬───────────┬─────────────┐\n",
       "│ postcode ┆ lat       ┆ long      ┆ distance_km │\n",
       "│ ---      ┆ ---       ┆ ---       ┆ ---         │\n",
       "│ str      ┆ f64       ┆ f64       ┆ f64         │\n",
       "╞══════════╪═══════════╪═══════════╪═════════════╡\n",
       "│ W1J 8HX  ┆ 51.506738 ┆ -0.143261 ┆ 3.852557    │\n",
       "│ W1J 8HS  ┆ 51.507445 ┆ -0.141877 ┆ 3.744058    │\n",
       "│ W1J 8HR  ┆ 51.507578 ┆ -0.141757 ┆ 3.733327    │\n",
       "│ W1J 8HZ  ┆ 51.506578 ┆ -0.14393  ┆ 3.901403    │\n",
       "│ W1J 8HP  ┆ 51.507541 ┆ -0.141686 ┆ 3.729191    │\n",
       "└──────────┴───────────┴───────────┴─────────────┘"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Bank station coordinates\n",
    "BANK_LAT = 51.5133\n",
    "BANK_LON = -0.0886\n",
    "\n",
    "\n",
    "def haversine_km(lat1, lon1, lat2, lon2):\n",
    "    \"\"\"Calculate the great circle distance between two points in km.\"\"\"\n",
    "    R = 6371  # Earth's radius in km\n",
    "\n",
    "    lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])\n",
    "    dlat = lat2 - lat1\n",
    "    dlon = lon2 - lon1\n",
    "\n",
    "    a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2\n",
    "    c = 2 * atan2(sqrt(a), sqrt(1 - a))\n",
    "\n",
    "    return R * c\n",
    "\n",
    "\n",
    "# Calculate distance to Bank for each postcode\n",
    "df = df.with_columns(\n",
    "    pl.struct([\"lat\", \"long\"])\n",
    "    .map_elements(\n",
    "        lambda x: haversine_km(x[\"lat\"], x[\"long\"], BANK_LAT, BANK_LON),\n",
    "        return_dtype=pl.Float64,\n",
    "    )\n",
    "    .alias(\"distance_km\")\n",
    ")\n",
    "\n",
    "df.select([\"postcode\", \"lat\", \"long\", \"distance_km\"]).head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:51.944770Z",
     "iopub.status.busy": "2026-01-28T19:53:51.944696Z",
     "iopub.status.idle": "2026-01-28T19:53:51.948131Z",
     "shell.execute_reply": "2026-01-28T19:53:51.947811Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Postcodes with journey data: 662723/840000\n"
     ]
    }
   ],
   "source": [
    "# Filter to rows with valid journey times\n",
    "df_valid = df.filter(\n",
    "    pl.col(\"public_transport_easy_minutes\").is_not_null()\n",
    "    | pl.col(\"public_transport_quick_minutes\").is_not_null()\n",
    "    | pl.col(\"cycling_minutes\").is_not_null()\n",
    ")\n",
    "\n",
    "print(f\"Postcodes with journey data: {df_valid.height}/{df.height}\")\n",
    "\n",
    "if df_valid.height == 0:\n",
    "    print(\"\\nNo journey time data available yet. Run the fetcher first:\")\n",
    "    print(\"  uv run python -m pipeline.journey_times\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:51.948922Z",
     "iopub.status.busy": "2026-01-28T19:53:51.948847Z",
     "iopub.status.idle": "2026-01-28T19:53:51.952849Z",
     "shell.execute_reply": "2026-01-28T19:53:51.952531Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Journey Time Statistics (minutes):\n",
      "shape: (1, 6)\n",
      "┌──────────────┬─────────────┬─────────────┬───────────────┬──────────────┬───────────────────┐\n",
      "│ PT Easy Mean ┆ PT Easy Min ┆ PT Easy Max ┆ PT Quick Mean ┆ Cycling Mean ┆ Avg Distance (km) │\n",
      "│ ---          ┆ ---         ┆ ---         ┆ ---           ┆ ---          ┆ ---               │\n",
      "│ f64          ┆ i64         ┆ i64         ┆ f64           ┆ f64          ┆ f64               │\n",
      "╞══════════════╪═════════════╪═════════════╪═══════════════╪══════════════╪═══════════════════╡\n",
      "│ 77.879366    ┆ 0           ┆ 388         ┆ 72.765991     ┆ 75.200799    ┆ 34.670045         │\n",
      "└──────────────┴─────────────┴─────────────┴───────────────┴──────────────┴───────────────────┘\n",
      "\n",
      "Distance Statistics:\n",
      "shape: (1, 3)\n",
      "┌──────────┬────────────┬───────────┐\n",
      "│ Min km   ┆ Max km     ┆ Median km │\n",
      "│ ---      ┆ ---        ┆ ---       │\n",
      "│ f64      ┆ f64        ┆ f64       │\n",
      "╞══════════╪════════════╪═══════════╡\n",
      "│ 0.013939 ┆ 109.991355 ┆ 28.875436 │\n",
      "└──────────┴────────────┴───────────┘\n"
     ]
    }
   ],
   "source": [
    "# Summary statistics\n",
    "if df_valid.height > 0:\n",
    "    print(\"Journey Time Statistics (minutes):\")\n",
    "    print(\n",
    "        df_valid.select(\n",
    "            [\n",
    "                pl.col(\"public_transport_easy_minutes\").mean().alias(\"PT Easy Mean\"),\n",
    "                pl.col(\"public_transport_easy_minutes\").min().alias(\"PT Easy Min\"),\n",
    "                pl.col(\"public_transport_easy_minutes\").max().alias(\"PT Easy Max\"),\n",
    "                pl.col(\"public_transport_quick_minutes\").mean().alias(\"PT Quick Mean\"),\n",
    "                pl.col(\"cycling_minutes\").mean().alias(\"Cycling Mean\"),\n",
    "                pl.col(\"distance_km\").mean().alias(\"Avg Distance (km)\"),\n",
    "            ]\n",
    "        )\n",
    "    )\n",
    "\n",
    "    print(\"\\nDistance Statistics:\")\n",
    "    print(\n",
    "        df_valid.select(\n",
    "            [\n",
    "                pl.col(\"distance_km\").min().alias(\"Min km\"),\n",
    "                pl.col(\"distance_km\").max().alias(\"Max km\"),\n",
    "                pl.col(\"distance_km\").median().alias(\"Median km\"),\n",
    "            ]\n",
    "        )\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:51.953471Z",
     "iopub.status.busy": "2026-01-28T19:53:51.953401Z",
     "iopub.status.idle": "2026-01-28T19:53:53.919178Z",
     "shell.execute_reply": "2026-01-28T19:53:53.918809Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_1997193/4280370301.py:45: UserWarning: Creating legend with loc=\"best\" can be slow with large amounts of data.\n",
      "  plt.tight_layout()\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 410180 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot: Travel Time vs Distance by Mode\n",
    "if df_valid.height > 0:\n",
    "    fig, ax = plt.subplots(figsize=(12, 8))\n",
    "\n",
    "    # Convert to pandas for plotting\n",
    "    pdf = df_valid.to_pandas()\n",
    "\n",
    "    # Plot each mode\n",
    "    if pdf[\"public_transport_quick_minutes\"].notna().any():\n",
    "        ax.scatter(\n",
    "            pdf[\"distance_km\"],\n",
    "            pdf[\"public_transport_quick_minutes\"],\n",
    "            alpha=0.3,\n",
    "            label=\"Public Transport (Quick)\",\n",
    "            c=\"red\",\n",
    "            s=50,\n",
    "        )\n",
    "\n",
    "    if pdf[\"public_transport_easy_minutes\"].notna().any():\n",
    "        ax.scatter(\n",
    "            pdf[\"distance_km\"],\n",
    "            pdf[\"public_transport_easy_minutes\"],\n",
    "            alpha=0.3,\n",
    "            label=\"Public Transport (Easy)\",\n",
    "            c=\"green\",\n",
    "            s=50,\n",
    "        )\n",
    "\n",
    "    if pdf[\"cycling_minutes\"].notna().any():\n",
    "        ax.scatter(\n",
    "            pdf[\"distance_km\"],\n",
    "            pdf[\"cycling_minutes\"],\n",
    "            alpha=0.3,\n",
    "            label=\"Cycling\",\n",
    "            c=\"orange\",\n",
    "            s=50,\n",
    "        )\n",
    "\n",
    "    ax.set_xlabel(\"Distance to Bank (km)\", fontsize=12)\n",
    "    ax.set_ylabel(\"Travel Time (minutes)\", fontsize=12)\n",
    "    ax.set_title(\"Travel Time vs Distance to Bank Station\", fontsize=14)\n",
    "    ax.legend()\n",
    "    ax.grid(True, alpha=0.3)\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:53.920561Z",
     "iopub.status.busy": "2026-01-28T19:53:53.920429Z",
     "iopub.status.idle": "2026-01-28T19:53:55.185911Z",
     "shell.execute_reply": "2026-01-28T19:53:55.185544Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 257496 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot: Speed (km/h) vs Distance by Mode\n",
    "if df_valid.height > 0:\n",
    "    fig, ax = plt.subplots(figsize=(12, 8))\n",
    "\n",
    "    # Calculate effective speed for each mode\n",
    "    pdf = df_valid.with_columns(\n",
    "        [\n",
    "            (\n",
    "                pl.col(\"distance_km\") / (pl.col(\"public_transport_easy_minutes\") / 60)\n",
    "            ).alias(\"speed_easy_kmh\"),\n",
    "            (\n",
    "                pl.col(\"distance_km\") / (pl.col(\"public_transport_quick_minutes\") / 60)\n",
    "            ).alias(\"speed_quick_kmh\"),\n",
    "            (pl.col(\"distance_km\") / (pl.col(\"cycling_minutes\") / 60)).alias(\n",
    "                \"speed_cycling_kmh\"\n",
    "            ),\n",
    "        ]\n",
    "    ).to_pandas()\n",
    "\n",
    "    if pdf[\"speed_easy_kmh\"].notna().any():\n",
    "        ax.scatter(\n",
    "            pdf[\"distance_km\"],\n",
    "            pdf[\"speed_easy_kmh\"],\n",
    "            alpha=0.6,\n",
    "            label=\"Public Transport (Easy)\",\n",
    "            c=\"blue\",\n",
    "            s=50,\n",
    "        )\n",
    "\n",
    "    if pdf[\"speed_quick_kmh\"].notna().any():\n",
    "        ax.scatter(\n",
    "            pdf[\"distance_km\"],\n",
    "            pdf[\"speed_quick_kmh\"],\n",
    "            alpha=0.6,\n",
    "            label=\"Public Transport (Quick)\",\n",
    "            c=\"green\",\n",
    "            s=50,\n",
    "        )\n",
    "\n",
    "    if pdf[\"speed_cycling_kmh\"].notna().any():\n",
    "        ax.scatter(\n",
    "            pdf[\"distance_km\"],\n",
    "            pdf[\"speed_cycling_kmh\"],\n",
    "            alpha=0.6,\n",
    "            label=\"Cycling\",\n",
    "            c=\"orange\",\n",
    "            s=50,\n",
    "        )\n",
    "\n",
    "    ax.set_xlabel(\"Distance to Bank (km)\", fontsize=12)\n",
    "    ax.set_ylabel(\"Effective Speed (km/h)\", fontsize=12)\n",
    "    ax.set_title(\"Effective Travel Speed vs Distance to Bank Station\", fontsize=14)\n",
    "    ax.legend()\n",
    "    ax.grid(True, alpha=0.3)\n",
    "    ax.set_ylim(0, 100)  # Cap y-axis for readability\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:55.187209Z",
     "iopub.status.busy": "2026-01-28T19:53:55.187131Z",
     "iopub.status.idle": "2026-01-28T19:53:56.842052Z",
     "shell.execute_reply": "2026-01-28T19:53:56.841680Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 229308 characters]",
      "text/plain": [
       "<Figure size 1000x800 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Time saved by taking quick route:\n",
      "  Mean: 9.2 minutes\n",
      "  Max: 147 minutes\n"
     ]
    }
   ],
   "source": [
    "# Comparison: Quick vs Easy public transport\n",
    "if df_valid.height > 0:\n",
    "    df_both = df_valid.filter(\n",
    "        pl.col(\"public_transport_easy_minutes\").is_not_null()\n",
    "        & pl.col(\"public_transport_quick_minutes\").is_not_null()\n",
    "    )\n",
    "\n",
    "    if df_both.height > 0:\n",
    "        fig, ax = plt.subplots(figsize=(10, 8))\n",
    "\n",
    "        pdf = df_both.to_pandas()\n",
    "\n",
    "        ax.scatter(\n",
    "            pdf[\"public_transport_easy_minutes\"],\n",
    "            pdf[\"public_transport_quick_minutes\"],\n",
    "            c=pdf[\"distance_km\"],\n",
    "            cmap=\"viridis\",\n",
    "            s=60,\n",
    "            alpha=0.7,\n",
    "        )\n",
    "\n",
    "        # Add diagonal line (equal times)\n",
    "        max_val = max(\n",
    "            pdf[\"public_transport_easy_minutes\"].max(),\n",
    "            pdf[\"public_transport_quick_minutes\"].max(),\n",
    "        )\n",
    "        ax.plot([0, max_val], [0, max_val], \"r--\", alpha=0.5, label=\"Equal time\")\n",
    "\n",
    "        ax.set_xlabel(\"Easy Route (minutes)\", fontsize=12)\n",
    "        ax.set_ylabel(\"Quick Route (minutes)\", fontsize=12)\n",
    "        ax.set_title(\n",
    "            \"Quick vs Easy Public Transport Times\\n(color = distance in km)\",\n",
    "            fontsize=14,\n",
    "        )\n",
    "        ax.legend()\n",
    "        ax.grid(True, alpha=0.3)\n",
    "\n",
    "        plt.colorbar(ax.collections[0], label=\"Distance (km)\")\n",
    "        plt.tight_layout()\n",
    "        plt.show()\n",
    "\n",
    "        # Calculate time savings\n",
    "        savings = df_both.with_columns(\n",
    "            (\n",
    "                pl.col(\"public_transport_easy_minutes\")\n",
    "                - pl.col(\"public_transport_quick_minutes\")\n",
    "            ).alias(\"time_saved\")\n",
    "        )\n",
    "        print(\"\\nTime saved by taking quick route:\")\n",
    "        print(f\"  Mean: {savings['time_saved'].mean():.1f} minutes\")\n",
    "        print(f\"  Max: {savings['time_saved'].max():.0f} minutes\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-01-28T19:53:56.842982Z",
     "iopub.status.busy": "2026-01-28T19:53:56.842908Z",
     "iopub.status.idle": "2026-01-28T19:53:56.849543Z",
     "shell.execute_reply": "2026-01-28T19:53:56.849208Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Journey times sorted by distance to Bank:\n",
      "shape: (662_723, 5)\n",
      "┌──────────┬─────────────┬───────────────────────────┬───────────────────────────┬─────────────────┐\n",
      "│ postcode ┆ distance_km ┆ public_transport_easy_min ┆ public_transport_quick_mi ┆ cycling_minutes │\n",
      "│ ---      ┆ ---         ┆ utes                      ┆ nutes                     ┆ ---             │\n",
      "│ str      ┆ f64         ┆ ---                       ┆ ---                       ┆ i64             │\n",
      "│          ┆             ┆ i64                       ┆ i64                       ┆                 │\n",
      "╞══════════╪═════════════╪═══════════════════════════╪═══════════════════════════╪═════════════════╡\n",
      "│ EC3V 3LA ┆ 0.0         ┆ 0                         ┆ 0                         ┆ 0               │\n",
      "│ EC3V 3NR ┆ 0.0         ┆ 1                         ┆ 1                         ┆ 0               │\n",
      "│ EC3V 3QR ┆ 0.0         ┆ 1                         ┆ 1                         ┆ 0               │\n",
      "│ EC3V 3QS ┆ 0.0         ┆ 0                         ┆ 0                         ┆ 0               │\n",
      "│ EC3V 9AQ ┆ 0.0         ┆ 2                         ┆ 2                         ┆ 0               │\n",
      "│ …        ┆ …           ┆ …                         ┆ …                         ┆ …               │\n",
      "│ OX15 5JT ┆ 110.0       ┆ null                      ┆ 153                       ┆ null            │\n",
      "│ OX15 5LD ┆ 110.0       ┆ null                      ┆ 153                       ┆ null            │\n",
      "│ OX15 5ND ┆ 110.0       ┆ null                      ┆ 153                       ┆ null            │\n",
      "│ OX15 5NE ┆ 110.0       ┆ null                      ┆ 153                       ┆ null            │\n",
      "│ OX7 5TG  ┆ 110.0       ┆ null                      ┆ 241                       ┆ null            │\n",
      "└──────────┴─────────────┴───────────────────────────┴───────────────────────────┴─────────────────┘\n"
     ]
    }
   ],
   "source": [
    "# Data table: sorted by distance\n",
    "if df_valid.height > 0:\n",
    "    print(\"Journey times sorted by distance to Bank:\")\n",
    "    display_df = df_valid.select(\n",
    "        [\n",
    "            \"postcode\",\n",
    "            pl.col(\"distance_km\").round(1),\n",
    "            \"public_transport_easy_minutes\",\n",
    "            \"public_transport_quick_minutes\",\n",
    "            \"cycling_minutes\",\n",
    "        ]\n",
    "    ).sort(\"distance_km\")\n",
    "\n",
    "    print(display_df)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "property-map",
   "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.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
