252106862eder commited on
Commit
5e29983
·
verified ·
1 Parent(s): f79e124

Update model_utils.py

Browse files

"Fixed SyntaxError by separating f-string and raw string parts for LaTeX percentage formatting; logo diagnostic check."

Files changed (1) hide show
  1. model_utils.py +56 -74
model_utils.py CHANGED
@@ -276,10 +276,10 @@ 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(fr'\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")
282
- latex_story.append(r'Não foi possível realizar uma simulação pois o DataFrame de teste ou dados interativos não estão disponíveis.\n\n')
283
 
284
  # --- 2. Detalhes do Processo de Treinamento ---
285
  markdown_story.append("## 2. Detalhes do Processo de Treinamento")
@@ -320,7 +320,7 @@ class ChurnModelPipeline:
320
  latex_story.append(NoEscape(training_details_latex))
321
  else:
322
  markdown_story.append("Nenhum detalhe de treinamento disponível.\n")
323
- latex_story.append(r'Nenhum detalhe de treinamento disponível.\n\n')
324
 
325
 
326
  # --- 3. Descrição do Modelo e Metodologia ---
@@ -328,22 +328,22 @@ class ChurnModelPipeline:
328
  latex_story.append(Section(NoEscape(r'Descrição do Modelo e Metodologia'), False))
329
 
330
  markdown_story.append("O modelo utiliza **Regressão Logística** para classificar a probabilidade de um cliente sair (churn). Foram aplicadas as seguintes etapas para garantir robustez e tratar as características dos dados:\n")
331
- latex_story.append(r'O modelo utiliza \textbf{Regressão Logística} para classificar a probabilidade de um cliente sair (churn). Foram aplicadas as seguintes etapas para garantir robustez e tratar as características dos dados:\n\n')
332
 
333
  markdown_story.append("- **Pré-processamento de Dados:**\n - **Features Numéricas:** Imputação de valores ausentes (mediana) e escalonamento (`StandardScaler`) para padronização.\n - **Features Categóricas:** Imputação de valores ausentes (moda) e codificação One-Hot (`OneHotEncoder`) para transformar categorias em formato numérico.\n")
334
- latex_story.append(r'\begin{itemize}' + '\n')
335
- latex_story.append(r'\item \textbf{Pré-processamento de Dados:}' + '\n')
336
- latex_story.append(r'\begin{itemize}' + '\n')
337
- latex_story.append(r'\item \textbf{Features Numéricas:} Imputação de valores ausentes (mediana) e escalonamento (\texttt{StandardScaler}) para padronização.' + '\n')
338
- latex_story.append(r'\item \textbf{Features Categóricas:} Imputação de valores ausentes (moda) e codificação One-Hot (\texttt{OneHotEncoder}) para transformar categorias em formato numérico.' + '\n')
339
- latex_story.append(r'\end{itemize}' + '\n')
340
 
341
  markdown_story.append("- **Balanceamento de Classes (SMOTE):** O conjunto de dados original apresentava desbalanceamento significativo na variável alvo (`Exited`). O algoritmo SMOTE (Synthetic Minority Over-sampling Technique) foi aplicado para gerar amostras sintéticas da classe minoritária (clientes que saem), garantindo que o modelo não seja viesado para a classe majoritária (clientes que permanecem).\n")
342
- latex_story.append(r'\item \textbf{Balanceamento de Classes (SMOTE):} O conjunto de dados original apresentava desbalanceamento significativo na variável alvo (\texttt{Exited}). O algoritmo SMOTE (Synthetic Minority Over-sampling Technique) foi aplicado para gerar amostras sintéticas da classe minoritária (clientes que saem), garantindo que o modelo não seja viesado para a classe majoritária (clientes que permanecem).' + '\n')
343
 
344
  markdown_story.append("- **Regularização (L2):** A Regressão Logística foi configurada com um parâmetro `C=0.1` (inverso da força de regularização), que aplica regularização L2. Isso ajuda a prevenir o overfitting, penalizando coeficientes grandes e promovendo um modelo mais generalizável.\n")
345
- latex_story.append(r'\item \textbf{Regularização (L2):} A Regressão Logística foi configurada com um parâmetro \texttt{C=0.1} (inverso da força de regularização), que aplica regularização L2. Isso ajuda a prevenir o overfitting, penalizando coeficientes grandes e promovendo um modelo mais generalizável.' + '\n')
346
- latex_story.append(r'\end{itemize}' + '\n\n')
347
 
348
 
349
  # --- 4. Como a Probabilidade de Churn é Calculada ---
@@ -351,17 +351,17 @@ class ChurnModelPipeline:
351
  latex_story.append(Section(NoEscape(r'Como a Probabilidade de Churn é Calculada'), False))
352
 
353
  markdown_story.append("A Regressão Logística é um modelo de classificação que estima a probabilidade de um evento (neste caso, o churn do cliente) ocorrer. Ao contrário da regressão linear, que prevê um valor contínuo, a regressão logística utiliza a **função sigmoide** para mapear qualquer valor real para um valor entre 0 e 1, que pode ser interpretado como probabilidade.\n")
354
- latex_story.append(r'A Regressão Logística é um modelo de classificação que estima a probabilidade de um evento (neste caso, o churn do cliente) ocorrer. Ao contrário da regressão linear, que prevê um valor contínuo, a regressão logística utiliza a \textbf{função sigmoide} para mapear qualquer valor real para um valor entre 0 e 1, que pode ser interpretado como probabilidade.\n\n')
355
 
356
  markdown_story.append("A equação básica de um modelo linear (`L`) é:\n`L = β₀ + β₁X₁ + β₂X₂ + ... + βₙXₙ`\nOnde `β` são os coeficientes (pesos) das features (`X`).\n")
357
  latex_story.append(Math(data=[NoEscape(r'L = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \dots + \beta_n X_n')]))
358
- latex_story.append(r'\nOnde $\beta$ são os coeficientes (pesos) das features ($X$).\n\n')
359
 
360
  markdown_story.append("A probabilidade (`P`) de churn é então calculada aplicando-se a função sigmoide (σ) a `L`:\n`P(Churn) = σ(L) = 1 / (1 + e⁻ᴸ)`\n")
361
  latex_story.append(Math(data=[NoEscape(r'P(\text{Churn}) = \sigma(L) = \frac{1}{1 + e^{-L}}')]))
362
 
363
  markdown_story.append("Esta função garante que a saída esteja sempre entre 0 e 1, representando a probabilidade de o cliente pertencer à classe 'Churn' (ou seja, `Exited = 1`). Se `P(Churn)` for maior que um determinado limiar (geralmente 0.5), o cliente é classificado como provável churn.\n")
364
- latex_story.append(r'\nEsta função garante que a saída esteja sempre entre 0 e 1, representando a probabilidade de o cliente pertencer à classe `Churn` (ou seja, \texttt{Exited = 1}). Se $P(\text{Churn})$ for maior que um determinado limiar (geralmente 0.5), o cliente é classificado como provável churn.\n\n')
365
 
366
  # --- Subseção: Exemplo de Simulação Numérica (AGORA COM DADOS REAIS DA SIMULAÇÃO) ---
367
  markdown_story.append("### Exemplo de Simulação Numérica com Cliente Simulado")
@@ -370,7 +370,7 @@ class ChurnModelPipeline:
370
  if sample_customer_df is not None:
371
  # Reutilizamos os valores de logit_sample e prob_sample calculados anteriormente para o cliente simulado
372
  markdown_story.append("Para ilustrar o cálculo, vamos usar as características do cliente simulado acima (ou o último cliente da Previsão Interativa) e os coeficientes do modelo treinado. Note que as características numéricas são **escalonadas** e as categóricas **one-hot encoded** antes de serem multiplicadas pelos coeficientes. \n")
373
- latex_story.append(r'Para ilustrar o cálculo, vamos usar as características do cliente simulado acima (ou o último cliente da Previsão Interativa) e os coeficientes do modelo treinado. Note que as características numéricas são \textbf{escalonadas} e as categóricas \textbf{one-hot encoded} antes de serem multiplicadas pelos coeficientes. \n\n')
374
 
375
  markdown_story.append(f"**Características do Cliente 'Simulado':**\n" + sample_display_df.to_markdown(index=False) + "\n")
376
  latex_story.append(NoEscape(sample_display_df.to_latex(index=False, caption='Características do Cliente Simulado', label='tab:sim_customer_example', longtable=False)))
@@ -384,33 +384,31 @@ class ChurnModelPipeline:
384
  markdown_story.append(f"1. **Calcular o Logit (L):** O Logit é a soma ponderada de todas as características do cliente (já processadas pelo pré-processador do modelo) multiplicadas por seus respectivos coeficientes, mais o intercepto do modelo. Para o cliente simulado, o modelo calculou um Logit de:\n`L = {logit_sample_formatted}`\n")
385
 
386
  # LaTeX for Logit calculation
387
- latex_story.append(r'\textbf{Passos do Cálculo para o Cliente "Simulado":}\n')
388
- latex_story.append(r'\begin{enumerate}')
389
- latex_story.append(fr'\item \textbf{{Calcular o Logit (L):}} O Logit é a soma ponderada de todas as características do cliente (já processadas pelo pré-processador do modelo) multiplicadas por seus respectivos coeficientes, mais o intercepto do modelo. Para o cliente simulado, o modelo calculou um Logit de:')
390
  latex_story.append(Math(data=[NoEscape(fr'L = {logit_sample_formatted}')]))
391
 
392
  markdown_story.append(f"2. **Calcular a Probabilidade de Churn (P) usando a função Sigmoide:** A probabilidade é obtida aplicando-se a função sigmoide ao valor de `L`:\n`P(Churn) = 1 / (1 + e^(-L))`\n`P(Churn) = 1 / (1 + e^(-({logit_sample_formatted})))`\n`P(Churn) = 1 / (1 + e^{{-{logit_sample_formatted}}})`\n`P(Churn) ≈ {prob_sample_formatted}`\n")
393
 
394
  # LaTeX for Probability calculation
395
- latex_story.append(r'\item \textbf{Calcular a Probabilidade de Churn (P) usando a função Sigmoide:} A probabilidade é obtida aplicando-se a função sigmoide ao valor de $L$:')
396
  latex_story.append(Math(data=[NoEscape(r'P(\text{Churn}) = \frac{1}{1 + e^{-L}}')]))
397
- # LINHA 403 CORRIGIDA: Removido os parênteses extras do expoente.
398
- latex_story.append(Math(data=[NoEscape(fr'P(\text{Churn}) = \frac{{1}}{{1 + e^{{-{logit_sample_formatted}}}}}')]))
399
- # LINHA 405 CORRIGIDA: Idem.
400
- latex_story.append(Math(data=[NoEscape(fr'P(\text{Churn}) = \frac{{1}}{{1 + e^{{-{logit_sample_formatted}}}}}')]))
401
  latex_story.append(Math(data=[NoEscape(fr'P(\text{Churn}) \approx {prob_sample_formatted}')]))
402
- latex_story.append(r'\end{enumerate}\n')
403
 
404
  markdown_story.append(f"**Resultado da Simulação para o Cliente 'Simulado':**\n")
405
  markdown_story.append(f"A probabilidade de Churn para este cliente específico é de **{prob_sample_formatted}**, ou seja, **{prob_sample_percent_formatted}**.\n")
406
  markdown_story.append(f"Este resultado indica que o cliente possui uma probabilidade de churn de {prob_sample_percent_formatted}, guiando a interpretação do risco.\n")
407
 
408
- latex_story.append(r'\textbf{Resultado da Simulação para o Cliente "Simulado":}\n')
409
- latex_story.append(fr'A probabilidade de Churn para este cliente específico é de \textbf{{{prob_sample_formatted}}}, ou seja, \textbf{{{prob_sample_percent_formatted}}}. ')
410
- latex_story.append(fr'Este resultado indica que o cliente possui uma probabilidade de churn de {prob_sample_percent_formatted}, guiando a interpretação do risco.\n\n')
411
  else:
412
  markdown_story.append("Não foi possível gerar o exemplo de simulação numérica, pois nenhum cliente simulado foi fornecido.\n")
413
- latex_story.append(r'Não foi possível gerar o exemplo de simulação numérica, pois nenhum cliente simulado foi fornecido.\n\n')
414
 
415
  # --- Fim da Subseção de Exemplo ---
416
 
@@ -419,64 +417,48 @@ class ChurnModelPipeline:
419
  latex_story.append(Section(NoEscape(r'Importância das Variáveis (Coeficientes e Odds Ratio)'), False))
420
 
421
  markdown_story.append("A análise dos coeficientes do modelo de Regressão Logística, transformados em Odds Ratios, nos permite entender a influência de cada característica na probabilidade de Churn. Um Odds Ratio maior que 1 indica que o aumento daquela feature (ou pertencer àquela categoria) aumenta as chances de Churn, enquanto um valor menor que 1 diminui.\n")
422
- latex_story.append(r'A análise dos coeficientes do modelo de Regressão Logística, transformados em Odds Ratios, nos permite entender a influência de cada característica na probabilidade de Churn. Um Odds Ratio maior que 1 indica que o aumento daquela feature (ou pertencer àquela categoria) aumenta as chances de Churn, enquanto um valor menor que 1 diminui.\n\n')
423
 
424
  if not self.coefficients_df.empty:
425
  markdown_story.append(self.coefficients_df.to_markdown(index=False) + "\n")
426
  latex_story.append(NoEscape(self.coefficients_df.to_latex(index=False, caption='Coeficientes e Odds Ratios das Variáveis', label='tab:coefficients', longtable=False)))
427
-
428
- markdown_story.append("### **Interpretação Estratégica:**\n- **Odds Ratio > 1:** Features como `Age`, `NumOfProducts`, `Balance`, ou certas categorias de `Geography` ou `Gender` (dependendo dos valores) que possuem um Odds Ratio alto, indicam que o cliente tem **maior chance de churn** à medida que essas características aumentam ou são verdadeiras. O banco pode focar em clientes com essas características para ações proativas de retenção.\n- **Odds Ratio < 1:** Features como `IsActiveMember` (se for 1 para ativo) ou `CreditScore` (se um score mais alto diminuir o churn) que possuem Odds Ratio baixo, indicam que a característica está associada a uma **menor chance de churn**.\n")
429
- latex_story.append(r'\textbf{Interpretação Estratégica:}\n\n')
430
- latex_story.append(r'\begin{itemize}' + '\n')
431
- latex_story.append(r'\item \textbf{Odds Ratio > 1:} Features como \texttt{Age}, \texttt{NumOfProducts}, \texttt{Balance}, ou certas categorias de \texttt{Geography} ou \texttt{Gender} (dependendo dos valores) que possuem um Odds Ratio alto, indicam que o cliente tem \textbf{maior chance de churn} à medida que essas características aumentam ou são verdadeiras. O banco pode focar em clientes com essas características para ações proativas de retenção.' + '\n')
432
- latex_story.append(r'\item \textbf{Odds Ratio < 1:} Features como \texttt{IsActiveMember} (se for 1 para ativo) ou \texttt{CreditScore} (se um score mais alto diminuir o churn) que possuem Odds Ratio baixo, indicam que a característica está associada a uma \textbf{menor chance de churn}.' + '\n')
433
- latex_story.append(r'\end{itemize}' + '\n\n')
434
- else:
435
- markdown_story.append("Nenhum coeficiente disponível. O modelo pode não ter sido treinado ou não possui coeficientes acessíveis.\n")
436
- latex_story.append(r'Nenhum coeficiente disponível. O modelo pode não ter sido treinado ou não possui coeficientes acessíveis.\n\n')
437
 
438
- # --- 6. Avaliação de Desempenho do Modelo (Conjunto de Teste) ---
439
- markdown_story.append("## 6. Avaliação de Desempenho do Modelo (Conjunto de Teste)")
440
- latex_story.append(Section(NoEscape(r'Avaliação de Desempenho do Modelo (Conjunto de Teste)'), False))
441
-
442
- markdown_story.append("O desempenho do modelo foi avaliado em um conjunto de dados de teste (20% dos dados originais) para garantir sua capacidade de generalização em dados não vistos. As métricas são as seguintes:\n")
443
- latex_story.append(r'O desempenho do modelo foi avaliado em um conjunto de dados de teste (20\% dos dados originais) para garantir sua capacidade de generalização em dados não vistos. As métricas são as seguintes:\n\n')
444
-
445
- if self.metrics_dict:
446
- metrics_table = pd.DataFrame(self.metrics_dict.items(), columns=['Métrica', 'Valor'])
447
- markdown_story.append(metrics_table.to_markdown(index=False) + "\n")
448
- latex_story.append(NoEscape(metrics_table.to_latex(index=False, caption='Métricas de Avaliação do Modelo', label='tab:metrics', longtable=False)))
449
-
450
- markdown_story.append("### **Interpretação das Métricas:**\n")
451
- latex_story.append(r'\textbf{Interpretação das Métricas:}\n\n')
452
- latex_story.append(r'\begin{itemize}' + '\n')
453
-
454
- markdown_story.append(f"- **Acurácia ({self.metrics_dict.get('Acurácia', 0):.2%}):** Proporção de previsões corretas (tanto churn quanto não-churn) em relação ao total. Indica a precisão geral do modelo. Um valor de {self.metrics_dict.get('Acurácia', 0):.2%} significa que o modelo acertou essa porcentagem das vezes no conjunto de teste.\n")
455
- latex_story.append(fr'\item \textbf{{Acurácia ({self.metrics_dict.get("Acurácia", 0):.2f}\%)}}: Proporção de previsões corretas (tanto churn quanto não-churn) em relação ao total. Indica a precisão geral do modelo. Um valor de {self.metrics_dict.get("Acurácia", 0):.2f}\% significa que o modelo acertou essa porcentagem das vezes no conjunto de teste.' + '\n')
456
-
457
- markdown_story.append(f"- **AUC ROC ({self.metrics_dict.get('AUC ROC', 0):.4f}):** A Área sob a Curva Característica de Operação do Receptor mede a capacidade do modelo de distinguir entre as classes. Um valor de 0.5 indica desempenho aleatório, enquanto 1.0 indica um classificador perfeito. Seu modelo obteve um AUC de \textbf{{{self.metrics_dict.get("AUC ROC", 0):.4f}}}.\n")
458
- latex_story.append(fr'\item \textbf{{AUC ROC ({self.metrics_dict.get("AUC ROC", 0):.4f})}}: A Área sob a Curva Característica de Operação do Receptor mede a capacidade do modelo de distinguir entre as classes. Um valor de 0.5 indica desempenho aleatório, enquanto 1.0 indica um classificador perfeito. Seu modelo obteve um AUC de \textbf{{{self.metrics_dict.get("AUC ROC", 0):.4f}}}.' + '\n')
459
-
460
- markdown_story.append(f"- **Precisão ({self.metrics_dict.get('Precisão', 0):.2%}):** Das previsões de churn (`1`), quantos realmente foram churn. É importante para o banco não abordar clientes que não iriam dar churn (reduzir falsos positivos). Um valor de {self.metrics_dict.get('Precisão', 0):.2%} significa que das vezes que o modelo previu churn, essa porcentagem estava correta.\n")
461
- latex_story.append(fr'\item \textbf{{Precisão ({self.metrics_dict.get("Precisão", 0):.2f}\%)}}: Das previsões de churn (\texttt{1}), quantos realmente foram churn. É importante para o banco não abordar clientes que não iriam dar churn (reduzir falsos positivos). Um valor de {self.metrics_dict.get("Precisão", 0):.2f}\% significa que das vezes que o modelo previu churn, essa porcentagem estava correta.' + '\n')
462
-
463
- markdown_story.append(f"- **Recall (Sensibilidade) ({self.metrics_dict.get('Recall (Sensibilidade)', 0):.2%}):** Dos clientes que realmente deram churn (`1`), quantos o modelo identificou. É crucial para o banco identificar o máximo de clientes em risco (reduzir falsos negativos). Um valor de {self.metrics_dict.get('Recall (Sensibilidade)', 0):.2%} significa que essa porcentagem de clientes que de fato deram churn foi corretamente identificada pelo modelo.\n")
464
- latex_story.append(fr'\item \textbf{{Recall (Sensibilidade) ({self.metrics_dict.get("Recall (Sensibilidade)", 0):.2f}\%)}}: Dos clientes que realmente deram churn (\texttt{1}), quantos o modelo identificou. É crucial para o banco identificar o máximo de clientes em risco (reduzir falsos negativos). Um valor de {self.metrics_dict.get("Recall (Sensibilidade)", 0):.2f}\% significa que essa porcentagem de clientes que de fato deram churn foi corretamente identificada pelo modelo.' + '\n'))
465
 
