vsalgs commited on
Commit
4e7bf4a
·
verified ·
1 Parent(s): c749430

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +174 -59
src/streamlit_app.py CHANGED
@@ -18,16 +18,13 @@ import numpy as np
18
  import io
19
  from sklearn.feature_selection import RFE
20
  from sklearn.linear_model import LogisticRegression
 
21
 
22
  # Configuração da página do Streamlit
23
  st.set_page_config(layout="wide", page_title="Previsão de Reclamações de Clientes")
24
 
25
- st.markdown("""
26
- <div style='background-color: #f0f2f6; padding: 10px; border-radius: 8px; margin-bottom: 15px;'>
27
- <b>Autor:</b> Vinicius Salgueiro Costa &nbsp;&nbsp; <b>Matrícula:</b> 180028880
28
- </div>
29
- """, unsafe_allow_html=True)
30
-
31
  st.title("📊 Previsão de Reclamações de Clientes com Modelos Supervisionados")
32
  st.markdown(
33
  "Este dashboard tem como objetivo identificar clientes com maior probabilidade de terem feito uma reclamação nos últimos 2 anos, utilizando modelos de Machine Learning.")
@@ -99,10 +96,9 @@ def train_and_evaluate_models(X_train_raw, X_test_raw, y_train, y_test, _scaler,
99
 
100
  results = {}
101
 
102
- # --- NOVO: Se for uma chamada inicial, apenas retorna as chaves sem tentar treinar ---
103
  if st.session_state.get('is_initial_call', False):
104
  return {name: {} for name in models.keys()}
105
- # --- FIM NOVO ---
106
 
107
  # Check if y_train has at least two classes before attempting to train (real training calls)
108
  if len(np.unique(y_train)) < 2:
@@ -177,8 +173,6 @@ def train_and_evaluate_models(X_train_raw, X_test_raw, y_train, y_test, _scaler,
177
  "y_prob": y_prob
178
  }
179
  except Exception as e:
180
- # Captura erros de treinamento, mas não os exibe para o usuário final durante a seleção inicial
181
- # O warning será exibido APENAS se o treinamento real for solicitado (nos botões "Treinar" ou "Analisar")
182
  results[name] = {
183
  "Model": None, "Accuracy": 0, "Precision": 0, "Recall": 0, "F1-score": 0,
184
  "AUC": 0.5, "Confusion Matrix": np.array([[0, 0], [0, 0]]), "FPR": [0, 1], "TPR": [0, 1],
@@ -261,7 +255,7 @@ tab1, tab2, tab3, tab4, tab5 = st.tabs([
261
  ])
262
 
263
  with tab1:
264
- st.header("Visão Geral dos Dados")
265
  st.subheader("Primeiras 5 Linhas do Dataset")
266
  st.dataframe(df.head())
267
 
@@ -275,43 +269,64 @@ with tab1:
275
  st.text(s)
276
 
277
  # NOVO: Análise de valores ausentes
278
- st.subheader("Valores Ausentes por Coluna")
279
  missing = df.isnull().sum()
280
- missing = missing[missing > 0]
281
  if not missing.empty:
282
  st.dataframe(missing.to_frame('Nulos'))
 
283
  else:
284
  st.info("Não há valores ausentes no dataset original.")
285
 
286
  # NOVO: Distribuição de variáveis numéricas
287
  num_cols = df_processed.select_dtypes(include=np.number).columns.tolist()
 
 
 
 
288
  if num_cols:
289
- st.subheader("Distribuição de Variáveis Numéricas")
290
  selected_num = st.selectbox("Escolha uma variável numérica para visualizar:", num_cols, key="num_hist")
291
  fig, ax = plt.subplots()
292
  sns.histplot(df_processed[selected_num], kde=True, ax=ax)
293
  ax.set_title(f"Distribuição de {selected_num}")
 
 
294
  st.pyplot(fig)
295
 
296
- # NOVO: Distribuição de variáveis categóricas
297
- cat_cols = [col for col in df.columns if df[col].dtype == 'object']
298
- if cat_cols:
299
- st.subheader("Distribuição de Variáveis Categóricas")
300
- selected_cat = st.selectbox("Escolha uma variável categórica para visualizar:", cat_cols, key="cat_bar")
 
 
 
 
 
301
  fig, ax = plt.subplots()
302
- df[selected_cat].value_counts().plot(kind='bar', ax=ax)
303
  ax.set_title(f"Distribuição de {selected_cat}")
 
 
 
304
  st.pyplot(fig)
305
 
306
  # NOVO: Mapa de calor de correlação
307
- st.subheader("Mapa de Calor das Correlações (variáveis numéricas)")
308
- fig, ax = plt.subplots(figsize=(8, 5))
309
- sns.heatmap(df_processed[num_cols].corr(), cmap='coolwarm', ax=ax, annot=False)
310
- st.pyplot(fig)
 
 
 
 
 
 
311
 
312
  st.subheader("Distribuição da Variável Alvo ('Complain') Original")
313
  fig, ax = plt.subplots(figsize=(6, 4))
314
- sns.countplot(x=y, ax=ax)
315
  ax.set_title("Distribuição Original da Variável 'Complain'")
316
  ax.set_xlabel("Reclamou (0: Não, 1: Sim)")
317
  ax.set_ylabel("Contagem")
@@ -320,12 +335,18 @@ with tab1:
320
  st.warning("Observe o desbalanceamento da classe 'Complain' (poucas reclamações).")
321
 
322
  st.subheader("Comparação de Variáveis com a Variável Alvo (Complain)")
323
- selected_bi = st.selectbox("Escolha uma variável para comparar com 'Complain':", num_cols)
324
- fig, ax = plt.subplots()
325
- sns.boxplot(x=df_processed['Complain'], y=df_processed[selected_bi], ax=ax)
326
- st.pyplot(fig)
 
 
 
 
 
 
327
 
328
- st.subheader("Resumo Estatístico Customizado")
329
  st.dataframe(df_processed.describe(percentiles=[.01, .05, .25, .5, .75, .95, .99]).T)
330
 
331
  with tab2:
@@ -341,22 +362,23 @@ with tab2:
341
  smote = SMOTE(random_state=42)
342
  try:
343
  if len(np.unique(y_display)) < 2:
344
- st.error("SMOTE não pode ser aplicado: A variável alvo contém apenas uma classe.")
 
345
  X_res, y_res = X_display, y_display
346
  else:
347
  X_res, y_res = smote.fit_resample(X_display, y_display)
348
  st.success("Dados balanceados com sucesso!")
349
- st.write(f"'Complain' variable distribution after SMOTE balancing: {Counter(y_res)}")
350
 
351
  fig, ax = plt.subplots(figsize=(6, 4))
352
- sns.countplot(x=y_res, ax=ax)
353
  ax.set_title("Distribuição da Variável 'Complain' Após SMOTE")
354
  ax.set_xlabel("Reclamou (0: Não, 1: Sim)")
355
  ax.set_ylabel("Contagem")
356
  st.pyplot(fig)
357
  except Exception as e:
358
  st.error(
359
- f"Erro ao aplicar SMOTE: {e}. Isso pode acontecer se houver poucas amostras na classe minoritária ou muitas features.")
360
  X_res, y_res = X_display, y_display
361
  else:
362
  st.info("SMOTE desabilitado. O balanceamento não será aplicado.")
@@ -376,16 +398,18 @@ with tab2:
376
  "Não foi possível usar `stratify` no `train_test_split` pois o alvo tem apenas uma classe após o processamento. Dividindo sem estratificação.")
377
  X_train, X_test, y_train, y_test = train_test_split(X_res, y_res, test_size=test_size, random_state=42)
378
 
379
- st.write(f"**Shape dos dados de treino:** {X_train.shape}")
380
- st.write(f"**Shape dos dados de teste:** {X_test.shape}")
381
- st.write(f"**Shape y_train:** {y_train.shape}")
382
- st.write(f"**Shape y_test:** {y_test.shape}")
383
- st.write(f"**Shape do DataFrame (após pré-processamento):** {df_processed.shape}")
384
- st.write(f"**Tipos das colunas (após pré-processamento):**")
 
 
385
  st.dataframe(df_processed.dtypes.astype(str).reset_index().rename(columns={'index': 'Coluna', 0: 'Tipo de Dado'}))
386
- st.write(f"**Primeiras 5 linhas (após pré-processamento):**")
387
  st.dataframe(df_processed.head())
388
- st.write(f"**Classes em y_train:** {np.unique(y_train)}")
389
 
390
  if X_train.empty or y_train.empty:
391
  st.error("Os dados de treino estão vazios! Verifique o carregamento ou pré-processamento dos dados.")
@@ -449,7 +473,6 @@ with tab4:
449
  with st.spinner(f"Analisando {model_choice}..."):
450
  selected_model_results = train_and_evaluate_models(X_train, X_test, y_train, y_test, StandardScaler(),
451
  model_selected=model_choice)
452
-
453
  if model_choice not in selected_model_results or selected_model_results[model_choice]['Model'] is None:
454
  st.error(
455
  f"Não foi possível analisar o modelo {model_choice}. Ele pode ter falhado no treinamento. Erro: {selected_model_results.get(model_choice, {}).get('Error', 'Desconhecido')}")
@@ -530,43 +553,135 @@ with tab5:
530
  model_instance = selected_model_results[model_choice]["Model"]
531
 
532
  st.subheader("Importância das Variáveis")
 
 
533
 
 
534
  if hasattr(model_instance, 'feature_importances_'):
535
  feature_importances = model_instance.feature_importances_
536
  feature_names = X.columns.tolist()
537
  importance_df = pd.DataFrame(
538
  {'Variável': feature_names, 'Importância Relativa': feature_importances})
539
  importance_df = importance_df.sort_values(by='Importância Relativa', ascending=False)
 
 
540
  st.dataframe(importance_df.head(10).set_index('Variável'))
541
 
542
  fig_imp, ax_imp = plt.subplots(figsize=(10, 6))
543
- sns.barplot(x='Importância Relativa', y='Variável', data=importance_df.head(10), ax=ax_imp)
544
- ax_imp.set_title('Top 10 Variáveis Mais Importantes')
 
 
 
 
545
  st.pyplot(fig_imp)
546
 
 
547
  elif hasattr(model_instance, 'coef_'):
548
- st.info("Para modelos lineares, os coeficientes podem ser interpretados como importância.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
  else:
550
  st.info(
551
- "Não foi possível extrair a importância das variáveis para este tipo de modelo de forma direta.")
552
 
553
- st.subheader("Análise e Recomendações Gerenciais")
 
 
 
554
 
555
- st.markdown("""
556
- Com base nas variáveis mais importantes, podemos formular estratégias proativas:
 
 
 
 
 
 
 
 
 
 
 
 
557
 
558
- **Exemplo de Cenário e Recomendação (Ajuste com base nos resultados reais das suas variáveis importantes):**
 
 
559
 
560
- Se, por exemplo, 'MntWines' (gasto com vinho), 'NumWebVisitsMonth' (visitas ao site) e 'Dt_Customer' (dias desde a última compra) forem as variáveis mais importantes:
 
 
561
 
562
- * **Clientes com alto gasto em vinho (`MntWines`)** que apresentam **alta frequência de visitas ao site (`NumWebVisitsMonth`) mas baixo engajamento recente (`Dt_Customer` elevado)** podem estar enfrentando dificuldades para encontrar produtos, informações ou ter problemas não resolvidos.
 
563
 
564
- **Recomendação Gerencial:**
565
 
566
- Priorize esses clientes com **ações proativas de atendimento e retenção**. Por exemplo:
567
- 1. **Suporte Proativo:** Monitore clientes com alto `NumWebVisitsMonth` que não resultam em compra ou que têm histórico de altos gastos e ofereça ajuda via chat ou contato telefônico personalizado.
568
- 2. **Campanhas de Reengajamento:** Crie campanhas segmentadas para clientes com `Dt_Customer` elevado, oferecendo descontos em seus produtos preferidos (ex: vinhos) ou convidando-os a fornecer feedback sobre a experiência recente.
569
- 3. **Melhoria na Experiência Online:** Analise as páginas mais visitadas por esses clientes com `NumWebVisitsMonth` alto para identificar gargalos ou informações ausentes que possam estar gerando frustração.
570
 
571
- Ao antecipar e resolver proativamente as insatisfações, a empresa pode **melhorar a experiência do consumidor, reduzir as taxas de reclamação e aumentar a lealdade do cliente.**
572
  """)
 
18
  import io
19
  from sklearn.feature_selection import RFE
20
  from sklearn.linear_model import LogisticRegression
21
+ from sklearn.inspection import permutation_importance # Importa Permutation Importance
22
 
23
  # Configuração da página do Streamlit
24
  st.set_page_config(layout="wide", page_title="Previsão de Reclamações de Clientes")
25
 
26
+ st.markdown("""<div style='background-color: #f0f2f6; padding: 10px; border-radius: 8px; margin-bottom: 15px;'>
27
+ <b>Autor:</b> Vinicius Salgueiro Costa &nbsp;&nbsp; <b>Matrícula:</b> 180028880</div>""", unsafe_allow_html=True)
 
 
 
 
28
  st.title("📊 Previsão de Reclamações de Clientes com Modelos Supervisionados")
29
  st.markdown(
30
  "Este dashboard tem como objetivo identificar clientes com maior probabilidade de terem feito uma reclamação nos últimos 2 anos, utilizando modelos de Machine Learning.")
 
96
 
97
  results = {}
98
 
99
+ # Se for uma chamada inicial, apenas retorna as chaves sem tentar treinar
100
  if st.session_state.get('is_initial_call', False):
101
  return {name: {} for name in models.keys()}
 
102
 
103
  # Check if y_train has at least two classes before attempting to train (real training calls)
104
  if len(np.unique(y_train)) < 2:
 
173
  "y_prob": y_prob
174
  }
175
  except Exception as e:
 
 
176
  results[name] = {
177
  "Model": None, "Accuracy": 0, "Precision": 0, "Recall": 0, "F1-score": 0,
178
  "AUC": 0.5, "Confusion Matrix": np.array([[0, 0], [0, 0]]), "FPR": [0, 1], "TPR": [0, 1],
 
255
  ])
