252106862eder commited on
Commit
4248ee7
·
verified ·
1 Parent(s): d804c4f

Update model_utils.py

Browse files
Files changed (1) hide show
  1. model_utils.py +62 -56
model_utils.py CHANGED
@@ -19,7 +19,7 @@ import re # Para auxiliar na limpeza de markdown para LaTeX
19
 
20
  # Importações para LaTeX (pylatex)
21
  from pylatex import Document, Section, Command, LongTable, Tabular, Figure, NoEscape, Math, LineBreak
22
- from pylatex.utils import italic, NoEscape
23
  from pylatex.base_classes import Environment
24
 
25
  # --- DEFINIÇÃO DAS FEATURES PREDITIVAS E COLUNA ALVO PARA SEU data.csv ---
@@ -139,7 +139,7 @@ class ChurnModelPipeline:
139
 
140
  plot_dir = tempfile.mkdtemp()
141
  self.plot_paths = {}
142
- dpi = 100 # Qualidade da imagem para Gradio
143
 
144
  # --- 1. Correlation Heatmap ---
145
  if self.df_raw_for_plots is not None and not self.df_raw_for_plots.empty:
@@ -189,12 +189,7 @@ class ChurnModelPipeline:
189
  plt.tight_layout()
190
  cm_path = os.path.join(plot_dir, 'confusion_matrix.png')
191
  plt.savefig(cm_path)
192
- # Ajuste para garantir que o plot_dir é um diretório válido
193
- if not os.path.exists(plot_dir):
194
- os.makedirs(plot_dir)
195
- cm_path = os.path.join(plot_dir, 'confusion_matrix.png')
196
- plt.savefig(cm_path)
197
- plt.close()
198
  self.plot_paths['confusion_matrix'] = cm_path
199
 
200
  # --- 4. ROC Curve ---
@@ -214,7 +209,7 @@ class ChurnModelPipeline:
214
  plt.tight_layout()
215
  roc_path = os.path.join(plot_dir, 'roc_curve.png')
216
  plt.savefig(roc_path)
217
- plt.close()
218
  self.plot_paths['roc_curve'] = roc_path
219
 
220
  def predict_churn(self, input_data: pd.DataFrame) -> Tuple[int, float]:
@@ -276,6 +271,7 @@ class ChurnModelPipeline:
276
  latex_story.append(NoEscape(sample_display_df.to_latex(index=False, caption='Características do Cliente Simulado', label='tab:sim_customer', longtable=False)))
277
 
278
  markdown_story.append(f"**Resultado da Simulação:** O cliente **{churn_status_sample}** (Probabilidade de Churn: **{prob_sample:.2%}**)\n")
 
279
  latex_story.append(NoEscape(f'\textbf{{Resultado da Simulação:}} O cliente \textbf{{{churn_status_sample}}} (Probabilidade de Churn: \textbf{{{prob_sample:.2f}\%}})\n\n'))
280
  else:
281
  markdown_story.append("Não foi possível realizar uma simulação pois o DataFrame de teste ou dados interativos não estão disponíveis.\n")
@@ -295,12 +291,14 @@ class ChurnModelPipeline:
295
  training_details_markdown += f"- **Shape X_train (antes pré-processamento):** `{self.training_details.get('X_train_shape', 'N/A')}`\n"
296
 
297
  y_train_before_smote = self.training_details.get('y_train_value_counts_before_smote', {})
298
- training_details_markdown += f"- **Balanceamento \`Exited\` (antes SMOTE):** `Não Churn: {y_train_before_smote.get(0, 'N/A')}, Churn: {y_train_before_smote.get(1, 'N/A')}`\n"
 
299
 
300
  training_details_markdown += f"- **Shape X_train (após pré-processamento):** `{self.training_details.get('X_train_processed_shape', 'N/A')}`\n"
301
 
302
  y_train_after_smote = self.training_details.get('y_train_resampled_value_counts_after_smote', {})
303
- training_details_markdown += f"- **Balanceamento \`Exited\` (após SMOTE):** `Não Churn: {y_train_after_smote.get(0, 'N/A')}, Churn: {y_train_after_smote.get(1, 'N/A')}`\n"
 
304
 
305
  training_details_markdown += f"- **Modelo Treinado:** `{'Sim' if self.training_details.get('model_trained_successfully', False) else 'Não'}`\n"
306
 
@@ -309,10 +307,10 @@ class ChurnModelPipeline:
309
  training_details_latex += fr'\item \textbf{{Dataset Carregado:}} {self.training_details.get("dataset_rows", "N/A")} linhas' + '\n'
