Evaluación de Riesgos en Imputación de Datos mediante Modelos Aditivos Explicables

  1. Contexto del Problema: Imputación y Transparencia en Datos Críticos

En sectores como la medicina, las finanzas o el mantenimiento industrial, la presencia de datos ausentes es una constante. La práctica habitual consiste en aplicar técnicas de imputación, desde métodos básicos como la media o mediana, hasta algoritmos complejos como MissForest o K-Nearest Neighbors (KNN). Existe la creencia generalizada de que las técnicas más sofisticadas garantizan mejores resultados. Sin embargo, en entornos de decisión de alto riesgo, esta suposición puede introducri sesgos peligrosos.

Considere un modelo predictivo de mortalidad en UCI. Si un indicador vital como el índice de oxigenación (P/F) falta porque el médico lo consideró normal, un algoritmo de imputación avanzado podría asignar un valor bajo riesgo basándose en otras variables. El modelo aprendería erróneamente que esos pacientes tianen menor riesgo, potencialmente retrasando intervenciones críticas. Este sesgo introducido en el preprocesamiento es difícil de detectar en modelos de "caja negra".

Para mitigar esto, se requiere transparencia. Los Modelos Aditivos Explicables (Explainable Boosting Machines, EBM) funcionan como herramientas de diagnóstico. Al ser Generalized Additive Models (GAM), producen "funciones de forma" que muestran la relación no lineal entre cada característica y el objetivo. Esto permite auditar cómo la imputación afecta la lógica del modelo, identificando anomalías que las métricas globales (como AUC) ocultan.

  1. Fundamentos Teóricos: EBM como Herramienta de Diagnóstico

La capacidad del EBM para revelar problemas de imputación radica en su estructura matemática y la visualización de sus componentes.

2.1 Funciones de Forma y Sensibilidad

Un EBM suma las contribuciones de cada característica. La predicción se expresa como:

g(E[y]) = β_0 + Σ f_i(x_i)

Donde f_i(x_i) es la función de forma para la característica i. Esta curva representa cómo cambia el riesgo (log-odds) al variar el valor de la característica, manteniendo las demás constantes.

Las funciones de forma son sensibles a la distribución de los datos. Si una técnica de imputación distorsiona la relación real entre los datos y la etiqueta, esta distorsión se manifiesta como irregularidades en la curva: picos abruptos, discontinuidades o tendencias contrarias al conocimiento del dominio. Por ejemplo, un pico estrecho en un valor específico sugiere que el modelo ha aprendido un patrón espurio, probablemente causado por una imputación masiva en ese punto.

2.2 Estrategias de Detección

El análisis se divide en dos enfoques principales:

  • Detección de Picos (Imputación Simple): La imputación por media crea una concentración artificial de datos. Si el mecanismo de missing no es aleatorio, el modelo puede asignar un peso excesivo a ese valor, creando un "pinchazo" en la función de forma. Se detecta calculando la segunda derivada discreta (curvatura) y aplicando detección de anomalías.
  • Desplazamiento de Distribución (Imputación Avanzada): Métodos como MissForest pueden alterar sistemáticamente el riesgo percibido para el subgrupo de datos imputados. Mediante una técnica de desplazamiento de valores, se pueden entrenar funciones de forma separadas para los datos originales y los imputados, revelando si el modelo trata a estos grupos de manera inconsistente.
  1. Implementación Técnica en Python

A continuación se describe la construcción de un pipeline de auditoría utilizando la librería interpret y scikit-learn.

3.1 Configuración del Entorno y Datos

Se requieren las siguientes dependencias:

pip install interpret scikit-learn pandas numpy matplotlib

Se simula un conjunto de datos clínicos con valores faltantes artificiales para demostrar la metodología.

import pandas as pd
import numpy as np
from interpret.glassbox import ExplainableBoostingClassifier
from sklearn.ensemble import IsolationForest, RandomForestRegressor
from sklearn.impute import SimpleImputer, IterativeImputer
import matplotlib.pyplot as plt

# Carga simulada de dataset
conjunto_datos = pd.read_csv('registros_clinicos.csv')
atributo_objetivo = 'presion_sistolica'
etiqueta_clinica = 'mortalidad_uci'

# Copias para diferentes estrategias
ds_promedio = conjunto_datos.copy()
ds_avanzado = conjunto_datos.copy()

# Generación de missing values (30% aleatorio)
np.random.seed(42)
indices_faltantes = ds_promedio[atributo_objetivo].sample(frac=0.3, random_state=42).index
ds_promedio.loc[indices_faltantes, atributo_objetivo] = np.nan
ds_avanzado.loc[indices_faltantes, atributo_objetivo] = np.nan

