Metadata-Version: 2.4
Name: wa-setpieces
Version: 0.5.0
Summary: 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
Author: Waltzing Analytics
License: MIT
Project-URL: Homepage, https://github.com/marclamberts/waltzinganalytics
Project-URL: Documentation, https://waltzinganalytics.readthedocs.io
Keywords: football,soccer,opta,set-pieces,sports-analytics
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Intended Audience :: Science/Research
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas>=1.5
Requires-Dist: numpy>=1.23
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Provides-Extra: viz
Requires-Dist: matplotlib>=3.6; extra == "viz"
Requires-Dist: mplsoccer>=1.2; extra == "viz"
Provides-Extra: docs
Requires-Dist: sphinx>=7.0; extra == "docs"
Requires-Dist: pydata-sphinx-theme>=0.15; extra == "docs"
Requires-Dist: sphinx-gallery>=0.15; extra == "docs"
Requires-Dist: myst-parser; extra == "docs"
Requires-Dist: matplotlib>=3.6; extra == "docs"
Requires-Dist: mplsoccer>=1.2; extra == "docs"
Dynamic: license-file

# 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](https://mplsoccer.readthedocs.io).

<p align="center">
  <img src="docs/source/_static/hero_corners.png" alt="Corner delivery map drawn with mplsoccer" width="640">
</p>

**Full documentation, with a runnable plot gallery: https://waltzinganalytics.readthedocs.io**

## Install

```bash
pip install wa-setpieces
```

Not yet published to PyPI? Install from source instead:

```bash
git clone https://github.com/marclamberts/waltzinganalytics.git
cd waltzinganalytics
pip install -e .
```

## Quickstart

```python
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

```python
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

```bash
pip install -e ".[viz]"   # matplotlib + mplsoccer
```

```python
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](https://waltzinganalytics.readthedocs.io/en/latest/gallery/index.html)
for all ten plots with full source code.

## Command line

```bash
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 tidy `pandas.DataFrame`; `load_events_multi` stacks 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 (optional `viz` extra).
- `wa_setpieces.theme` — the validated color palette every plot draws from.
- `wa_setpieces.cli` — `wa-setpieces` command-line tool.

## Development

```bash
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):

1. On PyPI: <https://pypi.org/manage/account/publishing/> → add a pending
   trusted publisher with project name `wa-setpieces`, owner
   `marclamberts`, repository `waltzinganalytics`, workflow `publish.yml`,
   environment `pypi`.
2. 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:

```bash
pip install -e ".[docs]"
sphinx-build -b html docs/source docs/_build/html
```

## License

MIT
