wa-setpieces 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. wa_setpieces-0.4.0/LICENSE +21 -0
  2. wa_setpieces-0.4.0/PKG-INFO +221 -0
  3. wa_setpieces-0.4.0/README.md +188 -0
  4. wa_setpieces-0.4.0/pyproject.toml +54 -0
  5. wa_setpieces-0.4.0/setup.cfg +4 -0
  6. wa_setpieces-0.4.0/src/wa_setpieces/__init__.py +76 -0
  7. wa_setpieces-0.4.0/src/wa_setpieces/chains.py +99 -0
  8. wa_setpieces-0.4.0/src/wa_setpieces/cli.py +85 -0
  9. wa_setpieces-0.4.0/src/wa_setpieces/constants.py +86 -0
  10. wa_setpieces-0.4.0/src/wa_setpieces/filters.py +116 -0
  11. wa_setpieces-0.4.0/src/wa_setpieces/loader.py +139 -0
  12. wa_setpieces-0.4.0/src/wa_setpieces/metrics.py +124 -0
  13. wa_setpieces-0.4.0/src/wa_setpieces/phases.py +241 -0
  14. wa_setpieces-0.4.0/src/wa_setpieces/retention.py +117 -0
  15. wa_setpieces-0.4.0/src/wa_setpieces/theme.py +99 -0
  16. wa_setpieces-0.4.0/src/wa_setpieces/viz.py +531 -0
  17. wa_setpieces-0.4.0/src/wa_setpieces/xt.py +220 -0
  18. wa_setpieces-0.4.0/src/wa_setpieces/zones.py +157 -0
  19. wa_setpieces-0.4.0/src/wa_setpieces.egg-info/PKG-INFO +221 -0
  20. wa_setpieces-0.4.0/src/wa_setpieces.egg-info/SOURCES.txt +32 -0
  21. wa_setpieces-0.4.0/src/wa_setpieces.egg-info/dependency_links.txt +1 -0
  22. wa_setpieces-0.4.0/src/wa_setpieces.egg-info/entry_points.txt +2 -0
  23. wa_setpieces-0.4.0/src/wa_setpieces.egg-info/requires.txt +17 -0
  24. wa_setpieces-0.4.0/src/wa_setpieces.egg-info/top_level.txt +1 -0
  25. wa_setpieces-0.4.0/tests/test_chains.py +33 -0
  26. wa_setpieces-0.4.0/tests/test_filters.py +74 -0
  27. wa_setpieces-0.4.0/tests/test_loader.py +59 -0
  28. wa_setpieces-0.4.0/tests/test_metrics.py +56 -0
  29. wa_setpieces-0.4.0/tests/test_phases.py +50 -0
  30. wa_setpieces-0.4.0/tests/test_retention.py +45 -0
  31. wa_setpieces-0.4.0/tests/test_theme.py +49 -0
  32. wa_setpieces-0.4.0/tests/test_viz.py +142 -0
  33. wa_setpieces-0.4.0/tests/test_xt.py +76 -0
  34. wa_setpieces-0.4.0/tests/test_zones.py +70 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Waltzing Analytics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: wa-setpieces
