Skip to content

Releases: PySport/kloppy

v3.19.0

Choose a tag to compare

@probberechts probberechts released this 07 Jun 11:30
99f76f0

✨ 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_alive default to False for all providers: Previously, the only_alive parameter defaulted to True for 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 to False across all providers. If your code relies on the old behavior, you should now explicitly pass only_alive=True when loading data. (#556)

🚀 Features

  • SkillCorner: Improved the mapping of SkillCorner position IDs to kloppy's internal PositionType enum, adding support for previously missing IDs like Goalkeeper and LeftBack. (#516)
  • Metadata Restructuring: Moved coach information from the top-level Metadata object to the Team object (accessible via team.coach). For backward compatibility, the old home_coach and away_coach properties 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 StringElement objects 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-sig encoding, 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 KeyError exceptions 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 login and password keys. (#534)
  • General:
    • Made end_timestamp and end_coordinates optional fields for CarryEvent and end_timestamp optional for PressureEvent, aligning them with other event classes and preventing TypeError exceptions 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)

📚 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_dataload) in the Jupyter notebooks that demonstrate how to load data. (#547)

👷 Refactoring

  • Removed the deprecated to_pandas method. (#518)
  • Refactored dataset filtering and event deserialization logic for better maintainability. (#521)

⚙️ CI/CD

  • Switched from pip to uv for faster and more reliable dependency management. (#487)
  • Replaced the black code formatter with ruff, 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:

Full Changelog: v3.18.0...v3.19.0

v3.18.0 - IMPECT data

Choose a tag to compare

@probberechts probberechts released this 23 Oct 09:14
369a9b5

✨ 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

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

🪲 Fixes

  • [SkillCorner] Updated URLs for open data loader by @koenvo in #510

Full Changelog: v3.17.1...v3.18.0

v3.17.1

Choose a tag to compare

@probberechts probberechts released this 19 Aug 15:02
ccf9b91

This minor release patches bugs in the Sportec and Tracab tracking data parsers.

🪲 Fixes

🚀 Other updates

Full Changelog: v3.17.0...v3.17.1

v3.17.0

Choose a tag to compare

@probberechts probberechts released this 28 May 10:16
ed74515
Bump version: 3.16.0 → 3.17.0 (#471)

v3.16.0

Choose a tag to compare

@probberechts probberechts released this 17 Dec 23:58

✨ 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) 
LCB

Team 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

🪲 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 positions in get_player_by_position by @DriesDeprest in #342

👷 Refactoring

🚨 Testing

  • [Wyscout v3] Use publicly available Wyscout v3 event data in tests by @DriesDeprest in #350

👋 New Contributors

Full Changelog: v3.15.0...v3.16.0

v3.15.0

Choose a tag to compare

@koenvo koenvo released this 17 Dec 23:59

What's Changed

Full Changelog: v3.14.0...v3.15.0

v3.14.0

Choose a tag to compare

@koenvo koenvo released this 18 Dec 00:00

What's Changed

Full Changelog: v3.13.0...v3.14.0

v3.13.0

Choose a tag to compare

@koenvo koenvo released this 18 Dec 00:01
  • Add MiscontrolEvent (#207)
  • Add GoalkeeperEvent (#196)

v3.12.0

Choose a tag to compare

@koenvo koenvo released this 18 Dec 00:01
  • Add ClearanceEvent (#195)
  • Fixed foot left/right typo in shot parsing for WyScout v3 (#197)
  • Improvements to Opta and Wyscout deserializers (#198)
  • Fix issue with automated tests on macos (#199)
  • Add speed parsing for SecondSpectrum (#201)
  • Add DuelEvent (#204)
  • Sportec tracking (#208)

v3.11.0

Choose a tag to compare

@koenvo koenvo released this 18 Dec 00:01
  • Fix datatype of SkillCorner metadata.periods (#189)
  • Fix inputs of kloppy.helpers.transform (#186)
  • Refactor pathlib tests (#193)
  • StatsPerform deserializer (#191)
  • Allow chaining of operators on a Dataset (filter + map) (#183)
  • Opta remove deleted (#182)