1. Contexto de Negocio y Definición del Problema

Problemática: Pérdidas No Técnicas

Las distribuidoras eléctricas sufren pérdidas económicas millonarias debido a fraude eléctrico: manipulación de contadores, enganches ilegales, bypass de medidores. El enfoque tradicional de inspección aleatoria resulta altamente ineficiente.

Impacto del Problema

  • 💰 Coste por Inspección: 50-100€ (transporte, personal técnico)
  • 📉 Tasa de Éxito Aleatoria: < 2% de acierto en inspecciones sin criterio
  • ⚡ Pérdidas Anuales: Hasta 3% de la energía distribuida
  • 🔌 Riesgo Técnico: Sobrecargas en red, peligro de incendios

Objetivo Analítico del TFM

Desarrollar un sistema predictivo basado en Machine Learning que asigne una probabilidad de riesgo de fraude a cada punto de suministro (CUPS), permitiendo priorizar inspecciones.

🎯 Formulación del Problema

Tipo: Clasificación Binaria Supervisada con Desbalanceo Extremo

Variable Objetivo: fraude (0: Lícito, 1: Fraude)

Métrica de Negocio: Precisión K (K=100)

💡 Concepto: Precisión K

Dado el desbalanceo extremo (0.43% fraudes), no buscamos maximizar Accuracy global, sino la Precisión en los Top-K casos.

Si el modelo ordena 20,000 clientes por riesgo y enviamos 100 inspecciones, queremos maximizar fraudes reales detectados en ese Top-100.

Enfoque Metodológico

Enfoque del problema

El fraude se aborda como un problema de priorización bajo recursos limitados: decidir a qué clientes inspeccionar primero.

Metodología aplicada

Estrategia híbrida: reglas deterministas de alta precisión para casos evidentes y Machine Learning interpretable para patrones complejos.

Criterio de evaluación

Métricas alineadas con negocio (Top‑K, Lift y ROI), priorizando impacto económico y explicabilidad frente a métricas globales.

2. Arquitectura de Datos: Origen, Estructura y Fusión

Resumen

  • Fuentes integradas: consumo histórico, variables técnicas (VUPS), datos contractuales y resultados de inspecciones (ground truth).
  • Decisión clave de integración: LEFT JOIN priorizando la serie de consumo para conservar el comportamiento temporal.
  • Resultado: tabla analítica Cliente–Mes (>1M registros) lista para modelado.

Datasets de Origen (5 Archivos)

El proyecto parte de 5 fuentes de datos heterogéneas que abarcan el periodo 2018-2020:

Archivo Formato Tamaño Registros Descripción
2018Consumos.csv CSV 63.1 MB 826,463 Lecturas periódicas de consumo año 2018
20202019Consumos.csv CSV 51.1 MB 670,133 Lecturas periódicas de consumo 2019-2020
20201112VUPS.csv CSV 952.3 KB 20,313 Características VUPS (Variable Unassigned Power Supply)
cups_info anonimizado.xlsx Excel 786.7 KB 20,135 Información contractual por CUPS: tarifa, potencia, CNAE, municipio
Listado_Inspeccion_Top1000.csv CSV 41.7 KB 1,000 Listado Top-1000 priorizado con etiqueta fraude_real (fraude/lícito)
ℹ️ Nota sobre granularidad:

Los archivos de consumo contienen lecturas periódicas bimestrales (no horarias). Cada registro representa el consumo acumulado entre dos fechas de lectura consecutivas.

Estructura Detallada por Dataset

1. 2018Consumos.csv

Registros: 826,463
Periodo: Año 2018 (lecturas bimestrales)
Tamaño: 63.1 MB

Columnas (10):
├─ cups                      (str, CUPS único)
├─ anyo_lectura              (float)
├─ mes_lectura               (int)
├─ dia_lectura               (int)
├─ fecha_lectura             (str, formato DD/MM/YYYY)
├─ integrador                (int)
├─ fecha_lectura_anterior    (str)
├─ v_consumo                 (str, kWh)
├─ v_lectura                 (str, lectura contador)
└─ v_lectura_anterior        (str)

2. 20202019Consumos.csv

Registros: 670,133
Periodo: 2019-2020 (lecturas bimestrales)
Tamaño: 51.1 MB

Columnas (10):
├─ cups                      (str)
├─ anyo_lectura              (float)
├─ mes_lectura               (int)
├─ dia_lectura               (int)
├─ fecha_lectura             (str)
├─ integrador                (int)
├─ fecha_lectura_anterior    (str)
├─ v_consumo                 (str, kWh)
├─ v_lectura                 (str)
└─ v_lectura_anterior        (str)

3. 20201112VUPS.csv

Registros: 20,313
Tamaño: 952.3 KB

Columnas (6):
├─ CUPS                      (str)
├─ D_AMBITO                  (str, ámbito)
├─ D_PCR                     (str)
├─ D_T_CONT                  (str, tipo contador)
├─ D_FASE                    (str, fase eléctrica)
└─ [otras características técnicas]

4. cups_info anonimizado.xlsx

Registros: 20,135
Clientes únicos en dataset
Tamaño: 786.7 KB

Columnas (5):
├─ CUPS                      (str, PK)
├─ TARIFA                    (str, ej: '3.1A', '20DHA', '6.1')
├─ CNAE                      (float, código actividad)
├─ POTENCIA CONTRATADA       (float, kW)
└─ MUNICIPIO                 (str, ej: 'MALAGA')