256
 
257
  with tab1:
258
+ st.header("1. Visão Geral dos Dados")
259
  st.subheader("Primeiras 5 Linhas do Dataset")
260
  st.dataframe(df.head())
261
 
 
269
  st.text(s)
270
 
271
  # NOVO: Análise de valores ausentes
272
+ st.subheader("Valores Ausentes por Coluna (Dataset Original)")
273
  missing = df.isnull().sum()
274
+ missing = missing[missing > 0].sort_values(ascending=False)
275
  if not missing.empty:
276
  st.dataframe(missing.to_frame('Nulos'))
277
+ st.info("Valores ausentes foram tratados durante o pré-processamento.")
278
  else:
279
  st.info("Não há valores ausentes no dataset original.")
280
 
281
  # NOVO: Distribuição de variáveis numéricas
282
  num_cols = df_processed.select_dtypes(include=np.number).columns.tolist()
283
+ # Remover 'Complain' da lista de colunas numéricas para histograma, pois é o alvo
284
+ if 'Complain' in num_cols:
285
+ num_cols.remove('Complain')
286
+
287
  if num_cols:
288
+ st.subheader("Distribuição de Variáveis Numéricas (Após Pré-processamento)")
289
  selected_num = st.selectbox("Escolha uma variável numérica para visualizar:", num_cols, key="num_hist")
