Interpretable binning with temporal stability diagnostics for credit risk, PD, and scorecard workflows.
Project description
RiskBands
Binning para risco de credito com foco em robustez temporal, comparacao entre candidatos e racional auditavel.
Documentacao oficial | PyPI | Benchmark PD vintage | Quickstart | Auditoria e plots | API
O que e o RiskBands
O RiskBands e uma biblioteca para construir, comparar e auditar candidatos de binning quando o problema real nao e apenas maximizar uma metrica estatica, mas tambem defender o resultado ao longo do tempo.
Ele foi pensado especialmente para contextos como:
- modelos de PD
- scorecards de credito
- variaveis com drift temporal
- leitura relevante por safra ou vintage
- estruturas com bins raros, baixa cobertura ou reversoes de ranking
A pergunta central do projeto e simples:
Um binning que parece otimo no agregado continua defensavel quando voce abre o comportamento por safra?
Onde o projeto se diferencia
O OptimalBinning ja resolve muito bem o problema de corte estatico. O
RiskBands nao tenta negar isso.
No fluxo supervisionado numerico do repositorio atual, o projeto reaproveita
optbinning.OptimalBinning no backend do corte estatico. O diferencial esta no
que vem depois:
- diagnostico temporal por variavel, bin e periodo
- penalizacoes estruturais para fragilidade, baixa cobertura e volatilidade
- comparacao entre candidatos via
BinComparator - score objetivo mais alinhado a trade-offs de risco de credito
- resumos auditaveis para explicar por que um candidato venceu
Em outras palavras:
OptimalBinningpuro ajuda a encontrar um bom corte estaticoRiskBandsajuda a decidir se esse corte continua sendo a melhor resposta para credito quando o tempo entra na analise
Arquitetura de score
O repositório agora expõe dois caminhos explícitos de score:
legacyMantém o objetivo histórico baseado em componentes positivos menos penalidades.stableIntroduz a estratégia pública recomendada para robustez temporal, orientada a minimização, com componentes normalizados e foco em equilíbrio entre separação e estabilidade.
O stable combina:
- variância temporal ponderada do WoE shrinkado
- drift entre janelas adjacentes
- penalidade de inversão de ranking entre bins
- penalidade de separação insuficiente
- entropy penalty para distribuições degeneradas
- PSI como proxy de estabilidade em produção
Todos os componentes são normalizados em modo absoluto, então o score funciona mesmo quando apenas um candidato está sendo avaliado.
Os pesos padrão são:
temporal_variance_weight=0.22window_drift_weight=0.18rank_inversion_weight=0.20separation_weight=0.20entropy_weight=0.08psi_weight=0.12
O shrink de WoE é tratado como camada de robustez, não como score isolado:
- WoE raw por bin e período
- shrink em direção ao WoE global do bin
- uso do WoE shrinkado nos componentes temporais
Instalacao
Instalacao base:
pip install riskbands
Extra opcional para graficos Plotly e export HTML dos benchmarks:
pip install "riskbands[viz]"
Para desenvolvimento, testes e notebooks:
git clone https://github.com/joaaomaia/RiskBands.git
cd RiskBands
pip install -e .[dev]
Pacote no PyPI: riskbands.
Como comecar
Porta tecnica:
Porta metodologica:
- Por que RiskBands
- Por que nao usar apenas OptimalBinning
- Benchmark PD vintage
- Como ler os graficos
- Robustez temporal em risco de credito
Tipos de entrada suportados
A API atual e orientada a pandas.DataFrame e pandas.Series. O fluxo usual e
treinar com fit(df, y="target", column="score", time_col="month") ou com uma
lista de colunas em um DataFrame pandas.
PySpark DataFrame ainda nao e suportado nativamente. Para bases grandes em
Spark ou Databricks, a recomendacao conservadora e treinar o binning em uma
amostra, agregado ou extrato pandas auditavel, exportar as regras e aplica-las
depois no ambiente distribuido.
Variaveis categoricas e overrides
Colunas categoricas podem ser marcadas explicitamente com force_categorical.
O tratamento de categorias raras, valores missing e categorias desconhecidas no
transform e deterministico, com mapeamento aprendido no fit.
binner = Binner(
strategy="supervised",
force_categorical=["rating_interno"],
)
Quando a inferencia automatica de tipo nao for suficiente, use
force_numeric e force_categorical para fixar a intencao. A mesma coluna nao
deve aparecer nas duas listas; isso e tratado como conflito.
binner = Binner(
strategy="supervised",
force_numeric=["qtd_restritivos"],
force_categorical=["rating_interno"],
)
Export auditavel e supply chain
export_bundle(...) gera artefatos tabulares e JSON para auditoria. Nomes de
features usados nos artefatos exportados sao sanitizados para evitar paths
inseguros, mantendo a rastreabilidade dos nomes originais no manifest.
Dependencias de solver e verificacoes de supply chain ficam resumidas em docs/supply_chain_dependencies.md.
Quickstart minimo
import numpy as np
import pandas as pd
from riskbands import Binner
rng = np.random.default_rng(0)
n = 800
df = pd.DataFrame({"score": rng.normal(size=n)})
df["month"] = rng.choice([202301, 202302, 202303, 202304], size=n)
proba = 0.20 + 0.15 * df["score"] + 0.02 * (df["month"] - 202301)
proba = np.clip(proba, 0.01, 0.99)
df["target"] = (rng.random(n) < proba).astype(int)
binner = Binner(
strategy="supervised",
max_n_bins=5,
check_stability=True,
monotonic="ascending",
min_event_rate_diff=0.03,
score_strategy="stable",
normalization_strategy="absolute",
woe_shrinkage_strength=40.0,
)
binner.fit(df, y="target", column="score", time_col="month")
score_bins = binner.transform(df["score"])
summary = binner.summary()
score_table = binner.score_table()
audit_table = binner.audit_table()
binner.export_binnings_json("artifacts/riskbands_binnings.json")
binner.export_bundle("artifacts/quickstart_run")
binner.plot_bad_rate_over_time(df, y="target", column="score", time_col="month")
binner.plot_bad_rate_heatmap(df, y="target", column="score", time_col="month")
binner.plot_bin_share_over_time(df, y="target", column="score", time_col="month")
binner.plot_score_components(column="score")
Fluxo mais amigavel, no estilo sklearn/pandas:
fit(df, y="target", column="score", time_col="month")transform(df)outransform(df["score"])fit_transform(df["score"], y=df["target"])binning_table(),score_table(),audit_table(),report(),diagnostics()export_binnings_json()eexport_bundle()para auditoria e governanca- plots diretos para bad rate, heatmap, share temporal e score components
get_params()eset_params(...)com aliases comomax_n_binsemonotonic_trend
Customização do objective
binner = Binner(
strategy="supervised",
check_stability=True,
use_optuna=True,
time_col="month",
score_strategy="stable",
score_weights={
"temporal_variance_weight": 0.18,
"window_drift_weight": 0.16,
"rank_inversion_weight": 0.22,
"separation_weight": 0.24,
"entropy_weight": 0.08,
"psi_weight": 0.12,
},
normalization_strategy="absolute",
woe_shrinkage_strength=35.0,
strategy_kwargs={"n_trials": 10},
)
Leitura rápida:
- no
legacy, maiores scores continuam melhores - no
stable, menores scores são melhores - relatórios auditáveis expõem score final, componentes raw, componentes normalizados, pesos, estratégia e parâmetros de shrink
Benchmark principal do repositorio
O benchmark mais importante hoje compara tres lentes:
OptimalBinningpuro como baseline externaRiskBandsestatico como baseline internaRiskBandsbalanceado/temporal como abordagem orientada a credito
Materiais principais:
- pd_vintage_benchmark.py
- pd_vintage_benchmark.ipynb
- riskbands_synthetic_plotly_comparative_demo.ipynb
- stable_score_demo.py
- pd_vintage_champion_challenger.py
- temporal_stability_example.py
O que o projeto nao tenta ser
O foco do RiskBands e binning. Ele nao tenta, sozinho, ser:
- pipeline completo de modelagem de PD
- framework de monitoramento de carteira
- solucao completa de MLOps para credito
A proposta e ser uma camada especializada e forte de decisao sobre binning.
Mensagem principal
O RiskBands nao tenta substituir a forca do OptimalBinning.
Ele tenta responder melhor a pergunta que aparece no mundo real de credito:
Entre os candidatos que parecem bons no agregado, qual continua mais defensavel quando o tempo entra na decisao?
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file riskbands-2.0.3.tar.gz.
File metadata
- Download URL: riskbands-2.0.3.tar.gz
- Upload date:
- Size: 79.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c68a26de52c20fe8d0f2e55a3f0d74ae9b120fb334b5fc8820639b355a712977
|
|
| MD5 |
5cbf5f2f9304c87e1f8d1bc6bd0edfde
|
|
| BLAKE2b-256 |
e0693f7aa3305646612a3fde74ca4f449e8d44267afdd6caeb48842a57150123
|
Provenance
The following attestation bundles were made for riskbands-2.0.3.tar.gz:
Publisher:
publish-pypi.yml on joaaomaia/RiskBands
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
riskbands-2.0.3.tar.gz -
Subject digest:
c68a26de52c20fe8d0f2e55a3f0d74ae9b120fb334b5fc8820639b355a712977 - Sigstore transparency entry: 1430065406
- Sigstore integration time:
-
Permalink:
joaaomaia/RiskBands@688ca4b1d620fc461a24ccc2435b4ad0457bf88e -
Branch / Tag:
refs/tags/v2.0.3 - Owner: https://github.com/joaaomaia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@688ca4b1d620fc461a24ccc2435b4ad0457bf88e -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file riskbands-2.0.3-py3-none-any.whl.
File metadata
- Download URL: riskbands-2.0.3-py3-none-any.whl
- Upload date:
- Size: 68.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
305e975bb6d304501a8f5a928952bdd9a4732fb063186e94359276d825522269
|
|
| MD5 |
b14ce010ea1fad4cdf920fce168e73ef
|
|
| BLAKE2b-256 |
2cefb8cf5959e6134e93a0c6e17fe3357de6aa7e079054cf0ccb56af3ed1351e
|
Provenance
The following attestation bundles were made for riskbands-2.0.3-py3-none-any.whl:
Publisher:
publish-pypi.yml on joaaomaia/RiskBands
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
riskbands-2.0.3-py3-none-any.whl -
Subject digest:
305e975bb6d304501a8f5a928952bdd9a4732fb063186e94359276d825522269 - Sigstore transparency entry: 1430065505
- Sigstore integration time:
-
Permalink:
joaaomaia/RiskBands@688ca4b1d620fc461a24ccc2435b4ad0457bf88e -
Branch / Tag:
refs/tags/v2.0.3 - Owner: https://github.com/joaaomaia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@688ca4b1d620fc461a24ccc2435b4ad0457bf88e -
Trigger Event:
workflow_dispatch
-
Statement type: