import streamlit as st import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt from scipy.stats import chi2_contingency # Configuration de la page st.set_page_config( page_title="Matrice V de Cramer", page_icon="📊", layout="wide" ) # Fonction de calcul du V de Cramer def cramers_v(x, y): """ Calcule le V de Cramer entre deux variables nominales (qualitatives). """ confusion_matrix = pd.crosstab(x, y) chi2 = chi2_contingency(confusion_matrix)[0] n = confusion_matrix.sum().sum() phi2 = chi2 / n r, k = confusion_matrix.shape # Correction pour les petites tailles d'échantillon phi2_corrected = max(0, phi2 - ((k-1)*(r-1))/(n-1)) r_corrected = r - ((r-1)**2)/(n-1) k_corrected = k - ((k-1)**2)/(n-1) return np.sqrt(phi2_corrected / min(k_corrected-1, r_corrected-1)) # Fonction pour calculer la matrice def calculate_cramer_matrix(df, columns): """ Calcule la matrice des V de Cramer pour les colonnes sélectionnées. """ v_cramer_matrix = pd.DataFrame(index=columns, columns=columns) for col1 in columns: for col2 in columns: if col1 == col2: v_cramer_matrix.loc[col1, col2] = 1.0 else: v_cramer = cramers_v(df[col1], df[col2]) v_cramer_matrix.loc[col1, col2] = v_cramer return v_cramer_matrix.astype(float) # Titre de l'application st.title("📊 Matrice des V de Cramer") st.markdown("Analyse de l'association entre variables catégorielles") # Sidebar pour la sélection des données st.sidebar.header("Configuration") # Choix de la source de données data_source = st.sidebar.radio( "Source des données", ["Jeu de données Seaborn", "Importer un fichier"], label_visibility="visible" ) df = None # Chargement des données if data_source == "Jeu de données Seaborn": # Liste des jeux de données disponibles seaborn_datasets = [ 'titanic', 'tips', 'penguins', 'diamonds', 'mpg', 'taxis' ] selected_dataset = st.sidebar.selectbox( "Choisir un jeu de données", seaborn_datasets ) try: df = sns.load_dataset(selected_dataset) st.sidebar.success(f"✅ Jeu '{selected_dataset}' chargé") except Exception as e: st.sidebar.error(f"Erreur : {e}") else: # Import de fichier uploaded_file = st.sidebar.file_uploader( "Importer un fichier CSV", type=['csv'] ) if uploaded_file is not None: try: # Détection automatique du séparateur avec engine='python' df = pd.read_csv(uploaded_file, sep=None, engine='python') st.sidebar.success("✅ Fichier importé") except Exception as e: st.sidebar.error(f"Erreur : {e}") # Traitement des données if df is not None: # Sélection des colonnes catégorielles categorical_cols = df.select_dtypes(include=['object', 'category', 'bool']).columns.tolist() # Ajouter les colonnes numériques avec peu de valeurs uniques numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns for col in numeric_cols: if df[col].nunique() <= 10: categorical_cols.append(col) if len(categorical_cols) == 0: st.warning("⚠️ Aucune variable catégorielle détectée.") else: st.sidebar.write(f"**{len(categorical_cols)}** variables détectées") # Sélection des colonnes à analyser selected_cols = st.sidebar.multiselect( "Variables à analyser", categorical_cols, default=categorical_cols[:min(6, len(categorical_cols))] ) if len(selected_cols) < 2: st.info("ℹ️ Veuillez sélectionner au moins 2 variables.") else: # Préparation des données df_cat = df[selected_cols].copy() # Options de traitement des valeurs manquantes nan_handling = st.sidebar.radio( "Valeurs manquantes", ["Remplacer par 'Missing'", "Supprimer les lignes"] ) # Traitement des valeurs manquantes if nan_handling == "Remplacer par 'Missing'": for col in selected_cols: # Convertir d'abord en string pour éviter l'erreur avec les categories df_cat[col] = df_cat[col].astype(str) df_cat[col] = df_cat[col].replace('nan', 'Missing') df_cat[col] = df_cat[col].replace('None', 'Missing') df_cat[col] = df_cat[col].astype('category') else: df_cat = df_cat.dropna() # Convertir en category for col in selected_cols: df_cat[col] = df_cat[col].astype('category') # Calcul de la matrice if st.sidebar.button("🔄 Calculer", type="primary"): with st.spinner("Calcul en cours..."): v_cramer_matrix = calculate_cramer_matrix(df_cat, selected_cols) # Onglets pour organiser l'affichage tab1, tab2 = st.tabs(["📊 Visualisation", "📋 Données"]) with tab1: col1, col2 = st.columns([2.5, 1]) with col1: # Création de la heatmap (taille réduite) fig, ax = plt.subplots(figsize=(8, 6)) sns.heatmap( v_cramer_matrix, annot=True, cmap='coolwarm', fmt=".2f", linewidths=0.5, cbar_kws={'label': 'V de Cramer'}, ax=ax, vmin=0, vmax=1 ) plt.title('Matrice des V de Cramer', fontsize=12, pad=15) plt.tight_layout() st.pyplot(fig) with col2: st.markdown("**Interprétation :**") st.markdown(""" - **0.0-0.1** : Très faible - **0.1-0.3** : Faible - **0.3-0.5** : Modérée - **0.5-0.7** : Forte - **0.7-1.0** : Très forte """) st.markdown("**Top 5 associations :**") # Extraire les valeurs uniques (triangle supérieur) associations = [] for i, col1_name in enumerate(selected_cols): for j, col2_name in enumerate(selected_cols): if i < j: associations.append({ 'Var 1': col1_name, 'Var 2': col2_name, 'V': v_cramer_matrix.loc[col1_name, col2_name] }) if associations: top_assoc = pd.DataFrame(associations).sort_values( 'V', ascending=False ).head(5) st.dataframe(top_assoc, hide_index=True, height=210) with tab2: st.write(f"**Dimensions :** {df.shape[0]} lignes × {df.shape[1]} colonnes") st.dataframe(df.head(20), use_container_width=True) else: st.info("👈 Veuillez sélectionner ou importer un jeu de données.") # Footer st.markdown("---") st.markdown("""