290
  fig, ax = plt.subplots()
291
  sns.histplot(df_processed[selected_num], kde=True, ax=ax)
292
  ax.set_title(f"Distribuição de {selected_num}")
293
+ ax.set_xlabel(selected_num)
294
+ ax.set_ylabel("Frequência")
295
  st.pyplot(fig)
296
 
297
+ # NOVO: Distribuição de variáveis categóricas (original para visualização)
298
+ # Pegamos as colunas originais antes do get_dummies para visualização
299
+ original_cat_cols = ['Education', 'Marital_Status']
300
+ # Filtra as colunas que realmente existem no df original
301
+ original_cat_cols = [col for col in original_cat_cols if col in df.columns]
302
+
303
+ if original_cat_cols:
304
+ st.subheader("Distribuição de Variáveis Categóricas (Dataset Original)")
305
+ selected_cat = st.selectbox("Escolha uma variável categórica para visualizar:", original_cat_cols,
306
+ key="cat_bar")
307
  fig, ax = plt.subplots()
308
+ sns.countplot(x=df[selected_cat], ax=ax, palette='viridis')
309
  ax.set_title(f"Distribuição de {selected_cat}")
310
+ ax.set_xlabel(selected_cat)
311
+ ax.set_ylabel("Contagem")
312
+ plt.xticks(rotation=45, ha='right')
313
  st.pyplot(fig)
