Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| import os | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.preprocessing import StandardScaler | |
| from sklearn.impute import SimpleImputer | |
| from sklearn.feature_selection import RFE | |
| from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier | |
| from sklearn.tree import DecisionTreeClassifier | |
| from sklearn.svm import SVC | |
| from sklearn.neighbors import KNeighborsClassifier | |
| from sklearn.neural_network import MLPClassifier | |
| from sklearn.metrics import ( | |
| accuracy_score, recall_score, precision_score, f1_score, | |
| roc_auc_score, confusion_matrix, roc_curve | |
| ) | |
| # Correção da biblioteca de balanceamento | |
| from imblearn.over_sampling import SMOTE | |
| # Instalações condicionais | |
| try: | |
| import xgboost as xgb | |
| from xgboost import XGBClassifier | |
| except ImportError: | |
| pass # Tratado no código | |
| try: | |
| import lightgbm as lgb | |
| from lightgbm import LGBMClassifier | |
| except ImportError: | |
| pass # Tratado no código | |
| # --- CONFIGURAÇÃO DA PÁGINA --- | |
| st.set_page_config( | |
| page_title="Analytics: Previsão de Insatisfação", | |
| layout="wide", | |
| page_icon="📈" | |
| ) | |
| # --- FUNÇÕES DE CARREGAMENTO E PREPARAÇÃO --- | |
| def load_data(): | |
| file_path = "marketing_campaign.csv" | |
| if not os.path.exists(file_path): | |
| return None | |
| try: | |
| # O dataset original usa separador ';' | |
| df = pd.read_csv(file_path, sep=';') | |
| return df | |
| except Exception as e: | |
| st.error(f"Erro ao ler a base de dados: {e}") | |
| return None | |
| def preprocess_data(df): | |
| df_clean = df.copy() | |
| # 1. Engenharia de Atributos | |
| # Calcular Idade (Baseado em 2014, referência do dataset) | |
| df_clean['Age'] = 2014 - df_clean['Year_Birth'] | |
| # Calcular Tempo de Cliente | |
| df_clean['Dt_Customer'] = pd.to_datetime(df_clean['Dt_Customer'], dayfirst=True) | |
| df_clean['Customer_Days'] = (pd.to_datetime("2014-12-31") - df_clean['Dt_Customer']).dt.days | |
| # Total de Filhos e Gasto Total | |
| df_clean['Children'] = df_clean['Kidhome'] + df_clean['Teenhome'] | |
| mnt_cols = [col for col in df_clean.columns if 'Mnt' in col] | |
| df_clean['Total_Spent'] = df_clean[mnt_cols].sum(axis=1) | |
| # 2. Limpeza | |
| df_clean = df_clean[df_clean['Year_Birth'] > 1900] # Remover outliers de idade | |
| # Remover colunas não preditivas ou vazadas | |
| cols_drop = ['ID', 'Year_Birth', 'Dt_Customer', 'Z_CostContact', 'Z_Revenue'] | |
| df_clean = df_clean.drop(columns=[c for c in cols_drop if c in df_clean.columns]) | |
| # Tratar Nulos (Imputação pela mediana) | |
| imputer = SimpleImputer(strategy='median') | |
| numeric_cols = df_clean.select_dtypes(include=[np.number]).columns.drop('Complain', errors='ignore') | |
| df_clean[numeric_cols] = imputer.fit_transform(df_clean[numeric_cols]) | |
| # 3. Encoding (One-Hot para categóricas) | |
| cat_cols = df_clean.select_dtypes(include=['object']).columns | |
| df_clean = pd.get_dummies(df_clean, columns=cat_cols, drop_first=True) | |
| return df_clean | |
| # --- INTERFACE PRINCIPAL --- | |
| st.title("📈 Customer Success Analytics") | |
| st.markdown("### Monitoramento e Previsão de Reclamações de Clientes") | |
| st.markdown(""" | |
| Este painel utiliza Inteligência Artificial para analisar o comportamento do consumidor e identificar perfis com alta probabilidade de insatisfação (Churn/Reclamação). | |
| """) | |
| # --- CARREGAMENTO AUTOMÁTICO --- | |
| df_raw = load_data() | |
| if df_raw is None: | |
| st.error("⚠️ Arquivo 'marketing_campaign.csv' não encontrado no diretório. Por favor, verifique os arquivos do Space.") | |
| st.stop() | |
| df_processed = preprocess_data(df_raw) | |
| target_col = 'Complain' | |
| if target_col not in df_processed.columns: | |
| st.error(f"Erro Crítico: A coluna alvo '{target_col}' não existe na base de dados.") | |
| st.stop() | |
| X = df_processed.drop(columns=[target_col]) | |
| y = df_processed[target_col] | |
| # --- SIDEBAR: CONFIGURAÇÕES --- | |
| st.sidebar.header("⚙️ Configurações da Análise") | |
| # 1. Seleção de Variáveis | |
| with st.sidebar.expander("1. Seleção de Variáveis (Feature Selection)", expanded=False): | |
| selection_method = st.radio("Método:", ["Automático (RFE)", "Manual"]) | |
| features_selected = list(X.columns) | |
| if selection_method == "Manual": | |
| features_selected = st.multiselect("Variáveis:", options=list(X.columns), default=list(X.columns)) | |
| else: | |
| n_features = st.slider("Qtd. Variáveis:", 5, len(X.columns), 15) | |
| if st.button("Recalcular Relevância (RFE)"): | |
| with st.spinner("Analisando correlações..."): | |
| rfe = RFE(estimator=RandomForestClassifier(n_jobs=-1, random_state=42), n_features_to_select=n_features, step=1) | |
| rfe.fit(X, y) | |
| features_selected = X.columns[rfe.support_].tolist() | |
| st.success("Variáveis otimizadas!") | |
| X_final = X[features_selected] | |
| # 2. Parâmetros do Modelo | |
| with st.sidebar.expander("2. Ajuste de Hiperparâmetros", expanded=True): | |
| balance_data = st.toggle("Aplicar Balanceamento (SMOTE)", value=True) | |
| test_split = st.slider("Dados para Teste (%)", 10, 40, 30) / 100 | |
| st.write("**Ajuste Fino:**") | |
| n_trees = st.slider("Nº Árvores (Random Forest/Boosting)", 50, 300, 100) | |
| knn_k = st.slider("K-Vizinhos (KNN)", 3, 15, 5) | |
| # 3. Botão de Execução | |
| run_analysis = st.sidebar.button("🔄 EXECUTAR ANÁLISE PREDITIVA", type="primary") | |
| # --- ÁREA DE VISUALIZAÇÃO DE DADOS (Sempre visível) --- | |
| with st.expander("📊 Visão Geral dos Dados (Clique para abrir)"): | |
| col1, col2, col3 = st.columns(3) | |
| col1.metric("Total de Clientes", len(df_processed)) | |
| col1.metric("Taxa de Reclamação", f"{(y.mean()*100):.2f}%") | |
| fig_target = px.pie(values=y.value_counts(), names=["Satisfeito", "Reclamou"], title="Distribuição da Classe Alvo", hole=0.4) | |
| fig_target.update_layout(height=300, margin=dict(t=30, b=0, l=0, r=0)) | |
| col2.plotly_chart(fig_target, use_container_width=True) | |
| if 'Total_Spent' in df_processed.columns: | |
| fig_hist = px.histogram(df_processed, x="Total_Spent", color="Complain", nbins=30, title="Gasto Total x Reclamação") | |
| fig_hist.update_layout(height=300, margin=dict(t=30, b=0, l=0, r=0)) | |
| col3.plotly_chart(fig_hist, use_container_width=True) | |
| # --- EXECUÇÃO DOS MODELOS --- | |
| if run_analysis: | |
| st.divider() | |
| # Preparação | |
| X_train, X_test, y_train, y_test = train_test_split(X_final, y, test_size=test_split, random_state=42, stratify=y) | |
| scaler = StandardScaler() | |
| X_train_sc = scaler.fit_transform(X_train) | |
| X_test_sc = scaler.transform(X_test) | |
| if balance_data: | |
| smote = SMOTE(random_state=42) | |
| X_train_bal, y_train_bal = smote.fit_resample(X_train_sc, y_train) | |
| else: | |
| X_train_bal, y_train_bal = X_train_sc, y_train | |
| # Definição dos Modelos | |
| models = { | |
| "Random Forest": RandomForestClassifier(n_estimators=n_trees, random_state=42), | |
| "Gradient Boosting": GradientBoostingClassifier(n_estimators=n_trees, random_state=42), | |
| "Logistic Regression": LogisticRegression(max_iter=1000), | |
| "KNN": KNeighborsClassifier(n_neighbors=knn_k), | |
| "SVM": SVC(probability=True, random_state=42), | |
| "Neural Network": MLPClassifier(max_iter=500, random_state=42) | |
| } | |
| # Adicionar XGBoost/LightGBM se disponíveis | |
| try: | |
| models["XGBoost"] = XGBClassifier(n_estimators=n_trees, use_label_encoder=False, eval_metric='logloss', random_state=42) | |
| except: pass | |
| try: | |
| models["LightGBM"] = LGBMClassifier(n_estimators=n_trees, verbose=-1, random_state=42) | |
| except: pass | |
| results = [] | |
| best_auc = 0 | |
| best_model_name = "" | |
| best_model_obj = None | |
| progress_bar = st.progress(0) | |
| for i, (name, model) in enumerate(models.items()): | |
| # Treino e Predição | |
| model.fit(X_train_bal, y_train_bal) | |
| y_pred = model.predict(X_test_sc) | |
| y_proba = [0]*len(y_test) | |
| if hasattr(model, "predict_proba"): | |
| y_proba = model.predict_proba(X_test_sc)[:, 1] | |
| # Métricas | |
| auc = roc_auc_score(y_test, y_proba) if hasattr(model, "predict_proba") else 0.5 | |
| results.append({ | |
| "Modelo": name, | |
| "AUC": auc, | |
| "Precision": precision_score(y_test, y_pred, zero_division=0), | |
| "Recall": recall_score(y_test, y_pred), | |
| "F1-Score": f1_score(y_test, y_pred), | |
| "Accuracy": accuracy_score(y_test, y_pred), | |
| "Model_Obj": model, | |
| "y_proba": y_proba, | |
| "y_pred": y_pred | |
| }) | |
| if auc > best_auc: | |
| best_auc = auc | |
| best_model_name = name | |
| best_model_obj = model | |
| progress_bar.progress((i + 1) / len(models)) | |
| progress_bar.empty() | |
| df_results = pd.DataFrame(results).sort_values(by="AUC", ascending=False) | |
| # --- RESULTADOS --- | |
| tab1, tab2, tab3 = st.tabs(["🏆 Ranking de Modelos", "📉 Detalhes Técnicos", "💡 Insights de Negócio"]) | |
| with tab1: | |
| st.subheader("Performance Comparativa") | |
| # CORREÇÃO DO ERRO DE FORMATAÇÃO AQUI | |
| # Formatamos apenas as colunas numéricas, excluindo o nome do modelo | |
| cols_to_format = ["AUC", "Precision", "Recall", "F1-Score", "Accuracy"] | |
| st.dataframe( | |
| df_results.drop(columns=["Model_Obj", "y_proba", "y_pred"]) | |
| .style.format({col: "{:.2%}" for col in cols_to_format}) | |
| .background_gradient(cmap="Blues", subset=["AUC"]) | |
| ) | |
| st.info(f"O modelo **{best_model_name}** apresentou a melhor capacidade de discriminação (AUC = {best_auc:.2%}).") | |
| fig_comp = px.bar(df_results, x="Modelo", y=["AUC", "Recall"], barmode="group", title="AUC vs Recall (Sensibilidade)") | |
| st.plotly_chart(fig_comp, use_container_width=True) | |
| with tab2: | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("#### Curvas ROC") | |
| fig_roc = go.Figure() | |
| fig_roc.add_shape(type='line', line=dict(dash='dash'), x0=0, x1=1, y0=0, y1=1) | |
| for res in results: | |
| if hasattr(res['Model_Obj'], "predict_proba"): | |
| fpr, tpr, _ = roc_curve(y_test, res['y_proba']) | |
| fig_roc.add_trace(go.Scatter(x=fpr, y=tpr, mode='lines', name=f"{res['Modelo']}")) | |
| fig_roc.update_layout(xaxis_title='Falsos Positivos', yaxis_title='Verdadeiros Positivos', height=400) | |
| st.plotly_chart(fig_roc, use_container_width=True) | |
| with col2: | |
| st.markdown(f"#### Matriz de Confusão ({best_model_name})") | |
| best_row = df_results.iloc[0] | |
| cm = confusion_matrix(y_test, best_row['y_pred']) | |
| fig_cm = px.imshow(cm, text_auto=True, color_continuous_scale='Blues', | |
| labels=dict(x="Previsão", y="Real"), x=["Sem Reclamação", "Reclamou"], y=["Sem Reclamação", "Reclamou"]) | |
| st.plotly_chart(fig_cm, use_container_width=True) | |
| with tab3: | |
| st.subheader("Fatores de Influência") | |
| importances = None | |
| if hasattr(best_model_obj, 'feature_importances_'): | |
| importances = best_model_obj.feature_importances_ | |
| elif hasattr(best_model_obj, 'coef_'): | |
| importances = np.abs(best_model_obj.coef_[0]) | |
| if importances is not None: | |
| df_imp = pd.DataFrame({'Fator': features_selected, 'Importância': importances}).sort_values(by='Importância', ascending=False).head(10) | |
| fig_imp = px.bar(df_imp, x='Importância', y='Fator', orientation='h', color='Importância', title=f"Top 10 Variáveis ({best_model_name})", color_continuous_scale='Teal') | |
| fig_imp.update_layout(yaxis={'categoryorder':'total ascending'}) | |
| st.plotly_chart(fig_imp, use_container_width=True) | |
| top_3 = df_imp['Fator'].tolist()[:3] | |
| st.success(f""" | |
| **Recomendação Estratégica:** | |
| Os dados indicam que os fatores **{top_3[0]}**, **{top_3[1]}** e **{top_3[2]}** são os maiores preditores de reclamações. | |
| A equipe deve focar ações preventivas em clientes que apresentem variações nestes indicadores. | |
| """) | |
| else: | |
| st.warning("O modelo selecionado não fornece importância direta das variáveis.") | |
| else: | |
| st.info("👈 Configure os parâmetros na barra lateral e clique em 'EXECUTAR ANÁLISE PREDITIVA' para iniciar.") |