Request access to the NYC Subway Delay Radar

Released for non-commercial research and journalism. Requests are reviewed manually; most are approved.

Before you dive in

Short version: free for research, journalism, teaching, and personal projects.
Please credit me, and please don't use it to make money without asking first.

The MTA's feeds are theirs, and they're public — you can get them straight
from the source without agreeing to anything. What I'm sharing is what's built
on top: the captured history the model was fit to, the learned baselines that
define "normal", the coefficients, the calibration table, and the training
protocol. That's my work, and I'm licensing it to you under CC BY-NC 4.0.

Worth saying why the coefficients count as authored rather than found: they're
the output of a set of choices — how the features are defined, excluding
alert-active periods when learning "normal", collapsing metrics to episodes,
picking the threshold on the training side only. The design is the part I made.

What you can do

Use it, study it, fine-tune it, benchmark against it, and publish what you
find — just credit Henry Williams / subway.fyi. Academic work, journalism,
teaching, and personal projects are all welcome, and if you beat these numbers
I'd love to hear about it.

The one real limit: not for commercial use

Please don't use this model, its outputs, or anything derived from it to make
money without asking me first. That covers shipping it in a paid product or
service, fine-tuning or distilling it for commercial deployment (derived
weights carry the same limit), selling predictions or analysis made with it,
and for-profit internal use.

Access here isn't a commercial licence on its own — but do get in touch if you
want one. I'm open to it.

Two caveats worth knowing

  • Recall is low, by nature of the problem. It catches roughly one
    disruption in eight, at about 5.75 false alarms a day. It's a screening aid
    for a human reader, not a detector, and it isn't safety-certified — please
    don't put it anywhere that being wrong would actually matter.
  • It isn't an MTA product. Unofficial and unaffiliated, so please don't
    present its output as official service information.

Log in or Sign Up to review the conditions and access this model content.

NYC Subway Delay Radar

A delay-prediction model that tries to spot a subway service disruption from the public realtime feed before the MTA announces it. It runs in production on subway.fyi, evaluating every route roughly once a minute, and its full track record — misses included — is public at subway.fyi/accuracy.

The model is plain JSON logistic-regression coefficients (3.8 KB). There is no ML runtime: scoring is twelve multiply-adds and a sigmoid, which is why it can sit inside a lightweight poller container. predict.py in this repo is a complete, dependency-free implementation.

The honest headline

On a single held-out window it was scored exactly once, at a threshold frozen beforehand on earlier data:

delay radar (v2.7) naive rule
episode recall 0.13 0.053
precision 0.446 0.242
false alarms / day 5.75 5.88
median lead time 43 min
holdout window 2026-07-30 → 08-07 same

The naive rule is n_stations_30m >= 21, thresholded on the same validation slice. So: at an equal false-alarm budget the model roughly doubles precision and multiplies episode recall by ~2.5, and when it is right it fires a median of 43 minutes before the MTA posts.

Read the recall number correctly. It catches about one disruption in eight. This is a screening aid that surfaces some incidents early — not a comprehensive detector, and not something to make operational decisions on.

Two independent channels

The production radar combines the model in this repo with a second, non-learned channel. Both write to delay_predictions in the companion dataset.

Gap channel (this model). Logistic regression over route-level headway anomaly features: how many stations are running ≥2× their learned normal headway, how far that has spread, whether it is growing, and how much of the rest of the network is simultaneously anomalous. Baselines are per (route, station, day-of-week, hour), rebuilt nightly from months of history with alert-active periods excluded so "normal" never learns from a bad day.

Stall channel (rules, not learned). Physical evidence: one train anchored at a platform beyond that stop's learned routine-hold time, with more trains pinned behind it. A 9-minute hold at Canal St is normal bridge traffic; the same hold at 167 St is an emergency — so the threshold is per-platform, from ml_stop_hold_baselines. Guards learned from production misfires: lay-up trains parked on non-revenue tracks are rejected when other trains keep flowing through the same platform, and ghost trips (unassigned future trips parked at origins) are filtered using the NYCT is_assigned protobuf extension. Version stall-v2.2-2026-08-11; knobs and their replay-derived rationale are in poller/subway/predictor.py.

Usage

python3 predict.py
from predict import load, score, calibrate

m = load()
raw = score(m, feats)          # compare against m["threshold"] (0.6339)
p   = calibrate(m, raw)        # empirical P(alert within 45 min) — display only

