import pandas as pd def check_columns(df, required_columns): """ Vérifie si les colonnes requises sont présentes dans le DataFrame. Parameters: - df (pd.DataFrame): Le DataFrame à vérifier. - required_columns (list): Liste des noms de colonnes requis. Returns: - bool: True si toutes les colonnes sont présentes, sinon False. """ return all(column in df.columns for column in required_columns) class StatsManager: def __init__(self, events): self.events = events def calculate_stat(self, required_columns, func): """ Vérifie si les colonnes nécessaires sont présentes, puis applique la fonction de calcul si elles sont présentes. Parameters: - required_columns (list): Liste des colonnes requises. - func (callable): Fonction à appliquer si les colonnes sont présentes. Returns: - tuple: Les résultats de la fonction si les colonnes sont présentes, sinon (None, None). """ if check_columns(self.events, required_columns): return func(self.events) else: return None, None def get_possession(self): return self.calculate_stat(['possession_team'], self._calculate_possession) def _calculate_possession(self, events): possession_counts = events['possession_team'].value_counts() if len(possession_counts) < 2: return None, None total_possession = possession_counts.iloc[0] + possession_counts.iloc[1] home_possession = round((possession_counts.iloc[0] / total_possession) * 100, 1) away_possession = round((possession_counts.iloc[1] / total_possession) * 100, 1) return home_possession, away_possession def get_total_xg(self): return self.calculate_stat(['shot_statsbomb_xg', 'team'], self._calculate_total_xg) def _calculate_total_xg(self, events): data = events[['shot_statsbomb_xg', 'team']].dropna(subset=['shot_statsbomb_xg']) if data.empty: return None, None data = data.groupby('team')['shot_statsbomb_xg'].sum() if len(data) < 2: return None, None return round(data.iloc[0], 2), round(data.iloc[1], 2) def get_total_shots(self): return self.calculate_stat(['shot_statsbomb_xg', 'team'], self._calculate_total_shots) def _calculate_total_shots(self, events): data = events[['shot_statsbomb_xg', 'team']].dropna(subset=['shot_statsbomb_xg']) if data.empty: return None, None shot_counts = data.groupby('team').count()['shot_statsbomb_xg'] if len(shot_counts) < 2: return None, None return shot_counts.iloc[0], shot_counts.iloc[1] def get_total_shots_off_target(self): return self.calculate_stat(['shot_outcome', 'team'], self._calculate_total_shots_off_target) def _calculate_total_shots_off_target(self, events): off_target_outcomes = ['Off T', 'Blocked', 'Missed'] data = events[events['shot_outcome'].isin(off_target_outcomes)] if data.empty: return None, None off_target_counts = data.groupby('team').size() if len(off_target_counts) < 2: return None, None return off_target_counts.iloc[0], off_target_counts.iloc[1] def get_total_shots_on_target(self): return self.calculate_stat(['shot_outcome', 'team'], self._calculate_total_shots_on_target) def _calculate_total_shots_on_target(self, events): on_target_outcomes = ['Goal', 'Saved', 'Saved To Post', 'Shot Saved Off Target'] data = events[events['shot_outcome'].isin(on_target_outcomes)] if data.empty: return None, None on_target_counts = data.groupby('team').size() if len(on_target_counts) < 2: return None, None return on_target_counts.iloc[0], on_target_counts.iloc[1] def get_total_passes(self): return self.calculate_stat(['pass_end_location', 'team'], self._calculate_total_passes) def _calculate_total_passes(self, events): pass_counts = events.filter(regex='^pass_end_location|^team$').groupby('team').count()['pass_end_location'] if len(pass_counts) < 2: return None, None return pass_counts.iloc[0], pass_counts.iloc[1] def get_successful_passes(self): return self.calculate_stat(['pass_outcome', 'team'], self._calculate_successful_passes) def _calculate_successful_passes(self, events): home_passes, away_passes = self.get_total_passes() unsuccessful_passes = events.filter(regex='^pass_outcome|^team$').groupby('team').count().reset_index()['pass_outcome'] if len(unsuccessful_passes) < 2: return None, None home_unsuccessful_passes = unsuccessful_passes.iloc[0] away_unsuccessful_passes = unsuccessful_passes.iloc[1] return home_passes - home_unsuccessful_passes, away_passes - away_unsuccessful_passes def get_total_corners(self): return self.calculate_stat(['pass_type', 'team'], self._calculate_total_corners) def _calculate_total_corners(self, events): corner_counts = events.filter(regex='^pass_type|^team$').query('pass_type == "Corner"').groupby('team').count()['pass_type'] if len(corner_counts) < 2: return None, None return corner_counts.iloc[0], corner_counts.iloc[1] def get_total_fouls(self): return self.calculate_stat(['type', 'team'], self._calculate_total_fouls) def _calculate_total_fouls(self, events): fouls = events[events['type'] == 'Foul Committed'] foul_counts = fouls.groupby('team').size() if len(foul_counts) < 2: return None, None return foul_counts.iloc[0], foul_counts.iloc[1] def get_total_yellow_cards(self): return self.calculate_stat(['bad_behaviour_card', 'team'], self._calculate_total_yellow_cards) def _calculate_total_yellow_cards(self, events): yellow_card_counts = events.filter(regex='^bad_behaviour_card|^team$').query('bad_behaviour_card == "Yellow Card"').groupby('team').count()['bad_behaviour_card'] if len(yellow_card_counts) < 2: return None, None return yellow_card_counts.iloc[0], yellow_card_counts.iloc[1] def get_total_red_cards(self): return self.calculate_stat(['bad_behaviour_card', 'team'], self._calculate_total_red_cards) def _calculate_total_red_cards(self, events): red_card_counts = events.filter(regex='^bad_behaviour_card|^team$').query('bad_behaviour_card == "Red Card"').groupby('team').count()['bad_behaviour_card'] if len(red_card_counts) < 2: return None, None return red_card_counts.iloc[0], red_card_counts.iloc[1]