310
  training_details_latex += fr'\item \textbf{{Features Preditivas:}} \texttt{{{", ".join(self.training_details.get("predictor_features", ["N/A"]))}}}.' + '\n'
311
  training_details_latex += fr'\item \textbf{{Coluna Alvo:}} \texttt{{{self.training_details.get("target_column", "N/A")}}}.' + '\n'
312
- training_details_latex += fr'\item \textbf{{Shape $X_{train}$ (antes pré-processamento):}} {self.training_details.get("X_train_shape", "N/A")}.' + '\n'
313
- training_details_latex += fr'\item \textbf{{Balanceamento \texttt{Exited} (antes SMOTE):}} Não Churn: {y_train_before_smote.get(0, "N/A")}, Churn: {y_train_before_smote.get(1, "N/A")}.' + '\n'
314
- training_details_latex += fr'\item \textbf{{Shape $X_{train}$ (após pré-processamento):}} {self.training_details.get("X_train_processed_shape", "N/A")}.' + '\n'
315
- training_details_latex += fr'\item \textbf{{Balanceamento \texttt{Exited} (após SMOTE):}} Não Churn: {y_train_after_smote.get(0, "N/A")}, Churn: {y_train_after_smote.get(1, "N/A")}.' + '\n'
316
  training_details_latex += fr'\item \textbf{{Modelo Treinado:}} {"Sim" if self.training_details.get("model_trained_successfully", False) else "Não"}.' + '\n'
317
  training_details_latex += r'\end{itemize}' + '\n\n'
318
 
@@ -445,9 +443,10 @@ class ChurnModelPipeline:
445
  doc.append(Command('graphicspath', NoEscape(r'{./}'))) # Para imagens no mesmo diretório
446
 
447
  # --- Cabeçalho Personalizado (com base nas informações do usuário) ---
448
- doc.append(NoEscape(r'\title{MODELAGEM PREDITIVA DE CHURN DE CLIENTES BANCÁRIOS UTILIZANDO REGRESSÃO LOGÍSTICA}'))
449
- doc.append(NoEscape(r'\author{ÉDER MARCELO PONTES CUNHA}'))
450
- doc.append(NoEscape(r'\date{26 de Outubro de 2025}')) # Ajuste conforme necessário
 
451
 
452
  doc.append(NoEscape(r'\begin{titlepage}'))
453
  doc.append(Command('centering'))
@@ -457,7 +456,8 @@ class ChurnModelPipeline:
457
  if os.path.exists(logo_filename):
458
  with doc.create(Figure(position='h!')) as logo_fig:
459
  logo_fig.add_image(logo_filename, width='0.25\textwidth')
460
- logo_fig.add_caption(NoEscape(r'\vspace{-1.5cm}')) # Ajuste vertical para o texto abaixo do logo
 
461
  else:
462
  doc.append(Command('textbf', 'AVISO: Logo da UnB não encontrado! Certifique-se de que "marcador.png" esteja no mesmo diretório do arquivo .tex.'))
463
 
@@ -478,13 +478,38 @@ class ChurnModelPipeline:
478
 
479
  # Título do Trabalho (do usuário, ajustado para LaTeX)
480
  # Quebra de linha manual para o título
