Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
| 196 |
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
|
| 222 |
-
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 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:
|