silly-kicks 1.0.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 (71) hide show
  1. silly_kicks-1.0.0/.github/CODEOWNERS +1 -0
  2. silly_kicks-1.0.0/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
  3. silly_kicks-1.0.0/.github/ISSUE_TEMPLATE/feature_request.md +18 -0
  4. silly_kicks-1.0.0/.github/dependabot.yml +13 -0
  5. silly_kicks-1.0.0/.github/pull_request_template.md +17 -0
  6. silly_kicks-1.0.0/.github/workflows/ci.yml +42 -0
  7. silly_kicks-1.0.0/.github/workflows/publish.yml +35 -0
  8. silly_kicks-1.0.0/.gitignore +42 -0
  9. silly_kicks-1.0.0/CHANGELOG.md +73 -0
  10. silly_kicks-1.0.0/CLAUDE.md +36 -0
  11. silly_kicks-1.0.0/CODE_OF_CONDUCT.md +41 -0
  12. silly_kicks-1.0.0/CONTRIBUTING.md +46 -0
  13. silly_kicks-1.0.0/LICENSE +22 -0
  14. silly_kicks-1.0.0/PKG-INFO +181 -0
  15. silly_kicks-1.0.0/README.md +141 -0
  16. silly_kicks-1.0.0/SECURITY.md +27 -0
  17. silly_kicks-1.0.0/TODO.md +18 -0
  18. silly_kicks-1.0.0/assets/silly-kicks.jpg +0 -0
  19. silly_kicks-1.0.0/docs/DEFERRED.md +126 -0
  20. silly_kicks-1.0.0/docs/c4/architecture.dsl +89 -0
  21. silly_kicks-1.0.0/docs/c4/architecture.html +317 -0
  22. silly_kicks-1.0.0/pyproject.toml +87 -0
  23. silly_kicks-1.0.0/silly_kicks/__init__.py +9 -0
  24. silly_kicks-1.0.0/silly_kicks/atomic/__init__.py +5 -0
  25. silly_kicks-1.0.0/silly_kicks/atomic/spadl/__init__.py +15 -0
  26. silly_kicks-1.0.0/silly_kicks/atomic/spadl/base.py +238 -0
  27. silly_kicks-1.0.0/silly_kicks/atomic/spadl/config.py +57 -0
  28. silly_kicks-1.0.0/silly_kicks/atomic/spadl/schema.py +22 -0
  29. silly_kicks-1.0.0/silly_kicks/atomic/spadl/utils.py +60 -0
  30. silly_kicks-1.0.0/silly_kicks/atomic/vaep/__init__.py +6 -0
  31. silly_kicks-1.0.0/silly_kicks/atomic/vaep/base.py +82 -0
  32. silly_kicks-1.0.0/silly_kicks/atomic/vaep/features.py +272 -0
  33. silly_kicks-1.0.0/silly_kicks/atomic/vaep/formula.py +133 -0
  34. silly_kicks-1.0.0/silly_kicks/atomic/vaep/labels.py +186 -0
  35. silly_kicks-1.0.0/silly_kicks/spadl/__init__.py +27 -0
  36. silly_kicks-1.0.0/silly_kicks/spadl/_wyscout_events.py +573 -0
  37. silly_kicks-1.0.0/silly_kicks/spadl/_wyscout_mappings.py +300 -0
  38. silly_kicks-1.0.0/silly_kicks/spadl/base.py +81 -0
  39. silly_kicks-1.0.0/silly_kicks/spadl/config.py +105 -0
  40. silly_kicks-1.0.0/silly_kicks/spadl/kloppy.py +485 -0
  41. silly_kicks-1.0.0/silly_kicks/spadl/opta.py +516 -0
  42. silly_kicks-1.0.0/silly_kicks/spadl/schema.py +85 -0
  43. silly_kicks-1.0.0/silly_kicks/spadl/statsbomb.py +617 -0
  44. silly_kicks-1.0.0/silly_kicks/spadl/utils.py +166 -0
  45. silly_kicks-1.0.0/silly_kicks/spadl/wyscout.py +333 -0
  46. silly_kicks-1.0.0/silly_kicks/vaep/__init__.py +7 -0
  47. silly_kicks-1.0.0/silly_kicks/vaep/base.py +290 -0
  48. silly_kicks-1.0.0/silly_kicks/vaep/features.py +931 -0
  49. silly_kicks-1.0.0/silly_kicks/vaep/formula.py +145 -0
  50. silly_kicks-1.0.0/silly_kicks/vaep/hybrid.py +62 -0
  51. silly_kicks-1.0.0/silly_kicks/vaep/labels.py +184 -0
  52. silly_kicks-1.0.0/silly_kicks/vaep/learners.py +173 -0
  53. silly_kicks-1.0.0/silly_kicks/xthreat.py +416 -0
  54. silly_kicks-1.0.0/tests/atomic/test_atomic_features.py +53 -0
  55. silly_kicks-1.0.0/tests/atomic/test_atomic_labels.py +18 -0
  56. silly_kicks-1.0.0/tests/atomic/test_atomic_spadl.py +30 -0
  57. silly_kicks-1.0.0/tests/atomic/test_atomic_vaep.py +52 -0
  58. silly_kicks-1.0.0/tests/conftest.py +33 -0
  59. silly_kicks-1.0.0/tests/datasets/spadl/atomic_spadl.json +1 -0
  60. silly_kicks-1.0.0/tests/datasets/spadl/spadl.json +1 -0
  61. silly_kicks-1.0.0/tests/spadl/test_kloppy.py +19 -0
  62. silly_kicks-1.0.0/tests/spadl/test_opta.py +182 -0
  63. silly_kicks-1.0.0/tests/spadl/test_output_contract.py +147 -0
  64. silly_kicks-1.0.0/tests/spadl/test_schema.py +99 -0
  65. silly_kicks-1.0.0/tests/spadl/test_statsbomb.py +189 -0
  66. silly_kicks-1.0.0/tests/spadl/test_wyscout.py +384 -0
  67. silly_kicks-1.0.0/tests/test_benchmark.py +45 -0
  68. silly_kicks-1.0.0/tests/test_xthreat.py +235 -0
  69. silly_kicks-1.0.0/tests/vaep/test_features.py +213 -0
  70. silly_kicks-1.0.0/tests/vaep/test_labels.py +81 -0
  71. silly_kicks-1.0.0/tests/vaep/test_vaep.py +89 -0
