Analysis

01 - System-Wide OTP Trend

Core OTP Patterns

Coverage: 2016-11 to 2025-11 (from otp_monthly, scheduled_trips_monthly).

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

Page Navigation

Analysis Navigation

Data Provenance

flowchart LR
  01_system_trend(["01 - System-Wide OTP Trend"])
  t_otp_monthly[("otp_monthly")] --> 01_system_trend
  01_data_ingestion[["Data Ingestion"]] --> t_otp_monthly
  t_route_stops[("route_stops")] --> 01_system_trend
  01_data_ingestion[["Data Ingestion"]] --> t_route_stops
  t_routes[("routes")] --> 01_system_trend
  01_data_ingestion[["Data Ingestion"]] --> t_routes
  t_scheduled_trips_monthly[("scheduled_trips_monthly")] --> 01_system_trend
  02_scheduled_trips[["Scheduled Trips ETL"]] --> t_scheduled_trips_monthly
  d1_01_system_trend(("polars (lib)")) --> 01_system_trend
  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 01_system_trend page;
  class t_otp_monthly,t_route_stops,t_routes,t_scheduled_trips_monthly table;
  class d1_01_system_trend dep;
  class 01_data_ingestion,02_scheduled_trips pipeline;

Findings

Findings: System-Wide OTP Trend

Summary

PRT on-time performance has declined over the 2019--2025 window and has not recovered to pre-COVID levels. A bus-only stratification confirms the decline is not an artifact of mixing modes -- bus trends closely track the all-mode average.

Key Numbers

  • 2019 baseline: ~69% all-mode weighted OTP, ~68% bus-only weighted OTP (93 routes reporting)
  • COVID spike: 77% all-mode in March 2020 (low ridership improved adherence)
  • Post-COVID trough: 60% all-mode weighted in September 2022
  • Current level: 63--65% all-mode / 64--66% bus-only weighted OTP (late 2025)

Observations

  • The unweighted and weighted averages now track very closely (~0.1 pp gap on average). The previous ~2-3 pp gap was an artifact of using SUM(trips_7d) across stops, which conflated trip frequency with route length and over-weighted long routes.
  • The bus-only trend closely tracks the all-mode trend (gap averages ~0.5 pp), because bus routes (90+ of ~93 reporting) overwhelmingly dominate the system average. Rail's higher OTP lifts the all-mode average only slightly.
  • Year-over-year change was consistently negative from mid-2021 through late 2022, then stabilized near zero -- the system stopped declining but hasn't improved.
  • Route count ranges from 68 to 96 across months. Most months have 93--96 routes, but Nov 2020 is a low outlier at 68 and mid-2025 months drop to 77--79. This varying composition does not affect the per-month averaging directly (each month averages whichever routes reported), but it means the set of routes being averaged is not fixed across time.
  • For Jan 2019 -- Mar 2021 (27 months), weights come from WPRDC time-varying scheduled trip counts. For Apr 2021 onward (56 months), weights fall back to static MAX(trips_7d)/7 from route_stops.

Caveats

  • Time-varying weights are only available through Mar 2021. The static fallback for later months (MAX trips_7d / 7) is a single snapshot, not historical. If route frequency changed significantly after Mar 2021, the weighting for those months may not perfectly reflect actual service levels.
  • The OTP definition ("on-time") is not specified in the source data -- the threshold could be 1 minute, 5 minutes, or something else.
  • Five routes (37, 42, P2, RLSH, SWL) have zero weight because they appear in neither scheduled_trips_monthly nor route_stops. These routes still contribute to the unweighted average.

Discussion

The central finding -- that PRT OTP declined from ~69% to ~60% and has stabilized around 63--65% without recovering -- is robust to the weighting methodology change. The overall trend shape (pre-COVID baseline, COVID spike, sustained decline, plateau) is visible in both weighted and unweighted series.