# Imputación por Media
imputador_simple = SimpleImputer(strategy='mean')
ds_promedio_imputado = pd.DataFrame(imputador_simple.fit_transform(ds_promedio), columns=ds_promedio.columns)

# Imputación Iterativa (MissForest-like)
imputador_iterativo = IterativeImputer(
    estimator=RandomForestRegressor(n_estimators=100, random_state=42), 
    max_iter=10, 
    random_state=42
)
ds_avanzado_imputado = pd.DataFrame(imputador_iterativo.fit_transform(ds_avanzado), columns=ds_avanzado.columns)

3.2 Extracción de Funciones de Forma

Se entrena el EBM y se extraen los datos crudos de la función de forma para el atributo de interés.

def obtener_datos_forma_ebm(datos_entrenamiento, nombre_columna, nombre_etiqueta):
    """Entrena EBM y extrae bins y scores de una feature específica."""
    X = datos_entrenamiento.drop(columns=[nombre_etiqueta])
    y = datos_entrenamiento[nombre_etiqueta]

    modelo_ebm = ExplainableBoostingClassifier(random_state=42)
    modelo_ebm.fit(X, y)

    explicacion_global = modelo_ebm.explain_global()
    datos_features = explicacion_global.data()['features']
    
    for feat in datos_features:
        if feat['name'] == nombre_columna:
            bins = np.array(feat['bin_labels'])
            scores = np.array(feat['scores'])
            densidad = np.array(feat['density']) if 'density' in feat else None
            return bins, scores, densidad
    return None, None, None

# Extracción para ambos casos
bins_prom, scores_prom, _ = obtener_datos_forma_ebm(ds_promedio_imputado, atributo_objetivo, etiqueta_clinica)
bins_adv, scores_adv, _ = obtener_datos_forma_ebm(ds_avanzado_imputado, atributo_objetivo, etiqueta_clinica)

3.3 Algoritmo de Detección de Picos

Se calcula la segunda diferencia finita para estimar la curvatura y se utiliza un Bosque de Aislamiento para identificar valores atípicos en dicha curvatura.

def evaluar_anomalia_segundo_ordén(contribuciones, limites_bin, valor_referencia, prop_contaminacion=0.05):
    """
    Identifica si el bin correspondiente al valor de imputación es una anomalía estadística.
    """
    # Localizar índice del bin más cercano al valor de referencia
    idx_objetivo = np.argmin(np.abs(limites_bin - valor_referencia))
    
    # Calcular segunda derivada discreta (curvatura)
    # Se omiten los bordes para evitar errores de índice
    curvaturas = []
    indices_validos = []
    for k in range(1, len(contribuciones) - 1):
        delta = contribuciones[k+1] - 2*contribuciones[k] + contribuciones[k-1]
        curvaturas.append(delta)
        indices_validos.append(k)
    
    curvaturas = np.array(curvaturas).reshape(-1, 1)
    
    # Detección de outliers con Isolation Forest
    detector = IsolationForest(contamination=prop_contaminacion, random_state=42)
    predicciones = detector.fit_predict(curvaturas) # -1 es anomalía
    puntajes_anomalia = detector.decision_function(curvaturas)
    
    es_pico = False
    if idx_objetivo in indices_validos:
        pos_en_curvatura = indices_validos.index(idx_objetivo)
        if predicciones[pos_en_curvatura] == -1:
            es_pico = True
            print(f"ALERTA: Pico detectado en valor {limites_bin[idx_objetivo]:.2f} (Score: {puntajes_anomalia[pos_en_curvatura]:.4f})")
        else:
            print(f"OK: No hay anomalía significativa en {limites_bin[idx_objetivo]:.2f}")
            
    # Mapear scores de vuelta al tamaño original para plotting
    scores_completos = np.full(len(contribuciones), np.nan)
    for idx, val in zip(indices_validos, puntajes_anomalia):
        scores_completos[idx] = val
        
    return es_pico, scores_completos, idx_objetivo

valor_media_original = conjunto_datos[atributo_objetivo].mean()
es_riesgoso, scores_anomalia, idx_media = evaluar_anomalia_segundo_ordén(scores_prom, bins_prom, valor_media_original)

3.4 Visualización de Desplazamiento para Imputación Avanzada

Para comparar cómo el modelo trata los datos originales vs. imputados, se aplica un offset grande a los valores imputados, forzando al EBM a aprender funciones separadas.

