Skip to content

Commit

Permalink
Refactor of event_dfs in Match class (#213)
Browse files Browse the repository at this point in the history
* refactor of event_dfs in Match class

* Added test for match utils
  • Loading branch information
Alek050 committed Apr 17, 2024
1 parent 9c05b84 commit 3cd8741
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 163 deletions.
17 changes: 17 additions & 0 deletions databallpy/events/base_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class BaseOnBallEvent:
that is trained on the distance and angle to the goal, and the distance
times theangle to the goal. See the notebook in the notebooks folder for
more information on the model.
base_df_attributes (list[str]): list of attributes that are used to create a
DataFrame
"""

event_id: int
Expand Down Expand Up @@ -96,6 +98,21 @@ def xT(self) -> float:

return self._xt

@property
def base_df_attributes(self) -> list[str]:
return [
"event_id",
"period_id",
"minutes",
"seconds",
"datetime",
"start_x",
"start_y",
"team_id",
"team_side",
"xT",
]

def __post_init__(self):
if not isinstance(self.event_id, (np.integer, int)):
raise TypeError(f"event_id should be int, not {type(self.event_id)}")
Expand Down
13 changes: 13 additions & 0 deletions databallpy/events/dribble_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class DribbleEvent(BaseOnBallEvent):
that is trained on the distance and angle to the goal, and the distance
times the angle to the goal. See the notebook in the notebooks folder for
more information on the model.
df_attributes (list[str]): list of attributes that are used to create a
DataFrame
Raises:
TypeError: when one of the input arguments is of the wrong type
Expand Down Expand Up @@ -69,6 +71,17 @@ def __eq__(self, other):
]
return all(result)

@property
def df_attributes(self) -> list[str]:
base_attributes = super().base_df_attributes
return base_attributes + [
"player_id",
"related_event_id",
"duel_type",
"outcome",
"has_opponent",
]

def copy(self):
return DribbleEvent(
event_id=self.event_id,
Expand Down
23 changes: 23 additions & 0 deletions databallpy/events/pass_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class PassEvent(BaseOnBallEvent):
model that is trained on the distance and angle to the goal, and the
distance times the angle to the goal. See the notebook in the notebooks
folder for more information on the model.
df_attributes (list[str]): list of attributes that are used to create a
DataFrame.
Raises:
TypeError: If any of the inputtypes is not correct
Expand Down Expand Up @@ -297,6 +299,27 @@ def __post_init__(self):
)
_ = self._xt

@property
def df_attributes(self) -> list[str]:
base_attributes = super().base_df_attributes
return base_attributes + [
"outcome",
"player_id",
"end_x",
"end_y",
"pass_type",
"set_piece",
"receiver_id",
"pass_length",
"forward_distance",
"passer_goal_distance",
"pass_end_loc_goal_distance",
"opponents_in_passing_lane",
"pressure_on_passer",
"pressure_on_receiver",
"pass_goal_angle",
]


def get_opponents_in_passing_lane(
frame: pd.Series,
Expand Down
27 changes: 27 additions & 0 deletions databallpy/events/shot_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class ShotEvent(BaseOnBallEvent):
is trained on the distance and angle to the goal, and the distance times
the angle to the goal. See the notebook in the notebooks folder for more
information on the model.
df_attributes (list[str]): list of attributes that are used to create a
DataFrame.
Returns:
ShotEvent: instance of the ShotEvent class
Expand Down Expand Up @@ -118,6 +120,31 @@ def __post_init__(self):
self.set_piece = self.type_of_play
_ = self._xt

@property
def df_attributes(self) -> list[str]:
base_attributes = super().base_df_attributes
return base_attributes + [
"player_id",
"shot_outcome",
"y_target",
"z_target",
"body_part",
"type_of_play",
"first_touch",
"created_oppertunity",
"related_event_id",
"ball_goal_distance",
"ball_gk_distance",
"shot_angle",
"gk_optimal_loc_distance",
"pressure_on_ball",
"n_obstructive_players",
"n_obstructive_defenders",
"goal_gk_distance",
"xG",
"set_piece",
]

def add_tracking_data_features(
self,
tracking_data_frame: pd.Series,
Expand Down
150 changes: 10 additions & 140 deletions databallpy/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from databallpy.utils.errors import DataBallPyError
from databallpy.utils.logging import create_logger
from databallpy.utils.match_utils import (
create_event_attributes_dataframe,
player_column_id_to_full_name,
player_id_to_column_id,
)
Expand Down Expand Up @@ -237,7 +238,8 @@ def shots_df(self) -> pd.DataFrame:
"""Function to get all shots in the match
Returns:
pd.DataFrame: DataFrame with all information of the shots in the match"""
pd.DataFrame: DataFrame with all information of the shots in the match
"""

if self._shots_df is None:
LOGGER.info("Creating the match._shots_df dataframe in match.shots_df")
Expand All @@ -248,58 +250,9 @@ def shots_df(self) -> pd.DataFrame:
self.synchronise_tracking_and_event_data(n_batches="smart")
if self.is_synchronised:
self.add_tracking_data_features_to_shots()
res_dict = {
"event_id": [shot.event_id for shot in self.shot_events.values()],
"player_id": [shot.player_id for shot in self.shot_events.values()],
"period_id": [shot.period_id for shot in self.shot_events.values()],
"minutes": [shot.minutes for shot in self.shot_events.values()],
"seconds": [shot.seconds for shot in self.shot_events.values()],
"datetime": [shot.datetime for shot in self.shot_events.values()],
"start_x": [shot.start_x for shot in self.shot_events.values()],
"start_y": [shot.start_y for shot in self.shot_events.values()],
"team_id": [shot.team_id for shot in self.shot_events.values()],
"shot_outcome": [
shot.shot_outcome for shot in self.shot_events.values()
],
"y_target": [shot.y_target for shot in self.shot_events.values()],
"z_target": [shot.z_target for shot in self.shot_events.values()],
"body_part": [shot.body_part for shot in self.shot_events.values()],
"type_of_play": [
shot.type_of_play for shot in self.shot_events.values()
],
"first_touch": [shot.first_touch for shot in self.shot_events.values()],
"created_oppertunity": [
shot.created_oppertunity for shot in self.shot_events.values()
],
"related_event_id": [
shot.related_event_id for shot in self.shot_events.values()
],
"ball_goal_distance": [
shot.ball_goal_distance for shot in self.shot_events.values()
],
"ball_gk_distance": [
shot.ball_gk_distance for shot in self.shot_events.values()
],
"shot_angle": [shot.shot_angle for shot in self.shot_events.values()],
"gk_optimal_loc_distance": [
shot.gk_optimal_loc_distance for shot in self.shot_events.values()
],
"pressure_on_ball": [
shot.pressure_on_ball for shot in self.shot_events.values()
],
"n_obstructive_players": [
shot.n_obstructive_players for shot in self.shot_events.values()
],
"n_obstructive_defenders": [
shot.n_obstructive_defenders for shot in self.shot_events.values()
],
"goal_gk_distance": [
shot.goal_gk_distance for shot in self.shot_events.values()
],
"xG": [shot.xG for shot in self.shot_events.values()],
"xT": [shot.xT for shot in self.shot_events.values()],
}
self._shots_df = pd.DataFrame(res_dict)

self._shots_df = create_event_attributes_dataframe(self.shot_events)

LOGGER.info(
"Successfully created match._shots_df dataframe in match.shots_df"
)
Expand All @@ -318,49 +271,8 @@ def dribbles_df(self) -> pd.DataFrame:
LOGGER.info(
"Creating the match._dribbles_df dataframe in match.dribbles_df"
)
res_dict = {
"event_id": [
dribble.event_id for dribble in self.dribble_events.values()
],
"player_id": [
dribble.player_id for dribble in self.dribble_events.values()
],
"period_id": [
dribble.period_id for dribble in self.dribble_events.values()
],
"minutes": [
dribble.minutes for dribble in self.dribble_events.values()
],
"seconds": [
dribble.seconds for dribble in self.dribble_events.values()
],
"datetime": [
dribble.datetime for dribble in self.dribble_events.values()
],
"start_x": [
dribble.start_x for dribble in self.dribble_events.values()
],
"start_y": [
dribble.start_y for dribble in self.dribble_events.values()
],
"team_id": [
dribble.team_id for dribble in self.dribble_events.values()
],
"related_event_id": [
dribble.related_event_id for dribble in self.dribble_events.values()
],
"duel_type": [
dribble.duel_type for dribble in self.dribble_events.values()
],
"outcome": [
dribble.outcome for dribble in self.dribble_events.values()
],
"has_opponent": [
dribble.has_opponent for dribble in self.dribble_events.values()
],
"xT": [dribble.xT for dribble in self.dribble_events.values()],
}
self._dribbles_df = pd.DataFrame(res_dict)
self._dribbles_df = create_event_attributes_dataframe(self.dribble_events)

LOGGER.info(
"Successfully created match._dribbles_df dataframe in match.dribbles_df"
)
Expand All @@ -377,50 +289,8 @@ def passes_df(self) -> pd.DataFrame:

if self._passes_df is None:
LOGGER.info("Creating the match._passes_df dataframe in match.passes_df")
res_dict = {
"event_id": [pass_.event_id for pass_ in self.pass_events.values()],
"player_id": [pass_.player_id for pass_ in self.pass_events.values()],
"period_id": [pass_.period_id for pass_ in self.pass_events.values()],
"minutes": [pass_.minutes for pass_ in self.pass_events.values()],
"seconds": [pass_.seconds for pass_ in self.pass_events.values()],
"datetime": [pass_.datetime for pass_ in self.pass_events.values()],
"start_x": [pass_.start_x for pass_ in self.pass_events.values()],
"start_y": [pass_.start_y for pass_ in self.pass_events.values()],
"team_id": [pass_.team_id for pass_ in self.pass_events.values()],
"outcome": [pass_.outcome for pass_ in self.pass_events.values()],
"end_x": [pass_.end_x for pass_ in self.pass_events.values()],
"end_y": [pass_.end_y for pass_ in self.pass_events.values()],
"pass_length": [
pass_.pass_length for pass_ in self.pass_events.values()
],
"pass_type": [pass_.pass_type for pass_ in self.pass_events.values()],
"set_piece": [pass_.set_piece for pass_ in self.pass_events.values()],
"forward_distance": [
pass_.forward_distance for pass_ in self.pass_events.values()
],
"passer_goal_distance": [
pass_.passer_goal_distance for pass_ in self.pass_events.values()
],
"pass_end_loc_goal_distance": [
pass_.pass_end_loc_goal_distance
for pass_ in self.pass_events.values()
],
"opponents_in_passing_lane": [
pass_.opponents_in_passing_lane
for pass_ in self.pass_events.values()
],
"pressure_on_passer": [
pass_.pressure_on_passer for pass_ in self.pass_events.values()
],
"pressure_on_receiver": [
pass_.pressure_on_receiver for pass_ in self.pass_events.values()
],
"pass_goal_angle": [
pass_.pass_goal_angle for pass_ in self.pass_events.values()
],
"xT": [pass_.xT for pass_ in self.pass_events.values()],
}
self._passes_df = pd.DataFrame(res_dict)
self._passes_df = create_event_attributes_dataframe(self.pass_events)

LOGGER.info(
"Successfully created match._passes_df dataframe in match.passes_df"
)
Expand Down
21 changes: 21 additions & 0 deletions databallpy/utils/match_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pandas as pd

from databallpy.events import DribbleEvent, PassEvent, ShotEvent


def player_column_id_to_full_name(
home_players: pd.DataFrame, away_players: pd.DataFrame, column_id: str
Expand Down Expand Up @@ -46,3 +48,22 @@ def player_id_to_column_id(
return f"away_{num}"
else:
raise ValueError(f"{player_id} is not in either one of the teams")


def create_event_attributes_dataframe(
events: dict[str | int, ShotEvent | PassEvent | DribbleEvent]
) -> pd.DataFrame:
"""Function to create a DataFrame from a dictionary of events
Args:
events (dict[str | int, ShotEvent | PassEvent | DribbleEvent]):
The dictionary of events
Returns:
pd.DataFrame: DataFrame with the attributes of the events
"""
attributes = list(events.values())[0].df_attributes
res_dict = {
attr: [getattr(event, attr) for event in events.values()] for attr in attributes
}
return pd.DataFrame(res_dict)

0 comments on commit 3cd8741

Please sign in to comment.