314
 
315
  # NOVO: Mapa de calor de correlação
316
+ st.subheader("Mapa de Calor das Correlações (Variáveis Numéricas Processadas)")
317
+ if num_cols: # Garante que ainda há colunas numéricas após remoção de 'Complain'
318
+ fig, ax = plt.subplots(figsize=(12, 10))
319
+ corr_matrix = df_processed[num_cols].corr()
320
+ mask = np.triu(np.ones_like(corr_matrix, dtype=bool)) # Para não repetir a matriz
321
+ sns.heatmap(corr_matrix, cmap='coolwarm', annot=False, fmt=".2f", linewidths=.5, ax=ax, mask=mask)
322
+ ax.set_title('Mapa de Calor de Correlações')
323
+ st.pyplot(fig)
324
+ else:
325
+ st.info("Não há colunas numéricas suficientes para gerar o mapa de calor de correlação.")
326
 
327
  st.subheader("Distribuição da Variável Alvo ('Complain') Original")
328
  fig, ax = plt.subplots(figsize=(6, 4))
329
+ sns.countplot(x=y, ax=ax, palette='pastel')
330
  ax.set_title("Distribuição Original da Variável 'Complain'")
331
  ax.set_xlabel("Reclamou (0: Não, 1: Sim)")
332
  ax.set_ylabel("Contagem")
 
