tarefa4_siep / app.py
brunaaaz's picture
Update app.py
90a0988 verified
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.")