Eric2mangel commited on
Commit
ae39dfb
·
verified ·
1 Parent(s): f59bf48

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -122
app.py CHANGED
@@ -101,11 +101,22 @@ with st.sidebar:
101
  # Vérification que X n'est pas vide après suppression de la cible
102
  if len(X.columns) == 0:
103
  st.warning("⚠️ Aucune variable disponible après sélection de la cible.")
104
- st.stop()
105
-
106
- task = "Regression" if (y.dtype.kind in "ifu" and y.nunique() > 10) else "Classification"
107
- excluded_features = st.multiselect("Variables à exclure :", X.columns.tolist(), default=[])
108
- X = X.drop(columns=excluded_features)
 
 
 
 
 
 
 
 
 
 
 
109
  else:
110
  st.info("👈 Veuillez sélectionner ou importer un jeu de données.")
111
  X = None
@@ -115,7 +126,7 @@ with st.sidebar:
115
  # ------------------------------------------------------------
116
  # Onglets
117
  # ------------------------------------------------------------
118
- if df is not None and X is not None:
119
  tab1, tab2, tab3 = st.tabs(["📊 Analyse d'Importance", "📋 Données Brutes", "🔧 Types"])
120
 
121
  with tab2:
@@ -144,130 +155,150 @@ if df is not None and X is not None:
144
  # ------------------------------------------------------------
145
  with tab1:
146
  if len(X.columns) > 0:
147
- num_cols = X.select_dtypes(include=[np.number]).columns.tolist()
148
- cat_cols = X.select_dtypes(exclude=[np.number]).columns.tolist()
149
-
150
- # Vérification qu'il y a au moins une variable
151
- if len(num_cols) == 0 and len(cat_cols) == 0:
152
- st.warning("⚠️ Aucune variable disponible pour l'analyse. Veuillez ne pas tout exclure.")
153
- st.stop()
154
-
155
- # Construction du préprocesseur seulement avec les colonnes qui existent
156
- transformers = []
157
- if num_cols:
158
- transformers.append(("num", StandardScaler(), num_cols))
159
- if cat_cols:
160
- transformers.append(("cat", OneHotEncoder(drop="first", handle_unknown="ignore", sparse_output=False), cat_cols))
161
-
162
- if not transformers:
163
- st.warning("⚠️ Aucune colonne à traiter.")
164
- st.stop()
165
-
166
- preprocess = ColumnTransformer(transformers=transformers)
167
-
168
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)
169
-
170
- # Vérification qu'il y a assez de données pour le split
171
- if len(X_train) == 0 or len(X_test) == 0:
172
- st.error("❌ Pas assez de données pour créer les ensembles d'entraînement et de test.")
173
- st.info(f"Données disponibles : {len(X)} lignes. Minimum requis : 2 lignes.")
174
- st.stop()
175
-
176
- X_train_proc = preprocess.fit_transform(X_train)
177
-
178
- # Vérification que les données transformées ne sont pas vides
179
- if X_train_proc.shape[0] == 0 or X_train_proc.shape[1] == 0:
180
- st.error("❌ Erreur : Les données transformées sont vides.")
181
- st.info(f"Shape après transformation : {X_train_proc.shape}")
182
- st.info(f"Variables numériques : {num_cols}")
183
- st.info(f"Variables catégorielles : {cat_cols}")
184
- st.stop()
185
-
186
- feature_names = preprocess.get_feature_names_out()
187
-
188
- model = LinearRegression() if task == "Regression" else LogisticRegression(max_iter=1000)
189
- model.fit(X_train_proc, y_train)
190
-
191
- y_pred = model.predict(preprocess.transform(X_test))
192
- perf = r2_score(y_test, y_pred) if task == "Regression" else accuracy_score(y_test, y_pred)
193
-
194
- st.subheader("📊 Pertinence marginale vs conditionnelle")
195
- st.markdown(f"**🎯 Performance globale : {perf:.2f} ({'R²' if task == 'Regression' else 'Précision'})**")
 
196
 
