Skip to content

Commit

Permalink
Merge pull request #127 from koenvo/some-state-fixes
Browse files Browse the repository at this point in the history
Minor refactor and some fixes
  • Loading branch information
koenvo authored Jan 28, 2022
2 parents 1d19e9e + 83d74e8 commit 336454a
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 105 deletions.
3 changes: 2 additions & 1 deletion kloppy/domain/models/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Dict, List, Optional, Callable, Union, Any, TypeVar, Generic

from .pitch import PitchDimensions, Point, Dimension
from .formation import FormationType
from ...exceptions import OrientationError


Expand Down Expand Up @@ -144,7 +145,7 @@ class Team:
team_id: str
name: str
ground: Ground
starting_formation: str = ""
starting_formation: Optional[FormationType] = None
players: List[Player] = field(default_factory=list)

def __str__(self):
Expand Down
79 changes: 3 additions & 76 deletions kloppy/domain/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)

from .common import DataRecord, Dataset, Player, Team
from .formation import FormationType
from .pitch import Point


Expand Down Expand Up @@ -130,80 +131,6 @@ class CardType(Enum):
RED = "RED"


class FormationType(Enum):
"""
FormationType
Attributes:
THREE_ONE_TWO_ONE_THREE (str): 3-1-2-1-3 team formation
THREE_ONE_THREE_ONE_TWO (str):
THREE_ONE_FOUR_TWO (str):
THREE_TWO_TWO_ONE_TWO (str):
THREE_TWO_TWO_TWO_ONE (str):
THREE_TWO_THREE_TWO (str):
THREE_THREE_TWO_TWO (str):
THREE_TWO_FOUR_ONE (str):
THREE_THREE_THREE_ONE (str):
THREE_FOUR_ONE_TWO (str):
THREE_FOUR_TWO_ONE (str):
THREE_FOUR_THREE (str):
THREE_FIVE_ONE_ONE (str):
THREE_FIVE_TWO (str):
FOUR_ONE_TWO_ONE_TWO (str):
FOUR_ONE_TWO_TWO_ONE (str):
FOUR_ONE_THREE_TWO (str):
FOUR_ONE_FOUR_ONE (str):
FOUR_TWO_ONE_TWO_ONE (str):
FOUR_TWO_TWO_ONE_ONE (str):
FOUR_TWO_TWO_TWO (str):
FOUR_TWO_THREE_ONE (str):
FOUR_TWO_FOUR_ZERO (str):
FOUR_THREE_ONE_TWO (str):
FOUR_THREE_TWO_ONE (str):
FOUR_THREE_THREE (str):
FOUR_FOUR_ONE_ONE (str):
FOUR_FOUR_TWO (str):
FOUR_FIVE_ONE (str):
FIVE_TWO_TWO_ONE (str):
FIVE_THREE_TWO (str):
FIVE_FOUR_ONE (str):
"""

THREE_ONE_TWO_ONE_THREE = "3-1-2-1-3"
THREE_ONE_THREE_ONE_TWO = "3-1-3-1-2"
THREE_ONE_FOUR_TWO = "3-1-4-2"
THREE_TWO_TWO_ONE_TWO = "3-2-2-1-2"
THREE_TWO_TWO_TWO_ONE = "3-2-2-1-2"
THREE_TWO_THREE_TWO = "3-2-3-2"
THREE_THREE_TWO_TWO = "3-3-2-2"
THREE_TWO_FOUR_ONE = "3-2-4-1"
THREE_THREE_THREE_ONE = "3-3-3-1"
THREE_FOUR_ONE_TWO = "3-4-1-2"
THREE_FOUR_TWO_ONE = "3-4-2-1"
THREE_FOUR_THREE = "3-4-3"
THREE_FIVE_ONE_ONE = "3-5-1-1"
THREE_FIVE_TWO = "3-5-2"
FOUR_ONE_TWO_ONE_TWO = "4-1-2-1-2"
FOUR_ONE_TWO_TWO_ONE = "4-1-2-2-1"
FOUR_ONE_THREE_TWO = "4-1-3-2"
FOUR_ONE_FOUR_ONE = "4-1-4-1"
FOUR_TWO_ONE_TWO_ONE = "4-2-1-2-1"
FOUR_TWO_TWO_ONE_ONE = "4-2-2-1-1"
FOUR_TWO_TWO_TWO = "4-2-2-2"
FOUR_TWO_THREE_ONE = "4-2-3-1"
FOUR_TWO_FOUR_ZERO = "4-2-4-0"
FOUR_THREE_ONE_TWO = "4-3-1-2"
FOUR_THREE_TWO_ONE = "4-3-2-1"
FOUR_THREE_THREE = "4-3-3"
FOUR_FOUR_ONE_ONE = "4-4-1-1"
FOUR_FOUR_TWO = "4-4-2"
FOUR_FIVE_ONE = "4-5-1"
FIVE_TWO_TWO_ONE = "5-2-2-1"
FIVE_THREE_TWO = "5-3-2"
FIVE_FOUR_ONE = "5-4-1"


