Spaces:
Sleeping
Sleeping
File size: 15,058 Bytes
1cc6897 8387489 1cc6897 2c80a75 1cc6897 2c80a75 1cc6897 25f5b6f 1cc6897 25f5b6f 1cc6897 3c31915 1cc6897 25f5b6f ae39dfb 1cc6897 ae39dfb 1cc6897 ae39dfb 1cc6897 ae39dfb 2c80a75 ae39dfb 8387489 ae39dfb 8387489 ae39dfb 2c80a75 1cc6897 8387489 ae39dfb 2c80a75 ae39dfb 1cc6897 ae39dfb 1cc6897 ae39dfb 2c80a75 ae39dfb 2c80a75 ae39dfb 1cc6897 ae39dfb 1cc6897 ae39dfb 1cc6897 ae39dfb 1cc6897 ae39dfb 1cc6897 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 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 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
import streamlit as st
import seaborn as sns
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.feature_selection import mutual_info_regression, mutual_info_classif
from sklearn.metrics import r2_score, accuracy_score
from sklearn.model_selection import train_test_split
#from scipy.stats import pearsonr
from scipy.stats import spearmanr
# ------------------------------------------------------------
# Configuration Globale
# ------------------------------------------------------------
TEST_SIZE = 0.3
RANDOM_STATE = 42
st.set_page_config(page_title="Analyse d'importance", layout="wide")
st.title("🔍 Analyse de l'importance des caractéristiques")
st.markdown(
"""
Cette application illustre la différence entre la pertinence marginale et la pertinence conditionnelle d'une caractéristique.
- Pertinence marginale : corrélation ou information mutuelle avec la cible.
- Pertinence conditionnelle : valeur ajoutée d'une variable excluant les redondances après contrôle.
"""
)
# ------------------------------------------------------------
# Sidebar: Dataset et Importation
# ------------------------------------------------------------
with st.sidebar:
st.header("⚙️ Configuration")
# Choix de la source de données
data_source = st.radio(
"Source des données",
["Jeu de données Seaborn", "Importer un fichier"],
label_visibility="visible"
)
df = None
if data_source == "Importer un fichier":
uploaded_file = st.file_uploader("Importer un fichier CSV", type=["csv"])
if uploaded_file is not None:
try:
df = pd.read_csv(uploaded_file, sep=None, engine='python')
# Seuil de valeurs manquantes (configurable)
missing_threshold = st.slider(
"Seuil max de valeurs manquantes (%)",
min_value=0,
max_value=100,
value=50,
help="Les colonnes avec plus de X% de valeurs manquantes seront supprimées"
)
# Calcul du pourcentage de valeurs manquantes par colonne
missing_pct = (df.isnull().sum() / len(df)) * 100
cols_to_drop = missing_pct[missing_pct > missing_threshold].index.tolist()
if cols_to_drop:
st.info(f"ℹ️ {len(cols_to_drop)} colonne(s) supprimée(s) (>{missing_threshold}% manquantes) : {', '.join(cols_to_drop)}")
df = df.drop(columns=cols_to_drop)
# Suppression des lignes avec valeurs manquantes restantes
df = df.dropna()
if len(df) == 0:
st.error("❌ Aucune donnée après nettoyage. Essayez d'augmenter le seuil de valeurs manquantes.")
df = None
else:
st.success(f"✅ Fichier CSV chargé ! ({len(df)} lignes, {len(df.columns)} colonnes)")
except Exception as e:
st.error(f"Erreur : {e}")
df = None
else:
excluded_datasets = ['anagrams', 'anscombe', 'attention', 'brain_networks',
'car_crashes', 'dowjones','diamonds','flights','geyser',
'planets','seaice']
available_datasets = [d for d in sorted(sns.get_dataset_names()) if d not in excluded_datasets]
default_dataset = "iris"
default_index = available_datasets.index(default_dataset) if default_dataset in available_datasets else 0
dataset_name = st.selectbox(
"Dataset d'exemple",
available_datasets,
index=default_index
)
#dataset_name = st.selectbox("Dataset d'exemple", available_datasets)
try:
df = sns.load_dataset(dataset_name)
df = df.dropna()
st.success(f"✅ Jeu '{dataset_name}' chargé")
except Exception as e:
st.error(f"Erreur : {e}")
df = None
if df is not None:
target = st.selectbox("Sélection cible (Y)", df.columns)
y = df[target]
X = df.drop(columns=[target])
# Vérification que X n'est pas vide après suppression de la cible
if len(X.columns) == 0:
st.warning("⚠️ Aucune variable disponible après sélection de la cible.")
X = None
y = None
task = None
else:
task = "Regression" if (y.dtype.kind in "ifu" and y.nunique() > 10) else "Classification"
excluded_features = st.multiselect("Variables à exclure :", X.columns.tolist(), default=[])
if excluded_features:
X = X.drop(columns=excluded_features)
# Vérification après exclusion
if len(X.columns) == 0:
st.error("❌ Vous avez exclu toutes les variables ! Veuillez en garder au moins une.")
X = None
y = None
task = None
else:
st.info("👈 Veuillez sélectionner ou importer un jeu de données.")
X = None
y = None
task = None
# ------------------------------------------------------------
# Onglets
# ------------------------------------------------------------
if df is not None and X is not None and len(X.columns) > 0:
tab1, tab2, tab3 = st.tabs(["📊 Analyse d'Importance", "📋 Données Brutes", "🔧 Types"])
with tab2:
st.dataframe(df.head(20), use_container_width=True)
with tab3:
st.header("Types des variables")
num_cols = X.select_dtypes(include=[np.number]).columns.tolist()
cat_cols = X.select_dtypes(exclude=[np.number]).columns.tolist()
col1, col2 = st.columns(2)
with col1:
st.subheader("Numériques")
for col in num_cols or ["None"]:
st.write(f"- {col}")
with col2:
st.subheader("Catégorielles")
for col in cat_cols or ["None"]:
st.write(f"- {col}")
# ------------------------------------------------------------
# Analyse Principale (Tab 1)
# ------------------------------------------------------------
with tab1:
if len(X.columns) > 0:
try:
num_cols = X.select_dtypes(include=[np.number]).columns.tolist()
cat_cols = X.select_dtypes(exclude=[np.number]).columns.tolist()
# Vérification qu'il y a au moins une variable
if len(num_cols) == 0 and len(cat_cols) == 0:
st.warning("⚠️ Aucune variable disponible pour l'analyse. Veuillez ne pas tout exclure.")
st.stop()
# Construction du préprocesseur seulement avec les colonnes qui existent
transformers = []
if num_cols:
transformers.append(("num", StandardScaler(), num_cols))
if cat_cols:
transformers.append(("cat", OneHotEncoder(drop="first", handle_unknown="ignore", sparse_output=False), cat_cols))
if not transformers:
st.warning("⚠️ Aucune colonne à traiter.")
st.stop()
preprocess = ColumnTransformer(transformers=transformers)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)
# Vérification qu'il y a assez de données pour le split
if len(X_train) == 0 or len(X_test) == 0:
st.error("❌ Pas assez de données pour créer les ensembles d'entraînement et de test.")
st.info(f"Données disponibles : {len(X)} lignes. Minimum requis : 2 lignes.")
st.stop()
X_train_proc = preprocess.fit_transform(X_train)
# Vérification que les données transformées ne sont pas vides
if X_train_proc.shape[0] == 0 or X_train_proc.shape[1] == 0:
st.error("❌ Erreur : Les données transformées sont vides.")
st.info(f"Shape après transformation : {X_train_proc.shape}")
st.info(f"Variables numériques : {num_cols}")
st.info(f"Variables catégorielles : {cat_cols}")
st.stop()
feature_names = preprocess.get_feature_names_out()
model = LinearRegression() if task == "Regression" else LogisticRegression(max_iter=1000)
model.fit(X_train_proc, y_train)
y_pred = model.predict(preprocess.transform(X_test))
perf = r2_score(y_test, y_pred) if task == "Regression" else accuracy_score(y_test, y_pred)
st.subheader("📊 Pertinence marginale vs conditionnelle")
st.markdown(f"**🎯 Performance globale : {perf:.2f} ({'R²' if task == 'Regression' else 'Précision'})**")
# Métriques
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)
coefs = model.coef_.ravel() if task == "Regression" else model.coef_[0]
res = pd.DataFrame({
"Variable": feature_names,
"Importance seule (MI)": mi,
"Poids dans le modèle": np.abs(coefs),
"Sens": np.where(coefs > 0, "+", "-")
})
#if task == "Regression":
# res["Lien direct (Corr)"] = [pearsonr(X_train_proc[:, i], y_train)[0] for i in range(len(feature_names))]
if task == "Regression":
res["Lien direct (Corr)"] = [spearmanr(X_train_proc[:, i], y_train)[0] for i in range(len(feature_names))]
# Normalisation pour Score Synthétique
def normalize(s): return (s - s.min()) / (s.max() - s.min() + 1e-10)
mi_n = normalize(res["Importance seule (MI)"])
poids_n = normalize(res["Poids dans le modèle"])
if task == "Regression":
corr_n = normalize(res["Lien direct (Corr)"].abs())
res["Score synthétique"] = ((mi_n + corr_n) / 2 + poids_n) / 2
else:
res["Score synthétique"] = (mi_n + poids_n) / 2
res = res.sort_values("Score synthétique", ascending=False)
# Réorganisation des colonnes
cols = ["Variable", "Score synthétique", "Importance seule (MI)", "Poids dans le modèle", "Sens"]
if task == "Regression":
cols = ["Variable", "Score synthétique", "Importance seule (MI)", "Lien direct (Corr)", "Poids dans le modèle", "Sens"]
final_df = res[cols].copy()
# --- STYLISATION ET AFFICHAGE ---
# 1. Préparation du style pour la colonne Sens (couleurs)
def style_sign(val):
color = 'color: #2ecc71;' if val == '+' else 'color: #e74c3c;'
return f'{color} font-weight: bold; font-size: 20px;'
# 2. Application du formatage (2 décimales) et des gradients
num_cols_to_style = [c for c in cols if c not in ["Variable", "Sens", "Score synthétique"]]
styled_res = (final_df.style
.format({c: "{:.2f}" for c in cols if c not in ["Variable", "Sens"]})
.background_gradient(subset=num_cols_to_style, cmap="RdYlGn")
.map(style_sign, subset=['Sens'])
)
# 3. Affichage avec st.data_editor pour fixer la hauteur (6 lignes env = 250px)
st.data_editor(
styled_res,
use_container_width=True,
height=250, # Limite la hauteur avec scrollbar
hide_index=True,
disabled=True, # Empêche l'édition, agit comme un dataframe
column_config={
"Sens": st.column_config.Column(
"Sens",
help="Direction de l'influence",
width="small"
)
}
)
st.subheader("📖 Guide de lecture")
st.markdown(
"""
- **Score synthétique** : Note globale d'importance.
- **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.
- **Poids dans le modèle** : Contribution finale au modèle.
- **Sens (+) / (-)** : Direction de l'impact sur la cible.
"""
)
except ValueError as e:
if "Found array with 0 sample(s)" in str(e) or "shape=(0," in str(e):
st.error("❌ Erreur d'analyse : données insuffisantes ou incompatibles")
st.warning("⚠️ Vérifiez que :")
st.markdown("""
- Vous n'avez pas exclu toutes les variables
- La variable cible choisie est appropriée (elle ne doit pas être identique à une variable prédictive)
- Il reste suffisamment de données après nettoyage
- Les variables ont suffisamment de variance
""")
else:
st.error(f"❌ Erreur : {str(e)}")
except Exception as e:
st.error(f"❌ Une erreur s'est produite lors de l'analyse")
st.warning(f"Détails : {str(e)}")
st.info("💡 Essayez de changer de variable cible ou de variables prédictives.")
else:
st.info("ℹ️ Veuillez sélectionner au moins une variable.")
else:
st.info("👈 Veuillez sélectionner ou importer un jeu de données pour commencer l'analyse.") |