197
- # Métriques
198
- mi = mutual_info_regression(X_train_proc, y_train, random_state=0) if task == "Regression" else mutual_info_classif(X_train_proc, y_train, random_state=0)
199
- coefs = model.coef_.ravel() if task == "Regression" else model.coef_[0]
200
-
201
- res = pd.DataFrame({
202
- "Variable": feature_names,
203
- "Importance seule (MI)": mi,
204
- "Poids dans le Modèle": np.abs(coefs),
205
- "Sens": np.where(coefs > 0, "+", "-")
206
- })
207
-
208
- if task == "Regression":
209
- res["Lien Direct (Corr)"] = [pearsonr(X_train_proc[:, i], y_train)[0] for i in range(len(feature_names))]
210
-
211
- # Normalisation pour Score Synthétique
212
- def normalize(s): return (s - s.min()) / (s.max() - s.min() + 1e-10)
213
- mi_n = normalize(res["Importance seule (MI)"])
214
- poids_n = normalize(res["Poids dans le Modèle"])
215
 
216
- if task == "Regression":
217
- corr_n = normalize(res["Lien Direct (Corr)"].abs())
218
- res["Score synthétique"] = ((mi_n + corr_n) / 2 + poids_n) / 2
219
- else:
220
- res["Score synthétique"] = (mi_n + poids_n) / 2
221
 
222
- res = res.sort_values("Score synthétique", ascending=False)
223
 
224
- # Réorganisation des colonnes
225
- cols = ["Variable", "Score synthétique", "Importance seule (MI)", "Poids dans le Modèle", "Sens"]
226
- if task == "Regression":
227
- cols = ["Variable", "Score synthétique", "Importance seule (MI)", "Lien Direct (Corr)", "Poids dans le Modèle", "Sens"]
228
-
229
- final_df = res[cols].copy()
230
 
231
- # --- STYLISATION ET AFFICHAGE ---
232
- # 1. Préparation du style pour la colonne Sens (couleurs)
233
- def style_sign(val):
234
- color = 'color: #2ecc71;' if val == '+' else 'color: #e74c3c;'
235
- return f'{color} font-weight: bold; font-size: 20px;'
236
 
237
- # 2. Application du formatage (2 décimales) et des gradients
238
- num_cols_to_style = [c for c in cols if c not in ["Variable", "Sens", "Score synthétique"]]
239
-
240
- styled_res = (final_df.style
241
- .format({c: "{:.2f}" for c in cols if c not in ["Variable", "Sens"]})
242
- .background_gradient(subset=num_cols_to_style, cmap="RdYlGn")
243
- .map(style_sign, subset=['Sens'])
244
- )
245
 
246
- # 3. Affichage avec st.data_editor pour fixer la hauteur (6 lignes env = 250px)
247
- st.data_editor(
248
- styled_res,
249
- use_container_width=True,
250
- height=250, # Limite la hauteur avec scrollbar
251
- hide_index=True,
252
- disabled=True, # Empêche l'édition, agit comme un dataframe
253
- column_config={
254
- "Sens": st.column_config.Column(
255
- "Sens",
256
- help="Direction de l'influence",
257
- width="small"
258
- )
259
- }
260
- )
261
 