class EventType(Enum):
"""
Attributes:
Expand Down Expand Up @@ -653,10 +580,10 @@ class FormationChangeEvent(Event):
Attributes:
event_type (EventType): `EventType.FORMATION_CHANGE` (See [`EventType`][kloppy.domain.models.event.EventType])
event_name (str): `"card"`
formation: See [`FormationType`][kloppy.domain.models.event.FormationType]
formation_type: See [`FormationType`][kloppy.domain.models.formation.FormationType]
"""

formation: FormationType
formation_type: FormationType

event_type: EventType = EventType.FORMATION_CHANGE
event_name: str = "formation_change"
Expand Down
78 changes: 78 additions & 0 deletions kloppy/domain/models/formation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from enum import Enum


class FormationType(Enum):
"""
FormationType
Attributes:
THREE_ONE_TWO_ONE_THREE (str): 3-1-2-1-3 team formation
THREE_ONE_THREE_ONE_TWO (str):
THREE_ONE_FOUR_TWO (str):
THREE_TWO_TWO_ONE_TWO (str):
THREE_TWO_TWO_TWO_ONE (str):
THREE_TWO_THREE_TWO (str):
THREE_THREE_TWO_TWO (str):
THREE_TWO_FOUR_ONE (str):
THREE_THREE_THREE_ONE (str):
THREE_FOUR_ONE_TWO (str):
THREE_FOUR_TWO_ONE (str):
THREE_FOUR_THREE (str):
THREE_FIVE_ONE_ONE (str):
THREE_FIVE_TWO (str):
FOUR_ONE_TWO_ONE_TWO (str):
FOUR_ONE_TWO_TWO_ONE (str):
FOUR_ONE_THREE_TWO (str):
FOUR_ONE_FOUR_ONE (str):
FOUR_TWO_ONE_TWO_ONE (str):
FOUR_TWO_TWO_ONE_ONE (str):
FOUR_TWO_TWO_TWO (str):
FOUR_TWO_THREE_ONE (str):
FOUR_TWO_FOUR_ZERO (str):
FOUR_THREE_ONE_TWO (str):
FOUR_THREE_TWO_ONE (str):
FOUR_THREE_THREE (str):
FOUR_FOUR_ONE_ONE (str):
FOUR_FOUR_TWO (str):
FOUR_FIVE_ONE (str):
FIVE_TWO_TWO_ONE (str):
FIVE_THREE_TWO (str):
FIVE_FOUR_ONE (str):
"""

