Analysis

08: Hot-Spot Map

Core OTP Patterns

Coverage: 2019-01 to 2025-11 (from otp_monthly).

Built 2026-03-03 02:23 UTC · Commit defd5c8

Page Navigation

Analysis Navigation

Data Provenance

flowchart LR
  08_hotspot_map(["08: Hot-Spot Map"])
  t_otp_monthly[("otp_monthly")] --> 08_hotspot_map
  01_data_ingestion[["Data Ingestion"]] --> t_otp_monthly
  t_route_stops[("route_stops")] --> 08_hotspot_map
  01_data_ingestion[["Data Ingestion"]] --> t_route_stops
  t_routes[("routes")] --> 08_hotspot_map
  01_data_ingestion[["Data Ingestion"]] --> t_routes
  t_stops[("stops")] --> 08_hotspot_map
  01_data_ingestion[["Data Ingestion"]] --> t_stops
  d1_08_hotspot_map(("branca (lib)")) --> 08_hotspot_map
  d2_08_hotspot_map(("folium (lib)")) --> 08_hotspot_map
  d3_08_hotspot_map(("matplotlib (lib)")) --> 08_hotspot_map
  d4_08_hotspot_map(("polars (lib)")) --> 08_hotspot_map
  classDef page fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a,stroke-width:2px;
  classDef table fill:#ecfeff,stroke:#0e7490,color:#164e63;
  classDef dep fill:#fff7ed,stroke:#c2410c,color:#7c2d12,stroke-dasharray: 4 2;
  classDef file fill:#eef2ff,stroke:#6366f1,color:#3730a3;
  classDef api fill:#f0fdf4,stroke:#16a34a,color:#14532d;
  classDef pipeline fill:#f5f3ff,stroke:#7c3aed,color:#4c1d95;
  class 08_hotspot_map page;
  class t_otp_monthly,t_route_stops,t_routes,t_stops table;
  class d1_08_hotspot_map,d2_08_hotspot_map,d3_08_hotspot_map,d4_08_hotspot_map dep;
  class 01_data_ingestion pipeline;

Findings

Findings: Hot-Spot Map

Important: Derived Metric

Stop-level OTP is a derived metric: each stop inherits the average OTP of the routes serving it, weighted by trip frequency (trips_7d). It reflects route composition at each stop, not independently measured stop-level performance. A stop served by a single high-OTP route will appear "high-performing" even if that stop is a chronic delay point on that route. Conversely, a stop served by many routes will reflect the blended average of those routes.

Summary

6,212 stops were mapped with route-weighted OTP (after excluding 2 stops with null/NaN OTP due to zero total trips, and excluding routes with fewer than 12 months of data). Poor performance clusters in eastern Pittsburgh (Penn Hills, Squirrel Hill, Highland Park), while the best performance follows the light rail and busway corridors.

Geographic Patterns

  • Best corridors: The light rail T line (Beechview/Overbrook south to Library) and the East Busway (Wilkinsburg to downtown) form clear green bands on the map, with 80%+ OTP at most stops.
  • Worst corridors: Eastern neighborhoods served by Route 77 (Penn Hills) show the lowest stop-level OTP at 55.8%. The 61-series routes through McKeesport and Homestead also form a low-OTP cluster.
  • Downtown: Mixed performance. Stops in the Golden Triangle are served by many routes, so their weighted OTP reflects the system average (~65--70%).

Mode Context

The best-performing stops (88.4%) are all served exclusively by BUS routes -- specifically Route 18 (Manchester). The high-OTP corridor along the T line reflects rail's structural advantage (dedicated right-of-way), not independently measured stop performance. When interpreting the map:

  • Rail stops (light rail T line) appear green primarily because RAIL routes average ~84% OTP system-wide. These stops' high performance reflects mode advantage, not stop-specific factors.
  • Busway stops (East Busway, West Busway) also appear green for similar reasons -- dedicated right-of-way.
  • Genuinely high-performing bus stops include those on Route 18 (Manchester, 88.4%) and Route 39 (Brookline), which achieve high OTP on mixed-traffic streets.