The two scales are not interchangeable. Fire on the raw score. Show a human the calibrated one. The raw score runs ~3× overconfident: the worked example in predict.py scores 0.82 raw, which is really a 21% chance an official alert follows. Do not threshold on the calibrated value — that curve is deliberately flat near the top.

Features

Twelve, all computed per (route, evaluation minute) against the learned baselines. A station is "flagged" when observed headway ≥ 2× baseline.

n_flag_now, n_stations_30m, max_ratio, med_ratio, frac_stations, growth, buckets_active_30m, arrivals_ratio, n_other_routes_anom, hour_sin, hour_cos, is_weekend — each documented inline in predict.py, with the column names they carry in the published delay_predictions table.

How it was trained, and why the metrics are believable

train_delay_model.py + extract_clusters.sql reproduce the fit. The protocol was deliberately strict, because this problem makes it very easy to fool yourself:

  • Time-split, never random. Train on buckets before 2026-07-30; holdout after. Random splits leak — adjacent 5-minute rows from one incident are nearly identical.
  • Threshold picked train-side only, on the Jul 16–29 tail, at ≤5 false alarms/day maximising episode recall. The holdout was then scored once, at that frozen threshold. The table above is that single evaluation as it landed.
  • Episode-level metrics, never per-row AUC. Rows within one incident are hugely autocorrelated; per-row AUC on this data is meaningless flattery. Episodes collapse per route on a 90-minute window.
  • Labels timestamped by our own first observation of an MTA alert, never the MTA's backdated active_period start — otherwise the model gets credit for "predicting" something already announced.
  • Suppression parity between training candidates and live behaviour: a route with a currently active alert does not fire, so it must not train as if it would.
  • A GBM benchmark was fit and rejected. In-sample recall 0.289 looked much better than the linear model; out-of-sample validation recall was 0.052. The simpler model shipped.

frozen_v2_holdout inside delay_model.json records the previous version scored on the identical holdout candidates, so the improvement is a like-for-like comparison rather than a moved goalpost.

Limitations

  • Recall is low by design of the problem, not by accident. Most disruptions never produce a legible headway signature in the public feed before they are announced.
  • NYC subway only, and tied to MTA GTFS-RT semantics plus the NYCT protobuf extensions. Transferring to another agency means refitting from scratch; the feature definitions may survive, the coefficients will not.
  • Baselines must be live. The coefficients are worthless without per-(route, station, dow, hour) headway baselines rebuilt from recent history. A snapshot ships in the companion dataset (baselines_headway).
  • The calibration is mildly optimistic. It was fit on the holdout window itself. Acceptable for display; do not treat it as an independent estimate.
  • ~5.75 false alarms/day. Fine for a screening dashboard where a human reads the explanation. Not fine for anything automated.
  • Weekend coverage is thin in the holdout: Aug 1–2 was 82–84% suppressed by active planned work, so the Aug 1 D/Q/4/6 disruption is out of scope for these numbers.
  • Not safety-certified, not affiliated with or endorsed by the MTA.

Files

file what it is
delay_model.json production model v2.7 — coefficients, threshold, calibration table, and the metrics above
predict.py dependency-free reference scorer, mirrors production
train_delay_model.py the fit, including the episode collapse and the rejected GBM benchmark
extract_clusters.sql builds training candidates from the 5-minute analytics panel

Companion dataset

digitalhen/nyc-subway-realtime — the data this was trained on, including delay_predictions (every evaluation this model has made, with nightly outcome labels) and baselines_headway. That table is directly usable as a benchmark: the task is to beat these numbers on a later window.

Citation

@misc{williams2026delayradar,
  author = {Williams, Henry},
  title  = {NYC Subway Delay Radar: predicting subway disruptions from realtime feeds},
  year   = {2026},
  url    = {https://huggingface.co/digitalhen/nyc-subway-delay-radar},
  note   = {Live at https://subway.fyi}
}

Licence

CC BY-NC 4.0 — use, share, and adapt it freely, with credit to Henry Williams / subway.fyi, for non-commercial purposes.

The upstream MTA GTFS-RT feeds are the MTA's public open data, under their own terms, and aren't claimed here. The model itself — coefficients, calibration table, learned baselines, training protocol, and code — is my work, licensed to you rather than signed over.

For commercial use, including fine-tuning or distilling it for commercial deployment, please get in touch first — derived weights carry the same limit.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support