THREE_ONE_TWO_ONE_THREE = "3-1-2-1-3"
THREE_ONE_THREE_ONE_TWO = "3-1-3-1-2"
THREE_ONE_FOUR_TWO = "3-1-4-2"
THREE_TWO_TWO_ONE_TWO = "3-2-2-1-2"
THREE_TWO_TWO_TWO_ONE = "3-2-2-1-2"
THREE_TWO_THREE_TWO = "3-2-3-2"
THREE_THREE_TWO_TWO = "3-3-2-2"
THREE_TWO_FOUR_ONE = "3-2-4-1"
THREE_THREE_THREE_ONE = "3-3-3-1"
THREE_FOUR_ONE_TWO = "3-4-1-2"
THREE_FOUR_TWO_ONE = "3-4-2-1"
THREE_FOUR_THREE = "3-4-3"
THREE_FIVE_ONE_ONE = "3-5-1-1"
THREE_FIVE_TWO = "3-5-2"
FOUR_ONE_TWO_ONE_TWO = "4-1-2-1-2"
FOUR_ONE_TWO_TWO_ONE = "4-1-2-2-1"
FOUR_ONE_THREE_TWO = "4-1-3-2"
FOUR_ONE_FOUR_ONE = "4-1-4-1"
FOUR_TWO_ONE_TWO_ONE = "4-2-1-2-1"
FOUR_TWO_TWO_ONE_ONE = "4-2-2-1-1"
FOUR_TWO_TWO_TWO = "4-2-2-2"
FOUR_TWO_THREE_ONE = "4-2-3-1"
FOUR_TWO_FOUR_ZERO = "4-2-4-0"
FOUR_THREE_ONE_TWO = "4-3-1-2"
FOUR_THREE_TWO_ONE = "4-3-2-1"
FOUR_THREE_THREE = "4-3-3"
FOUR_FOUR_ONE_ONE = "4-4-1-1"
FOUR_FOUR_TWO = "4-4-2"
FOUR_FIVE_ONE = "4-5-1"
FIVE_TWO_TWO_ONE = "5-2-2-1"
FIVE_THREE_TWO = "5-3-2"
FIVE_FOUR_ONE = "5-4-1"

def __str__(self):
return self.value
2 changes: 1 addition & 1 deletion kloppy/domain/services/state_builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from kloppy.domain import List, EventDataset

# register all of them
from . import builders
from . import builders as _builders

from .registered import create_state_builder

Expand Down
13 changes: 8 additions & 5 deletions kloppy/domain/services/state_builder/builders/formation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import replace, dataclass
from typing import Optional

