enzograndino commited on
Commit
2476bc2
·
verified ·
1 Parent(s): c4cf493

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -82
app.py CHANGED
@@ -35,7 +35,7 @@ import lightgbm as lgb
35
  from sklearn.metrics import (roc_auc_score, precision_score, recall_score,
36
  f1_score, confusion_matrix, roc_curve)
37
 
38
- # --- 0. Configuração da Página e Estado de Sessão ---
39
 
40
  st.set_page_config(
41
  page_title="Analytics de Reclamações | Tarefa 04",
@@ -62,7 +62,7 @@ if 'pipeline_results' not in st.session_state:
62
  if 'model_results' not in st.session_state:
63
  st.session_state['model_results'] = None
64
 
65
- # --- 1. Funções de Carga e Engenharia (Cache) ---
66
 
67
  @st.cache_data
68
  def load_and_engineer_data(file_path):
@@ -95,9 +95,9 @@ def load_and_engineer_data(file_path):
95
 
96
  return df
97
 
98
- # --- 2. Carregamento Automático (Obrigatório) ---
99
 
100
- st.sidebar.title("Painel de Controle 🔬")
101
  st.sidebar.header("1. Status dos Dados")
102
 
103
  local_file = 'marketing_campaign.csv'
@@ -120,7 +120,7 @@ if st.session_state['df'] is None:
120
  else:
121
  st.sidebar.success("Dados carregados e prontos.")
122
 
123
- # --- 2.2. Filtros Dinâmicos (Sidebar) ---
124
  if st.session_state['df'] is not None:
125
  df = st.session_state['df']
126
  st.sidebar.header("2. Filtros Dinâmicos")
@@ -147,13 +147,13 @@ if st.session_state['df'] is not None:
147
  st.session_state['df_filtered'] = df_f
148
  st.sidebar.info("Filtros aplicados!")
149
 
150
- # --- 2.3. Pré-processamento (Sidebar) ---
151
  st.sidebar.header("3. Pipeline de Pré-processamento")
152
  scaler_choice = st.sidebar.radio("Scaler (Numérico)", ["StandardScaler", "MinMaxScaler"], key='scaler')
153
  use_smote = st.sidebar.toggle("Aplicar SMOTE (Balanceamento)", value=True, key='smote')
154
  fs_choice = st.sidebar.radio("Seleção de Variáveis", ["RFE", "Por Importância (RandomForest)", "Manual"], key='fs')
155
 
156
- # --- 2.4. Modelagem (Sidebar) ---
157
  st.sidebar.header("4. Modelagem e Hiperparâmetros")
158
 
159
  with st.sidebar.expander("Selecionar Modelos"):
@@ -174,7 +174,7 @@ with st.sidebar.expander("Ajuste Fino (Sensibilidade)"):
174
  k_neighbors = st.slider("KNN: K-Neighbors", 1, 21, 5, step=2)
175
  rf_estimators = st.slider("Random Forest: N-Estimators", 50, 500, 100, step=50)
176
 
177
- # --- 3. Layout Principal (Abas) ---
178
 
179
  st.title("Plataforma de Analytics - Previsão de Reclamações (Tarefa 04)")
180
 
@@ -187,11 +187,11 @@ df_display = st.session_state['df_filtered']
187
 
188
  # Define as abas do dashboard
189
  tab1, tab2, tab3, tab4, tab5 = st.tabs([
190
- "📥 1. Visão Geral dos Dados",
191
- "🛠️ 2. Pipeline Interativo",
192
- "🤖 3. Modelagem e Avaliação",
193
- "🧠 4. Explicabilidade (XAI)",
194
- "📄 5. Relatório Gerencial"
195
  ])
196
 
197
  # --- Aba 1: Carga e Análise Exploratória (EDA) ---
@@ -214,7 +214,7 @@ with tab1:
214
  st.metric("Taxa de Reclamação", f"{perc_reclamacao:.2f}%")
215
 
216
  # 1.2. Expander para Detalhes Técnicos
217
- with st.expander("🔍 Ver Dicionário de Dados e Detalhes Técnicos (Tipos e Nulos)"):
218
  col_tech1, col_tech2 = st.columns(2)
219
  with col_tech1:
220
  st.write("**Contagem de Nulos:**")
@@ -228,13 +228,13 @@ with tab1:
228
 
229
  st.markdown("---")
230
 
231
- # 1.3. Análise Visual Refinada (Atualizado)
232
  st.subheader("1.2. Análise Visual de Variáveis")
233
  st.caption("Entenda como as variáveis se comportam em relação às reclamações.")
234
 
235
  col_eda1, col_eda2 = st.columns(2)
236
  with col_eda1:
237
- st.markdown("#### 🎯 Variável Alvo (Target)")
238
 
239
  # Preparação dos dados para a pizza
240
  df_pie = df_display.copy()
@@ -246,10 +246,10 @@ with tab1:
246
  fig_pie.update_layout(margin=dict(t=0, b=0, l=0, r=0), height=300)
247
  st.plotly_chart(fig_pie, use_container_width=True)
248
 
249
- st.info("ℹ️ **Complain:** **1** = Cliente reclamou nos últimos 2 anos | **0** = Não reclamou.")
250
 
251
  with col_eda2:
252
- st.markdown("#### 🔎 Inspetor de Variáveis")
253
 
254
  # Seletor de Variável Dinâmico
255
  cols_to_exclude = ['Complain', 'Legenda']
@@ -291,11 +291,11 @@ with tab1:
291
  """, unsafe_allow_html=True)
292
 
293
  # Preview de Dados
294
- with st.expander("📋 Visualizar Tabela de Dados Brutos (Amostra)"):
295
  st.dataframe(df_display.head(10), use_container_width=True)
296
 
297
  # Correlação em Expander
298
- with st.expander("📊 Ver Matriz de Correlação e Pairplot"):
299
  st.markdown("### 1. Mapa de Calor (Correlação)")
300
  st.info("""
301
  **O que é isso?** Mostra o quanto duas variáveis numéricas "andam juntas".
@@ -325,7 +325,7 @@ with tab1:
325
 
326
  # Adicionando a conclusão sobre o baixo número de cancelamentos
327
  st.warning("""
328
- ⚠️ **Observação Importante:**
329
  Devido ao baixo número de reclamações (classe desbalanceada), pode ser difícil visualizar padrões claros nos gráficos de pares.
330
  A maioria dos pontos será azul (Não Reclamou), o que pode "esconder" os pontos vermelhos (Reclamou).
331
  Isso reforça a necessidade de usar técnicas como o **SMOTE** (balanceamento) na etapa de modelagem para que o algoritmo consiga aprender com esses poucos casos.
@@ -338,15 +338,15 @@ with tab1:
338
  fig_pair = sns.pairplot(df_sample[vars_present], hue='Complain' if 'Complain' in vars_present else None, diag_kind='kde')
339
  st.pyplot(fig_pair)
340
 
341
- # --- Aba 2: Pipeline Interativo ---
342
  with tab2:
343
  st.header("2. Pipeline de Pré-processamento Interativo")
344
  st.write("Configure o pipeline na barra lateral e clique em 'Executar' para processar os dados.")
345
 
346
  # --- Texto Explicativo sobre SMOTE ---
347
  st.markdown("""
348
- ### ⚖️ O que é SMOTE e por que usar?
349
- Como vimos na aba anterior, o dataset possui **menos de 1% de clientes que reclamaram**. Se treinarmos o modelo assim, ele ficará "preguiçoso" e dirá que ninguém vai reclamar, acertando 99% das vezes (Acurácia enganosa).
350
 
351
  **A Solução (SMOTE):**
352
  O *Synthetic Minority Over-sampling Technique* cria "clientes sintéticos" parecidos com os que reclamaram, equilibrando o jogo para que o modelo consiga aprender o padrão da reclamação.
@@ -458,12 +458,12 @@ with tab2:
458
  st.write("**Shape (Treino/Teste):**")
459
  st.code(f"Treino: {res['X_train_final'].shape}\nTeste: {res['X_test_final'].shape}")
460
 
461
- # --- Aba 3: Modelagem e Avaliação ---
462
  with tab3:
463
  st.header("3. Treinamento e Avaliação Comparativa de Modelos")
464
 
465
  if not st.session_state['pipeline_results']:
466
- st.warning("⚠️ Por favor, execute o 'Pipeline de Processamento' na Aba 2 primeiro.")
467
  st.stop()
468
 
469
  st.markdown("""
@@ -471,7 +471,7 @@ with tab3:
471
  **Objetivo:** Encontrar o equilíbrio entre não deixar passar nenhuma reclamação (Recall) e não gastar recursos com quem não ia reclamar (Precisão).
472
  """)
473
 
474
- if st.button("🚀 Treinar Modelos Selecionados", key='run_models'):
475
  # Carregar dados do pipeline
476
  pipe_res = st.session_state['pipeline_results']
477
  X_train = pipe_res['X_train_final']
@@ -558,7 +558,7 @@ with tab3:
558
  st.divider()
559
 
560
  # 6. Seleção Automática do Melhor Modelo
561
- st.subheader("🏆 Melhor Modelo Recomendado")
562
 
563
  if not df_metrics.empty:
564
  # Critério: F1-Score (Média harmônica) é o mais seguro para desbalanceados
@@ -583,51 +583,53 @@ with tab3:
583
  st.markdown("---")
584
 
585
  # 5. Tabela Comparativa
586
- st.subheader("📊 Comparativo de Performance")
587
  st.markdown("""
588
  - **Recall (Sensibilidade):** De 100 clientes que reclamaram, quantos o modelo acertou? (Vital para não perder clientes).
589
- - **Precision (Precisão):** Quando o modelo diz "vai reclamar", ele acerta quanto? (Vital para não gastar dinheiro à toa).
590
  """)
591
  st.dataframe(df_metrics.style.highlight_max(axis=0, color='lightgreen')
592
  .highlight_min(subset=['Tempo (s)'], color='lightgreen')
593
  .format("{:.4f}"))
594
 
595
- # --- GRAFICO 1: ROC (Vertical) ---
596
- st.subheader("Curvas ROC")
597
- fig_roc = go.Figure()
598
- for name, (fpr, tpr, auc) in res['rocs'].items():
599
- fig_roc.add_trace(go.Scatter(x=fpr, y=tpr, name=f"{name} (AUC={auc:.3f})"))
600
- fig_roc.add_shape(type='line', line=dict(dash='dash'), x0=0, x1=1, y0=0, y1=1)
601
- fig_roc.update_layout(title="Capacidade de Distinção (ROC)", xaxis_title="Falsos Positivos", yaxis_title="Verdadeiros Positivos", height=400)
602
- st.plotly_chart(fig_roc, use_container_width=True)
603
- st.info("💡 **Interpretação:** Quanto mais a curva estiver no canto **superior esquerdo**, melhor o modelo consegue separar quem reclama de quem não reclama.")
604
-
605
- st.divider() # Espaço entre os gráficos
606
-
607
- # --- GRAFICO 2: Matriz de Confusão (Vertical) ---
608
- st.subheader("Matriz de Confusão (Erros vs Acertos)")
609
- if res['trained_models']:
610
- model_to_show_cm = st.selectbox("Selecione um modelo para auditar:", res['trained_models'].keys())
611
-
612
- metric_idx = [m['Modelo'] for m in res['metrics']].index(model_to_show_cm)
613
- cm_data = res['metrics'][metric_idx]['CM']
614
-
615
- fig_cm = px.imshow(cm_data, text_auto=True,
616
- labels=dict(x="O que o Modelo Previu", y="O que Realmente Aconteceu"),
617
- x=['Não Reclamou', 'Reclamou'],
618
- y=['Não Reclamou', 'Reclamou'],
619
- color_continuous_scale='Blues')
620
- fig_cm.update_layout(title=f"Auditoria de Erros - {model_to_show_cm}", height=400)
621
- st.plotly_chart(fig_cm, use_container_width=True)
622
-
623
- st.markdown("""
624
- <small>
625
- <b>Como ler este gráfico de negócio:</b><br>
626
- ↘️ <b>Acertos (Diagonal):</b> Clientes classificados corretamente.<br>
627
- ↗️ <b>Falso Positivo (Alarme Falso):</b> Modelo disse que ia reclamar, mas cliente estava feliz. (Custo de contato desnecessário).<br>
628
- ️ <b>Falso Negativo (Perigo!):</b> Modelo disse que estava tudo bem, mas <b>o cliente reclamou</b>. (Risco de perder o cliente).
629
- </small>
630
- """, unsafe_allow_html=True)
 
 
631
 
632
  # --- Aba 4: Explicabilidade (XAI) ---
633
  with tab4:
@@ -655,7 +657,7 @@ with tab4:
655
  st.session_state['top_features'] = importances.index.tolist()
656
 
657
  st.markdown("""
658
- 📊 **Como ler este gráfico (Feature Importance):**
659
  Este gráfico mostra quais variáveis foram mais "usadas" pelo modelo para tomar decisões.
660
  * **Barras maiores:** Significam que a variável é crítica para separar quem reclama de quem não reclama.
661
  * **Barras menores:** A variável tem pouca influência na decisão final.
@@ -674,7 +676,7 @@ with tab4:
674
  st.session_state['top_features'] = coefs.abs().nlargest(5).index.tolist()
675
 
676
  st.markdown("""
677
- ⚖️ **Como ler este gráfico (Coeficientes):**
678
  * **Barras para a Direita (Positivas):** Aumentam a chance de Reclamação.
679
  * **Barras para a Esquerda (Negativas):** Diminuem a chance de Reclamação (protegem contra o churn).
680
  * **Tamanho:** Indica a força do impacto.
@@ -692,7 +694,7 @@ with tab4:
692
  st.session_state['top_features'] = perm_imp.nlargest(5).index.tolist()
693
 
694
  st.markdown("""
695
- 🔄 **Como ler este gráfico (Permutation Importance):**
696
  Como o modelo não diz quais variáveis ele usa, fizemos um "teste de estresse": embaralhamos os dados de uma coluna por vez.
697
  * Se a barra for **grande**, significa que o modelo errou muito quando bagunçamos essa variável -> Ela é **Importante**.
698
  * Se a barra for **pequena**, o modelo nem ligou -> Ela é **Irrelevante**.
@@ -717,8 +719,6 @@ with tab4:
717
  explainer = shap.TreeExplainer(model)
718
  shap_values = explainer.shap_values(X_test_sample)
719
  elif isinstance(model, (LogisticRegression, SVC)):
720
- # LinearExplainer para modelos lineares ou Kernel para outros
721
- # Para simplificar e garantir compatibilidade no dashboard:
722
  explainer = shap.KernelExplainer(model.predict_proba, X_train_sample)
723
  shap_values = explainer.shap_values(X_test_sample)
724
  else:
@@ -726,34 +726,48 @@ with tab4:
726
  explainer = shap.KernelExplainer(model.predict_proba, X_train_sample)
727
  shap_values = explainer.shap_values(X_test_sample)
728
 
729
- # Tratamento para saída do SHAP (alguns retornam lista para binário, outros array)
730
  if isinstance(shap_values, list):
731
- # Pega a classe 1 (Reclamação)
732
  shap_vals_target = shap_values[1]
733
  else:
734
- # Se tiver 3 dimensoes (amostra, features, classes), pega a classe 1
735
  if len(shap_values.shape) == 3:
736
  shap_vals_target = shap_values[:, :, 1]
737
  else:
738
  shap_vals_target = shap_values
739
 
740
- # Plotagem segura com Matplotlib no Streamlit
741
- st.write("**Summary Plot**")
742
  fig_shap, ax = plt.subplots()
743
  shap.summary_plot(shap_vals_target, X_test_sample, plot_type="bar", show=False)
744
  st.pyplot(fig_shap)
745
- plt.close(fig_shap) # Limpa memória
 
 
 
 
 
746
 
747
- st.write("**Beeswarm Plot**")
 
748
  fig_beeswarm, ax = plt.subplots()
749
  shap.summary_plot(shap_vals_target, X_test_sample, show=False)
750
  st.pyplot(fig_beeswarm)
751
  plt.close(fig_beeswarm)
 
 
 
 
 
 
 
 
 
 
752
 
753
  except Exception as e:
754
  st.error(f"Erro ao gerar SHAP: {e}. Tente usar modelos baseados em árvore para melhor compatibilidade.")
755
 
756
- # --- Aba 5: Relatório Gerencial ---
757
  with tab5:
758
  st.header("8. Recomendação Gerencial Automática")
759
 
@@ -844,9 +858,7 @@ with tab5:
844
  A implementação das ações acima foca na **antecipação**. Sabendo quem vai reclamar (alta probabilidade)
845
  e por quê (fatores críticos), a empresa deixa de ser reativa (apagar incêndios no SAC) e passa a ser
846
  proativa (resolver a dor antes que ela vire uma reclamação).
847
-
848
- Recomendamos monitorar a taxa de reclamação do grupo de controle vs. grupo impactado por estas ações
849
- nos próximos 30 dias para mensurar o ROI do projeto.
850
  ================================================================================
851
  """
852
 
 
35
  from sklearn.metrics import (roc_auc_score, precision_score, recall_score,
36
  f1_score, confusion_matrix, roc_curve)
37
 
38
+ # 0. Configuração da Página e Estado de Sessão
39
 
40
  st.set_page_config(
41
  page_title="Analytics de Reclamações | Tarefa 04",
 
62
  if 'model_results' not in st.session_state:
63
  st.session_state['model_results'] = None
64
 
65
+ # 1. Funções de Carga e Engenharia (Cache)
66
 
67
  @st.cache_data
68
  def load_and_engineer_data(file_path):
 
95
 
96
  return df
97
 
98
+ # 2. Carregamento Automático (Obrigatório)
99
 
100
+ st.sidebar.title("Painel de Controle ")
101
  st.sidebar.header("1. Status dos Dados")
102
 
103
  local_file = 'marketing_campaign.csv'
 
120
  else:
121
  st.sidebar.success("Dados carregados e prontos.")
122
 
123
+ # 2.2. Filtros Dinâmicos (Sidebar)
124
  if st.session_state['df'] is not None:
125
  df = st.session_state['df']
126
  st.sidebar.header("2. Filtros Dinâmicos")
 
147
  st.session_state['df_filtered'] = df_f
148
  st.sidebar.info("Filtros aplicados!")
149
 
150
+ # 2.3. Pré-processamento (Sidebar)
151
  st.sidebar.header("3. Pipeline de Pré-processamento")
152
  scaler_choice = st.sidebar.radio("Scaler (Numérico)", ["StandardScaler", "MinMaxScaler"], key='scaler')
153
  use_smote = st.sidebar.toggle("Aplicar SMOTE (Balanceamento)", value=True, key='smote')
154
  fs_choice = st.sidebar.radio("Seleção de Variáveis", ["RFE", "Por Importância (RandomForest)", "Manual"], key='fs')
155
 
156
+ # 2.4. Modelagem (Sidebar)
157
  st.sidebar.header("4. Modelagem e Hiperparâmetros")
158
 
159
  with st.sidebar.expander("Selecionar Modelos"):
 
174
  k_neighbors = st.slider("KNN: K-Neighbors", 1, 21, 5, step=2)
175
  rf_estimators = st.slider("Random Forest: N-Estimators", 50, 500, 100, step=50)
176
 
177
+ # 3. Layout Principal (Abas)
178
 
179
  st.title("Plataforma de Analytics - Previsão de Reclamações (Tarefa 04)")
180
 
 
187
 
188
  # Define as abas do dashboard
189
  tab1, tab2, tab3, tab4, tab5 = st.tabs([
190
+ " 1. Visão Geral dos Dados",
191
+ " 2. Pipeline Interativo",
192
+ " 3. Modelagem e Avaliação",
193
+ " 4. Explicabilidade (XAI)",
194
+ " 5. Relatório Gerencial"
195
  ])
196
 
197
  # --- Aba 1: Carga e Análise Exploratória (EDA) ---
 
214
  st.metric("Taxa de Reclamação", f"{perc_reclamacao:.2f}%")
215
 
216
  # 1.2. Expander para Detalhes Técnicos
217
+ with st.expander(" Ver Exemplo de Dicionário de Dados e Detalhes Técnicos (Tipos e Nulos)"):
218
  col_tech1, col_tech2 = st.columns(2)
219
  with col_tech1:
220
  st.write("**Contagem de Nulos:**")
 
228
 
229
  st.markdown("---")
230
 
231
+ # 1.3. Análise Visual Refinada
232
  st.subheader("1.2. Análise Visual de Variáveis")
233
  st.caption("Entenda como as variáveis se comportam em relação às reclamações.")
234
 
235
  col_eda1, col_eda2 = st.columns(2)
236
  with col_eda1:
237
+ st.markdown("#### Variável Alvo (Target)")
238
 
239
  # Preparação dos dados para a pizza
240
  df_pie = df_display.copy()
 
246
  fig_pie.update_layout(margin=dict(t=0, b=0, l=0, r=0), height=300)
247
  st.plotly_chart(fig_pie, use_container_width=True)
248
 
249
+ st.info(" **Complain:** **1** = Cliente reclamou nos últimos 2 anos | **0** = Não reclamou.")
250
 
251
  with col_eda2:
252
+ st.markdown("#### Inspetor de Variáveis")
253
 
254
  # Seletor de Variável Dinâmico
255
  cols_to_exclude = ['Complain', 'Legenda']
 
291
  """, unsafe_allow_html=True)
292
 
293
  # Preview de Dados
294
+ with st.expander(" Visualizar Tabela de Dados Brutos (Amostra)"):
295
  st.dataframe(df_display.head(10), use_container_width=True)
296
 
297
  # Correlação em Expander
298
+ with st.expander(" Ver Matriz de Correlação e Pairplot"):
299
  st.markdown("### 1. Mapa de Calor (Correlação)")
300
  st.info("""
301
  **O que é isso?** Mostra o quanto duas variáveis numéricas "andam juntas".
 
325
 
326
  # Adicionando a conclusão sobre o baixo número de cancelamentos
327
  st.warning("""
328
+ **Observação Importante:**
329
  Devido ao baixo número de reclamações (classe desbalanceada), pode ser difícil visualizar padrões claros nos gráficos de pares.
330
  A maioria dos pontos será azul (Não Reclamou), o que pode "esconder" os pontos vermelhos (Reclamou).
331
  Isso reforça a necessidade de usar técnicas como o **SMOTE** (balanceamento) na etapa de modelagem para que o algoritmo consiga aprender com esses poucos casos.
 
338
  fig_pair = sns.pairplot(df_sample[vars_present], hue='Complain' if 'Complain' in vars_present else None, diag_kind='kde')
339
  st.pyplot(fig_pair)
340
 
341
+ # Aba 2: Pipeline Interativo
342
  with tab2:
343
  st.header("2. Pipeline de Pré-processamento Interativo")
344
  st.write("Configure o pipeline na barra lateral e clique em 'Executar' para processar os dados.")
345
 
346
  # --- Texto Explicativo sobre SMOTE ---
347
  st.markdown("""
348
+ ### O que é SMOTE e por que usar?
349
+ Como vimos na aba anterior, o dataset possui **menos de 1% de clientes que reclamaram**. Se treinarmos o modelo assim, ele ficará "impreciso" e dirá que ninguém vai reclamar, acertando 99% das vezes (Acurácia enganosa).
350
 
351
  **A Solução (SMOTE):**
352
  O *Synthetic Minority Over-sampling Technique* cria "clientes sintéticos" parecidos com os que reclamaram, equilibrando o jogo para que o modelo consiga aprender o padrão da reclamação.
 
458
  st.write("**Shape (Treino/Teste):**")
459
  st.code(f"Treino: {res['X_train_final'].shape}\nTeste: {res['X_test_final'].shape}")
460
 
461
+ # Aba 3: Modelagem e Avaliação
462
  with tab3:
463
  st.header("3. Treinamento e Avaliação Comparativa de Modelos")
464
 
465
  if not st.session_state['pipeline_results']:
466
+ st.warning(" Por favor, execute o 'Pipeline de Processamento' na Aba 2 primeiro.")
467
  st.stop()
468
 
469
  st.markdown("""
 
471
  **Objetivo:** Encontrar o equilíbrio entre não deixar passar nenhuma reclamação (Recall) e não gastar recursos com quem não ia reclamar (Precisão).
472
  """)
473
 
474
+ if st.button(" Treinar Modelos Selecionados", key='run_models'):
475
  # Carregar dados do pipeline
476
  pipe_res = st.session_state['pipeline_results']
477
  X_train = pipe_res['X_train_final']
 
558
  st.divider()
559
 
560
  # 6. Seleção Automática do Melhor Modelo
561
+ st.subheader(" Melhor Modelo Recomendado")
562
 
563
  if not df_metrics.empty:
564
  # Critério: F1-Score (Média harmônica) é o mais seguro para desbalanceados
 
583
  st.markdown("---")
584
 
585
  # 5. Tabela Comparativa
586
+ st.subheader(" Comparativo de Performance")
587
  st.markdown("""
588
  - **Recall (Sensibilidade):** De 100 clientes que reclamaram, quantos o modelo acertou? (Vital para não perder clientes).
589
+ - **Precision (Precisão):** Quando o modelo diz "vai reclamar", ele acerta quanto?.
590
  """)
591
  st.dataframe(df_metrics.style.highlight_max(axis=0, color='lightgreen')
592
  .highlight_min(subset=['Tempo (s)'], color='lightgreen')
593
  .format("{:.4f}"))
594
 
595
+ col_charts1, col_charts2 = st.columns(2)
596
+
597
+ # Curvas ROC
598
+ with col_charts1:
599
+ st.subheader("Curvas ROC")
600
+ fig_roc = go.Figure()
601
+ for name, (fpr, tpr, auc) in res['rocs'].items():
602
+ fig_roc.add_trace(go.Scatter(x=fpr, y=tpr, name=f"{name} (AUC={auc:.3f})"))
603
+ fig_roc.add_shape(type='line', line=dict(dash='dash'), x0=0, x1=1, y0=0, y1=1)
604
+ fig_roc.update_layout(title="Capacidade de Distinção (ROC)", xaxis_title="Falsos Positivos", yaxis_title="Verdadeiros Positivos", height=400)
605
+ st.plotly_chart(fig_roc, use_container_width=True)
606
+ st.info(" **Interpretação:** Quanto mais a curva estiver no canto **superior esquerdo**, melhor o modelo consegue separar quem reclama de quem não reclama.")
607
+
608
+ # Matriz de Confusão
609
+ with col_charts2:
610
+ st.subheader("Matriz de Confusão (Erros vs Acertos)")
611
+ if res['trained_models']:
612
+ model_to_show_cm = st.selectbox("Selecione um modelo para auditar:", res['trained_models'].keys())
613
+
614
+ metric_idx = [m['Modelo'] for m in res['metrics']].index(model_to_show_cm)
615
+ cm_data = res['metrics'][metric_idx]['CM']
616
+
617
+ fig_cm = px.imshow(cm_data, text_auto=True,
618
+ labels=dict(x="O que o Modelo Previu", y="O que Realmente Aconteceu"),
619
+ x=['Não Reclamou', 'Reclamou'],
620
+ y=['Não Reclamou', 'Reclamou'],
621
+ color_continuous_scale='Blues')
622
+ fig_cm.update_layout(title=f"Auditoria de Erros - {model_to_show_cm}", height=400)
623
+ st.plotly_chart(fig_cm, use_container_width=True)
624
+
625
+ st.markdown("""
626
+ <small>
627
+ <b>Como ler este gráfico de negócio:</b><br>
628
+ ️ <b>Acertos (Diagonal):</b> Clientes classificados corretamente.<br>
629
+ ↗️ <b>Falso Positivo (Alarme Falso):</b> Modelo disse que ia reclamar, mas cliente estava feliz. (Custo de contato desnecessário).<br>
630
+ ↙️ <b>Falso Negativo (Perigo!):</b> Modelo disse que estava tudo bem, mas <b>o cliente reclamou</b>. (Risco de perder o cliente).
631
+ </small>
632
+ """, unsafe_allow_html=True)
633
 
634
  # --- Aba 4: Explicabilidade (XAI) ---
635
  with tab4:
 
657
  st.session_state['top_features'] = importances.index.tolist()
658
 
659
  st.markdown("""
660
+ **Como ler este gráfico (Feature Importance):**
661
  Este gráfico mostra quais variáveis foram mais "usadas" pelo modelo para tomar decisões.
662
  * **Barras maiores:** Significam que a variável é crítica para separar quem reclama de quem não reclama.
663
  * **Barras menores:** A variável tem pouca influência na decisão final.
 
676
  st.session_state['top_features'] = coefs.abs().nlargest(5).index.tolist()
677
 
678
  st.markdown("""
679
+ **Como ler este gráfico (Coeficientes):**
680
  * **Barras para a Direita (Positivas):** Aumentam a chance de Reclamação.
681
  * **Barras para a Esquerda (Negativas):** Diminuem a chance de Reclamação (protegem contra o churn).
682
  * **Tamanho:** Indica a força do impacto.
 
694
  st.session_state['top_features'] = perm_imp.nlargest(5).index.tolist()
695
 
696
  st.markdown("""
697
+ **Como ler este gráfico (Permutation Importance):**
698
  Como o modelo não diz quais variáveis ele usa, fizemos um "teste de estresse": embaralhamos os dados de uma coluna por vez.
699
  * Se a barra for **grande**, significa que o modelo errou muito quando bagunçamos essa variável -> Ela é **Importante**.
700
  * Se a barra for **pequena**, o modelo nem ligou -> Ela é **Irrelevante**.
 
719
  explainer = shap.TreeExplainer(model)
720
  shap_values = explainer.shap_values(X_test_sample)
721
  elif isinstance(model, (LogisticRegression, SVC)):
 
 
722
  explainer = shap.KernelExplainer(model.predict_proba, X_train_sample)
723
  shap_values = explainer.shap_values(X_test_sample)
724
  else:
 
726
  explainer = shap.KernelExplainer(model.predict_proba, X_train_sample)
727
  shap_values = explainer.shap_values(X_test_sample)
728
 
729
+ # Tratamento para saída do SHAP
730
  if isinstance(shap_values, list):
 
731
  shap_vals_target = shap_values[1]
732
  else:
 
733
  if len(shap_values.shape) == 3:
734
  shap_vals_target = shap_values[:, :, 1]
735
  else:
736
  shap_vals_target = shap_values
737
 
738
+ # Plotagem 1: Summary Plot (Barra)
739
+ st.write("**1. Impacto Médio Absoluto (Summary Plot)**")
740
  fig_shap, ax = plt.subplots()
741
  shap.summary_plot(shap_vals_target, X_test_sample, plot_type="bar", show=False)
742
  st.pyplot(fig_shap)
743
+ plt.close(fig_shap)
744
+
745
+ st.info("""
746
+ **Interpretação:** Este gráfico mostra a **força média** de cada variável.
747
+ Variáveis no topo são as mais poderosas para mudar a opinião do modelo, não importa se aumentam ou diminuem o risco.
748
+ """)
749
 
750
+ # Plotagem 2: Beeswarm
751
+ st.write("**2. Detalhamento do Impacto (Beeswarm Plot)**")
752
  fig_beeswarm, ax = plt.subplots()
753
  shap.summary_plot(shap_vals_target, X_test_sample, show=False)
754
  st.pyplot(fig_beeswarm)
755
  plt.close(fig_beeswarm)
756
+
757
+ st.info("""
758
+ **Como ler o Beeswarm (O gráfico mais rico do SHAP):**
759
+ * Cada **ponto** é um cliente.
760
+ * **Eixo X (Impacto):** Quanto mais para a **direita**, maior o risco de reclamação. Quanto mais para a **esquerda**, menor o risco.
761
+ * **Cor (Valor da Variável):** * 🔴 **Vermelho:** Valor Alto da variável (ex: Renda alta).
762
+ * 🔵 **Azul:** Valor Baixo da variável (ex: Renda baixa).
763
+
764
+ **Exemplo de Leitura:** Se os pontos vermelhos de 'MntWines' estão todos na direita, significa que **gastar muito com vinho aumenta o risco de reclamação**.
765
+ """)
766
 
767
  except Exception as e:
768
  st.error(f"Erro ao gerar SHAP: {e}. Tente usar modelos baseados em árvore para melhor compatibilidade.")
769
 
770
+ # Aba 5: Relatório Gerencial
771
  with tab5:
772
  st.header("8. Recomendação Gerencial Automática")
773
 
 
858
  A implementação das ações acima foca na **antecipação**. Sabendo quem vai reclamar (alta probabilidade)
859
  e por quê (fatores críticos), a empresa deixa de ser reativa (apagar incêndios no SAC) e passa a ser
860
  proativa (resolver a dor antes que ela vire uma reclamação).
861
+
 
 
862
  ================================================================================
863
  """
864