Skip to main content

StatsBomb 360 Football Matches — Flights pipeline + animated Dive

Build this flight plan with your AI assistant

I want a multi-stage Flight pipeline (raw to core to marts, transformed with in-warehouse SQL) paired with a bespoke D3 Dive that renders the data live, all deployed as code on MotherDuck. Help me adapt the "StatsBomb 360 Football Matches — Flights pipeline + animated Dive" recipe to my own data and use case, using it as a guide: https://motherduck.com/docs/cookbook/statsbomb-360-football-matches

A football-analytics stack built entirely on MotherDuck. Three Flights turn StatsBomb open-data — the event stream plus 360 freeze-frame player tracking — into a clean statsbomb database, and a Dive renders it: an animated match replay (players, ball, passes, a scrubbable timeline) and a Passes & Shots explorer (per-team shot maps and per-player flight rows). Everything reads and writes the one statsbomb database.

Animated StatsBomb 360 match replay

Two decoupled slices:

SliceWhat it is
flight/A three-stage Python/SQL pipeline run as MotherDuck Flights: statsbomb-raw-load (download open-data into raw.*), statsbomb-core-transform (de-normalize coordinates and resolve player tracking into core.*, all in-warehouse SQL), and statsbomb-marts (analysis-ready marts.* tables the Dive reads).
dive/A single-file MotherDuck Dive — an animated replay and a Passes & Shots explorer, built with D3 and deployed as code. Reads what flight/ writes.

The Flights keep the statsbomb database current; the Dive queries it live. Each slice's README covers its own development and deploy details.

Try it without building

The statsbomb database is also published as a public, read-only MotherDuck share, so you can explore the prebuilt raw / core / marts schemas — or point the Dive at them — without running the ingest Flights yourself. Attach it directly:

ATTACH 'md:_share/statsbomb/80f66346-f45d-47c1-8b17-9fef083ba22b' AS statsbomb (READ_ONLY);
FROM statsbomb.marts.match_stats LIMIT 10;

To deploy your own copy of the Dive against the share instead of a database you built, pass the share URL as the resource:

SB_RESOURCE_URL='md:_share/statsbomb/80f66346-f45d-47c1-8b17-9fef083ba22b' \
./dive/scripts/deploy-dive.sh

Anyone with the share URL in the same cloud region (aws-us-east-1) can attach it.

How it works

  • flight/README.md — the raw -> core -> marts pipeline, the StatsBomb data quirks it corrects (e.g. possession-normalized coordinates), registering and running the Flights, and the knobs.
  • dive/README.md — the replay + Passes & Shots Dive, running it locally, deploying it, and why the bespoke visuals run D3 inside React.

Questions to answer

  • Build it yourself, or just use the public share? To explore the data or demo the Dive, attach the prebuilt share above — no pipeline run needed. Run the Flights when you want your own copy or fresher data.
  • How much data? The full open-data set is large, so statsbomb-raw-load takes COMPETITION_IDS / MATCH_LIMIT to start with one competition (e.g. the 2022 World Cup) or a handful of matches.

What you'll adjust

KnobWherePurpose
Target databasestatsbomb literal in each flight/flights/*/main.py and the Dive's statsbomb resource aliasThe one database the whole stack reads/writes.
COMPETITION_IDS, MATCH_LIMITper-run config on statsbomb-raw-loadLimit ingest scope (one competition, or a smoke-test cap).
MATCH_IDSper-run config on statsbomb-core-transformRebuild a single match instead of the whole corpus.
DIVE_TITLE, SB_DATABASEenv for dive/scripts/deploy-dive.shDive title to create/update and the database its statsbomb alias binds to.

Learn more