app
Browse files- app/app.py +46 -0
- app/components/__pycache__/layout.cpython-38.pyc +0 -0
- app/components/__pycache__/visualization.cpython-38.pyc +0 -0
- app/components/charts.py +75 -0
- app/components/layout.py +35 -0
- app/components/visualization.py +209 -0
- app/config/__init__.py +1 -0
- app/config/__pycache__/__init__.cpython-38.pyc +0 -0
- app/config/__pycache__/config.cpython-38.pyc +0 -0
- app/config/config.py +96 -0
- app/utils/__init__.py +0 -0
- app/utils/__pycache__/__init__.cpython-38.pyc +0 -0
- app/utils/__pycache__/data_processing.cpython-38.pyc +0 -0
- app/utils/data_processing.py +77 -0
- data/stats.xlsx +0 -0
app/app.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from utils.data_processing import load_data
|
3 |
+
from components.layout import create_sidebar, create_main_layout
|
4 |
+
from components.visualization import create_pizza_chart, display_metrics, get_max_values_for_ville
|
5 |
+
import pandas as pd
|
6 |
+
|
7 |
+
# Configuration de la page Streamlit
|
8 |
+
st.set_page_config(
|
9 |
+
page_title="Statistiques Rugby",
|
10 |
+
page_icon="🏉",
|
11 |
+
layout="wide"
|
12 |
+
)
|
13 |
+
|
14 |
+
def main():
|
15 |
+
"""Fonction principale de l'application"""
|
16 |
+
st.title("🏉 Analyse des Statistiques Rugby")
|
17 |
+
|
18 |
+
# Chargement des données
|
19 |
+
data = load_data()
|
20 |
+
# Conversion des colonnes numériques
|
21 |
+
numeric_columns = data.select_dtypes(include=['object']).columns
|
22 |
+
for col in numeric_columns:
|
23 |
+
if col != 'name' and col != 'Ville':
|
24 |
+
data[col] = pd.to_numeric(data[col], errors='coerce')
|
25 |
+
|
26 |
+
# Création de l'interface
|
27 |
+
selected_ville, selected_player = create_sidebar(data)
|
28 |
+
col1, col2 = create_main_layout(data, selected_ville)
|
29 |
+
|
30 |
+
with col1:
|
31 |
+
st.subheader(f"Statistiques pour {selected_player} ({selected_ville})")
|
32 |
+
# Récupérer uniquement les valeurs maximales
|
33 |
+
max_values = get_max_values_for_ville(data, selected_ville)
|
34 |
+
fig = create_pizza_chart(data, selected_ville, selected_player, max_values)
|
35 |
+
if fig is not None:
|
36 |
+
st.pyplot(fig)
|
37 |
+
|
38 |
+
with col2:
|
39 |
+
st.subheader("Valeurs maximales pour ce match")
|
40 |
+
if max_values:
|
41 |
+
display_metrics(max_values)
|
42 |
+
else:
|
43 |
+
st.warning("Aucune donnée maximale disponible")
|
44 |
+
|
45 |
+
if __name__ == "__main__":
|
46 |
+
main()
|
app/components/__pycache__/layout.cpython-38.pyc
ADDED
Binary file (1.02 kB). View file
|
|
app/components/__pycache__/visualization.cpython-38.pyc
ADDED
Binary file (5.32 kB). View file
|
|
app/components/charts.py
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
|
3 |
+
def create_pizza_chart(final_df, selected_ville):
|
4 |
+
"""Crée le graphique pizza pour une ville donnée"""
|
5 |
+
ville_data = final_df[final_df['Ville'] == selected_ville].iloc[0]
|
6 |
+
|
7 |
+
params, values, slice_colors = [], [], []
|
8 |
+
max_values = {}
|
9 |
+
|
10 |
+
# Calcul des valeurs maximales
|
11 |
+
for cat_name, cat_info in CATEGORIES.items():
|
12 |
+
cat_values = []
|
13 |
+
for subcat in cat_info['subcats'].keys():
|
14 |
+
if subcat in final_df.columns and pd.notna(ville_data[subcat]):
|
15 |
+
cat_values.append(float(ville_data[subcat]))
|
16 |
+
if cat_values:
|
17 |
+
max_values[cat_name] = max(cat_values)
|
18 |
+
|
19 |
+
# Préparation des données pour le graphique
|
20 |
+
for cat_name, cat_info in CATEGORIES.items():
|
21 |
+
for subcat, display_text in cat_info['subcats'].items():
|
22 |
+
params.append(display_text)
|
23 |
+
if subcat in final_df.columns and pd.notna(ville_data[subcat]):
|
24 |
+
value = float(ville_data[subcat])
|
25 |
+
max_val = max_values[cat_name]
|
26 |
+
normalized_value = (value / max_val * 100) if max_val > 0 else 0
|
27 |
+
values.append(round(normalized_value, 2))
|
28 |
+
else:
|
29 |
+
values.append(0)
|
30 |
+
slice_colors.append(cat_info['color'])
|
31 |
+
|
32 |
+
# Création du graphique
|
33 |
+
text_colors = ["#000000"] * len(params)
|
34 |
+
fig, ax = plt.subplots(figsize=CHART_CONFIG["figsize"])
|
35 |
+
|
36 |
+
# Créer un dictionnaire de configuration sans figsize pour PyPizza
|
37 |
+
pizza_config = {k: v for k, v in CHART_CONFIG.items() if k != "figsize"}
|
38 |
+
|
39 |
+
baker = PyPizza(
|
40 |
+
params=params,
|
41 |
+
**pizza_config
|
42 |
+
)
|
43 |
+
|
44 |
+
fig, ax = baker.make_pizza(
|
45 |
+
values,
|
46 |
+
figsize=(10, 10),
|
47 |
+
color_blank_space="same",
|
48 |
+
slice_colors=slice_colors,
|
49 |
+
value_colors=text_colors,
|
50 |
+
value_bck_colors=slice_colors,
|
51 |
+
blank_alpha=0.4,
|
52 |
+
kwargs_slices=dict(
|
53 |
+
edgecolor="#F2F2F2",
|
54 |
+
zorder=2,
|
55 |
+
linewidth=1
|
56 |
+
),
|
57 |
+
kwargs_params=dict(
|
58 |
+
color="#000000",
|
59 |
+
fontsize=8,
|
60 |
+
va="center"
|
61 |
+
),
|
62 |
+
kwargs_values=dict(
|
63 |
+
color="#000000",
|
64 |
+
fontsize=8,
|
65 |
+
zorder=3,
|
66 |
+
bbox=dict(
|
67 |
+
edgecolor="#000000",
|
68 |
+
facecolor="white",
|
69 |
+
boxstyle="round,pad=0.2",
|
70 |
+
lw=1
|
71 |
+
)
|
72 |
+
)
|
73 |
+
)
|
74 |
+
|
75 |
+
return fig, max_values
|
app/components/layout.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from difflib import get_close_matches
|
3 |
+
|
4 |
+
def create_sidebar(data):
|
5 |
+
"""Crée la barre latérale avec les contrôles"""
|
6 |
+
with st.sidebar:
|
7 |
+
st.header("Configuration")
|
8 |
+
|
9 |
+
# Sélection de la ville
|
10 |
+
selected_ville = st.selectbox(
|
11 |
+
"Sélectionner une ville",
|
12 |
+
options=data['Ville'].unique()
|
13 |
+
)
|
14 |
+
|
15 |
+
# Filtrer les joueurs pour la ville sélectionnée
|
16 |
+
ville_players = data[data['Ville'] == selected_ville]['name'].unique()
|
17 |
+
|
18 |
+
# Sélection du joueur
|
19 |
+
selected_player = st.selectbox(
|
20 |
+
"Sélectionner un joueur",
|
21 |
+
options=ville_players
|
22 |
+
)
|
23 |
+
|
24 |
+
return selected_ville, selected_player
|
25 |
+
|
26 |
+
def create_main_layout(data, selected_ville):
|
27 |
+
"""Crée la disposition principale de l'application"""
|
28 |
+
# Affichage des données brutes dans un expander
|
29 |
+
with st.expander("Voir les données brutes"):
|
30 |
+
st.dataframe(data[data['Ville'] == selected_ville])
|
31 |
+
|
32 |
+
# Création des colonnes pour l'affichage
|
33 |
+
col1, col2 = st.columns([3, 4])
|
34 |
+
|
35 |
+
return col1, col2
|
app/components/visualization.py
ADDED
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import matplotlib.pyplot as plt
|
2 |
+
from mplsoccer import PyPizza
|
3 |
+
import pandas as pd
|
4 |
+
from config.config import CATEGORIES, CHART_CONFIG
|
5 |
+
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
from mplsoccer import PyPizza
|
8 |
+
import pandas as pd
|
9 |
+
import streamlit as st
|
10 |
+
from config.config import CATEGORIES, CHART_CONFIG
|
11 |
+
|
12 |
+
|
13 |
+
def create_pizza_chart(final_df, selected_ville, selected_player, max_values):
|
14 |
+
try:
|
15 |
+
from urllib.request import urlopen
|
16 |
+
from PIL import Image
|
17 |
+
import matplotlib.image as image
|
18 |
+
|
19 |
+
player_data = final_df[(final_df['Ville'] == selected_ville) &
|
20 |
+
(final_df['name'] == selected_player)]
|
21 |
+
|
22 |
+
if player_data.empty:
|
23 |
+
st.warning(f"Aucune donnée trouvée pour {selected_player} dans {selected_ville}")
|
24 |
+
return None
|
25 |
+
|
26 |
+
player_data = player_data.iloc[0]
|
27 |
+
|
28 |
+
params, values, slice_colors = [], [], []
|
29 |
+
min_ranges, max_ranges = [], []
|
30 |
+
|
31 |
+
# Préparation des données pour le graphique
|
32 |
+
for cat_name, cat_info in CATEGORIES.items():
|
33 |
+
for subcat, display_text in cat_info['subcats'].items():
|
34 |
+
params.append(display_text)
|
35 |
+
if subcat in final_df.columns and pd.notna(player_data[subcat]):
|
36 |
+
value = float(player_data[subcat])
|
37 |
+
values.append(value)
|
38 |
+
min_ranges.append(0)
|
39 |
+
max_ranges.append(max_values[cat_name][subcat])
|
40 |
+
else:
|
41 |
+
values.append(0)
|
42 |
+
min_ranges.append(0)
|
43 |
+
max_ranges.append(100)
|
44 |
+
slice_colors.append(cat_info['color'])
|
45 |
+
|
46 |
+
# Création du graphique
|
47 |
+
text_colors = ["#000000"] * len(params)
|
48 |
+
fig, ax = plt.subplots(figsize=CHART_CONFIG["figsize"])
|
49 |
+
|
50 |
+
pizza_config = {k: v for k, v in CHART_CONFIG.items() if k != "figsize"}
|
51 |
+
pizza_config['inner_circle_size'] = 30 # Augmenter la taille du cercle intérieur
|
52 |
+
|
53 |
+
baker = PyPizza(
|
54 |
+
params=params,
|
55 |
+
min_range=min_ranges,
|
56 |
+
max_range=max_ranges,
|
57 |
+
**pizza_config
|
58 |
+
)
|
59 |
+
|
60 |
+
fig, ax = baker.make_pizza(
|
61 |
+
values,
|
62 |
+
figsize=(10, 10),
|
63 |
+
color_blank_space="same",
|
64 |
+
slice_colors=slice_colors,
|
65 |
+
value_colors=text_colors,
|
66 |
+
value_bck_colors=slice_colors,
|
67 |
+
blank_alpha=0.4,
|
68 |
+
kwargs_slices=dict(
|
69 |
+
edgecolor="#F2F2F2",
|
70 |
+
zorder=2,
|
71 |
+
linewidth=1
|
72 |
+
),
|
73 |
+
kwargs_params=dict(
|
74 |
+
color="#000000",
|
75 |
+
fontsize=8,
|
76 |
+
va="center"
|
77 |
+
),
|
78 |
+
kwargs_values=dict(
|
79 |
+
color="#000000",
|
80 |
+
fontsize=8,
|
81 |
+
zorder=3,
|
82 |
+
bbox=dict(
|
83 |
+
edgecolor="#000000",
|
84 |
+
facecolor="white",
|
85 |
+
boxstyle="round,pad=0.2",
|
86 |
+
lw=1
|
87 |
+
)
|
88 |
+
)
|
89 |
+
)
|
90 |
+
|
91 |
+
# Ajouter l'image au centre
|
92 |
+
try:
|
93 |
+
# Charger et ajouter le logo au centre
|
94 |
+
logo_url = "https://upload.wikimedia.org/wikipedia/fr/thumb/0/01/Logo_Stade_Toulousain_Rugby.svg/775px-Logo_Stade_Toulousain_Rugby.svg.png?20180529221555"
|
95 |
+
logo = Image.open(urlopen(logo_url))
|
96 |
+
|
97 |
+
# Créer un nouvel axe pour l'image
|
98 |
+
ax_image = fig.add_axes([0.4478, 0.4315, 0.13, 0.127], zorder=2)
|
99 |
+
ax_image.imshow(logo)
|
100 |
+
ax_image.axis('off')
|
101 |
+
except Exception as e:
|
102 |
+
st.warning(f"Impossible de charger l'image: {str(e)}")
|
103 |
+
|
104 |
+
return fig
|
105 |
+
|
106 |
+
except Exception as e:
|
107 |
+
st.error(f"Erreur lors de la création du graphique: {str(e)}")
|
108 |
+
return None
|
109 |
+
|
110 |
+
def get_max_values_for_ville(final_df, selected_ville):
|
111 |
+
"""Calcule les valeurs maximales pour une ville donnée"""
|
112 |
+
# Filtrer les données pour la ville sélectionnée et exclure "GENERAL"
|
113 |
+
ville_data = final_df[(final_df['Ville'] == selected_ville) &
|
114 |
+
(final_df['name'] != "GENERAL")]
|
115 |
+
max_values = {}
|
116 |
+
|
117 |
+
for cat_name, cat_info in CATEGORIES.items():
|
118 |
+
cat_values = {} # Utiliser un dictionnaire pour stocker les max par sous-catégorie
|
119 |
+
for subcat in cat_info['subcats'].keys():
|
120 |
+
if subcat in final_df.columns:
|
121 |
+
values = ville_data[subcat].dropna()
|
122 |
+
if not values.empty:
|
123 |
+
cat_values[subcat] = max(values.astype(float))
|
124 |
+
if cat_values:
|
125 |
+
max_values[cat_name] = cat_values
|
126 |
+
|
127 |
+
return max_values
|
128 |
+
|
129 |
+
def display_metrics(max_values):
|
130 |
+
"""Affiche les valeurs maximales par catégorie de manière esthétique en deux colonnes"""
|
131 |
+
|
132 |
+
st.markdown("""
|
133 |
+
<style>
|
134 |
+
div[data-testid="stVerticalBlock"] div[style*="flex-direction: column"] div[data-testid="stVerticalBlock"] {
|
135 |
+
background-color: white;
|
136 |
+
padding: 20px;
|
137 |
+
border-radius: 10px;
|
138 |
+
box-shadow: 2px 2px 10px rgba(0,0,0,0.1);
|
139 |
+
margin-bottom: 20px;
|
140 |
+
}
|
141 |
+
</style>
|
142 |
+
""", unsafe_allow_html=True)
|
143 |
+
|
144 |
+
# Diviser les catégories en deux groupes
|
145 |
+
categories = list(max_values.items())
|
146 |
+
mid = (len(categories) + 1) // 2
|
147 |
+
|
148 |
+
# Créer deux colonnes principales
|
149 |
+
col_left, col_right = st.columns(2)
|
150 |
+
|
151 |
+
# Remplir la colonne gauche
|
152 |
+
with col_left:
|
153 |
+
for cat_name, subcats in categories[:mid]:
|
154 |
+
cat_color = CATEGORIES[cat_name]['color']
|
155 |
+
with st.container():
|
156 |
+
st.markdown(f"""
|
157 |
+
<h3 style='
|
158 |
+
color: {cat_color};
|
159 |
+
font-size: 20px;
|
160 |
+
padding-bottom: 10px;
|
161 |
+
border-bottom: 2px solid {cat_color};
|
162 |
+
margin-bottom: 15px;
|
163 |
+
'>
|
164 |
+
{cat_name}
|
165 |
+
</h3>
|
166 |
+
""", unsafe_allow_html=True)
|
167 |
+
|
168 |
+
for subcat, value in subcats.items():
|
169 |
+
st.markdown(f"""
|
170 |
+
<div style='display: flex; justify-content: space-between; padding: 5px 0;'>
|
171 |
+
<span style='color: #666; font-size: 16px;'>
|
172 |
+
{CATEGORIES[cat_name]['subcats'][subcat].replace(chr(10), ' ')}
|
173 |
+
</span>
|
174 |
+
<span style='color: #333; font-weight: bold; font-size: 16px;'>
|
175 |
+
{value}
|
176 |
+
</span>
|
177 |
+
</div>
|
178 |
+
""", unsafe_allow_html=True)
|
179 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
180 |
+
|
181 |
+
# Remplir la colonne droite
|
182 |
+
with col_right:
|
183 |
+
for cat_name, subcats in categories[mid:]:
|
184 |
+
cat_color = CATEGORIES[cat_name]['color']
|
185 |
+
with st.container():
|
186 |
+
st.markdown(f"""
|
187 |
+
<h3 style='
|
188 |
+
color: {cat_color};
|
189 |
+
font-size: 20px;
|
190 |
+
padding-bottom: 10px;
|
191 |
+
border-bottom: 2px solid {cat_color};
|
192 |
+
margin-bottom: 15px;
|
193 |
+
'>
|
194 |
+
{cat_name}
|
195 |
+
</h3>
|
196 |
+
""", unsafe_allow_html=True)
|
197 |
+
|
198 |
+
for subcat, value in subcats.items():
|
199 |
+
st.markdown(f"""
|
200 |
+
<div style='display: flex; justify-content: space-between; padding: 5px 0;'>
|
201 |
+
<span style='color: #666; font-size: 16px;'>
|
202 |
+
{CATEGORIES[cat_name]['subcats'][subcat].replace(chr(10), ' ')}
|
203 |
+
</span>
|
204 |
+
<span style='color: #333; font-weight: bold; font-size: 16px;'>
|
205 |
+
{value}
|
206 |
+
</span>
|
207 |
+
</div>
|
208 |
+
""", unsafe_allow_html=True)
|
209 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
app/config/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .config import CATEGORIES, CHART_CONFIG, ERROR_MESSAGES, DATA_DIR, EXCEL_PATH
|
app/config/__pycache__/__init__.cpython-38.pyc
ADDED
Binary file (284 Bytes). View file
|
|
app/config/__pycache__/config.cpython-38.pyc
ADDED
Binary file (1.86 kB). View file
|
|
app/config/config.py
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
# Chemins des fichiers
|
5 |
+
DATA_DIR = Path("./data") # Conversion en objet Path
|
6 |
+
EXCEL_PATH = DATA_DIR / "stats.xlsx"
|
7 |
+
|
8 |
+
# Ajout d'un print pour debug
|
9 |
+
print(f"Chemin du fichier Excel: {EXCEL_PATH}")
|
10 |
+
|
11 |
+
# Vérification et création du dossier data s'il n'existe pas
|
12 |
+
DATA_DIR.mkdir(exist_ok=True)
|
13 |
+
|
14 |
+
|
15 |
+
# Ajout d'un print pour debug
|
16 |
+
print(f"Chemin du fichier Excel: {EXCEL_PATH}")
|
17 |
+
|
18 |
+
# Vérification et création du dossier data s'il n'existe pas
|
19 |
+
DATA_DIR.mkdir(exist_ok=True)
|
20 |
+
|
21 |
+
# Configuration des catégories et leurs couleurs
|
22 |
+
CATEGORIES = {
|
23 |
+
'Duel': {
|
24 |
+
'color': "#1A78CF",
|
25 |
+
'subcats': {
|
26 |
+
'Duel_0': 'Duel\nPerdu',
|
27 |
+
'Duel_1.0': 'Duel\nNeutre',
|
28 |
+
'Duel_2.0': 'Duel\nGagné',
|
29 |
+
'Duel_3.0': 'Duel\nDécisif',
|
30 |
+
}
|
31 |
+
},
|
32 |
+
'Passe': {
|
33 |
+
'color': "#FF9300",
|
34 |
+
'subcats': {
|
35 |
+
'Passe_0': 'Passe\nPerdue',
|
36 |
+
'Passe_1.0': 'Passe\nNeutre',
|
37 |
+
'Passe_2.0': 'Passe\nGagnée',
|
38 |
+
'Passe_3.0': 'Passe\nDécisive',
|
39 |
+
}
|
40 |
+
},
|
41 |
+
'Plaquage': {
|
42 |
+
'color': "#D70232",
|
43 |
+
'subcats': {
|
44 |
+
'Plaquage_0': 'Plaquage\nPerdu',
|
45 |
+
'Plaquage_1.0': 'Plaquage\nNeutre',
|
46 |
+
'Plaquage_2.0': 'Plaquage\nGagné',
|
47 |
+
'Plaquage_3.0': 'Plaquage\nDécisif',
|
48 |
+
}
|
49 |
+
},
|
50 |
+
'Ruck': {
|
51 |
+
'color': "#2ECC71",
|
52 |
+
'subcats': {
|
53 |
+
'Ruck_0': 'Ruck\nPerdu',
|
54 |
+
'Ruck_1.0': 'Ruck\nNeutre',
|
55 |
+
'Ruck_2.0': 'Ruck\nGagné',
|
56 |
+
'Ruck_3.0': 'Ruck\nDécisif',
|
57 |
+
}
|
58 |
+
},
|
59 |
+
'JAP': {
|
60 |
+
'color': "#9B59B6",
|
61 |
+
'subcats': {
|
62 |
+
'JAP_0': 'JAP\nPerdu',
|
63 |
+
'JAP_1.0': 'JAP\nNeutre',
|
64 |
+
'JAP_2.0': 'JAP\nGagné',
|
65 |
+
'JAP_3.0': 'JAP\nDécisif',
|
66 |
+
}
|
67 |
+
},
|
68 |
+
'Reception JAP': {
|
69 |
+
'color': "#F1C40F",
|
70 |
+
'subcats': {
|
71 |
+
'Réception JAP_0': 'Récep\nPerdue',
|
72 |
+
'Réception JAP_1.0': 'Récep\nNeutre',
|
73 |
+
'Réception JAP_2.0': 'Récep\nGagnée',
|
74 |
+
'Réception JAP_3.0': 'Récep\nDécisive',
|
75 |
+
}
|
76 |
+
}
|
77 |
+
}
|
78 |
+
|
79 |
+
# Configuration du graphique
|
80 |
+
CHART_CONFIG = {
|
81 |
+
"background_color": "#EBEBE9",
|
82 |
+
"straight_line_color": "#EBEBE9",
|
83 |
+
"straight_line_lw": 1,
|
84 |
+
"last_circle_lw": 0,
|
85 |
+
"other_circle_lw": 0,
|
86 |
+
"inner_circle_size": 20,
|
87 |
+
"straight_line_limit": 100,
|
88 |
+
"figsize": (10, 10)
|
89 |
+
}
|
90 |
+
|
91 |
+
# Messages d'erreur
|
92 |
+
ERROR_MESSAGES = {
|
93 |
+
"data_load": "Impossible de charger les données",
|
94 |
+
"file_not_found": "Le fichier de données n'a pas été trouvé",
|
95 |
+
"data_processing": "Erreur lors du traitement des données"
|
96 |
+
}
|
app/utils/__init__.py
ADDED
File without changes
|
app/utils/__pycache__/__init__.cpython-38.pyc
ADDED
Binary file (154 Bytes). View file
|
|
app/utils/__pycache__/data_processing.cpython-38.pyc
ADDED
Binary file (1.99 kB). View file
|
|
app/utils/data_processing.py
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import streamlit as st
|
3 |
+
from config.config import EXCEL_PATH
|
4 |
+
|
5 |
+
|
6 |
+
def clean_dataframe(df):
|
7 |
+
# Récupérer les catégories principales (ligne 0)
|
8 |
+
categories = df.iloc[0]
|
9 |
+
# Récupérer les sous-catégories (ligne 2)
|
10 |
+
sub_categories = df.iloc[2]
|
11 |
+
|
12 |
+
# Créer un dictionnaire pour stocker les nouvelles colonnes
|
13 |
+
new_columns = {}
|
14 |
+
current_category = None
|
15 |
+
|
16 |
+
# Parcourir toutes les colonnes pour créer les nouveaux noms
|
17 |
+
for i, (cat, subcat) in enumerate(zip(categories, sub_categories)):
|
18 |
+
if pd.notna(cat):
|
19 |
+
current_category = cat
|
20 |
+
if pd.notna(subcat) and current_category is not None:
|
21 |
+
new_name = f"{current_category}_{subcat}"
|
22 |
+
new_columns[df.columns[i]] = new_name
|
23 |
+
|
24 |
+
# Garder la colonne 'name' telle quelle
|
25 |
+
new_columns[df.columns[0]] = 'name'
|
26 |
+
|
27 |
+
# Renommer les colonnes
|
28 |
+
df = df.rename(columns=new_columns)
|
29 |
+
|
30 |
+
# Supprimer les trois premières lignes et réinitialiser l'index
|
31 |
+
df = df.iloc[3:].reset_index(drop=True)
|
32 |
+
|
33 |
+
# Supprimer les lignes où le nom est vide ou NaN
|
34 |
+
df = df.dropna(subset=['name']).reset_index(drop=True)
|
35 |
+
|
36 |
+
# Extraire le numéro et nettoyer le nom AVANT d'ajouter la colonne Ville
|
37 |
+
df['numero'] = df['name'].str.extract(r'^(\d+)').fillna('')
|
38 |
+
df['name'] = df['name'].str.replace(r'^\d+\s*-\s*', '', regex=True).str.strip()
|
39 |
+
|
40 |
+
# Ne garder que les colonnes nécessaires
|
41 |
+
columns_to_keep = ['numero', 'name'] + [col for col in df.columns if '_' in str(col)]
|
42 |
+
df = df[columns_to_keep]
|
43 |
+
|
44 |
+
return df
|
45 |
+
|
46 |
+
@st.cache_data
|
47 |
+
def load_data():
|
48 |
+
"""Charge et prépare les données depuis le fichier Excel"""
|
49 |
+
try:
|
50 |
+
if not EXCEL_PATH.exists():
|
51 |
+
st.error(f"Le fichier Excel n'a pas été trouvé à l'emplacement : {EXCEL_PATH}")
|
52 |
+
st.info("Veuillez placer votre fichier stats.xlsx dans le dossier 'data'")
|
53 |
+
return None
|
54 |
+
|
55 |
+
all_sheets = pd.read_excel(EXCEL_PATH, sheet_name=None)
|
56 |
+
clean_data = []
|
57 |
+
|
58 |
+
for ville, df in all_sheets.items():
|
59 |
+
if ville != "Promedio partidos":
|
60 |
+
clean_df = clean_dataframe(df)
|
61 |
+
if not clean_df.empty:
|
62 |
+
clean_df['Ville'] = ville
|
63 |
+
clean_data.append(clean_df)
|
64 |
+
|
65 |
+
# st.write(clean_data)
|
66 |
+
|
67 |
+
if not clean_data:
|
68 |
+
st.warning("Aucune donnée trouvée")
|
69 |
+
return None
|
70 |
+
|
71 |
+
return pd.concat(clean_data, ignore_index=True)
|
72 |
+
|
73 |
+
except Exception as e:
|
74 |
+
st.error(f"Erreur lors du chargement des données: {str(e)}")
|
75 |
+
return None
|
76 |
+
|
77 |
+
|
data/stats.xlsx
ADDED
Binary file (215 kB). View file
|
|