Saltar a contenido

Binning, WoE/IV y selección de variables

Esta guía cubre las dos primeras etapas computacionales del pipeline de scorecard (Fase 1): el binning supervisado con Weight of Evidence (WoE) e Information Value (IV), y la selección de variables previa al modelo. Son los pasos que transforman variables crudas en predictores estables e interpretables y descartan lo que no aporta o rompe la regresión.

Dónde encaja

En el pipeline F1 el orden es binning → selección → modelo → scorecard → calibración → desempeño/estabilidad (ver Conceptos). El binning produce columnas WoE (una por variable cruda, con sufijo __woe); la selección filtra ese universo y publica el subconjunto que entra a la regresión logística. Ambas etapas son secciones declarativas del mismo BayesRiskConfig: config.binning y config.selection.

Superficie estable (SemVer 2.x)

Las secciones binning y selection del config son parte del pipeline de scorecard F1 y desde la versión 1,0 son API estable (no rompen hasta una versión 3,0).

Weight of Evidence (WoE)

El binning discretiza cada variable en bins (tramos numéricos o grupos de categorías) y reemplaza el valor crudo por el WoE de su bin. El WoE mide, para cada bin, cuánto se aparta la mezcla buenos/malos de ese tramo respecto de la población total.

Con la convención del motor (el evento es el malo / default), para el bin (i):

WoE_i = ln( (Non-event_i / Non-event_total) / (Event_i / Event_total) )
      = ln( distribución_de_buenos_i / distribución_de_malos_i )

Interpretación del signo (clave para leer una scorecard):

  • WoE > 0: el bin concentra más buenos que la media → menor riesgo.
  • WoE < 0: el bin concentra más malos que la media → mayor riesgo.
  • WoE = 0: el bin replica la mezcla poblacional (neutral).

Trabajar en escala WoE tiene tres ventajas que importan para una scorecard regulatoria: linealiza la relación variable–log-odds (encaja directo en la regresión logística), es monótono respecto de la tasa de evento del bin, y da un tratamiento explícito y auditable a missing y valores especiales (cada uno recibe su propio bin y su propio WoE).

Ejemplo: bins reales de ingreso_mensual

De la corrida de ejemplo (web/src/fixtures/demo/results.json, tabla del dominio binning para ingreso_mensual), el motor resolvió 6 bins numéricos más los bins técnicos Special y Missing:

Bin Count Tasa de evento WoE
(-inf, 242795.88) 210 0,4286 -0,9022
[242795.88, 354256.50) 587 0,3526 -0,5825
[354256.50, 464805.09) 806 0,2816 -0,2536
[464805.09, 631639.47) 917 0,2268 +0,0364
[631639.47, 913196.97) 876 0,1655 +0,4278
[913196.97, inf) 565 0,0832 +1,2099
Total 3961 0,2333 —

A mayor ingreso, la tasa de evento (default) baja de forma monótona (0,4286 → 0,0832) y el WoE sube de forma monótona (-0,9022 → +1,2099): el tramo de ingreso más bajo es el de mayor riesgo y el más alto el de menor riesgo. Este es exactamente el comportamiento que fuerza la restricción de monotonía (ver más abajo). Los bins Special y Missing existen aunque en este dataset sintético estén vacíos; en datos reales capturan centinelas y faltantes con su propio WoE.

Information Value (IV)

El IV resume, en un solo número por variable, cuánto separa a buenos de malos el binning completo. Se obtiene sumando el aporte de cada bin:

IV_i = (distribución_de_buenos_i - distribución_de_malos_i) * WoE_i
IV   = Σ_i IV_i     (suma sobre todos los bins de la variable)

Cada término combina la diferencia de masa entre buenos y malos del bin con su WoE, así que premia bins que además de estar sesgados hacia un lado concentran volumen. En el ejemplo de ingreso_mensual, el bin de ingreso más alto aporta el grueso del IV (≈0,145 de un IV total de 0,3048): pocos malos y muchos buenos, bien separados.

Bandas diagnósticas de IV

bayesrisk clasifica el IV en bandas fijas (helper iv_band, SDD-06 §3; frontera con límite inferior inclusivo). Estas bandas son diagnósticas, no son el filtro de corte (eso lo define selection.min_iv):

Banda Rango de IV Lectura
sin poder IV < 0,02 Sin poder predictivo útil
débil 0,02 ≤ IV < 0,10 Aporta poco por sí sola
medio 0,10 ≤ IV < 0,30 Predictor sólido
fuerte 0,30 ≤ IV < 0,50 Muy informativa
sospechoso IV ≥ 0,50 Sospechosamente alto (posible fuga de información)

Esas son las palabras que ves en el panel Selección de variables de Resultados. En el JSON la banda viaja como su identificador; la correspondencia está en la referencia de la API, junto a la de los motivos de exclusión.

