CTA Frequent Network Analysis

Reproducing the Analysis

Clone the repo, install dependencies, and run 8 notebooks to verify every finding on this site.

Quick Start

git clone https://github.com/sbahamon/bus-check
cd bus-check
uv sync
uv pip install -e .
uv run --no-sync jupyter lab

Requires Python 3.11+ and uv (Astral's package manager).

What You'll Need

The Critical Gotcha

Always pair uv pip install -e . with uv run --no-sync.

Plain uv run (without --no-sync) re-syncs the virtual environment and drops the editable install. This causes ModuleNotFoundError: No module named 'bus_check'.

The safe pattern is always:

uv pip install -e . && uv run --no-sync jupyter ...

If you see ModuleNotFoundError, just run uv pip install -e . again.

Running the Notebooks

All 8 notebooks are independent — they each fetch data live from public APIs. No chaining required.

#NotebookData SourceKey Output
01 Ridership Exploration
20-route YoY comparison + pooled DiD
SODA API YoY changes, pooled DiD estimate
02 Headway Exploration
GTFS scheduled vs. observed headways
GTFS + Cloudflare D1 Headway adherence rates
03 Phases 1–3 Only
Excludes Phase 4 (no post-data)
SODA API Filtered YoY comparison
04 Without Route #79
Sensitivity test excluding outlier
SODA API Robustness check
05 Ridership Share
FN share of total CTA ridership
SODA API Share trend lines
06 DiD by Phase
Phase-level DiD with bootstrap CIs
SODA API P1 +428, P2 +514, P3 +1,142
07 Staggered DiD
Callaway-Sant'Anna estimator
SODA API ATT estimate
08 Regression DiD
OLS with fixed effects + placebo
SODA API Regression DiD, placebo test

Execute one notebook

uv pip install -e . && uv run --no-sync jupyter execute notebooks/01_ridership_exploration.ipynb --inplace

Execute all at once

for nb in notebooks/0{1..8}_*.ipynb; do
  echo "Running $nb..."
  uv pip install -e . && uv run --no-sync jupyter execute "$nb" --inplace
done

Verifying the Results

Run the test suite to confirm all components work correctly:

uv run pytest   # 163 tests should pass

Then compare notebook outputs against the key numbers on the main page:

Data Sources

Ridership

Chicago Data Portal SODA API (dataset jyb9-n7fm). Public, no authentication required. Daily boardings by route from January 2023 through November 30, 2025.

Schedules (GTFS)

CTA GTFS static feed. Downloaded automatically by notebook 02 for scheduled headway computation.

Bus Tracker (optional)

CTA Bus Tracker API. Requires a CTA API key. Only needed for the real-time headway collector, not for ridership analysis.

Headway Data Caveat

The headway analysis (notebook 02) depends on real-time vehicle position data collected by a Cloudflare Worker that polls the CTA Bus Tracker API every 5 minutes during service hours and stores positions in Cloudflare D1. A fresh clone won't have this data. Your options:

uv run python -m bus_check.collector.headway_collector

This requires a CTA_API_KEY in your .env file. See .env.example for the template. The local collector writes to data/headway.db (SQLite). For the live site, a Cloudflare Worker (worker/) handles collection automatically.

Known Limitations for Reproducibility

Project Structure

For AI Coding Assistants

Using Claude Code, Cursor, Copilot, or another AI coding assistant? See REPRODUCING.md in the repo root for a machine-readable reproduction guide written in direct, imperative language that agents parse well.