262
- st.subheader("📖 Guide de lecture")
263
- st.markdown(
264
- """
265
- - **Score synthétique** : Note globale d'importance.
266
- - **Importance seule (MI)** : Mesure la dépendance globale entre la variable et la cible. Contrairement à la corrélation qui ne voit que les lignes droites, l'Information Mutuelle détecte toutes les formes de relations (courbes, motifs complexes, etc.). Elle indique quelle quantité d'information "pure" cette variable partage avec la cible, sans tenir compte des autres variables.
267
- - **Poids dans le modèle** : Contribution finale au modèle.
268
- - **Sens (+) / (-)** : Direction de l'impact sur la cible.
269
- """
270
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  else:
272
  st.info("ℹ️ Veuillez sélectionner au moins une variable.")
273
  else:
 
101
  # Vérification que X n'est pas vide après suppression de la cible
102
  if len(X.columns) == 0:
103
  st.warning("⚠️ Aucune variable disponible après sélection de la cible.")
104
+ X = None
105
+ y = None
106
+ task = None
107
+ else:
108
+ task = "Regression" if (y.dtype.kind in "ifu" and y.nunique() > 10) else "Classification"
109
+ excluded_features = st.multiselect("Variables à exclure :", X.columns.tolist(), default=[])
110
+
111
+ if excluded_features:
112
+ X = X.drop(columns=excluded_features)
113
+
114
+ # Vérification après exclusion
115
+ if len(X.columns) == 0:
116
+ st.error("❌ Vous avez exclu toutes les variables ! Veuillez en garder au moins une.")
117
+ X = None
118
+ y = None
119
+ task = None
120
  else:
121
  st.info("👈 Veuillez sélectionner ou importer un jeu de données.")
122
  X = None
 
126
  # ------------------------------------------------------------
127
  # Onglets
128
  # ------------------------------------------------------------
129
+ if df is not None and X is not None and len(X.columns) > 0:
130
  tab1, tab2, tab3 = st.tabs(["📊 Analyse d'Importance", "📋 Données Brutes", "🔧 Types"])
131
 
132
  with tab2:
 
155
  # ------------------------------------------------------------
156
  with tab1:
157
  if len(X.columns) > 0:
158
+ try:
159
+ num_cols = X.select_dtypes(include=[np.number]).columns.tolist()
160
+ cat_cols = X.select_dtypes(exclude=[np.number]).columns.tolist()
161
+
162
+ # Vérification qu'il y a au moins une variable
163
+ if len(num_cols) == 0 and len(cat_cols) == 0:
164
+ st.warning("⚠️ Aucune variable disponible pour l'analyse. Veuillez ne pas tout exclure.")
165
+ st.stop()
166
+
167
+ # Construction du préprocesseur seulement avec les colonnes qui existent
168
+ transformers = []
169
+ if num_cols:
170
+ transformers.append(("num", StandardScaler(), num_cols))
171
+ if cat_cols:
172
+ transformers.append(("cat", OneHotEncoder(drop="first", handle_unknown="ignore", sparse_output=False), cat_cols))
173
+
174
+ if not transformers:
175
+ st.warning("⚠️ Aucune colonne à traiter.")
176
+ st.stop()
177
+
178
+ preprocess = ColumnTransformer(transformers=transformers)
179
+
180
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)
181
+
182
+ # Vérification qu'il y a assez de données pour le split
183
+ if len(X_train) == 0 or len(X_test) == 0:
184
+ st.error(" Pas assez de données pour créer les ensembles d'entraînement et de test.")
185
+ st.info(f"Données disponibles : {len(X)} lignes. Minimum requis : 2 lignes.")
186
+ st.stop()
187
+
188
+ X_train_proc = preprocess.fit_transform(X_train)
189
+
190
+ # Vérification que les données transformées ne sont pas vides
191
+ if X_train_proc.shape[0] == 0 or X_train_proc.shape[1] == 0:
192
+ st.error(" Erreur : Les données transformées sont vides.")
193
+ st.info(f"Shape après transformation : {X_train_proc.shape}")
194
+ st.info(f"Variables numériques : {num_cols}")
195
+ st.info(f"Variables catégorielles : {cat_cols}")
196
+ st.stop()
197
+
198
+ feature_names = preprocess.get_feature_names_out()
199
+
200
+ model = LinearRegression() if task == "Regression" else LogisticRegression(max_iter=1000)
201
+ model.fit(X_train_proc, y_train)
202
+
203
+ y_pred = model.predict(preprocess.transform(X_test))
204
+ perf = r2_score(y_test, y_pred) if task == "Regression" else accuracy_score(y_test, y_pred)
205
+
206
+ st.subheader("📊 Pertinence marginale vs conditionnelle")
207
+ st.markdown(f"**🎯 Performance globale : {perf:.2f} ({'R²' if task == 'Regression' else 'Précision'})**")
208
 
209
+ # Métriques
210
+ mi = mutual_info_regression(X_train_proc, y_train, random_state=0) if task == "Regression" else mutual_info_classif(X_train_proc, y_train, random_state=0)
211
+ coefs = model.coef_.ravel() if task == "Regression" else model.coef_[0]
212
+
213
+ res = pd.DataFrame({
214
+ "Variable": feature_names,
215
+ "Importance seule (MI)": mi,
216
+ "Poids dans le Modèle": np.abs(coefs),
217
+ "Sens": np.where(coefs > 0, "+", "-")
218
+ })
219
+
220
+ if task == "Regression":
221
+ res["Lien Direct (Corr)"] = [pearsonr(X_train_proc[:, i], y_train)[0] for i in range(len(feature_names))]
222
+
223
+ # Normalisation pour Score Synthétique
224
+ def normalize(s): return (s - s.min()) / (s.max() - s.min() + 1e-10)
225
+ mi_n = normalize(res["Importance seule (MI)"])
226
+ poids_n = normalize(res["Poids dans le Modèle"])
227
 