Un IV muy alto no es automáticamente bueno: suele delatar una variable que filtra el resultado (leakage) o que es un proxy casi directo del target. Por eso la selección trata el extremo superior como una alerta, no como una virtud (max_iv, abajo).

IV por variable en la corrida de ejemplo

Del mismo fixture (binning.iv_by_variable y monotonicity_by_variable):

Variable IV Banda Monotonía (tasa de evento)
ingreso_mensual 0,3048 fuerte descending
deuda_ingreso 0,1635 medio ascending
utilizacion_linea 0,0610 débil ascending
antiguedad_meses 0,0414 débil descending
mora_max_12m 0,0219 débil ascending
segmento 0,0029 sin poder — (categórica, sin monotonía)

optbinning_version registrado en la corrida: 0.20.0.

Binning óptimo monotónico (OptBinning)

El binning de bayesrisk es supervisado y óptimo: no usa cortes arbitrarios (cuantiles fijos, ancho constante), sino que resuelve un problema de optimización sobre el target. El motor es OptBinning, envuelto por la sección binning.

El proceso, por variable, es en dos fases:

  1. Prebinning: se generan hasta max_n_prebins cortes candidatos (por defecto 20), cada uno con al menos min_prebin_size de la masa (por defecto 5%).
  2. Optimización: se agrupan los prebins en el conjunto final de bins que maximiza el IV sujeto a las restricciones declaradas: número de bins (max_n_bins, min_n_bins), tamaño mínimo por bin (min_bin_size), mínimos de eventos/no-eventos por bin, y sobre todo la forma monótona exigida.

Por qué monotonía

La monotonía obliga a que la tasa de evento (riesgo) se mueva en una sola dirección a través de los bins ordenados. No es un capricho estadístico; es un requisito de negocio y regulatorio:

  • Interpretabilidad y defensa ante tu regulador y ante la validación interna (SR 11-7): "a mayor deuda/ingreso, mayor riesgo" es una relación que se puede explicar y validar. Un binning no monótono (riesgo que sube, baja y vuelve a subir) casi siempre está ajustando ruido de la muestra.
  • Robustez fuera de muestra: las reversiones locales de riesgo raramente sobreviven en Holdout u OOT. Forzar monotonía es una forma de regularización que reduce el sobreajuste del binning.
  • Coherencia con la scorecard: si el WoE es monótono, el puntaje que asigna la scorecard también lo es, y no aparecen puntajes contraintuitivos.

El default de bayesrisk es monotonic_trend="auto_asc_desc": el solver elige automáticamente entre tendencia ascendente o descendente de la tasa de evento según los datos, sin permitir formas no monótonas. En el ejemplo, eligió descending para ingreso_mensual (más ingreso → menos riesgo) y ascending para deuda_ingreso (más carga → más riesgo).

Valores admitidos por monotonic_trend (tipo MonotonicTrend): auto, auto_heuristic, auto_asc_desc, ascending, descending, concave, convex, peak, peak_heuristic, valley, valley_heuristic. Las formas peak/valley (U o U invertida) se usan cuando la relación con el riesgo genuinamente no es monótona; deben justificarse, no ser el default.

Parámetros de la sección binning

Los nombres y defaults salen directo de bayesrisk.binning.config.BinningConfig:

Parámetro Default Qué controla
feature_columns "*" Variables a binear ("*" = todas las no estructurales).
categorical_columns () Fuerza tratamiento categórico (p. ej. códigos numéricos).
max_n_prebins 20 Cortes candidatos antes de optimizar.
min_prebin_size 0.05 Masa mínima por prebin candidato.
max_n_bins 8 Tope de bins finales por variable.
min_n_bins None Piso de bins finales (None = lo decide el solver).
min_bin_size 0.05 Masa mínima por bin final.
min_bin_n_event / min_bin_n_nonevent 1 / 1 Mínimo de malos / buenos por bin.
monotonic_trend "auto_asc_desc" Forma monótona exigida (ver arriba).
min_event_rate_diff 0.0 Separación mínima de tasa de evento entre bins vecinos.
max_pvalue None Exige significancia estadística entre bins (opcional).
solver / mip_solver "mip" / "bop" Solver óptimo ("cp" está deshabilitado a propósito).
require_optimal True Descarta variables cuyo solver no probó optimalidad.
n_jobs None 1 core por defecto, por reproducibilidad exacta.
special_handling "separate" Bin propio para valores especiales (vs. fusionar con missing).
metric_special / metric_missing "empirical" WoE de special/missing (empírico o forzado a un valor).
cat_cutoff 0.01 Frecuencia bajo la cual se agrupan niveles categóricos raros.
output_suffix "__woe" Sufijo de las columnas transformadas (p. ej. ingreso_mensual__woe).
fail_on_non_binnable False Si True, una variable constante o 100% missing aborta el fit.