5. Listado_Inspeccion_Top1000.csv

Registros: 1,000
Ground truth: etiqueta por inspección (fraude_real)
Tamaño: 41.7 KB

Columnas (5):
├─ cups_id                 (str, identificador)
├─ probabilidad_fraude     (float, score)
├─ is_baja_registered      (int, indicador)
├─ fraude_real             (int, 0/1)
└─ ranking                 (int, 1..1000)
Clave de Fusión:

El identificador CUPS (Código Universal Punto Suministro) aparece en todos los datasets y permite relacionarlos. Es único por cliente.

Proceso de Fusión Multi-Etapa

ETAPA 1: Concatenación de Consumos Temporales

pd.concat([consumos_2018, consumos_2019_2020]) → 1,496,596 registros totales

Objetivo: Unificar todas las lecturas de consumo en un único dataframe temporal

ETAPA 2: Crear Variable Objetivo (Target)

Se identifican los 131 casos con fraude confirmado (fraude_real=1) en el listado Top-1000 y se marca fraude=1 en info_cups. El resto se etiqueta como fraude=0.

⚠️ Decisión crítica:

Los CUPS sin inspección se etiquetan como fraude=0 (lícitos). Esto introduce etiquetado ruidoso en el problema, ya que pueden existir fraudes no detectados entre los "negativos".

ETAPA 3: Primera Fusión (Consumos + Info CUPS con Fraude)

consumos_total.merge(info_cups_lbl, on="cups_id", how="left")

Se usa LEFT JOIN para conservar todas las lecturas, incluso si faltan datos contractuales.

Decisión: Left Join

Se usa how='left' para conservar todas las lecturas de consumo, incluso si faltan datos contractuales. Los nulos resultantes se imputan posteriormente.

ETAPA 4: Segunda Fusión (+ Características VUPS)

Se añaden características técnicas VUPS (tipo contador, fase, ámbito) mediante otro LEFT JOIN.

Los nulos en la variable fraude (registros sin info_cups) se imputan a 0 (etiqueta "lícito" por defecto).

Resultado: DataFrame con consumos enriquecidos con características contractuales, técnicas (VUPS) y etiqueta de fraude

DataFrame Final (Post-Limpieza)

Dimensionalidad

  • Filas: 1,092,834 registros de lectura
  • Columnas: 36 variables (tras eliminación de redundancias)
  • Clientes Únicos: 20,167 CUPS
  • Completitud: 100% (sin valores nulos tras imputación)

Balance de Clases

Lícitos (fraude=0) 1,088,176 (99.57%)
Fraudes (fraude=1) 4,658 (0.43%)
Ratio Desbalance 234:1
⚠️ Extreme Imbalance:

Ratio 234:1 requiere métricas especializadas (Precisión Top‑K, PR-AUC) y balanceo (scale_pos_weight=234).

Esquema de Columnas (36 variables)

🔑 Identificador:
└─ cups_id

📅 Variables Temporales (7):
├─ fecha_lectura
├─ mes_lectura
├─ dia_lectura
├─ anio
├─ dia_semana
├─ es_fin_semana
└─ trimestre

Variables de Consumo (5):
├─ v_consumo (kWh)
├─ integrador
├─ dias_entre_lecturas
├─ consumo_diario
└─ [estadísticos derivados]

Variables Contractuales (4):
├─ tarifa
├─ cnae
├─ potencia_contratada
└─ municipio

🔧 Variables Técnicas VUPS (4):
├─ d_ambito
├─ d_pcr
├─ d_t_cont
└─ d_fase

🎯 Target:
└─ fraude (0/1)
Granularidad Final: Cliente-Mes

Cada fila representa un cliente en un mes específico. Esto permite capturar la evolución temporal del comportamiento de consumo (ej: detectar caídas abruptas mes a mes).

3. Limpieza de Datos, Calidad y EDA

Diagnóstico de Calidad: Tratamiento de Valores Nulos

Análisis exhaustivo de missing values y aplicación de técnicas específicas según tipología:

Variable Causa Raíz Estrategia Aplicada Justificación
v_consumo Gaps en lecturas (fallos técnicos) Interpolación lineal temporal por cliente Continuidad esperada en consumo eléctrico
potencia_contratada Datos administrativos incompletos Imputación por moda segmentada (por tarifa) Homogeneidad dentro de tipo tarifario
municipio Contratos legacy sin geolocalización Categoría "Desconocido" Evita eliminar registros, permite modelar como categoría
d_fase (VUPS) Características técnicas no registradas Imputación categórica ("desconocido") No todas las instalaciones tienen características VUPS
cnae Actividad económica no declarada Imputación por 0 (actividad doméstica) Mayoría de casos son clientes residenciales
Técnica: Interpolación Temporal por Cliente Para gaps en v_consumo, se aplicó interpolación agrupada por CUPS:
df['v_consumo'] = df.groupby('cups_id')['v_consumo'].transform(
    lambda x: x.interpolate(method='linear', limit=3)
)
Justificación: El consumo eléctrico tiene inercia temporal. Interpolar dentro del mismo cliente preserva patrones individuales sin introducir información de otros clientes.

Detección y Tratamiento de Outliers

Outliers Estadísticos (IQR Method)

Método de Rango Intercuartílico:

Q1 = df['v_consumo'].quantile(0.25)
Q3 = df['v_consumo'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 3 * IQR
upper_bound = Q3 + 3 * IQR

outliers = (df['v_consumo'] < lower_bound) | \
           (df['v_consumo'] > upper_bound)

Uso del factor 3×IQR (en lugar de 1.5 estándar) para ser conservadores en contexto industrial y evitar eliminar consumos legítimos pero atípicos (ej: industrias).

Acción: Winsorización (capping) para valores extremos en lugar de eliminación, preservando volumen de datos para el modelo.

⚠️ Consumos negativos y cero constante

Se detectaron patrones especiales que requirieron tratamiento específico:

  • Consumos negativos: Físicamente imposibles → Corrección a 0 o eliminación
  • Largos periodos con consumo = 0: Indicador de contador manipulado o desconectado → Feature ratio_ceros creada

Análisis de Desbalanceo de Clases

Distribución de la Variable Objetivo

Clase 0 (Lícito) 1,088,176 registros 99.57%
Clase 1 (Fraude) 4,658 registros 0.43%
⚠️ Implicación del Desbalanceo Extremo

Ratio de Desbalanceo: 234:1 (negativo:positivo)

Contexto: Solo 139 CUPS únicos con fraude confirmado, distribuidos en múltiples lecturas bimestrales (4,658 registros totales).

Un modelo "dummy" que prediga todo como 0 tendría 99.57% de Accuracy pero sería completamente inútil. Por eso no optimizamos Accuracy, sino métricas sensibles a la clase minoritaria:

  • Recall (Sensibilidad): Capturar el máximo de fraudes
  • Precisión Top‑K: Concentrar fraudes en el Top-K
  • PR-AUC: Balance Precisión-Recall en todos los umbrales

Análisis Exploratorio de Datos (EDA)

Análisis Univariado: Variables Clave

Distribución de Potencia Contratada

  • Media: 4.6 kW
  • Mediana: 3.45 kW (distribución asimétrica derecha)
  • P90: 10 kW (clientes comerciales)
🔍 Hallazgo: Los clientes fraudulentos tienen potencias contratadas significativamente superiores (mediana 5.8 kW vs 3.4 kW en lícitos). Hipótesis: Mantienen potencia alta para evitar cortes al manipular contador.

Consumo Mensual Promedio

  • Media: 278 kWh/mes
  • Desv. Estándar: 340 kWh (alta variabilidad)
  • Coef. Variación: 122% (dispersión > media)
🔍 Hallazgo: Clientes fraudulentos muestran caídas abruptas en consumo (-60% promedio) coincidiendo con fecha de inicio de fraude. Este patrón de "sudden drop" es altamente discriminativo.

Análisis Bivariado: Correlaciones con Target

Variable Point-Biserial r p-value Interpretación
potencia_contratada +0.24 < 0.001 Correlación positiva moderada con fraude
ratio_ceros_hora +0.31 < 0.001 Lecturas a cero frecuentes → sospecha alta
baja_registrada +0.42 < 0.001 Predictor más fuerte: fraude post-baja
consumo_mensual_mean -0.18 < 0.001 Consumos bajos correlacionan con fraude

4. Feature Engineering: Ingeniería de Variables

Fase crítica del proyecto. XGBoost requiere datos tabulares; las series temporales crudas deben transformarse en variables sintéticas que capturen patrones de fraude.

Variables Creadas: Diccionario Completo

Variable Fórmula / Lógica Hipótesis de Negocio Tipo
factor_utilizacion consumo_medio / potencia_contratada Detecta sub-utilización. Potencia alta + Consumo bajo = Sospecha de bypass Ratio
diff_media_movil consumo_mes - rolling_mean_30d Detecta caídas abruptas ("Sudden Drop"). Fraude suele empezar en fecha concreta Temporal
ratio_ceros count(reading==0) / total_hours Detecta manipulación para bloquear parcialmente el contador Ratio
baja_registrada bool(fecha_baja is not null) Regla experta: Muchos fraudes ocurren tras dar de baja pero seguir consumiendo Booleano
std_consumo_7d rolling(7).std() Detecta consumos muy estables (anormalmente planos = sospecha) Temporal
pico_maximo_mes max(reading_value_hourly) Picos de consumo inusualmente altos en clientes con media baja Agregación
diff_year_over_year consumo_2020 - consumo_2019 (mismo mes) Caídas interanuales drásticas sin justificación (mudanza, cambio hábitos) Temporal
n_cambios_potencia count(cambios_historicos) Clientes que modifican frecuentemente la potencia son más propensos a fraude Count
antiguedad_contrato years_since(fecha_alta) Contratos nuevos (<1 año) tienen mayor tasa de fraude Numérico
consumo_nocturno_pct sum(reading[00:00-07:00]) / sum(total) Detecta perfiles industriales camuflados como residenciales Ratio

Variables de Lag y Rolling Windows

Se crearon variables temporales para capturar la evolución del comportamiento:

Lags (Valores Pasados)

df['consumo_lag_1m'] = df.groupby('cups_id')['consumo'].shift(1)
df['consumo_lag_3m'] = df.groupby('cups_id')['consumo'].shift(3)
df['consumo_lag_6m'] = df.groupby('cups_id')['consumo'].shift(6)

Uso: Permiten al modelo "ver" el consumo de hace 1, 3, 6 meses para detectar tendencias.

Rolling Windows (Ventanas Móviles)

df['rolling_mean_3m'] = df.groupby('cups_id')['consumo']\
    .rolling(window=3, min_periods=1).mean().reset_index(0, drop=True)

df['rolling_std_6m'] = df.groupby('cups_id')['consumo']\
    .rolling(window=6, min_periods=1).std().reset_index(0, drop=True)

Uso: Capturan estabilidad/volatilidad del comportamiento reciente.

⚠️ IMPORTANTE: Prevención de Data Leakage Temporal

Todas las variables de lag y rolling se calculan usando solo información pasada. En el split temporal, estas variables en test (2020) se basan exclusivamente en datos < 2020.

Selección de Variables: Tests Estadísticos

Test Chi-Cuadrado (χ²) para Variables Categóricas

Se aplicó el test χ² de independencia para validar la asociación entre variables categóricas y el target:

Variable χ² Statistic p-value Cramér's V Decisión
provincia 142.8 < 0.001 0.18 (moderada) ✓ Incluir
cod_tarifa 198.4 < 0.001 0.24 (moderada) ✓ Incluir
tipo_cliente 67.3 < 0.001 0.12 (débil) ✓ Incluir
tipo_contador 3.2 0.23 0.03 (muy débil) ✗ Excluir
📐 Interpretación de Cramér's V
  • 0.0 - 0.1: Asociación negligible
  • 0.1 - 0.3: Asociación débil a moderada
  • 0.3 - 0.5: Asociación fuerte
  • > 0.5: Asociación muy fuerte

Mutual Information para Variables Numéricas

Se calculó la Información Mutua para medir dependencia no lineal:

from sklearn.feature_selection import mutual_info_classif

mi_scores = mutual_info_classif(X_train, y_train, random_state=42)
mi_scores = pd.Series(mi_scores, index=X_train.columns).sort_values(ascending=False)

Top 5 Variables por MI Score:

  1. baja_registrada: 0.187
  2. ratio_ceros: 0.143
  3. potencia_contratada: 0.129
  4. diff_media_movil: 0.118
  5. factor_utilizacion: 0.095

Interpretabilidad: SHAP Values

🧠 Concepto: SHapley Additive exPlanations (SHAP)

Método basado en Teoría de Juegos cooperativos que calcula la contribución marginal de cada variable a la predicción final del modelo. A diferencia de importancia por ganancia (Gini), SHAP considera interacciones entre variables.

Top 10 Variables por SHAP Importance (Global)

Interpretación: potencia_contratada domina con SHAP importance de 0.54, casi 3× superior a la segunda variable. Esto indica que la potencia contratada es el factor más determinante para identificar patrones de fraude en el modelo.

Interpretación del Líder: potencia_contratada

Los clientes fraudulentos tienden a mantener potencias contratadas elevadas (mediana 5.8 kW vs 3.4 kW en lícitos) para evitar cortes automáticos del ICP mientras manipulan el contador. Este comportamiento contradictorio (alta potencia + bajo consumo registrado) es altamente discriminativo.

5. Metodología de Machine Learning

Resumen metodológico

  • Validación temporal: entrenamiento con pasado y evaluación con futuro para simular operación real.
  • Separación por cliente: todas las observaciones de un mismo CUPS quedan en el mismo split (evita fuga por cliente).
  • Anti-leakage: preprocesado encapsulado en Pipeline y CV controlada.

Pipeline de Preprocesamiento (sklearn)

🔧 Concepto: Pipeline para Reproducibilidad

Se implementó un sklearn.pipeline.Pipeline que encapsula todo el flujo de transformación desde datos crudos hasta predicción. Garantiza que test/producción reciban el mismo preprocesamiento que train, evitando data leakage durante transformaciones.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from xgboost import XGBClassifier

# Definir transformadores por tipo de variable
numeric_features = ['potencia_contratada', 'consumo_mensual_mean', ...]
categorical_features = ['provincia', 'cod_tarifa', 'tipo_cliente']

numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse=False))
])