481
- title_parts = header_info["titulo_trabalho"].replace('UTILIZANDO', r'\UTILIZANDO').split(r'\')
 
 
482
  doc.append(Command('Huge'))
483
- doc.append(Command('textbf', NoEscape(title_parts[0])))
484
- for part in title_parts[1:]:
485
- doc.append(LineBreak())
486
- doc.append(Command('textbf', NoEscape(part)))
487
- doc.append(LineBreak())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
 
489
  doc.append(Command('vspace', '1.0cm'))
490
 
@@ -507,49 +532,30 @@ class ChurnModelPipeline:
507
  doc.append(Command('vfill')) # Empurra o conteúdo para cima
508
  doc.append(Command('end{titlepage}'))
509
 
510
- doc.append(Command('maketitle')) # Gera um título padrão (mas estamos sobrescrevendo com o titlepage)
 
 
511
  doc.append(Command('clearpage'))
512
 
513
  # Conteúdo do Resumo
514
  for item in latex_content_parts:
515
- if isinstance(item, str) and item.strip().startswith('\begin{tabular}'):
516
- # Handle DataFrame.to_latex output manually to fit pylatex's tabular environment
517
- # This is a bit tricky. pylatex's tabular works best with specific object types.
518
- # For simplicity, we'll embed the raw latex table string
519
- doc.append(NoEscape(item))
520
- elif isinstance(item, str) and (item.startswith('\section') or item.startswith('\subsection')):
521
- doc.append(NoEscape(item + '\n'))
522
- elif isinstance(item, str):
523
- # Process common markdown-like elements in raw string for LaTeX
524
- processed_str = item.replace('**', '\textbf{').replace('*', '\emph{').replace('`', '\texttt{')
525
- processed_str = processed_str.replace('}', '}}') # close bold/emph/texttt
526
- processed_str = processed_str.replace('}}', '}') # fix double close
527
- processed_str = processed_str.replace('%', '\%').replace('&', '\&').replace('_', '_') # escape LaTeX special chars
528
- doc.append(NoEscape(processed_str))
529
- else:
530
- doc.append(item) # Assume it's a pylatex object (Section, Math etc.)
531
- doc.append(LineBreak()) # Add a line break after each item
532
 
533
  # Adicionar imagens ao final do documento LaTeX
534
  doc.append(NoEscape(r'\clearpage'))
535
- doc.append(NoEscape(r'\section*{Visualizações Gráficas do Modelo}'))
536
- doc.append(NoEscape(r'\addcontentsline{toc}{section}{Visualizações Gráficas do Modelo}')) # Adicionar ao sumário
537
 
 
538
  for key, path in plot_paths.items():
539
  if os.path.exists(path):
540
- doc.append(NoEscape(r'\subsection*{'+ key.replace('_', ' ').title() +'}'))
541
- doc.append(NoEscape(r'\begin{figure}[htbp]'))
542
- doc.append(NoEscape(r'\centering'))
543
- # Ajustar width para caber na página, altura automática
544
- doc.append(NoEscape(f'\includegraphics[width=0.9\textwidth]{{{path}}}'))
545
- doc.append(NoEscape(f'\caption{{{key.replace("_", " ").title()}}}'))
546
- doc.append(NoEscape(f'\label{{fig:{key}}}'))
547
- doc.append(NoEscape(r'\end{figure}'))
548
- doc.append(NoEscape(r'\clearpage')) # Cada imagem em uma nova página
549
-
550
  # Salvar o arquivo .tex
551
  latex_output_dir = tempfile.mkdtemp()
552
  output_filename = os.path.join(latex_output_dir, 'relatorio_churn.tex')
553
  doc.generate_tex(output_filename) # Salva o arquivo .tex
554
 
555
- return output_filename
 
19
 
20
  # Importações para LaTeX (pylatex)
21
  from pylatex import Document, Section, Command, LongTable, Tabular, Figure, NoEscape, Math, LineBreak
22
+ from pylatex.utils import italic
23
  from pylatex.base_classes import Environment
24
 
25
  # --- DEFINIÇÃO DAS FEATURES PREDITIVAS E COLUNA ALVO PARA SEU data.csv ---
 
139
 
140
  plot_dir = tempfile.mkdtemp()
141
  self.plot_paths = {}
142
+ dpi = 150 # Aumentado DPI para melhor qualidade em relatórios
143
 
144
  # --- 1. Correlation Heatmap ---
145
  if self.df_raw_for_plots is not None and not self.df_raw_for_plots.empty:
 
189
  plt.tight_layout()
190
  cm_path = os.path.join(plot_dir, 'confusion_matrix.png')
191
  plt.savefig(cm_path)
192
+ plt.close() # Fechar a figura para liberar memória
 
 
 
 
 
193
  self.plot_paths['confusion_matrix'] = cm_path
194
 
195
  # --- 4. ROC Curve ---
 
209
  plt.tight_layout()
210
  roc_path = os.path.join(plot_dir, 'roc_curve.png')
211
  plt.savefig(roc_path)
212
+ plt.close() # Fechar a figura para liberar memória
213
  self.plot_paths['roc_curve'] = roc_path
214
 
215
  def predict_churn(self, input_data: pd.DataFrame) -> Tuple[int, float]:
 
271
  latex_story.append(NoEscape(sample_display_df.to_latex(index=False, caption='Características do Cliente Simulado', label='tab:sim_customer', longtable=False)))
272
 
273
  markdown_story.append(f"**Resultado da Simulação:** O cliente **{churn_status_sample}** (Probabilidade de Churn: **{prob_sample:.2%}**)\n")
274
+ # Corrigido o SyntaxWarning para '%' no f-string para LaTeX
275
  latex_story.append(NoEscape(f'\textbf{{Resultado da Simulação:}} O cliente \textbf{{{churn_status_sample}}} (Probabilidade de Churn: \textbf{{{prob_sample:.2f}\%}})\n\n'))
276
  else:
277
  markdown_story.append("Não foi possível realizar uma simulação pois o DataFrame de teste ou dados interativos não estão disponíveis.\n")
 
291
  training_details_markdown += f"- **Shape X_train (antes pré-processamento):** `{self.training_details.get('X_train_shape', 'N/A')}`\n"
292
 
293
  y_train_before_smote = self.training_details.get('y_train_value_counts_before_smote', {})
294
+ # Corrigido o SyntaxWarning para '`' no f-string para Markdown
295
+ training_details_markdown += f"- **Balanceamento `Exited` (antes SMOTE):** `Não Churn: {y_train_before_smote.get(0, 'N/A')}, Churn: {y_train_before_smote.get(1, 'N/A')}`\n"
296
 
297
  training_details_markdown += f"- **Shape X_train (após pré-processamento):** `{self.training_details.get('X_train_processed_shape', 'N/A')}`\n"
298
 
299
  y_train_after_smote = self.training_details.get('y_train_resampled_value_counts_after_smote', {})
300
+ # Corrigido o SyntaxWarning para '`' no f-string para Markdown
301
+ training_details_markdown += f"- **Balanceamento `Exited` (após SMOTE):** `Não Churn: {y_train_after_smote.get(0, 'N/A')}, Churn: {y_train_after_smote.get(1, 'N/A')}`\n"
302
 
303
  training_details_markdown += f"- **Modelo Treinado:** `{'Sim' if self.training_details.get('model_trained_successfully', False) else 'Não'}`\n"
304
 
 
307
  training_details_latex += fr'\item \textbf{{Dataset Carregado:}} {self.training_details.get("dataset_rows", "N/A")} linhas' + '\n'
308
  training_details_latex += fr'\item \textbf{{Features Preditivas:}} \texttt{{{", ".join(self.training_details.get("predictor_features", ["N/A"]))}}}.' + '\n'
309
  training_details_latex += fr'\item \textbf{{Coluna Alvo:}} \texttt{{{self.training_details.get("target_column", "N/A")}}}.' + '\n'
310
+ training_details_latex += fr'\item \textbf{{Shape $X_{{train}}$ (antes pré-processamento):}} {self.training_details.get("X_train_shape", "N/A")}.' + '\n' # $X_{train}$ corrigido
311
+ training_details_latex += fr'\item \textbf{{Balanceamento \texttt{{Exited}} (antes SMOTE):}} Não Churn: {y_train_before_smote.get(0, "N/A")}, Churn: {y_train_before_smote.get(1, "N/A")}.' + '\n'
312
+ training_details_latex += fr'\item \textbf{{Shape $X_{{train}}$ (após pré-processamento):}} {self.training_details.get("X_train_processed_shape", "N/A")}.' + '\n' # $X_{train}$ corrigido
313
+ training_details_latex += fr'\item \textbf{{Balanceamento \texttt{{Exited}} (após SMOTE):}} Não Churn: {y_train_after_smote.get(0, "N/A")}, Churn: {y_train_after_smote.get(1, "N/A")}.' + '\n'
314
  training_details_latex += fr'\item \textbf{{Modelo Treinado:}} {"Sim" if self.training_details.get("model_trained_successfully", False) else "Não"}.' + '\n'
315
  training_details_latex += r'\end{itemize}' + '\n\n'
316
 
 
443
  doc.append(Command('graphicspath', NoEscape(r'{./}'))) # Para imagens no mesmo diretório
444
 
445
  # --- Cabeçalho Personalizado (com base nas informações do usuário) ---
446
+ # Removendo title, author, date pois o titlepage vai sobrescrevê-los
447
+ # doc.append(NoEscape(r'\title{MODELAGEM PREDITIVA DE CHURN DE CLIENTES BANCÁRIOS UTILIZANDO REGRESSÃO LOGÍSTICA}'))
448
+ # doc.append(NoEscape(r'\author{ÉDER MARCELO PONTES CUNHA}'))
449
+ # doc.append(NoEscape(r'\date{26 de Outubro de 2025}')) # Ajuste conforme necessário
450
 
451
  doc.append(NoEscape(r'\begin{titlepage}'))
452
  doc.append(Command('centering'))
 
456
  if os.path.exists(logo_filename):
457
  with doc.create(Figure(position='h!')) as logo_fig:
458
  logo_fig.add_image(logo_filename, width='0.25\textwidth')
459
+ # A caption vazia ou um vspace garante que não haja texto extra colado no logo
460
+ logo_fig.add_caption(NoEscape(r'\vspace{-0.5cm}'))
461
  else:
462
  doc.append(Command('textbf', 'AVISO: Logo da UnB não encontrado! Certifique-se de que "marcador.png" esteja no mesmo diretório do arquivo .tex.'))
463
 
 
478
 
479
  # Título do Trabalho (do usuário, ajustado para LaTeX)
480
  # Quebra de linha manual para o título
481
+ # CORRIGIDO: title_parts = header_info["titulo_trabalho"].replace('UTILIZANDO', r'\UTILIZANDO').split(r'\')
482
+ # AQUI FOI O ERRO DE SYNTAX. Deve ser assim:
483
+ title_parts_raw = header_info["titulo_trabalho"].replace(' UTILIZANDO ', r'\ \large ').split(r'\')
484
  doc.append(Command('Huge'))
485
+ doc.append(Command('textbf', NoEscape(title_parts_raw[0]))) # Primeira parte do título
486
+
487
+ # As partes restantes são separadas por `\` que adicionamos
488
+ # Iterar sobre as partes restantes e adicionar com quebra de linha
489
+ # A lógica de split mudou para apenas quebrar em ' ' e adicionar o comando LaTeX manualmente
490
+ title_words = header_info["titulo_trabalho"].split()
491
+ latex_title_lines = []
492
+ current_line = []
493
+ for word in title_words:
494
+ if word == 'UTILIZANDO':
495
+ if current_line:
496
+ latex_title_lines.append(" ".join(current_line))
497
+ current_line = []
498
+ latex_title_lines.append(r'\ \large UTILIZANDO') # Comando LaTeX para quebra de linha e tamanho da fonte
499
+ else:
500
+ current_line.append(word)
501
+ if current_line:
502
+ latex_title_lines.append(" ".join(current_line))
503
+
504
+ doc.append(Command('Huge'))
505
+ doc.append(Command('textbf', NoEscape(latex_title_lines[0]))) # Primeira linha do título
506
+ for line_idx in range(1, len(latex_title_lines)):
507
+ doc.append(LineBreak())
508
+ # Se for a linha com 'UTILIZANDO', já está formatada, caso contrário, use textbf
509
+ if 'UTILIZANDO' in latex_title_lines[line_idx]:
510
+ doc.append(NoEscape(latex_title_lines[line_idx]))
511
+ else:
512
+ doc.append(Command('textbf', NoEscape(latex_title_lines[line_idx])))
513
 
514
  doc.append(Command('vspace', '1.0cm'))
515
 
 
532
  doc.append(Command('vfill')) # Empurra o conteúdo para cima
533
  doc.append(Command('end{titlepage}'))
534
 
535
+ # doc.append(Command('maketitle')) # Não precisamos de maketitle pois usamos titlepage
536
+ doc.append(Command('clearpage'))
537
+ doc.append(Command('tableofcontents')) # Sumário
538
  doc.append(Command('clearpage'))
539
 
540
  # Conteúdo do Resumo
541
  for item in latex_content_parts:
542
+ doc.append(item) # pylatex objects (Section, Math, NoEscape) are directly appended
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
  # Adicionar imagens ao final do documento LaTeX
545
  doc.append(NoEscape(r'\clearpage'))
546
+ doc.append(Section(NoEscape(r'Visualizações Gráficas do Modelo')))
 
547
 
548
+ # Aumentado a largura para preencher mais a página e centralizar
549
  for key, path in plot_paths.items():
550
  if os.path.exists(path):
551
+ with doc.create(Figure(position='htbp')) as plot_fig:
552
+ plot_fig.add_image(path, width='0.8\textwidth')
553
+ plot_fig.add_caption(NoEscape(f'{key.replace("_", " ").title()}'))
554
+ doc.append(Command('clearpage')) # Cada imagem em uma nova página
555
+
 
 
 
 
 
556
  # Salvar o arquivo .tex
557
  latex_output_dir = tempfile.mkdtemp()
558
  output_filename = os.path.join(latex_output_dir, 'relatorio_churn.tex')
559
  doc.generate_tex(output_filename) # Salva o arquivo .tex
560
 
561
+ return output_filename