Overrides por variable

variable_overrides permite ajustar monotonic_trend, max_n_bins, min_bin_size, cat_cutoff o dtype para una variable puntual sin tocar los globales — útil cuando una sola variable tiene una forma en U (valley) o necesita más granularidad que el resto.

Una categoría rara que queda sola y sin incumplimientos

El umbral de categorías raras puede dejar un solo nivel por debajo: el grupo de «raras» que debía protegerlo queda igual de solo, y si ese nivel no tiene ningún incumplimiento —o ninguna operación cumplida— en la muestra de desarrollo, su WoE no existe. Cuando pasa, el motor lo reagrupa una vez con el menor umbral que deja dos niveles debajo, lo registra en el trail y lo publica en la card de binning (rare_category_regroupings, con el umbral declarado y el efectivo); el resumen de «Tramos y WoE» lo dice en una línea. El config guardado conserva lo que declaraste: la corrida se reproduce porque la regla es determinista. Si ni juntando las dos categorías más raras aparece un incumplimiento, la corrida se detiene y el mensaje dice qué se intentó: puedes excluir la variable o subir su cat_cutoff en variable_overrides.

Faltantes o valores especiales sin incumplimientos

Si los faltantes de una variable —o sus valores especiales— no tienen ningún incumplimiento en la muestra de desarrollo, o no tienen ninguna operación cumplida, su WoE no existe. En vez de detener la corrida, el motor les asigna el WoE del tramo de la misma variable con la mayor tasa de malos observada: un grupo sin evidencia de incumplimiento no recibe un buen puntaje por falta de datos. La fila conserva IV 0 —no aporta evidencia—, comparte los puntos de ese tramo (un ajuste manual de puntos se hace sobre el tramo, no sobre los faltantes) y la asignación queda en el trail, en la card de binning (assigned_bins) y en una línea del resumen de «Tramos y WoE». Si declaraste un valor numérico en metric_missing o metric_special, la regla no entra.

Excluir una variable

sc.exclude("variable", reason=...) la descarta en toda la corrida: escribe binning.exclude_columns, así que la variable no se tramifica ni aparece en las tablas. Sus tramos fijados con set_bins/merge_bins quedan en suspenso —declarado en el trail— y sc.keep(...) la devuelve con esos mismos cortes.

Una categoría que no existía en Desarrollo

Si Holdout, fuera de tiempo o las operaciones fuera del ajuste traen una categoría que el ajuste no vio —un año fiscal posterior a la frontera, un programa nuevo—, esa variable le asigna WoE 0, el riesgo promedio. El resumen de «Tramos y WoE» lo avisa por variable y por muestra. Si la variable es la fecha con otro nombre (un año o un trimestre), considera excluirla: fuera de tiempo siempre traerá categorías nuevas.

Selección de variables

Tras el binning, la sección selection (bayesrisk.selection.config.SelectionConfig) aplica una batería de filtros auditables y deterministas sobre las columnas WoE candidatas. El objetivo es entregar a la regresión un conjunto que sea predictivo, no redundante y estable. Los filtros se aplican en cascada y cada decisión queda registrada (variable, motivo, métricas).

1. Filtro por IV

  • min_iv (default 0.02): descarta variables con IV bajo el umbral por poder predictivo insuficiente. Coincide con la frontera entre sin poder y débil de iv_band.
  • max_iv (default 0.50) + max_iv_action ("flag" | "exclude"): trata el IV sospechosamente alto. Por defecto solo marca la variable para revisión manual (posible leakage); con "exclude" la descarta automáticamente.

2. Métricas univariadas (diagnóstico y filtro opcional)

Con compute_univariate_metrics=True (default) se calculan AUC, KS y Gini de cada variable por separado en Desarrollo. Son diagnóstico por defecto; se vuelven filtro si se define min_auc, min_ks o min_gini (todos None por defecto). En la corrida de ejemplo, ingreso_mensual alcanzó AUC 0,6473 / KS 0,2118 / Gini 0,2946 univariados, y segmento apenas AUC 0,5146 / KS 0,0234 — coherente con su IV nulo.

3. Filtro por correlación

Descarta redundancia lineal entre columnas WoE (sub-config correlation):

  • method (default "pearson"; también "spearman", "kendall").
  • threshold (default 0.75): si |rho| entre dos variables lo supera, se conserva la de mayor prioridad (ver ranking) y se descarta la otra.
  • clustering_method (default "none"): "none" poda de a una en orden de prioridad; "connected_components" agrupa variables mutuamente correlacionadas y conserva solo la mejor de cada grupo.