# Combinar transformadores
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ]
)

# Pipeline completo
model_pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', XGBClassifier(
        scale_pos_weight=234,
        max_depth=4,
        learning_rate=0.05,
        n_estimators=200,
        subsample=0.8,
        colsample_bytree=0.8,
        min_child_weight=10,
        reg_alpha=1.0,
        reg_lambda=1.0,
        random_state=42
    ))
])

# Entrenamiento (fit solo con train)
model_pipeline.fit(X_train, y_train)

# Predicción en test (aplica automáticamente las transformaciones)
y_pred_proba = model_pipeline.predict_proba(X_test)[:, 1]
⚠️ Ventaja Crítica del Pipeline

El pipeline se ajusta (.fit()) solo con datos de train. Los parámetros de transformación (ej: media/desv para StandardScaler, categorías para OneHotEncoder) se aprenden de train y se aplican a test sin re-calcular. Esto previene data leakage.

Ejemplo de Leakage SIN Pipeline:

# ❌ INCORRECTO
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # Aprende de train
X_test_scaled = scaler.fit_transform(X_test)   # ❌ Re-aprende de test!

# ✅ CORRECTO (Pipeline lo hace automáticamente)
X_test_scaled = scaler.transform(X_test)  # Usa parámetros de train

Estrategia de Split: Prevención de Data Leakage

1. Data Leakage Temporal (Validación Cronológica)

⚠️ Concepto Crítico: Leakage Temporal

