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 StatsBomb shot result coordinates #232

Merged
Merged
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
34 changes: 28 additions & 6 deletions kloppy/infra/serializers/event/statsbomb/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,24 @@ def _parse_coordinates(
# +-----+------+
cell_side = 0.1 if fidelity_version == 2 else 1.0
cell_relative_center = cell_side / 2
return Point(
x=coordinates[0] - cell_relative_center,
y=coordinates[1] - cell_relative_center,
)
if len(coordinates) == 2:
return Point(
x=coordinates[0] - cell_relative_center,
y=coordinates[1] - cell_relative_center,
)
elif len(coordinates) == 3:
# A coordinate in the goal frame, only used for the end location of
# Shot events. The y-coordinates and z-coordinates are always detailed
# to a tenth of a yard.
return Point3D(
x=coordinates[0] - cell_relative_center,
y=coordinates[1] - 0.05,
z=coordinates[2] - 0.05,
)
else:
raise DeserializationError(
f"Unknown coordinates format: {coordinates}"
)


def _get_body_part_qualifiers(
Expand Down Expand Up @@ -385,7 +399,7 @@ def _parse_pass(pass_dict: Dict, team: Team, fidelity_version: int) -> Dict:
}


def _parse_shot(shot_dict: Dict) -> Dict:
def _parse_shot(shot_dict: Dict, fidelity_version: int) -> Dict:
outcome_id = shot_dict["outcome"]["id"]
if outcome_id == SB_SHOT_OUTCOME_OFF_TARGET:
result = ShotResult.OFF_TARGET
Expand All @@ -412,7 +426,14 @@ def _parse_shot(shot_dict: Dict) -> Dict:
body_part_qualifiers = _get_body_part_qualifiers(shot_dict)
qualifiers.extend(body_part_qualifiers)

return {"result": result, "qualifiers": qualifiers}
return {
"result": result,
"qualifiers": qualifiers,
"result_coordinates": _parse_coordinates(
shot_dict["end_location"],
fidelity_version,
),
}


def _parse_freeze_frame(
Expand Down Expand Up @@ -903,6 +924,7 @@ def deserialize(self, inputs: StatsBombInputs) -> EventDataset:
elif event_type == SB_EVENT_TYPE_SHOT:
shot_event_kwargs = _parse_shot(
shot_dict=raw_event["shot"],
fidelity_version=shot_fidelity_version,
)
shot_event = self.event_factory.build_shot(
**shot_event_kwargs,
Expand Down
31 changes: 31 additions & 0 deletions kloppy/tests/test_statsbomb.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
Orientation,
Period,
Point,
Point3D,
Provider,
FormationType,
SetPieceQualifier,
SetPieceType,
Position,
ShotResult,
)
Expand Down Expand Up @@ -255,6 +258,34 @@ def test_foul_committed(self, lineup_data: Path, event_data: Path):

assert len(dataset.events) == 23

def test_shot(self, lineup_data: Path, event_data: Path):
"""
Test shot events
"""
dataset = statsbomb.load(
lineup_data=lineup_data,
event_data=event_data,
event_types=["shot"],
coordinates="statsbomb",
)

assert len(dataset.events) == 29

shot = dataset.get_event_by_id("39f231e5-0072-461c-beb0-a9bedb420f83")
# A shot event should have a result
assert shot.result == ShotResult.POST
# A shot event should have end coordinates
assert shot.result_coordinates == Point3D(119.95, 42.95, 2.75)
# A shot event should have a body part
assert (
shot.get_qualifier_value(BodyPartQualifier) == BodyPart.LEFT_FOOT
)
# A shot event can have set piece qualifiers
assert (
shot.get_qualifier_value(SetPieceQualifier)
== SetPieceType.FREE_KICK
)

def test_own_goal(self, lineup_data: Path, event_data: Path):
"""
Test own goal events.
Expand Down