Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dribbled past duel type #282

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kloppy/domain/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ class DuelType(Enum):
LOOSE_BALL = "LOOSE_BALL"
SLIDING_TACKLE = "SLIDING_TACKLE"
TACKLE = "TACKLE"
DRIBBLED_PAST = "DRIBBLED_PAST"


@dataclass
Expand Down
21 changes: 19 additions & 2 deletions kloppy/infra/serializers/event/opta/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
EVENT_TYPE_TAKE_ON = 3
EVENT_TYPE_TACKLE = 7
EVENT_TYPE_AERIAL = 44
EVENT_TYPE_CHALLENGE = 45
EVENT_TYPE_50_50 = 67
EVENT_TYPE_ATTEMPTED_TACKLE = 83
EVENT_TYPE_INTERCEPTION = 8
EVENT_TYPE_CLEARANCE = 12
EVENT_TYPE_SHOT_MISS = 13
Expand Down Expand Up @@ -91,6 +93,8 @@
EVENT_TYPE_TACKLE,
EVENT_TYPE_AERIAL,
EVENT_TYPE_50_50,
EVENT_TYPE_CHALLENGE,
EVENT_TYPE_ATTEMPTED_TACKLE,
]

BALL_OWNING_EVENTS = (
Expand Down Expand Up @@ -403,8 +407,13 @@ def _parse_duel(
raw_qualifiers: Dict[int, str], type_id: int, outcome: int
) -> Dict:
qualifiers = _get_event_qualifiers(raw_qualifiers)
if type_id == EVENT_TYPE_TACKLE:
qualifiers.extend([DuelQualifier(value=DuelType.GROUND)])
if type_id in (EVENT_TYPE_TACKLE, EVENT_TYPE_ATTEMPTED_TACKLE):
qualifiers.extend(
[
DuelQualifier(value=DuelType.GROUND),
DuelQualifier(value=DuelType.TACKLE),
]
)
elif type_id == EVENT_TYPE_AERIAL:
qualifiers.extend(
[
Expand All @@ -420,6 +429,14 @@ def _parse_duel(
]
)

elif type_id == EVENT_TYPE_CHALLENGE:
qualifiers.extend(
[
DuelQualifier(value=DuelType.GROUND),
DuelQualifier(value=DuelType.DRIBBLED_PAST),
]
)

result = DuelResult.WON if outcome else DuelResult.LOST

return dict(
Expand Down
26 changes: 25 additions & 1 deletion kloppy/infra/serializers/event/statsbomb/specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,26 @@ def _create_events(
return [take_on_event]


class DRIBBLED_PAST(EVENT):
"""StatsBomb 39/Dribbled Past event."""

def _create_events(
self, event_factory: EventFactory, **generic_event_kwargs
) -> List[Event]:
duel_result = DuelResult.LOST
duel_qualifiers = [
DuelQualifier(value=DuelType.GROUND),
DuelQualifier(value=DuelType.DRIBBLED_PAST),
]
dribbled_past_event = event_factory.build_duel(
result=duel_result,
qualifiers=duel_qualifiers,
**generic_event_kwargs,
)

return [dribbled_past_event]


class CARRY(EVENT):
"""StatsBomb 43/Carry event."""

Expand Down Expand Up @@ -748,7 +768,10 @@ def _create_events(
DuelQualifier(value=DuelType.AERIAL),
]
elif type_id == DUEL.TYPE.TACKLE:
duel_qualifiers = [DuelQualifier(value=DuelType.GROUND)]
duel_qualifiers = [
DuelQualifier(value=DuelType.GROUND),
DuelQualifier(value=DuelType.TACKLE),
]

# Get duel result
duel_won_outcomes = [
Expand Down Expand Up @@ -1272,6 +1295,7 @@ def event_decoder(raw_event: Dict) -> Union[EVENT, Dict]:
EVENT_TYPE.CARRY: CARRY,
EVENT_TYPE.DUEL: DUEL,
EVENT_TYPE.FIFTY_FIFTY: FIFTY_FIFTY,
EVENT_TYPE.DRIBBLED_PAST: DRIBBLED_PAST,
EVENT_TYPE.GOALKEEPER: GOALKEEPER,
EVENT_TYPE.SUBSTITUTION: SUBSTITUTION,
EVENT_TYPE.BAD_BEHAVIOUR: BAD_BEHAVIOUR,
Expand Down
22 changes: 18 additions & 4 deletions kloppy/tests/test_statsbomb.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,16 +829,19 @@ def test_deserialize_all(self, dataset: EventDataset):
"""It should deserialize all duel and 50/50 events"""
events = dataset.find_all("duel")
assert (
len(events) == 59 + 4 + 26
) # duels + 50/50 + aerial won attribute
len(events) == 59 + 4 + 26 + 22
) # duels + 50/50 + aerial won attribute + dribbled past

def test_attributes(self, dataset: EventDataset):
"""Verify specific attributes of duels"""
duel = dataset.get_event_by_id("15c4bfaa-36fd-4b3e-bec1-bc8bcc1febb9")
# A duel should have a result
assert duel.result == DuelResult.WON
# A duel should have a duel type
assert duel.get_qualifier_values(DuelQualifier) == [DuelType.GROUND]
assert duel.get_qualifier_values(DuelQualifier) == [
DuelType.GROUND,
DuelType.TACKLE,
]
# A duel does not have a body part
assert duel.get_qualifier_value(BodyPartQualifier) is None

Expand All @@ -853,7 +856,10 @@ def test_aerial_duel_qualfiers(self, dataset: EventDataset):
def test_tackle_qualfiers(self, dataset: EventDataset):
"""It should add ground duel qualifiers"""
duel = dataset.get_event_by_id("15c4bfaa-36fd-4b3e-bec1-bc8bcc1febb9")
assert duel.get_qualifier_values(DuelQualifier) == [DuelType.GROUND]
assert duel.get_qualifier_values(DuelQualifier) == [
DuelType.GROUND,
DuelType.TACKLE,
]

def test_loose_ground_duel_qualfiers(self, dataset: EventDataset):
"""It should add ground duel + loose ball qualifiers"""
Expand All @@ -863,6 +869,14 @@ def test_loose_ground_duel_qualfiers(self, dataset: EventDataset):
DuelType.GROUND,
]

def test_dribbled_past_qualifiers(self, dataset: EventDataset):
"""It should add ground + dribbled past qualifiers"""
duel = dataset.get_event_by_id("ebf42396-6fc0-4700-9618-32b24df33bf3")
assert duel.get_qualifier_values(DuelQualifier) == [
DuelType.GROUND,
DuelType.DRIBBLED_PAST,
]


class TestStatsBombGoalkeeperEvent:
"""Tests related to deserializing 30/Goalkeeper events"""
Expand Down