466
  markdown_story.append(f"- **F1-Score ({self.metrics_dict.get('F1-Score', 0):.4f}):** É a média harmônica entre Precisão e Recall, útil quando há um desequilíbrio de classes e você precisa de um balanço entre identificar corretamente e não levantar falsos alarmes.\n")
467
- latex_story.append(fr'\item \textbf{{F1-Score ({self.metrics_dict.get("F1-Score", 0):.4f})}}: É a média harmônica entre Precisão e Recall, útil quando há um desequilíbrio de classes e você precisa de um balanço entre identificar corretamente e não levantar falsos alarmes.' + '\n'))
468
 
469
- latex_story.append(r'\end{itemize}' + '\n\n')
470
  else:
471
- markdown_story.append("Nenhuma métrica de avaliação disponível. O modelo pode não ter sido treinado ou avaliado.\n")
472
- latex_story.append(r'Nenhum dado de avaliação disponível. O modelo pode não ter sido treinado ou avaliado.\n\n')
473
 
474
  # --- 7. Conclusão e Próximos Passos ---
475
  markdown_story.append("## 7. Conclusão e Próximos Passos")
476
  latex_story.append(Section(NoEscape(r'Conclusão e Próximos Passos'), False))
477
 
478
  markdown_story.append("O modelo de Regressão Logística provê uma base sólida para a previsão de churn. As variáveis identificadas como mais influentes (pelos Odds Ratios) devem ser o foco para o planejamento estratégico de retenção. Por exemplo, campanhas de marketing direcionadas a grupos de maior risco ou ofertas personalizadas podem ser desenvolvidas com base nas características que aumentam a probabilidade de churn.\nPara aprimoramento contínuo, sugere-se a exploração de outros modelos, engenharia de novas features, e reavaliação periódica do modelo com dados mais recentes.")