228
+ if task == "Regression":
229
+ corr_n = normalize(res["Lien Direct (Corr)"].abs())
230
+ res["Score synthétique"] = ((mi_n + corr_n) / 2 + poids_n) / 2
231
+ else:
232
+ res["Score synthétique"] = (mi_n + poids_n) / 2
233
 
234
+ res = res.sort_values("Score synthétique", ascending=False)
235
 
236
+ # Réorganisation des colonnes
237
+ cols = ["Variable", "Score synthétique", "Importance seule (MI)", "Poids dans le Modèle", "Sens"]
238
+ if task == "Regression":
239
+ cols = ["Variable", "Score synthétique", "Importance seule (MI)", "Lien Direct (Corr)", "Poids dans le Modèle", "Sens"]
240
+
241
+ final_df = res[cols].copy()
242
 
243
+ # --- STYLISATION ET AFFICHAGE ---
244
+ # 1. Préparation du style pour la colonne Sens (couleurs)
245
+ def style_sign(val):
246
+ color = 'color: #2ecc71;' if val == '+' else 'color: #e74c3c;'
247
+ return f'{color} font-weight: bold; font-size: 20px;'
248
 
249
+ # 2. Application du formatage (2 décimales) et des gradients
250
+ num_cols_to_style = [c for c in cols if c not in ["Variable", "Sens", "Score synthétique"]]
251
+
252
+ styled_res = (final_df.style
253
+ .format({c: "{:.2f}" for c in cols if c not in ["Variable", "Sens"]})
254
+ .background_gradient(subset=num_cols_to_style, cmap="RdYlGn")
255
+ .map(style_sign, subset=['Sens'])
256
+ )
257
 
258
+ # 3. Affichage avec st.data_editor pour fixer la hauteur (6 lignes env = 250px)
259
+ st.data_editor(
260
+ styled_res,
261
+ use_container_width=True,
262
+ height=250, # Limite la hauteur avec scrollbar
263
+ hide_index=True,
264
+ disabled=True, # Empêche l'édition, agit comme un dataframe
265
+ column_config={
266
+ "Sens": st.column_config.Column(
267
+ "Sens",
268
+ help="Direction de l'influence",
269
+ width="small"
270
+ )
271
+ }
272
+ )
273
 
274
+ st.subheader("📖 Guide de lecture")
275
+ st.markdown(
276
+ """
277
+ - **Score synthétique** : Note globale d'importance.
278
+ - **Importance seule (MI)** : Mesure la dépendance globale entre la variable et la cible. Contrairement à la corrélation qui ne voit que les lignes droites, l'Information Mutuelle détecte toutes les formes de relations (courbes, motifs complexes, etc.). Elle indique quelle quantité d'information "pure" cette variable partage avec la cible, sans tenir compte des autres variables.
279
+ - **Poids dans le modèle** : Contribution finale au modèle.
280
+ - **Sens (+) / (-)** : Direction de l'impact sur la cible.
281
+ """
282
+ )
283
+
284
+ except ValueError as e:
285
+ if "Found array with 0 sample(s)" in str(e) or "shape=(0," in str(e):
286
+ st.error("❌ Erreur d'analyse : données insuffisantes ou incompatibles")
287
+ st.warning("⚠️ Vérifiez que :")
288
+ st.markdown("""
289
+ - Vous n'avez pas exclu toutes les variables
290
+ - La variable cible choisie est appropriée (elle ne doit pas être identique à une variable prédictive)
291
+ - Il reste suffisamment de données après nettoyage
292
+ - Les variables ont suffisamment de variance
293
+ """)
294
+ else:
295
+ st.error(f"❌ Erreur : {str(e)}")
296
+
297
+ except Exception as e:
298
+ st.error(f"❌ Une erreur s'est produite lors de l'analyse")
299
+ st.warning(f"Détails : {str(e)}")
300
+ st.info("💡 Essayez de changer de variable cible ou de variables prédictives.")
301
+
302
  else:
303
  st.info("ℹ️ Veuillez sélectionner au moins une variable.")
304
  else: