Stade_U18 / streamlit_app /components /player_analysis.py
2nzi's picture
update palyer analysis
d2bdda3 verified
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}"
)