2nzi commited on
Commit
1900d51
·
verified ·
1 Parent(s): e8b92d9
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