150 mil solicitantes, 11 colunas, 6,68% de inadimplência. Parece uma base limpa de tutorial. Não é — e as seis armadilhas que encontrei nela são um catálogo do que dá errado quando se confia num read_csv e parte para a modelagem.
1. O arquivo nem abre direito
Separador ;, quebra de linha CRLF. pd.read_csv no padrão não entrega a base — entrega uma coluna só. Começo modesto, mas sintomático: dado real raramente chega no formato do exemplo.
2. Duas colunas numéricas chegaram como texto
Esta é a armadilha cara. uso_limite_rotativo e razao_divida vieram como texto porque a exportação em locale pt-BR gravou os valores ≥ 1 com ponto de milhar. A reação automática seria:
# o conserto que estraga tudo df["razao_divida"] = pd.to_numeric(df["razao_divida"], errors="coerce")
errors="coerce" transforma o que não converte em NaN. Silenciosamente. Nesta base, isso apagaria 2.950 clientes — que têm 40% de inadimplência, seis vezes a taxa da base. O modelo perderia justamente o grupo que ele existe para identificar, e ninguém veria um erro na tela.
errors="coerce" é uma decisão de negócio disfarçada de argumento técnico. Antes de usá-lo, conte quantas linhas ele descarta e olhe a taxa do alvo nelas.
3. Sentinelas 96 e 98 nas colunas de atraso
269 clientes com valores 96 ou 98 nas colunas de atraso — sempre nas três ao mesmo tempo. Não é alguém que atrasou 96 vezes: é código de sistema legado para "sem informação". E esse grupo tem 54,65% de inadimplência. Tratar como número real distorce qualquer estatística; jogar fora descarta um sinal fortíssimo.
4. Um teto artificial em 2,0
uso_limite_rotativo e razao_divida têm um vácuo total de registros entre 2 e 10, com 20,81% da base empilhada exatamente em 2,0. Isso é truncamento na origem, não comportamento de cliente. Um outlier natural é raro e espalhado; um empilhamento em número redondo é regra de sistema.
5. A ausência de dado é sinal de risco MENOR
O achado mais contraintuitivo. Quem não informou renda ou dependentes tem inadimplência abaixo da média — provavelmente perfis que passaram por um fluxo diferente de originação. Por isso renda_ausente e dependentes_ausente entraram como features, não como lixo a imputar e esquecer. O fato de faltar carrega informação.
6. Não existe identificador nem coluna temporal
Sem id_cliente e sem data, duas coisas ficam impossíveis: split temporal (treinar no passado, testar no futuro, que é o protocolo correto em crédito) e detecção de drift. Isso não tem conserto no escopo do projeto — então virou limitação declarada, registrada na documentação. Projeto honesto não esconde o que não dá para fazer.
O que fica
Nenhuma dessas seis coisas aparece em df.info(). Todas aparecem quando você cruza cada coluna com o alvo e pergunta "esse valor faz sentido para um ser humano?". A fase de entendimento dos dados não é burocracia do CRISP-DM — é onde o projeto deixa de ser sobre o dataset e passa a ser sobre o negócio que o gerou.
Parte 3 de 12 da série Aurora — um modelo de risco de crédito do zero ao deploy. O mapa completo da série reúne todas as partes e os números finais. Veja também a documentação técnica e o código no GitHub ↗.