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 --- @st.cache_data 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.")