Observations

  • The worst-performing stops (55.8%) are exclusively served by Route 77, the system's lowest-ranked route.
  • The best-performing stops (88.4%) are exclusively served by Route 18 (Manchester) -- a bus route, not rail.
  • Stops served by multiple routes tend toward the system mean, since the weighting blends good and bad routes.
  • The system average displayed in the chart title (unweighted stop-level average) treats every stop equally regardless of trip volume. A trip-weighted system average would differ slightly.

Caveats

  • The map uses a simple lat/lon scatter, not a true geographic projection. At Pittsburgh's latitude, this introduces minor distortion but the overall shape is recognizable.
  • Stops with null or NaN OTP values (2 stops) were excluded from the map.
  • OTP is averaged across all months (2019--2025), so the map doesn't show temporal changes.
  • Routes with fewer than 12 months of OTP data were excluded to avoid projecting noisy estimates onto the map.

Review History

  • 2026-02-11: RED-TEAM-REPORTS/2026-02-11-analyses-01-05-07-11.md -- 7 issues (1 significant). Documented derived-metric nature of stop OTP, added mode column and bus/rail context, added minimum 12-month filter for route OTP, clarified unweighted system average, corrected NaN claim, fixed hood="0" sentinel, added dropped-stop logging.

Output

Methods

Methods: Hot-Spot Map

Question

Where do poor-performing routes cluster geographically? Are there corridor-level bottlenecks visible on a map?

Approach

  • For each stop, compute the trip-weighted average OTP of all routes serving it. This is a derived metric ("route-weighted OTP"): each stop inherits the average OTP of the routes serving it, weighted by trip frequency (trips_7d). It reflects route composition at each stop, not independently measured stop-level performance.
  • Only include routes with at least 12 months of OTP data to avoid projecting noisy estimates onto the map.
  • Plot stops on a lat/lon scatter plot, colored by route-weighted OTP performance.
  • Use a diverging red-yellow-green colormap so low OTP areas are immediately visible.
  • Display the unweighted stop-level average as a reference (note: this is unweighted across stops, not weighted by trip volume).
  • Track the mode (BUS/RAIL) of routes serving each stop for context.
  • Stops with null or NaN OTP (due to zero total trips or missing data) are excluded from the map.

Data

Name Description Source
otp_monthly Monthly OTP per route (averaged across all months, routes with < 12 months excluded) prt.db table
route_stops Which stops are served by which routes, with trip counts prt.db table
routes Route metadata including mode prt.db table
stops Lat/lon coordinates prt.db table
shapes.txt Route polyline geometries GTFS file (data/GTFS/)
trips.txt Route-to-shape mapping GTFS file (data/GTFS/)

Output

  • output/hotspot_map.csv -- per-stop route-weighted OTP with coordinates
  • output/hotspot_map.png -- geographic scatter plot
  • output/hotspot_map.html -- interactive folium map over OpenStreetMap tiles with per-stop popups

Sources

NameTypeWhy It MattersOwnerFreshnessCaveat
otp_monthly table Primary analytical table used in this page's computations. Produced by Data Ingestion. Updated when the producing pipeline step is rerun. Coverage depends on upstream source availability and ETL assumptions.
route_stops table Primary analytical table used in this page's computations. Produced by Data Ingestion. Updated when the producing pipeline step is rerun. Coverage depends on upstream source availability and ETL assumptions.
routes table Primary analytical table used in this page's computations. Produced by Data Ingestion. Updated when the producing pipeline step is rerun. Coverage depends on upstream source availability and ETL assumptions.
stops table Primary analytical table used in this page's computations. Produced by Data Ingestion. Updated when the producing pipeline step is rerun. Coverage depends on upstream source availability and ETL assumptions.
branca dependency Runtime dependency required for this page's pipeline or analysis code. Open-source Python ecosystem maintainers. Version pinned by project environment until dependency updates are applied. Library updates may change behavior or defaults.
folium dependency Runtime dependency required for this page's pipeline or analysis code. Open-source Python ecosystem maintainers. Version pinned by project environment until dependency updates are applied. Library updates may change behavior or defaults.
matplotlib dependency Runtime dependency required for this page's pipeline or analysis code. Open-source Python ecosystem maintainers. Version pinned by project environment until dependency updates are applied. Library updates may change behavior or defaults.
polars dependency Runtime dependency required for this page's pipeline or analysis code. Open-source Python ecosystem maintainers. Version pinned by project environment until dependency updates are applied. Library updates may change behavior or defaults.