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.
- silly_kicks-1.0.0/.github/CODEOWNERS +1 -0
- silly_kicks-1.0.0/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- silly_kicks-1.0.0/.github/ISSUE_TEMPLATE/feature_request.md +18 -0
- silly_kicks-1.0.0/.github/dependabot.yml +13 -0
- silly_kicks-1.0.0/.github/pull_request_template.md +17 -0
- silly_kicks-1.0.0/.github/workflows/ci.yml +42 -0
- silly_kicks-1.0.0/.github/workflows/publish.yml +35 -0
- silly_kicks-1.0.0/.gitignore +42 -0
- silly_kicks-1.0.0/CHANGELOG.md +73 -0
- silly_kicks-1.0.0/CLAUDE.md +36 -0
- silly_kicks-1.0.0/CODE_OF_CONDUCT.md +41 -0
- silly_kicks-1.0.0/CONTRIBUTING.md +46 -0
- silly_kicks-1.0.0/LICENSE +22 -0
- silly_kicks-1.0.0/PKG-INFO +181 -0
- silly_kicks-1.0.0/README.md +141 -0
- silly_kicks-1.0.0/SECURITY.md +27 -0
- silly_kicks-1.0.0/TODO.md +18 -0
- silly_kicks-1.0.0/assets/silly-kicks.jpg +0 -0
- silly_kicks-1.0.0/docs/DEFERRED.md +126 -0
- silly_kicks-1.0.0/docs/c4/architecture.dsl +89 -0
- silly_kicks-1.0.0/docs/c4/architecture.html +317 -0
- silly_kicks-1.0.0/pyproject.toml +87 -0
- silly_kicks-1.0.0/silly_kicks/__init__.py +9 -0
- silly_kicks-1.0.0/silly_kicks/atomic/__init__.py +5 -0
- silly_kicks-1.0.0/silly_kicks/atomic/spadl/__init__.py +15 -0
- silly_kicks-1.0.0/silly_kicks/atomic/spadl/base.py +238 -0
- silly_kicks-1.0.0/silly_kicks/atomic/spadl/config.py +57 -0
- silly_kicks-1.0.0/silly_kicks/atomic/spadl/schema.py +22 -0
- silly_kicks-1.0.0/silly_kicks/atomic/spadl/utils.py +60 -0
- silly_kicks-1.0.0/silly_kicks/atomic/vaep/__init__.py +6 -0
- silly_kicks-1.0.0/silly_kicks/atomic/vaep/base.py +82 -0
- silly_kicks-1.0.0/silly_kicks/atomic/vaep/features.py +272 -0
- silly_kicks-1.0.0/silly_kicks/atomic/vaep/formula.py +133 -0
- silly_kicks-1.0.0/silly_kicks/atomic/vaep/labels.py +186 -0
- silly_kicks-1.0.0/silly_kicks/spadl/__init__.py +27 -0
- silly_kicks-1.0.0/silly_kicks/spadl/_wyscout_events.py +573 -0
- silly_kicks-1.0.0/silly_kicks/spadl/_wyscout_mappings.py +300 -0
- silly_kicks-1.0.0/silly_kicks/spadl/base.py +81 -0
- silly_kicks-1.0.0/silly_kicks/spadl/config.py +105 -0
- silly_kicks-1.0.0/silly_kicks/spadl/kloppy.py +485 -0
- silly_kicks-1.0.0/silly_kicks/spadl/opta.py +516 -0
- silly_kicks-1.0.0/silly_kicks/spadl/schema.py +85 -0
- silly_kicks-1.0.0/silly_kicks/spadl/statsbomb.py +617 -0
- silly_kicks-1.0.0/silly_kicks/spadl/utils.py +166 -0
- silly_kicks-1.0.0/silly_kicks/spadl/wyscout.py +333 -0
- silly_kicks-1.0.0/silly_kicks/vaep/__init__.py +7 -0
- silly_kicks-1.0.0/silly_kicks/vaep/base.py +290 -0
- silly_kicks-1.0.0/silly_kicks/vaep/features.py +931 -0
- silly_kicks-1.0.0/silly_kicks/vaep/formula.py +145 -0
- silly_kicks-1.0.0/silly_kicks/vaep/hybrid.py +62 -0
- silly_kicks-1.0.0/silly_kicks/vaep/labels.py +184 -0
- silly_kicks-1.0.0/silly_kicks/vaep/learners.py +173 -0
- silly_kicks-1.0.0/silly_kicks/xthreat.py +416 -0
- silly_kicks-1.0.0/tests/atomic/test_atomic_features.py +53 -0
- silly_kicks-1.0.0/tests/atomic/test_atomic_labels.py +18 -0
- silly_kicks-1.0.0/tests/atomic/test_atomic_spadl.py +30 -0
- silly_kicks-1.0.0/tests/atomic/test_atomic_vaep.py +52 -0
- silly_kicks-1.0.0/tests/conftest.py +33 -0
- silly_kicks-1.0.0/tests/datasets/spadl/atomic_spadl.json +1 -0
- silly_kicks-1.0.0/tests/datasets/spadl/spadl.json +1 -0
- silly_kicks-1.0.0/tests/spadl/test_kloppy.py +19 -0
- silly_kicks-1.0.0/tests/spadl/test_opta.py +182 -0
- silly_kicks-1.0.0/tests/spadl/test_output_contract.py +147 -0
- silly_kicks-1.0.0/tests/spadl/test_schema.py +99 -0
- silly_kicks-1.0.0/tests/spadl/test_statsbomb.py +189 -0
- silly_kicks-1.0.0/tests/spadl/test_wyscout.py +384 -0
- silly_kicks-1.0.0/tests/test_benchmark.py +45 -0
- silly_kicks-1.0.0/tests/test_xthreat.py +235 -0
- silly_kicks-1.0.0/tests/vaep/test_features.py +213 -0
- silly_kicks-1.0.0/tests/vaep/test_labels.py +81 -0
- 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
|
+

|
|
44
|
+
<sup>Comic by NanoBanana — inspired by Monty Python's <em>Ministry of Silly Walks</em></sup>
|
|
45
|
+
|
|
46
|
+
[](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.
|