335
  st.warning("Observe o desbalanceamento da classe 'Complain' (poucas reclamações).")
336
 
337
  st.subheader("Comparação de Variáveis com a Variável Alvo (Complain)")
338
+ if num_cols:
339
+ selected_bi = st.selectbox("Escolha uma variável para comparar com 'Complain':", num_cols, key="bivariate_plot")
340
+ fig, ax = plt.subplots(figsize=(8, 6))
341
+ sns.boxplot(x=df_processed['Complain'], y=df_processed[selected_bi], ax=ax, palette='coolwarm')
342
+ ax.set_title(f"Distribuição de {selected_bi} por Classe de Reclamação")
343
+ ax.set_xlabel("Reclamou (0: Não, 1: Sim)")
344
+ ax.set_ylabel(selected_bi)
345
+ st.pyplot(fig)
346
+ else:
347
+ st.info("Não há colunas numéricas para comparação bivariada.")
348
 
349
+ st.subheader("Resumo Estatístico Customizado (Dataset Processado)")
350
  st.dataframe(df_processed.describe(percentiles=[.01, .05, .25, .5, .75, .95, .99]).T)
351
 
352
  with tab2:
 
362
  smote = SMOTE(random_state=42)
363
  try:
364
  if len(np.unique(y_display)) < 2:
365
+ st.error(
366
+ "SMOTE não pode ser aplicado: A variável alvo contém apenas uma classe. Isso geralmente ocorre em datasets muito pequenos ou após uma filtragem intensa.")
367
  X_res, y_res = X_display, y_display
368
  else:
369
  X_res, y_res = smote.fit_resample(X_display, y_display)
370
  st.success("Dados balanceados com sucesso!")
371
+ st.write(f"**Distribuição da variável 'Complain' depois do balanceamento com SMOTE:** {Counter(y_res)}")
372
 
373
  fig, ax = plt.subplots(figsize=(6, 4))
374
+ sns.countplot(x=y_res, ax=ax, palette='pastel')
375
  ax.set_title("Distribuição da Variável 'Complain' Após SMOTE")
376
  ax.set_xlabel("Reclamou (0: Não, 1: Sim)")
377
  ax.set_ylabel("Contagem")
378
  st.pyplot(fig)
379
  except Exception as e:
380
  st.error(
381
+ f"Erro ao aplicar SMOTE: {e}. Isso pode acontecer se houver poucas amostras na classe minoritária ou muitas features. Os dados não foram balanceados.")
382
  X_res, y_res = X_display, y_display
383
  else:
384
  st.info("SMOTE desabilitado. O balanceamento não será aplicado.")
 
398
  "Não foi possível usar `stratify` no `train_test_split` pois o alvo tem apenas uma classe após o processamento. Dividindo sem estratificação.")
