import streamlit as st import pandas as pd from st_image_carousel import image_carousel from st_circular_kpi import circular_kpi import base64 from analytics.scoring import get_player_match_scores import plotly.express as px def calculate_stats(df, column, agg_func='sum', round_digits=1): """Calcule min, max, mean pour une colonne groupée par joueur""" stats = df.groupby('Nom').agg({column: agg_func}) return { 'min': float(stats.min().round(round_digits).values[0]), 'max': float(stats.max().round(round_digits).values[0]), 'mean': float(stats.mean().round(round_digits).values[0]) } def show_player_analysis(df): """Composant simple pour l'analyse des joueuses""" # Calcul des statistiques globales actions_stats = calculate_stats(df, 'Nb_actions', 'sum') matches_stats = calculate_stats(df, 'Match', 'nunique') # Calcul des moyennes par match player_avg_stats = df.groupby('Nom').agg({ 'Nb_actions': 'sum', 'Match': 'nunique' }).assign( avg_per_match=lambda x: x['Nb_actions'] / x['Match'] ) avg_per_match_stats = { 'min': float(player_avg_stats['avg_per_match'].min()), 'max': float(player_avg_stats['avg_per_match'].max()), 'mean': float(player_avg_stats['avg_per_match'].mean().round(1)) } # Créer un dictionnaire avec les noms uniques des joueurs joueurs_images = [ { "name": f"{row['Prenom']} {row['Nom']}", "url": "" } for _, row in sorted(df[['Nom', 'Prenom']].drop_duplicates().iterrows(), key=lambda x: (x[1]['Nom'], x[1]['Prenom'])) ] # Créer un dictionnaire avec les noms uniques des matchs matchs_images = [ { "name": match_name, "url": "" } for match_name in sorted(df['Match'].unique()) ] result = image_carousel( images=joueurs_images, selected_image=None, background_color="#ffffff", active_border_color="#000000", active_glow_color="rgba(0, 0, 0, 0.7)", fallback_background="#ffffff", fallback_gradient_end="#ffffff", text_color="#000000", arrow_color="#31333f", key="player_carousel" ) # Liste des joueuses if result and result.get('selected_image'): selected_player = result['selected_image'].split(" ")[1] else: selected_player = None # Tableau des données if selected_player: # Filtrer les données pour la joueuse sélectionnée player_data = df[df['Nom'] == selected_player] # Créer une liste de matchs filtrée pour cette joueuse (seulement les matchs où elle a participé) player_matches = player_data.groupby('Match')['Nb_actions'].sum() player_matches = player_matches[player_matches > 0].index.tolist() # Seulement les matchs avec des actions matchs_images_filtered = [ { "name": match_name, "url": "" } for match_name in sorted(player_matches) ] # Statistiques du joueur total_actions = player_data['Nb_actions'].sum() nb_matchs = player_data['Match'].nunique() avg_per_match = total_actions / nb_matchs if nb_matchs > 0 else 0 # Récupérer les scores de la joueuse sélectionnée player_scores = get_player_match_scores(df) player_scores_filtered = player_scores[ (player_scores['Prenom'] == player_data['Prenom'].iloc[0]) & (player_scores['Nom'] == selected_player) ].sort_values('Match') # st.write(player_scores_filtered) # Statistiques simples col1, col2, col3, col4 = st.columns(4) with col1: circular_kpi( value=total_actions, label="Actions", range=(-10, actions_stats['max']), min_value=actions_stats['min'], max_value=actions_stats['max'], mean_value=actions_stats['mean'], color_scheme="blue_purple", background_color="transparent", key="actions_kpi" ) with col2: circular_kpi( value=nb_matchs, label="Matchs", range=(0, matches_stats['max']), min_value=matches_stats['min'], max_value=matches_stats['max'], mean_value=matches_stats['mean'], color_scheme="red", background_color="transparent", key="matches_kpi" ) with col3: circular_kpi( value=avg_per_match.round(1), label="Moyenne actions par match", range=(0, avg_per_match_stats['max']), min_value=avg_per_match_stats['min'], max_value=avg_per_match_stats['max'], mean_value=avg_per_match_stats['mean'], color_scheme="green", key="avg_per_match_kpi" ) with col4: # Calculer les statistiques globales sur les moyennes par joueuse all_player_scores = get_player_match_scores(df) # Calculer la moyenne par joueuse player_averages = all_player_scores.groupby(['Prenom', 'Nom'])['note_match_joueuse'].mean().reset_index() global_score_stats = { 'min': float(player_averages['note_match_joueuse'].min().round(1)), 'max': float(player_averages['note_match_joueuse'].max().round(1)), 'mean': float(player_averages['note_match_joueuse'].mean().round(1)) } circular_kpi( value=player_scores_filtered['note_match_joueuse'].mean().round(1), label="Note moyenne", range=(0, 100), min_value=global_score_stats['min'], max_value=global_score_stats['max'], mean_value=global_score_stats['mean'], color_scheme="blue_purple", key="note_moyenne_kpi" ) # st.metric("Note moyenne", f"{player_scores_filtered['note_match_joueuse'].mean():.1f}") #note de la joueuse moyenne sur tous les matchs if not player_scores_filtered.empty: # Créer un graphique des scores par match fig = px.line( player_scores_filtered, x='Match', y='note_match_joueuse', markers=True, color_discrete_sequence=['black'] # Ligne noire ) fig.update_layout( height=400, # Moins haut showlegend=False, xaxis_title="", # Pas de titre axe X yaxis_title="Note" # Pas de titre axe Y ) # Ajouter une ligne de moyenne avg_score = player_scores_filtered['note_match_joueuse'].mean() fig.add_hline( y=avg_score, line_dash="dash", line_color="red", annotation_text=f"Moyenne: {avg_score:.1f}", annotation_position="top right" ) # Centrer le graphique col1, col2, col3 = st.columns([1, 4, 2]) with col2: st.plotly_chart(fig, use_container_width=False, key="scores_evolution_chart") else: st.info("Aucun score disponible pour cette joueuse.") st.divider() # Définir les colonnes col_stats, col_match = st.columns([4, 1]) with col_match: result_match = image_carousel( images=matchs_images_filtered, selected_image=None, background_color="#ffffff", active_border_color="#000000", active_glow_color="rgba(0, 0, 0, 0.7)", fallback_background="#ffffff", fallback_gradient_end="#ffffff", text_color="#000000", arrow_color="#31333f", orientation="vertical", key="match_carousel" ) # Récupérer le match sélectionné pour le filtrage selected_match = None if result_match and result_match.get('selected_image'): selected_match = result_match['selected_image'] with col_stats: # Filtrer les données selon le match sélectionné display_data = player_data if selected_match: # display_data = player_data[player_data['Match'] == selected_match] display_data = player_data[player_data['Match'] == selected_match] # st.write(display_data) # Recalculer les statistiques avec les données filtrées filtered_total_actions = display_data['Nb_actions'].sum() filtered_nb_matchs = display_data['Match'].nunique() filtered_avg_per_match = filtered_total_actions / filtered_nb_matchs if filtered_nb_matchs > 0 else 0 # Calculer les statistiques POUR CETTE JOUEUSE SEULEMENT player_match_stats = player_data.groupby('Match')['Nb_actions'].sum() player_actions_stats = { 'min': float(player_match_stats.min()), 'max': float(player_match_stats.max()), 'mean': float(player_match_stats.mean().round(1)) } col_actions, col_matchs, col_avg_per_match = st.columns(3) with col_actions: circular_kpi( value=filtered_total_actions, label="Actions", range=(0, player_actions_stats['max']), min_value=player_actions_stats['min'], max_value=player_actions_stats['max'], mean_value=player_actions_stats['mean'], color_scheme="blue_purple", background_color="transparent", key="actions_kpi_by_match" ) with col_matchs: # Calculer le nombre d'actions par niveau pour ce match actions_by_level = display_data.groupby('Niveau')['Nb_actions'].sum().reset_index() # Créer un DataFrame avec tous les niveaux (0, 1, 2, 3) même s'ils n'existent pas all_levels = pd.DataFrame({'Niveau': [0, 1, 2, 3]}) actions_by_level = all_levels.merge(actions_by_level, on='Niveau', how='left').fillna(0) fig = px.bar( actions_by_level, x='Niveau', y='Nb_actions', color='Niveau', color_discrete_map={ 0: '#ff6b6b', # Rouge pour niveau 0 1: '#4ecdc4', # Turquoise pour niveau 1 2: '#45b7d1', # Bleu pour niveau 2 3: '#96ceb4' # Vert pour niveau 3 } ) # Supprimer complètement la barre de couleur fig.update_layout( showlegend=False, coloraxis_showscale=False, height=300 # Réduire la hauteur du graphique ) st.plotly_chart(fig, use_container_width=True, key="niveau_actions_chart") # st.metric((display_data['Niveau']*display_data['Nb_actions']).sum()/display_data['Nb_actions'].sum()) with col_avg_per_match: match_note = player_scores_filtered[player_scores_filtered['Match'] == selected_match]['note_match_joueuse'].mean() if pd.notna(match_note): match_note = round(match_note, 1) else: match_note = 0.0 circular_kpi( value=match_note, label="Note du match", range=(0, 100) ) # Boucle sur les types d'actions avec niveau non-null uniquement # Filtrer d'abord les données avec niveau non-null actions_with_level = display_data[display_data['Niveau'].notna()] filtered_total_actions_by_action = actions_with_level.groupby('Action')['Nb_actions'].sum() # Calculer le score total du match (somme de tous les scores d'actions) total_match_score = 0 for action, value in filtered_total_actions_by_action.items(): action_data = display_data[display_data['Action'] == action] actions_by_level = action_data.groupby('Niveau')['Nb_actions'].sum().reset_index() # Créer un DataFrame avec tous les niveaux (0, 1, 2, 3) même s'ils n'existent pas all_levels = pd.DataFrame({'Niveau': [0, 1, 2, 3]}) actions_by_level = all_levels.merge(actions_by_level, on='Niveau', how='left').fillna(0) # Calculer le score pour cette action if actions_by_level['Nb_actions'].sum() > 0: score_action = (actions_by_level['Niveau']*actions_by_level['Nb_actions']).sum()/actions_by_level['Nb_actions'].sum() total_match_score += score_action # Multiplier par 100 pour obtenir le pourcentage total_match_score = total_match_score # Dictionnaire des descriptions des niveaux par type d'action level_descriptions = { 'DUEL': { 0: 'RECULE/PERTE', 1: 'N\'AVANCE PAS', 2: 'AVANCE/PASSE', 3: 'PLAGE CASSÉ' }, 'PASSE': { 0: 'MANQUEE+PERTE', 1: 'MANQUEE', 2: 'PASSE COURSE/MAINS', 3: 'PASSE COURSE+MAINS' }, 'JAP': { 0: 'CONTRE/GOBÉ', 1: 'REBOND', 2: 'GAIN TERRAIN', 3: 'GAIN TERRAIN+POS' }, 'PLAQUAGE': { 0: 'MANQUÉ', 1: 'SUBI', 2: 'NEUTRE', 3: 'DOMINANT' }, 'RUCK': { 0: 'INSPECTEUR', 1: 'LENT 5+S', 2: 'RAPIDE 3-4S', 3: 'TRÈS RAPIDE 1-2S' }, 'RECEPTION JAP': { 0: 'REBOND/PERTE', 1: 'REBOND', 2: 'MAUVAIS GOBE', 3: 'GOBE' } } for action, value in filtered_total_actions_by_action.items(): # Calculer les statistiques POUR CETTE JOUEUSE ET CE TYPE D'ACTION sur tous les matchs player_action_stats = player_data.groupby(['Match', 'Action'])['Nb_actions'].sum().reset_index() player_action_stats = player_action_stats[player_action_stats['Action'] == action] # Statistiques pour ce type d'action de cette joueuse action_stats = { 'min': float(player_action_stats['Nb_actions'].min()) if len(player_action_stats) > 0 else 0, 'max': float(player_action_stats['Nb_actions'].max()) if len(player_action_stats) > 0 else 0, 'mean': float(round(player_action_stats['Nb_actions'].mean(), 1)) if len(player_action_stats) > 0 else 0 } # Créer 3 colonnes pour chaque type d'action avec niveau col_action1, col_action2, col_action3 = st.columns(3) with col_action1: circular_kpi( value=value, label=f"{action}", range=(0, action_stats['max']), min_value=action_stats['min'], max_value=action_stats['max'], mean_value=action_stats['mean'], color_scheme="blue_purple", background_color="transparent", key=f"actions_kpi_{action}" ) with col_action2: # Calculer le nombre d'actions par niveau pour cette action action_data = display_data[display_data['Action'] == action] actions_by_level = action_data.groupby('Niveau')['Nb_actions'].sum().reset_index() # Créer un DataFrame avec tous les niveaux (0, 1, 2, 3) même s'ils n'existent pas all_levels = pd.DataFrame({'Niveau': [0, 1, 2, 3]}) actions_by_level = all_levels.merge(actions_by_level, on='Niveau', how='left').fillna(0) # Ajouter les descriptions des niveaux if action in level_descriptions: actions_by_level['Niveau_Desc'] = actions_by_level['Niveau'].map(level_descriptions[action]) else: actions_by_level['Niveau_Desc'] = actions_by_level['Niveau'].astype(str) fig = px.bar( actions_by_level, x='Niveau_Desc', y='Nb_actions', color='Niveau', color_discrete_map={ 0: '#ff6b6b', # Rouge pour niveau 0 1: '#4ecdc4', # Turquoise pour niveau 1 2: '#45b7d1', # Bleu pour niveau 2 3: '#96ceb4' # Vert pour niveau 3 } ) # Supprimer complètement la barre de couleur fig.update_layout( showlegend=False, coloraxis_showscale=False, height=300, # Réduire la hauteur du graphique xaxis_title="", # Pas de titre axe X yaxis_title="" # Pas de titre axe Y ) st.plotly_chart(fig, use_container_width=True, key=f"bar_chart_{action}") with col_action3: # st.write(actions_by_level) # Calculer le score pour cette action spécifique score_action = (actions_by_level['Niveau']*actions_by_level['Nb_actions']).sum()/actions_by_level['Nb_actions'].sum() score_action_percent = score_action/total_match_score*100 # st.write(actions_by_level) # st.write((actions_by_level['Niveau']*actions_by_level['Nb_actions']).sum()) # st.write(actions_by_level['Nb_actions'].sum()) # st.write((actions_by_level['Niveau']*actions_by_level['Nb_actions']).sum()/actions_by_level['Nb_actions'].sum()) # st.write((actions_by_level['Niveau']*actions_by_level['Nb_actions']).sum()/actions_by_level['Nb_actions'].sum()*100/33) circular_kpi( value=score_action_percent.round(1), label=f"de la note", range=(0, 100), unit="%", key=f"note_kpi_{action}" ) # Maintenant afficher les actions avec niveau null (sur la même ligne) actions_without_level = display_data[display_data['Niveau'].isna()] filtered_total_actions_by_action_null = actions_without_level.groupby('Action')['Nb_actions'].sum() st.divider() if len(filtered_total_actions_by_action_null) > 0: # Créer autant de colonnes que d'actions sans niveau num_null_actions = len(filtered_total_actions_by_action_null) if num_null_actions > 0: # Créer les colonnes dynamiquement cols = st.columns(num_null_actions) for i, (action, value) in enumerate(filtered_total_actions_by_action_null.items()): # Calculer les statistiques POUR CETTE JOUEUSE ET CE TYPE D'ACTION sur tous les matchs player_action_stats = player_data.groupby(['Match', 'Action'])['Nb_actions'].sum().reset_index() player_action_stats = player_action_stats[player_action_stats['Action'] == action] # Statistiques pour ce type d'action de cette joueuse action_stats = { 'min': float(player_action_stats['Nb_actions'].min()) if len(player_action_stats) > 0 else 0, 'max': float(player_action_stats['Nb_actions'].max()) if len(player_action_stats) > 0 else 0, 'mean': float(player_action_stats['Nb_actions'].mean().round(1)) if len(player_action_stats) > 0 else 0 } # Afficher le KPI dans la colonne correspondante with cols[i]: circular_kpi( value=value, label=f"{action}", range=(0, action_stats['max']), min_value=action_stats['min'], max_value=action_stats['max'], mean_value=action_stats['mean'], color_scheme="green", background_color="transparent", key=f"actions_kpi_null_{action}" )