UNB logo

Programa de Pós-graduação em Computação Aplicada – PPCA (UnB)

Análise Estatística de Dados e Informações - Prova Final

Professor: João Gabriel de Moraes Souza
Aluno: Angelo Donizete Buso Júnior
In [1]:
import warnings
warnings.filterwarnings("ignore")

# Manipulação de Dados
import pandas as pd
from IPython.display import display
import numpy as np

# Visualização de Dados
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

# Estatística e Testes Estatísticos
import shap
from scipy import stats
from scipy.stats import kurtosis, skew, f_oneway, shapiro, levene, kruskal
import statsmodels.api as sm
from statsmodels.formula.api import ols
from statsmodels.stats.outliers_influence import variance_inflation_factor

# Pré-Processamento de Dados
from sklearn.model_selection import KFold, cross_validate
from sklearn.preprocessing import StandardScaler, LabelEncoder, MinMaxScaler, OneHotEncoder
from imblearn.under_sampling import RandomUnderSampler
from imblearn.over_sampling import SMOTE
from collections import Counter

# Algoritmos de Machine Learning
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.svm import SVC

# Treinamento e Validação de Modelos
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV, StratifiedKFold

# Avaliação de Modelos
from sklearn.metrics import (
    mean_squared_error, mean_absolute_error, r2_score,
    confusion_matrix, accuracy_score, precision_score, recall_score,
    classification_report, roc_auc_score, roc_curve, ConfusionMatrixDisplay
)
from scikitplot.metrics import plot_confusion_matrix, plot_roc

# Configurações Opcionais
pd.set_option('display.float_format', '{:.2f}'.format)
sns.set_theme(style="whitegrid")
plt.rc("figure", figsize=(10, 6))

# Verificação de Versões
print("Versão do Seaborn:", sns.__version__)
print("Versão do Pandas:", pd.__version__)
Versão do Seaborn: 0.13.2
Versão do Pandas: 2.2.3

Questão 4¶

1. Compreensão Problema de Negócio¶

O risco de crédito é uma das principais preocupações do setor bancário e financeiro. Ele se refere à probabilidade de um cliente não cumprir suas obrigações financeiras, resultando em inadimplência e potenciais perdas para as instituições financeiras. Para mitigar esse risco, bancos e outras entidades financeiras utilizam modelos preditivos que analisam o comportamento de clientes e identificam padrões que indicam a probabilidade de um indivíduo ser um bom ou mau pagador, ou no jargão financeiro, o cliente dar ou não default.

A importância da modelagem do risco de crédito se traduz em tomadas de decisão mais eficientes e baseadas em dados, ajudando a determinar limites de crédito adequados ao cliente, ainda suavizar as estratégias de recuperação de crédito. Além disso, a correta precificação do risco contribui para a sustentabilidade do mercado de crédito, evitando alta exposição à alavancagens financeiras (que podem levar a crises financeiras catastróficas) bem como, restrições muito rígidas ao crédito (que limitam a fluidez do mercado e desacelera o crescimento econômico).

Neste estudo, utilizaremos um conjunto de dados reais de risco de crédito para desenvolver modelos preditivos capazes de classificar clientes entre bons e maus pagadores. Exploraremos as principais variáveis que influenciam essa decisão, aplicaremos técnicas de modelagem estatística e aprendizado de máquina e analisaremos as implicações dos resultados para a gestão do risco de crédito.

Avaliar o risco de concessão de crédito a clientes de IF. Problemas do tipo classificação, faremos previsão de classes. Os dados possui uma serie de informação e gera como saída se Posso conceder crédito ou não. Problema de aprendizagem supervisionada.Modelo vai receber dados de entrada e saída, aprenderá os coeficientes que melhor explica a relação entre Var_target/Label e as Var_Explanatória. Com este modelo treinda será capaz de prever novas classificação de concessão de crédito.

1.1 Dicionário dados¶

Variável Tipo da Variável Descrição
checking_status Categórica Ordinal Situação da conta corrente do cliente. Indica o nível de saldo disponível na conta corrente. Geralmente, um saldo menor ou "sem conta" pode indicar maior risco.
duration Numérica Contínua Duração do crédito em meses. Tempo total que o cliente terá para pagar o empréstimo. Prazos mais longos podem aumentar o risco devido a incertezas futuras.
credit_history Categórica Ordinal Histórico de crédito do cliente. Avalia o comportamento de pagamento em créditos anteriores. Um histórico problemático (crédito em atraso, crítico) sinaliza maior risco.
purpose Categórica Nominal Propósito do crédito. Indica para que o cliente pretende usar o dinheiro do empréstimo. Alguns propósitos (ex: carro novo, casa) podem ser vistos como menos arriscados que outros (ex: negócios, "outros").
credit_amount Numérica Contínua Valor do crédito solicitado. Montante total do empréstimo. Valores muito altos em relação à renda do cliente podem aumentar o risco de inadimplência.
savings_status Categórica Ordinal Reserva financeira do cliente. Indica o nível de poupança ou reserva financeira. Mais poupança geralmente indica menor risco.
employment Categórica Ordinal Tempo de emprego atual. Indica a estabilidade profissional do cliente. Empregos mais longos geralmente indicam menor risco.
installment_commitment Numérica Discreta Taxa de comprometimento da parcela. Percentual da renda disponível do cliente que será destinada ao pagamento da parcela do crédito. Taxas muito altas (próximas de 4, que pode representar uma taxa alta em escala ordinal) indicam maior dificuldade de pagamento.
personal_status Categórica Nominal Estado civil e gênero. Pode influenciar o risco de crédito, embora o impacto seja menos direto e possa estar ligado a fatores socioeconômicos associados a cada categoria.
other_parties Categórica Nominal Outras partes envolvidas no crédito. Indica se há avalistas ou coobrigados).
residence_since Numérica Discreta Tempo de residência atual em anos. Estabilidade residencial pode indicar maior estabilidade geral do cliente, sugerindo menor risco.
property_magnitude Categórica Ordinal Bens móveis. Indica o tipo de propriedade que o cliente possui (se possui alguma). Propriedades de maior valor (ex: real estate - imóveis) podem ser vistas como garantia, reduzindo o risco para o credor.
age Numérica Contínua Idade do cliente em anos. A idade pode estar correlacionada com risco de crédito de maneiras complexas. Clientes muito jovens podem ter menos histórico, enquanto clientes mais velhos podem ter mais estabilidade.
other_payment_plans Categórica Nominal Outros planos de pagamento. Indica se o cliente possui outros compromissos financeiros (ex: outras dívidas, planos de pagamento parcelados). Muitos planos podem sobrecarregar o orçamento e aumentar o risco.
housing Categórica Nominal Tipo de moradia. Indica se o cliente é proprietário, aluga ou mora de favor. Ser proprietário pode indicar maior estabilidade financeira.
existing_credits Numérica Discreta Número de créditos existentes. Quantidade de outros créditos que o cliente já possui. Muitos créditos podem indicar alta alavancagem e maior risco.
job Categórica Ordinal Tipo de emprego. Indica a qualificação profissional e nível hierárquico do emprego. Empregos de maior qualificação (ex: "high qualif/self emp/mgmt") geralmente indicam maior estabilidade e menor risco.
num_dependents Numérica Discreta Número de dependentes. Quantidade de pessoas que dependem financeiramente do cliente. Mais dependentes podem aumentar a pressão sobre o orçamento familiar e potencialmente aumentar o risco.
own_telephone Categórica Binária Possui telefone registrado. Em datasets mais antigos, a posse de telefone poderia ser um indicador de estabilidade e acessibilidade do cliente. Hoje em dia, pode ter menos relevância.
foreign_worker Categórica Binária Trabalhador estrangeiro. Pode ser um fator de risco em alguns contextos (ex: instabilidade de residência, barreiras linguísticas/culturais em certos casos). No entanto, generalizações sobre risco baseadas em nacionalidade são problemáticas e podem ser discriminatórias.
class Categórica Binária Classe de risco de crédito (Variável Alvo). Indica se o cliente é considerado um "bom" ou "mau" pagador, com base em dados históricos. É a variável que o modelo de machine learning tentará prever.

2. Coleta de Dados¶

Para este projeto de machine learning, utilizaremos o conjunto de dados credit_risk_customers, disponível no Kaggle.

Este conjunto de dados classifica pessoas descritas por um conjunto de atributos como bons ou maus riscos de crédito. As variáveis presentes podem ser classificadas nos seguintes grupos:

  • Informações do Cliente: variáveis relacionadas ao perfil pessoal e histórico do cliente.

    • age → Idade do cliente
    • personal_status → Estado civil e status pessoal
    • job → Tipo de emprego
    • num_dependents → Número de dependentes
    • own_telephone → Possui telefone próprio (Sim/Não)
    • foreign_worker → Se é trabalhador estrangeiro (Sim/Não)
    • property_magnitude → Tipo de propriedade que o cliente possui (ex.: imóvel, carro, seguro de vida, nenhum)
    • housing → Tipo de moradia (ex.: próprio, alugado, gratuito)
    • residence_since → Tempo de residência no endereço atual
    • employment → Tempo de emprego atual
    • class → Indica se o cliente é "good" (bom pagador) ou "bad" (mau pagador) variável target
  • Informações sobre histórico financeiro: variáveis relacionadas ao histórico de crédito e situação financeira do cliente

    • checking_status → Status da conta corrente (ex.: saldo negativo, saldo entre faixas específicas, sem conta)
    • savings_status → Status da poupança (ex.: sem economia, valores entre faixas específicas)
    • credit_history → Histórico de crédito (ex.: pagamentos anteriores, inadimplência, novos créditos)
    • existing_credits → Quantidade de créditos existentes
    • other_payment_plans → Outros planos de pagamento existentes (ex.: banco, lojas, nenhum)
  • Informações sobre características do crédito tomado: variáveis que descrevem a solicitação de crédito feita pelo cliente.

    • credit_amount → Valor do crédito solicitado
    • duration → Duração do crédito solicitado (em meses)
    • installment_commitment → Percentual da renda comprometida com a parcela
    • purpose → Finalidade do crédito (ex.: carro, eletrodoméstico, negócio)
    • other_parties → Outros co-participantes na solicitação de crédito (ex.: cônjuge, avalista, nenhum)
    • days_in_waiting_list → Tempo que o cliente aguardou na fila para análise (se aplicável)
    • booking_changes → Alterações feitas na solicitação de crédito
  • Informações financeiras:

    • adr: Taxa média diária aplicada à reserva.
    • required_car_parking_spaces: Número de vagas de estacionamento requeridas.
    • total_of_special_requests: Número de pedidos especiais feitos pelo hóspede.

Essas informações nos permitirão identificar padrões e fatores que influenciam a probabilidade de default de um cliente, auxiliando na construção de um modelo preditivo eficaz.

Dicas:

  1. Observar as extensão do arquivo dataSet.
  2. Verificar os acessos aos dataSet (LGPD).
  3. "Timeframe" dos dados (desde quando tem-se os dados).
  4. Valores Missing (será substituido por default ou outro valor - média, por exemplo).
  5. Cabeçalho (header=T), caso False, irá indicar cabeçalho.
  6. Dados possuem algum comentário.
  7. Possui delimitador os dados.

2.1 Carga dados¶

In [184]:
dados = pd.read_csv('/home/buso/mestrado/aedi-ppca/dados/credit_customers.csv')
print('dados: (Linhas,Colunas)',dados.shape)
dados: (Linhas,Colunas) (1000, 21)

3. Análise Exploratória¶

3.1 Visão Geral dataSet¶

Nesta estapa estamos interessados em entender melhor a pergunta "Quais tipos de dados tem no nosso dataSet?"

  • Tipo Objeto que estamos trabalhando
  • Tipos Dados
  • Shape - Dimensionalidade
  • Índices
  • Descrição dos dados
In [185]:
def visaogeral(df, mensagem):
    print(f'{mensagem}:\n')
    print("Qtd Observações:", df.shape[0])
    print("\nQtd Atributos:", df.shape[1])
    print("\nAtributos:")
    print(df.columns.values)
    # print(df.columns.tolist())
    print("\nQtd Valores missing:", df.isnull().sum().values.sum())
    print("\nValores Unicos:")
    print(df.nunique().sort_values(ascending=True))
In [186]:
visaogeral(dados,'Visão Geral dataSet treino')
Visão Geral dataSet treino:

Qtd Observações: 1000

Qtd Atributos: 21

Atributos:
['checking_status' 'duration' 'credit_history' 'purpose' 'credit_amount'
 'savings_status' 'employment' 'installment_commitment' 'personal_status'
 'other_parties' 'residence_since' 'property_magnitude' 'age'
 'other_payment_plans' 'housing' 'existing_credits' 'job' 'num_dependents'
 'own_telephone' 'foreign_worker' 'class']

Qtd Valores missing: 0

Valores Unicos:
class                       2
own_telephone               2
num_dependents              2
foreign_worker              2
housing                     3
other_payment_plans         3
other_parties               3
job                         4
existing_credits            4
property_magnitude          4
checking_status             4
installment_commitment      4
personal_status             4
residence_since             4
employment                  5
savings_status              5
credit_history              5
purpose                    10
duration                   33
age                        53
credit_amount             921
dtype: int64

O conjunto de dados contém 1.000 observações e 21 atributos, sem valores ausentes (missing values). Os atributos representam diversas características que influenciam a classificação de clientes como bons ou maus pagadores de crédito. A seguir, categorizamos os atributos de acordo com sua natureza:

Atributos quantitativos: As features duration, age e credit_amount possuem valores numéricos, podendo ser contínuos ou discretos.

Atributos categóricos: As demais variáveis possuem natureza categórica, representando diferentes classificações e características qualitativas. Para utilizá-las adequadamente nos modelos de Machine Learning, será necessário aplicar técnicas de pré-processamento, como codificação categórica.

Nosso objetivo é avaliar se a estrutura atual dos dados reflete corretamente a informação essencial de cada variável. A correta combinação de atributos categóricos e quantitativos desempenha um papel crucial na construção de um modelo eficiente, garantindo que ele consiga capturar os principais fatores que influenciam a probabilidade de inadimplência (default) de um cliente.

In [187]:
dados.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 21 columns):
 #   Column                  Non-Null Count  Dtype  
---  ------                  --------------  -----  
 0   checking_status         1000 non-null   object 
 1   duration                1000 non-null   float64
 2   credit_history          1000 non-null   object 
 3   purpose                 1000 non-null   object 
 4   credit_amount           1000 non-null   float64
 5   savings_status          1000 non-null   object 
 6   employment              1000 non-null   object 
 7   installment_commitment  1000 non-null   float64
 8   personal_status         1000 non-null   object 
 9   other_parties           1000 non-null   object 
 10  residence_since         1000 non-null   float64
 11  property_magnitude      1000 non-null   object 
 12  age                     1000 non-null   float64
 13  other_payment_plans     1000 non-null   object 
 14  housing                 1000 non-null   object 
 15  existing_credits        1000 non-null   float64
 16  job                     1000 non-null   object 
 17  num_dependents          1000 non-null   float64
 18  own_telephone           1000 non-null   object 
 19  foreign_worker          1000 non-null   object 
 20  class                   1000 non-null   object 
dtypes: float64(7), object(14)
memory usage: 164.2+ KB

A análise exploratória realizada proporcionou uma compreensão inicial sobre a estrutura e qualidade do conjunto de dados. O dataset contém mil registros de clientes de uma instiruição financeira, descrevendo características e informações, para indicar se aquele vetor de informação deu default ou não.

A inspeção das variáveis revelou a presença de atributos categóricos, exigindo a aplicação de técnicas de codificação de variáveis, como One-Hot Encoding, para possibilitar sua utilização em modelos de Machine Learning baseados em algoritmos que requerem representações numéricas.

Outro aspecto positivo é que o dataset não apresenta valores ausentes nesta fase inicial. No entanto, é importante ressaltar que, durante o processo de engenharia de atributos (feature engineering) e transformação de variáveis, podem surgir valores nulos em decorrência de operações como discretização, normalização ou criação de novas variáveis.

Portanto, será essencial monitorar e tratar esses valores ausentes de maneira adequada para garantir a qualidade e integridade dos dados, assegurando um bom desempenho do modelo preditivo.

TRADE-OFFs da Ciência de Dados e o Dilema Viés-Variância

No desenvolvimento de modelos de Machine Learning, é fundamental equilibrar quantidade de dados, dimensionalidade e tempo de treinamento, garantindo um modelo eficiente e generalizável. Esse processo envolve decisões estratégicas para evitar problemas como underfitting e overfitting, além de considerar o dilema viés-variância.

Tamanho do Conjunto de Dados

Se o número de observações no conjunto de dados for muito grande, o treinamento pode ser computacionalmente custoso, exigindo técnicas como amostragem representativa, processamento distribuído (ex.: Spark, Dask) ou redução de dimensionalidade (ex.: PCA). Por outro lado, um número muito pequeno de registros pode levar ao underfitting, onde o modelo não consegue capturar padrões relevantes e apresenta baixa capacidade preditiva.

Dimensionalidade e Seleção de Features

Muitas colunas/atributos podem gerar problemas de alta dimensionalidade, dificultando a modelagem e aumentando o risco de overfitting, quando o modelo se ajusta excessivamente aos dados de treino e perde capacidade de generalização. Para mitigar esse problema, técnicas como seleção de variáveis (ex.: feature selection) e redução de dimensionalidade (ex.: PCA, Autoencoders) são recomendadas.

O Dilema Viés-Variância

O dilema viés-variância descreve o equilíbrio entre simplicidade e complexidade do modelo:

  • Viés alto (underfitting): O modelo é muito simples e não captura os padrões dos dados adequadamente, erra muito ou tudo.
  • Variância alta (overfitting): O modelo é muito complexo e se ajusta excessivamente aos dados de treino, resultando em baixa generalização.

A solução ideal envolve encontrar um meio-termo, onde o modelo aprende bem os padrões dos dados sem se tornar excessivamente específico.

Divisão de Dados e Validação

A prática recomendada é dividir o conjunto de dados em:

  • Treino (70%): Para ensinar o modelo.
  • Teste (30%): Para avaliar a performance em dados novos.
  • Validação cruzada: Para evitar dependência de uma única divisão dos dados e garantir que o modelo tenha boa generalização.

E então?

Não existe uma solução única para esses desafios, e cada problema exige um equilíbrio entre quantidade de dados, dimensionalidade, tempo de treinamento e capacidade de generalização. Estratégias como seleção de variáveis, validação cruzada e ajuste de hiperparâmetros são essenciais para otimizar o modelo e garantir desempenho robusto e confiável.

In [188]:
# Visualizando as primeiras linhas
dados.head()
Out[188]:
checking_status duration credit_history purpose credit_amount savings_status employment installment_commitment personal_status other_parties ... property_magnitude age other_payment_plans housing existing_credits job num_dependents own_telephone foreign_worker class
0 <0 6.00 critical/other existing credit radio/tv 1169.00 no known savings >=7 4.00 male single none ... real estate 67.00 none own 2.00 skilled 1.00 yes yes good
1 0<=X<200 48.00 existing paid radio/tv 5951.00 <100 1<=X<4 2.00 female div/dep/mar none ... real estate 22.00 none own 1.00 skilled 1.00 none yes bad
2 no checking 12.00 critical/other existing credit education 2096.00 <100 4<=X<7 2.00 male single none ... real estate 49.00 none own 1.00 unskilled resident 2.00 none yes good
3 <0 42.00 existing paid furniture/equipment 7882.00 <100 4<=X<7 2.00 male single guarantor ... life insurance 45.00 none for free 1.00 skilled 2.00 none yes good
4 <0 24.00 delayed previously new car 4870.00 <100 1<=X<4 3.00 male single none ... no known property 53.00 none for free 2.00 skilled 2.00 none yes bad

5 rows × 21 columns

In [189]:
# Observando os dados finais do dataSet
dados.tail()
Out[189]:
checking_status duration credit_history purpose credit_amount savings_status employment installment_commitment personal_status other_parties ... property_magnitude age other_payment_plans housing existing_credits job num_dependents own_telephone foreign_worker class
995 no checking 12.00 existing paid furniture/equipment 1736.00 <100 4<=X<7 3.00 female div/dep/mar none ... real estate 31.00 none own 1.00 unskilled resident 1.00 none yes good
996 <0 30.00 existing paid used car 3857.00 <100 1<=X<4 4.00 male div/sep none ... life insurance 40.00 none own 1.00 high qualif/self emp/mgmt 1.00 yes yes good
997 no checking 12.00 existing paid radio/tv 804.00 <100 >=7 4.00 male single none ... car 38.00 none own 1.00 skilled 1.00 none yes good
998 <0 45.00 existing paid radio/tv 1845.00 <100 1<=X<4 4.00 male single none ... no known property 23.00 none for free 1.00 skilled 1.00 yes yes bad
999 0<=X<200 45.00 critical/other existing credit used car 4576.00 100<=X<500 unemployed 3.00 male single none ... car 27.00 none own 1.00 skilled 1.00 none yes good

5 rows × 21 columns

Relembrando o shape dos dados:

In [190]:
print('Os dados possuem {:.2f} linhas e {} colunas'.format(dados.shape[0], dados.shape[1]))
Os dados possuem 1000.00 linhas e 21 colunas
In [191]:
# checando índice (endereço de cada obs) e sua distribuição
dados.index
Out[191]:
RangeIndex(start=0, stop=1000, step=1)
In [192]:
# checando os nomes das colunas
dados.columns
Out[192]:
Index(['checking_status', 'duration', 'credit_history', 'purpose',
       'credit_amount', 'savings_status', 'employment',
       'installment_commitment', 'personal_status', 'other_parties',
       'residence_since', 'property_magnitude', 'age', 'other_payment_plans',
       'housing', 'existing_credits', 'job', 'num_dependents', 'own_telephone',
       'foreign_worker', 'class'],
      dtype='object')

3.1.1 Variáveis Explicatórias e Variável Alvo¶

Para a construção do modelo preditivo, é fundamental separar as variáveis do conjunto de dados em dois grupos distintos:

  • Variável Alvo (y): Representa o resultado que desejamos prever. No contexto deste estudo, a variável alvo é a class, que indica se o cliente é um bom pagador (good) ou mal pagador (bad).

  • Variáveis Explicatórias (X): São as demais variáveis que contêm informações relevantes, que explicam, ou ajuda a explicar, o comportamento que queromos analisar. Elas serão utilizadas como fatores preditivos para estimar a probabilidade do default. Antes do treinamento, é comum excluir variáveis que não apresentarem valor preditivo.

A separação correta entre X e y é essencial para garantir que o modelo aprenda a partir de variáveis relevantes e seja capaz de realizar previsões precisas sobre a saída ou permanência dos clientes.

In [193]:
dados.head()
Out[193]:
checking_status duration credit_history purpose credit_amount savings_status employment installment_commitment personal_status other_parties ... property_magnitude age other_payment_plans housing existing_credits job num_dependents own_telephone foreign_worker class
0 <0 6.00 critical/other existing credit radio/tv 1169.00 no known savings >=7 4.00 male single none ... real estate 67.00 none own 2.00 skilled 1.00 yes yes good
1 0<=X<200 48.00 existing paid radio/tv 5951.00 <100 1<=X<4 2.00 female div/dep/mar none ... real estate 22.00 none own 1.00 skilled 1.00 none yes bad
2 no checking 12.00 critical/other existing credit education 2096.00 <100 4<=X<7 2.00 male single none ... real estate 49.00 none own 1.00 unskilled resident 2.00 none yes good
3 <0 42.00 existing paid furniture/equipment 7882.00 <100 4<=X<7 2.00 male single guarantor ... life insurance 45.00 none for free 1.00 skilled 2.00 none yes good
4 <0 24.00 delayed previously new car 4870.00 <100 1<=X<4 3.00 male single none ... no known property 53.00 none for free 2.00 skilled 2.00 none yes bad

5 rows × 21 columns

In [194]:
X= dados.drop(columns = 'class', axis=1)
Y= pd.DataFrame(dados['class'])
print('DataSet original com {} atributos e {} observações'.format(dados.shape[1], dados.shape[0]))
print('As variáveis independentes com {} atributos e {} observações'.format(X.shape[1], X.shape[0]))
print('A variável dependente - iremos prever com {} atributo e {} observações'.format(Y.shape[1], Y.shape[0]))
DataSet original com 21 atributos e 1000 observações
As variáveis independentes com 20 atributos e 1000 observações
A variável dependente - iremos prever com 1 atributo e 1000 observações
In [195]:
X.shape
Out[195]:
(1000, 20)
In [196]:
Y
Out[196]:
class
0 good
1 bad
2 good
3 good
4 bad
... ...
995 good
996 good
997 good
998 bad
999 good

1000 rows × 1 columns

3.1.2 Variável Alvo¶

A variável alvo deste estudo é o atributo class, que indica se o cliente deu default ou não na instituição financeiro (IF). Essa variável é categórica binária e representa o resultado que o modelo de machine learning deve prever. Seus valores são definidos da seguinte forma:

  • good → O cliente é um bom pagador.
  • bad → O cliente nã é um bom pagador.

Observerve que o valor desse atributo está no formato textual, jpa indicando que teremos que realizar pré-processamento antes de modelar!

3.1.2.1 Amplitude¶
In [197]:
# por ser classe max=1 min=0 (Classificação)
Y.describe()
Out[197]:
class
count 1000
unique 2
top good
freq 700
In [198]:
Y.value_counts()
Out[198]:
class
good     700
bad      300
Name: count, dtype: int64

Observando os valores únicos da variável class, confirmamos que se trata de uma variável categórica binária, com duas categorias distintas:

  • good → O cliente é um bom pagador.
  • bad → O cliente não é um bom pagador.

Dessa forma, estamos diante de um problema de aprendizado supervisionado do tipo classificação, no qual o objetivo é prever a probabilidade de um cliente deixar ou não a instituição financeira.

Para essa tarefa, utilizaremos um modelo classificador, que será treinado para identificar padrões de comportamento associados nosso problema.

3.1.2.2 Balanceamento dos Dados¶

Em problemas de classificação, é frequente encontrar conjuntos de dados com classes desbalanceadas, onde uma das classes apresenta um número de registros significativamente maior do que a outra. Esse desequilíbrio pode comprometer o desempenho do modelo, tornando-o tendencioso para a classe majoritária e dificultando a correta identificação da classe minoritária.

O balanceamento das classes é um dos pressupostos fundamentais a serem considerados na construção de modelos de machine learning. Caso não seja tratado adequadamente, o modelo pode apresentar baixa capacidade de prever eventos menos frequentes. Por isso, é essencial adotar estratégias para equilibrar as classes durante o pré-processamento, garantindo previsões mais precisas e confiáveis.

In [199]:
dados['class'].value_counts()   
Out[199]:
class
good    700
bad     300
Name: count, dtype: int64
In [200]:
balData = pd.DataFrame(dados['class'].value_counts())
balData.rename(columns={'count':'quantidade'}, inplace=True)
In [201]:
balData['% total'] = round(100*balData['quantidade']/dados.shape[0], 2)
print(balData)
print("\nObserva-se que {:.1f}% do dataSet não deram default".format((dados.groupby('class').size()[1] / dados.shape[0])*100))
print("Enquanto que {:.1f}% dos dados deram default a IF.".format((dados.groupby('class').size()[0] / dados.shape[0])*100))
       quantidade  % total
class                     
good          700    70.00
bad           300    30.00

Observa-se que 70.0% do dataSet não deram default
Enquanto que 30.0% dos dados deram default a IF.

No presente estudo, observamos que a variável alvo, class, apresenta um desbalanceamento leve. Embora exista uma diferença na proporção entre as classes "bom pagador" e "mau pagador", a princípio, não se faz necessária a aplicação imediata de técnicas de balanceamento. A prioridade será avaliar o impacto deste desbalanceamento no desempenho dos modelos de machine learning antes de implementar qualquer ajuste.

Caso seja confirmado um impacto negativo significativo, as principais técnicas de balanceamento de classes a serem consideradas incluem:

Oversampling: Esta técnica visa equilibrar as classes através do aumento da representatividade da classe minoritária. Isso pode ser feito replicando registros existentes da classe minoritária ou, de forma mais sofisticada, gerando novas amostras sintéticas. Uma das abordagens mais populares de oversampling sintético é o SMOTE (Synthetic Minority Over-sampling Technique). O SMOTE cria novas instâncias minoritárias através da interpolação entre exemplos minoritários vizinhos, expandindo o espaço de características da classe minoritária de maneira inteligente.

Undersampling: Em contraste com o oversampling, o undersampling busca o equilíbrio reduzindo a representatividade da classe majoritária. Isso é alcançado através da eliminação aleatória ou criteriosa de registros da classe majoritária. Embora o undersampling possa acelerar o processo de treinamento do modelo, ele apresenta o risco de descartar informações potencialmente relevantes contidas nos registros da classe majoritária que foram removidos.

A decisão final sobre a necessidade e qual técnica de balanceamento aplicar será estritamente baseada em evidências empíricas. Se a avaliação do modelo indicar um viés excessivo em favor da classe majoritária, manifestado por uma performance insatisfatória na identificação de maus pagadores, as técnicas de balanceamento serão reavaliadas e aplicadas para mitigar este viés e otimizar a capacidade preditiva do modelo para ambas as classes.

3.1.2.3 Plots Variável Alvo¶
In [202]:
sns.set_theme(style='darkgrid')
sns.set_palette("hls", 3)
In [203]:
print(balData)
default_plot = sns.countplot(data=dados, x='class', order=dados['class'].value_counts().index, hue= 'class', palette='coolwarm')

plt.title('Distribuição das classes')
plt.ylabel('Quantidade')
plt.xlabel('Classificação do cliente')

plt.tight_layout()
plt.show()
       quantidade  % total
class                     
good          700    70.00
bad           300    30.00
No description has been provided for this image
3.1.2.4 Relações com a variável alvo¶
In [204]:
dados.columns   
Out[204]:
Index(['checking_status', 'duration', 'credit_history', 'purpose',
       'credit_amount', 'savings_status', 'employment',
       'installment_commitment', 'personal_status', 'other_parties',
       'residence_since', 'property_magnitude', 'age', 'other_payment_plans',
       'housing', 'existing_credits', 'job', 'num_dependents', 'own_telephone',
       'foreign_worker', 'class'],
      dtype='object')
In [205]:
dados.groupby(['class'])['age'].agg([np.mean,np.std])
Out[205]:
mean std
class
bad 33.96 11.22
good 36.22 11.38
In [206]:
dados.pivot_table(['age', 'credit_amount', 'duration'],
               ['class'], aggfunc='mean')
Out[206]:
age credit_amount duration
class
bad 33.96 3938.13 24.86
good 36.22 2985.46 19.21

3.1.3 Preditores Categóricos¶

Durante a análise exploratória, identificamos que o conjunto de dados contém variáveis categóricas que são aquelas que representam categorias seja elas nominais ou ordinais. Elas auxiliam para mapear o entender o evento que estamos analisando.

In [207]:
x_categoricos =  dados.select_dtypes(include=['object'])
x_categoricos['class'] = dados['class']
x_categoricos.head()
Out[207]:
checking_status credit_history purpose savings_status employment personal_status other_parties property_magnitude other_payment_plans housing job own_telephone foreign_worker class
0 <0 critical/other existing credit radio/tv no known savings >=7 male single none real estate none own skilled yes yes good
1 0<=X<200 existing paid radio/tv <100 1<=X<4 female div/dep/mar none real estate none own skilled none yes bad
2 no checking critical/other existing credit education <100 4<=X<7 male single none real estate none own unskilled resident none yes good
3 <0 existing paid furniture/equipment <100 4<=X<7 male single guarantor life insurance none for free skilled none yes good
4 <0 delayed previously new car <100 1<=X<4 male single none no known property none for free skilled none yes bad
In [208]:
fig, axes = plt.subplots(6, 2, figsize=(40, 40))
axes = axes.flatten()
for i, var in enumerate(list(x_categoricos.columns[:-3])):
    sns.histplot(dados, x=var, hue="class", multiple="stack", shrink=0.8, ax=axes[i], palette='coolwarm')
    #sns.countplot(data=dados, x=var, order=dados[var].value_counts().index, hue='class', ax=axes[i], palette= 'coolwarm')
    axes[i].set_title(f"Distribuição de {var}")
    axes[i].tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.show()
No description has been provided for this image
In [209]:
plt.figure(figsize=(10, 6))
sns.countplot(data=dados, x='checking_status', hue='class', palette="coolwarm", order=['<0', '0<=X<200', '>=200', 'no checking'])

plt.title('Distribuição de default por saldo da conta corrente')
plt.xlabel('Situação da conta corrente')
plt.ylabel('Quantidade de clientes')
plt.xticks(rotation=45, ha='right')
plt.legend(title='Perfil de default', labels=['Bom', 'Mau'])

plt.tight_layout()
plt.show()
No description has been provided for this image
In [210]:
dados.groupby(['class', 'checking_status']).size()
Out[210]:
class  checking_status
bad    0<=X<200           105
       <0                 135
       >=200               14
       no checking         46
good   0<=X<200           164
       <0                 139
       >=200               49
       no checking        348
dtype: int64
  1. Clientes com saldo menor que zero (<0): A distribuição entre bons e maus pagadores está mais equilibrada. Isso sugere que clientes com saldo negativo possuem um risco mais elevado de inadimplência, já que há uma presença relativamente alta de maus pagadores.

  2. Clientes com saldo entre 0 e 200 (0<=X<200): Essa categoria tem um número considerável de clientes bons pagadores, porém também há uma quantidade expressiva de maus pagadores. Isso sugere que possuir um saldo positivo baixo pode não ser suficiente para mitigar o risco de crédito.

  3. Clientes com saldo superior a 200 (>=200): Esse grupo tem a menor quantidade de clientes, mas a maior proporção de bons pagadores. Isto sugere que um saldo da conta corrente considerado alto está fortemente correlacionado com um menor risco de crédito.

  4. Clientes sem verificação de saldo (no checking): Essa é a categoria com a maior quantidade de clientes. Observa-se que a proporção de bons pagadores é significativamente maior do que a de maus pagadores. Há uma proporção de maus pagadores, mostrando importante entender por que esses clientes não conseguiu-se realizar essa verificação ou se é ausência de conta corrente.

In [211]:
plt.figure(figsize=(12, 6))
sns.countplot(data=dados, x='credit_history', hue='class', palette="pastel", order=['critical/other existing credit', 'delayed previously', 'existing paid', 'no credits/all paid', 'all paid'])

plt.title('Distribuição de default por histórico de crédito')
plt.xlabel('Histórico de crédito')
plt.ylabel('Quantidade de clientes')
plt.xticks(rotation=45, ha='right')
plt.legend(title='Perfil de default', labels=['Bom', 'Mau'])
plt.tight_layout()
plt.show()
No description has been provided for this image
In [212]:
dados.groupby(['class', 'credit_history']).size()
Out[212]:
class  credit_history                
bad    all paid                           28
       critical/other existing credit     50
       delayed previously                 28
       existing paid                     169
       no credits/all paid                25
good   all paid                           21
       critical/other existing credit    243
       delayed previously                 60
       existing paid                     361
       no credits/all paid                15
dtype: int64
  1. Clientes com histórico de crédito "existing paid" (créditos existentes pagos)
  • Essa é a maior categoria no conjunto de dados.
  • A maior parte dos clientes pertence à classe bom pagador, mas há um número significativo de clientes que entraram em default.
  • Isso indica que ter um histórico de pagamentos regulares reduz, mas não elimina completamente o risco de inadimplência.
  1. Clientes com histórico "critical/other existing credit" (crédito crítico ou outras dívidas existentes)
  • Esse grupo tem uma quantidade expressiva de clientes bons pagadores, mas a presença de maus pagadores também é importante observar. Embora haja mais clientes "Bons" do que "Maus" nesta categoria, a proporção de "Maus" é a segunda maior da série dos cliente q deram default.
  1. Clientes que tiveram atrasos anteriormente ("delayed previously")
  • Esse grupo a barra azul ("Bom") é novamente maior, mas a barra laranja ("Mau") ainda é visível e representa uma parcela notável.
  • Isso sugere que atrasos anteriores podem ser um forte indicativo de maior risco de crédito.
  1. Clientes sem histórico de crédito ou com todos os créditos quitados (no credits/all paid e all paid)
  • O número de clientes nessas categorias é pequeno.
  • Chama a atenção que nessas duas categorias se sobressaiu clientes que deram default.

Sendo assim, reforça a importância crítica do histórico de crédito passado como um preditor robusto de risco de crédito futuro. A análise detalhada das categorias permite entender nuances e tomar decisões mais precisas na gestão de risco.

In [213]:
plt.figure(figsize=(10, 6))
sns.countplot(data=dados, x='savings_status', hue='class', palette="pastel", order=['no known savings', '<100', '100<=X<500', '500<=X<1000', '>=1000'])

plt.title('Distribuição de default por reservas financeiras')
plt.xlabel('Situação da reserva')
plt.ylabel('Quantidade de clientes')
plt.xticks(rotation=45, ha='right')
plt.legend(title='Perfil de default', labels=['Bom', 'Mau'])

plt.tight_layout()
plt.show()
No description has been provided for this image

Clientes com reservas financeiras inferiores a 100 (<100): Representam a maior parte da base de dados. A proporção de maus pagadores é consideravelmente maior nesse grupo em comparação com os demais. Isso sugere que clientes com pouca ou nenhuma reserva financeira possuem maior risco de inadimplência.

Clientes sem informações sobre poupança (no known savings): Esse grupo tem um número significativo de bons pagadores, em comparação a outra classe. A ausência de informações sobre reservas financeiras pode dificultar a avaliação de risco, sendo um fator de incerteza para concessão de crédito.

Clientes com reservas entre 100 e 500 (100<=X<500) e entre 500 e 1000 (500<=X<1000) Há uma redução significativa no número de maus pagadores conforme o valor das reservas financeiras aumenta. Isso indica que pessoas com mais reservas financeiras tendem a ter menor risco de crédito.

Clientes com mais de 1000 (>=1000) Esse grupo apresenta o menor número de maus pagadores. Quanto maior a reserva financeira, menor o risco de inadimplência, o que reforça a importância dessa variável na análise de risco.

Um gráfico de densidade é muito útil para verificar como se comporta as distribuições de cada variável, segregada por classe, afim de detectar possíveis anomalias, bem como qual atributo possuem distribuições que diferem uma da outra, permitindo identificar as classes positivas (fraudes, nesse caso) mais facilmente. O que se procura nesse tipo de visualização são distribuições que diferem uma da outra, permitindo identificar classes positivas mais facilmente.

3.1.4 Preditores Numéricos¶

As variáveis numéricas desempenham um papel crucial na construção de modelos preditivos, pois capturam informações quantitativas que podem influenciar diretamente a probabilidade de cancelamento.

In [214]:
x_numericos = dados.select_dtypes(include=['int64', 'float64'])
x_numericos['class'] = dados['class']
x_numericos.head()
Out[214]:
duration credit_amount installment_commitment residence_since age existing_credits num_dependents class
0 6.00 1169.00 4.00 4.00 67.00 2.00 1.00 good
1 48.00 5951.00 2.00 2.00 22.00 1.00 1.00 bad
2 12.00 2096.00 2.00 3.00 49.00 1.00 2.00 good
3 42.00 7882.00 2.00 4.00 45.00 1.00 2.00 good
4 24.00 4870.00 3.00 4.00 53.00 2.00 2.00 bad
In [215]:
x_numericos.nunique().sort_values()
Out[215]:
num_dependents              2
class                       2
installment_commitment      4
residence_since             4
existing_credits            4
duration                   33
age                        53
credit_amount             921
dtype: int64
In [216]:
x_numericos[['num_dependents', 'installment_commitment', 'residence_since', 'existing_credits']].head()
Out[216]:
num_dependents installment_commitment residence_since existing_credits
0 1.00 4.00 4.00 2.00
1 1.00 2.00 2.00 1.00
2 2.00 2.00 3.00 1.00
3 2.00 2.00 4.00 1.00
4 2.00 3.00 4.00 2.00

Com base nos valores distintos, as features num_dependents, installment_commitment, residence_since e existing_credits possuem indicação de serem categóricas discretas. Na fase de pré-pocessamento iremos mudar para o tipo inteiro.

In [217]:
x_numericos = x_numericos[['duration', 'credit_amount', 'age', 'class']]
x_numericos.head()
Out[217]:
duration credit_amount age class
0 6.00 1169.00 67.00 good
1 48.00 5951.00 22.00 bad
2 12.00 2096.00 49.00 good
3 42.00 7882.00 45.00 good
4 24.00 4870.00 53.00 bad
In [218]:
x_numericos[['duration', 'credit_amount', 'age']].hist(layout=(3,3))
plt.show()
No description has been provided for this image
In [219]:
cores_classes = {'good': 'lightblue', 'bad': 'salmon'}
for var in x_numericos:
    plt.figure(figsize=(10, 5))
    sns.histplot(data=x_numericos, x=var, hue='class', element="step", fill=True,
                 palette=cores_classes, kde=True)
    
    plt.title(f'Histograma de {var} por Risco de Crédito')
    plt.xlabel(var.capitalize())
    plt.ylabel('Frequência')
    plt.legend(title='Risco de Crédito', labels=['Bom', 'Mau'])
    bom_patch = mpatches.Patch(color=cores_classes['good'], label='Bom')
    mau_patch = mpatches.Patch(color=cores_classes['bad'], label='Mau')
    plt.legend(handles=[bom_patch, mau_patch], title='Risco de Crédito')
    
    plt.tight_layout()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [220]:
for var in x_numericos:
    plt.figure(figsize=(8, 6))
    sns.violinplot(data=dados, x='class', y=var, palette="coolwarm", order=['good', 'bad'])
    plt.title(f'Violin Plot de {var} por Risco de Crédito')
    plt.xlabel('Risco de Crédito')
    plt.ylabel(var.capitalize())
    plt.xticks(ticks=[0, 1], labels=['Bom', 'Mau'])
    plt.tight_layout()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
  1. A variável duration (duração do crédito - em meses) apresenta uma distribuição assimétrica à direita, com maior concentração de valores entre 6 e 24 meses. Observa-se que clientes com bom histórico de crédito (em azul) estão mais distribuídos ao longo do eixo, mas há uma concentração em valores menores de duração. Já os clientes com maior risco de inadimplência (em laranja) também apresentam concentração nos períodos mais curtos, mas há uma leve tendência de ocorrência para valores um pouco mais altos de duração do crédito. O histograma sugere que a relação entre duration e class não é simplesmente linear. Durações curtas/médias são comuns para ambos os grupos: Tanto "bons" quanto "maus" pagadores solicitam créditos com durações curtas e médias. Nota-se também que durações longas podem aumentar o risco, a cauda mais longa da distribuição "Mau" em durações elevadas sugere que prazos de crédito muito longos podem estar associados a um risco ligeiramente maior.

  2. A variável credit_amount (valor do crédito) tem distribuição assimétrica à direita, com grande concentração de valores abaixo de 5.000. Clientes bons pagadores (em azul) têm maior distribuição e predominância para valores mais baixos, mas ainda apresentam algumas ocorrências em valores elevados. Já os maus pagadores (em laranja) também estão mais concentrados nos créditos de menor valor, mas possuem uma distribuição um pouco mais espalhada em relação ao eixo dos valores. Isso sugere que valores de crédito mais baixos estão associados a uma menor probabilidade de inadimplência, enquanto créditos elevados precisam de maior análise, pois apresentam tanto bons quanto maus pagadores.

  3. A variável age (idade) apresenta uma distribuição aproximadamente normal, com uma leve assimetria à direita. A maioria dos clientes está na faixa entre 20 e 50 anos. Clientes bons pagadores (em azul) são mais numerosos nas faixas acima de 30 anos, enquanto clientes maus pagadores (em laranja) têm maior concentração entre 20 e 35 anos. Isso pode indicar que clientes mais jovens possuem maior risco de inadimplência, possivelmente devido à menor estabilidade financeira ou experiência de crédito, enquanto clientes mais velhos tendem a ter um histórico de crédito mais sólido e menor risco.

3.1.4.1 Distribuição dos Dados – Assimetria (Skewness) e Curtose (Kurtosis)¶

Nesta etapa, será analisada a distribuição dos dados de cada variável, com foco em duas medidas estatísticas importantes: assimetria (skewness) e curtose (kurtosis).

  • Assimetria (Skewness): Indica o grau de simetria da distribuição dos dados em relação à média.

    • Uma distribuição simétrica tem skew próximo de 0.
    • Assimetria positiva (skew > 0) indica concentração de valores à esquerda, com cauda alongada à direita.
    • Assimetria negativa (skew < 0) indica concentração à direita, com cauda alongada à esquerda.
  • Curtose (Kurtosis): Mede o grau de achatamento ou picos da distribuição em comparação com a distribuição normal.

    • Curtose próxima de 0 indica uma distribuição semelhante à normal.
    • Curtose positiva indica uma distribuição com picos acentuados e caudas mais longas (leptocúrtica).
    • Curtose negativa indica uma distribuição mais achatada (platicúrtica).

Muitos algoritmos de Machine Learning pressupõem que as variáveis possuem uma distribuição normal (gaussiana). Avaliar a assimetria e a curtose permite aplicar técnicas de pré-processamento adequadas (como transformação logarítmica, normalização ou padronização) para ajustar a distribuição dos dados. Esse ajuste pode melhorar significativamente a performance e a acurácia do modelo preditivo.

In [221]:
x_numericos = x_numericos[['duration', 'credit_amount', 'age']]
x_numericos.skew()
Out[221]:
duration        1.09
credit_amount   1.95
age             1.02
dtype: float64
In [222]:
x_numericos.kurt()
Out[222]:
duration        0.92
credit_amount   4.29
age             0.60
dtype: float64

De forma geral, os preditores numéricos apresentam assimetria positiva, donforme já demonstrado de forma visual pelos gráficos. Sugere que pode ser necessário aplicar transformações logaritmica, por exemplo, para reduzir essa assimetria antes do uso em um modelo de aprendizado de máquina.

Buscaremos agora compreender a força e direção de uma relação linear entre a variável independente e dependente.

In [223]:
x_numericos.corr(method = 'pearson')
Out[223]:
duration credit_amount age
duration 1.00 0.62 -0.04
credit_amount 0.62 1.00 0.03
age -0.04 0.03 1.00
In [224]:
correlations = x_numericos.corr(method = 'pearson')

fig = plt.figure()
ax = fig.add_subplot()
cax = ax.matshow(correlations, vmin = -1, vmax = 1, cmap='coolwarm')
fig.colorbar(cax)

ticks = np.arange(len(correlations))
ax.set_xticks(ticks)
ax.set_yticks(ticks)
ax.set_xticklabels(correlations.columns, fontsize=20)
ax.set_yticklabels(correlations.columns, fontsize=20)
plt.setp(ax.get_xticklabels(), rotation=45, ha="left",
         rotation_mode="anchor")

plt.tight_layout()
plt.show()
No description has been provided for this image
3.1.4.2 Identificação de Outliers¶

A análise de outliers é uma etapa essencial no processo de pré-processamento de dados, pois valores extremos podem distorcer estatísticas descritivas e impactar negativamente o desempenho dos modelos de Machine Learning.

Para identificar esses pontos fora do padrão, utilizaremos boxplots, que oferecem uma representação visual da distribuição de cada variável. A interpretação do boxplot inclui:

  • A linha central da caixa representa a mediana (50º percentil).
  • A borda inferior da caixa indica o primeiro quartil (Q1 - 25%).
  • A borda superior da caixa indica o terceiro quartil (Q3 - 75%).
  • As extensões se estendem até o menor e maior valor dentro de 1,5 vezes o intervalo interquartílico (IQR).
  • Pontos fora do boxplot são considerados outliers.

Identificar outliers permite compreender melhor a dispersão dos dados e avaliar a necessidade de tratamento adequado, como remoção ou transformação desses valores. Essa análise contribui para melhorar a qualidade dos dados e aumentar a robustez do modelo preditivo.

In [225]:
# Configurando o tamanho da figura e o layout dos subplots
num_cols = 5  # Número de colunas de subplots
num_rows = len(x_numericos.columns) // num_cols + (len(x_numericos.columns) % num_cols > 0)
fig, axes = plt.subplots(num_rows, num_cols, figsize=(20, 4 * num_rows))

# Flatten the axes array for easy iteration
axes = axes.flatten()

# Loop para criar um boxplot para cada coluna numérica
for i, col in enumerate(x_numericos.columns):
    sns.boxplot(y=dados[col], ax=axes[i], palette='coolwarm')
    axes[i].set_title(col)

# Remover subplots vazios
for j in range(i + 1, len(axes)):
    fig.delaxes(axes[j])
No description has been provided for this image

Considerando a definição clássica de outliers — valores que excedem 1,5 vezes o intervalo interquartílico (IQR) —, identificamos que as variáveis numéricas apresentam valores extremos. No entanto, antes de decidir por sua remoção ou transformação, é essencial analisar o contexto dos dados para garantir que essas observações não representem simplesmente características naturais do fenômeno estudado.

Entre as variáveis analisadas, destacamos:

duration: Apresenta alguns valores extremos acima de 40 meses. Embora esses prazos sejam atípicos, eles podem representar financiamentos de longo prazo, comuns em determinadas linhas de crédito. Excluir essas observações sem uma análise detalhada pode resultar na perda de informações valiosas sobre clientes que optam por prazos mais longos.

credit_amount: Possui a maior quantidade de outliers visíveis. Essa dispersão sugere que existem clientes com altos montantes financiados, que podem estar associados a diferentes perfis de risco. Podemos elencar ainda finalidades específicas, como financiamento de grandes projetos, aquisição imóveis, que tendem a ser para o longo prazo. Dependendo do objetivo da análise, pode ser interessante realizar uma transformação logarítmica para reduzir a influência desses valores extremos no modelo.

age: Apesar de apresentar alguns outliers acima dos 70 anos, a distribuição parece relativamente normal. Esses valores podem representar clientes mais velhos, que ainda possuem acesso ao crédito e podem ter um comportamento de pagamento distinto dos clientes mais jovens. Assim, a manutenção dessas observações é recomendada para capturar corretamente a heterogeneidade etária.

3.1.4.3 Avaliando a Multicolinearidade¶

Uma das abordagens mais eficientes para detectar associações multicolineares e possíveis problemas numéricos na inversão de matrizes é a análise de autovetores e autovalores. Em termos simples, autovetores são combinações lineares das variáveis originais que capturam a variância compartilhada entre elas, enquanto os autovalores indicam a magnitude dessa variância.

A decomposição espectral da matriz de correlação pode ser realizada utilizando a função numpy.linalg.eig, que retorna:

Autovalores: Representam a quantidade de variância acumulada em cada novo eixo (componente);

Autovetores: Uma matriz que indica como as novas variáveis se relacionam com as variáveis originais.

A multicolinearidade detectada pelo autovalor/autovetor indica que existe uma combinação linear entre um subconjunto das variáveis

In [226]:
# a) correlação
corr = np.corrcoef(x_numericos, rowvar = 0)
# b) técnicas
eigenvalues, eigenvectors = np.linalg.eig(corr)

Após extrair os autovalores, ordenamos em ordem decrescente e verificamos:

  1. Valores muito próximos de zero → Indicam colinearidade extrema, podendo causar problemas na inversão de matrizes e afetar modelos de regressão.

  2. Valores relativamente pequenos → Representam uma elevada, mas não crítica, multicolinearidade, podendo impactar a estabilidade dos coeficientes do modelo.

In [227]:
print (eigenvalues)
[0.37124585 1.62499358 1.00376056]

O autovalor mais baixo é 0,371, o que sugere uma possível multicolinearidade significativa, mas não extrema.

Para identificar quais variáveis estão envolvidas na colinearidade, analisamos os autovetores correspondentes às posições dos autovalores baixos.

Em termos absolutos, focamos em coeficientes com magnitude maior que 0,1 pois esses indicam forte influência das variáveis originais nas combinações lineares.

Para efeito de ilustração e encontrar quais os atributos, caso o autovalor apresentasse valores ZERO, da posição desse valor, pediremos o autovetor do menor valor, que está na posição "0".

O vetor abaixo mostra o quanto cada variável contribui para essa combinação linear.

In [228]:
print (eigenvectors[:,0])
[-0.70510167  0.70489107 -0.0772024 ]
In [229]:
eigenvectors_df = pd.DataFrame(eigenvectors, columns=[f'Component_{i+1}' for i in range(len(eigenvalues))])
eigenvectors_df['Variable'] = x_numericos.columns
eigenvectors_df[['Variable', 'Component_1']].sort_values(by='Component_1', key=abs, ascending=False)
Out[229]:
Variable Component_1
0 duration -0.71
1 credit_amount 0.70
2 age -0.08

Interpretação da multicolinearidade: O critério de valores absolutos próximos de 0,1 é uma prática comum. Valores próximos de zero indicam baixa relação com a multicolinearidade. Valores altos (próximos de 1 ou -1) indicam forte participação na colinearidade. Conforme podemos ver na matriz de correlação.

4. Pré-Processamento dos Dados¶

Preparando os Dados para Machine Learning¶

O pré-processamento dos dados é uma etapa fundamental no desenvolvimento de modelos de Machine Learning, pois muitos algoritmos exigem que os dados estejam em formatos específicos e adequadamente preparados para garantir um desempenho eficiente. Essa preparação inclui desde o tratamento de dados ausentes, normalização e padronização até a codificação de variáveis categóricas e remoção de outliers. O desafio é o fato que cada algoritmo requer uma estrutura diferente, o que pode requerer transformações diferentes nos dados.

Importância do Pré-Processamento:

  • Melhora a performance e a eficiência dos algoritmos.
  • Garante que os dados estejam em um formato compatível com o modelo.
  • Reduz ruídos e viéses, aumentando a precisão das previsões.

Explorar diferentes técnicas de preparação de dados e avaliar seu impacto no desempenho dos modelos faz parte do processo iterativo da Ciência de Dados. Essa experimentação é essencial para desenvolver soluções robustas e eficazes.

Testar diferentes abordagens é uma das partes mais interessantes e criativas da Ciência de Dados!

4.1 Transformação de Variáveis/Atributos¶

In [230]:
X.head()
Out[230]:
checking_status duration credit_history purpose credit_amount savings_status employment installment_commitment personal_status other_parties residence_since property_magnitude age other_payment_plans housing existing_credits job num_dependents own_telephone foreign_worker
0 <0 6.00 critical/other existing credit radio/tv 1169.00 no known savings >=7 4.00 male single none 4.00 real estate 67.00 none own 2.00 skilled 1.00 yes yes
1 0<=X<200 48.00 existing paid radio/tv 5951.00 <100 1<=X<4 2.00 female div/dep/mar none 2.00 real estate 22.00 none own 1.00 skilled 1.00 none yes
2 no checking 12.00 critical/other existing credit education 2096.00 <100 4<=X<7 2.00 male single none 3.00 real estate 49.00 none own 1.00 unskilled resident 2.00 none yes
3 <0 42.00 existing paid furniture/equipment 7882.00 <100 4<=X<7 2.00 male single guarantor 4.00 life insurance 45.00 none for free 1.00 skilled 2.00 none yes
4 <0 24.00 delayed previously new car 4870.00 <100 1<=X<4 3.00 male single none 4.00 no known property 53.00 none for free 2.00 skilled 2.00 none yes
In [232]:
df_proc = dados.copy()
df_proc.head()
Out[232]:
checking_status duration credit_history purpose credit_amount savings_status employment installment_commitment personal_status other_parties ... property_magnitude age other_payment_plans housing existing_credits job num_dependents own_telephone foreign_worker class
0 <0 6.00 critical/other existing credit radio/tv 1169.00 no known savings >=7 4.00 male single none ... real estate 67.00 none own 2.00 skilled 1.00 yes yes good
1 0<=X<200 48.00 existing paid radio/tv 5951.00 <100 1<=X<4 2.00 female div/dep/mar none ... real estate 22.00 none own 1.00 skilled 1.00 none yes bad
2 no checking 12.00 critical/other existing credit education 2096.00 <100 4<=X<7 2.00 male single none ... real estate 49.00 none own 1.00 unskilled resident 2.00 none yes good
3 <0 42.00 existing paid furniture/equipment 7882.00 <100 4<=X<7 2.00 male single guarantor ... life insurance 45.00 none for free 1.00 skilled 2.00 none yes good
4 <0 24.00 delayed previously new car 4870.00 <100 1<=X<4 3.00 male single none ... no known property 53.00 none for free 2.00 skilled 2.00 none yes bad

5 rows × 21 columns

a. Transformando o tipo de variveis categoricas que estão em float em inteiros

In [233]:
df_proc[['num_dependents', 'installment_commitment', 'residence_since', 'existing_credits']].info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 4 columns):
 #   Column                  Non-Null Count  Dtype  
---  ------                  --------------  -----  
 0   num_dependents          1000 non-null   float64
 1   installment_commitment  1000 non-null   float64
 2   residence_since         1000 non-null   float64
 3   existing_credits        1000 non-null   float64
dtypes: float64(4)
memory usage: 31.4 KB
In [234]:
df_proc[['num_dependents', 'installment_commitment', 'residence_since', 'existing_credits']] = df_proc[['num_dependents', 'installment_commitment', 'residence_since', 'existing_credits']].astype(int)
df_proc[['num_dependents', 'installment_commitment', 'residence_since', 'existing_credits']].info()    
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 4 columns):
 #   Column                  Non-Null Count  Dtype
---  ------                  --------------  -----
 0   num_dependents          1000 non-null   int64
 1   installment_commitment  1000 non-null   int64
 2   residence_since         1000 non-null   int64
 3   existing_credits        1000 non-null   int64
dtypes: int64(4)
memory usage: 31.4 KB

4.1.1 Imputação de Valores Ausentes¶

Embora o conjunto de dados atual não apresente valores ausentes, é importante documentar estratégias de imputação para lidar com situações futuras em que dados faltantes possam surgir durante o processo de pré-processamento ou em atualizações da base de dados.

A imputação de valores ausentes deve ser realizada com cuidado, considerando o tipo de variável (numérica ou categórica) e a distribuição dos dados. Abaixo estão dois métodos amplamente utilizados para variáveis numéricas:

Imputação pela Mediana
A substituição de valores ausentes pela mediana é recomendada quando a variável possui outliers, pois a mediana é menos sensível a valores extremos do que a média.

Implementação com SimpleImputer:

from sklearn.impute import SimpleImputer
import numpy as np

# Criando o imputador com estratégia de mediana
imp_num = SimpleImputer(missing_values=np.nan, strategy='median', add_indicator=True)

# Aplicando a imputação nos dados
dados_imputados = imp_num.fit_transform(dados)
In [235]:
pct_miss = 100*dados.isnull().sum()/len(dados)
pct_miss[(-pct_miss).argsort()]
Out[235]:
checking_status          0.00
own_telephone            0.00
num_dependents           0.00
job                      0.00
existing_credits         0.00
housing                  0.00
other_payment_plans      0.00
age                      0.00
property_magnitude       0.00
foreign_worker           0.00
residence_since          0.00
personal_status          0.00
installment_commitment   0.00
employment               0.00
savings_status           0.00
credit_amount            0.00
purpose                  0.00
credit_history           0.00
duration                 0.00
other_parties            0.00
class                    0.00
dtype: float64

4.2 Feature Scaling¶

4.2.1 Nomalização¶

A normalização altera a escala dos dados, garantindo que todas as variáveis fiquem dentro de um intervalo padronizado. Isso não muda a distribuição dos valores, apenas os ajusta proporcionalmente.

-> Técnicas: sklearn.preprocessing import (i) MinMaxScaler e (ii) Normalizer

Útil para conjuntos de dados esparsos (muitos zeros) com atributos de escalas variadas ao usar algoritmos que ponderam valores de entrada, como redes neurais e algoritmos que usam medidas de distância,

🔹 Por que normalizar?

Evita que atributos com escalas diferentes dominem a modelagem. Além de facilitar a convergência de algoritmos de aprendizado de máquina. Essencial para modelos que utilizam distâncias (ex.: KNN, SVM) e redes neurais.

4.2.1.1 MinMaxScaler¶

https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html

E uma das primeiras tarefas dentro do pré-processamento, é colocar seus dados na mesma escala. Muitos algoritmos de Machine Learning vão se beneficiar disso e produzir resultados melhores. Esta etapa também é chamada de normalização e significa colocar os dados em uma escala com range entre 0 e 1.

Isso é útil para a otimização, sendo usado no core dos algoritmos de Machine Learning, como gradient descent. Isso também é útil para algoritmos como regressão e redes neurais e algoritmos que usam medidas de distância, como KNN. O scikit-learn possui uma função para esta etapa, chamada MinMaxScaler().

In [236]:
x_numericos.head()
Out[236]:
duration credit_amount age
0 6.00 1169.00 67.00
1 48.00 5951.00 22.00
2 12.00 2096.00 49.00
3 42.00 7882.00 45.00
4 24.00 4870.00 53.00
In [237]:
list(x_numericos.columns)
Out[237]:
['duration', 'credit_amount', 'age']
In [238]:
scaler = MinMaxScaler()
x_norm = pd.DataFrame(scaler.fit_transform(df_proc[list(x_numericos.columns)]), columns= list(x_numericos.columns))

print("Dados Originais: \n\n", df_proc[list(x_numericos.columns)].head())
print("\nDados Normalizados: \n\n", x_norm.head() ) 
Dados Originais: 

    duration  credit_amount   age
0      6.00        1169.00 67.00
1     48.00        5951.00 22.00
2     12.00        2096.00 49.00
3     42.00        7882.00 45.00
4     24.00        4870.00 53.00

Dados Normalizados: 

    duration  credit_amount  age
0      0.03           0.05 0.86
1      0.65           0.31 0.05
2      0.12           0.10 0.54
3      0.56           0.42 0.46
4      0.29           0.25 0.61
In [239]:
df_proc.head()
Out[239]:
checking_status duration credit_history purpose credit_amount savings_status employment installment_commitment personal_status other_parties ... property_magnitude age other_payment_plans housing existing_credits job num_dependents own_telephone foreign_worker class
0 <0 6.00 critical/other existing credit radio/tv 1169.00 no known savings >=7 4 male single none ... real estate 67.00 none own 2 skilled 1 yes yes good
1 0<=X<200 48.00 existing paid radio/tv 5951.00 <100 1<=X<4 2 female div/dep/mar none ... real estate 22.00 none own 1 skilled 1 none yes bad
2 no checking 12.00 critical/other existing credit education 2096.00 <100 4<=X<7 2 male single none ... real estate 49.00 none own 1 unskilled resident 2 none yes good
3 <0 42.00 existing paid furniture/equipment 7882.00 <100 4<=X<7 2 male single guarantor ... life insurance 45.00 none for free 1 skilled 2 none yes good
4 <0 24.00 delayed previously new car 4870.00 <100 1<=X<4 3 male single none ... no known property 53.00 none for free 2 skilled 2 none yes bad

5 rows × 21 columns

In [240]:
df_proc[list(x_numericos.columns)] = x_norm
In [241]:
df_proc.head()
Out[241]:
checking_status duration credit_history purpose credit_amount savings_status employment installment_commitment personal_status other_parties ... property_magnitude age other_payment_plans housing existing_credits job num_dependents own_telephone foreign_worker class
0 <0 0.03 critical/other existing credit radio/tv 0.05 no known savings >=7 4 male single none ... real estate 0.86 none own 2 skilled 1 yes yes good
1 0<=X<200 0.65 existing paid radio/tv 0.31 <100 1<=X<4 2 female div/dep/mar none ... real estate 0.05 none own 1 skilled 1 none yes bad
2 no checking 0.12 critical/other existing credit education 0.10 <100 4<=X<7 2 male single none ... real estate 0.54 none own 1 unskilled resident 2 none yes good
3 <0 0.56 existing paid furniture/equipment 0.42 <100 4<=X<7 2 male single guarantor ... life insurance 0.46 none for free 1 skilled 2 none yes good
4 <0 0.29 delayed previously new car 0.25 <100 1<=X<4 3 male single none ... no known property 0.61 none for free 2 skilled 2 none yes bad

5 rows × 21 columns

4.3 Feature Engineer¶

4.3.1 Codificação de Variáveis Categóricas (Dummies)¶

Para que os algoritmos de Machine Learning possam processar corretamente os dados, todas as variáveis precisam estar em formato numérico. Como muitas variáveis categóricas contêm informações importantes para o modelo, é necessário convertê-las para valores numéricos sem perder informação semântica. Esse processo é conhecido como codificação de variáveis categóricas.

A técnica de codificação utilizada depende do tipo de variável categórica:

Codificação de Variáveis Binárias:

Quando a variável possui apenas duas categorias distintas (ex.: Gender com valores Masculino e Feminino), a abordagem mais eficiente é a codificação ordinal.

Método Utilizado: LabelEncoder

Atribui um valor numérico sequencial para representar cada categoria. Exemplo: Masculino → 0 Feminino → 1 Essa abordagem é simples e evita a criação de colunas adicionais, tornando o modelo mais eficiente.

Codificação de Variáveis Multiclasse:

Quando a variável contém mais de duas categorias distintas (ex.: Market_Segment, Deposit_Type, Meal), o método mais adequado é a codificação One-Hot Encoding.

Método Utilizado: OneHotEncoder (de sklearn.preprocessing)

  • Cria colunas binárias para cada categoria. Cada coluna nova indica a presença (1) ou ausência (0) daquela categoria na amostra.
  • O OneHotEncoder oferece mais flexibilidade e controle, especialmente quando integrado em pipelines de pré-processamento com scikit-learn.

Atenção à Dimensionalidade dos Dados!

Criar muitas colunas adicionais pode aumentar significativamente a dimensionalidade do dataset, tornando o modelo mais complexo e podendo levar a overfitting. Para evitar redundância, recomenda-se excluir uma das colunas geradas ((evitar dummy variable trap - efeito da multicolinearidade).

In [242]:
df_proc[df_proc.select_dtypes(include=['object']).columns].nunique().sort_values(ascending=True)
Out[242]:
own_telephone           2
foreign_worker          2
class                   2
other_parties           3
other_payment_plans     3
housing                 3
checking_status         4
personal_status         4
property_magnitude      4
job                     4
credit_history          5
savings_status          5
employment              5
purpose                10
dtype: int64
In [243]:
df_proc.select_dtypes(include=['object']).columns
Out[243]:
Index(['checking_status', 'credit_history', 'purpose', 'savings_status',
       'employment', 'personal_status', 'other_parties', 'property_magnitude',
       'other_payment_plans', 'housing', 'job', 'own_telephone',
       'foreign_worker', 'class'],
      dtype='object')

Binárias

In [244]:
label_encoder = LabelEncoder()

df_proc['own_telephone'] = label_encoder.fit_transform(df_proc['own_telephone'])
df_proc['foreign_worker'] = label_encoder.fit_transform(df_proc['foreign_worker'])
df_proc['class'] = label_encoder.fit_transform(df_proc['class'])

df_proc.head()
Out[244]:
checking_status duration credit_history purpose credit_amount savings_status employment installment_commitment personal_status other_parties ... property_magnitude age other_payment_plans housing existing_credits job num_dependents own_telephone foreign_worker class
0 <0 0.03 critical/other existing credit radio/tv 0.05 no known savings >=7 4 male single none ... real estate 0.86 none own 2 skilled 1 1 1 1
1 0<=X<200 0.65 existing paid radio/tv 0.31 <100 1<=X<4 2 female div/dep/mar none ... real estate 0.05 none own 1 skilled 1 0 1 0
2 no checking 0.12 critical/other existing credit education 0.10 <100 4<=X<7 2 male single none ... real estate 0.54 none own 1 unskilled resident 2 0 1 1
3 <0 0.56 existing paid furniture/equipment 0.42 <100 4<=X<7 2 male single guarantor ... life insurance 0.46 none for free 1 skilled 2 0 1 1
4 <0 0.29 delayed previously new car 0.25 <100 1<=X<4 3 male single none ... no known property 0.61 none for free 2 skilled 2 0 1 0

5 rows × 21 columns

In [245]:
df_proc['class'].value_counts()
Out[245]:
class
1    700
0    300
Name: count, dtype: int64
In [246]:
print(f'dataset antes do tratamento de variáveis categóricas: {df_proc.shape}')
dataset antes do tratamento de variáveis categóricas: (1000, 21)

Multiclasse

In [247]:
ohe_colunas = ['other_parties', 'other_payment_plans','housing','checking_status','personal_status',
               'property_magnitude','job','credit_history','savings_status','employment','purpose']

drop=firstTrue: Este parâmetro indica que a primeira categoria de cada coluna categórica deve ser removida. Isso é feito para evitar a multicolinearidade em modelos de regressão ( Dummy Variable Trap).

In [248]:
encoder = OneHotEncoder(drop='first', sparse_output=False)

encoded_array = encoder.fit_transform(df_proc[ohe_colunas])
In [249]:
encoded_df = pd.DataFrame(encoded_array, columns=encoder.get_feature_names_out(ohe_colunas))
encoded_df.head()
Out[249]:
other_parties_guarantor other_parties_none other_payment_plans_none other_payment_plans_stores housing_own housing_rent checking_status_<0 checking_status_>=200 checking_status_no checking personal_status_male div/sep ... employment_unemployed purpose_domestic appliance purpose_education purpose_furniture/equipment purpose_new car purpose_other purpose_radio/tv purpose_repairs purpose_retraining purpose_used car
0 0.00 1.00 1.00 0.00 1.00 0.00 1.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
1 0.00 1.00 1.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
2 0.00 1.00 1.00 0.00 1.00 0.00 0.00 0.00 1.00 0.00 ... 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3 1.00 0.00 1.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00
4 0.00 1.00 1.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00

5 rows × 39 columns

In [250]:
df_proc = pd.concat([df_proc.drop(columns=ohe_colunas), encoded_df], axis=1)
df_proc.head()
Out[250]:
duration credit_amount installment_commitment residence_since age existing_credits num_dependents own_telephone foreign_worker class ... employment_unemployed purpose_domestic appliance purpose_education purpose_furniture/equipment purpose_new car purpose_other purpose_radio/tv purpose_repairs purpose_retraining purpose_used car
0 0.03 0.05 4 4 0.86 2 1 1 1 1 ... 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
1 0.65 0.31 2 2 0.05 1 1 0 1 0 ... 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
2 0.12 0.10 2 3 0.54 1 2 0 1 1 ... 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3 0.56 0.42 2 4 0.46 1 2 0 1 1 ... 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00
4 0.29 0.25 3 4 0.61 2 2 0 1 0 ... 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00

5 rows × 49 columns

In [251]:
print(f'dataset após o tratamento de variáveis categóricas: {df_proc.shape}')
dataset após o tratamento de variáveis categóricas: (1000, 49)
In [252]:
df_proc.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 49 columns):
 #   Column                                         Non-Null Count  Dtype  
---  ------                                         --------------  -----  
 0   duration                                       1000 non-null   float64
 1   credit_amount                                  1000 non-null   float64
 2   installment_commitment                         1000 non-null   int64  
 3   residence_since                                1000 non-null   int64  
 4   age                                            1000 non-null   float64
 5   existing_credits                               1000 non-null   int64  
 6   num_dependents                                 1000 non-null   int64  
 7   own_telephone                                  1000 non-null   int64  
 8   foreign_worker                                 1000 non-null   int64  
 9   class                                          1000 non-null   int64  
 10  other_parties_guarantor                        1000 non-null   float64
 11  other_parties_none                             1000 non-null   float64
 12  other_payment_plans_none                       1000 non-null   float64
 13  other_payment_plans_stores                     1000 non-null   float64
 14  housing_own                                    1000 non-null   float64
 15  housing_rent                                   1000 non-null   float64
 16  checking_status_<0                             1000 non-null   float64
 17  checking_status_>=200                          1000 non-null   float64
 18  checking_status_no checking                    1000 non-null   float64
 19  personal_status_male div/sep                   1000 non-null   float64
 20  personal_status_male mar/wid                   1000 non-null   float64
 21  personal_status_male single                    1000 non-null   float64
 22  property_magnitude_life insurance              1000 non-null   float64
 23  property_magnitude_no known property           1000 non-null   float64
 24  property_magnitude_real estate                 1000 non-null   float64
 25  job_skilled                                    1000 non-null   float64
 26  job_unemp/unskilled non res                    1000 non-null   float64
 27  job_unskilled resident                         1000 non-null   float64
 28  credit_history_critical/other existing credit  1000 non-null   float64
 29  credit_history_delayed previously              1000 non-null   float64
 30  credit_history_existing paid                   1000 non-null   float64
 31  credit_history_no credits/all paid             1000 non-null   float64
 32  savings_status_500<=X<1000                     1000 non-null   float64
 33  savings_status_<100                            1000 non-null   float64
 34  savings_status_>=1000                          1000 non-null   float64
 35  savings_status_no known savings                1000 non-null   float64
 36  employment_4<=X<7                              1000 non-null   float64
 37  employment_<1                                  1000 non-null   float64
 38  employment_>=7                                 1000 non-null   float64
 39  employment_unemployed                          1000 non-null   float64
 40  purpose_domestic appliance                     1000 non-null   float64
 41  purpose_education                              1000 non-null   float64
 42  purpose_furniture/equipment                    1000 non-null   float64
 43  purpose_new car                                1000 non-null   float64
 44  purpose_other                                  1000 non-null   float64
 45  purpose_radio/tv                               1000 non-null   float64
 46  purpose_repairs                                1000 non-null   float64
 47  purpose_retraining                             1000 non-null   float64
 48  purpose_used car                               1000 non-null   float64
dtypes: float64(42), int64(7)
memory usage: 382.9 KB
In [253]:
df_proc.corr()
Out[253]:
duration credit_amount installment_commitment residence_since age existing_credits num_dependents own_telephone foreign_worker class ... employment_unemployed purpose_domestic appliance purpose_education purpose_furniture/equipment purpose_new car purpose_other purpose_radio/tv purpose_repairs purpose_retraining purpose_used car
duration 1.00 0.62 0.07 0.03 -0.04 -0.01 -0.02 0.16 0.14 -0.21 ... -0.01 -0.04 0.00 -0.06 -0.11 0.10 -0.04 -0.02 -0.07 0.14
credit_amount 0.62 1.00 -0.27 0.03 0.03 0.02 0.02 0.28 0.05 -0.15 ... 0.09 -0.07 -0.01 -0.03 -0.04 0.19 -0.17 -0.03 -0.07 0.25
installment_commitment 0.07 -0.27 1.00 0.05 0.06 0.02 -0.07 0.01 0.09 -0.07 ... -0.05 0.02 0.04 -0.06 -0.05 -0.03 0.14 0.04 0.04 -0.09
residence_since 0.03 0.03 0.05 1.00 0.27 0.09 0.04 0.10 0.05 -0.00 ... 0.04 -0.02 0.04 -0.01 0.02 0.04 -0.09 0.03 0.01 0.11
age -0.04 0.03 0.06 0.27 1.00 0.15 0.12 0.15 0.01 0.09 ... 0.11 -0.01 0.07 -0.13 0.08 0.04 -0.05 0.04 -0.01 0.05
existing_credits -0.01 0.02 0.02 0.09 0.15 1.00 0.11 0.07 0.01 0.05 ... 0.01 -0.06 -0.01 -0.07 0.04 0.02 -0.03 0.07 -0.01 -0.01
num_dependents -0.02 0.02 -0.07 0.04 0.12 0.11 1.00 -0.01 -0.08 0.00 ... -0.04 -0.05 0.04 -0.09 0.10 0.00 -0.08 0.03 0.02 0.05
own_telephone 0.16 0.28 0.01 0.10 0.15 0.07 -0.01 1.00 0.11 0.04 ... 0.08 -0.02 0.02 -0.05 -0.04 0.12 -0.08 -0.05 -0.01 0.14
foreign_worker 0.14 0.05 0.09 0.05 0.01 0.01 -0.08 0.11 1.00 -0.08 ... 0.05 0.02 0.04 0.01 -0.15 -0.03 0.06 -0.01 0.02 0.03
class -0.21 -0.15 -0.07 -0.00 0.09 0.05 0.00 0.04 -0.08 1.00 ... -0.04 -0.01 -0.07 -0.02 -0.10 -0.03 0.11 -0.02 0.04 0.10
other_parties_guarantor -0.04 -0.07 -0.01 -0.03 -0.02 -0.02 0.04 -0.07 -0.10 0.06 ... -0.06 -0.03 -0.05 -0.03 -0.01 0.02 0.11 0.03 -0.02 -0.03
other_parties_none 0.01 -0.00 0.01 0.02 0.03 0.02 -0.01 0.07 0.12 0.00 ... -0.00 0.04 0.07 -0.02 0.01 -0.12 -0.08 -0.02 0.03 0.06
other_payment_plans_none -0.07 -0.05 -0.02 0.02 -0.04 -0.05 -0.07 -0.03 -0.03 0.11 ... -0.00 0.03 -0.01 0.00 0.03 -0.14 0.03 0.05 0.02 0.01
other_payment_plans_stores 0.07 0.02 0.06 -0.05 -0.01 0.02 0.01 0.03 0.04 -0.05 ... 0.04 -0.02 -0.01 0.03 -0.07 0.02 -0.00 -0.00 -0.02 0.00
housing_own -0.08 -0.12 0.05 -0.30 0.01 0.04 -0.03 -0.04 -0.02 0.13 ... -0.05 0.03 -0.10 -0.04 -0.01 -0.01 0.13 0.02 0.04 -0.14
housing_rent -0.06 -0.02 -0.09 0.17 -0.21 -0.06 -0.06 -0.05 -0.03 -0.09 ... -0.03 -0.00 0.00 0.11 -0.01 -0.05 -0.07 -0.03 -0.02 0.04
checking_status_<0 0.02 -0.02 0.05 0.09 -0.01 -0.03 0.06 -0.07 -0.06 -0.26 ... 0.04 0.04 -0.01 0.13 0.07 0.01 -0.11 -0.02 -0.01 -0.02
checking_status_>=200 -0.08 -0.10 -0.04 -0.06 0.04 -0.04 -0.01 -0.03 -0.04 0.04 ... -0.05 0.01 0.02 -0.00 0.01 -0.03 0.06 -0.04 -0.02 -0.06
checking_status_no checking -0.06 -0.04 0.02 0.00 0.06 0.09 0.01 0.06 0.02 0.32 ... -0.06 -0.01 0.00 -0.07 -0.06 -0.07 0.08 -0.01 0.01 0.09
personal_status_male div/sep 0.01 0.03 -0.10 -0.04 0.06 -0.03 -0.06 0.02 0.02 -0.05 ... -0.00 0.02 -0.03 0.07 -0.02 0.02 -0.07 0.03 -0.02 -0.03
personal_status_male mar/wid -0.08 -0.14 0.01 -0.10 -0.15 -0.03 -0.12 -0.03 -0.05 0.02 ... -0.02 -0.00 -0.06 -0.09 -0.01 -0.04 0.13 0.02 0.08 -0.04
personal_status_male single 0.12 0.15 0.12 0.06 0.21 0.12 0.28 0.08 -0.03 0.08 ... -0.02 -0.05 -0.00 -0.07 0.03 0.03 -0.03 -0.00 -0.04 0.09
property_magnitude_life insurance -0.06 -0.03 -0.02 -0.02 -0.03 -0.01 -0.01 -0.02 -0.04 -0.01 ... 0.01 0.00 0.02 0.17 -0.01 -0.04 -0.11 -0.03 0.07 -0.04
property_magnitude_no known property 0.21 0.25 0.04 0.19 0.21 -0.01 0.08 0.14 0.05 -0.13 ... 0.11 -0.02 0.16 -0.07 0.03 0.08 -0.12 0.03 -0.04 0.13
property_magnitude_real estate -0.24 -0.25 -0.03 -0.09 0.01 0.01 0.02 -0.16 -0.11 0.12 ... -0.07 0.03 -0.10 -0.05 0.04 -0.05 0.12 0.04 -0.01 -0.13
job_skilled 0.06 -0.09 0.04 -0.00 -0.15 -0.00 -0.11 -0.06 0.05 0.01 ... -0.23 0.05 0.00 0.06 -0.09 -0.12 0.09 -0.01 -0.06 -0.03
job_unemp/unskilled non res -0.04 -0.03 -0.09 -0.03 0.06 0.06 -0.01 -0.04 -0.04 -0.01 ... 0.41 0.05 -0.00 -0.05 0.09 0.05 -0.06 0.07 -0.01 -0.03
job_unskilled resident -0.18 -0.16 -0.06 0.01 0.04 -0.01 0.15 -0.25 -0.09 0.02 ... -0.12 -0.03 0.00 -0.02 0.07 -0.03 0.01 0.04 0.08 -0.11
credit_history_critical/other existing credit -0.08 -0.04 0.04 0.09 0.16 0.50 0.02 0.04 -0.04 0.18 ... 0.01 -0.05 0.04 -0.02 0.05 -0.01 -0.01 -0.01 -0.04 0.04
credit_history_delayed previously 0.14 0.11 -0.01 -0.02 0.02 0.14 0.04 0.05 0.06 -0.01 ... 0.01 -0.03 0.01 -0.05 -0.03 0.03 -0.04 0.03 -0.03 -0.01
credit_history_existing paid -0.07 -0.09 -0.02 -0.08 -0.16 -0.54 -0.08 -0.06 -0.00 -0.04 ... -0.02 0.07 -0.03 0.05 -0.02 -0.04 0.08 -0.01 0.00 -0.02
credit_history_no credits/all paid 0.12 0.15 -0.05 0.00 -0.02 0.11 0.01 -0.00 -0.01 -0.14 ... -0.03 -0.02 -0.05 -0.00 -0.03 0.02 -0.08 0.04 0.03 -0.02
savings_status_500<=X<1000 -0.04 -0.06 -0.02 0.03 0.03 -0.06 -0.01 0.00 0.03 0.07 ... -0.02 0.05 -0.02 0.01 -0.02 -0.03 0.05 -0.01 0.02 -0.02
savings_status_<100 -0.05 -0.04 -0.01 -0.09 -0.04 0.03 -0.02 -0.06 0.00 -0.16 ... 0.02 -0.02 -0.00 0.10 -0.01 0.01 0.00 0.01 -0.03 -0.08
savings_status_>=1000 -0.05 -0.06 0.03 -0.00 0.03 0.04 -0.01 0.01 -0.03 0.09 ... -0.06 -0.02 -0.01 0.03 -0.00 -0.02 -0.05 0.03 -0.02 0.03
savings_status_no known savings 0.07 0.11 0.02 0.08 0.08 -0.02 0.03 0.09 -0.00 0.13 ... 0.01 0.02 0.02 -0.08 -0.01 -0.00 0.00 -0.04 0.06 0.11
employment_4<=X<7 0.08 0.05 -0.00 -0.03 -0.08 0.04 0.02 0.04 -0.01 0.08 ... -0.12 -0.00 -0.02 -0.03 0.01 -0.05 0.00 -0.01 0.04 0.01
employment_<1 -0.06 -0.05 -0.03 -0.16 -0.21 -0.10 -0.05 -0.07 -0.04 -0.11 ... -0.12 0.05 0.00 0.08 0.00 -0.05 -0.01 0.00 -0.02 -0.06
employment_>=7 0.02 -0.01 0.13 0.30 0.36 0.12 0.08 0.08 0.05 0.06 ... -0.15 -0.06 0.01 -0.06 -0.01 0.02 0.05 -0.01 -0.01 0.06
employment_unemployed -0.01 0.09 -0.05 0.04 0.11 0.01 -0.04 0.08 0.05 -0.04 ... 1.00 0.01 -0.04 -0.00 0.03 0.12 -0.09 0.05 0.02 0.06
purpose_domestic appliance -0.04 -0.07 0.02 -0.02 -0.01 -0.06 -0.05 -0.02 0.02 -0.01 ... 0.01 1.00 -0.03 -0.05 -0.06 -0.01 -0.07 -0.02 -0.01 -0.04
purpose_education 0.00 -0.01 0.04 0.04 0.07 -0.01 0.04 0.02 0.04 -0.07 ... -0.04 -0.03 1.00 -0.11 -0.13 -0.03 -0.14 -0.03 -0.02 -0.08
purpose_furniture/equipment -0.06 -0.03 -0.06 -0.01 -0.13 -0.07 -0.09 -0.05 0.01 -0.02 ... -0.00 -0.05 -0.11 1.00 -0.26 -0.05 -0.29 -0.07 -0.04 -0.16
purpose_new car -0.11 -0.04 -0.05 0.02 0.08 0.04 0.10 -0.04 -0.15 -0.10 ... 0.03 -0.06 -0.13 -0.26 1.00 -0.06 -0.34 -0.08 -0.05 -0.19
purpose_other 0.10 0.19 -0.03 0.04 0.04 0.02 0.00 0.12 -0.03 -0.03 ... 0.12 -0.01 -0.03 -0.05 -0.06 1.00 -0.07 -0.02 -0.01 -0.04
purpose_radio/tv -0.04 -0.17 0.14 -0.09 -0.05 -0.03 -0.08 -0.08 0.06 0.11 ... -0.09 -0.07 -0.14 -0.29 -0.34 -0.07 1.00 -0.09 -0.06 -0.21
purpose_repairs -0.02 -0.03 0.04 0.03 0.04 0.07 0.03 -0.05 -0.01 -0.02 ... 0.05 -0.02 -0.03 -0.07 -0.08 -0.02 -0.09 1.00 -0.01 -0.05
purpose_retraining -0.07 -0.07 0.04 0.01 -0.01 -0.01 0.02 -0.01 0.02 0.04 ... 0.02 -0.01 -0.02 -0.04 -0.05 -0.01 -0.06 -0.01 1.00 -0.03
purpose_used car 0.14 0.25 -0.09 0.11 0.05 -0.01 0.05 0.14 0.03 0.10 ... 0.06 -0.04 -0.08 -0.16 -0.19 -0.04 -0.21 -0.05 -0.03 1.00

49 rows × 49 columns

In [254]:
df_proc.corr().iloc[9]
Out[254]:
duration                                        -0.21
credit_amount                                   -0.15
installment_commitment                          -0.07
residence_since                                 -0.00
age                                              0.09
existing_credits                                 0.05
num_dependents                                   0.00
own_telephone                                    0.04
foreign_worker                                  -0.08
class                                            1.00
other_parties_guarantor                          0.06
other_parties_none                               0.00
other_payment_plans_none                         0.11
other_payment_plans_stores                      -0.05
housing_own                                      0.13
housing_rent                                    -0.09
checking_status_<0                              -0.26
checking_status_>=200                            0.04
checking_status_no checking                      0.32
personal_status_male div/sep                    -0.05
personal_status_male mar/wid                     0.02
personal_status_male single                      0.08
property_magnitude_life insurance               -0.01
property_magnitude_no known property            -0.13
property_magnitude_real estate                   0.12
job_skilled                                      0.01
job_unemp/unskilled non res                     -0.01
job_unskilled resident                           0.02
credit_history_critical/other existing credit    0.18
credit_history_delayed previously               -0.01
credit_history_existing paid                    -0.04
credit_history_no credits/all paid              -0.14
savings_status_500<=X<1000                       0.07
savings_status_<100                             -0.16
savings_status_>=1000                            0.09
savings_status_no known savings                  0.13
employment_4<=X<7                                0.08
employment_<1                                   -0.11
employment_>=7                                   0.06
employment_unemployed                           -0.04
purpose_domestic appliance                      -0.01
purpose_education                               -0.07
purpose_furniture/equipment                     -0.02
purpose_new car                                 -0.10
purpose_other                                   -0.03
purpose_radio/tv                                 0.11
purpose_repairs                                 -0.02
purpose_retraining                               0.04
purpose_used car                                 0.10
Name: class, dtype: float64

A análise da correlação das variáveis com a variável alvo (class) nos dá uma primeira indicação sobre quais features podem ter maior impacto na previsão de bons ou maus pagadores. No entanto, a correlação isoladamente não é suficiente para determinar as variáveis mais relevantes, pois não captura relações não lineares ou interações entre variáveis.

4.4 Split dos dados¶

Para avaliar o desempenho do modelo, é essencial dividir os dados em conjuntos de treino e teste. Existem duas abordagens amplamente utilizadas para esse processo:

(a). Divisão Estática (train_test_split) Utiliza a função train_test_split da biblioteca sklearn.model_selection, separando os dados em uma única iteração. Permite definir a proporção de treino e teste (exemplo: 75% treino e 25% teste). Simples e eficiente para cenários onde os dados são homogêneos e não precisam de múltiplas divisões.

(b). Validação Cruzada (Cross-Validation) Realiza múltiplas divisões aleatórias dos dados, treinando e avaliando o modelo em diferentes subconjuntos. Reduz o impacto de variações na amostra e melhora a robustez da avaliação. É útil especialmente quando temos um volume menor de dados ou queremos ajustar hiperparâmetros.

In [159]:
X = df_proc.drop(columns= ['class'], axis= 1)
y = df_proc['class']
In [160]:
y.head()
Out[160]:
0    1
1    0
2    1
3    1
4    0
Name: class, dtype: int64
In [161]:
X.head()
Out[161]:
duration credit_amount installment_commitment residence_since age existing_credits num_dependents own_telephone foreign_worker other_parties_guarantor ... employment_unemployed purpose_domestic appliance purpose_education purpose_furniture/equipment purpose_new car purpose_other purpose_radio/tv purpose_repairs purpose_retraining purpose_used car
0 0.03 0.05 4 4 0.86 2 1 1 1 0.00 ... 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
1 0.65 0.31 2 2 0.05 1 1 0 1 0.00 ... 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
2 0.12 0.10 2 3 0.54 1 2 0 1 0.00 ... 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3 0.56 0.42 2 4 0.46 1 2 0 1 1.00 ... 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00
4 0.29 0.25 3 4 0.61 2 2 0 1 0.00 ... 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00

5 rows × 48 columns

In [162]:
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, stratify=y, shuffle=True)
In [163]:
print('tamanho total dos dados são {}, para treino temos {} e teste são {}'.format(df_proc.shape[0], len(X_treino), len(X_teste)))
tamanho total dos dados são 1000, para treino temos 750 e teste são 250

4.4.1 Cross Validation - K folds¶

Cross-Validation é uma técnica robusta e amplamente utilizada para avaliar o desempenho de modelos de Machine Learning de forma mais confiável do que uma simples divisão treino/teste. A principal vantagem do Cross-Validation reside na sua capacidade de reduzir a variância na estimativa da performance do modelo, fornecendo uma avaliação mais estável e menos dependente de uma única divisão aleatória dos dados.

O processo de Cross-Validation envolve a divisão do dataset em k partes mutuamente exclusivas, denominadas folds. O valor de k é um hiperparâmetro definido pelo usuário (comumente k=5 ou k=10). Em cada iteração do Cross-Validation, um dos k folds é reservado como conjunto de teste (ou validação), enquanto os k-1 folds restantes são combinados para formar o conjunto de treino.

O modelo é então treinado utilizando o conjunto de treino e avaliado no fold de teste reservado. Este procedimento é repetido k vezes, de modo que cada um dos k folds seja utilizado exatamente uma vez como conjunto de teste. Ao final das k iterações, obtemos k métricas de performance (por exemplo, acurácia, precisão, recall, etc.), uma para cada fold.

Para sumarizar a performance geral do modelo, calculam-se estatísticas descritivas sobre as k métricas obtidas, tipicamente a média e o desvio padrão. A média das métricas oferece uma estimativa da performance esperada do modelo em dados não vistos, enquanto o desvio padrão quantifica a variabilidade dessa estimativa. Um baixo desvio padrão indica que a performance do modelo é consistente entre diferentes folds, aumentando a confiança na avaliação.

Benefícios do Cross-Validation:

  • Estimativa de performance mais robusta: Menor variância em comparação com uma única divisão treino/teste.
  • Melhor utilização dos dados: Cada observação é usada tanto para treino quanto para teste (em diferentes iterações).
  • Detecção de problemas de generalização: Permite identificar se o modelo está excessivamente ajustado a uma partição específica dos dados.

Considerações ao usar Cross-Validation:

  • Custo computacional: Aumenta o tempo de treinamento, pois o modelo é treinado k vezes.
  • Escolha do valor de k: Um valor de k muito pequeno pode aumentar a variância da estimativa, enquanto um valor muito grande pode aumentar o custo computacional e aproximar-se do comportamento de treino em todo o dataset (perdendo um pouco do benefício da validação). Valores comuns como 5 e 10 geralmente oferecem um bom equilíbrio.
  • Não substitui o conjunto de teste final: O Cross-Validation é primariamente uma técnica de avaliação e seleção de modelos. Um conjunto de teste separado ainda é recomendado para uma avaliação final e imparcial do modelo escolhido após o processo de Cross-Validation.

Para mais detalhes e opções de implementação, consulte a documentação do scikit-learn: https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html

In [169]:
num_folds = 6
seed = 42
kfold = KFold(num_folds, shuffle=True, random_state = seed)

4.5 Avaliação de Multicolinearidade¶

A multicolinearidade ocorre quando variáveis independentes estão altamente correlacionadas, o que pode distorcer os coeficientes do modelo, dificultar a interpretação e reduzir a precisão preditiva.

Para detectar esse problema, utiliza-se o ator de Inflação da Variância (VIF), que indica o quanto a variância de um coeficiente é ampliada pela correlação entre variáveis.

Interpretação do VIF:

  • VIF ≈ 1: Baixa correlação (sem multicolinearidade).
  • VIF > 10: Indica multicolinearidade severa.

Tratamento da Multicolinearidade:

  • Ajustar ou remover variáveis com VIF elevado para melhorar a robustez e a interpretabilidade do modelo.
  • Quando a remoção não for viável, aplicar técnicas de regularização como:
    • Ridge Regression (L2): Penaliza coeficientes grandes, suavizando o impacto de variáveis correlacionadas.
    • Lasso Regression (L1): Realiza seleção de variáveis ao reduzir coeficientes menos relevantes a zero.

Essas técnicas ajudam a mitigar a multicolinearidade sem a necessidade de excluir variáveis importantes.

In [144]:
X.head()
Out[144]:
duration credit_amount installment_commitment residence_since age existing_credits num_dependents own_telephone foreign_worker other_parties_guarantor ... employment_unemployed purpose_domestic appliance purpose_education purpose_furniture/equipment purpose_new car purpose_other purpose_radio/tv purpose_repairs purpose_retraining purpose_used car
0 0.03 0.05 4 4 0.86 2 1 1 1 0.00 ... 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
1 0.65 0.31 2 2 0.05 1 1 0 1 0.00 ... 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
2 0.12 0.10 2 3 0.54 1 2 0 1 0.00 ... 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3 0.56 0.42 2 4 0.46 1 2 0 1 1.00 ... 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00
4 0.29 0.25 3 4 0.61 2 2 0 1 0.00 ... 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00

5 rows × 48 columns

In [145]:
vif_data = pd.DataFrame()
vif_data['Feature'] = X.columns
vif_data['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]

print("Valores de VIF para cada variável:")
print(vif_data)
Valores de VIF para cada variável:
                                          Feature   VIF
0                                        duration  6.07
1                                   credit_amount  5.31
2                          installment_commitment 10.57
3                                 residence_since  9.96
4                                             age  4.56
5                                existing_credits 11.13
6                                  num_dependents 12.35
7                                   own_telephone  2.23
8                                  foreign_worker 26.03
9                         other_parties_guarantor  2.24
10                             other_parties_none 21.62
11                       other_payment_plans_none  7.69
12                     other_payment_plans_stores  1.41
13                                    housing_own 15.97
14                                   housing_rent  4.54
15                             checking_status_<0  2.33
16                          checking_status_>=200  1.31
17                    checking_status_no checking  2.80
18                   personal_status_male div/sep  1.24
19                   personal_status_male mar/wid  1.37
20                    personal_status_male single  3.55
21              property_magnitude_life insurance  1.85
22           property_magnitude_no known property  3.01
23                 property_magnitude_real estate  2.21
24                                    job_skilled  7.16
25                    job_unemp/unskilled non res  1.43
26                         job_unskilled resident  3.49
27  credit_history_critical/other existing credit  8.59
28              credit_history_delayed previously  3.11
29                   credit_history_existing paid 12.28
30             credit_history_no credits/all paid  1.99
31                     savings_status_500<=X<1000  1.70
32                            savings_status_<100  7.26
33                          savings_status_>=1000  1.54
34                savings_status_no known savings  2.94
35                              employment_4<=X<7  1.58
36                                  employment_<1  1.62
37                                 employment_>=7  2.24
38                          employment_unemployed  1.70
39                     purpose_domestic appliance  1.18
40                              purpose_education  1.66
41                    purpose_furniture/equipment  3.17
42                                purpose_new car  3.69
43                                  purpose_other  1.21
44                               purpose_radio/tv  4.32
45                                purpose_repairs  1.29
46                             purpose_retraining  1.16
47                               purpose_used car  2.31

5. Seleção Algoritmos¶

A escolha do algoritmo de classificação mais adequado não é previsível, pois diferentes modelos podem apresentar desempenhos distintos dependendo das características dos dados. Por esse motivo, é essencial ajustar (fit) e avaliar diversos algoritmos de classificação no conjunto de dados. O objetivo é identificar o modelo que oferece o melhor desempenho preditivo e generalização.

5.1 Validação Cruzada¶

In [166]:
modelo_v2 = LogisticRegression()
In [174]:
# Executar cross-validation com múltiplas métricas
cv_results = cross_validate(modelo_v2, X_treino, y_treino, cv=kfold, scoring=['accuracy', 'precision', 'recall', 'f1'])

# Exibir métricas médias
print(f"Acurácia Média: {cv_results['test_accuracy'].mean():.4f}")
print(f"Precisão Média: {cv_results['test_precision'].mean():.4f}")
print(f"Recall Médio: {cv_results['test_recall'].mean():.4f}")
print(f"F1-Score Médio: {cv_results['test_f1'].mean():.4f}")
Acurácia Média: 0.7493
Precisão Média: 0.7938
Recall Médio: 0.8689
F1-Score Médio: 0.8283

5.2 Treinamento modelo¶

In [ ]:
ridge_model = LogisticRegression(penalty='l2', solver='lbfgs', max_iter=1000, random_state=42)

ridge_model.fit(X_treino, y_treino)
Out[ ]:
LogisticRegression(max_iter=1000, random_state=42)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression(max_iter=1000, random_state=42)
In [ ]:
y_pred = ridge_model.predict(X_teste)
print("\nRelatório de Classificação:\n", classification_report(y_teste, y_pred))
Relatório de Classificação:
               precision    recall  f1-score   support

           0       0.58      0.44      0.50        75
           1       0.78      0.86      0.82       175

    accuracy                           0.74       250
   macro avg       0.68      0.65      0.66       250
weighted avg       0.72      0.74      0.72       250

O modelo de Regressão Logística foi treinado considerando todas as variáveis, sem a remoção de multicolinearidade detectada pelo VIF.

  1. Visão Geral
  • Acurácia geral: 74% → O modelo classifica corretamente 74% dos clientes entre bons e maus pagadores.
  • Precisão (Precision): 68% → Média da precisão entre as classes.
  • Recall: 65% → Média da capacidade do modelo em encontrar corretamente cada classe.
  • F1-score: 66% → Média harmônica entre precisão e recall.
  1. Classe 0 (Mau Pagador) Precisão = 58% → Dos clientes que o modelo classificou como maus pagadores. Recall = 44% → O modelo identificou corretamente apenas 44% dos maus pagadores, deixando passar 56% dos clientes realmente inadimplentes (falsos negativos). F1-score = 50% → Indica um desempenho fraco na detecção de inadimplentes.

  2. Classe 1 (Bom Pagador) Precisão = 78% → Dos clientes que o modelo classificou como bons pagadores, 22% foram erros (falsos negativos). Recall = 86% → O modelo identificou corretamente 86% dos bons pagadores, errando apenas 14%. F1-score = 82% → Indica um bom desempenho geral na classificação de bons pagadores.

In [176]:
# Avaliar coeficientes após regularização
coef_df = pd.DataFrame({'Feature': X_treino.columns, 'Coefficient': ridge_model.coef_[0]})
print(coef_df.sort_values(by='Coefficient', ascending=False))
                                          Feature  Coefficient
17                    checking_status_no checking         1.34
27  credit_history_critical/other existing credit         1.26
9                         other_parties_guarantor         1.07
28              credit_history_delayed previously         0.79
33                          savings_status_>=1000         0.75
4                                             age         0.70
47                               purpose_used car         0.70
46                             purpose_retraining         0.52
11                       other_payment_plans_none         0.50
34                savings_status_no known savings         0.46
35                              employment_4<=X<7         0.44
10                             other_parties_none         0.41
20                    personal_status_male single         0.37
16                          checking_status_>=200         0.36
43                                  purpose_other         0.35
7                                   own_telephone         0.29
25                    job_unemp/unskilled non res         0.28
44                               purpose_radio/tv         0.25
26                         job_unskilled resident         0.20
41                    purpose_furniture/equipment         0.18
29                   credit_history_existing paid         0.17
19                   personal_status_male mar/wid         0.12
23                 property_magnitude_real estate         0.10
37                                 employment_>=7         0.07
39                     purpose_domestic appliance         0.05
12                     other_payment_plans_stores         0.03
31                     savings_status_500<=X<1000         0.02
13                                    housing_own         0.02
3                                 residence_since        -0.00
21              property_magnitude_life insurance        -0.02
24                                    job_skilled        -0.07
36                                  employment_<1        -0.15
22           property_magnitude_no known property        -0.16
6                                  num_dependents        -0.21
45                                purpose_repairs        -0.29
2                          installment_commitment        -0.31
5                                existing_credits        -0.33
15                             checking_status_<0        -0.36
14                                   housing_rent        -0.38
38                          employment_unemployed        -0.41
30             credit_history_no credits/all paid        -0.46
18                   personal_status_male div/sep        -0.57
32                            savings_status_<100        -0.58
42                                purpose_new car        -0.60
40                              purpose_education        -0.99
8                                  foreign_worker        -1.02
1                                   credit_amount        -1.66
0                                        duration        -1.80

Resumo dos Principais Coeficientes e seus Impactos

Variável Coeficiente Impacto
checking_status_no checking 1.34 Aumenta a chance de ser bom pagador
credit_history_critical/other existing credit 1.26 Aumenta a chance de ser bom pagador
other_parties_guarantor 1.07 Ter um garantidor aumenta a chance de bom pagador
credit_history_delayed previously 0.79 Histórico de atraso favorece bom pagador
savings_status_>=1000 0.75 Alto saldo de poupança favorece bom pagador
age 0.70 Idade maior favorece ser bom pagador
purpose_used car 0.70 Empréstimos para carros usados associam-se a bom pagador
purpose_retraining 0.52 Financiamento para requalificação profissional é positivo
other_payment_plans_none 0.50 Não possuir outros planos de pagamento aumenta chance de bom pagador
savings_status_no known savings 0.46 Não ter poupança favorece bom pagador
employment_4<=X<7 0.44 Tempo de emprego entre 4 e 7 anos favorece bom pagador
checking_status_<0 -0.36 Saldo negativo reduz chance de ser bom pagador
employment_unemployed -0.41 Desemprego reduz chance de ser bom pagador
credit_amount -1.66 Valores elevados de crédito estão associados a inadimplência
duration -1.80 Duração maior do crédito reduz chance de ser bom pagador

Os coeficientes positivos indicam variáveis que aumentam a chance do cliente ser bom pagador, enquanto os negativos sugerem maior risco de inadimplência. Destacam-se saldo bancário, histórico de crédito e propósito do empréstimo como fatores críticos na decisão do modelo.

In [177]:
# Probabilidades previstas (classe positiva)
y_prob = ridge_model.predict_proba(X_teste)[:, 1]

# Curva ROC
fpr, tpr, thresholds = roc_curve(y_teste, y_prob)
auc = roc_auc_score(y_teste, y_prob)

# Plot da Curva ROC
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f'AUC = {auc:.2f}', color='blue')
plt.plot([0, 1], [0, 1], 'k--', label='Modelo Aleatório')
plt.title('Curva ROC')
plt.xlabel('Taxa de Falsos Positivos (FPR)')
plt.ylabel('Taxa de Verdadeiros Positivos (TPR)')
plt.legend(loc='lower right')
plt.grid()
plt.show()
plt.tight_layout();
No description has been provided for this image
<Figure size 1000x600 with 0 Axes>
  • Linha preta tracejada: Representa um modelo aleatório, onde a taxa de verdadeiros positivos cresce proporcionalmente à taxa de falsos positivos. Isso significa que o modelo não tem capacidade de distinguir entre as classes.

  • Curva Azul (Modelo Treinado): Indica o desempenho do modelo real. Quanto mais distante da linha diagonal e mais próximo do canto superior esquerdo, melhor o modelo.

  • Área sob a curva (AUC = 0.78): O valor 0.78 indica que o modelo tem boa capacidade de discriminar entre bons e maus pagadores. Um AUC = 0.5 indicaria um modelo aleatório (sem capacidade preditiva). Um AUC próximo de 1.0 indicaria um modelo perfeito.

Com um AUC de 0.78, o modelo apresenta um bom desempenho na classificação dos clientes. Ele acerta mais do que erra, mas ainda há espaço para melhorias. Dependendo do objetivo do modelo (reduzir falsos positivos ou falsos negativos), pode-se ajustar o limiar de decisão para otimizar a classificação.

In [179]:
# Matriz de confusão
cm = confusion_matrix(y_teste, y_pred)
tn, fp, fn, tp = cm.ravel()
specificity = tn / (tn + fp)
print(f"Especificidade: {specificity:.2f}")

disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=ridge_model.classes_)
disp.plot(cmap='Blues', values_format='d')
plt.title('Matriz de Confusão')
plt.grid(False)
plt.show()
Especificidade: 0.44
No description has been provided for this image
Verdadeiro \ Predito Mau Pagador (0) Bom Pagador (1)
Mau Pagador (0) 33 (Verdadeiro Negativo - TN) 42 (Falso Positivo - FP)
Bom Pagador (1) 24 (Falso Negativo - FN) 151 (Verdadeiro Positivo - TP)

O modelo tem uma boa capacidade de identificar clientes adimplentes (Classe 1), mas precisa melhorar a detecção de inadimplentes (Classe 0).

Ajustar o threshold da classificação ou explorar técnicas como penalização de classes pode melhorar a detecção dos maus pagadores.

Modelos alternativos como árvores de decisão, Random Forest ou XGBoost poderiam ser testados para melhorar a capacidade preditiva.

5.3 SHAP value¶

In [181]:
explainer = shap.Explainer(ridge_model, X_treino)
shap_values = explainer(X_teste)
In [ ]:
shap.summary_plot(shap_values, X_teste)
No description has been provided for this image

checking_status_no checking -> Clientes sem conta corrente, ou sem verificcação de saldo têm maior risco de inadimplência. Bancos podem exigir histórico de movimentação antes de conceder crédito.

credit_history_critical/other existing credit -> Clientes com histórico de crédito crítico são mais propensos ao não pagamento. Estratégias de mitigação incluem análise mais rigorosa e taxas de juros diferenciadas.

savings_status_no known savings -> Clientes sem histórico de poupança demonstram menor capacidade de reserva financeira, aumentando o risco de default.

purpose_new car -> Clientes que buscam crédito para compra de carro novo podem apresentar perfis de risco diferentes, dependendo da renda e comprometimento financeiro.

age -> Clientes mais jovens podem apresentar maior risco devido a menor estabilidade financeira e menor tempo no mercado de trabalho.

A Instituição deveria:

  1. Política de Crédito Mais Rigorosa para clientes sem histórico bancário ou com crédito crítico.
  2. Análise mais aprofundada do perfil financeiro para clientes sem poupança.
  3. Personalização da Concessão de Crédito: Dependendo do propósito do empréstimo (ex: carro, educação), aplicar taxas de juros diferenciadas.
  4. Incorporar Novas Fontes de Dados: Como histórico de pagamentos de contas básicas (água, luz, telefone) para perfis sem histórico bancário.

6. Conclusões¶

O desenvolvimento deste modelo de classificação de risco de crédito representa a versão inicial de um sistema preditivo que, apesar de entregar resultados iniciais, ainda tem um grande potencial de refinamento.

Modelos de Machine Learning não é um produto finalizado, mas um organismo vivo. Ele evolui constantemente à medida que novos dados são incorporados, técnicas mais avançadas são aplicadas e ajustes na engenharia de atributos são realizados.

Relembrando o que fizemos nesta primeira versão:

  • Análise exploratória dos dados para identificar padrões e relações entre variáveis.
  • Pré-processamento básico com codificação de variáveis categóricas e normalização de variáveis numéricas.
  • Treinamento de um modelo base utilizando Regressão Logística, sem otimizações profundas.
  • Avaliação da performance com métricas como Acurácia, Recall, Precisão, Curva ROC e SHAP para explicar a importância das variáveis.

Oportunidades de melhoria para versões futuras:

  • Engenharia de Atributos: Criar novas variáveis que capturem melhor o comportamento dos clientes, como um indicador de estabilidade financeira combinando idade, histórico de crédito e status bancário.
  • Feature Selection: Refinar a seleção de variáveis para evitar multicolinearidade e melhorar a interpretabilidade do modelo.
  • Testar Modelos Mais Avançados: Como Random Forest, XGBoost e Redes Neurais, que podem capturar relações não lineares entre as variáveis.
  • Ajustar Hiperparâmetros: Utilizar técnicas como Grid Search e Random Search para otimizar o desempenho do modelo.
  • Atualização Contínua: Incorporar novos dados periodicamente para refletir as mudanças no perfil dos clientes e nas tendências econômicas.