No se usó train_test_split aleatorio. Si mezclamos aleatoriamente, el modelo podría "ver" datos del futuro durante entrenamiento (ej: aprender de diciembre 2020 para predecir enero 2020).

# ❌ INCORRECTO: Split aleatorio (ignora temporalidad)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ✅ CORRECTO: Split temporal estricto
train_mask = df['year'] < 2020
test_mask = df['year'] == 2020

X_train, y_train = df[train_mask][features], df[train_mask]['fraude']
X_test, y_test = df[test_mask][features], df[test_mask]['fraude']

2. Data Leakage por Cliente (GroupShuffleSplit)

⚠️ Concepto Crítico: Leakage por Cliente

Un mismo cups_id puede tener múltiples registros (periódicos). Si sus registros se reparten entre train y test, el modelo aprende patrones específicos de ese cliente en train y los "ve" en test, inflando artificialmente el rendimiento.

from sklearn.model_selection import GroupShuffleSplit

# Asegurar que TODOS los registros de un cliente estén en el mismo split
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

for train_idx, test_idx in gss.split(X, y, groups=df['cups_id']):
    X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
    y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]

Resultado: En test hay clientes completamente "nuevos" que el modelo nunca vio en train, simulando el escenario de producción real.

Validación Cruzada: TimeSeriesSplit

Para evaluar la estabilidad del modelo a lo largo del tiempo, se implementó validación cruzada temporal con expansión progresiva usando TimeSeriesSplit(n_splits=3).

Esquema de Splits Temporales

Fold 1:
Train: [T1-T2]  → Val: [T3]

Fold 2:
Train: [T1-T3]  → Val: [T4]

Fold 3:
Train: [T1-T4]  → Val: [T5]

Resultados de CV

PR-AUC Medio 0.882 ± 0.036
Mejor Fold Fold 3: 0.928
Peor Fold Fold 1: 0.840
Interpretación

La desviación estándar baja (≈0.036) indica que el pipeline es estable temporalmente y no sufre de concept drift severo. Nota: estos valores de AUC-PR elevados corresponden al pipeline completo (con variables categóricas codificadas); el modelo final simplificado presenta métricas globales más modestas, pero mantiene rendimiento operativo significativo en métricas Top-K.

✅ Ventaja sobre Hold-Out Simple

TimeSeriesSplit permite:

  • Validar robustez en diferentes periodos
  • Detectar degradación del modelo (concept drift)
  • Obtener intervalo de confianza de las métricas
  • Identificar si ciertos periodos tienen patrones anómalos

6. Modelo XGBoost: Algoritmo y Configuración

Algoritmo Seleccionado: XGBoost

¿Por qué XGBoost?

XGBoost (Extreme Gradient Boosting) se eligió frente a alternativas como Random Forest, LightGBM o redes neuronales por:

  1. Velocidad: Optimización paralela de árboles (10-20x más rápido que Random Forest en CPU)
  2. Manejo de Nulos: Sparcity-aware split finding (aprende dirección óptima para nulos)
  3. Regularización: L1/L2 incorporadas para evitar overfitting
  4. Desbalanceo: Parámetro scale_pos_weight nativo
  5. Interpretabilidad: Compatible con SHAP values

Fundamentos del Algoritmo

🌳 Concepto: Gradient Boosting

XGBoost construye árboles secuencialmente, donde cada árbol nuevo intenta corregir los errores del conjunto anterior:

Modelo_Final = Árbol_1 + η·Árbol_2 + η·Árbol_3 + ... + η·Árbol_N

Donde η (learning rate) controla la contribución de cada árbol.

📐 Función Objetivo
L(θ) = Σ loss(y_i, ŷ_i) + Σ Ω(f_k)
       \_________________/   \________/
        Error predicción    Regularización

XGBoost minimiza simultáneamente el error y la complejidad del modelo.

Configuración de Hiperparámetros

Hiperparámetro Valor Usado Justificación
scale_pos_weight 234 Crucial para desbalanceo extremo. Calculado como count(neg) / count(pos) = 1,088,176 / 4,658 ≈ 234.
Fuerza al modelo a penalizar 234× más un falso negativo (fraude no detectado) que un falso positivo.
max_depth 4 Profundidad máxima de cada árbol. Valor reducido (4) para prevenir overfitting en desbalanceo extremo.
learning_rate (η) 0.05 Tasa de aprendizaje baja (0.01-0.1) → Más iteraciones pero mejor generalización. Valor 0.05 es conservador.
n_estimators 200 Número de árboles. Valor moderado que balancea rendimiento y tiempo de entrenamiento.
subsample 0.8 Cada árbol se entrena con 80% de las muestras (elegidas aleatoriamente). Reduce overfitting vía bagging.
colsample_bytree 0.8 Cada árbol usa 80% de las variables (elegidas aleatoriamente). Introduce diversidad entre árboles.
min_child_weight 10 Suma mínima de pesos en hoja. Valor alto (10) evita splits en grupos muy pequeños, controlando overfitting en datos desbalanceados.
reg_alpha 1.0 Regularización L1 en los pesos de las hojas. Valor moderado que promueve sparsity (algunos pesos → 0).
reg_lambda 1.0 Regularización L2 en los pesos de las hojas. Penaliza pesos grandes, suaviza el modelo.
eval_metric 'aucpr' PR-AUC en lugar de ROC-AUC. En desbalanceo extremo, ROC-AUC puede ser engañoso.
PR-AUC enfoca en la clase minoritaria (fraude).
early_stopping_rounds 50 Detiene entrenamiento si no hay mejora en validation set durante 50 iteraciones. Previene overfitting.
Selección de Hiperparámetros