479
- latex_story.append(r'O modelo de Regressão Logística provê uma base sólida para a previsão de churn. As variáveis identificadas como mais influentes (pelos Odds Ratios) devem ser o foco para o planejamento estratégico de retenção. Por exemplo, campanhas de marketing direcionadas a grupos de maior risco ou ofertas personalizadas podem ser desenvolvidas com base nas características que aumentam a probabilidade de churn.\n\nPara aprimoramento contínuo, sugere-se a exploração de outros modelos, engenharia de novas features, e reavaliação periódica do modelo com dados mais recentes.')
480
 
481
  return "\n".join(markdown_story), latex_story, self.plot_paths
482
 
@@ -518,7 +500,7 @@ class ChurnModelPipeline:
518
  shutil.copy2(logo_filename, logo_target_path) # Copia o logo para o diretório temporário do LaTeX
519
  with doc.create(Figure(position='h!')) as logo_fig:
520
  # Referencia pelo nome do arquivo, pois está no mesmo diretório do .tex
521
- logo_fig.add_image(os.path.basename(logo_target_path), width='0.25\textwidth')
522
  logo_fig.add_caption(NoEscape(r'\vspace{-0.5cm}'))
523
  else:
524
  doc.append(Command('textbf', 'AVISO: Logo da UnB não encontrado! Certifique-se de que "MARCADOR.png" esteja na raiz do seu Hugging Face Space.'))
 
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(fr'\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")
282
+ latex_story.append(NoEscape(r'Não foi possível realizar uma simulação pois o DataFrame de teste ou dados interativos não estão disponíveis.\n\n'))
283
 
284
  # --- 2. Detalhes do Processo de Treinamento ---
285
  markdown_story.append("## 2. Detalhes do Processo de Treinamento")
 
320
  latex_story.append(NoEscape(training_details_latex))
321
  else:
322
  markdown_story.append("Nenhum detalhe de treinamento disponível.\n")
323
+ latex_story.append(NoEscape(r'Nenhum detalhe de treinamento disponível.\n\n'))
324
 
325
 
326
  # --- 3. Descrição do Modelo e Metodologia ---
 
328
  latex_story.append(Section(NoEscape(r'Descrição do Modelo e Metodologia'), False))
329
 
330
  markdown_story.append("O modelo utiliza **Regressão Logística** para classificar a probabilidade de um cliente sair (churn). Foram aplicadas as seguintes etapas para garantir robustez e tratar as características dos dados:\n")
331
+ latex_story.append(NoEscape(r'O modelo utiliza \textbf{Regressão Logística} para classificar a probabilidade de um cliente sair (churn). Foram aplicadas as seguintes etapas para garantir robustez e tratar as características dos dados:\n\n'))
332
 
333
  markdown_story.append("- **Pré-processamento de Dados:**\n - **Features Numéricas:** Imputação de valores ausentes (mediana) e escalonamento (`StandardScaler`) para padronização.\n - **Features Categóricas:** Imputação de valores ausentes (moda) e codificação One-Hot (`OneHotEncoder`) para transformar categorias em formato numérico.\n")
334
+ latex_story.append(NoEscape(r'\begin{itemize}' + '\n'))
335
+ latex_story.append(NoEscape(r'\item \textbf{Pré-processamento de Dados:}' + '\n'))
336
+ latex_story.append(NoEscape(r'\begin{itemize}' + '\n'))
337
+ latex_story.append(NoEscape(r'\item \textbf{Features Numéricas:} Imputação de valores ausentes (mediana) e escalonamento (\texttt{StandardScaler}) para padronização.' + '\n'))
338
+ latex_story.append(NoEscape(r'\item \textbf{Features Categóricas:} Imputação de valores ausentes (moda) e codificação One-Hot (\texttt{OneHotEncoder}) para transformar categorias em formato numérico.' + '\n'))
339
+ latex_story.append(NoEscape(r'\end{itemize}' + '\n'))
340
 
341
  markdown_story.append("- **Balanceamento de Classes (SMOTE):** O conjunto de dados original apresentava desbalanceamento significativo na variável alvo (`Exited`). O algoritmo SMOTE (Synthetic Minority Over-sampling Technique) foi aplicado para gerar amostras sintéticas da classe minoritária (clientes que saem), garantindo que o modelo não seja viesado para a classe majoritária (clientes que permanecem).\n")
342
+ latex_story.append(NoEscape(r'\item \textbf{Balanceamento de Classes (SMOTE):} O conjunto de dados original apresentava desbalanceamento significativo na variável alvo (\texttt{Exited}). O algoritmo SMOTE (Synthetic Minority Over-sampling Technique) foi aplicado para gerar amostras sintéticas da classe minoritária (clientes que saem), garantindo que o modelo não seja viesado para a classe majoritária (clientes que permanecem).' + '\n'))
343
 
344
  markdown_story.append("- **Regularização (L2):** A Regressão Logística foi configurada com um parâmetro `C=0.1` (inverso da força de regularização), que aplica regularização L2. Isso ajuda a prevenir o overfitting, penalizando coeficientes grandes e promovendo um modelo mais generalizável.\n")
345
+ latex_story.append(NoEscape(r'\item \textbf{Regularização (L2):} A Regressão Logística foi configurada com um parâmetro \texttt{C=0.1} (inverso da força de regularização), que aplica regularização L2. Isso ajuda a prevenir o overfitting, penalizando coeficientes grandes e promovendo um modelo mais generalizável.' + '\n'))
346
+ latex_story.append(NoEscape(r'\end{itemize}' + '\n\n'))
347
 
348
 
349
  # --- 4. Como a Probabilidade de Churn é Calculada ---
 
351
  latex_story.append(Section(NoEscape(r'Como a Probabilidade de Churn é Calculada'), False))
352
 
353
  markdown_story.append("A Regressão Logística é um modelo de classificação que estima a probabilidade de um evento (neste caso, o churn do cliente) ocorrer. Ao contrário da regressão linear, que prevê um valor contínuo, a regressão logística utiliza a **função sigmoide** para mapear qualquer valor real para um valor entre 0 e 1, que pode ser interpretado como probabilidade.\n")
354
+ latex_story.append(NoEscape(r'A Regressão Logística é um modelo de classificação que estima a probabilidade de um evento (neste caso, o churn do cliente) ocorrer. Ao contrário da regressão linear, que prevê um valor contínuo, a regressão logística utiliza a \textbf{função sigmoide} para mapear qualquer valor real para um valor entre 0 e 1, que pode ser interpretado como probabilidade.\n\n'))
355
 
356
  markdown_story.append("A equação básica de um modelo linear (`L`) é:\n`L = β₀ + β₁X₁ + β₂X₂ + ... + βₙXₙ`\nOnde `β` são os coeficientes (pesos) das features (`X`).\n")
357
  latex_story.append(Math(data=[NoEscape(r'L = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \dots + \beta_n X_n')]))
358
+ latex_story.append(NoEscape(r'\nOnde $\beta$ são os coeficientes (pesos) das features ($X$).\n\n'))
359
 
360
  markdown_story.append("A probabilidade (`P`) de churn é então calculada aplicando-se a função sigmoide (σ) a `L`:\n`P(Churn) = σ(L) = 1 / (1 + e⁻ᴸ)`\n")
361
  latex_story.append(Math(data=[NoEscape(r'P(\text{Churn}) = \sigma(L) = \frac{1}{1 + e^{-L}}')]))
362
 
363
  markdown_story.append("Esta função garante que a saída esteja sempre entre 0 e 1, representando a probabilidade de o cliente pertencer à classe 'Churn' (ou seja, `Exited = 1`). Se `P(Churn)` for maior que um determinado limiar (geralmente 0.5), o cliente é classificado como provável churn.\n")
364
+ latex_story.append(NoEscape(r'\nEsta função garante que a saída esteja sempre entre 0 e 1, representando a probabilidade de o cliente pertencer à classe `Churn` (ou seja, \texttt{Exited = 1}). Se $P(\text{Churn})$ for maior que um determinado limiar (geralmente 0.5), o cliente é classificado como provável churn.\n\n'))
365
 
366
  # --- Subseção: Exemplo de Simulação Numérica (AGORA COM DADOS REAIS DA SIMULAÇÃO) ---
367
  markdown_story.append("### Exemplo de Simulação Numérica com Cliente Simulado")
 
370
  if sample_customer_df is not None:
371
  # Reutilizamos os valores de logit_sample e prob_sample calculados anteriormente para o cliente simulado
372
  markdown_story.append("Para ilustrar o cálculo, vamos usar as características do cliente simulado acima (ou o último cliente da Previsão Interativa) e os coeficientes do modelo treinado. Note que as características numéricas são **escalonadas** e as categóricas **one-hot encoded** antes de serem multiplicadas pelos coeficientes. \n")
373
+ latex_story.append(NoEscape(r'Para ilustrar o cálculo, vamos usar as características do cliente simulado acima (ou o último cliente da Previsão Interativa) e os coeficientes do modelo treinado. Note que as características numéricas são \textbf{escalonadas} e as categóricas \textbf{one-hot encoded} antes de serem multiplicadas pelos coeficientes. \n\n'))
374
 
375
  markdown_story.append(f"**Características do Cliente 'Simulado':**\n" + sample_display_df.to_markdown(index=False) + "\n")
376
  latex_story.append(NoEscape(sample_display_df.to_latex(index=False, caption='Características do Cliente Simulado', label='tab:sim_customer_example', longtable=False)))
 
384
  markdown_story.append(f"1. **Calcular o Logit (L):** O Logit é a soma ponderada de todas as características do cliente (já processadas pelo pré-processador do modelo) multiplicadas por seus respectivos coeficientes, mais o intercepto do modelo. Para o cliente simulado, o modelo calculou um Logit de:\n`L = {logit_sample_formatted}`\n")
385
 
386
  # LaTeX for Logit calculation
387
+ latex_story.append(NoEscape(r'\textbf{Passos do Cálculo para o Cliente "Simulado":}\n'))
388
+ latex_story.append(NoEscape(r'\begin{enumerate}'))
389
+ latex_story.append(NoEscape(fr'\item \textbf{{Calcular o Logit (L):}} O Logit é a soma ponderada de todas as características do cliente (já processadas pelo pré-processador do modelo) multiplicadas por seus respectivos coeficientes, mais o intercepto do modelo. Para o cliente simulado, o modelo calculou um Logit de:'))
390
  latex_story.append(Math(data=[NoEscape(fr'L = {logit_sample_formatted}')]))
391
 
392
  markdown_story.append(f"2. **Calcular a Probabilidade de Churn (P) usando a função Sigmoide:** A probabilidade é obtida aplicando-se a função sigmoide ao valor de `L`:\n`P(Churn) = 1 / (1 + e^(-L))`\n`P(Churn) = 1 / (1 + e^(-({logit_sample_formatted})))`\n`P(Churn) = 1 / (1 + e^{{-{logit_sample_formatted}}})`\n`P(Churn) ≈ {prob_sample_formatted}`\n")
393
 
394
  # LaTeX for Probability calculation
395
+ latex_story.append(NoEscape(r'\item \textbf{Calcular a Probabilidade de Churn (P) usando a função Sigmoide:} A probabilidade é obtida aplicando-se a função sigmoide ao valor de $L$:'))
396
  latex_story.append(Math(data=[NoEscape(r'P(\text{Churn}) = \frac{1}{1 + e^{-L}}')]))
397
+ latex_story.append(Math(data=[NoEscape(fr'P(\text{Churn}) = \frac{{1}}{{1 + e^{{-({logit_sample_formatted})}}}}')]))
398
+ latex_story.append(Math(data=[NoEscape(fr'P(\text{Churn}) = \frac{{1}}{{1 + e^{{-{logit_sample_formatted}}}}}}')]))
 
 
399
  latex_story.append(Math(data=[NoEscape(fr'P(\text{Churn}) \approx {prob_sample_formatted}')]))
400
+ latex_story.append(NoEscape(r'\end{enumerate}\n'))
401
 
402
  markdown_story.append(f"**Resultado da Simulação para o Cliente 'Simulado':**\n")
403
  markdown_story.append(f"A probabilidade de Churn para este cliente específico é de **{prob_sample_formatted}**, ou seja, **{prob_sample_percent_formatted}**.\n")
404
  markdown_story.append(f"Este resultado indica que o cliente possui uma probabilidade de churn de {prob_sample_percent_formatted}, guiando a interpretação do risco.\n")
405
 
406
+ latex_story.append(NoEscape(r'\textbf{Resultado da Simulação para o Cliente "Simulado":}\n'))
407
+ latex_story.append(NoEscape(fr'A probabilidade de Churn para este cliente específico é de \textbf{{{prob_sample_formatted}}}, ou seja, \textbf{{{prob_sample_percent_formatted}}}. '))
408
+ latex_story.append(NoEscape(fr'Este resultado indica que o cliente possui uma probabilidade de churn de {prob_sample_percent_formatted}, guiando a interpretação do risco.\n\n'))
409
  else:
410
  markdown_story.append("Não foi possível gerar o exemplo de simulação numérica, pois nenhum cliente simulado foi fornecido.\n")
411
+ latex_story.append(NoEscape(r'Não foi possível gerar o exemplo de simulação numérica, pois nenhum cliente simulado foi fornecido.\n\n'))
412
 
413
  # --- Fim da Subseção de Exemplo ---
414
 
 
417
  latex_story.append(Section(NoEscape(r'Importância das Variáveis (Coeficientes e Odds Ratio)'), False))
418
 
419
  markdown_story.append("A análise dos coeficientes do modelo de Regressão Logística, transformados em Odds Ratios, nos permite entender a influência de cada característica na probabilidade de Churn. Um Odds Ratio maior que 1 indica que o aumento daquela feature (ou pertencer àquela categoria) aumenta as chances de Churn, enquanto um valor menor que 1 diminui.\n")
420
+ latex_story.append(NoEscape(r'A análise dos coeficientes do modelo de Regressão Logística, transformados em Odds Ratios, nos permite entender a influência de cada característica na probabilidade de Churn. Um Odds Ratio maior que 1 indica que o aumento daquela feature (ou pertencer àquela categoria) aumenta as chances de Churn, enquanto um valor menor que 1 diminui.\n\n'))
421
 
422
  if not self.coefficients_df.empty:
423
  markdown_story.append(self.coefficients_df.to_markdown(index=False) + "\n")
424
  latex_story.append(NoEscape(self.coefficients_df.to_latex(index=False, caption='Coeficientes e Odds Ratios das Variáveis', label='tab:coefficients', longtable=False)))
 
 
 
 
 
 
 
 
 
 
425
 
426
+ # Início das correções para `SyntaxError: f-string: single '}' is not allowed`
427
+ # Linha original de Precisão (equivalente a ~458)
428
+ precision_value = self.metrics_dict.get('Precisão', 0)
429
+ precision_text_latex = (
430
+ f'\item \textbf{{Precisão ({precision_value:.2f}\%):}} ' # Usando f-string para formatar valor e \%
431
+ + r'Das previsões de churn (\texttt{1}), quantos realmente foram churn. '
432
+ r'É importante para o banco não abordar clientes que não iriam dar churn (reduzir falsos positivos). '
433
+ f'Um valor de {precision_value:.2f}\% significa que das vezes que o modelo previu churn, essa porcentagem estava correta.'
434
+ )
435
+ latex_story.append(NoEscape(precision_text_latex + '\n'))
436
+
437
+ # Linha original de Recall (equivalente a ~464)
438
+ recall_value = self.metrics_dict.get('Recall (Sensibilidade)', 0)
439
+ recall_text_latex = (
440
+ f'\item \textbf{{Recall (Sensibilidade) ({recall_value:.2f}\%):}} ' # Usando f-string para formatar valor e \%
441
+ + r'Dos clientes que realmente deram churn (\texttt{1}), quantos o modelo identificou. '
442
+ r'É crucial para o banco identificar o máximo de clientes em risco (reduzir falsos negativos). '
443
+ f'Um valor de {recall_value:.2f}\% significa que essa porcentagem de clientes que de fato deram churn foi corretamente identificada pelo modelo.'
444
+ )
445
+ latex_story.append(NoEscape(recall_text_latex + '\n'))
446
+ # Fim das correções
 
 
 
 
 
 
447
 
448
  markdown_story.append(f"- **F1-Score ({self.metrics_dict.get('F1-Score', 0):.4f}):** É a média harmônica entre Precisão e Recall, útil quando há um desequilíbrio de classes e você precisa de um balanço entre identificar corretamente e não levantar falsos alarmes.\n")
449
+ latex_story.append(NoEscape(fr'\item \textbf{{F1-Score ({self.metrics_dict.get("F1-Score", 0):.4f})}}: É a média harmônica entre Precisão e Recall, útil quando há um desequilíbrio de classes e você precisa de um balanço entre identificar corretamente e não levantar falsos alarmes.' + '\n'))
450
 
451
+ latex_story.append(NoEscape(r'\end{itemize}' + '\n\n'))
452
  else:
453
+ markdown_story.append("Nenhum coeficiente disponível. O modelo pode não ter sido treinado ou não possui coeficientes acessíveis.\n")
454
+ latex_story.append(NoEscape(r'Nenhum dado de avaliação disponível. O modelo pode não ter sido treinado ou avaliado.\n\n'))
455
 
456
  # --- 7. Conclusão e Próximos Passos ---
457
  markdown_story.append("## 7. Conclusão e Próximos Passos")
458
  latex_story.append(Section(NoEscape(r'Conclusão e Próximos Passos'), False))
459
 
460
  markdown_story.append("O modelo de Regressão Logística provê uma base sólida para a previsão de churn. As variáveis identificadas como mais influentes (pelos Odds Ratios) devem ser o foco para o planejamento estratégico de retenção. Por exemplo, campanhas de marketing direcionadas a grupos de maior risco ou ofertas personalizadas podem ser desenvolvidas com base nas características que aumentam a probabilidade de churn.\nPara aprimoramento contínuo, sugere-se a exploração de outros modelos, engenharia de novas features, e reavaliação periódica do modelo com dados mais recentes.")
461
+ latex_story.append(NoEscape(r'O modelo de Regressão Logística provê uma base sólida para a previsão de churn. As variáveis identificadas como mais influentes ( pelos Odds Ratios) devem ser o foco para o planejamento estratégico de retenção. Por exemplo, campanhas de marketing direcionadas a grupos de maior risco ou ofertas personalizadas podem ser desenvolvidas com base nas características que aumentam a probabilidade de churn.\n\nPara aprimoramento contínuo, sugere-se a exploração de outros modelos, engenharia de novas features, e reavaliação periódica do modelo com dados mais recentes.'))
462
 
463
  return "\n".join(markdown_story), latex_story, self.plot_paths
464
 
 
500
  shutil.copy2(logo_filename, logo_target_path) # Copia o logo para o diretório temporário do LaTeX
501
  with doc.create(Figure(position='h!')) as logo_fig:
502
  # Referencia pelo nome do arquivo, pois está no mesmo diretório do .tex
503
+ logo_fig.add_image(os.path.basename(logo_target_path), width='0.25\\textwidth')
504
  logo_fig.add_caption(NoEscape(r'\vspace{-0.5cm}'))
505
  else:
506
  doc.append(Command('textbf', 'AVISO: Logo da UnB não encontrado! Certifique-se de que "MARCADOR.png" esteja na raiz do seu Hugging Face Space.'))