Releases: PySport/kloppy
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
✨ Highlights ✨
Major Documentation Overhaul by @probberechts in #346
We’ve completely rewritten and reorganized the documentation from scratch to make it clearer and easier to use. Explore the new docs here: https://kloppy.pysport.org/.
💡 We’d love your help to continue improving it! If something confuses you, update it once you figure it out. It’s a great way to pay it forward. Check out #465 for a list of open documentation tasks where your contributions are welcome.
New Tracking Data Providers
We've added support for three more tracking data providers:
- Hawkeye 2D (centroids and ball) by @UnravelSports and @probberechts in #408
- Signality by @DriesDeprest in #356
- PFF FC by @razor3598 in #409
Detailed instructions on how to load data from these providers can be found here.
Attach Statistics to Events & Frames by @DriesDeprest in #302
Events and tracking frames now support a .statistics attribute for attaching metrics like xG, possession value, and pitch control.
Example:
>>> from kloppy import statsbomb
>>> dataset = statsbomb.load_open_data(match_id="3794687")
>>> goal = dataset.find("shot.goal")
>>> print(goal.statistics)
[ExpectedGoals(name='xG', value=0.0232)]Currently, xG and PSxG are parsed from StatsBomb, Stats Perform, Opta (F73) and Wyscout (v3) datasets.
New "Under Pressure" Event Qualifier by @DriesDeprest in #296
StatsBomb events now include a boolean UnderPressureQualifier.
>>> from kloppy import statsbomb
>>> dataset = statsbomb.load_open_data(match_id="3794687")
>>> event = dataset.get_event_by_id("c2a03c46-c936-4f7b-9b26-72d470a892ef")
>>> print(event.qualifiers)
[UnderPressureQualifier(value=True)]Support Creating Custom Coordinate Systems by @probberechts in #422
Apart from the ones used by the supported data providers, kloppy now also provides support to create a custom coordinate system using the CustomCoordinateSystem class.
from kloppy.domain import CustomCoordinateSystem, Origin, VerticalOrientation, NormalizedPitchDimensions, Dimension
my_coordinate_system = CustomCoordinateSystem(
origin=Origin.TOP_LEFT,
vertical_orientation=VerticalOrientation.TOP_TO_BOTTOM,
pitch_dimensions=NormalizedPitchDimensions(
pitch_length=105,
pitch_width=68,
),
)Our new documentation contains a detailed guide.
🚀 Other updates
- [SkillCorner] Added support for V3 SkillCorner tracking data by @DriesDeprest in #454
- [Sportec] Added AssistantVAR and pre-kick-off events filter by @UnravelSports in #452
- [Stats Perform] Use standardized position types across all deserializers by @DriesDeprest in #387
- [Stats Perform] Identifying the pass receiver on event deserialization by @douglaspampu in #392
- [Wyscout v3] Deduce kick-off qualifier by @rmotmans in #390
- [Wyscout V3] Add own goal result type by @DriesDeprest in #393
- [Wyscout V3] Add through ball qualifier by @DriesDeprest in #424
- [Wyscout V3] Add shot result z coordinates by @DriesDeprest in #427
- [Wyscout V3] Add high pass qualifier by @lode-mgp in #439
- [Wyscout v3] Add jersey number by @DriesDeprest in #443
🪲 Fixes
- Use correct attributes in
AngleToGoalTransformerby @lodevt in #416 - Fix transforming pitch dimensions when dataset has a coordinate system by @probberechts in #422
- Disassociate sample_rate and limit by @UnravelSports in #431
- Incorrect MinutesPlayed aggregation by @UnravelSports in #411
- Reset positions and formations during
EventDatasetcreation by @DriesDeprest in #389 - [PFF] Changes to metadata and rosters by @razor3598 in #426
- [PFF] Fix orientation recognition to accomodate bad data by @UnravelSports in #462
- [PFF] Missing Attacking Midfielder by @UnravelSports in #449
- [SecondSpectrum] Fix reversed pitch dimensions by @SportsDynamicsDS in #404
- [SecondSpectrum] Align position mapping + add PositionType.Unknown to StatsPerform & Metrica by @UnravelSports in #432
- [SkillCorner] Fix data loading error for game ids 2269 and 3442 by @probberechts in #376
- [Skillcorner] Avoid crash if coaches are missing by @UnravelSports in #428
- [Sportec] Fixed vertical orientation of event data coordinate system by @UnravelSports in #447
- [Sportec] Fix missing
players_databy @UnravelSports in #468 - [StatsBomb] Insert synthetic ball out events at the correct position by @probberechts in #380
- [StatsBomb] Fix player identity inference issues in freeze frame data by @AndrewRook in #386
- [StatsBomb] Deserialize failed recoveries as loose ball duels by @probberechts in #391
- [Stats Perform] Fallback to parsing player's full name from MA1 file when short name is unavailable by @DriesDeprest in #402
- [Stats Perform/Opta] Add FormationType.UNKNOWN and PostionType.Unknown handling by @UnravelSports in #456
- [Wyscout v3] Handle unrecognized players gracefully to prevent crashes by @DriesDeprest in #358
- [Wyscout v3] Set formation to
FormationType.UNKNOWNwhen a 10 player formation is recognized by @DriesDeprest in #330 - [Wyscout v3] Create periods prior to creating events by @DriesDeprest in #441
👷 Refactoring
- Replace deprecated function calls by @probberechts in #381
- Upgrade syntax to Python 3.8+ by @probberechts in #382
- Add
io.expand_inputs; refactor adapters to usefsspecby @probberechts in #419 - [Sportec] update DFL Open Data urls + citation by @probberechts in #458
- [Sportec] Reduce memory usage of tracking deserializer by @probberechts in #463
- [StatsBomb] Minor code refactoring by @probberechts in #425
- [Stats Perform] Use metadata parser to read in match metadata by @DriesDeprest in #433
- [Tracab] Decoupled Tracab dat / json from meta data file types by @UnravelSports and @probberechts in #364
🚨 Testing
- [Wyscout v2] Remove false goal tag from test data file by @probberechts in #399
🔋 Dependencies
- Allow
lxml>=5.0.0by @probberechts in #420 - Fixed aoihttp import by @UnravelSports in #445
- Add missing aiohttp test dependency by @probberechts in #457
- fsspec>2025.3.0 does not support python <=3.8 by @probberechts in #448
📚 Docs
- Add entry for ShotResult.OWN_GOAL by @tcp in #435
- Use build script for docs by @probberechts in #461
👋 New Contributors
- @AndrewRook made their first contribution in #386
- @rmotmans made their first contribution in #390
- @douglaspampu made their first contribution in #392
- @razor3598 made their first contribution in #409
- @lodevt made their first contribution in #416
- @tcp made their first contribution in #435
- @lode-mgp made their first contribution in #439
Full Changelog: v3.16.0...v3.17.0
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