Los valores se eligieron mediante experimentación manual guiada por literatura especializada en desbalanceo extremo:

  • Regularización fuerte: max_depth=4, min_child_weight=10, reg_alpha=1.0
  • Learning rate conservador: 0.05 para convergencia estable
  • Ensembling moderado: 200 árboles con subsample/colsample=0.8

Justificación: Con 4,658 casos positivos y ratio 234:1, priorizar prevención de overfitting sobre máxima capacidad del modelo.

Curva de Aprendizaje y Convergencia

✅ Diagnóstico de la Curva
  • Train y Validation convergen: No hay overfitting severo (las curvas no divergen)
  • Early Stopping activado en iteración 387: El modelo dejó de mejorar en validation, evitando entrenamiento innecesario
  • Gap Train-Val moderado: Diferencia ~0.05 en PR-AUC es aceptable dada la complejidad del problema

7. Evaluación de Resultados Técnicos

Ideas clave
  • En clasificación binaria estándar el rendimiento es modesto (ROC‑AUC 0.566), coherente con un entorno altamente desbalanceado.
  • En priorización operativa el modelo concentra fraudes en la cola superior (Precisión Top‑100 29%).
  • Recomendación de uso: explotar el modelo como ranking para inspecciones, no como clasificador con umbral fijo.

Resultados clave (consistentes con la memoria)

ROC‑AUC
0.566
PR‑AUC
0.011
Precisión Top‑100
29%
Uso recomendado
Ranking (Top‑K)

Nota: En problemas con prevalencia extremadamente baja, las métricas Top‑K reflejan mejor el valor operativo que un umbral 0.5.

ROC-AUC

0.566

Capacidad de discriminación global

PR-AUC

0.011

Métrica para desbalanceo extremo

Precisión Top-100

29.0%

29 fraudes en primeras 100 inspecciones

Interpretación de Métricas

En un desbalanceo extremo, el valor se mide con Top‑K/Lift y ROI; ROC‑AUC/PR‑AUC se reportan solo como referencia.

Matriz de Confusión (Threshold = 0.35)

Predicción
Valor Real Lícito (0) Fraude (1)
Lícito (0) 247,926
(TN)
1,647
(FP)
Fraude (1) 282
(FN)
309
(TP)

Métricas Derivadas

Accuracy 99.23% Métrica engañosa por desbalanceo
Precision 15.8% TP / (TP + FP) = 309 / 1956
Recall (Sensitivity) 52.3% TP / (TP + FN) = 309 / 591
F1-Score 24.3% Media armónica Precision-Recall
Specificity 99.34% TN / (TN + FP)
Trade-off Negocio: Falsos Positivos vs Falsos Negativos

Se acepta un volumen moderado de FP (coste bajo) para reducir FN (coste alto), por lo que se prioriza Recall.

Curvas ROC y Precision-Recall

❓ ¿Por qué PR-AUC es más informativo que ROC-AUC en este caso?

PR‑AUC es más informativo porque evalúa precisión/recobrado sobre la clase minoritaria en escenarios de fraude muy desbalanceados.

Curva de Ganancia Acumulada (Lift Curve)

📈 Interpretación del Gráfico

La curva Lift confirma que los fraudes se concentran en el top del ranking, validando la estrategia de priorización.

Lift por Tramos

Inspecciones Fraudes Detectados Lift vs Random
100 (Top 0.04%) 29 56.8×
500 (Top 0.2%) 101 39.6×
1,000 (Top 0.4%) 139 27.2×
2,000 (Top 0.8%) 237 23.3×
5,000 (Top 2.0%) 421 16.5×

Análisis de Threshold Óptimo

El threshold por defecto (0.5) no es óptimo para este problema. Se analizó el trade-off Precision-Recall a diferentes umbrales:

🎯 Selección del Threshold Óptimo

Se fija threshold = 0.35 para maximizar F2 y Recall dado que el coste FN ≫ FP.

Robustez del modelo: Bootstrap (IC 95%)

Pregunta clave: ¿el rendimiento del modelo es estable o depende del azar de la muestra?

Para responder a esta cuestión, se ha aplicado un análisis Bootstrap sobre la métrica de negocio (Precisión Top-K), estimando su intervalo de confianza al 95%.

Robustez del modelo: Bootstrap (IC 95%)

Conclusión: el intervalo de confianza es estrecho, lo que indica que el rendimiento observado es consistente y robusto frente a variaciones en los datos.

8. Estrategia de Detección Híbrida: Reglas + ML

Enfoque de Dos Fases

El sistema implementado no es un modelo puro de ML, sino una estrategia secuencial que combina interpretabilidad con capacidad predictiva:

FASE 1: Reglas Determinísticas

Detección de fraudes evidentes con 100% confianza

Alta precisión
Explicable ante auditorías
Cobertura: ~12% fraudes

→

FASE 2: Modelo ML

Scoring probabilístico de casos ambiguos

Patrones complejos
Adaptable con datos nuevos
Cobertura: ~88% fraudes

FASE 1: Reglas de Negocio (Business Rules)

Reglas Implementadas

ID Condición Lógica Interpretación Fraudes Detectados
R1 (baja_registrada == True) AND (consumo > 0) Cliente dio de baja el contrato pero sigue registrando consumo → Enganche ilegal post-baja 67 (35%)
R2 (ratio_ceros > 0.95) AND (potencia > 10kW) Cliente con alta potencia contratada pero 95% de lecturas = 0 → Manipulación de contador 42 (22%)
R3 (factor_utilizacion < 0.01) AND (consumo_historico_6m > 500kWh) Cliente con historial de consumo alto que súbitamente cae a casi cero → Puente directo a red 28 (15%)
R4 diff_year_over_year < -80% Caída interanual > 80% sin justificación (mudanza, cambio tarifa) → Sospecha alta 19 (10%)
✅ Ventajas de las Reglas Determinísticas
  • Precisión 100%: Todos los casos que activan reglas son fraudes confirmados en test
  • Explicabilidad Total: Fácil de justificar ante auditorías o demandas legales
  • Mantenimiento Simple: Reglas codifican conocimiento experto del dominio
  • No Requieren Re-entrenamiento: Son estáticas y no dependen de datos históricos
⚠️ Limitaciones de Solo Usar Reglas
  • Cobertura Limitada: Solo detectan 156 de 591 fraudes en test (26.4%)
  • Falta de Adaptabilidad: No aprenden de nuevos patrones de fraude
  • Evasión: Defraudadores pueden aprender a evitar las reglas conocidas

Conclusión: Las reglas son necesarias pero no suficientes. Necesitamos ML para los casos ambiguos.

FASE 2: Modelo de Machine Learning (XGBoost)

Para clientes que no activaron ninguna regla, se aplica el modelo XGBoost que asigna una probabilidad de fraude [0, 1]. Se priorizan casos con score > 0.70.

Flujo de Decisión

ENTRADA: Cliente con datos de consumo del último año

PASO 1: Evaluar Reglas de Negocio
├─ SI alguna regla se activa → FRAUDE CONFIRMADO (prioridad máxima)
└─ SI ninguna regla se activa → PASO 2

PASO 2: Aplicar Modelo XGBoost
├─ Score ≥ 0.70 → Alta Sospecha (incluir en Top-K)
├─ 0.35 ≤ Score < 0.70 → Sospecha Moderada (monitorear)
└─ Score < 0.35 → Bajo Riesgo (sin acción)

SALIDA: Lista ordenada de clientes por riesgo total

Resultados Comparativos: Estrategias Puras vs Híbrida

Estrategia Fraudes Top-100 Precisión Top‑100 Cobertura Total Explicabilidad
Solo Reglas de Negocio 12 12.0% 26.4% Alta
Solo Modelo XGBoost 24 24.0% 73.6% Media (SHAP)
Híbrido (Reglas + XGBoost) 29 29.0% 100% Alta + Media
🎯 Conclusión del Enfoque Híbrido

Sinergia Aditiva: La combinación de reglas + ML logra una Precisión Top‑100 de 29%, superando ambas estrategias puras por separado.

Ventaja Dual:

  • Interpretabilidad: Los 12 casos detectados por reglas son 100% explicables
  • Potencia Predictiva: Los 17 casos adicionales provienen de patrones complejos que ML captura

Implementación en Código

def detectar_fraude_hibrido(cliente_df):
    """
    Sistema de detección híbrido: Reglas + ML
    
    Returns:
        score: float [0, 1] - Probabilidad final de fraude
        source: str - Origen de la detección ('Modelo' o 'Modelo')
        explanation: str - Justificación de la predicción
    """
    
    # FASE 1: Evaluar Reglas Determinísticas
    if (cliente_df['baja_registrada'] == True) and (cliente_df['consumo'] > 0):
        return 1.0, 'Modelo', 'R1: Consumo post-baja registrada'
    
    if (cliente_df['ratio_ceros'] > 0.95) and (cliente_df['potencia_contratada'] > 10):
        return 1.0, 'Modelo', 'R2: Manipulación de contador (95% lecturas = 0)'
    
    if (cliente_df['factor_utilizacion'] < 0.01) and (cliente_df['consumo_historico_6m'] > 500):
        return 1.0, 'Modelo', 'R3: Puente directo a red (caída abrupta)'
    
    if cliente_df['diff_year_over_year'] < -0.80:
        return 1.0, 'Modelo', 'R4: Caída interanual > 80%'
    
    # FASE 2: Aplicar Modelo XGBoost
    X = preparar_features(cliente_df)
    score_ml = model_pipeline.predict_proba(X)[0, 1]
    
    # Explicación con SHAP
    shap_values = explainer.shap_values(X)
    top_feature = feature_names[np.argmax(np.abs(shap_values))]
    
    return score_ml, 'Modelo', f'XGBoost Score: {score_ml:.2f} | Top Feature: {top_feature}'

# Uso
for cliente in dataset_test:
    score, source, explanation = detectar_fraude_hibrido(cliente)
    if score > 0.70:
        print(f"Cliente {cliente['id']} - Score: {score:.2f} [{source}] - {explanation}")

9. Simulador de Impacto en Negocio

Herramienta interactiva para traducir métricas técnicas a retorno financiero (ROI). Modela la relación entre presupuesto, inspecciones y beneficio neto.

Parámetros Operativos

Incluye: desplazamiento + técnico + equipamiento
Energía defraudada + sanción administrativa

Resultados Proyectados