399
  X_train, X_test, y_train, y_test = train_test_split(X_res, y_res, test_size=test_size, random_state=42)
400
 
401
+ st.markdown("---")
402
+ st.subheader("Status dos Dados de Treino e Teste")
403
+ st.write(f"**Shape dos dados de treino (X_train):** {X_train.shape}")
404
+ st.write(f"**Shape dos dados de teste (X_test):** {X_test.shape}")
405
+ st.write(f"**Shape dos rótulos de treino (y_train):** {y_train.shape}")
406
+ st.write(f"**Shape dos rótulos de teste (y_test):** {y_test.shape}")
407
+ st.write(f"**Shape do DataFrame completo (após pré-processamento e antes do split):** {df_processed.shape}")
408
+ st.write(f"**Tipos de Dados das Colunas (após pré-processamento):**")
409
  st.dataframe(df_processed.dtypes.astype(str).reset_index().rename(columns={'index': 'Coluna', 0: 'Tipo de Dado'}))
410
+ st.write(f"**Primeiras 5 linhas do DataFrame (após pré-processamento):**")
411
  st.dataframe(df_processed.head())
412
+ st.write(f"**Classes únicas em y_train:** {np.unique(y_train)}")
413
 
414
  if X_train.empty or y_train.empty:
415
  st.error("Os dados de treino estão vazios! Verifique o carregamento ou pré-processamento dos dados.")
 
473
  with st.spinner(f"Analisando {model_choice}..."):
474
  selected_model_results = train_and_evaluate_models(X_train, X_test, y_train, y_test, StandardScaler(),
475
  model_selected=model_choice)
 
476
  if model_choice not in selected_model_results or selected_model_results[model_choice]['Model'] is None:
477
  st.error(
478
  f"Não foi possível analisar o modelo {model_choice}. Ele pode ter falhado no treinamento. Erro: {selected_model_results.get(model_choice, {}).get('Error', 'Desconhecido')}")
 
553
  model_instance = selected_model_results[model_choice]["Model"]
554
 
555
  st.subheader("Importância das Variáveis")
556
+ st.markdown(
557
+ "A importância das variáveis indica o quanto cada atributo contribui para a capacidade preditiva do modelo. Entender esses fatores é crucial para a tomada de decisão estratégica.")
558
 
559
+ # Tenta obter feature_importances (para modelos de árvore)
560
  if hasattr(model_instance, 'feature_importances_'):
561
  feature_importances = model_instance.feature_importances_
562
  feature_names = X.columns.tolist()
563
  importance_df = pd.DataFrame(
564
  {'Variável': feature_names, 'Importância Relativa': feature_importances})
565
  importance_df = importance_df.sort_values(by='Importância Relativa', ascending=False)
566
+
567
+ st.markdown("**Importância por Ganho de Informação / Gini (para modelos em árvore):**")
568
  st.dataframe(importance_df.head(10).set_index('Variável'))
569
 
570
  fig_imp, ax_imp = plt.subplots(figsize=(10, 6))
571
+ sns.barplot(x='Importância Relativa', y='Variável', data=importance_df.head(10), ax=ax_imp,
572
+ palette='viridis')
573
+ ax_imp.set_title('Top 10 Variáveis Mais Importantes (Feature Importance)')
574
+ ax_imp.set_xlabel('Importância Relativa')
575
+ ax_imp.set_ylabel('Variável')
576
+ plt.tight_layout()
577
  st.pyplot(fig_imp)
578
 
579
+ # Tenta obter coeficientes (para modelos lineares)
580
  elif hasattr(model_instance, 'coef_'):