@@ -0,0 +1 @@
1
+ * @karsten-s-nielsen
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: Bug Report
3
+ about: Report a bug in silly-kicks
4
+ title: "[Bug] "
5
+ labels: bug
6
+ ---
7
+
8
+ **Describe the bug**
9
+ A clear description of what the bug is.
10
+
11
+ **To reproduce**
12
+ ```python
13
+ # Minimal code to reproduce
14
+ ```
15
+
16
+ **Expected behavior**
17
+ What you expected to happen.
18
+
19
+ **Environment**
20
+ - silly-kicks version:
21
+ - Python version:
22
+ - pandas version:
23
+ - OS:
24
+
25
+ **Additional context**
26
+ Any other context (stack trace, data sample, etc.)
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: Feature Request
3
+ about: Suggest a feature for silly-kicks
4
+ title: "[Feature] "
5
+ labels: enhancement
6
+ ---
7
+
8
+ **Is this related to a problem?**
9
+ A clear description of the problem. E.g., "I'm frustrated when..."
10
+
11
+ **Proposed solution**
12
+ Describe the solution you'd like.
13
+
14
+ **Alternatives considered**
15
+ Any alternative solutions or features you've considered.
16
+
17
+ **Additional context**
18
+ Any other context (links to papers, data examples, etc.)
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ open-pull-requests-limit: 5
8
+
9
+ - package-ecosystem: "github-actions"
10
+ directory: "/"
11
+ schedule:
12
+ interval: "weekly"
13
+ open-pull-requests-limit: 5
@@ -0,0 +1,17 @@
1
+ ## Summary
2
+
3
+ Brief description of what this PR does.
4
+
5
+ ## Changes
6
+
7
+ -
8
+
9
+ ## Testing
10
+
11
+ - [ ] Tests pass (`pytest tests/ -m "not e2e"`)
12
+ - [ ] Lint clean (`ruff check silly_kicks/ tests/`)
13
+ - [ ] Types clean (`pyright silly_kicks/`)
14
+
15
+ ## Related issues
16
+
17
+ Closes #
@@ -0,0 +1,42 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions: read-all
10
+
11
+ jobs:
12
+ lint:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
16
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
17
+ with:
18
+ python-version: "3.12"
19
+ - run: pip install ruff==0.15.7 pyright==1.1.395
20
+ - run: ruff check silly_kicks/ tests/
21
+ - run: ruff format --check silly_kicks/ tests/
22
+ - run: pip install -e ".[test]"
23
+ - run: pyright silly_kicks/
24
+
25
+ test:
26
+ runs-on: ${{ matrix.os }}
27
+ strategy:
28
+ matrix:
29
+ os: [ubuntu-latest, windows-latest]
30
+ python-version: ["3.10", "3.11", "3.12"]
31
+ exclude:
32
+ - os: windows-latest
33
+ python-version: "3.10"
34
+ - os: windows-latest
35
+ python-version: "3.11"
36
+ steps:
37
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
38
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
39
+ with:
40
+ python-version: ${{ matrix.python-version }}
41
+ - run: pip install -e ".[kloppy,xgboost,test]"
42
+ - run: pytest tests/ -m "not e2e" -v --tb=short
@@ -0,0 +1,35 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
12
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
13
+ with:
14
+ python-version: "3.12"
15
+ - run: pip install build
16
+ - run: python -m build
17
+ - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
18
+ with:
19
+ name: dist
20
+ path: dist/
21
+
22
+ publish:
23
+ needs: build
24
+ runs-on: ubuntu-latest
25
+ environment: pypi
26
+ permissions:
27
+ id-token: write
28
+ steps:
29
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+ - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
34
+ with:
35
+ print-hash: true
@@ -0,0 +1,42 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ *.egg
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+
14
+ # IDE
15
+ .vscode/
16
+ .idea/
17
+ *.swp
18
+ *.swo
19
+
20
+ # Testing
21
+ .pytest_cache/
22
+ .coverage
23
+ htmlcov/
24
+ .mypy_cache/
25
+
26
+ # OS
27
+ .DS_Store
28
+ Thumbs.db
29
+
30
+ # Secrets and credentials
31
+ .env
32
+ .env.*
33
+ *.key
34
+ *.pem
35
+ credentials.json
36
+
37
+ # Downloaded test datasets (not committed)
38
+ tests/datasets/statsbomb/
39
+ tests/datasets/wyscout_public/
40
+
41
+ # Claude Code
42
+ .claude/
@@ -0,0 +1,73 @@
1
+ # Changelog
2
+
3
+ All notable changes to silly-kicks will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] — 2026-04-07
9
+
10
+ ### Added
11
+ - DEBUG logging for kloppy silent event drops (aerial duels, unrecognized GK subtypes)
12
+ - `.github/CODEOWNERS` for code owner review enforcement
13
+
14
+ ### Fixed
15
+ - StatsBomb converter now accepts both `"goalkeeper"` and `"goal_keeper"` keys in the
16
+ extra dict — adapters that snake-case the event type name no longer silently lose all
17
+ keeper actions
18
+
19
+ ### Improved
20
+ - `ConversionReport` docstring: full Attributes section, usage example, provider-specific
21
+ key type note
22
+ - `add_names()` docstring: explicit guarantee that caller-added columns are preserved
23
+ - `_finalize_output()` docstring: guarantee that all SPADL_COLUMNS are present
24
+ - `config.py` docstring: `actiontype_id`, `result_id`, `bodypart_id` reverse dicts documented
25
+ - Wyscout `convert_to_actions()`: Returns section now documents `ConversionReport`;
26
+ `goalkeeper_ids` notes `None` ≡ empty set equivalence
27
+
28
+ ### Removed
29
+ - `docs/plans/` and `docs/specs/` — internal development artifacts with local paths
30
+
31
+ ### Changed
32
+ - Version bump: 0.1.0 → 1.0.0 (Production/Stable)
33
+ - C4 diagram genericized (removed project-specific references)
34
+
35
+ ## [0.1.0] — 2026-04-06
36
+
37
+ ### Added
38
+ - Initial release as maintained successor to socceraction v1.5.3
39
+ - SPADL converters: StatsBomb, Opta, Wyscout, Kloppy
40
+ - VAEP and Atomic-VAEP frameworks
41
+ - HybridVAEP — result-leakage-free action valuation
42
+ - xG-targeted labels via `xg_column` parameter
43
+ - Expected Saves (xS) label via `save_from_shot()`
44
+ - Expected Claims (xC) label via `claim_from_cross()`
45
+ - Cross zone feature (Gelade 2017 four-zone classification)
46
+ - Assist type feature (through ball, cutback, cross, set piece, progressive pass)
47
+ - Wyscout `goalkeeper_ids` parameter for GK aerial duel routing (#37)
48
+ - `ConversionReport` audit trail for every conversion
49
+ - `validate_spadl()` utility for DataFrame validation
50
+ - Input validation with clear error messages per provider
51
+ - "Nothing Left Behind" mapping registries (mapped/excluded/unrecognized events)
52
+ - Reproducible training via `random_state` parameter
53
+
54
+ ### Changed (from socceraction v1.5.3)
55
+ - Dropped pandera dependency — schemas are plain Python constants
56
+ - Dropped multimethod dependency
57
+ - Removed numpy<2.0 upper bound
58
+ - All converters return `tuple[pd.DataFrame, ConversionReport]`
59
+ - All `apply(axis=1)` hot paths replaced with `np.select` vectorization
60
+ - Wyscout module decomposed into 3 files
61
+ - Gamestates uses vectorized shift instead of `groupby().apply()`
62
+ - Config DataFrame factories cached with `@functools.cache`
63
+ - Labels vectorized (shift-based accumulation replaces 27-column loop)
64
+ - `actiontype_result_onehot` uses numpy broadcasting
65
+
66
+ ### Fixed
67
+ - Bug #507: Empty game crash in `gamestates()`
68
+ - Bug #950: `actiontype` feature wrong for Atomic-SPADL
69
+ - Bug #784: Opta converter silently drops card events
70
+ - Bug #831: Atomic-SPADL missing "out" for blocked/saved shots
71
+ - Bug #37/D44: Wyscout keeper_claim/punch differentiation
72
+ - Bug #946: pandas 3.0 `fillna(inplace=True)` deprecation
73
+ - pandas 3.0 `groupby().apply(as_index=False)` key column drop
@@ -0,0 +1,36 @@
1
+ # silly-kicks
2
+
3
+ Maintained fork of socceraction — SPADL event conversion + VAEP action valuation for soccer analytics.
4
+
5
+ ## Architecture
6
+
7
+ - **Hexagonal**: All core functions are pure (pandas in, pandas out). Zero I/O, zero global state mutation.
8
+ - **Converters** (`spadl/`): Each provider (StatsBomb, Opta, Wyscout, Kloppy) has its own module. All return `tuple[pd.DataFrame, ConversionReport]` with guaranteed columns/dtypes via `_finalize_output()`.
9
+ - **VAEP** (`vaep/`): Feature engineering + gradient boosting binary classifiers. `HybridVAEP` removes result leakage from a0 features. xG-targeted labels available via `xg_column` parameter.
10
+ - **Atomic-SPADL** (`atomic/`): Variant where actions are decomposed into atomic sub-actions (receival, interception, out, etc.).
11
+
12
+ ## Key conventions
13
+
14
+ - No pandera — schemas are plain Python dicts (`SPADL_COLUMNS`, `ATOMIC_SPADL_COLUMNS`).
15
+ - Config DataFrames (`actiontypes_df()`, etc.) are cached with `@functools.cache`.
16
+ - Vectorized dispatch: converters use `np.select` over pre-flattened columns, not `apply(axis=1)`.
17
+ - All `warnings.warn()` calls include `stacklevel=2`.
18
+ - ML naming conventions (uppercase `X`, `Y`, `Pscores`) are allowed in `vaep/` and `xthreat.py` per ruff per-file-ignores.
19
+
20
+ ## Testing
21
+
22
+ ```bash
23
+ python -m pytest tests/ -m "not e2e" -v --tb=short
24
+ ```
25
+
26
+ e2e tests require dataset fixtures not committed to the repo.
27
+
28
+ ## Open Items
29
+
30
+ See [TODO.md](TODO.md) for tracked work. Audit history in [docs/DEFERRED.md](docs/DEFERRED.md).
31
+
32
+ ## Dependencies
33
+
34
+ - Runtime: pandas, numpy, scikit-learn (no pandera, no multimethod)
35
+ - Optional: kloppy, xgboost, catboost, lightgbm
36
+ - numpy>=2.0 compatible
@@ -0,0 +1,41 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to a positive environment:
15
+
16
+ - Using welcoming and inclusive language
17
+ - Being respectful of differing viewpoints and experiences
18
+ - Gracefully accepting constructive criticism
19
+ - Focusing on what is best for the community
20
+ - Showing empathy towards other community members
21
+
22
+ Examples of unacceptable behavior:
23
+
24
+ - The use of sexualized language or imagery, and sexual attention or advances
25
+ - Trolling, insulting or derogatory comments, and personal or political attacks
26
+ - Public or private harassment
27
+ - Publishing others' private information without explicit permission
28
+ - Other conduct which could reasonably be considered inappropriate
29
+
30
+ ## Enforcement
31
+
32
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
33
+ reported to the project maintainers. All complaints will be reviewed and
34
+ investigated and will result in a response that is deemed necessary and
35
+ appropriate to the circumstances.
36
+
37
+ ## Attribution
38
+
39
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
40
+ version 2.1, available at
41
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
@@ -0,0 +1,46 @@
1
+ # Contributing to silly-kicks
2
+
3
+ ## Development Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/karsten-s-nielsen/silly-kicks.git
7
+ cd silly-kicks
8
+ pip install -e ".[kloppy,xgboost,test,dev]"
9
+ ```
10
+
11
+ ## Running Tests
12
+
13
+ ```bash
14
+ # Unit tests (fast, no external data needed)
15
+ python -m pytest tests/ -m "not e2e" -v
16
+
17
+ # With coverage
18
+ python -m pytest tests/ -m "not e2e" --cov=silly_kicks
19
+ ```
20
+
21
+ ## Code Quality
22
+
23
+ ```bash
24
+ # Lint
25
+ ruff check silly_kicks/ tests/
26
+ ruff format silly_kicks/ tests/
27
+
28
+ # Type check
29
+ pyright silly_kicks/
30
+ ```
31
+
32
+ ## Pull Request Process
33
+
34
+ 1. Create a feature branch from `main`
35
+ 2. Write tests first (TDD preferred)
36
+ 3. Ensure all CI checks pass: `ruff check`, `ruff format --check`, `pyright`, `pytest`
37
+ 4. Keep commits focused — one logical change per commit
38
+ 5. Include a clear description of what and why
39
+
40
+ ## Architecture Guidelines
41
+
42
+ - All core functions are pure: pandas in, pandas out
43
+ - Converters return `tuple[pd.DataFrame, ConversionReport]`
44
+ - Use `np.select` for vectorized dispatch, not `apply(axis=1)`
45
+ - Add `stacklevel=2` to all `warnings.warn()` calls
46
+ - New public functions need docstrings with Parameters/Returns sections
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 KU Leuven Machine Learning Research Group - Tom Decroos, Pieter Robberechts
4
+ Copyright (c) 2026 Karsten S. Nielsen
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: silly-kicks
3
+ Version: 1.0.0
4
+ Summary: Classify and value on-ball football actions using SPADL and VAEP
5
+ Project-URL: Homepage, https://github.com/karsten-s-nielsen/silly-kicks
6
+ Project-URL: Repository, https://github.com/karsten-s-nielsen/silly-kicks
7
+ Author: Karsten S. Nielsen
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: SPADL,VAEP,actions,analytics,football,soccer
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: numpy>=1.26.0
21
+ Requires-Dist: pandas>=2.1.1
22
+ Requires-Dist: scikit-learn>=1.3.1
23
+ Provides-Extra: catboost
24
+ Requires-Dist: catboost>=1.2; extra == 'catboost'
25
+ Provides-Extra: dev
26
+ Requires-Dist: pyright>=1.1.380; extra == 'dev'
27
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
28
+ Provides-Extra: kloppy
29
+ Requires-Dist: kloppy>=3.15.0; extra == 'kloppy'
30
+ Provides-Extra: lightgbm
31
+ Requires-Dist: lightgbm>=4.0; extra == 'lightgbm'
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest-benchmark>=4.0.0; extra == 'test'
34
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
35
+ Requires-Dist: pytest-mock>=3.11.1; extra == 'test'
36
+ Requires-Dist: pytest>=7.4.2; extra == 'test'
37
+ Provides-Extra: xgboost
38
+ Requires-Dist: xgboost>=2.0.0; extra == 'xgboost'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # silly kicks
42
+
43
+ ![The Modern SPADL Analyst vs The Chief SPADL Evaluator & Classifier](assets/silly-kicks.jpg)
44
+ <sup>Comic by NanoBanana &mdash; inspired by Monty Python's <em>Ministry of Silly Walks</em></sup>
45
+
46
+ [![CI](https://github.com/karsten-s-nielsen/silly-kicks/actions/workflows/ci.yml/badge.svg)](https://github.com/karsten-s-nielsen/silly-kicks/actions/workflows/ci.yml)
47
+
48
+ *The Ministry requires that all football actions be properly classified and valued.*
49
+
50
+ **silly-kicks** is a Python library for objectively quantifying the impact of
51
+ individual actions performed by football players using event stream data.
52
+
53
+ It is an independently maintained successor to
54
+ [socceraction](https://github.com/ML-KULeuven/socceraction), originally
55
+ developed by Tom Decroos and Pieter Robberechts at KU Leuven. Built under the
56
+ MIT license with full attribution preserved.
57
+
58
+ ## Features
59
+
60
+ - **SPADL** -- Soccer Player Action Description Language: a unified schema for
61
+ on-ball actions with converters for StatsBomb, Wyscout, Opta, and kloppy
62
+ - **VAEP** -- Valuing Actions by Estimating Probabilities: a framework for
63
+ quantifying the value of individual actions
64
+ - **Atomic SPADL** -- continuous (non-discretized) action representation
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ pip install silly-kicks
70
+ ```
71
+
72
+ Requires Python 3.10 or later.
73
+
74
+ With optional provider support:
75
+
76
+ ```bash
77
+ pip install "silly-kicks[kloppy,xgboost]"
78
+ ```
79
+
80
+ ## Quick Start
81
+
82
+ ```python
83
+ import silly_kicks.spadl as spadl
84
+
85
+ # Convert StatsBomb events to SPADL actions
86
+ actions, report = spadl.statsbomb.convert_to_actions(events, home_team_id=123)
87
+
88
+ # Add human-readable names
89
+ actions = spadl.add_names(actions)
90
+ ```
91
+
92
+ ## VAEP Workflow
93
+
94
+ The full pipeline: convert provider events to SPADL, train a VAEP model, and
95
+ rate individual actions.
96
+
97
+ ```python
98
+ from silly_kicks.spadl import statsbomb
99
+ from silly_kicks.vaep import VAEP
100
+
101
+ # 1. Convert provider events to SPADL
102
+ actions, report = statsbomb.convert_to_actions(
103
+ events_df, home_team_id=home_team_id,
104
+ xy_fidelity_version=2, shot_fidelity_version=2,
105
+ )
106
+
107
+ # 2. Train a VAEP model
108
+ model = VAEP(nb_prev_actions=3)
109
+ features = model.compute_features(game, actions)
110
+ labels = model.compute_labels(game, actions)
111
+ model.fit(features, labels, learner="xgboost", random_state=42)
112
+
113
+ # 3. Rate actions
114
+ ratings = model.rate(game, actions)
115
+ # Returns DataFrame with offensive_value, defensive_value, vaep_value
116
+ ```
117
+
118
+ ### Hybrid-VAEP
119
+
120
+ Standard VAEP includes the action's result (success/fail) as a feature, which
121
+ creates information leakage. HybridVAEP removes result information from the
122
+ current action while preserving it for previous actions.
123
+
124
+ ```python
125
+ from silly_kicks.vaep import HybridVAEP
126
+
127
+ # HybridVAEP removes result leakage from current-action features
128
+ model = HybridVAEP(nb_prev_actions=3)
129
+ # Same fit/rate API as standard VAEP
130
+ ```
131
+
132
+ ### Multi-Provider Support
133
+
134
+ All converters share the same output schema, so downstream code works
135
+ identically regardless of the data provider.
136
+
137
+ ```python
138
+ from silly_kicks.spadl import opta, wyscout
139
+
140
+ actions_opta, _ = opta.convert_to_actions(opta_events, home_team_id)
141
+ actions_wyscout, _ = wyscout.convert_to_actions(wyscout_events, home_team_id)
142
+ ```
143
+
144
+ ## Architecture
145
+
146
+ Open [`docs/c4/architecture.html`](docs/c4/architecture.html) in a browser to explore the C4 architecture diagrams (System Context, Containers).
147
+
148
+ ## Attribution
149
+
150
+ This project builds on the foundational research by the KU Leuven Machine
151
+ Learning Research Group. If you use this library in academic work, please cite
152
+ the original papers:
153
+
154
+ ```bibtex
155
+ @inproceedings{Decroos2019VAEP,
156
+ title = {Actions Speak Louder than Goals: Valuing Player Actions in Soccer},
157
+ author = {Tom Decroos and Lotte Bransen and Jan Van Haaren and Jesse Davis},
158
+ booktitle = {Proceedings of the 25th ACM SIGKDD International Conference
159
+ on Knowledge Discovery \& Data Mining},
160
+ pages = {1851--1861},
161
+ year = {2019},
162
+ doi = {10.1145/3292500.3330758}
163
+ }
164
+
165
+ @inproceedings{Decroos2020AtomicSPADL,
166
+ title = {Interpretable Prediction of Goals in Soccer},
167
+ author = {Tom Decroos and Jesse Davis},
168
+ booktitle = {Proceedings of the AAAI-20 Workshop on Artificial Intelligence
169
+ in Team Sports},
170
+ year = {2020}
171
+ }
172
+ ```
173
+
174
+ ## Contributing
175
+
176
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards,
177
+ and PR process. Open items and planned work are tracked in [TODO.md](TODO.md).
178
+
179
+ ## License
180
+
181
+ MIT License. See [LICENSE](LICENSE) for details.