Analysis

02 - Mode Comparison

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
  02_mode_comparison(["02 - Mode Comparison"])
  t_otp_monthly[("otp_monthly")] --> 02_mode_comparison
  01_data_ingestion[["Data Ingestion"]] --> t_otp_monthly
  t_route_stops[("route_stops")] --> 02_mode_comparison
  01_data_ingestion[["Data Ingestion"]] --> t_route_stops
  t_routes[("routes")] --> 02_mode_comparison
  01_data_ingestion[["Data Ingestion"]] --> t_routes
  d1_02_mode_comparison(("numpy (lib)")) --> 02_mode_comparison
  d2_02_mode_comparison(("polars (lib)")) --> 02_mode_comparison
  d3_02_mode_comparison(("scipy (lib)")) --> 02_mode_comparison
  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 02_mode_comparison page;
  class t_otp_monthly,t_route_stops,t_routes table;
  class d1_02_mode_comparison,d2_02_mode_comparison,d3_02_mode_comparison dep;
  class 01_data_ingestion pipeline;

Findings

Findings: Mode Comparison

Summary

Light rail consistently outperforms bus by a wide margin, and the difference is statistically significant (Mann-Whitney U = 6,563, p < 0.001). Among bus routes, dedicated right-of-way (busway) routes perform nearly as well as rail, and limited-stop variants beat their local counterparts.

Key Numbers

Mode / Type Avg OTP (unweighted) Avg OTP (trip-weighted) Route Count
RAIL 84% 84% 3
Busway (P1, P3, G2) 71--76% -- 3
Flyer (P/G/O prefix) ~70% -- ~16
Limited (L suffix) ~72% -- varies
Express (X suffix) ~70% -- varies
Local bus 63--69% -- ~60

Statistical Tests

  • Mann-Whitney U test (RAIL vs BUS monthly OTP): U = 6,563, p < 0.001 (n = 83 months each). Rail median monthly OTP = 86.1%, bus median = 69.2%. The difference is highly significant.
  • Paired route comparison (2 pairs: 51/51L, 53/53L): Limited variants average +3.5 percentage points over their local counterparts (paired t-test: t = 7.37, p < 0.001, 95% CI: [+2.5, +4.4 pp], n = 85 paired monthly observations across 2 pairs). While the test is significant, the sample of only 2 route pairs limits the generalizability of this finding.
  • Trip-weighted mode average: Bus trip-weighted OTP (66.8%) is about 2 pp below the unweighted average (68.9%), confirming that high-frequency bus routes tend to perform worse. Rail is nearly the same weighted (83.8%) vs unweighted (84.1%).

Observations

  • Busway routes (P1, P3, G2) perform nearly as well as rail, consistent with the dedicated-right-of-way hypothesis.
  • The previous classification grouped all P/G-prefix routes as "busway," which incorrectly included flyer routes like P17 (Lincoln Park Flyer), P78 (Oakmont Flyer), G3 (Moon Flyer), and G31 (Bridgeville Flyer). The corrected classification identifies only P1, P2, P3, and G2 as true busway routes.
  • Only 2 local/limited pairs were found in the data (routes with matching base IDs). More pairs would strengthen the comparison.
  • The RAIL--BUS gap has been roughly stable over time -- both modes declined in parallel, suggesting system-wide factors rather than mode-specific ones.
  • The INCLINE mode has no OTP data and was excluded.

Caveats

  • Five UNKNOWN-mode routes (37, 42, P2, RLSH, SWL) were excluded from the analysis. P2 (East Busway Short) is plausibly a BUS/busway route, but its mode is listed as UNKNOWN in the database and it also lacks route_stops data.
  • Bus route classification uses route ID naming conventions. True busway routes are identified as P1, P2, P3, G2; all other P/G/O-prefix routes are classified as flyers. Some routes may still be misclassified if their ID doesn't follow the standard pattern.
  • Rail has only 3 routes (RED, BLUE, SLVR), so its average is sensitive to any single route's performance. The Mann-Whitney test has adequate statistical power due to 83 months of observations, but the underlying data comes from only 3 routes.
  • The mode-level unweighted average treats each route equally regardless of trip volume. The trip-weighted version provides a ridership-adjusted perspective.
  • Route composition varies across months (68--96 routes reporting). No balanced-panel filter is applied, so the set of routes contributing to each month's average is not fixed.

Review History

  • 2026-02-11: RED-TEAM-REPORTS/2026-02-11-analyses-01-05-07-11.md -- 6 issues (1 significant). Replaced "significantly outperforms" with formal Mann-Whitney U test, added paired t-test with 95% CI, excluded UNKNOWN-mode routes, fixed classify_bus_route to correctly distinguish busway from flyer routes, added trip-weighted mode comparison, and documented composition variability.

Output

Methods

Methods: Mode Comparison

Question

Does light rail (dedicated right-of-way) consistently outperform bus? Do limited/express routes beat their local counterparts?

Approach

  • Exclude UNKNOWN-mode routes (37, 42, P2, RLSH, SWL) from the analysis to avoid ambiguous classifications.
  • Group routes by mode (BUS, RAIL) and compute average OTP per mode per month, both unweighted and trip-weighted (using trips_7d from route_stops).
  • Perform a Mann-Whitney U test on the monthly mode-level OTP distributions to formally test whether the RAIL--BUS difference is statistically significant.
  • Classify bus routes by type using route ID patterns:
    • Busway: P1, P2, P3, G2 (dedicated right-of-way)
    • Flyer: Other P/G-prefix routes (e.g., P17, P78, G3, G31) and O-prefix routes (park-and-ride express services)
    • Limited: L-suffix routes (e.g., 51L, 53L)
    • Express: X-suffix routes
    • Local: All other bus routes
  • Compare paired routes sharing the same corridor (e.g. 51 vs 51L) as natural experiments. Perform a paired t-test on monthly OTP differences and report the mean difference with a 95% confidence interval.
  • Test whether the mode gap changes over time.

Data

Name Description Source
otp_monthly Monthly OTP per route prt.db table
routes Mode classification (filter out UNKNOWN) prt.db table
route_stops Trip counts for trip-weighted mode averages prt.db table

Output

  • output/mode_comparison.csv -- monthly OTP by mode/type (unweighted)
  • output/mode_comparison_weighted.csv -- monthly OTP by mode (trip-weighted)
  • output/mode_comparison.png -- comparison chart

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.
numpy 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.
scipy 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.