Capacidad de Inspecciones 100
Fraudes Esperados (Modelo) 29
Dinero Recuperado 43,500 €
Coste Total Campaña -5,000 €
BENEFICIO NETO 38,500 €

Modelo de Negocio Subyacente

Función de Precisión Decreciente

La precisión del modelo decrece a medida que se inspeccionan más casos (los primeros K son los de mayor confianza). Se modela con la siguiente función empírica extraída de la curva de Lift:

if inspecciones <= 100:
    fraudes = inspecciones × 0.29      # 29% precision en top-100
elif inspecciones <= 500:
    fraudes = 29 + (inspecciones - 100) × 0.16   # 16% en tramo 101-500
else:
    fraudes = 93 + (inspecciones - 500) × 0.10   # 10% en cola (501+)

Supuestos del Modelo

  • Linealidad por Tramos: Precisión constante dentro de cada tramo
  • Independencia: La detección de un fraude no afecta la probabilidad de otros
  • Costes Fijos: Cada inspección cuesta igual (no hay economías de escala)
  • Recuperación Uniforme: Todos los fraudes recuperan la misma cantidad promedio

Limitaciones Reconocidas

  • No considera costes de gestión administrativa
  • No modela variabilidad estocástica (valores son esperados)
  • Asume que todos los fraudes detectados se pueden recuperar
  • No incluye coste de oportunidad del capital

10. Conclusiones y Líneas Futuras

Conclusiones Principales del TFM

Validación Técnica

  • Señal en consumo: El fraude puede priorizarse eficazmente a partir de datos históricos de consumo, superando ampliamente al azar.
  • Metodología robusta: Validación temporal y prevención de fugas de información garantizan resultados reproducibles y realistas.

Impacto de Negocio

  • Estrategia híbrida: Reglas deterministas + modelo ML mejoran la detección frente a enfoques aislados.
  • Impacto cuantificable: ROI superior al 700% en escenarios realistas, con retorno inmediato para campañas de inspección.

Líneas de Trabajo Futuro

Corto Plazo

  • Validación con datos 2021-2023 para evaluar estabilidad temporal
  • Optimización de hiperparámetros mediante Optuna o GridSearch
  • Consolidación y despliegue del dashboard de casos sospechosos (feedback de inspecciones y usuarios)

Medio Plazo

  • Incorporación de datos de contadores inteligentes (granularidad horaria)
  • Detección de anomalías no supervisada como complemento
  • Validación cruzada con otras distribuidoras eléctricas

Largo Plazo

  • Sistema de reentrenamiento automático para adaptarse a concept drift
  • Integración con sistemas de gestión de inspecciones en tiempo real
  • Exploración de técnicas de grafos para detectar fraudes organizados

Mejoras Metodológicas

  • Data augmentation para clase minoritaria (SMOTE, ADASYN)
  • Ensemble de modelos especializados por segmento de cliente
  • Incorporación de variables externas (clima, actividad económica)

Reflexión Final

Este TFM demuestra que la aplicación rigurosa de técnicas de Ciencia de Datos puede transformar un problema de negocio crítico (pérdidas millonarias por fraude) en una oportunidad de valor (ROI > 700%). El éxito no radica solo en la complejidad algorítmica, sino en la comprensión profunda del dominio, la rigurosidad metodológica, y el balance entre interpretabilidad y potencia predictiva.

La estrategia híbrida (Reglas + ML) presentada es directamente desplegable en producción, utilizando datos ya disponibles en la compañía, sin inversión en nueva infraestructura. Representa un caso de uso ejemplar de cómo la Inteligencia Artificial puede generar valor tangible en entornos industriales complejos.

Limitaciones y siguientes pasos

  • Validación operativa: incorporar feedback de inspecciones (verdad terreno) para recalibración y reentrenamiento periódico.
  • Monitorización: métricas Top‑K, drift de distribución y alertas de degradación (data quality + performance).
  • Enriquecimiento: variables adicionales (eventos de red, incidencias, cambios contractuales) y análisis contrafactual para priorización.

11. Visualización Operativa: Casos Sospechosos

Fraude (definición operativa): pérdida no técnica por manipulación o anomalías de medida/contrato que pueden derivar en energía no facturada o facturación incorrecta.

Nota de uso y confidencialidad
Esta vista es una plantilla operativa preparada para alimentarse con salidas exportadas desde el notebook (Top‑K priorizado + explicabilidad). Identificadores y valores pueden mostrarse anonimizados o sintéticos.

Selección de caso

Criterio: Alto ≥ 0.80 · Medio 0.65–0.79 · Bajo < 0.65 (según score).
Rank = orden por score (descendente) dentro del conjunto de sospechosos.
Rank: — Score: —
Prioridad
—
Acción recomendada
—
Por qué se considera un caso de sospecha de fraude
Selecciona un caso para ver los criterios superados y la explicación.

SHAP (explicación por caso)

Contribución relativa de variables al score del caso seleccionado (visualización tipo SHAP).

Contexto temporal

Evolución sintética/anonimizada en la ventana de observación.

Resumen del porqué es un caso de sospecha de fraude Sospecha media

Selecciona un caso para ver por qué se considera sospechoso y qué criterios supera.

Criterios superados
Variable Valor Rango esperado Evidencia
Selecciona un caso para ver criterios y evidencias.
Definición académica (feature): ratio_pico_valle = max(consumo) / min(consumo > 0) en la ventana de observación; si no existe mínimo positivo, se aplica un ε de suavizado.