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)
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 JOINpriorizando 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) |
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)
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
pd.concat([consumos_2018, consumos_2019_2020]) → 1,496,596 registros totales
Objetivo: Unificar todas las lecturas de consumo en un único dataframe temporal
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.
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".
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.
Se usa how='left' para conservar todas las lecturas de consumo, incluso si faltan datos contractuales. Los nulos resultantes se imputan posteriormente.
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 |
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)
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 |
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.
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_ceroscreada
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% |
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)
Consumo Mensual Promedio
- Media: 278 kWh/mes
- Desv. Estándar: 340 kWh (alta variabilidad)
- Coef. Variación: 122% (dispersión > media)
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.
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 |
- 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:
baja_registrada: 0.187ratio_ceros: 0.143potencia_contratada: 0.129diff_media_movil: 0.118factor_utilizacion: 0.095
Interpretabilidad: SHAP Values
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.
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
Pipeliney CV controlada.
Pipeline de Preprocesamiento (sklearn)
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]
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)
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)
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 |
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.
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:
- Velocidad: Optimización paralela de árboles (10-20x más rápido que Random Forest en CPU)
- Manejo de Nulos: Sparcity-aware split finding (aprende dirección óptima para nulos)
- Regularización: L1/L2 incorporadas para evitar overfitting
- Desbalanceo: Parámetro
scale_pos_weightnativo - Interpretabilidad: Compatible con SHAP values
Fundamentos del Algoritmo
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.
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. |
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
- 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
- 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)
Nota: En problemas con prevalencia extremadamente baja, las métricas Top‑K reflejan mejor el valor operativo que un umbral 0.5.
ROC-AUC
Capacidad de discriminación global
PR-AUC
Métrica para desbalanceo extremo
Precisión Top-100
29 fraudes en primeras 100 inspecciones
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) |
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
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)
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:
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%.
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%) |
- 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
- 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 |
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
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
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.
Selección de caso
SHAP (explicación por caso)
Contexto temporal
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.
| Variable | Valor | Rango esperado | Evidencia |
|---|---|---|---|
| Selecciona un caso para ver criterios y evidencias. | |||
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.