Skip to content

Commit

Permalink
Add offensive & duel event types for SB, Opta, Wyscout
Browse files Browse the repository at this point in the history
  • Loading branch information
DriesDeprest committed Nov 30, 2023
1 parent 096f959 commit 0f22b0b
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 18 deletions.
2 changes: 2 additions & 0 deletions kloppy/domain/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ class DuelType(Enum):
GROUND = "GROUND"
LOOSE_BALL = "LOOSE_BALL"
SLIDING_TACKLE = "SLIDING_TACKLE"
OFFENSIVE = "OFFENSIVE"
DEFENSIVE = "DEFENSIVE"


@dataclass
Expand Down
8 changes: 8 additions & 0 deletions kloppy/infra/serializers/event/opta/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@
EVENT_QUALIFIER_ASSIST = 210
EVENT_QUALIFIER_ASSIST_2ND = 218

EVENT_QUALIFIER_DEFENSIVE_DUEL = 285
EVENT_QUALIFIER_OFFENSIVE_DUEL = 286

EVENT_QUALIFIER_FIRST_YELLOW_CARD = 31
EVENT_QUALIFIER_SECOND_YELLOW_CARD = 32
EVENT_QUALIFIER_RED_CARD = 33
Expand Down Expand Up @@ -397,6 +400,11 @@ def _parse_duel(

result = DuelResult.WON if outcome else DuelResult.LOST

if EVENT_QUALIFIER_OFFENSIVE_DUEL in raw_qualifiers:
qualifiers.append(DuelQualifier(value=DuelType.OFFENSIVE))
elif EVENT_QUALIFIER_DEFENSIVE_DUEL in raw_qualifiers:
qualifiers.append(DuelQualifier(value=DuelType.DEFENSIVE))

return dict(
result=result,
qualifiers=qualifiers,
Expand Down
11 changes: 11 additions & 0 deletions kloppy/infra/serializers/event/statsbomb/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,11 @@ def _parse_duel(
DuelQualifier(value=DuelType.GROUND),
]

if raw_event["possession_team"]["id"] == raw_event["team"]["id"]:
duel_qualifiers.append(DuelQualifier(value=DuelType.OFFENSIVE))
else:
duel_qualifiers.append(DuelQualifier(value=DuelType.DEFENSIVE))

qualifiers = duel_qualifiers + _get_body_part_qualifiers(duel_dict)

outcome_name = duel_dict.get("outcome", {}).get("name") or duel_dict.get(
Expand All @@ -600,6 +605,12 @@ def _parse_aerial_won_duel(raw_event: dict, type_name: str) -> Dict:
DuelQualifier(value=DuelType.LOOSE_BALL),
DuelQualifier(value=DuelType.AERIAL),
]

if raw_event["possession_team"]["id"] == raw_event["team"]["id"]:
duel_qualifiers.append(DuelQualifier(value=DuelType.OFFENSIVE))
else:
duel_qualifiers.append(DuelQualifier(value=DuelType.DEFENSIVE))

qualifiers = duel_qualifiers + _get_body_part_qualifiers(aerial_won_dict)

result = DuelResult.WON
Expand Down
36 changes: 18 additions & 18 deletions kloppy/infra/serializers/event/wyscout/deserializer_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ def _parse_duel(raw_event: Dict) -> Dict:
qualifiers = _generic_qualifiers(raw_event)
duel_qualifiers = []
secondary_types = raw_event["type"]["secondary"]
result = DuelResult.LOST

if "ground_duel" in secondary_types:
duel_qualifiers.append(DuelQualifier(value=DuelType.GROUND))
Expand All @@ -313,6 +314,10 @@ def _parse_duel(raw_event: Dict) -> Dict:
DuelQualifier(value=DuelType.AERIAL),
]
)
if raw_event["aerialDuel"]["firstTouch"]:
result = DuelResult.WON
else:
result = DuelResult.LOST
else:
if (
"loose_ball_duel" in secondary_types
Expand Down Expand Up @@ -340,25 +345,20 @@ def _parse_duel(raw_event: Dict) -> Dict:
]
)

qualifiers.extend(duel_qualifiers)
if "offensive_duel" in secondary_types:
duel_qualifiers.append(DuelQualifier(value=DuelType.OFFENSIVE))
if raw_event["groundDuel"]["keptPossession"]:
result = DuelResult.WON
else:
result = DuelResult.LOST
elif "defensive_duel" in secondary_types:
duel_qualifiers.append(DuelQualifier(value=DuelType.DEFENSIVE))
if raw_event["groundDuel"]["recoveredPossession"]:
result = DuelResult.WON
else:
result = DuelResult.LOST

if (
"offensive_duel" in secondary_types
and raw_event["groundDuel"]["keptPossession"]
):
result = DuelResult.WON
elif (
"defensive_duel" in secondary_types
and raw_event["groundDuel"]["recoveredPossession"]
):
result = DuelResult.WON
elif (
"aerial_duel" in secondary_types
and raw_event["aerialDuel"]["firstTouch"]
):
result = DuelResult.WON
else:
result = DuelResult.LOST
qualifiers.extend(duel_qualifiers)

return {"result": result, "qualifiers": qualifiers}

Expand Down
3 changes: 3 additions & 0 deletions kloppy/tests/test_opta.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ def test_correct_deserialization(self, f7_data: str, f24_data: str):
dataset.events[7].get_qualifier_values(DuelQualifier)[1].value
== DuelType.AERIAL
)
assert DuelQualifier(value=DuelType.DEFENSIVE) in dataset.events[
7
].get_qualifier_values(DuelQualifier)
assert (
dataset.events[8].get_qualifier_values(DuelQualifier)[1].value
== DuelType.GROUND
Expand Down
6 changes: 6 additions & 0 deletions kloppy/tests/test_statsbomb.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ def test_correct_deserialization(
dataset.events[194].get_qualifier_values(DuelQualifier)[1].value
== DuelType.AERIAL
)
assert DuelQualifier(value=DuelType.DEFENSIVE) in dataset.events[
194
].get_qualifier_values(DuelQualifier)
assert DuelQualifier(value=DuelType.OFFENSIVE) in dataset.events[
195
].get_qualifier_values(DuelQualifier)
assert (
dataset.events[307].get_qualifier_values(DuelQualifier)[0].value
== DuelType.GROUND
Expand Down
3 changes: 3 additions & 0 deletions kloppy/tests/test_wyscout.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def test_correct_v3_deserialization(self, event_v3_data: Path):
dataset.events[6].get_qualifier_value(DuelQualifier)
== DuelType.GROUND
)
assert DuelQualifier(value=DuelType.OFFENSIVE) in dataset.events[
6
].get_qualifier_values(DuelQualifier)
assert (
dataset.events[7].get_qualifier_values(DuelQualifier)[1].value
== DuelType.AERIAL
Expand Down

0 comments on commit 0f22b0b

Please sign in to comment.