581
+ # Para SVM, se for linear, ou Logistic Regression (se adicionado)
582
+ # Certifique-se que X_train_raw é usado se o modelo não usa scaler
583
+ # ou use X_train_processed se o modelo usou scaler
584
+
585
+ # Para este dashboard, o X_train_raw tem as colunas originais (ou após RFE)
586
+ # e é o que deve ser usado para mapear coeficientes
587
+
588
+ # É crucial que o scaler usado aqui seja o mesmo que foi ajustado na função de treinamento
589
+ # Reajustar o scaler para obter os coeficientes no espaço original das features,
590
+ # se o modelo linear for treinado em dados escalados.
591
+
592
+ # Vamos assumir que para SVM (que usa coef_), ele foi treinado em X_train_processed (escalado)
593
+ # Para interpretar, seria ideal reverter o escalonamento ou entender os coeficientes no espaço escalado.
594
+ # Para simplicidade e foco na interpretabilidade gerencial, permutation importance é mais direto aqui.
595
+
596
+ st.markdown("**Coeficientes (para modelos lineares como SVM Linear):**")
597
+ # Para simplificar, mostraremos os coeficientes, mas a interpretação direta é mais complexa com features escaladas
598
+ if model_instance.coef_.ndim > 1:
599
+ coefs = model_instance.coef_[0]
600
+ else:
601
+ coefs = model_instance.coef_
602
+
603
+ coef_df = pd.DataFrame({'Variável': X_train.columns, 'Coeficiente': coefs})
604
+ coef_df = coef_df.sort_values(by='Coeficiente', ascending=False)
605
+ st.dataframe(coef_df)
606
+ st.info(
607
+ "Para coeficientes, o sinal indica a direção da relação (positiva/negativa) e a magnitude a força. Em modelos com dados escalados, a magnitude é relativa.")
608
+
609
+ # Importância por Permutação (geralmente aplicável a qualquer modelo)
610
+ # Esta é uma técnica robusta e interpretável.
611
+ st.markdown("---")
612
+ st.markdown("**Importância por Permutação (Mede o Impacto na Performance do Modelo):**")
613
+ st.info(
614
+ "A importância por permutação embaralha uma variável e mede a queda no desempenho do modelo, indicando quão crucial ela é para as previsões. É robusta para diferentes tipos de modelos.")
615
+
616
+ # Certifica-se de que o modelo foi treinado e X_test/y_test não estão vazios
617
+ if model_instance and not X_test.empty and not y_test.empty:
618
+ # Ajusta X_test para o modelo (escalado ou não)
619
+ X_test_for_pi = StandardScaler().fit_transform(X_test) if model_choice in ["K-Nearest Neighbors",
620
+ "Support Vector Machine"] else X_test
621
+
622
+ try:
623
+ perm_importance = permutation_importance(model_instance, X_test_for_pi, y_test, n_repeats=10,
624
+ random_state=42, n_jobs=-1)
625
+ perm_df = pd.DataFrame({
626
+ 'Variável': X_test.columns[perm_importance.importances_mean.argsort()[::-1]],
627
+ # Ordena pelo mean
628
+ 'Importância Média': perm_importance.importances_mean[
629
+ perm_importance.importances_mean.argsort()[::-1]],
630
+ 'Desvio Padrão': perm_importance.importances_std[
631
+ perm_importance.importances_mean.argsort()[::-1]]
632
+ })
633
+ st.dataframe(perm_df.head(10).set_index('Variável'))
634
+
635
+ fig_pi, ax_pi = plt.subplots(figsize=(10, 6))
636
+ sns.barplot(x='Importância Média', y='Variável', data=perm_df.head(10), ax=ax_pi,
637
+ palette='cividis')
638
+ ax_pi.set_title('Top 10 Variáveis Mais Importantes (Permutation Importance)')
639
+ ax_pi.set_xlabel('Importância Média (Queda na Performance)')
640
+ ax_pi.set_ylabel('Variável')
641
+ plt.tight_layout()
642
+ st.pyplot(fig_pi)
643
+
644
+ except Exception as e:
645
+ st.warning(
646
+ f"Não foi possível calcular a Importância por Permutação: {e}. Isso pode ocorrer com alguns modelos ou se os dados de teste forem muito pequenos.")
647
  else:
648
  st.info(
649
+ "Dados de teste insuficientes ou modelo não treinado para calcular Importância por Permutação.")
650
 