4. Filtro por VIF (multicolinealidad)

El Variance Inflation Factor captura colinealidad multivariada (una variable explicada por una combinación de otras), que la correlación por pares puede no ver. Sub-config vif:

  • threshold (default 5.0): VIF máximo tolerado (rangos de corte típicos 5–10).
  • Poda iterativa: en cada ronda se elimina la variable con peor VIF y se recalcula, hasta que todas queden bajo el umbral (max_iterations acota las rondas; None = hasta cumplir).
  • add_intercept (default True): incluye constante en las regresiones auxiliares del VIF.

5. Estabilidad (PSI/CSI por característica)

Sub-config stability: mide el Population Stability Index / Characteristic Stability Index de cada variable en Holdout/OOT contra Desarrollo, para detectar variables cuya distribución se corre en el tiempo.

  • action (default "report_only"): solo informa; con "exclude" descarta las inestables.
  • stable_threshold (default 0.10) y review_threshold (default 0.25): bajo stable_threshold la variable es estable; desde ese valor y por debajo de review_threshold queda "en revisión"; al alcanzar o superar review_threshold pasa a "rediseñar" y, si action="exclude", se descarta.

6. Ranking y overrides de negocio

  • priority_order (default ("iv", "auc", "ks", "name")): ranking determinista para decidir qué variable conservar en los desempates de correlación y VIF ("name" rompe empates alfabéticamente, garantizando reproducibilidad).
  • force_include / force_exclude: negocio conserva o descarta variables por criterio experto. force_include mantiene una variable aunque no pase los filtros (salvo que sea inválida o genere un conflicto de VIF irresoluble); force_exclude tiene prioridad sobre todo. No pueden compartir variables.
  • fail_if_no_features (default True): si ninguna variable sobrevive, la corrida aborta en vez de publicar solo diagnóstico.

Selección en la corrida de ejemplo

Del fixture (selection en web/src/fixtures/demo/results.json), con los umbrales del preset estándar (min_iv=0.02, max_iv=0.5 acción flag, correlation.threshold=0.75 pearson, vif.threshold=5.0):

  • 6 candidatas → 5 seleccionadas, 1 excluida.
  • La única exclusión fue segmento, por IV insuficiente: iv=0.00292 < min_iv=0.02. El panel de Resultados muestra esa misma fila con su motivo y con el detalle que dejó escrito el motor, que es lo que se audita.
  • Sin banderas de IV alto ni de estabilidad.
  • Tras la selección, la máxima correlación absoluta entre variables retenidas fue 0,0303 y el máximo VIF 1,0016: el conjunto final es prácticamente ortogonal, así que ni el filtro de correlación ni el de VIF necesitaron podar nada (el trabajo lo hizo el filtro de IV).

Configurar y leer los resultados

Ambas secciones se editan como cualquier otra parte del BayesRiskConfig. Partiendo del preset estándar (ver Quickstart):

import bayesrisk
from bayesrisk.core.config import BayesRiskConfig
from bayesrisk.ui.presets import standard_preset

cfg_dict = standard_preset()["config"]

# Binning: forma monótona automática y tope de 6 bins por variable.
cfg_dict["binning"]["monotonic_trend"] = "auto_asc_desc"
cfg_dict["binning"]["max_n_bins"] = 6

# Selección: umbrales de IV, correlación y VIF.
cfg_dict["selection"]["min_iv"] = 0.02
cfg_dict["selection"]["correlation"]["threshold"] = 0.75
cfg_dict["selection"]["vif"]["threshold"] = 5.0

config = BayesRiskConfig.model_validate(cfg_dict)

Tras study = bayesrisk.run(config) (y verificar study.run_context.status == "done"), los resultados viven namespaced en study.artifacts. Claves reales que publican estas etapas:

study = bayesrisk.run(config)
assert study.run_context.status == "done"

# --- Binning ---
tablas = study.artifacts.get("binning", "tables")        # dict: variable -> tabla WoE/IV
tabla_ingreso = tablas["ingreso_mensual"]                # bins, tasa de evento, WoE, IV por bin
resumen = study.artifacts.get("binning", "summary")      # IV, banda, monotonía y nº de bins
woe_frame = study.artifacts.get("binning", "woe_frame")  # dataset ya transformado a WoE

# --- Selección ---
elegidas = study.artifacts.get("selection", "selected_features")   # variables que pasan
tabla_sel = study.artifacts.get("selection", "selection_table")    # decisión + motivo por variable
vif = study.artifacts.get("selection", "vif_table")                # VIF final por variable

Además, cada etapa deja una sección compacta para la ficha del modelo (binning_card y selection_card), que es lo que consume la gobernanza cuando la enciendes (Gobernanza y ficha del modelo).

Ver también