def entrenar_con_desplazamiento_valor(datos_base, columna, etiqueta, magnitud_offset):
    """
    Crea una feature desplazada para separar visualmente los grupos observados e imputados.
    """
    datos_mod = datos_base.copy()
    mascara_faltantes = datos_base[columna].isna()
    
    # Recuperar valores imputados (asumiendo que ya existen en un dataset previo procesado)
    # En este ejemplo, usamos los datos ya imputados globalmente para obtener los valores
    valores_imputados = ds_avanzado_imputado.loc[mascara_faltantes, columna]
    
    columna_desplazada = columna + '_desplazada'
    datos_mod[columna_desplazada] = datos_mod[columna].copy()
    datos_mod.loc[mascara_faltantes, columna_desplazada] = valores_imputados + magnitud_offset
    
    X_mod = datos_mod[[columna_desplazada]]
    y_mod = datos_mod[etiqueta]
    
    ebm_desplazado = ExplainableBoostingClassifier(random_state=42)
    ebm_desplazado.fit(X_mod, y_mod)
    
    exp_global = ebm_desplazado.explain_global()
    for f in exp_global.data()['features']:
        if f['name'] == columna_desplazada:
            b_all = np.array(f['bin_labels'])
            s_all = np.array(f['scores'])
            
            # Separar basado en el offset
            mascara_obs = b_all < (magnitud_offset / 2)
            mascara_imp = ~mascara_obs
            
            b_obs = b_all[mascara_obs]
            s_obs = s_all[mascara_obs]
            b_imp = b_all[mascara_imp] - magnitud_offset # Restaurar escala
            s_imp = s_all[mascara_imp]
            
            return b_obs, s_obs, b_imp, s_imp
    return None, None, None, None

offset_grande = conjunto_datos[atributo_objetivo].max() + 50
b_obs, s_obs, b_imp, s_imp = entrenar_con_desplazamiento_valor(ds_avanzado, atributo_objetivo, etiqueta_clinica, offset_grande)

  1. Interpretación de Resultados y Mejores Prácticas

4.1 Aálisis de Gráficos de Picos

Al visualizar la función de forma junto con los scores de anomalía:

  • Función de Forma: Busque discontinuidades agudas en la línea azul alrededor de la línea roja (valor de media). Una subida o bajada vertical localizada indica que el modelo atribuye un riesgo desproporcionado a ese valor exacto.
  • Score de Anomalía: En el gráfico inferior, los puntos con scores negativos altos (lejos de cero) son candidatos a picos. Si el punto correspondiente a la media es un outlier claro, la imputación por media está introduciendo ruido.

4.2 Análisis de Desplazamiento de Distribución

Al superponer las curvas del grupo observado (verde) y el grupo imputado (magenta):

  • Si la curva imputada está sistemáticamente por debajo de la observada, el algoritmo de imputación está suavizando el riesgo para ese subgrupo, lo cual puede ser peligroso en predicción de fallos o mortalidad.
  • Diferencias de forma en rangos críticos (ej. presión muy baja) indican que la relación learned por el imputador no coincide con la realidad fisiológica.

4.3 Recomendaciones de Configuración

  • Parámetros EBM: Ajuste max_bins. Demasiados bins generan ruido; muy pocos ocultan picos. Un rango de 15-25 suele ser equilibrado para diagnóstico.
  • Validación Humana: La detección automática es una guía. Un pico en glucosa baja podría ser real (hipoglucemia) y no un artefacto. Siempre consulte con expertos de dominio.
  • Mitigación: Si se detecta riesgo, considere usar un indicador binario de "missing" en lugar de imputar, o edite manualmente la función de forma en la librería interpret para suavizar el artefacto.
  1. Resolución de Incidencias Comunes

Problema: Error en cálculo de diferencias o índices vacíos.
Causa: El EBM generó muy pocos bins para esa feature.
Solución: Incremente max_bins en la inicialización del clasificador o verifique que el valor objetivo no caiga en los bordes extremos.

Problema: Curvas superpuestas en visualización de desplazamiento.
Causa: El offset no es suficientemente grande o hay pocos datos imputados.
Solución: Aumente el offset (ej. máximo de la feature * 2) y asegúrese de que la tasa de missing sea significativa (>5%).

Problema: Rendimiento del modelo cae tras imputación avanzada.
Causa: La imputación introduce correlaciones espurias entre features.
Solución: Compare matrices de correlación antes y después. Pruebe usar indicadores de missing en lugar de imputación compleja.

Problema: Features categóricas.
Causa: La lógica de segunda derivada no aplica directamente.
Solución: Aplique detección de anomalías sobre el vector de contribuciones de cada categoría. Si la categoría "más frecuente" (usada para imputar) tiene un score extremo, es sospechosa.

Problema: Lentitud en entrenamiento.
Causa: Dataset muy grande.
Solución: Use submuestreo representativo para la fase de diagnóstico. Las funciones de forma convergen rápidamente con muestras de 50k-100k registros.

Etiquetas: explainable-boosting-machine data-imputation shape-functions isolation-forest scikit-learn

Publicado el 10-5 08:17