Credit Default API
Estimar a probabilidade de um cliente de cartão ficar inadimplente no próximo mês, escolher o limiar de decisão de propósito e servir tudo como uma API REST validada.
- dataset
- Default of Credit Card Clients (UCI)
- tarefa
- classificação binária · default no próximo mês
- modelo
- XGBoost (grid search) + threshold 0,24
- serving
- FastAPI · POST /predict · Docker
- interface
- cliente Streamlit via Docker Compose
- status
- roda localmente · sem deploy público ainda
- repositório
- github.com/Igor-Millis/credit-default-api ↗
Acurácia é o alvo errado
Com 77,9% dos clientes adimplentes, um modelo que prevê “não inadimplente” para todos já tem 78% de acurácia — e não serve para nada. Os custos dos dois erros não são simétricos: deixar passar um inadimplente costuma ser pior do que mandar um bom cliente para revisão.
Por isso o projeto avalia precision, recall, F1 e ROC AUC, mantém um conjunto de teste estratificado (6.000 clientes) intocado até o fim e trata o limiar de decisão como uma escolha de modelagem.
Um pipeline, quatro famílias de modelos
O pré-processamento fica dentro de um Pipeline do scikit-learn, para que treino e inferência apliquem as mesmas transformações: StandardScaler nos valores, OneHotEncoder em sexo, escolaridade e estado civil, e o histórico ordinal de pagamentos (PAY_0…PAY_6) mantido na escala original. Cada família foi treinada como baseline e com grid search.
| modelo | variante | acurácia | precision | recall | F1 | ROC AUC |
|---|---|---|---|---|---|---|
| Logistic Regression | baseline | 0,8095 | 0,6966 | 0,2457 | 0,3632 | 0,7100 |
| Logistic Regression | grid search | 0,6780 | 0,3675 | 0,6323 | 0,4648 | 0,7105 |
| Decision Tree | tuned | 0,8173 | — | — | — | 0,7477 |
| Random Forest | baseline | 0,8135 | — | — | — | 0,7546 |
| Random Forest | grid search | 0,7867 | — | — | — | 0,7735 |
| XGBoost | baseline | 0,8183 | 0,6626 | 0,3640 | 0,4698 | 0,7731 |
| XGBoost | grid search | 0,8173 | 0,6620 | 0,3557 | 0,4627 | 0,7770 |
Conjunto de teste, threshold 0,50. Árvore de Decisão e Random Forest têm apenas acurácia e AUC na fonte. Fonte: README.md, notebooks/09_model_selection.ipynb.
melhores hiperparâmetros · 72 combinações · CV ROC AUC 0,7853
colsample_bytree = 0.8 learning_rate = 0.05 max_depth = 7 min_child_weight = 5 n_estimators = 100 reg_lambda = 5 subsample = 0.8
Mover o limiar é o ganho de verdade
No corte padrão de 0,50, o grid search quase não mudou o XGBoost (AUC 0,773 → 0,777) e o recall ficou perto de 36%. A ordenação das probabilidades era boa; o gargalo era a regra de decisão. Arraste o threshold:
O modelo final usa τ = 0,24: passa a encontrar 60,1% dos inadimplentes em vez de 35,6%, trocando precision (0,66 → 0,49) por recall — uma decisão de negócio explicitada. Por que 0,24 e o que isso implica está no artigo “Um threshold é uma decisão”.
Matriz de confusão final — teste, τ = 0,24
| métrica | valor |
|---|---|
| acurácia | 0,7733 |
| precision | 0,4899 |
| recall | 0,6006 |
| F1 | 0,5396 |
| ROC AUC | 0,7770 |
O que move a previsão
A análise SHAP e a importância de variáveis do modelo concordam: o histórico recente de pagamentos domina. Só o PAY_0 (status do mês mais recente) concentra cerca de 44% da importância.
Do pickle à API
O pipeline e o threshold são carregados uma vez com joblib. POST /predict recebe as 23 variáveis do cliente, validadas por um schema Pydantic com domínios Literal para os códigos categóricos — SEX = 7 é recusado com HTTP 422 antes de chegar ao modelo.
POST /predict
{
"LIMIT_BAL": 20000, "SEX": 1, "EDUCATION": 2, "MARRIAGE": 1, "AGE": 24,
"PAY_0": 2, "PAY_2": 2, "PAY_3": 0, "PAY_4": 0, "PAY_5": 0, "PAY_6": 0,
"BILL_AMT1": 3913, ... "PAY_AMT6": 0
}
→ 200 OK
{
"probability": 0.3297,
"threshold": 0.24,
"prediction": 1,
"classification": "inadimplente"
}- Testes: pytest +
TestClientdo FastAPI — entrada válida retorna 200 com probabilidade e threshold em [0, 1]; categoria inválida retorna 422. - Docker: imagem Python 3.13 com um
requirements-api.txtmínimo; só os artefatos do modelo final são copiados. - Compose: um cliente Streamlit conversa com o contêiner da API pela rede interna.
Limitações e próximos passos
- Ainda sem deploy público — a API roda localmente ou no Docker.
- Calibração verificada depois (artigo): os escores são calibrados (ECE ≈ 0,008 no teste), então τ = 0,24 pode ser lido como uma razão de custos de cerca de 3×.
- Planejado: deploy, integração da interface com a API hospedada e documentação final.