The collapse of the weighted-unweighted gap from ~2-3 pp to ~0.1 pp is itself a notable finding. The previous gap was interpreted as evidence that "high-frequency routes perform worse than low-frequency ones." This was wrong: it was an artifact of the SUM-across-stops weight, which inflated the weights of routes with many stops (i.e., long routes), not high-frequency routes. Once weights correctly reflect trip frequency (via WPRDC scheduled counts or MAX-based proxy), the weighted and unweighted averages converge, meaning route frequency does not systematically predict OTP at the system level. This is consistent with the null result from Analysis 10 (cross-sectional) and Analysis 30 (longitudinal panel).

The time-varying weights for the first 27 months (Jan 2019 -- Mar 2021) capture real service changes, including the COVID-era cuts that reduced weekday trips by 31% between March and April 2020. This means the weighted OTP for 2020--2021 reflects actual service levels rather than retroactively applying today's schedule. The static fallback for later months remains a limitation -- if PRT restructured service significantly after March 2021, those changes are not captured. Extending the WPRDC data or obtaining historical GTFS archives would close this gap.

The bus-only stratification confirms that system trends are driven by bus performance. Rail's higher OTP (~84%) lifts the all-mode average by only ~0.5 pp because bus routes represent >96% of the reporting routes. Any policy intervention to improve system OTP must focus on bus operations.

Methodology Change (2026-02-15)

Replaced static SUM(trips_7d) weighting with time-varying daily_trips from WPRDC scheduled trip counts (Jan 2019 -- Mar 2021) and MAX(trips_7d)/7 static fallback for later months. This fixes Methodology Issues #1 (SUM conflation with route length) and #2 (static weights across 7 years). The weighted-unweighted gap collapsed from ~2-3 pp to ~0.1 pp, confirming the old gap was an artifact of the SUM-based weight inflation, not a genuine high-frequency penalty.

Review History

  • 2026-02-11: RED-TEAM-REPORTS/2026-02-11-analyses-01-05-07-11.md -- 5 issues (1 significant). Added bus-only trend line, fixed METHODS.md data section, documented route count variability (68--96), corrected "stable at 93" claim, and documented excluded routes.
  • 2026-02-15: Fixed Methodology Issues #1 and #2. Switched to time-varying WPRDC trip weights for overlap period, MAX-based static fallback for remaining months.

Output

Methods

Methods: System-Wide OTP Trend

Question

Is PRT on-time performance improving, declining, or stable over the 2019--2025 period?

Approach

  • Compute a monthly system-wide OTP by averaging across all routes, weighted by daily trip counts so high-frequency routes count more.
  • Time-varying weights (Jan 2019 -- Mar 2021): Use daily_trips from scheduled_trips_monthly (WEEKDAY day type), sourced from WPRDC monthly schedule data. This provides month-specific service levels per route.
  • Static fallback (Apr 2021 -- Nov 2025): Use MAX(trips_7d) / 7 from route_stops as an approximate daily trip count per route. MAX avoids the SUM-across-stops conflation with route length (Methodology Issue #1).
  • Also compute an unweighted mean for comparison.
  • Stratify by mode: compute a separate bus-only weighted and unweighted trend to isolate bus performance from structurally higher-performing rail routes.
  • Plot all four series (all-mode weighted/unweighted, bus-only weighted/unweighted) as time series to identify trends, seasonal patterns, and the COVID-era shift. The time-varying weight region is shaded on the chart.
  • Compute year-over-year change to quantify the direction.

Data

Name Description Source
otp_monthly Monthly OTP per route prt.db table
scheduled_trips_monthly WPRDC time-varying daily trip counts per route per month (WEEKDAY), Nov 2016 -- Mar 2021 prt.db table
route_stops Static trip counts for fallback weighting (Apr 2021 onward) prt.db table
routes Mode classification (used to compute bus-only trends) prt.db table

Output

  • output/system_trend.csv -- monthly weighted and unweighted OTP (all modes), with pct_time_varying column
  • output/system_trend_bus_only.csv -- monthly weighted and unweighted OTP (bus only)
  • output/system_trend.png -- time series chart with all-mode and bus-only overlays; shaded region marks time-varying weight period

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.
scheduled_trips_monthly table Primary analytical table used in this page's computations. Produced by Scheduled Trips ETL. Updated when the producing pipeline step is rerun. Coverage depends on upstream source availability and ETL assumptions.
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.