651
+ st.markdown("---")
652
+ st.subheader("Análise Estratégica e Recomendações Gerenciais")
653
+ st.markdown(f"""
654
+ Como analista de dados, sua missão é transformar os insights do modelo de **Análise de Personalidade do Cliente** em ações concretas para a empresa varejista. A capacidade de prever reclamações nos permite adotar uma abordagem proativa, em vez de reativa, melhorando a experiência do consumidor, priorizando suporte personalizado e antecipando insatisfações.
655
 
656
+ O modelo **{model_choice}** nos forneceu as variáveis mais influentes na probabilidade de um cliente registrar uma reclamação. A partir da análise das "Importâncias de Variáveis" (seja por Ganho/Gini, Coeficientes ou Permutação), podemos identificar os **perfis de cliente de alto risco** e propor intervenções estratégicas.
657
+
658
+ **Interpretando os Fatores Chave :**
659
+
660
+ As variáveis que consistentemente aparecem no topo das listas de importância (Feature Importance e/ou Permutation Importance) são seus principais alvos. Por exemplo, se seu modelo destacar:
661
+
662
+ * **`Days_Since_Customer` (Dias Desde a Última Compra)**: Se essa variável tiver alta importância e uma correlação positiva com reclamações, indica que clientes inativos ou que não compram há muito tempo tendem a reclamar mais. Eles podem estar se sentindo negligenciados ou ter tido uma experiência negativa não resolvida.
663
+ * **`NumWebVisitsMonth` (Número de Visitas ao Site no Último Mês)**: Se importante e associado a reclamações, pode significar que clientes estão visitando o site com frequência, talvez buscando soluções para problemas ou informações que não encontram facilmente, levando à frustração.
664
+ * **`MntWines` (Gasto em Vinho)** ou outras categorias de produtos: Altos gastos em categorias específicas podem indicar clientes de alto valor. Se essa variável for importante para prever reclamações, pode sinalizar que esses clientes têm altas expectativas ou que problemas relacionados a esses produtos/serviços geram maior insatisfação.
665
+ * **`Kidhome` / `Teenhome` (Presença de Crianças/Adolescentes)**: Fatores demográficos podem revelar segmentos com necessidades específicas ou sensibilidades que, se não atendidas, resultam em reclamações.
666
+
667
+ **Recomendações Gerenciais Estratégicas:**
668
+
669
+ Com base nesses insights, a empresa pode adaptar suas estratégias de marketing e relacionamento, focando nos segmentos de maior risco:
670
 
671
+ 1. **Priorização do Suporte Personalizado Proativo:**
672
+ * Monitorar clientes que o modelo classifica com alta probabilidade de reclamação.
673
+ * Para clientes com **longo tempo de inatividade** e/ou **altas visitas ao site sem conversão**, acione proativamente um contato (e-mail personalizado, ligação, ou pop-up de chat) oferecendo ajuda, coletando feedback ou reengajando com ofertas relevantes. Isso transforma uma potencial reclamação em uma oportunidade de fidelização.
674
 
675
+ 2. **Otimização da Jornada do Cliente e Conteúdo:**
676
+ * Se o **número de visitas do site** for um fator chave para reclamações, investigue as páginas mais visitadas por esses clientes de alto risco. Há informações faltando? O processo de compra ou suporte é confuso? Use esses dados para otimizar a usabilidade do site e a clareza das informações.
677
+ * Crie **conteúdo direcionado** para as necessidades específicas de segmentos identificados (ex: famílias com crianças/adolescentes) se `Kidhome` ou `Teenhome` forem relevantes.
678
 
679
+ 3. **Gestão da Satisfação para Clientes de Alto Valor:**
680
+ * Para clientes que gastam muito em **categorias de produtos específicas** (como vinho), mas que têm alta propensão a reclamar, implemente um programa de "check-up" de satisfação pós-compra ou um canal de suporte VIP. A perda desses clientes tem um impacto financeiro maior.
681
 
682
+ **Exemplo de Recomendação Acionável (Ajuste com seus dados reais):**
683
 
684
+ "Clientes que não realizam compras ** mais de 180 dias** e que registraram **mais de 5 visitas ao site no último mês** sem interações significativas, apresentam uma probabilidade de reclamação ** maior**. Estes clientes devem ser imediatamente incluídos em uma **campanha de 'reconexão proativa'**, por meio de comunicações como CRM ou Whatsapp oferecendo suporte personalizado para dúvidas não resolvidas e incentivos de reengajamento adaptados aos seus históricos de compra."
 
 
 
685
 
686
+ Ao aplicar esses insights da **Análise de Personalidade do Cliente**, a empresa pode não apenas reduzir o volume de reclamações, mas também aprofundar o entendimento sobre seus clientes, construir lealdade e, em última análise, impulsionar o crescimento do negócio.
687
  """)