Releases: PySport/kloppy
Release list
v3.19.0
✨ Highlights ✨
- Write Support: Added comprehensive write capabilities to kloppy. You can now write data to local files or S3 URIs with automatic support for compression formats (
.gz,.bz2,.xz). This feature is a first step towards implementing the Common Data Format (CDF) exporter (#519), which will allow users to output tracking data in the CDF specification. (#515)
import json
from kloppy import io, statsbomb
# 1. Load StatsBomb data
dataset = statsbomb.load_open_data(match_id="3788741")
# 2. Use the low-level 'open_as_file' with write mode
with io.open_as_file("event_data_export.json.gz", mode="wb") as f:
# Extract just the event names and IDs for this example
data_to_save = [
{"id": event.event_id, "type": event.event_name}
for event in dataset.records[:100]
]
# Serialize to JSON and write to the stream
# The '.gz' extension triggers automatic compression
f.write(json.dumps(data_to_save).encode("utf-8"))- Sportscode Serializer Enhancement: The Sportscode serializer can now handle multiple labels per group. This allows for generating multiple XML elements for a single group by using a list of label values, making serialized data richer and more structured. (#499)
code = Code(
period=period,
timestamp=timedelta(seconds=10),
statistics=[],
ball_owning_team=None,
ball_state=None,
code_id="1",
code="Shot",
end_timestamp=timedelta(seconds=12),
labels={
"Player": "Lionel Messi",
"Action": ["Goal", "Left Foot", "Outside Box"] # Multiple labels for 'Action' group
}
)When serialized, each item in the list becomes its own element while maintaining the same name:
<?xml version='1.0' encoding='utf-8'?>
<file>
<ALL_INSTANCES>
<instance>
<ID>1</ID>
<start>10.0</start>
<end>12.0</end>
<code>Shot</code>
<label>
<group>Player</group>
<text>Lionel Messi</text>
</label>
<label>
<group>Action</group>
<text>Goal</text>
</label>
<label>
<group>Action</group>
<text>Left Foot</text>
</label>
<label>
<group>Action</group>
<text>Outside Box</text>
</label>
</instance>
</ALL_INSTANCES>
</file>- Switch to uv & ruff: We've overhauled our internal tooling, switching from pip to uv for dependency management and adopting ruff for linting and formatting. The development environment setup and CI pipelines are now significantly faster and more reliable. (#487, #517)
⚠️ Breaking Changes
- Changed
only_alivedefault toFalsefor all providers: Previously, theonly_aliveparameter defaulted toTruefor StatsPerform, SecondSpectrum and Skillcorner, meaning that only frames for which the ball was in play were included in the output. This default has now been changed toFalseacross all providers. If your code relies on the old behavior, you should now explicitly passonly_alive=Truewhen loading data. (#556)
🚀 Features
- SkillCorner: Improved the mapping of SkillCorner position IDs to kloppy's internal
PositionTypeenum, adding support for previously missing IDs likeGoalkeeperandLeftBack. (#516) - Metadata Restructuring: Moved coach information from the top-level
Metadataobject to theTeamobject (accessible viateam.coach). For backward compatibility, the oldhome_coachandaway_coachproperties are deprecated but still work. (#520) - Sequence State Builder: Improved the builder to create more logical action sequences by including more events (like goalkeeper pickups and successful interceptions) as sequence starters and correctly handling duels and defensive actions. (#484)
🪲 Fixes
- Tracab: Fixed an issue where player names were being parsed as
StringElementobjects instead of standard Python strings. (#505) - SecondSpectrum: Resolved an issue with JSONL files containing a UTF-8 BOM (Byte Order Mark) by using the
utf-8-sigencoding, ensuring they are parsed correctly. (#522) - Sportec IDSSE: Updated the source for the IDSSE open dataset from Figshare to HuggingFace, as the original Figshare URLs were broken. Also updated the documentation to reflect the dataset's publication status. (#529, #542)
- StatsBomb:
- Fixed two bugs in the coordinate transformation for freeze frames, which prevented correct transformation when no orientation change was needed and caused the ball's coordinates to be transformed twice. (#526)
- Added missing formation mappings, including the 3-2-4-1 formation, to prevent
KeyErrorexceptions when deserializing tactical shift events. (#553, #554)
- HTTP Basic Auth: The configuration for HTTP Basic Auth now accepts both a dictionary and a tuple, making it easier to use. The documentation has also been corrected to reflect the proper
loginandpasswordkeys. (#534) - General:
- Made
end_timestampandend_coordinatesoptional fields forCarryEventandend_timestampoptional forPressureEvent, aligning them with other event classes and preventingTypeErrorexceptions when these fields are missing. (#557) - Added support for XML feeds containing a BOM (Byte Order Mark) by correctly handling the encoding. (#540)
- Fixed an issue in the SkillCorrener adapter where a coordinate value of exactly 0.0 was being handled incorrectly. (#538)
- Made
📚 Documentation
- A brand new and improved Contributor's Guide is now available to help new developers get started. (#488)
- Fixed numerous typos and grammatical errors across the documentation for a smoother reading experience. (#558)
- Corrected typos and an incorrect function call (
load_open_data→load) in the Jupyter notebooks that demonstrate how to load data. (#547)
👷 Refactoring
- Removed the deprecated
to_pandasmethod. (#518) - Refactored dataset filtering and event deserialization logic for better maintainability. (#521)
⚙️ CI/CD
- Switched from
piptouvfor faster and more reliable dependency management. (#487) - Replaced the
blackcode formatter withruff, which is faster and also handles linting. (#517) - Made the release workflow more robust. (#560)
Contributors
This release was made possible by the following contributors:
- @Alek050 made their first contribution in #540
- @cb-jodv made their first contribution in #553
- @DriesDeprest
- @EoinTrial made their first contribution in #547
- @koenvo
- @lode-mgp
- @probberechts
- @stephTchembeu made their first contribution in #513 and #519
- @UnravelSports
Full Changelog: v3.18.0...v3.19.0
v3.18.0 - IMPECT data
✨ Highlights ✨
This release introduces support for loading IMPECT event data. We’re excited to share that this feature was sponsored by IMPECT, who also made a public open dataset available at https://github.com/ImpectAPI/open-data.
from kloppy import impect
dataset = impect.load_open_data(match_id=122976)
print(dataset.to_df().head())Implementation by @DriesDeprest, @koenvo and @UnravelSports in #504. A huge thank-you to IMPECT for sponsoring and supporting this contribution to the open-source community!
🚀 Other updates
- Added
PositionType.position_groupby @UnravelSports in #472
This makes it easier to check a player’s top-level position group:
player = dataset.metadata.teams[0].get_player_by_id("3089")
assert player.starting_position == PositionType.RightAttackingMidfield
assert player.starting_position.position_group == PositionType.Midfielder- [SkillCorner] Add
only_alivefunctionality by @UnravelSports in #509
🪲 Fixes
Full Changelog: v3.17.1...v3.18.0
v3.17.1
This minor release patches bugs in the Sportec and Tracab tracking data parsers.
🪲 Fixes
- [Sportec] Fix missing player coordinates for period 2 by @probberechts in #490
- [Tracab] Fix ModuleNotFoundError by @probberechts in #492
🚀 Other updates
- [SkillCorner] Add BallState by @UnravelSports in #486
Full Changelog: v3.17.0...v3.17.1
v3.17.0
Bump version: 3.16.0 → 3.17.0 (#471)
v3.16.0
✨ Highlights ✨
Load DFL Open Data by @UnravelSports in #365
We've added support for loading a new public dataset! The dataset contains seven full matches of raw event and position data collected by Sportec Solutions from the German Men's Bundesliga season 2022/23 first and second division.
from kloppy import sportec
event_dataset = sportec.load_open_event_data(match_id="J03WMX")
tracking_dataset = sportec.load_open_tracking_data(match_id="J03WMX")For more info about the dataset, see
Bassek, M., Weber, H., Rein, R., & Memmert, D. (2024). "An integrated dataset of synchronized spatiotemporal and event data in elite soccer." In Submission.
Standardized player positions by @DriesDeprest in #334
Instead of relying on provider-specific player positions, kloppy now implements a standardized PositionType.
>>> from kloppy import statsbomb
>>> event_dataset = statsbomb.load_open_data(match_id="15946")
>>> event = event_dataset.get_event_by_id("549567bd-36de-4ac8-b8dc-6b5d3f1e4be8")
>>> event.player.starting_position
<PositionType.LeftMidfield: ('Left Midfield', 'LM', 'WideMidfield')>The positions are ordered hierarchically.
>>> from kloppy.domain import PositionType
>>> print(PositionType.LeftBack.parent)
FullBack
>>> print(PositionType.Defender.is_subtype_of(PositionType.Defender))
True
>>> print(PositionType.LeftCenterBack.is_subtype_of(PositionType.Defender))
True
>>> print(PositionType.LeftBack.is_subtype_of(PositionType.Midfielder))
False
>>> print(PositionType.LeftCenterBack)
Left Center Back
>>> print(PositionType.LeftCenterBack.code)
LCBTeam formation changes by @DriesDeprest in #332
In addition to a starting_formation attribute, a Team entity now also has a formations attribute that holds the team's formation changes throughout the game.
>>> from kloppy import statsbomb
>>> event_dataset = statsbomb.load_open_data(match_id="15946")
>>> event = event_dataset.get_event_by_id("549567bd-36de-4ac8-b8dc-6b5d3f1e4be8")
>>> event.team.starting_formation
<FormationType.FOUR_FIVE_ONE: '4-5-1'>
>>> event.team.formations
TimeContainer[FormationType]({'P2T22:53': <FormationType.FOUR_FOUR_TWO: '4-4-2'>})🚀 Other updates
- [StatsBomb] Added a
CounterAttackQualifierby @DriesDeprest in #291 - [Wyscout v3] Estimate shot end coordinates by @fubininho in #320
- [Wyscout v3] Added support for extra time and penalties by @DriesDeprest in #351
- [Wyscout V3] Added position information for players by @DriesDeprest in #366
- [Wyscout V3] Added a
PassQualifier(PassType.SHOT_ASSIST)by @DriesDeprest in #347 - [Stats Perform - Event] Infer ball state from previous event by @DriesDeprest in #341
- [Stats Perform - Event] Correct deflected pass end coordinates and result by @DriesDeprest in #311
- [Stats Perform - Event] Added formation and lineup changes by @DriesDeprest in #333 and #361
- Enriched metadata with date, game_week, game_id and officials by @SportsDynamicsDS in #340 and @UnravelSports in #371
🪲 Fixes
- Fix common bug in parsing of UTC datetimes by @probberechts in #373
- [Stats Perform - Event] Fix creation of goalkeeper events for event type 10/Save when an outfield player blocks a shot (qualifier 94) by @DriesDeprest in #335
- [Stats Perform - Tracking] Recognize referee player type and handle accordingly by @DriesDeprest in #357
- [Stats Perform - Tracking] Support frames with no player data by @DriesDeprest in #349
- [Stats Perform - Tracking] Handle missing end period for abandoned matches by @DriesDeprest in #355
- [SkillCorner] Support renaming "time" → "timestamp" by @UnravelSports in #338
- [Tracab] Support new meta data format by @UnravelSports in #353
- [Wyscout v3] Fix location for blocked crosses by @DriesDeprest in #343
- Use
positionsinget_player_by_positionby @DriesDeprest in #342
👷 Refactoring
- Remove
dateutildependency by @probberechts in #373 - [SkillCorner] Remove
numpydependency by @probberechts in #375
🚨 Testing
- [Wyscout v3] Use publicly available Wyscout v3 event data in tests by @DriesDeprest in #350
👋 New Contributors
- @SportsDynamicsDS made their first contribution in #340
- @fubininho made their first contribution in #320
Full Changelog: v3.15.0...v3.16.0
v3.15.0
What's Changed
- Redo of #PR215 by @UnravelSports in #278
- Fix Wyscout orientation by @DriesDeprest in #279
- [Opta] Fix datatype of score by @probberechts in #286
- [Opta] Refactor tests by @probberechts in #289
- [Opta] Add BALL_STATE flag by @probberechts in #287
- [Opta] Fix F24 timestamp parsing by @probberechts in #288
- [Metrica] Fix metadata after transform by @probberechts in #290
- Fix transform orientation by @probberechts in #190
- Add support for JSON Tracab data by @DriesDeprest in #295
- Add shot assist to data model and fix Opta & StatsBomb deserializers accordingly by @DriesDeprest in #281
- Add pressure event to data model & StatsBomb parser by @DriesDeprest in #292
- Uniform implementation of timestamps by @probberechts in #294
- Upgrade MkDocs to version 1.5.3 by @JanVanHaaren in #303
- Upgrade GitHub Actions to latest versions by @JanVanHaaren in #304
- Accurate transformation of pitch dimensions by @probberechts in #297
- Add broken test for issue 305 by @koenvo in #306
- [Tracab] Parse xml meta information by @DriesDeprest in #300
- Remove Python3.7 support, add Python 3.12 to tests by @koenvo in #318
- Opta Incomplete / Live F7 missing Result tag fix by @UnravelSports in #322
- [Wyscout v3] add parsing of carry event by @DriesDeprest in #323
- [StatsPerform] Bugfixes for tracking data (MA25) + Support for event data (MA3) by @probberechts in #310
- Fix StatsBomb create_periods by @DriesDeprest in #313
- [IO] Improved IO with support for reading data from compressed files by @probberechts in #308
- Time-based positions + MinutedPlayed Aggregator by @koenvo in #326
- Don't use deprecated Player.position in tests by @koenvo in #327
- [Opta] Allow missing value for qualifier 210 by @probberechts in #328
- [Opta] Set bodypart=head for flick-ons by @probberechts in #329
- Fix for older/incomplete/other versions of Tracab Meta data files by @UnravelSports in #336
- [StatsPerform] Fix origin and vertical orientation of SportVU by @DriesDeprest in #345
- Add GH workflows for release by @koenvo in #348
Full Changelog: v3.14.0...v3.15.0
v3.14.0
What's Changed
- Add SetPieceQualifiers for StatsBomb Shot event by @probberechts in #223
- Add CardQualifiers for StatsBomb FoulCommited event by @probberechts in #221
- Run build docs by @koenvo in #231
- Fixing epts metrica loader when score is not on the metadata file by @jcnunezmoreno in #227
- Fix Bodypart qualifiers in StatsBomb clearance event by @probberechts in #228
- Add own goals in StatsBomb deserializer by @probberechts in #229
- Add InterceptionEvent by @MKlaasman in #230
- Add Opta shot result coordinates by @probberechts in #233
- Fix Wyscout deserialization of teams and players by @probberechts in #235
- Fix sportec current by @koenvo in #217
- Add StatsBomb shot result coordinates by @probberechts in #232
- Recognize take on event for Wyscout by @DriesDeprest in #238
- Fix multiple pass qualifiers per pass event by @DriesDeprest in #241
- Add tackle as duel type and recognize StatsPerform challenge event by @DriesDeprest in #242
- Fix Wyscout v2 set-piece qualifiers by @probberechts in #245
- Use new Wyscout open data format. by @koenvo in #248
- Support JSON meta data in Stats Perform tracking data deserializer by @JanVanHaaren in #255
- Remove duplicate Metrica set-piece events by @probberechts in #264
- Add artificial formation change event in Wyscout deserializer by @DriesDeprest in #250
- Estimate Wyscout v2 shot result coordinates by @probberechts in #246
- Fix shot end coordinates for Opta deserializer by @probberechts in #247
- Fix receiver_coordinates of passes for Wyscout v2 by @probberechts in #259
- Fix GoalKeeperActionType.SAVE coordinates for Wyscout v2 by @probberechts in #260
- Refactor StatsBomb deserializer by @probberechts in #262
- Implement GoalkeeperActionType.PICK_UP and GoalkeeperActionType.CLAIM for StatsBomb by @probberechts in #224
- Add shot body part qualifiers for Wyscout v2 by @probberechts in #258
- Add Opta support for extra time & penalty shootouts by @DriesDeprest in #263
- Fix FoulComitted + Card for Wyscout v2 by @probberechts in #261
- Revert changes to duel parsing logic in Opta deserializer by @JanVanHaaren in #273
Full Changelog: v3.13.0...v3.14.0