import streamlit as st import seaborn as sns import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.svm import SVR from sklearn.neighbors import KNeighborsRegressor from sklearn.metrics import r2_score from sklearn.model_selection import train_test_split from scipy.stats import spearmanr import plotly.graph_objects as go from xgboost import XGBRegressor # Configuration globale RANDOM_STATE = 42 st.set_page_config(page_title="Analyse d'imputation", layout="wide") # CSS personnalisé pour un rendu plus professionnel st.markdown(""" """, unsafe_allow_html=True) st.title("🔍 Analyse de fiabilité de l'imputation") # st.markdown( # """ # Cette application évalue la capacité à imputer chaque variable d'un dataset en utilisant les autres variables. # **Méthodologie :** Standardisation des données • Suppression des variables jumelles (corrélation Spearman) • Modélisation au choix • Évaluation de la qualité d'imputation (R²) # """ # ) st.markdown( """ Cette application évalue la capacité à imputer chaque variable d'un dataset en utilisant les autres variables. """ ) # Sidebar: Configuration with st.sidebar: run_analysis = st.button("🚀 Lancer l'analyse", type="primary", use_container_width=True) st.header("⚙️ Configuration") # 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: dataset_name = uploaded_file.name try: df = pd.read_csv(uploaded_file, sep=None, engine='python') df = df.select_dtypes(include=[np.number]) df = df.dropna() if len(df) == 0: st.error("❌ Aucune donnée numérique après nettoyage.") df = None #else: # st.success(f"✅ Fichier 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', 'exercise', 'fmri','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 ) try: df = sns.load_dataset(dataset_name) df = df.select_dtypes(include=[np.number]) 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 and len(df.columns) > 1: st.subheader("Paramètres") # Expander Modélisation with st.expander("▶ Modélisation", expanded=True): algo = st.selectbox( "Algorithme de régression", ["Régression Linéaire", "Random Forest", "SVR", "KNN", "XGBoost"], help="Algorithme utilisé pour prédire chaque variable" ) test_size = st.slider( "Taille de l'ensemble test (%)", min_value=10, max_value=50, value=30, step=5, help="Pourcentage des données pour le test" ) # Expander Nettoyage & Filtres with st.expander("▶ Réglages", expanded=False): corr_threshold = st.slider( "Seuil de corrélation (Spearman)", min_value=0.5, max_value=0.99, value=0.92, step=0.01, help="Variables avec corrélation > seuil seront considérées comme jumelles" ) outlier_threshold = st.slider( "Seuil de suppression des outliers (écart-types)", min_value=1.0, max_value=6.0, value=6.0, step=0.5, help="Supprime les valeurs à plus de X écart-types de la moyenne" ) sample_size = st.slider( "Échantillon du dataset (%)", min_value=10, max_value=100, value=100, step=10, help="Pourcentage du dataset à utiliser pour l'analyse" ) #st.divider() #run_analysis = st.button("🚀 Lancer l'analyse", type="primary", use_container_width=True) else: if df is not None: st.warning("⚠️ Le dataset doit contenir au moins 2 variables numériques.") else: st.info("👈 Veuillez sélectionner ou importer un jeu de données.") # Fonctions utilitaires def get_model(algo_name): """Retourne le modèle selon l'algorithme sélectionné""" if algo_name == "Régression Linéaire": return LinearRegression() elif algo_name == "Random Forest": return RandomForestRegressor(n_estimators=100, random_state=RANDOM_STATE, n_jobs=-1) elif algo_name == "SVR": return SVR(kernel='rbf') elif algo_name == "XGBoost": return XGBRegressor(n_estimators=100, random_state=RANDOM_STATE, n_jobs=-1, verbosity=0) else: # KNN return KNeighborsRegressor(n_neighbors=5) def remove_outliers(df, threshold): """Supprime les outliers à plus de X écart-types""" df_clean = df.copy() for col in df_clean.columns: mean = df_clean[col].mean() std = df_clean[col].std() df_clean = df_clean[np.abs(df_clean[col] - mean) <= threshold * std] return df_clean def remove_twin_variables(X, threshold): """Supprime les variables jumelles basé sur corrélation Spearman""" corr_matrix = X.corr(method='spearman').abs() upper_tri = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)) to_drop = set() for column in upper_tri.columns: correlated = upper_tri.index[upper_tri[column] > threshold].tolist() if correlated: to_drop.update(correlated) return X.drop(columns=list(to_drop)), list(to_drop) def backward_elimination(X, y, p_threshold=0.05): """Backward elimination basé sur les p-values""" import statsmodels.api as sm X_with_const = sm.add_constant(X) selected_features = list(X.columns) while len(selected_features) > 0: model = sm.OLS(y, X_with_const[['const'] + selected_features]).fit() p_values = model.pvalues[1:] max_p_value = p_values.max() if max_p_value > p_threshold: exclude_feature = p_values.idxmax() selected_features.remove(exclude_feature) else: break return selected_features def evaluate_imputation(df, target_col, corr_threshold, test_size, algo): """Évalue la faisabilité d'imputation pour une variable""" X = df.drop(columns=[target_col]) y = df[target_col] # Vérification minimale de données if len(X) < 10 or len(X.columns) == 0: return None, [], [] # Standardisation scaler = StandardScaler() X_scaled = pd.DataFrame(scaler.fit_transform(X), columns=X.columns, index=X.index) # Suppression des jumelles X_filtered, dropped_twins = remove_twin_variables(X_scaled, corr_threshold) if len(X_filtered.columns) == 0: return 0.0, [], dropped_twins # Split train/test try: X_train, X_test, y_train, y_test = train_test_split( X_filtered, y, test_size=test_size/100, random_state=RANDOM_STATE ) except: return None, [], dropped_twins # Backward elimination (seulement pour régression linéaire avec seuil fixe de 0.05) selected_features = list(X_train.columns) if algo == "Régression Linéaire" and len(X_train.columns) > 1: try: selected_features = backward_elimination(X_train, y_train, p_threshold=0.05) except: pass if len(selected_features) == 0: return 0.0, [], dropped_twins # Entraînement du modèle model = get_model(algo) X_train_selected = X_train[selected_features] X_test_selected = X_test[selected_features] try: model.fit(X_train_selected, y_train) y_pred = model.predict(X_test_selected) r2 = r2_score(y_test, y_pred) return max(0, r2), selected_features, dropped_twins except: return 0.0, selected_features, dropped_twins # Interface principale if df is not None and len(df.columns) > 1: tab1, tab2, tab3, tab4, tab5 = st.tabs(["📊 Analyse", "📋 Détails par variable", "📈 Statistiques", "💾 Données brutes", "ℹ️ Information"]) with tab4: st.dataframe(df.head(20), use_container_width=True) with tab5: st.header("À propos de l'analyse") st.markdown(f""" **Nom du dataset :** {dataset_name} **Dataset :** {len(df)} lignes × {len(df.columns)} colonnes **Interprétation du R² :** - **R² > 0.7** : Imputation très fiable ✅ - **0.5 < R² < 0.7** : Imputation acceptable ⚠️ - **R² < 0.5** : Imputation difficile ❌ **Méthodologie :** 1. Chaque variable est tour à tour considérée comme cible 2. Les autres variables servent de prédicteurs 3. Suppression des variables jumelles (corrélation > {corr_threshold}) 4. Évaluation avec {algo} """) with tab1: if 'run_analysis' in locals() and run_analysis: # Préparation du dataset avec outliers et échantillonnage df_processed = df.copy() # Suppression des outliers if outlier_threshold < 6.0: df_before = len(df_processed) df_processed = remove_outliers(df_processed, outlier_threshold) df_after = len(df_processed) st.info(f"🧹 Outliers supprimés : {df_before - df_after} lignes ({(df_before - df_after)/df_before*100:.1f}%)") # Échantillonnage if sample_size < 100: df_processed = df_processed.sample(frac=sample_size/100, random_state=RANDOM_STATE) st.info(f"📊 Échantillon utilisé : {len(df_processed)} lignes ({sample_size}% du dataset)") results = [] progress_bar = st.progress(0) status_text = st.empty() for idx, col in enumerate(df_processed.columns): status_text.text(f"Analyse de '{col}' ({idx+1}/{len(df_processed.columns)})...") r2, selected_vars, dropped_twins = evaluate_imputation( df_processed, col, corr_threshold, test_size, algo ) if r2 is not None: results.append({ 'Variable': col, 'R²': r2, 'Prédicteurs': len(selected_vars), 'Jumelles': len(dropped_twins), 'Statut': '✅ Excellent' if r2 > 0.7 else ('⚠️ Moyen' if r2 > 0.5 else '❌ Difficile') }) progress_bar.progress((idx + 1) / len(df_processed.columns)) status_text.empty() progress_bar.empty() if results: results_df = pd.DataFrame(results).sort_values('R²', ascending=False) # Stocker dans session_state st.session_state['results_df'] = results_df # Graphique interactif st.subheader("📈 Résultats de l'analyse") fig = go.Figure() colors = ['#28a745' if r >= 0.7 else '#ffc107' if r >= 0.5 else '#dc3545' for r in results_df['R²']] fig.add_trace(go.Bar( x=results_df['Variable'], y=results_df['R²'], marker_color=colors, text=results_df['R²'].round(3), textposition='outside', hovertemplate='%{x}
R²: %{y:.3f}' )) fig.add_hline(y=0.7, line_dash="dash", line_color="#28a745", annotation_text="Excellent (0.7)", annotation_position="right") fig.add_hline(y=0.5, line_dash="dash", line_color="#ffc107", annotation_text="Acceptable (0.5)", annotation_position="right") fig.update_layout( title=f"Fiabilité de l'imputation par variable ({algo})", xaxis_title="Variable", yaxis_title="R² Score", height=470, showlegend=False, hovermode='x', plot_bgcolor='white', paper_bgcolor='white', font=dict(family="Arial, sans-serif", size=12, color="#333333") ) fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='#f0f0f0') fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='#f0f0f0') st.plotly_chart(fig, use_container_width=True) else: st.error("❌ Aucun résultat. Vérifiez vos données.") elif 'run_analysis' not in locals(): st.info("👈 Cliquez sur le bouton 'Lancer l'analyse' dans la sidebar") else: st.info("👈 Cliquez sur le bouton 'Lancer l'analyse' dans la sidebar") with tab2: if 'results_df' in st.session_state: st.subheader("📋 Détails par variable") # Tableau avec style personnalisé results_display = st.session_state['results_df'].copy() styled_df = results_display.style.format({ 'R²': '{:.3f}' }).background_gradient(subset=['R²'], cmap='RdYlGn', vmin=0, vmax=1) st.dataframe(styled_df, use_container_width=True, hide_index=True, height=400) else: st.info("👈 Lancez d'abord une analyse pour voir les détails par variable") with tab3: if 'results_df' in st.session_state: st.subheader("📈 Statistiques récapitulatives") results_df = st.session_state['results_df'] # Statistiques récapitulatives col1, col2, col3 = st.columns(3) with col1: excellent = len(results_df[results_df['R²'] > 0.7]) st.metric("Nombre d'imputations fiables", excellent, delta=f"{excellent/len(results_df)*100:.1f}%") with col2: acceptable = len(results_df[(results_df['R²'] > 0.5) & (results_df['R²'] <= 0.7)]) st.metric("Nombre d'imputations acceptables", acceptable, delta=f"{acceptable/len(results_df)*100:.1f}%") with col3: difficult = len(results_df[results_df['R²'] <= 0.5]) st.metric("Nombre d'imputations déconseillées", difficult, delta=f"{difficult/len(results_df)*100:.1f}%") else: st.info("👈 Lancez d'abord une analyse pour voir les statistiques") else: st.info("👈 Veuillez sélectionner un jeu de données avec au moins 2 variables numériques.")