Project 1 · Portfolio

Bus Timetable Reliability Analysis

A tool that checks whether a real UK bus route actually runs when it says it will — by comparing the bus company's official timetable against the bus's own real, recorded GPS location data.

View the live demo

What it does

Every UK bus route publishes an official timetable — e.g. "a bus every 20 minutes." This project picks one real, live bus route and quietly collects two things over time: what the timetable promised, and where the actual bus really was, second by second, from the UK government's own live bus-tracking data (the Bus Open Data Service, or BODS).

It then lines the two up and shows, in simple charts, how late (or early) buses actually run, and how uneven the gap between buses really is — broken down by day of the week, since Monday traffic and Saturday traffic are not the same.

The dashboard also includes an interactive map of exactly where the route runs, drawn straight from the official timetable's own geography. You can switch between the route's different directions, and each one shows its own scheduled days and departure times.

Why it matters

A published timetable is a promise, not a guarantee. Passengers plan their day around the published times, but a route listed as "every 20 minutes" can, in practice, swing between buses arriving 12 minutes apart and buses arriving 31 minutes apart — the timetable alone can't tell you that.

This project exists to measure that gap honestly, using real public data rather than guesswork, and to turn it into something a non-technical reader can look at and immediately understand. It reflects a growing shift in the transport industry toward "probabilistic" timetabling — publishing a realistic range of likely arrival times instead of one fixed clock-face number — the exact kind of reliability analysis real transport-technology companies build their products around.

Who it helps

For a passenger, it answers the question a timetable can't: "how much should I actually trust this time?" For a bus operator or transport planner, the same breakdown — by day of week, lateness, and gap-between-buses — is exactly the kind of evidence used to decide where a schedule needs fixing. And for anyone reviewing this as a piece of work, it demonstrates handling real, messy, government open data end-to-end: collecting it continuously, joining it correctly against a timetable, and presenting the result clearly — not a toy dataset.

Under the hood

Technology used, and why

Every tool below was picked for a specific reason, not out of habit — the table explains each one in plain terms.

Python

The programming language everything is written in — chosen because it's widely used for exactly this kind of "take messy real-world data and make sense of it" work.

pandas

A data-handling library that turns "compare thousands of scheduled times against thousands of actual times" into a few lines of code instead of thousands.

gtfs-realtime-bindings

The government's live bus-location feed arrives in a compact format built for machines, not plain text. This library is required, not a nice-to-have, to read it at all.

Streamlit

Turns the analysis straight into the web page you can open in a browser, without having to separately build a front end from scratch.

SQLite

A simple, file-based database that stores every bus position collected. Ideal for this proof of concept, rapid prototype demo - ensuring no separate database server is required to be install or manage.

pydeck

Draws the interactive route map, so you can see exactly where the bus is meant to go, not just when.

pytest

Every change is written test-first and checked by an automated suite before it's trusted — including a hand-checked example of the trickiest step (matching timetable times to real bus times), plus a final check in a real browser once deployed.

Docker & Docker Compose

Packages the whole app consistently and runs its two moving parts side by side — one that quietly collects data all the time, one that shows it to you on demand.

How it's built

Architecture

Shown here as two diagrams at two levels of detail — first the big picture of the running system, then what actually happens to the data, step by step.

1. The big picture — two always-on helpers sharing one memory

One piece keeps collecting live bus positions in the background, all the time. A separate piece builds the web page you actually look at, on demand. Both read and write the same shared storage, so nothing is lost between the two.

flowchart LR
    accTitle: System overview diagram
    accDescr: Official bus data from the UK government flows into an always-running data collector, which writes into shared storage. A dashboard web app reads from that same shared storage, and you view it in your browser.
    A["Official bus data
(UK government)"] --> B["Data collector
(always running)"] B --> C[("Shared
storage")] C --> D["Dashboard
web app"] D --> E["You, in
your browser"] classDef ext fill:#fde8d7,stroke:#c2410c,stroke-width:2px,color:#1f2937 classDef proc fill:#dcfce7,stroke:#15803d,stroke-width:2px,color:#1f2937 classDef store fill:#e0e7ff,stroke:#4338ca,stroke-width:2px,color:#1f2937 class A,E ext class B,D proc class C store

In plain terms: government bus data goes in on the left, a dashboard you can read comes out on the right, with one shared "memory" in the middle keeping everything safe in between.

2. What happens to the data, step by step

Zooming in on the "shared storage" box above: this is the five-step journey each piece of data takes before it becomes a chart on screen.

flowchart LR
    accTitle: Data processing pipeline diagram
    accDescr: The official timetable is loaded in, then real bus positions are collected, then the two are matched against each other, then lateness and gaps are worked out, then the result is shown as charts.
    A["Timetable
loaded in"] --> B["Real positions
collected"] B --> C["Matched:
planned vs actual"] C --> D["Lateness & gaps
worked out"] D --> E["Shown as
charts"] classDef step fill:#dcfce7,stroke:#15803d,stroke-width:2px,color:#1f2937 class A,B,C,D,E step

The "matched: planned vs actual" step is the trickiest part of the whole project — getting it subtly wrong wouldn't crash anything, it would just quietly produce a chart that looks fine but is wrong. For that reason, this step has its own dedicated, hand-checked test data to catch exactly that risk.

See it running for real

This isn't a mock-up — it's a live dashboard, built from a real bus route's real data.

View the live demo