3
+ Version: 0.4.0
4
+ Summary: Set-piece metrics 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
5
+ Author: Waltzing Analytics
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/marclamberts/waltzinganalytics
8
+ Project-URL: Documentation, https://waltzinganalytics.readthedocs.io
9
+ Keywords: football,soccer,opta,set-pieces,sports-analytics
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
14
+ Classifier: Intended Audience :: Science/Research
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: pandas>=1.5
19
+ Requires-Dist: numpy>=1.23
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0; extra == "dev"
22
+ Provides-Extra: viz
23
+ Requires-Dist: matplotlib>=3.6; extra == "viz"
24
+ Requires-Dist: mplsoccer>=1.2; extra == "viz"
25
+ Provides-Extra: docs
26
+ Requires-Dist: sphinx>=7.0; extra == "docs"
27
+ Requires-Dist: pydata-sphinx-theme>=0.15; extra == "docs"
28
+ Requires-Dist: sphinx-gallery>=0.15; extra == "docs"
29
+ Requires-Dist: myst-parser; extra == "docs"
30
+ Requires-Dist: matplotlib>=3.6; extra == "docs"
31
+ Requires-Dist: mplsoccer>=1.2; extra == "docs"
32
+ Dynamic: license-file
33
+
34
+ # wa-setpieces
35
+
36
+ Set-piece metrics for football (soccer) matches from **Opta / Stats Perform F24**
37
+ event-feed JSON exports: penalties, kick-offs, free kicks, corners,
38
+ throw-ins and goal kicks.
39
+
40
+ Given a raw F24 match file, this package tags every set-piece restart,
41
+ aggregates attempts/success rates by team and player, tracks pass end
42
+ locations for delivery maps, and links each set piece to the shot or goal
43
+ it produced (via Opta's assist-chain qualifier). It also covers, for
44
+ corners and free kicks specifically: second-phase detection, Expected
45
+ Threat (xT), pitch zones/thirds/channels, and possession retention —
46
+ with pitch plots built on [mplsoccer](https://mplsoccer.readthedocs.io).
47
+
48
+ <p align="center">
49
+ <img src="docs/source/_static/hero_corners.png" alt="Corner delivery map drawn with mplsoccer" width="640">
50
+ </p>
51
+
52
+ **Full documentation, with a runnable plot gallery: https://waltzinganalytics.readthedocs.io**
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install wa-setpieces
58
+ ```
59
+
60
+ Not yet published to PyPI? Install from source instead:
61
+
62
+ ```bash
63
+ git clone https://github.com/marclamberts/waltzinganalytics.git
64
+ cd waltzinganalytics
65
+ pip install -e .
66
+ ```
67
+
68
+ ## Quickstart
69
+
70
+ ```python
71
+ from wa_setpieces import load_events, set_piece_summary
72
+
73
+ match = load_events("match.json")
74
+ summary = set_piece_summary(match.events)
75
+ print(summary)
76
+ ```
77
+
78
+ ```
79
+ contestantId set_piece_type attempts successful success_rate shots goals
80
+ cxb4hqite921i... corner 2 1 0.500 1 0
81
+ cxb4hqite921i... free_kick 12 9 0.750 0 0
82
+ cxb4hqite921i... goal_kick 8 5 0.625 0 0
83
+ cxb4hqite921i... kick_off 1 1 1.000 0 0
84
+ cxb4hqite921i... throw_in 20 16 0.800 1 0
85
+ ...
86
+ ```
87
+
88
+ ## Second phases, xT, zones and retention
89
+
90
+ ```python
91
+ from wa_setpieces import (
92
+ second_phases, second_phase_summary, # corner/free-kick second-phase shots
93
+ retention_detail, retention_rate, # possession retained N seconds later
94
+ add_thirds, add_channels, add_zone_grid, # pitch location tagging
95
+ XTModel, set_piece_delivery_xt, set_piece_xt_summary, # Expected Threat
96
+ )
97
+
98
+ second_phases(match.events, "corner") # per-corner: cleared / first-phase shot / second-phase shot
99
+ second_phase_summary(match.events, "free_kick") # per-team roll-up
100
+
101
+ retention_rate(match.events, "corner") # per-team: % of corners where the ball is retained ~8s later
102
+
103
+ tagged = add_thirds(match.events) # defensive_third / middle_third / attacking_third
104
+ tagged = add_channels(tagged, n=5) # wide / half-space / central
105
+
106
+ model = XTModel.fit(match.events) # fit an xT grid (fit on many matches for real use!)
107
+ set_piece_xt_summary(match.events, "corner", model) # total/average xT added per team
108
+ ```
109
+
110
+ All four are **derived heuristics**, not raw Opta fields — see
111
+ `docs/source/advanced.rst` (or the hosted docs) for the exact assumptions
112
+ and tunable thresholds behind each one.
113
+
114
+ ## Plots
115
+
116
+ ```bash
117
+ pip install -e ".[viz]" # matplotlib + mplsoccer
118
+ ```
119
+
120
+ ```python
121
+ from wa_setpieces.viz import (
122
+ plot_delivery_map, # arrow map of deliveries, colored by outcome
123
+ plot_zone_heatmap, # where events happen, gridded onto the pitch
124
+ plot_xt_grid, # a fitted XTModel's grid, as a heatmap
125
+ plot_second_phase, # one corner/free-kick's phase sequence, numbered
126
+ plot_team_comparison, # grouped bars: both teams, every set-piece type
127
+ plot_xt_added_bars, # diverging bar chart of xT added per delivery
128
+ plot_corner_sonar, # polar plot of delivery angle + distance
129
+ plot_match_timeline, # every set piece on one shared match-minute axis
130
+ plot_dashboard, # one-figure report card combining several of the above
131
+ )
132
+
133
+ plot_delivery_map(delivery_locations(match.events, "corner"), title="Corner deliveries")
134
+ plot_dashboard(match.events, team_id, set_piece_type="corner") # the "hero" figure
135
+ ```
136
+
137
+ Every plotting function returns `(fig, ax)` (`plot_dashboard` returns just
138
+ `fig`, being multi-panel) for further customization. Colors are assigned by
139
+ the job they do — a validated categorical palette for team identity, a
140
+ status pair for success/fail, single-hue sequential ramps for magnitude,
141
+ and a diverging pair for signed quantities like xT added — not picked for
142
+ looks; see `wa_setpieces/theme.py`. See the
143
+ [gallery](https://waltzinganalytics.readthedocs.io/en/latest/gallery/index.html)
144
+ for all nine plots with full source code.
145
+
146
+ ## Command line
147
+
148
+ ```bash
149
+ wa-setpieces match.json
150
+ wa-setpieces match.json --csv summary.csv
151
+ wa-setpieces match.json --xt # also fit + print xT for this match (illustrative on one match)
152
+ ```
153
+
154
+ ## What counts as a set piece
155
+
156
+ | Type | Detected on | Opta qualifierId |
157
+ |-------------|-------------------------------------------|-------------------|
158
+ | Penalty | shot event (miss/post/saved/goal) | 9 |
159
+ | Kick-off | pass event | 279 |
160
+ | Free kick | pass event (corners excluded) | 5 |
161
+ | Corner | pass event | 6 |
162
+ | Throw-in | pass event | 107 |
163
+ | Goal kick | pass event | 124 |
164
+
165
+ These qualifier IDs are the standard Opta/Stats Perform F24 vocabulary and
166
+ were cross-checked against a real match export (see `tests/data/sample_match.json`
167
+ and `tests/test_filters.py`): tagged events line up with their expected pitch
168
+ location (corner arc, touchline, centre spot, six-yard line).
169
+
170
+ ## Package layout
171
+
172
+ - `wa_setpieces.loader` — parse F24 JSON into a tidy `pandas.DataFrame`; `load_events_multi` stacks a whole season.
173
+ - `wa_setpieces.constants` — Opta typeId / qualifierId reference.
174
+ - `wa_setpieces.filters` — extract/tag each set-piece type.
175
+ - `wa_setpieces.metrics` — team/player counts, success rates, delivery locations.
176
+ - `wa_setpieces.chains` — link set pieces to the shots/goals they produced.
177
+ - `wa_setpieces.zones` — pitch thirds, channels and a configurable zone grid.
178
+ - `wa_setpieces.phases` — second-phase detection for corners/free kicks.
179
+ - `wa_setpieces.retention` — possession retention after any restart.
180
+ - `wa_setpieces.xt` — grid-based Expected Threat (xT), fit from data.
181
+ - `wa_setpieces.viz` — mplsoccer/matplotlib plots: delivery maps, heatmaps, sonar, timeline, dashboard (optional `viz` extra).
182
+ - `wa_setpieces.theme` — the validated color palette every plot draws from.
183
+ - `wa_setpieces.cli` — `wa-setpieces` command-line tool.
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ pip install -e ".[dev]"
189
+ pytest
190
+ ```
191
+
192
+ ## Releasing
193
+
194
+ Publishing to PyPI is automated via GitHub Actions trusted publishing
195
+ (`.github/workflows/publish.yml`) — no API token stored anywhere. **One-time
196
+ setup** (only a PyPI project owner can do this, since it requires logging
197
+ into PyPI):
198
+
199
+ 1. On PyPI: <https://pypi.org/manage/account/publishing/> → add a pending
200
+ trusted publisher with project name `wa-setpieces`, owner
201
+ `marclamberts`, repository `waltzinganalytics`, workflow `publish.yml`,
202
+ environment `pypi`.
203
+ 2. From then on, publishing a GitHub Release (or pushing a `v*` tag) builds
204
+ the sdist/wheel and uploads them automatically.
205
+
206
+ ## Docs
207
+
208
+ Docs are built with Sphinx (`pydata-sphinx-theme` + `sphinx-gallery`, the
209
+ same stack mplsoccer's docs use) and hosted on Read the Docs
210
+ (`.readthedocs.yaml` at the repo root). The gallery under `examples_gallery/`
211
+ is executed at build time, so its plots and DataFrame outputs are always
212
+ current. To build locally:
213
+
214
+ ```bash
215
+ pip install -e ".[docs]"
216
+ sphinx-build -b html docs/source docs/_build/html
217
+ ```
218
+
219
+ ## License
220
+
221
+ MIT
@@ -0,0 +1,188 @@
1
+ # wa-setpieces
2
+
3
+ Set-piece metrics for football (soccer) matches from **Opta / Stats Perform F24**
4
+ event-feed JSON exports: penalties, kick-offs, free kicks, corners,
5
+ throw-ins and goal kicks.
6
+
7
+ Given a raw F24 match file, this package tags every set-piece restart,
8
+ aggregates attempts/success rates by team and player, tracks pass end
9
+ locations for delivery maps, and links each set piece to the shot or goal
10
+ it produced (via Opta's assist-chain qualifier). It also covers, for
11
+ corners and free kicks specifically: second-phase detection, Expected
12
+ Threat (xT), pitch zones/thirds/channels, and possession retention —
13
+ with pitch plots built on [mplsoccer](https://mplsoccer.readthedocs.io).
14
+
15
+ <p align="center">
16
+ <img src="docs/source/_static/hero_corners.png" alt="Corner delivery map drawn with mplsoccer" width="640">
17
+ </p>
18
+
19
+ **Full documentation, with a runnable plot gallery: https://waltzinganalytics.readthedocs.io**
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install wa-setpieces
25
+ ```
26
+
27
+ Not yet published to PyPI? Install from source instead:
28
+
29
+ ```bash
30
+ git clone https://github.com/marclamberts/waltzinganalytics.git
31
+ cd waltzinganalytics
32
+ pip install -e .
33
+ ```
34
+
35
+ ## Quickstart
36
+
37
+ ```python
38
+ from wa_setpieces import load_events, set_piece_summary
39
+
40
+ match = load_events("match.json")
41
+ summary = set_piece_summary(match.events)
42
+ print(summary)
43
+ ```
44
+
45
+ ```
46
+ contestantId set_piece_type attempts successful success_rate shots goals
47
+ cxb4hqite921i... corner 2 1 0.500 1 0
48
+ cxb4hqite921i... free_kick 12 9 0.750 0 0
49
+ cxb4hqite921i... goal_kick 8 5 0.625 0 0
50
+ cxb4hqite921i... kick_off 1 1 1.000 0 0
51
+ cxb4hqite921i... throw_in 20 16 0.800 1 0
52
+ ...
53
+ ```
54
+
55
+ ## Second phases, xT, zones and retention
56
+
57
+ ```python
58
+ from wa_setpieces import (
59
+ second_phases, second_phase_summary, # corner/free-kick second-phase shots
60
+ retention_detail, retention_rate, # possession retained N seconds later
61
+ add_thirds, add_channels, add_zone_grid, # pitch location tagging
62
+ XTModel, set_piece_delivery_xt, set_piece_xt_summary, # Expected Threat
63
+ )
64
+
65
+ second_phases(match.events, "corner") # per-corner: cleared / first-phase shot / second-phase shot
66
+ second_phase_summary(match.events, "free_kick") # per-team roll-up
67
+
68
+ retention_rate(match.events, "corner") # per-team: % of corners where the ball is retained ~8s later
69
+
70
+ tagged = add_thirds(match.events) # defensive_third / middle_third / attacking_third
71
+ tagged = add_channels(tagged, n=5) # wide / half-space / central
72
+
73
+ model = XTModel.fit(match.events) # fit an xT grid (fit on many matches for real use!)
74
+ set_piece_xt_summary(match.events, "corner", model) # total/average xT added per team
75
+ ```
76
+
77
+ All four are **derived heuristics**, not raw Opta fields — see
78
+ `docs/source/advanced.rst` (or the hosted docs) for the exact assumptions
79
+ and tunable thresholds behind each one.
80
+
81
+ ## Plots
82
+
83
+ ```bash
84
+ pip install -e ".[viz]" # matplotlib + mplsoccer
85
+ ```
86
+
87
+ ```python
88
+ from wa_setpieces.viz import (
89
+ plot_delivery_map, # arrow map of deliveries, colored by outcome
90
+ plot_zone_heatmap, # where events happen, gridded onto the pitch
91
+ plot_xt_grid, # a fitted XTModel's grid, as a heatmap
92
+ plot_second_phase, # one corner/free-kick's phase sequence, numbered
93
+ plot_team_comparison, # grouped bars: both teams, every set-piece type
94
+ plot_xt_added_bars, # diverging bar chart of xT added per delivery
95
+ plot_corner_sonar, # polar plot of delivery angle + distance
96
+ plot_match_timeline, # every set piece on one shared match-minute axis
97
+ plot_dashboard, # one-figure report card combining several of the above
98
+ )
99
+
100
+ plot_delivery_map(delivery_locations(match.events, "corner"), title="Corner deliveries")
101
+ plot_dashboard(match.events, team_id, set_piece_type="corner") # the "hero" figure
102
+ ```
103
+
104
+ Every plotting function returns `(fig, ax)` (`plot_dashboard` returns just
105
+ `fig`, being multi-panel) for further customization. Colors are assigned by
106
+ the job they do — a validated categorical palette for team identity, a
107
+ status pair for success/fail, single-hue sequential ramps for magnitude,
108
+ and a diverging pair for signed quantities like xT added — not picked for
109
+ looks; see `wa_setpieces/theme.py`. See the
110
+ [gallery](https://waltzinganalytics.readthedocs.io/en/latest/gallery/index.html)
111
+ for all nine plots with full source code.
112
+
113
+ ## Command line
114
+
115
+ ```bash
116
+ wa-setpieces match.json
117
+ wa-setpieces match.json --csv summary.csv
118
+ wa-setpieces match.json --xt # also fit + print xT for this match (illustrative on one match)
119
+ ```
120
+
121
+ ## What counts as a set piece
122
+
123
+ | Type | Detected on | Opta qualifierId |
124
+ |-------------|-------------------------------------------|-------------------|
125
+ | Penalty | shot event (miss/post/saved/goal) | 9 |
126
+ | Kick-off | pass event | 279 |
127
+ | Free kick | pass event (corners excluded) | 5 |
128
+ | Corner | pass event | 6 |
129
+ | Throw-in | pass event | 107 |
130
+ | Goal kick | pass event | 124 |
131
+
132
+ These qualifier IDs are the standard Opta/Stats Perform F24 vocabulary and
133
+ were cross-checked against a real match export (see `tests/data/sample_match.json`
134
+ and `tests/test_filters.py`): tagged events line up with their expected pitch
135
+ location (corner arc, touchline, centre spot, six-yard line).
136
+
137
+ ## Package layout
138
+
139
+ - `wa_setpieces.loader` — parse F24 JSON into a tidy `pandas.DataFrame`; `load_events_multi` stacks a whole season.
140
+ - `wa_setpieces.constants` — Opta typeId / qualifierId reference.
141
+ - `wa_setpieces.filters` — extract/tag each set-piece type.
142
+ - `wa_setpieces.metrics` — team/player counts, success rates, delivery locations.
143
+ - `wa_setpieces.chains` — link set pieces to the shots/goals they produced.
144
+ - `wa_setpieces.zones` — pitch thirds, channels and a configurable zone grid.
145
+ - `wa_setpieces.phases` — second-phase detection for corners/free kicks.
146
+ - `wa_setpieces.retention` — possession retention after any restart.
147
+ - `wa_setpieces.xt` — grid-based Expected Threat (xT), fit from data.
148
+ - `wa_setpieces.viz` — mplsoccer/matplotlib plots: delivery maps, heatmaps, sonar, timeline, dashboard (optional `viz` extra).
149
+ - `wa_setpieces.theme` — the validated color palette every plot draws from.
150
+ - `wa_setpieces.cli` — `wa-setpieces` command-line tool.
151
+
152
+ ## Development
153
+
154
+ ```bash
155
+ pip install -e ".[dev]"
156
+ pytest
157
+ ```
158
+
159
+ ## Releasing
160
+
161
+ Publishing to PyPI is automated via GitHub Actions trusted publishing
162
+ (`.github/workflows/publish.yml`) — no API token stored anywhere. **One-time
163
+ setup** (only a PyPI project owner can do this, since it requires logging
164
+ into PyPI):
165
+
166
+ 1. On PyPI: <https://pypi.org/manage/account/publishing/> → add a pending
167
+ trusted publisher with project name `wa-setpieces`, owner
168
+ `marclamberts`, repository `waltzinganalytics`, workflow `publish.yml`,
169
+ environment `pypi`.
170
+ 2. From then on, publishing a GitHub Release (or pushing a `v*` tag) builds
171
+ the sdist/wheel and uploads them automatically.
172
+
173
+ ## Docs
174
+
175
+ Docs are built with Sphinx (`pydata-sphinx-theme` + `sphinx-gallery`, the
176
+ same stack mplsoccer's docs use) and hosted on Read the Docs
177
+ (`.readthedocs.yaml` at the repo root). The gallery under `examples_gallery/`
178
+ is executed at build time, so its plots and DataFrame outputs are always
179
+ current. To build locally:
180
+
181
+ ```bash
182
+ pip install -e ".[docs]"
183
+ sphinx-build -b html docs/source docs/_build/html
184
+ ```
185
+
186
+ ## License
187
+
188
+ MIT
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "wa-setpieces"
7
+ version = "0.4.0"
8
+ description = "Set-piece metrics 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"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Waltzing Analytics" }]
13
+ keywords = ["football", "soccer", "opta", "set-pieces", "sports-analytics"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Scientific/Engineering :: Information Analysis",
19
+ "Intended Audience :: Science/Research",
20
+ ]
21
+ dependencies = [
22
+ "pandas>=1.5",
23
+ "numpy>=1.23",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = [
28
+ "pytest>=7.0",
29
+ ]
30
+ viz = [
31
+ "matplotlib>=3.6",
32
+ "mplsoccer>=1.2",
33
+ ]
34
+ docs = [
35
+ "sphinx>=7.0",
36
+ "pydata-sphinx-theme>=0.15",
37
+ "sphinx-gallery>=0.15",
38
+ "myst-parser",
39
+ "matplotlib>=3.6",
40
+ "mplsoccer>=1.2",
41
+ ]
42
+
43
+ [project.scripts]
44
+ wa-setpieces = "wa_setpieces.cli:main"
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/marclamberts/waltzinganalytics"
48
+ Documentation = "https://waltzinganalytics.readthedocs.io"
49
+
50
+ [tool.setuptools.packages.find]
51
+ where = ["src"]
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,76 @@
1
+ """wa-setpieces: set-piece metrics (penalties, kick-offs, free kicks,
2
+ corners, throw-ins, goal kicks) from Opta / Stats Perform F24 event data.
3
+ """
4
+
5
+ from .chains import link_set_piece_shots, set_piece_goal_summary
6
+ from .filters import (
7
+ extract_all,
8
+ extract_corners,
9
+ extract_free_kicks,
10
+ extract_goal_kicks,
11
+ extract_kick_offs,
12
+ extract_penalties,
13
+ extract_throw_ins,
14
+ tag_set_pieces,
15
+ )
16
+ from .loader import Match, load_events, load_events_multi
17
+ from .metrics import (
18
+ delivery_locations,
19
+ player_set_piece_counts,
20
+ set_piece_summary,
21
+ team_set_piece_counts,
22
+ )
23
+ from .phases import (
24
+ PhaseResult,
25
+ classify_phase,
26
+ second_phase_summary,
27
+ second_phases,
28
+ )
29
+ from .retention import retention_detail, retention_rate
30
+ from .xt import XTModel, set_piece_delivery_xt, set_piece_xt_summary
31
+ from .zones import (
32
+ add_channels,
33
+ add_thirds,
34
+ add_zone_grid,
35
+ to_reference_frame,
36
+ zone_counts,
37
+ zone_id,
38
+ )
39
+
40
+ __version__ = "0.4.0"
41
+
42
+ __all__ = [
43
+ "Match",
44
+ "load_events",
45
+ "load_events_multi",
46
+ "extract_all",
47
+ "extract_corners",
48
+ "extract_free_kicks",
49
+ "extract_goal_kicks",
50
+ "extract_kick_offs",
51
+ "extract_penalties",
52
+ "extract_throw_ins",
53
+ "tag_set_pieces",
54
+ "link_set_piece_shots",
55
+ "set_piece_goal_summary",
56
+ "team_set_piece_counts",
57
+ "player_set_piece_counts",
58
+ "delivery_locations",
59
+ "set_piece_summary",
60
+ "PhaseResult",
61
+ "classify_phase",
62
+ "second_phases",
63
+ "second_phase_summary",
64
+ "retention_detail",
65
+ "retention_rate",
66
+ "XTModel",
67
+ "set_piece_delivery_xt",
68
+ "set_piece_xt_summary",
69
+ "add_thirds",
70
+ "add_channels",
71
+ "add_zone_grid",
72
+ "to_reference_frame",
73
+ "zone_id",
74
+ "zone_counts",
75
+ "__version__",
76
+ ]
@@ -0,0 +1,99 @@
1
+ """Link set pieces to the shots and goals they produce.
2
+
3
+ Opta connects a shot to the pass that created it via qualifier 55
4
+ ("related event id"), which stores the ``eventId`` of the assisting pass.
5
+ A shot can also *be* a set piece directly (e.g. a direct free kick or a
6
+ penalty), in which case there is no separate assisting pass to look up.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import pandas as pd
12
+
13
+ from . import constants as c
14
+ from .filters import tag_set_pieces
15
+
16
+
17
+ def link_set_piece_shots(events: pd.DataFrame) -> pd.DataFrame:
18
+ """Return one row per shot (incl. goals) with its set-piece origin, if any.
19
+
20
+ Columns:
21
+ eventId, typeId, outcome, is_goal, contestantId, playerId, playerName,
22
+ x, y, timeMin, timeSec, set_piece_type, set_piece_event_id.
23
+
24
+ ``set_piece_type`` is ``None`` for shots that did not originate from a
25
+ tagged set piece (open play, or a phase the assist qualifier does not
26
+ reach back to).
27
+ """
28
+ tagged = tag_set_pieces(events)
29
+ shots = tagged.loc[tagged["typeId"].isin(c.SHOT_TYPE_IDS)].copy()
30
+
31
+ by_event_id = tagged.set_index("eventId", drop=False)
32
+
33
+ set_piece_type: list[str | None] = []
34
+ set_piece_event_id: list[int | None] = []
35
+ for _, shot in shots.iterrows():
36
+ # Case 1: the shot itself is the set piece (direct free kick, penalty).
37
+ if shot["set_piece_type"] is not None:
38
+ set_piece_type.append(shot["set_piece_type"])
39
+ set_piece_event_id.append(shot["eventId"])
40
+ continue
41
+
42
+ # Case 2: walk back to the assisting pass via qualifier 55.
43
+ related_raw = shot.get(f"q_{c.QUALIFIER_RELATED_EVENT_ID}")
44
+ origin_type = None
45
+ origin_event_id = None
46
+ if related_raw is not None:
47
+ try:
48
+ related_id = int(float(related_raw))
49
+ except (TypeError, ValueError):
50
+ related_id = None
51
+ if related_id is not None and related_id in by_event_id.index:
52
+ origin = by_event_id.loc[related_id]
53
+ if isinstance(origin, pd.DataFrame): # duplicate eventId guard
54
+ origin = origin.iloc[0]
55
+ if origin["set_piece_type"] is not None:
56
+ origin_type = origin["set_piece_type"]
57
+ origin_event_id = int(related_id)
58
+ set_piece_type.append(origin_type)
59
+ set_piece_event_id.append(origin_event_id)
60
+
61
+ shots["is_goal"] = shots["typeId"] == c.TYPE_GOAL
62
+ shots["set_piece_type"] = set_piece_type
63
+ shots["set_piece_event_id"] = set_piece_event_id
64
+
65
+ cols = [
66
+ "eventId",
67
+ "typeId",
68
+ "outcome",
69
+ "is_goal",
70
+ "contestantId",
71
+ "playerId",
72
+ "playerName",
73
+ "x",
74
+ "y",
75
+ "timeMin",
76
+ "timeSec",
77
+ "set_piece_type",
78
+ "set_piece_event_id",
79
+ ]
80
+ return shots[cols].reset_index(drop=True)
81
+
82
+
83
+ def set_piece_goal_summary(events: pd.DataFrame) -> pd.DataFrame:
84
+ """Goals scored per set-piece type per team.
85
+
86
+ Returns a DataFrame indexed by ``(contestantId, set_piece_type)`` with a
87
+ ``goals`` count, restricted to set pieces that produced at least one goal.
88
+ """
89
+ linked = link_set_piece_shots(events)
90
+ goals = linked.loc[linked["is_goal"] & linked["set_piece_type"].notna()]
91
+ if goals.empty:
92
+ return pd.DataFrame(columns=["contestantId", "set_piece_type", "goals"])
93
+ summary = (
94
+ goals.groupby(["contestantId", "set_piece_type"])
95
+ .size()
96
+ .rename("goals")
97
+ .reset_index()
98
+ )
99
+ return summary