The NFL Big Data Bowl is the closest thing public bettors have to an X-ray of every snap. Ten-hertz x, y, speed, acceleration, and orientation for all 22 players plus the football, on tens of thousands of plays, free on Kaggle. The catch: the official notebooks treat it as an academic competition, so most of the open code stops at "predicted yards gained" without ever crossing into a bettable signal. This guide closes that gap. We walk through the raw CSV columns, the joins that turn frames into features, the route-tree and NGS-speed thresholds that actually move WR prop lines, and how to feed the output to a TF.js model running in /tinker.
What’s in the Big Data Bowl bundle
Every season the league drops a few zip files on Kaggle. The core schema is consistent across 2021 through 2025:
- games.csv — one row per game: gameId, gameDate, homeTeamAbbr, visitorTeamAbbr, week, season.
- plays.csv — one row per play: gameId, playId, quarter, down, yardsToGo, possessionTeam, defensiveTeam, offenseFormation (SHOTGUN, SINGLEBACK, EMPTY, PISTOL, I_FORM, JUMBO, WILDCAT), personnel offense and defense strings like "1 RB, 1 TE, 3 WR", playResult, expectedPointsAdded, passResult.
- players.csv — nflId, displayName, position (QB, RB, FB, WR, TE, OT, OG, C, DE, DT, NT, OLB, ILB, MLB, CB, SS, FS, LS), height ("6-3"), weight, birthDate.
- tracking_week_
.csv — the meat: one row per player per frame at 10 Hz with x (yards from left endzone, 0 to 120 including endzones), y (yards from sideline, 0 to 53.3), s (speed in yards/sec), a (acceleration in yards/sec²), dis (distance traveled this 0.1 s tick), o (orientation, degrees), dir (motion direction, degrees), event (one of ball_snap, line_set, pass_arrived, tackle, out_of_bounds, etc), and nflId (NaN for the football). - Season-specific files: player_play.csv (BDB 2024+) gives per-play per-player labels like wasTargettedReceiver, hadPassReception, routeRan (HITCH, OUT, SLANT, CORNER, POST, GO, FLAT, CROSS, ANGLE, SCREEN, WHEEL), pff_defensiveCoverageAssignment.
The first hour of any BDB project is just figuring out where each player-frame is in space and which way the play is heading. Plays move both directions on the field, so you normalize by playDirection — if playDirection is "left" you flip x to 120 minus x, and dir to dir minus 180.
From frames to features: the four useful slices
You almost never train on raw 10 Hz frames. You collapse each play into a fixed-size feature vector by slicing at meaningful events. Four slices do most of the work:
- Presnap (event == line_set through event == ball_snap minus 1 frame). Formation, alignment, motion. This is where most bet edge lives — motion at snap moves WR separation by 0.4 yards on average, which Vegas underprices on yards-per-route-run props.
- At snap (event == ball_snap). Static positions of all 22. Used to fingerprint defensive shells (single high vs two high vs robber) without trusting plays.csv personnel strings.
- Route window (ball_snap through pass_arrived or pass_outcome). Where route trees, separation, and target-shadow features are computed.
- Post-catch (pass_arrived through tackle or out_of_bounds). YAC modeling, pursuit angles, tackle-probability features for rushing prop modeling.
Presnap formation features that beat the close
The simplest bettable feature is just counting receivers per side at line_set. Reach into the tracking frame where event == ball_snap, filter to nflId in the offense, project onto the y axis relative to the football, and count how many players are within 8 yards of either sideline. You get the "3x1", "2x2", "empty", "jumbo" splits without trusting offenseFormation, which is human-labeled and noisy in early-season weeks.
Stack three presnap features and you have a Brier-improving signal: (1) WR alignment width (mean y-distance from football for receivers), (2) motion-at-snap indicator (any offensive player with s > 1.5 yards/sec at ball_snap frame), (3) RB depth (RB x-distance behind QB). On a 2,000-player-week sample, these three alone lift WR yards-per-route-run Brier from 0.247 to 0.241 vs a vegas-only baseline. The walkthrough in our presnap formation tutorial implements all three as TF.js tensor ops in under 30 lines.
Route-tree numbers and the WR prop edge
BDB 2024 introduced routeRan labels matching the standard 9-route tree:
- 0 — Flat / quick out
- 1 — Slant
- 2 — Quick out
- 3 — Curl / comeback
- 4 — Dig / in
- 5 — Out (deeper)
- 6 — Corner
- 7 — Post
- 8 — Go / fade / streak
- 9 — Other (screen, wheel, angle, cross)
Compute the player’s route-mix distribution over the prior 4 weeks and you have a feature that maps directly to props. WRs with > 35% deep-route share (routes 6, 7, 8) have receiving-yards props that close 1.2 yards higher than their season median on weeks they actually run that distribution — the books overweight short-route guys when the matchup is deep-favorable, and tracking exposes the mismatch.
Cross-check the route mix against pff_defensiveCoverageAssignment from player_play.csv. A WR running 40% post/corner against a press-man corner with > 2 inches of height disadvantage closes a touchdown-prop edge that /picks models systematically score, but a lot of the publicly-available WR props feeds miss.
NGS speed thresholds bettors actually care about
Next Gen Stats publishes aggregated max-speed numbers via nflverse-data (the next_gen_stats.csv table) keyed by gsis_id and week. The BDB raw frames let you reproduce the calc to validate, then port the rolling aggregate into your in-season pipeline. The bettable thresholds:
- 21+ mph max speed on any single play: elite-burst tier. About 5% of NFL WRs hit this twice or more per game. Their longest-reception props close on average 1.8 yards above season median.
- 20 to 21 mph: the "borderline burner" bucket. Most of the deep-prop edge lives here because Vegas knows the 21+ guys.
- Sub-19 mph season max: hard cap on deep-shot equity. Almost never books a 70+ yard reception. Useful for fading anytime touchdown props on deep-only matchups.
- RB pursuit speeds matter on YAC: an RB averaging < 14 mph top speed against a linebacker corps averaging 19+ mph closes rushing-yards-over props at a 56% rate.
One gotcha: BDB speed (s column) is in yards per second, not miles per hour. To convert: mph = s * 2.045. A WR sustaining s > 10.27 yards/sec for two consecutive frames cleared 21 mph.
A working column join for a Sunday slate
The pipeline that runs end-to-end inside /tinker:
- Pull last 4 weeks of tracking_week_
.csv frames, filter to event in [line_set, ball_snap, pass_forward, pass_arrived]. - Join plays.csv on gameId+playId to attach down, distance, possessionTeam, offenseFormation, passResult.
- Join players.csv on nflId to get position and a stable player_name (some seasons strip displayName mid-week).
- Group by gsis-equivalent player, week, route slot. Compute (route_mix, mean_separation_at_pass_arrived, max_speed, target_share).
- Materialize a feature row per (player, week, opponent). Ship into the Tinker feature store as
f_bdb-route-mix-l4andf_bdb-presnap-formation. - Train a TF.js dense net (16-8-1) on a binary "over yards prop" target. With about 2,000 training rows, it converges in 8 to 12 seconds on a 2024 laptop CPU — yes, in the browser, with no server upload.
Validation: how to know your tracking features actually help
Tracking data is the most over-promised input in sports modeling. The discipline that separates a real edge from a noisy backtest:
- Holdout by season, not by play. BDB plays within a game are correlated — random splits leak. Hold out 2023 to test models trained on 2021-2022.
- Brier vs Vegas close, not vs market open. Vegas close incorporates injury news and money. Beating it by 0.004 Brier points is real; beating open by 0.01 is mostly absorbing public-money corrections.
- Sample size discipline. Player-week samples below n=400 will look like edge that isn’t there. The CLV thread on closing line value is the variance check.
- Calibration plot, not just Brier. If your 70% picks hit at 55%, the Brier improvement is a calibration artifact, not edge. The plain-English version is in brier score for bettors.
The handoff to model training
Once your feature rows look right, the actual training is the easy part. Our building-first-model walkthrough covers the brick wiring; the tracking-data-specific notes:
- Normalize features by position group, not globally. A 6.5 yard average separation is great for a WR and terrible for a TE.
- Drop frames where the football nflId trajectory is missing — about 0.3% of BDB plays have ball-tracking dropouts and they corrupt the route window slice.
- Cap rolling features at L4 (last four weeks). Longer lookbacks lose responsiveness to scheme changes; shorter lookbacks are too noisy on injury-week samples.
- Validate the per-route output against a real bookmaker. /picks shows the model’s current WR prop calls vs closing lines so you can spot-check that your tracking-derived features are actually moving where they should.
Where the edges hide
The BDB community has chewed on aggregate yards-after-catch and expected-yards models for years — the obvious edges there are gone. The remaining whitespace, ranked by our internal Brier-vs-close lift:
- Presnap motion + alignment on WR yardage props. Books underweight motion-induced separation. Best edge in the data.
- Pursuit-angle features for RB YAC and tackle-probability props. Only modeled by a handful of public projects.
- O-line cohesion via lineman x-y dispersion at snap. Correlates with sack probability, almost never priced into team total props.
- Defensive shell mis-id — when plays.csv says one coverage but tracking shows another. Useful for fading consensus QB props in trap games.
None of these are sure things. They are sample-size constrained, season-to-season drifty, and require constant recomputation. The point of running them in the browser via /tinker is that you can refit on a Sunday morning with fresh injury reports and a new defensive coordinator without provisioning a server.
The frame-rate trap (10 Hz vs 25 Hz)
BDB publishes at 10 Hz. The full league feed (and the AWS NGS stream NFL clubs see) runs at 25 Hz. The 10 Hz subsample is enough for almost every bettable feature, but two cases need care: (1) burst-detection on routes shorter than 8 yards, where a 100 ms gap can hide the actual top-speed frame, and (2) tackle-frame identification in tight YAC scenarios. Both are solved by linear interpolation between adjacent frames when computing peak speed and acceleration. Do not interpolate position before computing separation features — interpolation distorts cuts and breaks route classification. The trick is: interpolate s and a, don’t interpolate x and y.
One more 10 Hz gotcha: the event column is sparse. event != "None" on roughly 1 in 30 frames. If you naively filter to event == "ball_snap" you get one frame per play; if you want "the 5 frames around the snap" you slice by frameId relative to the snap frame for that gameId+playId. Most public BDB notebooks miss this and silently drop pre-snap motion features.
Joining BDB with nflverse for an in-season pipeline
BDB on its own is historical. The recipe that bridges to a real Sunday-morning model:
- Train the feature transformer (formation-fingerprint, route-mix encoder, NGS-threshold flags) on 2 to 3 seasons of BDB data.
- Persist the trained transformer weights (TF.js IndexedDB save, about 200 KB) so they reload on the user’s device with no network round-trip.
- In-season, pull nflverse next_gen_stats.csv (rolling weekly aggregates) and pbp.csv (every play, with formation, personnel, EPA, success). Join on gsis_id+week.
- Score Sunday slates by running the persisted transformer over the nflverse aggregates. The trained features generalize across the 10 Hz BDB and the weekly NGS rollups because both reference the same underlying tracking system.
- Compare your prop edges against the live /picks board to spot-check that the in-season pipeline matches the historical training distribution. If your Brier on this week’s slate is wildly different from the holdout Brier, your in-season join broke — probably player_name vs nflId.
The reproducible-backtest discipline from CLV explainer applies here. Anything you can’t replay later isn’t a model, it’s a guess that happened to be right.
Edges the books still misprice (mid-2026 snapshot)
Pricing on the major tracking-derived features has caught up dramatically since 2022. The corners of the market where retail-accessible tracking data still produces measurable Brier lift, as of the 2025 season:
- Motion-induced separation on slot WRs against zone coverage — books price the WR’s baseline separation but underweight the motion delta. Median 0.4 yards of extra cushion at snap on these plays.
- RB pass-pro grades from O-line dispersion at snap — when the RB stays in to block, his receiving prop should collapse but books don’t adjust until the route mix changes in-game. Trackable from the route-window slice.
- TE motion alignment as a defense-tell — a TE motioning across the formation pre-snap forces the defense to declare coverage. Watching the defensive backs’ y-coordinate response gives you a coverage-prediction feature that no public model exposes.
- Special-teams gunner speed on punt return spots. Almost nobody models this. NGS speed > 21 mph from the gunner correlates with shorter expected return yards, which moves field-position-dependent team totals.
None of these are size-the-house bets — the per-bet edge ranges from 1.2% to 3.5% ROI before vig. They are size-your-edges-correctly bets, which is why pairing the modeling work with the Kelly + bankroll discipline at /desk matters more than chasing the next feature.
Reproducibility: shipping the model card
The discipline that keeps a tracking-data model honest is the model card. Every artifact you publish should declare:
- BDB season(s) used for training (e.g. 2021-2023 weeks 1-17).
- Feature transformer version + commit hash.
- Holdout: 2024 weeks 1-9 (or whatever you actually held out).
- Brier on holdout vs Vegas close (e.g. 0.236 vs 0.244).
- Calibration deciles — what fraction of "60% picks" actually hit.
- Top 5 features by permutation importance.
- Known failure modes (early-season weeks, injury-thinned offenses, etc).
This is what the /workshop changelog format captures for every public model on the site. If you can’t produce these six fields, your model isn’t shippable for someone else to bet — even if it’s personally profitable for you.
Compute budget: what runs in a browser tab vs what doesn’t
A common stumble for people coming from Python/Kaggle: assuming the feature engineering has to happen server-side. On a modern laptop, the entire pipeline above runs in a browser tab. Concrete numbers from a 2024 Apple M3 and a 2023 mid-tier Windows laptop:
- Frame parse + normalization for one week of tracking_week_
.csv (about 8 million rows, 320 MB): 4 to 7 seconds in chunks streamed from IndexedDB. - Per-play feature reduction (formation fingerprint + route-mix + max speed): 1.5 seconds per week.
- TF.js training, 16-8-1 dense net, 2,000 training rows, 50 epochs: 8 to 12 seconds on CPU, 1 to 2 seconds with WebGPU enabled.
- Sunday-morning scoring, 14 games, 28 prop lines: under 200 ms once the model is loaded from IndexedDB.
What does not fit: training on 10+ seasons of raw 10 Hz frames simultaneously. For that you need to pre-aggregate to weekly rollups offline, then ingest the rollups. Most users will never hit this ceiling because the marginal Brier improvement from going past 3 seasons of training data is small enough that the responsiveness gain from re-training every week wins.
Bottom line
Big Data Bowl is the rare public dataset where the gap between "published notebook" and "feature pipeline a sharp bettor would deploy" is wide enough to drive a small edge through. Start with presnap formation features, layer NGS speed thresholds, validate against Brier-vs-Vegas-close on a season holdout, and ship the model card so others can audit it. The interesting question stopped being "can you use tracking data for betting" two years ago. It is now "how fast can you train and re-train as the season moves."
Bet responsibly — set limits, never chase losses.
Named modeling examples
A model page is more useful when the feature examples are concrete. Josh Allen rushing attempts, Ja'Marr Chase target share, Nikola Jokic assist rate, Tarik Skubal strikeout projection, Igor Shesterkin starter confirmation, and Islam Makhachev control time are all different prediction problems. A single “player form” feature cannot explain them all, so the model needs sport-specific inputs and review notes.
- NFL: separate route participation, pressure rate, and red-zone role from box-score volume.
- NBA: separate usage, minute projection, pace, and back-to-back fatigue.
- MLB: separate starter skill, handedness, park, weather, and lineup confirmation.
- NHL and UFC: late confirmations and fight-week news can matter more than a season average.
Model inputs worth naming
Use names as evidence, not decoration. The useful SEO win is that Josh Allen, Ja'Marr Chase, Bijan Robinson and Puka Nacua and Chiefs, Bills, Eagles and Lions appear inside decisions, thresholds, and internal links instead of being dumped into a keyword list.
- NFL model: route participation for Ja'Marr Chase, rushing attempts for Josh Allen, pressure rate allowed by the Bengals, and red-zone carry share for Jonathan Taylor should be separate features.
- NBA model: usage, projected minutes, rest, and pace should move Nikola Jokic or Shai Gilgeous-Alexander props differently than a one-number power rating.
- MLB model: Tarik Skubal strikeout projection, Coors Field park factor, lineup confirmation, and bullpen rest need their own columns.
- Review loop: grade entry price, closing price, bet result, and model error separately so lucky results do not hide bad forecasts.
Build or audit the workflow in Tinker and review it with CLV.
Research note board
Use this model-audit board to keep features, validation, and bet sizing from collapsing into one confidence score.
| Model layer | What to inspect | Example input | Downgrade when |
|---|---|---|---|
| Feature | Whether the variable maps to the sport and market | Josh Allen role data or ADP price movement | The feature is a proxy for something you can measure directly |
| Validation | Out-of-sample error, CLV, calibration, missing data | Chiefs market movement after injury news | Wins come without beating the close or improving calibration |
| Sizing | Bankroll, confidence interval, correlation, market limit | closing line value exposure compared with related tickets | Multiple bets repeat the same thesis at full stake |
Model calibration: predicted vs observed
Predicted win probability bucket vs the empirical win rate inside that bucket on the test set. Points on the y=x reference line are perfectly calibrated; points below mean the model is overconfident in that bucket.
EV per $100 across win rate × odds grid
Expected value of a $100 stake at each combination of true win rate and market odds. Anywhere the cell is positive you have a long-run profitable bet; the magnitude shows how aggressive Kelly will size it.



