Set-piece metrics, added-value scoring and mplsoccer pitch visualizations (penalties, kick-offs, free kicks, corners, throw-ins, goal kicks) from Opta F24 event data, including second phases, xT, zones and retention
Project description
wa-setpieces
Set-piece metrics for football (soccer) matches from Opta / Stats Perform F24 event-feed JSON exports: penalties, kick-offs, free kicks, corners, throw-ins and goal kicks.
Given a raw F24 match file, this package tags every set-piece restart, aggregates attempts/success rates by team and player, tracks pass end locations for delivery maps, and links each set piece to the shot or goal it produced (via Opta's assist-chain qualifier). It also covers, for corners and free kicks specifically: second-phase detection, Expected Threat (xT), pitch zones/thirds/channels, and possession retention — with pitch plots built on mplsoccer.
Full documentation, with a runnable plot gallery: https://waltzinganalytics.readthedocs.io
Install
pip install wa-setpieces
Not yet published to PyPI? Install from source instead:
git clone https://github.com/marclamberts/waltzinganalytics.git
cd waltzinganalytics
pip install -e .
Quickstart
from wa_setpieces import load_events, set_piece_summary
match = load_events("match.json")
summary = set_piece_summary(match.events)
print(summary)
contestantId set_piece_type attempts successful success_rate shots goals
cxb4hqite921i... corner 2 1 0.500 1 0
cxb4hqite921i... free_kick 12 9 0.750 0 0
cxb4hqite921i... goal_kick 8 5 0.625 0 0
cxb4hqite921i... kick_off 1 1 1.000 0 0
cxb4hqite921i... throw_in 20 16 0.800 1 0
...
Second phases, xT, zones, retention and added value
from wa_setpieces import (
second_phases, second_phase_summary, # corner/free-kick second-phase shots
retention_detail, retention_rate, # possession retained N seconds later
add_thirds, add_channels, add_zone_grid, # pitch location tagging
XTModel, set_piece_delivery_xt, set_piece_xt_summary, # Expected Threat
set_piece_added_value, set_piece_value_summary, # xT + shot quality + goals, blended
corner_report, free_kick_report, # all of the above, merged into one table per team
)
second_phases(match.events, "corner") # per-corner: cleared / first-phase shot / second-phase shot
second_phase_summary(match.events, "free_kick") # per-team roll-up
retention_rate(match.events, "corner") # per-team: % of corners where the ball is retained ~8s later
tagged = add_thirds(match.events) # defensive_third / middle_third / attacking_third
tagged = add_channels(tagged, n=5) # wide / half-space / central
model = XTModel.fit(match.events) # fit an xT grid (fit on many matches for real use!)
set_piece_xt_summary(match.events, "corner", model) # total/average xT added per team
set_piece_added_value(match.events, "corner", model) # per-delivery: xT added + resulting shot quality + goal
corner_report(match.events, model=model) # attempts, success/retention/second-phase rate, added value -- one table
All of the above are derived heuristics, not raw Opta fields — see
docs/source/advanced.rst (or the hosted docs) for the exact assumptions
and tunable thresholds behind each one. That page also documents a real bug
this uncovered and fixed: F24's eventId is only unique within one team's
own event stream, not globally — every delivery/shot lookup in this
package is scoped accordingly.
Plots
pip install -e ".[viz]" # matplotlib + mplsoccer
from wa_setpieces.viz import (
plot_delivery_map, # arrow map of deliveries, colored by outcome
plot_zone_heatmap, # where events happen, gridded onto the pitch
plot_xt_grid, # a fitted XTModel's grid, as a heatmap
plot_second_phase, # one corner/free-kick's phase sequence, numbered
plot_team_comparison, # grouped bars: both teams, every set-piece type
plot_xt_added_bars, # diverging bar chart of xT added per delivery
plot_corner_sonar, # polar plot of delivery angle + distance
plot_match_timeline, # every set piece on one shared match-minute axis
plot_dashboard, # one-figure report card combining several of the above
plot_set_piece_radar, # two-team radar over a corner_report/free_kick_report
)
plot_delivery_map(delivery_locations(match.events, "corner"), title="Corner deliveries")
plot_dashboard(match.events, team_id, set_piece_type="corner") # the "hero" figure
plot_set_piece_radar(corner_report(match.events, model=model)) # team A vs. team B, one glance
Every plotting function returns (fig, ax) (plot_dashboard returns just
fig, being multi-panel) for further customization. Colors are assigned by
the job they do — a validated categorical palette for team identity, a
status pair for success/fail, single-hue sequential ramps for magnitude,
and a diverging pair for signed quantities like xT added — not picked for
looks; see wa_setpieces/theme.py. See the
gallery
for all ten plots with full source code.
Command line
wa-setpieces match.json
wa-setpieces match.json --csv summary.csv
wa-setpieces match.json --xt # also fit + print xT for this match (illustrative on one match)
What counts as a set piece
| Type | Detected on | Opta qualifierId |
|---|---|---|
| Penalty | shot event (miss/post/saved/goal) | 9 |
| Kick-off | pass event | 279 |
| Free kick | pass event (corners excluded) | 5 |
| Corner | pass event | 6 |
| Throw-in | pass event | 107 |
| Goal kick | pass event | 124 |
These qualifier IDs are the standard Opta/Stats Perform F24 vocabulary and
were cross-checked against a real match export (see tests/data/sample_match.json
and tests/test_filters.py): tagged events line up with their expected pitch
location (corner arc, touchline, centre spot, six-yard line).
Package layout
wa_setpieces.loader— parse F24 JSON into a tidypandas.DataFrame;load_events_multistacks a whole season.wa_setpieces.constants— Opta typeId / qualifierId reference.wa_setpieces.filters— extract/tag each set-piece type.wa_setpieces.metrics— team/player counts, success rates, delivery locations.wa_setpieces.chains— link set pieces to the shots/goals they produced.wa_setpieces.zones— pitch thirds, channels and a configurable zone grid.wa_setpieces.phases— second-phase detection for corners/free kicks.wa_setpieces.retention— possession retention after any restart.wa_setpieces.xt— grid-based Expected Threat (xT), fit from data.wa_setpieces.value— set-piece added value: delivery xT + resulting shot quality + goals, blended.wa_setpieces.report—corner_report/free_kick_report: everything above, merged into one table per team.wa_setpieces.viz— mplsoccer/matplotlib plots: delivery maps, heatmaps, sonar, timeline, dashboard, radar (optionalvizextra).wa_setpieces.theme— the validated color palette every plot draws from.wa_setpieces.cli—wa-setpiecescommand-line tool.
Development
pip install -e ".[dev]"
pytest
Releasing
Publishing to PyPI is automated via GitHub Actions trusted publishing
(.github/workflows/publish.yml) — no API token stored anywhere. One-time
setup (only a PyPI project owner can do this, since it requires logging
into PyPI):
- On PyPI: https://pypi.org/manage/account/publishing/ → add a pending
trusted publisher with project name
wa-setpieces, ownermarclamberts, repositorywaltzinganalytics, workflowpublish.yml, environmentpypi. - From then on, publishing a GitHub Release (or pushing a
v*tag) builds the sdist/wheel and uploads them automatically.
Docs
Docs are built with Sphinx (pydata-sphinx-theme + sphinx-gallery, the
same stack mplsoccer's docs use) and hosted on Read the Docs
(.readthedocs.yaml at the repo root). The gallery under examples_gallery/
is executed at build time, so its plots and DataFrame outputs are always
current. To build locally:
pip install -e ".[docs]"
sphinx-build -b html docs/source docs/_build/html
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wa_setpieces-0.5.0.tar.gz.
File metadata
- Download URL: wa_setpieces-0.5.0.tar.gz
- Upload date:
- Size: 44.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82b7d41f02b449bdec57e167d8d169655c8c1ac56353d2b54ea6e7180863ef77
|
|
| MD5 |
6776e3c0bb9669b6e0daa8f54e6f9d64
|
|
| BLAKE2b-256 |
cd0b70b97047a06e4131e758382691614ef445bb8f36128faf2c32176bf75d11
|
Provenance
The following attestation bundles were made for wa_setpieces-0.5.0.tar.gz:
Publisher:
publish.yml on marclamberts/waltzinganalytics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wa_setpieces-0.5.0.tar.gz -
Subject digest:
82b7d41f02b449bdec57e167d8d169655c8c1ac56353d2b54ea6e7180863ef77 - Sigstore transparency entry: 2185543196
- Sigstore integration time:
-
Permalink:
marclamberts/waltzinganalytics@135a68fdebb0d6650eae5a2423f17e0ca6b8b9ad -
Branch / Tag:
refs/heads/main - Owner: https://github.com/marclamberts
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@135a68fdebb0d6650eae5a2423f17e0ca6b8b9ad -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file wa_setpieces-0.5.0-py3-none-any.whl.
File metadata
- Download URL: wa_setpieces-0.5.0-py3-none-any.whl
- Upload date:
- Size: 41.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5db5803deef082470bf539b04c5b10381605278801aba4915fbe382efbf84e5d
|
|
| MD5 |
9dde3b2051159d4cb436f4cb0011b46b
|
|
| BLAKE2b-256 |
35b754fd9abae4681725d8402427c20ec123c98ada98e1baf5df4335e8736714
|
Provenance
The following attestation bundles were made for wa_setpieces-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on marclamberts/waltzinganalytics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wa_setpieces-0.5.0-py3-none-any.whl -
Subject digest:
5db5803deef082470bf539b04c5b10381605278801aba4915fbe382efbf84e5d - Sigstore transparency entry: 2185543620
- Sigstore integration time:
-
Permalink:
marclamberts/waltzinganalytics@135a68fdebb0d6650eae5a2423f17e0ca6b8b9ad -
Branch / Tag:
refs/heads/main - Owner: https://github.com/marclamberts
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@135a68fdebb0d6650eae5a2423f17e0ca6b8b9ad -
Trigger Event:
workflow_dispatch
-
Statement type: