Previsão de Inadimplência

Pandas Couple
13 min readApr 29, 2021

--

Photo by Andre Taissin on Unsplash

A inadimplência dos clientes é um problema que as instituições financeiras enfrentam, algo que pode trazer grande prejuízo a elas. Conseguir prever se um cliente irá deixar de cumprir suas obrigações financeiras com a empresa é muito importante já que pode evitar prejuízos à mesma.

As instituições financeiras -grandes bancos e fintechs- tem apostado cada vez mais em Inteligência Artificial e Machine Learning com a finalidade de prever a inadimplência e então minimizar o risco de default.

O desafio deste projeto é justamente este: prever o default para reduzi-lo, evitando prejuízos por inadimplência dos clientes.

Obtenção dos dados

Os dados utilizados para desenvolver esse projeto foram obtidos do Default of Credit Card Clients Dataset do UCI Machine Learning Repository disponível no kaggle e contém dados de clientes de cartões de crédito do Taiwan no ano de 2005.

O dataset possui informações dos pagamentos de inadimplência, dados de crédito, históricos de pagamentos e extratos de contas de clientes do Taiwan de abril a setembro de 2005.

Agradecimentos

O dataset original encontra-se no UCI Machine Learning Repository.

Lichman, M. (2013). UCI Machine Learning Repository (http://archive.ics.uci.edu/ml) . Irvine, CA: University of California, School of Information and Computer Science.

Análise Exploratória dos Dados

Com os dados importados para um DataFrame, vamos dar início à primeira fase de um projeto de Machine Learning: a Análise Exploratória de Dados (EDA — Exploratory Data Analysis) onde vamos conhecer os dados a serem trabalhados.

Variáveis e entradas

Vamos verificar:

  • o tamanho do dataframe, ou seja, quantas entradas (linhas) e quantas variáveis (colunas) possui
Entradas:   30000 
Variáveis: 25
  • As primeiras entradas do dataframe
primeiras entradas do dataframe
  • os nomes das variáveis (colunas) e seus tipos
Tipos de variáveis:ID                              int64
LIMIT_BAL float64
SEX int64
EDUCATION int64
MARRIAGE int64
AGE int64
PAY_0 int64
PAY_2 int64
PAY_3 int64
PAY_4 int64
PAY_5 int64
PAY_6 int64
BILL_AMT1 float64
BILL_AMT2 float64
BILL_AMT3 float64
BILL_AMT4 float64
BILL_AMT5 float64
BILL_AMT6 float64
PAY_AMT1 float64
PAY_AMT2 float64
PAY_AMT3 float64
PAY_AMT4 float64
PAY_AMT5 float64
PAY_AMT6 float64
default.payment.next.month int64
dtype: object
  • a descrição das variáveis

ID: ID do cliente

LIMIT_BAL: valor do crédito concedido, na moeda local (novo dólar taiwanês), inclui crédito individual e familiar/suplementar

SEX: gênero do cliente (1=masculino, 2=feminino)

EDUCATION: grau de escolaridade (1=pós graduação, 2=ensino superior, 3=ensino médio, 4=outro, 5=desconhecido, 6=desconhecido)

MARRIAGE: estado civil (1=casado, 2=solteiro, 3=outro)

AGE: idade (anos)

PAY_0: status do pagamento da conta de setembro de 2005 (-1= paga em dia, 1= atraso de pagamento por um mês, 2= atraso de pagamento por dois meses, … 8= atraso de pagamento por oito meses, 9= atraso de pagamento por nove meses ou mais)

PAY_2: status do pagamento da conta de agosto de 2005 (mesma escala acima)

PAY_3: status do pagamento da conta de julho de 2005 (mesma escala acima)

PAY_4: status do pagamento da conta de junho de 2005 (mesma escala acima)

PAY_5: status do pagamento da conta de maio de 2005 (mesma escala acima)

PAY_6: status do pagamento da conta de abril de 2005 (mesma escala acima)

BILL_AMT1: valor da conta de setembro de 2005, na moeda local (novo dólar taiwanês)

BILL_AMT2: valor da conta de agosto de 2005, na moeda local (novo dólar taiwanês)

BILL_AMT3: valor da conta de julho de 2005, na moeda local (novo dólar taiwanês)

BILL_AMT4: valor da conta de junho de 2005, na moeda local (novo dólar taiwanês)

BILL_AMT5: valor da conta de maio de 2005, na moeda local (novo dólar taiwanês)

BILL_AMT6: valor da conta de abril de 2005, na moeda local (novo dólar taiwanês)

PAY_AMT1: valor do pagamento anterior em setembro de 2005, na moeda local (novo dólar taiwanês)

PAY_AMT2: valor do pagamento anterior em agosto de 2005, na moeda local (novo dólar taiwanês)

PAY_AMT3: valor do pagamento anterior em julho de 2005, na moeda local (novo dólar taiwanês)

PAY_AMT4: valor do pagamento anterior em junho de 2005, na moeda local (novo dólar taiwanês)

PAY_AMT5: valor do pagamento anterior em maio de 2005, na moeda local (novo dólar taiwanês)

PAY_AMT6: valor do pagamento anterior em abril de 2005, na moeda local (novo dólar taiwanês)

default.payment.next.month: pagamento do default (1=sim, 0=não)

Aqui já é possível fazer algumas observações iniciais:

  • a coluna ID é praticamente um índice e podemos eliminá-la pois não tem informação relevante para esta análise
  • as colunas EDUCATION e MARRIAGE são variáveis categóricas múltiplas, então vamos codificá-las.
  • o mesmo se aplica para as colunas PAY_0 a PAY_6 e também vamos codificá-las
  • as variáveis BILL_AMT1 a BILL_AMT6 e PAY_AMT1 a PAY_AMT6 são do tipo float e por ora não precisamos modificar nada nelas.
  • A coluna default.payment.next.month é nossa variável alvo.

Vamos verificar o resumo estatístico das variáveis numéricas.

resumo estatístico

Pelo resumo estatístico já conseguimos observar algo estranho nas variáveis EDUCATION e MARRIAGE que apresentam min=0 quando em suas descrições o 0 não era uma opção de entrada.

Outro ponto que chama a atenção é em relação às variáveis PAY_0 a PAY_6 que apresentam min=-2 e mediana=0, sendo que nenhum desses dois valores foram citados como opção de entrada em suas descrições.

Vamos verificar a presença de outliers plotando os boxplots de algumas variáveis.

boxplots para LIMIT_BAL, BILL_AMT1 e PAY_AMT1

Verificamos a presença de alguns outliers nas variáveis LIMIT_BAL, BILL_AMT1 e PAY_AMT1.

Iremos eliminar alguns para que nosso modelo não sofra influência por esses valores.

O boxplot da variável BILL_AMT1 será assumido como base para exclusão de entradas das variáveis BILL_AMT1 a BILL_AMT6.

Similarmente, o boxplot da variável PAY_AMT1 será assumido como base para a exclusão de entradas das variáveis PAY_AMT1 a PAY_AMT6.

Valores Ausentes

Vamos verificar a proporção de dados ausentes em cada coluna, ou seja, dados faltantes.

Quanto mais dados ausentes um dataset possui, mais ele perde em qualidade. Existem maneiras de tratar essa falta de dados, mas jamais será o equivalente a tê-los.

Proporção de valores ausentes:ID                            0.0
PAY_AMT5 0.0
PAY_AMT4 0.0
PAY_AMT3 0.0
PAY_AMT2 0.0
PAY_AMT1 0.0
BILL_AMT6 0.0
BILL_AMT5 0.0
BILL_AMT4 0.0
BILL_AMT3 0.0
BILL_AMT2 0.0
PAY_AMT6 0.0
BILL_AMT1 0.0
PAY_5 0.0
PAY_4 0.0
PAY_3 0.0
PAY_2 0.0
PAY_0 0.0
AGE 0.0
MARRIAGE 0.0
EDUCATION 0.0
SEX 0.0
LIMIT_BAL 0.0
PAY_6 0.0
default.payment.next.month 0.0
dtype: float64

O dataset aparentemente não apresenta valor ausente em nenhuma coluna. Isso indicaria ótima qualidade, porém é necessário investigarmos mais afundo pois essa pode não ser a realidade.

Vamos investigar os valores únicos de cada variável para verificar se estão de acordo com a descrição das mesmas.

Valores únicos:ID                            30000
LIMIT_BAL 81
SEX 2
EDUCATION 7
MARRIAGE 4
AGE 56
PAY_0 11
PAY_2 11
PAY_3 11
PAY_4 11
PAY_5 10
PAY_6 10
BILL_AMT1 22723
BILL_AMT2 22346
BILL_AMT3 22026
BILL_AMT4 21548
BILL_AMT5 21010
BILL_AMT6 20604
PAY_AMT1 7943
PAY_AMT2 7899
PAY_AMT3 7518
PAY_AMT4 6937
PAY_AMT5 6897
PAY_AMT6 6939
default.payment.next.month 2
dtype: int64

Algumas variáveis categóricas parecem suspeitas, vamos entender o porquê:

  • EDUCATION possui 7 valores únicos, porém em sua descrição vimos que ela deveria apresentar valores de 1 a 6.
  • MARRIAGE possui 4 valores únicos e sua descrição indica apenas 3.
  • PAY_0 a PAY_4 apresentam 11 valores únicos, sendo apenas 10 em sua descrição.

Vamos então visualizar quais os valores únicos de cada uma dessas colunas, assim como a quantidade de cada um deles.

Valores únicos de EDUCATION:2    14030
1 10585
3 4917
5 280
4 123
6 51
0 14
Name: EDUCATION, dtype: int64
Valores únicos de MARRIAGE:2 15964
1 13659
3 323
0 54
Name: MARRIAGE, dtype: int64

A variável EDUCATION apresenta 14 entradas de valor 0. Neste caso vamos substituir essas entradas por 4 que representa "outro". Faremos isso também para as entradas 5 e 6 que representam "desconhecido". Todas essas entradas são redundantes e serão agrupadas em 4 representando "outro".

A variável MARRIAGE apresenta 54 entradas de valor 0. Vamos substituir essas entradas por 3 que representa "outro", agrupando-as.

Valores únicos de PAY_0: 0    14737
-1 5686
1 3688
-2 2759
2 2667
3 322
4 76
5 26
8 19
6 11
7 9
Name: PAY_0, dtype: int64
Valores únicos de PAY_2: 0 15730
-1 6050
2 3927
-2 3782
3 326
4 99
1 28
5 25
7 20
6 12
8 1
Name: PAY_2, dtype: int64
Valores únicos de PAY_3: 0 15764
-1 5938
-2 4085
2 3819
3 240
4 76
7 27
6 23
5 21
1 4
8 3
Name: PAY_3, dtype: int64
Valores únicos de PAY_4: 0 16455
-1 5687
-2 4348
2 3159
3 180
4 69
7 58
5 35
6 5
8 2
1 2
Name: PAY_4, dtype: int64
Valores únicos de PAY_5: 0 16947
-1 5539
-2 4546
2 2626
3 178
4 84
7 58
5 17
6 4
8 1
Name: PAY_5, dtype: int64
Valores únicos de PAY_6: 0 16286
-1 5740
-2 4895
2 2766
3 184
4 49
7 46
6 19
5 13
8 2
Name: PAY_6, dtype: int64

As variáveis PAY_0 a PAY_6 apresentam grande volume de entradas com valor 0 e outro volume considerável de valores -2.

Vamos trabalhar com a hipótese de que ambos esses valores (0 e -2) representam que a conta foi devidamente paga sem atrasos, ou seja, podem ser substituídas por -1, sendo assim agrupadas à categoria de conta paga em dia.

Distribuição da variável alvo

Vamos plotar um gráfico de barras para avaliar o balanceamento da variável default.payment.next.month que é nossa variável alvo, ou seja, aquela que queremos prever.

distribuição da variável alvo

O dataset apresenta desbalanceamento leve da variável alvo.

De forma geral, os modelos conseguem melhor desempenho em datasets balanceados, no entanto, iremos treinar primeiramente com a distribuição verdadeira dos dados. Caso seja necessário, ou seja, caso o modelo não generalize e não apresente bom desempenho, faremos o balanceamento.

Correlação entre variáveis

Vamos explorar a correlação entre as variáveis numéricas.

Através do coeficiente de correlação criamos uma matriz de correlação e geramos um heatmap para melhor visualização.

heatmap da correlação das variáveis

Antes de realizarmos qualquer limpeza, balanceamento ou padronização é possível observar que a variável alvo não tem correlação alta com nenhuma das variáveis. Isso certamente irá mudar após realizarmos a preparação dos dados.

De toda forma, é interessante já pontuarmos:

  • as variáveis PAY_0 a PAY_6 apresentam certa correlação positiva com a variável alvo, assim como entre si, o que faz sentido já que o status dos pagamentos das contas (em dia ou atrasada em x meses) tem influência na chance de inadimplência do mês seguinte.
  • a variável LIMIT_BAL apresenta correlação positiva com as variáveis BILL_AMT1 a BILL_AMT6 o que era esperado.
  • as variáveis BILL_AMT1 a BILL_AMT6 também tem correlação positiva entre si e faz sentido pois os valores das contas dos clientes tende a permanecer num padrão.

Preparação dos dados

Neste momento faremos a preparação dos dados para que possamos construir nosso modelo.

Conforme identificamos nas etapas anteriores, seguiremos os seguintes passos para a preparação:

  • Limpeza dos dados agrupando entradas das variáveis e excluindo outliers;
  • Seleção das variáveis que são interessantes e eliminação das que não serão úteis ou são menos relevantes ao modelo assim como junção de colunas similares;
  • Codificação das variáveis categóricas (binárias e múltiplas) em numéricas discretas;
  • Divisão do dataset em treino e teste.

Limpeza dos dados

Faremos as substituições necessárias nas entradas das variáveis categóricas EDUCATION, MARRIAGE e PAY_0 a PAY_6 agrupando valores conforme explicado anteriomente.

Valores únicos de EDUCATION após limpeza:2    14030
1 10585
3 4917
4 468
Name: EDUCATION, dtype: int64
Valores únicos de MARRIAGE após limpeza:2 15964
1 13659
3 377
Name: MARRIAGE, dtype: int64
Valores únicos de PAY_0 após limpeza:-1 23182
1 3688
2 2667
3 322
4 76
5 26
8 19
6 11
7 9
Name: PAY_0, dtype: int64
Valores únicos de PAY_2 após limpeza:-1 25562
2 3927
3 326
4 99
1 28
5 25
7 20
6 12
8 1
Name: PAY_2, dtype: int64
Valores únicos de PAY_3 após limpeza:-1 25787
2 3819
3 240
4 76
7 27
6 23
5 21
1 4
8 3
Name: PAY_3, dtype: int64
Valores únicos de PAY_4 após limpeza:-1 26490
2 3159
3 180
4 69
7 58
5 35
6 5
8 2
1 2
Name: PAY_4, dtype: int64
Valores únicos de PAY_5 após limpeza:-1 27032
2 2626
3 178
4 84
7 58
5 17
6 4
8 1
Name: PAY_5, dtype: int64
Valores únicos de PAY_6 após limpeza:-1 26921
2 2766
3 184
4 49
7 46
6 19
5 13
8 2
Name: PAY_6, dtype: int64

Faremos também a exclusão de algumas entradas que foram verificadas com outliers e podem prejudicar nosso modelo.

Serão excluídos as entradas com os seguintes valores:

  • valores acima de 700000 para a variável LIMIT_BAL;
  • valores acima de 500000 e abaixo de -100000 para as variáveis BILL_AMT1 a BILL_AMT6;
  • valores acima de 250000 para as variáveis PAY_AMT1 a PAY_AMT6.
novos boxplots após limpeza

Seleção de variáveis

Iremos desconsiderar a variável ID, conforme identificamos anteriormente ela não é relevante para a construção do nosso modelo.

Além disso, vamos criar uma coluna TOTAL_BILL que será a somatória das colunas BILL_AMT1 a BILL_AMT6, ou seja, o total do valor das contas de abril a setembro de 2005, na moeda local (novo dólar taiwanês).

Similarmente, vamos criar a coluna TOTAL_PAY que será a somatória das colunas PAY_AMT1 a PAY_AMT6, ou seja, o total do valor dos pagamentos de abril a setembro de 2005, na moeda local (novo dólar taiwanês).

Colunas após limpeza:<class 'pandas.core.frame.DataFrame'>
Int64Index: 29843 entries, 0 to 29999
Data columns (total 14 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 LIMIT_BAL 29843 non-null float64
1 SEX 29843 non-null int64
2 EDUCATION 29843 non-null int64
3 MARRIAGE 29843 non-null int64
4 AGE 29843 non-null int64
5 PAY_0 29843 non-null int64
6 PAY_2 29843 non-null int64
7 PAY_3 29843 non-null int64
8 PAY_4 29843 non-null int64
9 PAY_5 29843 non-null int64
10 PAY_6 29843 non-null int64
11 default.payment.next.month 29843 non-null int64
12 TOTAL_BILL 29843 non-null float64
13 TOTAL_PAY 29843 non-null float64
dtypes: float64(3), int64(11)
memory usage: 3.4 MB
None

Vamos plotar nova matriz de correlação para verificar como a limpeza afetou as correlações das variáveis.

heatmap da correlação das variáveis após limpeza

É possível perceber um pequeno aumento na correlação da vaiável alvo com as variáveis PAY_0 a PAY_6.

Codificação das variáveis categóricas

Faremos a transformação das variáveis categóricas múltiplas EDUCATION, MARRIAGE e PAY_0 a PAY_6 com get_dummies.

Agora nosso dataset está preparado para a próxima etapa: modelagem.

Divisão do dataset

Vamos dividir o dataset entre treino e teste. Os dados de teste serão usados somente ao final.

Formato de X_train: (22352, 56)
Formato de y_train: (22352,)
Formato de X_test: (7451, 56)
Formato de y_test: (7451,)

Modelos de Machine Learning

Para escolher o modelo final, iremos realizar alguns testes.

Como premissa de projeto, iremos avaliar as métricas recall e F-1 score. Vamos entender o porquê da escolha dessas métricas.

O objetivo é prever o default, ou seja, quando um cliente ficará inadimplente. Para isso, queremos que o número de true positives (verdadeiro=1, previsto=1) seja o mais alto possível e o número de false negatives (verdadeiro=1, previsto=0) seja o menor possível.

Por outro lado, se o modelo prever alguns false positives (verdadeiro=0, previsto=1), não há tanto problema pois esse erro não custará tão caro para a instituição financeira. Logo, a acurácia do modelo não é nossa métrica de maior preocupação para este projeto.

Padronização e Balanceamento

Primeiramente vamos treinar um modelo de base com os dados desbalanceados e padronizados com StandardScaler para obtermos o desempenho.

Em seguida, faremos testes com 2 tipos de padronização (StandardScaler e MinMaxScaler) e 2 tipos de balanceamento (RandomUnderSampler e SMOTE) afim de escolhermos a melhor dupla de padronização e balanceamento.

O modelo escolhido para realizar esses testes foi Logistic Regression.

Baseline — StandardScaler + dados desbalanceados

Baseline:
Recall: 0.35 (+/-0.02)
F-1 score: 0.46 (+/-0.02)

Teste 1 — StandardScaler + RandomUnderSampler

Teste 1: StandardScaler + RandomUnderSampler
Recall: 0.56 (+/-0.02)
F-1 score: 0.65 (+/-0.01)

Teste 2 — StandardScaler + SMOTE

Teste 2: StandardScaler + SMOTE
Recall: 0.56 (+/-0.01)
F-1 score: 0.65 (+/-0.01)

Teste 3 — MinMaxScaler + RandomUnderSampler

Teste 3: MinMaxScaler + RandomUnderSampler
Recall: 0.56 (+/-0.02)
F-1 score: 0.65 (+/-0.02)

Teste 4 — MinMaxScaler + SMOTE

Teste 4: MinMaxScaler + SMOTE
Recall: 0.56 (+/-0.02)
F-1 score: 0.65 (+/-0.01)

Conforme testamos acima, houve aumento significativo do baseline para o primeiro teste após o balanceamento.

Entre os testes 1 a 4, ou seja, variando as formas de balanceamento e padronização, não houve diferença significativa.

De toda forma, vamos adotar a combinação do teste 4: padronização com o MinMaxScaler e o balanceamento com SMOTE.

Vamos plotar o novo gráfico de barras para ver o balanceamento dos dados, assim como o tamanho do dataset.

distribuição da variável alvo após balanceamento com SMOTE
Formato de X_train_final: (34806, 56)
Formato de y_train_final: (34806,)
Formato de X_test: (7451, 56)
Formato de y_test: (7451,)

Escolha dos modelos

Vamos fazer a validação cruzada para vários modelos de classificação afim de obter seus desempenhos (através das métricas recall e f1-score) e então escolher um ou dois que nos pareça mais adequado.

Os modelos são:

  • Random Forest
  • Decision Tree
  • SGD
  • SVC
  • XGBoost
  • LGBM
recall e f-1 score para cada modelo testado com o cross-validation

Modelo 1 — Random Forest

O modelo que obteve melhor desempenho nos dados de treino foi o Random Forest, então vamos treiná-lo e ver seu desempenho nos dados de teste.

Pelo visto o modelo Random Forest não conseguiu generalizar bem para os dados de teste. Vamos testar um modelo que não seja de árvore para ver se conseguimos um desempenho mais próximo daquele com os dados de treino.

Modelo 2 — SVC

matriz de confusão para modelo SVC

Conclusão

Após concluir todas as etapas de um projeto de Machine Learning percebemos que variar os métodos de padronização e balanceamento não apresentaram resultados com diferenças significativas. Por outro lado, os dados que não receberam nenhum tratamento de balanceamento apresentaram desempenho significativamente inferior, reforçando a importancia do balanceamento dos dados para problemas de classificação.

Os modelos de árvore apresentaram bom desempenho nos dados de treino, porém o modelo Random Forest não obteve desempenho similar nos dados de teste, ou seja, não foi capaz de generalizar, se tornando insatisfatório.

O modelo escolhido foi o SVC onde conseguimos desempenho nos dados de teste similar ao desempenho com dados de treino, ou seja, o modelo conseguiu generalizar dentro do esperado. Obtivemos um desempenho mais realista e satisfatório.

Obrigada !

Obrigada pelo seu tempo!

Espero que tenha gostado! Se tiver qualquer dúvida, feedback ou sugestão vou adorar que você entre em contato comigo pelo LinkedIn.

Para ver esse e outros projetos completos, acesse meu portfólio completo no GitHub.

Valeu!

--

--

Pandas Couple
Pandas Couple

Written by Pandas Couple

Casal de Cientistas de Dados, contribuindo para a comunidade de Data Science.

No responses yet