From 80e16fb861c9a1ad6ad64c8acfcac5c3f5f3f222 Mon Sep 17 00:00:00 2001 From: driesdeprest Date: Thu, 21 Nov 2024 18:49:00 +0100 Subject: [PATCH 1/5] wyscout v3 - add position information for players --- .../event/wyscout/deserializer_v3.py | 79 ++++++++++++++----- kloppy/tests/test_wyscout.py | 7 ++ 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/kloppy/infra/serializers/event/wyscout/deserializer_v3.py b/kloppy/infra/serializers/event/wyscout/deserializer_v3.py index 08088dd1..64e93a1e 100644 --- a/kloppy/infra/serializers/event/wyscout/deserializer_v3.py +++ b/kloppy/infra/serializers/event/wyscout/deserializer_v3.py @@ -45,6 +45,7 @@ Team, FormationType, CarryResult, + PositionType, ) from kloppy.exceptions import DeserializationError from kloppy.utils import performance_logging @@ -81,36 +82,78 @@ "3-2-3-2": FormationType.THREE_TWO_THREE_TWO, } +position_types_mapping: Dict[str, PositionType] = { + "GK": PositionType.Goalkeeper, + "LB": PositionType.LeftBack, + "LWB": PositionType.LeftWing, + "LB5": PositionType.LeftBack, + "LCB": PositionType.LeftCenterBack, + "LCB3": PositionType.LeftCenterBack, + "CB": PositionType.CenterBack, + "RCB": PositionType.RightCenterBack, + "RCB3": PositionType.RightCenterBack, + "RB": PositionType.RightBack, + "RWB": PositionType.RightWing, + "RB5": PositionType.RightBack, + "LW": PositionType.LeftWing, + "LAMF": PositionType.LeftAttackingMidfield, + "LCMF3": PositionType.LeftCentralMidfield, + "LCMF": PositionType.LeftCentralMidfield, + "DMF": PositionType.DefensiveMidfield, + "LDMF": PositionType.LeftDefensiveMidfield, + "RDMF": PositionType.RightDefensiveMidfield, + "RCMF3": PositionType.RightCentralMidfield, + "RCMF": PositionType.RightCentralMidfield, + "RAMF": PositionType.RightAttackingMidfield, + "RW": PositionType.RightWing, + "AMF": PositionType.AttackingMidfield, + "LWF": PositionType.LeftForward, + "CF": PositionType.Striker, + "SS": PositionType.Striker, + "RWF": PositionType.RightForward, +} + def _flip_point(point: Point) -> Point: return Point(x=100 - point.x, y=100 - point.y) def _parse_team(raw_events, wyId: str, ground: Ground) -> Team: + # Get the first formation description + first_period_formation_info = raw_events["formations"][wyId]["1H"] + first_formation_descr = next(iter(first_period_formation_info.values())) + formation_str, formation_info = next(iter(first_formation_descr.items())) + + # Extract the formation and players' positions + starting_formation = formations[formation_str] + starting_players_positions = { + player_id: position_types_mapping[player_info["position"].upper()] + for player_descr in formation_info["players"] + for player_id, player_info in player_descr.items() + } + team = Team( team_id=wyId, name=raw_events["teams"][wyId]["team"]["officialName"], ground=ground, - starting_formation=formations[ - next( - iter( - raw_events["formations"][wyId]["1H"][ - next(iter(raw_events["formations"][wyId]["1H"])) - ] - ) - ) - ], + starting_formation=starting_formation, ) - team.players = [ - Player( - player_id=str(player["player"]["wyId"]), - team=team, - jersey_no=None, - first_name=player["player"]["firstName"], - last_name=player["player"]["lastName"], + + for player in raw_events["players"][wyId]: + player_id = str(player["player"]["wyId"]) + starting_position = starting_players_positions.get(player_id) + team.players.append( + Player( + player_id=player_id, + team=team, + jersey_no=None, + first_name=player["player"]["firstName"], + last_name=player["player"]["lastName"], + starting=starting_position is not None, + starting_position=starting_position, + ) ) - for player in raw_events["players"][wyId] - ] + return team diff --git a/kloppy/tests/test_wyscout.py b/kloppy/tests/test_wyscout.py index 725ae92f..5d28f64e 100644 --- a/kloppy/tests/test_wyscout.py +++ b/kloppy/tests/test_wyscout.py @@ -23,6 +23,7 @@ Time, PassType, PassQualifier, + PositionType, ) from kloppy import wyscout @@ -203,6 +204,12 @@ def test_metadata(self, dataset: EventDataset): == FormationType.FOUR_THREE_ONE_TWO ) + cr7 = dataset.metadata.teams[0].get_player_by_id("3322") + + assert cr7.full_name == "Cristiano Ronaldo dos Santos Aveiro" + assert cr7.starting is True + assert cr7.positions.last() == PositionType.Striker + def test_enriched_metadata(self, dataset: EventDataset): date = dataset.metadata.date if date: From 854bb4da06a078540fcd506afe8ddcbe2bf6c814 Mon Sep 17 00:00:00 2001 From: Dries Deprest Date: Tue, 3 Dec 2024 11:21:26 +0100 Subject: [PATCH 2/5] Update kloppy/infra/serializers/event/wyscout/deserializer_v3.py Co-authored-by: Pieter Robberechts --- kloppy/infra/serializers/event/wyscout/deserializer_v3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kloppy/infra/serializers/event/wyscout/deserializer_v3.py b/kloppy/infra/serializers/event/wyscout/deserializer_v3.py index 64e93a1e..a998e6d3 100644 --- a/kloppy/infra/serializers/event/wyscout/deserializer_v3.py +++ b/kloppy/infra/serializers/event/wyscout/deserializer_v3.py @@ -125,7 +125,7 @@ def _parse_team(raw_events, wyId: str, ground: Ground) -> Team: formation_str, formation_info = next(iter(first_formation_descr.items())) # Extract the formation and players' positions - starting_formation = formations[formation_str] + starting_formation = formations.get(formation_str) starting_players_positions = { player_id: position_types_mapping[player_info["position"].upper()] for player_descr in formation_info["players"] From 5395db58bb7d2e87339f0fc7bbbc3133c206c2e9 Mon Sep 17 00:00:00 2001 From: Dries Deprest Date: Tue, 3 Dec 2024 11:24:18 +0100 Subject: [PATCH 3/5] Update kloppy/infra/serializers/event/wyscout/deserializer_v3.py Co-authored-by: Pieter Robberechts From 17322328bb37b69902afa9fa9aac91b93de9b477 Mon Sep 17 00:00:00 2001 From: Dries Deprest Date: Tue, 3 Dec 2024 11:24:26 +0100 Subject: [PATCH 4/5] Update kloppy/infra/serializers/event/wyscout/deserializer_v3.py Co-authored-by: Pieter Robberechts --- kloppy/infra/serializers/event/wyscout/deserializer_v3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kloppy/infra/serializers/event/wyscout/deserializer_v3.py b/kloppy/infra/serializers/event/wyscout/deserializer_v3.py index a998e6d3..f6a604fb 100644 --- a/kloppy/infra/serializers/event/wyscout/deserializer_v3.py +++ b/kloppy/infra/serializers/event/wyscout/deserializer_v3.py @@ -127,7 +127,7 @@ def _parse_team(raw_events, wyId: str, ground: Ground) -> Team: # Extract the formation and players' positions starting_formation = formations.get(formation_str) starting_players_positions = { - player_id: position_types_mapping[player_info["position"].upper()] + player_id: position_types_mapping.get(player_info["position"].upper(), PositionType.Unknown) for player_descr in formation_info["players"] for player_id, player_info in player_descr.items() } From 3769453e2c171d24a4e8a1e91cc759de35846efc Mon Sep 17 00:00:00 2001 From: driesdeprest Date: Tue, 3 Dec 2024 11:26:53 +0100 Subject: [PATCH 5/5] add wing back positions --- kloppy/domain/models/position.py | 2 ++ kloppy/infra/serializers/event/wyscout/deserializer_v3.py | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/kloppy/domain/models/position.py b/kloppy/domain/models/position.py index c0daebfd..84d08280 100644 --- a/kloppy/domain/models/position.py +++ b/kloppy/domain/models/position.py @@ -13,6 +13,8 @@ class PositionType(Enum): CenterBack = ("Center Back", "CB", "Defender") LeftCenterBack = ("Left Center Back", "LCB", "CenterBack") RightCenterBack = ("Right Center Back", "RCB", "CenterBack") + LeftWingBack = ("Left Wing Back", "LWB", "WingBack") + RightWingBack = ("Right Wing Back", "RWB", "WingBack") Midfielder = ("Midfielder", "MID", None) DefensiveMidfield = ("Defensive Midfield", "DM", "Midfielder") diff --git a/kloppy/infra/serializers/event/wyscout/deserializer_v3.py b/kloppy/infra/serializers/event/wyscout/deserializer_v3.py index f6a604fb..87ba360b 100644 --- a/kloppy/infra/serializers/event/wyscout/deserializer_v3.py +++ b/kloppy/infra/serializers/event/wyscout/deserializer_v3.py @@ -85,7 +85,7 @@ position_types_mapping: Dict[str, PositionType] = { "GK": PositionType.Goalkeeper, "LB": PositionType.LeftBack, - "LWB": PositionType.LeftWing, + "LWB": PositionType.LeftWingBack, "LB5": PositionType.LeftBack, "LCB": PositionType.LeftCenterBack, "LCB3": PositionType.LeftCenterBack, @@ -93,7 +93,7 @@ "RCB": PositionType.RightCenterBack, "RCB3": PositionType.RightCenterBack, "RB": PositionType.RightBack, - "RWB": PositionType.RightWing, + "RWB": PositionType.RightWingBack, "RB5": PositionType.RightBack, "LW": PositionType.LeftWing, "LAMF": PositionType.LeftAttackingMidfield, @@ -127,7 +127,9 @@ def _parse_team(raw_events, wyId: str, ground: Ground) -> Team: # Extract the formation and players' positions starting_formation = formations.get(formation_str) starting_players_positions = { - player_id: position_types_mapping.get(player_info["position"].upper(), PositionType.Unknown) + player_id: position_types_mapping.get( + player_info["position"].upper(), PositionType.Unknown + ) for player_descr in formation_info["players"] for player_id, player_info in player_descr.items() }