from kloppy.domain import (
FormationChangeEvent,
Expand All @@ -12,11 +13,13 @@

@dataclass
class Formation:
home: FormationType
away: FormationType
home: Optional[FormationType] = None
away: Optional[FormationType] = None

def __str__(self):
return f"{self.home} {self.away}"
if self.home and self.away:
return f"{self.home.value} {self.away.value}"
return "Unknown"


class FormationStateBuilder(StateBuilder):
Expand All @@ -33,7 +36,7 @@ def reduce_before(self, state: Formation, event: Event) -> Formation:
def reduce_after(self, state: Formation, event: Event) -> Formation:
if isinstance(event, FormationChangeEvent):
if event.team.ground == Ground.HOME:
state = replace(state, home=event.formation.value)
state = replace(state, home=event.formation_type)
else:
state = replace(state, away=event.formation.value)
state = replace(state, away=event.formation_type)
return state
9 changes: 2 additions & 7 deletions kloppy/infra/serializers/event/opta/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,13 @@
BallState,
DatasetFlag,
Orientation,
PitchDimensions,
Dimension,
PassEvent,
ShotEvent,
TakeOnEvent,
CarryEvent,
GenericEvent,
PassResult,
ShotResult,
TakeOnResult,
CarryResult,
EventType,
Ground,
Score,
Provider,
Expand Down Expand Up @@ -289,7 +284,7 @@ def _parse_formation_change(raw_qualifiers: List) -> Dict:
formation_id = int(raw_qualifiers[EVENT_QUALIFIER_TEAM_FORMATION])
formation = formations[formation_id]

return dict(formation=formation)
return dict(formation_type=formation)


def _parse_shot(
Expand Down Expand Up @@ -349,7 +344,7 @@ def _team_from_xml_elm(team_elm, f7_root) -> Team:
ground=Ground.HOME
if team_elm.attrib["Side"] == "Home"
else Ground.AWAY,
starting_formation=formation,
starting_formation=FormationType(formation),
)
team.players = [
Player(
Expand Down
17 changes: 10 additions & 7 deletions kloppy/infra/serializers/event/statsbomb/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def _parse_card(card_dict: Dict) -> Dict:
def _parse_formation_change(formation_id: int) -> Dict:
formation = formations[formation_id]

return dict(formation=formation)
return dict(formation_type=formation)


def _determine_xy_fidelity_versions(events: List[Dict]) -> Tuple[int, int]:
Expand Down Expand Up @@ -413,17 +413,19 @@ def deserialize(self, inputs: StatsbombInputs) -> EventDataset:
for player in raw_event["tactics"]["lineup"]
}

starting_formations = [
str(raw_event["tactics"]["formation"])
starting_formations = {
raw_event["team"]["id"]: FormationType(
"-".join(list(str(raw_event["tactics"]["formation"])))
)
for raw_event in raw_events
if raw_event["type"]["id"] == SB_EVENT_TYPE_STARTING_XI
]
}

home_team = Team(
team_id=str(home_lineup["team_id"]),
name=home_lineup["team_name"],
ground=Ground.HOME,
starting_formation="-".join(list(starting_formations[0])),
starting_formation=starting_formations[home_lineup["team_id"]],
)
home_team.players = [
Player(
Expand All @@ -440,7 +442,7 @@ def deserialize(self, inputs: StatsbombInputs) -> EventDataset:
team_id=str(away_lineup["team_id"]),
name=away_lineup["team_name"],
ground=Ground.AWAY,
starting_formation="-".join(list(starting_formations[1])),
starting_formation=starting_formations[away_lineup["team_id"]],
)
away_team.players = [
Player(
Expand Down Expand Up @@ -662,12 +664,13 @@ def deserialize(self, inputs: StatsbombInputs) -> EventDataset:
formation_change_event_kwargs = _parse_formation_change(
raw_event["tactics"]["formation"]
)
event = FormationChangeEvent.create(
formation_change_event = FormationChangeEvent.create(
result=None,
qualifiers=None,
**formation_change_event_kwargs,
**generic_event_kwargs,
)
new_events.append(formation_change_event)
# rest: generic
else:
generic_event = GenericEvent.create(
Expand Down
9 changes: 7 additions & 2 deletions kloppy/tests/test_opta.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
PassType,
DatasetType,
CardType,
FormationType,
)

from kloppy import opta
Expand Down Expand Up @@ -52,10 +53,14 @@ def test_correct_deserialization(self, f7_data: str, f24_data: str):
)
assert dataset.metadata.teams[0].name == "FC København"
assert dataset.metadata.teams[0].ground == Ground.HOME
assert dataset.metadata.teams[0].starting_formation == "4-4-2"
assert dataset.metadata.teams[0].starting_formation == FormationType(
"4-4-2"
)
assert dataset.metadata.teams[1].name == "FC Nordsjælland"
assert dataset.metadata.teams[1].ground == Ground.AWAY
assert dataset.metadata.teams[1].starting_formation == "4-3-3"
assert dataset.metadata.teams[1].starting_formation == FormationType(
"4-3-3"
)

player = dataset.metadata.teams[0].players[0]
assert player.player_id == "111319"
Expand Down
12 changes: 8 additions & 4 deletions kloppy/tests/test_state_builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from itertools import groupby

from kloppy.domain import EventType, Event, EventDataset
from kloppy.domain import EventType, Event, EventDataset, FormationType
from kloppy.domain.services.state_builder.builder import StateBuilder, T
from kloppy.utils import performance_logging
from kloppy import statsbomb
Expand Down Expand Up @@ -95,11 +95,15 @@ def test_formation_state_builder(self):
events_per_formation_change[str(formation)] = len(events)

# inspect FormationChangeEvent usage and formation state_builder
assert events_per_formation_change["4-1-4-1"] == 3073
assert events_per_formation_change["4-1-4-1"] == 3074
assert events_per_formation_change["4-4-2"] == 949

assert dataset.metadata.teams[0].starting_formation == "4-4-2"
assert dataset_with_state.events[1].state["formation"].home == "4-4-2"
assert dataset.metadata.teams[0].starting_formation == FormationType(
"4-4-2"
)
assert dataset_with_state.events[1].state[
"formation"
].home == FormationType("4-4-2")

def test_register_custom_builder(self):
class CustomStateBuilder(StateBuilder):
Expand Down
Loading

0 comments on commit 336454a

Please sign in to comment.