alicetport commited on
Commit
b35ed0b
·
verified ·
1 Parent(s): 7eb616f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +646 -0
app.py ADDED
@@ -0,0 +1,646 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ import streamlit as st
5
+ import pandas as pd
6
+ import numpy as np
7
+ from sklearn.linear_model import LogisticRegression
8
+ from sklearn.feature_selection import RFE
9
+ from sklearn.model_selection import train_test_split, GridSearchCV
10
+ from sklearn.metrics import (
11
+ accuracy_score, roc_auc_score, precision_score,
12
+ recall_score, f1_score, confusion_matrix, roc_curve, classification_report
13
+ )
14
+ from sklearn.preprocessing import StandardScaler, LabelEncoder
15
+ from sklearn.neighbors import KNeighborsClassifier
16
+ from sklearn.svm import SVC
17
+ from sklearn.tree import DecisionTreeClassifier
18
+ from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
19
+ from sklearn.neural_network import MLPClassifier
20
+ import plotly.express as px
21
+ import plotly.graph_objects as go
22
+ import statsmodels.api as sm
23
+ import matplotlib.pyplot as plt
24
+ import seaborn as sns
25
+ import warnings
26
+ warnings.filterwarnings('ignore')
27
+
28
+ # Tentar importar bibliotecas com fallback
29
+ try:
30
+ from imblearn.over_sampling import SMOTE
31
+ SMOTE_AVAILABLE = True
32
+ except ImportError:
33
+ SMOTE_AVAILABLE = False
34
+
35
+ try:
36
+ from xgboost import XGBClassifier
37
+ XGB_AVAILABLE = True
38
+ except ImportError:
39
+ XGB_AVAILABLE = False
40
+
41
+ try:
42
+ from lightgbm import LGBMClassifier
43
+ LGBM_AVAILABLE = True
44
+ except ImportError:
45
+ LGBM_AVAILABLE = False
46
+
47
+ # Configuração da página
48
+ st.set_page_config(
49
+ page_title="Sistema de Previsão de Reclamações - Tarefa 4",
50
+ layout="wide",
51
+ initial_sidebar_state="expanded"
52
+ )
53
+
54
+ # CSS personalizado
55
+ st.markdown("""
56
+ <style>
57
+ .main-header {
58
+ font-size: 2.5rem;
59
+ color: #2E86AB;
60
+ text-align: center;
61
+ margin-bottom: 1rem;
62
+ font-weight: bold;
63
+ }
64
+ .sub-header {
65
+ font-size: 1.5rem;
66
+ color: #A23B72;
67
+ text-align: center;
68
+ margin-bottom: 2rem;
69
+ }
70
+ .metric-card {
71
+ background-color: #f0f2f6;
72
+ padding: 15px;
73
+ border-radius: 10px;
74
+ border-left: 5px solid #2E86AB;
75
+ margin: 10px 0;
76
+ }
77
+ </style>
78
+ """, unsafe_allow_html=True)
79
+
80
+ # Cabeçalho
81
+ st.markdown('<p class="main-header">🔍 Sistema de Previsão de Reclamações de Clientes</p>', unsafe_allow_html=True)
82
+ st.markdown('<p class="sub-header">Tarefa 4 - Modelos Supervisionados para Previsão de Reclamações</p>', unsafe_allow_html=True)
83
+
84
+ st.markdown("---")
85
+
86
+ # Descrição da Tarefa 4
87
+ with st.expander("🎯 OBJETIVOS DA TAREFA 4 - CONFORME ENUNCIADO"):
88
+ st.markdown("""
89
+ **I. Análise Preditiva com Múltiplos Modelos Supervisionados**
90
+
91
+ a) **Balanceamento da Base:** Verificar desbalanceamento e aplicar SMOTE
92
+ b) **Seleção de Variáveis:** Utilizar RFE (Recursive Feature Elimination)
93
+ c) **Treinamento de Modelos:** Aplicar, comparar e discutir desempenho dos grupos:
94
+ - Modelos baseados em distância: KNN, SVM
95
+ - Modelos de Bagging: Árvore de Decisão, Random Forest
96
+ - Modelos de Boosting: AdaBoost, Gradient Boosting, XGBoost, LightGBM
97
+ - Redes Neurais
98
+
99
+ d) **Sensibilidade aos Hiperparâmetros:** Analisar impacto dos ajustes
100
+ e) **Tomada de Decisão e Aplicação Gerencial:** Analisar fatores que influenciam reclamações
101
+
102
+ **Avaliar com métricas:** AUC, Curva ROC, Precisão, Recall, F1-score, Matriz de Confusão
103
+ """)
104
+
105
+ # Carregar dados
106
+ @st.cache_data
107
+ def load_data():
108
+ try:
109
+ data = pd.read_csv('marketing_campaign.csv', sep='\t')
110
+ return data
111
+ except Exception as e:
112
+ st.error(f"❌ ERRO: Não foi possível carregar o arquivo 'marketing_campaign.csv'")
113
+ st.error(f"Detalhes: {e}")
114
+ st.stop()
115
+
116
+ data = load_data()
117
+
118
+ # Sidebar para configurações
119
+ with st.sidebar:
120
+ st.header("⚙️ CONFIGURAÇÕES - TAREFA 4")
121
+
122
+ st.subheader("I.a) Balanceamento")
123
+ aplicar_smote = st.checkbox("Aplicar SMOTE para balanceamento", value=True)
124
+
125
+ st.subheader("I.b) Seleção de Variáveis")
126
+ aplicar_rfe = st.checkbox("Aplicar RFE para seleção de variáveis", value=True)
127
+ if aplicar_rfe:
128
+ n_features = st.slider("Número de variáveis via RFE", 8, 15, 12)
129
+
130
+ st.subheader("I.c) Modelos Supervisionados")
131
+ st.write("**Selecione os modelos para comparação:**")
132
+
133
+ st.write("📏 **Baseados em Distância:**")
134
+ use_knn = st.checkbox("K-Nearest Neighbors (KNN)", value=True)
135
+ use_svm = st.checkbox("Support Vector Machine (SVM)", value=True)
136
+
137
+ st.write("🌳 **Modelos de Bagging:**")
138
+ use_tree = st.checkbox("Árvore de Decisão", value=True)
139
+ use_rf = st.checkbox("Random Forest", value=True)
140
+
141
+ st.write("🚀 **Modelos de Boosting:**")
142
+ use_ada = st.checkbox("AdaBoost", value=True)
143
+ use_gb = st.checkbox("Gradient Boosting", value=True)
144
+ use_xgb = st.checkbox("XGBoost", value=XGB_AVAILABLE)
145
+ use_lgbm = st.checkbox("LightGBM", value=LGBM_AVAILABLE)
146
+
147
+ st.write("🧠 **Redes Neurais:**")
148
+ use_nn = st.checkbox("Rede Neural (MLP)", value=True)
149
+
150
+ st.subheader("I.d) Sensibilidade Hiperparâmetros")
151
+ tunar_hiperparametros = st.checkbox("Ajustar hiperparâmetros (GridSearchCV)", value=False)
152
+
153
+ st.subheader("Divisão dos Dados")
154
+ test_size = st.slider("Proporção teste", 0.1, 0.5, 0.3, 0.05)
155
+
156
+ # =============================================================================
157
+ # PRÉ-PROCESSAMENTO (IGUAL AO SEU CÓDIGO)
158
+ # =============================================================================
159
+
160
+ st.header("🔧 PRÉ-PROCESSAMENTO DOS DADOS")
161
+
162
+ # Criar cópia para processamento
163
+ df_clean = data.copy()
164
+
165
+ # Remover valores nulos
166
+ initial_shape = df_clean.shape
167
+ df_clean = df_clean.dropna()
168
+ st.write(f"✅ Dados limpos: {initial_shape[0]} → {df_clean.shape[0]} linhas")
169
+
170
+ # Remover colunas desnecessárias
171
+ cols_to_remove = ['ID', 'Z_CostContact', 'Z_Revenue']
172
+ df_clean = df_clean.drop(columns=[col for col in cols_to_remove if col in df_clean.columns])
173
+
174
+ # Transformar Year_Birth em Idade
175
+ df_clean['Age'] = 2023 - df_clean['Year_Birth']
176
+ df_clean = df_clean.drop('Year_Birth', axis=1)
177
+
178
+ # Transformar Dt_Customer em tempo como cliente
179
+ df_clean['Dt_Customer'] = pd.to_datetime(df_clean['Dt_Customer'], format='%d-%m-%Y')
180
+ df_clean['Customer_Days'] = (df_clean['Dt_Customer'].max() - df_clean['Dt_Customer']).dt.days
181
+ df_clean = df_clean.drop('Dt_Customer', axis=1)
182
+
183
+ # Encoding de variáveis categóricas
184
+ education_map = {'Basic': 1, '2n Cycle': 2, 'Graduation': 3, 'Master': 4, 'PhD': 5}
185
+ df_clean['Education'] = df_clean['Education'].map(education_map)
186
+
187
+ marital_mapping = {
188
+ 'Single': 'Single', 'Together': 'Married', 'Married': 'Married',
189
+ 'Divorced': 'Divorced', 'Widow': 'Other', 'Alone': 'Other',
190
+ 'Absurd': 'Other', 'YOLO': 'Other'
191
+ }
192
+ df_clean['Marital_Status'] = df_clean['Marital_Status'].map(marital_mapping)
193
+ marital_dummies = pd.get_dummies(df_clean['Marital_Status'], prefix='Marital')
194
+ marital_dummies = marital_dummies[['Marital_Single', 'Marital_Married', 'Marital_Divorced']]
195
+ df_clean = pd.concat([df_clean, marital_dummies], axis=1)
196
+ df_clean = df_clean.drop('Marital_Status', axis=1)
197
+
198
+ st.success("✅ Pré-processamento concluído!")
199
+
200
+ # =============================================================================
201
+ # I.a) ANÁLISE DE BALANCEAMENTO
202
+ # =============================================================================
203
+
204
+ st.header("📊 I.a) ANÁLISE DO BALANCEAMENTO DA BASE")
205
+
206
+ col1, col2 = st.columns(2)
207
+
208
+ with col1:
209
+ # Distribuição da target
210
+ fig_target = px.pie(
211
+ values=df_clean['Complain'].value_counts().values,
212
+ names=['Não Reclamou', 'Reclamou'],
213
+ title='Distribuição Original da Variável Complain'
214
+ )
215
+ st.plotly_chart(fig_target)
216
+
217
+ with col2:
218
+ count_no_complain = (df_clean['Complain']==0).sum()
219
+ count_complain = (df_clean['Complain']==1).sum()
220
+ total = len(df_clean)
221
+
222
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
223
+ st.metric("Total de Clientes", total)
224
+ st.metric("Clientes sem Reclamações", f"{count_no_complain} ({count_no_complain/total*100:.1f}%)")
225
+ st.metric("Clientes com Reclamações", f"{count_complain} ({count_complain/total*100:.1f}%)")
226
+ st.markdown('</div>', unsafe_allow_html=True)
227
+
228
+ # =============================================================================
229
+ # APLICAÇÃO DO SMOTE
230
+ # =============================================================================
231
+
232
+ st.header("⚖️ APLICAÇÃO DO SMOTE")
233
+
234
+ # Separar features e target
235
+ X = df_clean.drop('Complain', axis=1)
236
+ y = df_clean['Complain']
237
+
238
+ # Dividir em treino e teste
239
+ X_train, X_test, y_train, y_test = train_test_split(
240
+ X, y, test_size=test_size, random_state=42, stratify=y
241
+ )
242
+
243
+ if aplicar_smote and SMOTE_AVAILABLE:
244
+ smote = SMOTE(random_state=42)
245
+ X_train_bal, y_train_bal = smote.fit_resample(X_train, y_train)
246
+ st.success("✅ SMOTE aplicado com sucesso!")
247
+
248
+ col1, col2 = st.columns(2)
249
+ with col1:
250
+ st.write("**ANTES do SMOTE:**")
251
+ st.write(f"Classe 0: {(y_train==0).sum()} ({(y_train==0).sum()/len(y_train)*100:.1f}%)")
252
+ st.write(f"Classe 1: {(y_train==1).sum()} ({(y_train==1).sum()/len(y_train)*100:.1f}%)")
253
+
254
+ with col2:
255
+ st.write("**APÓS o SMOTE:**")
256
+ st.write(f"Classe 0: {(y_train_bal==0).sum()} ({(y_train_bal==0).sum()/len(y_train_bal)*100:.1f}%)")
257
+ st.write(f"Classe 1: {(y_train_bal==1).sum()} ({(y_train_bal==1).sum()/len(y_train_bal)*100:.1f}%)")
258
+ elif aplicar_smote and not SMOTE_AVAILABLE:
259
+ st.error("❌ SMOTE não disponível - usando oversampling manual")
260
+ from sklearn.utils import resample
261
+ # Oversampling manual
262
+ X_train_combined = pd.concat([X_train, y_train], axis=1)
263
+ majority = X_train_combined[X_train_combined.Complain==0]
264
+ minority = X_train_combined[X_train_combined.Complain==1]
265
+ minority_oversampled = resample(minority, replace=True, n_samples=len(majority), random_state=42)
266
+ oversampled = pd.concat([majority, minority_oversampled])
267
+ X_train_bal = oversampled.drop('Complain', axis=1)
268
+ y_train_bal = oversampled['Complain']
269
+ else:
270
+ X_train_bal, y_train_bal = X_train, y_train
271
+ st.warning("⚠️ SMOTE não aplicado")
272
+
273
+ # =============================================================================
274
+ # I.b) SELEÇÃO DE VARIÁVEIS COM RFE
275
+ # =============================================================================
276
+
277
+ st.header("🎯 I.b) SELEÇÃO DE VARIÁVEIS COM RFE")
278
+
279
+ if aplicar_rfe:
280
+ # Abordagem híbrida para garantir variáveis importantes
281
+ core_vars = ['Income', 'Recency', 'Customer_Days', 'MntWines', 'Age', 'NumWebPurchases']
282
+ available_core_vars = [var for var in core_vars if var in X_train_bal.columns]
283
+
284
+ # RFE para variáveis restantes
285
+ remaining_vars = [col for col in X_train_bal.columns if col not in available_core_vars]
286
+ n_additional = max(1, n_features - len(available_core_vars))
287
+
288
+ if len(remaining_vars) > n_additional:
289
+ rfe = RFE(estimator=LogisticRegression(max_iter=1000, random_state=42),
290
+ n_features_to_select=n_additional)
291
+ rfe.fit(X_train_bal[remaining_vars], y_train_bal)
292
+ rfe_selected = [remaining_vars[i] for i in range(len(remaining_vars)) if rfe.support_[i]]
293
+ selected_features = available_core_vars + rfe_selected
294
+ else:
295
+ selected_features = available_core_vars + remaining_vars
296
+
297
+ st.success(f"✅ RFE aplicado - {len(selected_features)} variáveis selecionadas")
298
+ else:
299
+ selected_features = X_train_bal.columns.tolist()
300
+
301
+ st.write("**Variáveis selecionadas:**")
302
+ for i, feature in enumerate(selected_features, 1):
303
+ st.write(f"{i}. {feature}")
304
+
305
+ # Preparar dados finais
306
+ X_train_final = X_train_bal[selected_features]
307
+ X_test_final = X_test[selected_features]
308
+
309
+ # =============================================================================
310
+ # I.c) TREINAMENTO DOS MODELOS SUPERVISIONADOS
311
+ # =============================================================================
312
+
313
+ st.header("🤖 I.c) TREINAMENTO E COMPARAÇÃO DOS MODELOS")
314
+
315
+ # Dicionário de modelos conforme grupos da tarefa
316
+ models = {}
317
+
318
+ # Modelos baseados em distância
319
+ if use_knn: models['K-Nearest Neighbors'] = KNeighborsClassifier()
320
+ if use_svm: models['Support Vector Machine'] = SVC(probability=True, random_state=42)
321
+
322
+ # Modelos de Bagging
323
+ if use_tree: models['Decision Tree'] = DecisionTreeClassifier(random_state=42)
324
+ if use_rf: models['Random Forest'] = RandomForestClassifier(random_state=42)
325
+
326
+ # Modelos de Boosting
327
+ if use_ada: models['AdaBoost'] = AdaBoostClassifier(random_state=42)
328
+ if use_gb: models['Gradient Boosting'] = GradientBoostingClassifier(random_state=42)
329
+ if use_xgb and XGB_AVAILABLE: models['XGBoost'] = XGBClassifier(random_state=42, eval_metric='logloss')
330
+ if use_lgbm and LGBM_AVAILABLE: models['LightGBM'] = LGBMClassifier(random_state=42)
331
+
332
+ # Redes Neurais
333
+ if use_nn: models['Neural Network'] = MLPClassifier(random_state=42, max_iter=1000)
334
+
335
+ if not models:
336
+ st.error("❌ Selecione pelo menos um modelo para treinamento")
337
+ st.stop()
338
+
339
+ # Treinar modelos e coletar resultados
340
+ results = {}
341
+ progress_bar = st.progress(0)
342
+ status_text = st.empty()
343
+
344
+ for i, (name, model) in enumerate(models.items()):
345
+ status_text.text(f"Treinando {name}...")
346
+
347
+ try:
348
+ model.fit(X_train_final, y_train_bal)
349
+ y_pred = model.predict(X_test_final)
350
+ y_proba = model.predict_proba(X_test_final)[:, 1] if hasattr(model, "predict_proba") else None
351
+
352
+ # Calcular métricas
353
+ auc = roc_auc_score(y_test, y_proba) if y_proba is not None else 0
354
+ accuracy = accuracy_score(y_test, y_pred)
355
+ precision = precision_score(y_test, y_pred)
356
+ recall = recall_score(y_test, y_pred)
357
+ f1 = f1_score(y_test, y_pred)
358
+
359
+ results[name] = {
360
+ 'model': model,
361
+ 'auc': auc,
362
+ 'accuracy': accuracy,
363
+ 'precision': precision,
364
+ 'recall': recall,
365
+ 'f1': f1,
366
+ 'y_pred': y_pred,
367
+ 'y_proba': y_proba
368
+ }
369
+
370
+ except Exception as e:
371
+ st.error(f"Erro no modelo {name}: {e}")
372
+
373
+ progress_bar.progress((i + 1) / len(models))
374
+
375
+ status_text.text("✅ Todos os modelos treinados!")
376
+
377
+ # =============================================================================
378
+ # COMPARAÇÃO DOS MODELOS
379
+ # =============================================================================
380
+
381
+ st.subheader("📊 COMPARAÇÃO DOS MODELOS - MÉTRICAS")
382
+
383
+ # Tabela de comparação
384
+ results_df = pd.DataFrame([
385
+ {
386
+ 'Modelo': name,
387
+ 'AUC': result['auc'],
388
+ 'Acurácia': result['accuracy'],
389
+ 'Precisão': result['precision'],
390
+ 'Recall': result['recall'],
391
+ 'F1-Score': result['f1']
392
+ }
393
+ for name, result in results.items()
394
+ ]).sort_values('AUC', ascending=False)
395
+
396
+ st.dataframe(results_df.style.format({
397
+ 'AUC': '{:.3f}', 'Acurácia': '{:.3f}', 'Precisão': '{:.3f}',
398
+ 'Recall': '{:.3f}', 'F1-Score': '{:.3f}'
399
+ }).background_gradient(subset=['AUC', 'F1-Score'], cmap='Blues'))
400
+
401
+ # Identificar melhor modelo
402
+ best_model_name = results_df.iloc[0]['Modelo']
403
+ best_model = results[best_model_name]
404
+ st.success(f"🏆 MELHOR MODELO: {best_model_name} (AUC: {best_model['auc']:.3f})")
405
+
406
+ # =============================================================================
407
+ # I.d) SENSIBILIDADE AOS HIPERPARÂMETROS
408
+ # =============================================================================
409
+
410
+ st.header("🎛️ I.d) SENSIBILIDADE AOS HIPERPARÂMETROS")
411
+
412
+ if tunar_hiperparametros and len(models) >= 2:
413
+ st.write("**Ajuste de hiperparâmetros para dois modelos selecionados:**")
414
+
415
+ # Selecionar dois modelos para tuning
416
+ models_to_tune = list(models.keys())[:2]
417
+
418
+ for model_name in models_to_tune:
419
+ with st.expander(f"🔧 Ajuste de {model_name}"):
420
+ if model_name == 'Random Forest':
421
+ param_grid = {
422
+ 'n_estimators': [50, 100, 200],
423
+ 'max_depth': [5, 10, None],
424
+ 'min_samples_split': [2, 5, 10]
425
+ }
426
+ base_model = RandomForestClassifier(random_state=42)
427
+ elif model_name == 'K-Nearest Neighbors':
428
+ param_grid = {
429
+ 'n_neighbors': [3, 5, 7, 10],
430
+ 'weights': ['uniform', 'distance'],
431
+ 'metric': ['euclidean', 'manhattan']
432
+ }
433
+ base_model = KNeighborsClassifier()
434
+ else:
435
+ continue
436
+
437
+ grid_search = GridSearchCV(base_model, param_grid, cv=5, scoring='roc_auc', n_jobs=-1)
438
+ grid_search.fit(X_train_final, y_train_bal)
439
+
440
+ st.write(f"**Melhores parâmetros:** {grid_search.best_params_}")
441
+ st.write(f"**Melhor score (validação):** {grid_search.best_score_:.3f}")
442
+
443
+ # Comparar com modelo padrão
444
+ original_auc = results[model_name]['auc']
445
+ tuned_auc = roc_auc_score(y_test, grid_search.best_estimator_.predict_proba(X_test_final)[:, 1])
446
+
447
+ st.write(f"**Comparação de AUC:**")
448
+ st.write(f"- Original: {original_auc:.3f}")
449
+ st.write(f"- Com tuning: {tuned_auc:.3f}")
450
+ st.write(f"- Melhoria: {tuned_auc - original_auc:.3f}")
451
+
452
+ # =============================================================================
453
+ # ANÁLISE DO MELHOR MODELO
454
+ # =============================================================================
455
+
456
+ st.header("🔍 ANÁLISE DETALHADA DO MELHOR MODELO")
457
+
458
+ col1, col2 = st.columns(2)
459
+
460
+ with col1:
461
+ # Curva ROC
462
+ if best_model['y_proba'] is not None:
463
+ fpr, tpr, _ = roc_curve(y_test, best_model['y_proba'])
464
+ fig_roc = go.Figure()
465
+ fig_roc.add_trace(go.Scatter(x=fpr, y=tpr, mode='lines',
466
+ name=f'{best_model_name} (AUC = {best_model["auc"]:.3f})',
467
+ line=dict(width=3)))
468
+ fig_roc.add_trace(go.Scatter(x=[0, 1], y=[0, 1], mode='lines',
469
+ name='Classificador Aleatório', line=dict(dash='dash', color='red')))
470
+ fig_roc.update_layout(title='Curva ROC', xaxis_title='Taxa de Falsos Positivos',
471
+ yaxis_title='Taxa de Verdadeiros Positivos', showlegend=True)
472
+ st.plotly_chart(fig_roc)
473
+
474
+ with col2:
475
+ # Matriz de Confusão
476
+ cm = confusion_matrix(y_test, best_model['y_pred'])
477
+ fig_cm = px.imshow(cm, text_auto=True, title='Matriz de Confusão',
478
+ labels=dict(x="Predito", y="Real", color="Quantidade"),
479
+ x=['Não Reclamou', 'Reclamou'],
480
+ y=['Não Reclamou', 'Reclamou'],
481
+ color_continuous_scale="Blues")
482
+ st.plotly_chart(fig_cm)
483
+
484
+ # Relatório de classificação
485
+ st.subheader("📋 Relatório de Classificação Detalhado")
486
+ st.text(classification_report(y_test, best_model['y_pred'], target_names=['Não Reclamou', 'Reclamou']))
487
+
488
+ # =============================================================================
489
+ # I.e) TOMADA DE DECISÃO E APLICAÇÃO GERENCIAL
490
+ # =============================================================================
491
+
492
+ st.header("💡 I.e) TOMADA DE DECISÃO E APLICAÇÃO GERENCIAL")
493
+
494
+ st.subheader("📈 ANÁLISE DE IMPORTÂNCIA DAS VARIÁVEIS")
495
+
496
+ if hasattr(best_model['model'], 'feature_importances_'):
497
+ # Modelos baseados em árvore
498
+ importances = best_model['model'].feature_importances_
499
+ feature_imp_df = pd.DataFrame({
500
+ 'feature': selected_features,
501
+ 'importance': importances
502
+ }).sort_values('importance', ascending=True)
503
+
504
+ fig_imp = px.bar(feature_imp_df.tail(10), x='importance', y='feature',
505
+ orientation='h', title='Top 10 Variáveis Mais Importantes - Feature Importance')
506
+ st.plotly_chart(fig_imp)
507
+
508
+ # Análise gerencial
509
+ st.subheader("🎯 INTERPRETAÇÃO GERENCIAL")
510
+
511
+ top_features = feature_imp_df.tail(5)
512
+ st.write("**Variáveis com maior impacto nas reclamações:**")
513
+ for _, row in top_features.iterrows():
514
+ st.write(f"• **{row['feature']}**: {row['importance']:.3f}")
515
+
516
+ elif hasattr(best_model['model'], 'coef_'):
517
+ # Modelos lineares
518
+ coefficients = best_model['model'].coef_[0]
519
+ coef_df = pd.DataFrame({
520
+ 'feature': selected_features,
521
+ 'coefficient': coefficients,
522
+ 'odds_ratio': np.exp(coefficients)
523
+ }).sort_values('odds_ratio', ascending=True)
524
+
525
+ fig_coef = px.bar(coef_df, x='odds_ratio', y='feature',
526
+ orientation='h', title='Importância das Variáveis - Odds Ratios')
527
+ fig_coef.add_vline(x=1, line_dash="dash", line_color="red", annotation_text="Linha Neutra")
528
+ st.plotly_chart(fig_coef)
529
+
530
+ # Análise gerencial
531
+ st.subheader("🎯 INTERPRETAÇÃO GERENCIAL")
532
+
533
+ st.write("**Variáveis que AUMENTAM a probabilidade de reclamação (Odds Ratio > 1):**")
534
+ positive_impact = coef_df[coef_df['odds_ratio'] > 1].sort_values('odds_ratio', ascending=False)
535
+ for _, row in positive_impact.head(3).iterrows():
536
+ increase = (row['odds_ratio'] - 1) * 100
537
+ st.write(f"• **{row['feature']}**: {row['odds_ratio']:.2f} (aumento de {increase:.1f}%)")
538
+
539
+ st.write("**Variáveis que DIMINUEM a probabilidade de reclamação (Odds Ratio < 1):**")
540
+ negative_impact = coef_df[coef_df['odds_ratio'] < 1].sort_values('odds_ratio', ascending=True)
541
+ for _, row in negative_impact.head(3).iterrows():
542
+ decrease = (1 - row['odds_ratio']) * 100
543
+ st.write(f"• **{row['feature']}**: {row['odds_ratio']:.2f} (redução de {decrease:.1f}%)")
544
+
545
+ # =============================================================================
546
+ # MODELO STATSMODELS (ESTILO PROFESSOR)
547
+ # =============================================================================
548
+
549
+ st.header("📊 MODELO STATSMODELS - REGRESSÃO LOGÍSTICA")
550
+
551
+ if 'Logistic Regression' in [name for name in models.keys() if 'Logistic' in name]:
552
+ X_train_sm = sm.add_constant(X_train_final)
553
+ logit_model = sm.Logit(y_train_bal, X_train_sm)
554
+ result = logit_model.fit(disp=0)
555
+
556
+ st.subheader("Resumo Estatístico")
557
+ st.text(result.summary().as_text())
558
+
559
+ # Tabela de coeficientes
560
+ st.subheader("Coeficientes e Odds Ratios")
561
+ coefficients_df = pd.DataFrame({
562
+ 'Variável': X_train_sm.columns,
563
+ 'Coeficiente': result.params,
564
+ 'Odds Ratio': np.exp(result.params),
565
+ 'P-valor': result.pvalues
566
+ }).round(4)
567
+
568
+ st.dataframe(coefficients_df[coefficients_df['Variável'] != 'const']
569
+ .sort_values('Odds Ratio', ascending=False)
570
+ .style.format({'Coeficiente': '{:.4f}', 'Odds Ratio': '{:.4f}', 'P-valor': '{:.4f}'}))
571
+
572
+ # =============================================================================
573
+ # VISUALIZAÇÃO E INTERPRETAÇÃO (ESTILO PROFESSOR)
574
+ # =============================================================================
575
+
576
+ st.header("📈 VISUALIZAÇÃO E INTERPRETAÇÃO")
577
+
578
+ # Gráfico de importância estilo professor
579
+ if 'coefficients_df' in locals():
580
+ coefficients_plot = coefficients_df[coefficients_df['Variável'] != 'const'].sort_values('Odds Ratio', ascending=True)
581
+
582
+ fig, ax = plt.subplots(figsize=(10, 8))
583
+ y_pos = np.arange(len(coefficients_plot))
584
+ ax.barh(y_pos, coefficients_plot['Odds Ratio'], color=['#1f77b4' if x < 1 else '#ff7f0e' for x in coefficients_plot['Odds Ratio']])
585
+ ax.set_yticks(y_pos)
586
+ ax.set_yticklabels(coefficients_plot['Variável'])
587
+ ax.set_xlabel('Odds Ratio')
588
+ ax.set_title('Importância das Variáveis - Odds Ratios')
589
+ ax.axvline(x=1, color='red', linestyle='--', alpha=0.7, label='Linha Neutra')
590
+ ax.legend()
591
+
592
+ # Adicionar valores nas barras
593
+ for i, v in enumerate(coefficients_plot['Odds Ratio']):
594
+ ax.text(v + 0.01, i, f'{v:.2f}', va='center', fontweight='bold')
595
+
596
+ st.pyplot(fig)
597
+
598
+ # =============================================================================
599
+ # RECOMENDAÇÕES ESTRATÉGICAS
600
+ # =============================================================================
601
+
602
+ st.header("🚀 RECOMENDAÇÕES ESTRATÉGICAS")
603
+
604
+ st.markdown("""
605
+ **Baseado na análise do melhor modelo, recomenda-se:**
606
+
607
+ 1. **Segmentação de Clientes:** Identificar perfis com maior propensão a reclamações
608
+ 2. **Ações Proativas:** Contatar clientes de alto risco antes que reclamem
609
+ 3. **Otimização de Recursos:** Alocar mais recursos de suporte para segmentos problemáticos
610
+ 4. **Melhoria Contínua:** Monitorar continuamente os fatores que influenciam reclamações
611
+
612
+ **Exemplo de insight acionável:**
613
+ *"Clientes com maior gasto em vinhos, presença de filhos e maior tempo desde a última compra tendem a apresentar maior propensão a reclamações."*
614
+ """)
615
+
616
+ # =============================================================================
617
+ # EXPORTAÇÃO DE RESULTADOS
618
+ # =============================================================================
619
+
620
+ st.header("💾 EXPORTAÇÃO DE RESULTADOS")
621
+
622
+ # Criar relatório final
623
+ relatorio_final = pd.DataFrame({
624
+ 'Modelo': results_df['Modelo'],
625
+ 'AUC': results_df['AUC'],
626
+ 'Acurácia': results_df['Acurácia'],
627
+ 'Precisão': results_df['Precisão'],
628
+ 'Recall': results_df['Recall'],
629
+ 'F1-Score': results_df['F1-Score']
630
+ })
631
+
632
+ csv = relatorio_final.to_csv(index=False)
633
+ st.download_button(
634
+ label="📥 Baixar Resultados em CSV",
635
+ data=csv,
636
+ file_name="resultados_tarefa4_reclamacoes.csv",
637
+ mime="text/csv"
638
+ )
639
+
640
+ # Footer
641
+ st.markdown("---")
642
+ st.markdown(
643
+ "<p style='text-align: center; color: gray;'>Tarefa 4 - Previsão de Reclamações com Modelos Supervisionados | "
644
+ "Aplicação contempla todos os requisitos: Balanceamento (SMOTE), RFE, Múltiplos Modelos, Análise de Hiperparâmetros, Interpretação Gerencial</p>",
645
+ unsafe_allow_html=True
646
+ )