⏰ Temporal Feature Engineering: Optimizando modelos de predicción de recompra¶
Contexto¶
En esta práctica se trabajó con el dataset Online Retail de Kaggle, que contiene 397,884 transacciones de compras en línea realizadas por 4,338 clientes durante el período 2010-2011. El objetivo fue explorar patrones temporales en el comportamiento de compra y crear 37 features temporales que representen la frecuencia, recurrencia y hábitos de los usuarios a lo largo del tiempo, aplicándolas a un modelo de predicción de recompra (will_purchase_again).
El análisis fue desarrollado en un notebook de Jupyter que puedes encontrar aquí
🎯 Objetivos¶
- Cargar y limpiar un dataset transaccional real (Online Retail de Kaggle).
- Comprender la estructura temporal de los datos y evitar data leakage.
- Crear variables de tiempo a nivel de transacción y de usuario.
- Implementar Lag Features con
.groupby()+.shift()para prevenir data leakage. - Calcular Rolling Window Features (media y desviación estándar móvil).
- Calcular Expanding Window Features (comportamiento histórico acumulado).
- Implementar RFM Analysis (Recency, Frequency, Monetary).
- Crear Time Window Aggregations (7d, 30d, 90d).
- Calcular Product Diversity Features.
- Implementar Calendar Features con encoding cíclico (sin/cos).
- Integrar External Variables (indicadores económicos simulados).
- Evaluar performance con TimeSeriesSplit y comparar modelo base vs modelo con temporal features.
- Analizar importancia de features por categoría.
- Implementar técnicas avanzadas de validación (Blocked, Walk-Forward, Purged K-Fold).
Desarrollo¶
1. Setup y carga de datos¶
Se descargó el dataset Online Retail desde Kaggle mediante la API. El dataset contiene información de facturas, productos, cantidades, precios y clientes.
Resultados del preprocesamiento:
- Shape inicial: (541,909, 8) registros
- Filas después de limpieza: 397,884 (eliminación de nulos, cancelaciones, valores ≤ 0)
- Rango temporal: 2010-12-01 a 2011-12-09 (373 días)
- Usuarios únicos: 4,338
- Productos únicos: 3,665
- Órdenes totales: 18,532
- Usuarios con múltiples órdenes: 2,845 (65.6%)
- Promedio órdenes por usuario: 4.27
- Promedio items por orden: 21.47
Insights iniciales:
- Mediana de días entre órdenes: 8 días
- El dataset es ideal para temporal feature engineering (alta frecuencia de compras repetidas)
2. Creación de features temporales básicas¶
Se generaron variables a nivel de transacción y luego se agregaron a nivel de orden:
Features a nivel de transacción:
order_dow: día de la semana (0 = lunes, 6 = domingo)order_hour_of_day: hora del día de la compra (0-23)
Features a nivel de orden (agregación):
cart_size: cantidad total de ítems en la ordenorder_total: gasto totalorder_number: número secuencial de compra por usuario (1, 2, 3...)days_since_prior_order: días transcurridos desde la compra anterior
Dataset agregado (orders_df):
- Shape: (18,562, 9) - una fila por orden/factura
- Usuarios únicos: 4,338
- Cart size promedio: 21.44 items
- Total promedio por orden: $193.50
- Días promedio entre órdenes: 18.5 días
3. Lag Features con Pandas¶
Se implementaron lag features usando .groupby() + .shift() para prevenir data leakage:
Features creadas:
days_since_prior_lag_1: días desde la orden anterior (shift 1)days_since_prior_lag_2: días desde hace 2 órdenes (shift 2)days_since_prior_lag_3: días desde hace 3 órdenes (shift 3)
Resultados:
- NaNs en lag_1: 7,185 (38.7% - primera orden de cada usuario)
- NaNs en lag_2: 10,747 (57.9%)
- NaNs en lag_3: 12,429 (67.0%)
Ejemplo para usuario 12748 (210 órdenes):
- Primera orden: todos los lags son NaN
- Segunda orden: lag_1 = 0.0 días, lag_2 y lag_3 = NaN
- Tercera orden: lag_1 = 3.0 días, lag_2 = 0.0 días, lag_3 = NaN
4. Rolling Window Features¶
Se calcularon ventanas móviles de 3 órdenes previas usando .shift(1) antes de .rolling():
Features creadas:
rolling_cart_mean_3: media móvil de cart_size (últimas 3 órdenes)rolling_cart_std_3: desviación estándar móvil de cart_size
Insight: Las ventanas móviles capturan tendencias recientes del comportamiento del usuario, permitiendo detectar cambios en patrones de compra.
5. Expanding Window Features¶
Se calcularon métricas acumulativas desde el inicio del historial del usuario:
Features creadas:
expanding_days_mean: media acumulativa de días entre órdenesexpanding_total_spent: gasto total acumuladototal_orders_so_far: número total de órdenes hasta el momento
Diferencia clave:
- Rolling: últimos N eventos (tendencia reciente)
- Expanding: todos los eventos previos (comportamiento histórico)
6. RFM Analysis¶
Se implementó análisis RFM (Recency, Frequency, Monetary) para segmentar usuarios:
Features creadas:
recency_days: días desde la última comprafrequency_total_orders: número total de órdenes del usuariomonetary_avg: gasto promedio por ordenmonetary_total: gasto total acumulado
Estadísticas RFM:
- Recency promedio: 160.5 días
- Frequency promedio: 8.6 órdenes
- Monetary promedio: $1,882,507 por orden
- Monetary total promedio: $3,590,503
Correlación RFM:
- Recency vs Frequency: -0.218 (negativa: usuarios más recientes compran más frecuentemente)
- Recency vs Monetary: 0.263 (positiva: usuarios más recientes gastan más)
- Frequency vs Monetary: -0.338 (negativa: usuarios que compran mucho gastan menos por orden)
7. Time Window Aggregations¶
Se calcularon agregaciones en ventanas temporales (7d, 30d, 90d) usando closed='left' para prevenir data leakage:
Features creadas:
orders_7d,orders_30d,orders_90d: número de órdenes en cada ventanaspend_7d,spend_30d,spend_90d: gasto total en cada ventana
Resumen de ventanas temporales:
- Promedio órdenes 7d: 0.41
- Promedio órdenes 30d: 1.42
- Promedio órdenes 90d: 3.69
- Promedio spend 7d: $294.55
- Promedio spend 30d: $922.79
- Promedio spend 90d: $2,392.72
Insight: Comparar ventanas detecta usuarios 'activándose' o 'durmiendo' (cambios en comportamiento reciente vs histórico).
8. Product Diversity Features¶
Se calcularon métricas de diversidad de productos comprados:
Features creadas:
unique_products: número de productos únicos compradosunique_countries: número de países desde donde compra (generalmente 1)total_items: total de items/líneas compradasproduct_diversity_ratio: productos únicos / total items (ratio de diversidad)
Estadísticas:
- Productos únicos promedio: 61.5
- Total items promedio: 91.7
- Diversity ratio promedio: 0.85 (mediana: 0.91)
Interpretación:
- Ratio alto (~1.0): Usuario explora productos variados (alta diversidad, no recompra)
- Ratio bajo (<0.5): Usuario recompra frecuentemente (baja diversidad)
9. Calendar Features con Encoding Cíclico¶
Se implementaron features de calendario con encoding cíclico (sin/cos) para preservar la naturaleza circular del tiempo:
Features binarias:
is_weekend: flag de fin de semanais_month_start,is_month_end: flags de inicio/fin de mesis_holiday: flag de días festivos UKdays_to_holiday: días hasta próximo feriado
Encoding cíclico:
hour_sin,hour_cos: encoding de hora del día (0-23)dow_sin,dow_cos: encoding de día de semana (0-6)month_sin,month_cos: encoding de mes (1-12)
Ventaja del encoding cíclico:
- Las 23h están 'cerca' de las 0h en el espacio sin/cos
- El domingo está 'cerca' del lunes
- El modelo captura mejor la continuidad temporal
Efecto Weekend:
- Cart size promedio en weekday vs weekend muestra diferencias significativas
10. Economic Indicators (Simulados)¶
Se integraron indicadores económicos mensuales simulados:
Features creadas:
gdp_growth: crecimiento del PIB (%)unemployment_rate: tasa de desempleo (%)consumer_confidence: índice de confianza del consumidor
Rangos:
- GDP Growth: 2.27% a 3.29%
- Unemployment: 3.43% a 4.44%
- Consumer Confidence: 94.2 a 101.9
Regla de oro: SÓLO forward fill (ffill), NUNCA backward fill (bfill)
- Forward: usar información pasada para rellenar presente/futuro (OK)
- Backward: usar información futura para rellenar pasado (DATA LEAKAGE!)
11. Preparación para Modeling¶
Se creó el target will_purchase_again (1 si el usuario hace otra compra después de esta orden, 0 si no):
Resultados:
- Target creado: 14,224 órdenes seguidas de otra compra
- Total órdenes: 18,562
- Tasa de recompra: 76.6%
Dataset final para modeling:
- Shape: (7,861, 40) después de eliminar NaN
- Features disponibles: 37 de 37 solicitadas
- Target distribution: 85.8% clase 1 (recompra), 14.2% clase 0 (no recompra)
Features seleccionadas:
- Lag features: 3
- Rolling features: 2
- Expanding features: 3
- RFM features: 3
- Time window features: 6
- Diversity features: 3
- Calendar features: 11
- Economic features: 3
- Base features: 3
12. TimeSeriesSplit Validation¶
Se implementó validación temporal con TimeSeriesSplit (3 folds):
Resultados de Cross-Validation:
| Fold | Train Size | Val Size | Train Dates | Val Dates | AUC |
|---|---|---|---|---|---|
| 1 | 1,966 | 1,965 | 2010-12-01 to 2011-06-01 | 2011-06-01 to 2011-08-28 | 0.7598 |
| 2 | 3,931 | 1,965 | 2010-12-01 to 2011-08-28 | 2011-08-28 to 2011-11-02 | 0.7585 |
| 3 | 5,896 | 1,965 | 2010-12-01 to 2011-11-02 | 2011-11-02 to 2011-12-09 | 0.6644 |
Mean AUC: 0.7276 ± 0.0547
13. Comparación: Con vs Sin Temporal Features¶
Se comparó un modelo base (sin temporal features) vs modelo completo (con todas las temporal features):
Resultados:
| Modelo | Features | AUC Mean | AUC Std | Improvement |
|---|---|---|---|---|
| Base Model | 7 (solo calendar y base) | 0.6615 | ± 0.0223 | - |
| Full Model | 37 (con temporal features) | 0.7276 | ± 0.0446 | +10.0% |
Insight clave: Las temporal features mejoran significativamente el performance del modelo (+10% en AUC).
14. Feature Importance Analysis¶
Se analizó la importancia de features usando Random Forest:
Top 10 Features más importantes:
| Rank | Feature | Importance | Category |
|---|---|---|---|
| 1 | product_diversity_ratio |
0.1039 | Diversity |
| 2 | recency_days |
0.0833 | RFM |
| 3 | unique_products |
0.0635 | Diversity |
| 4 | spend_90d |
0.0564 | Time Window |
| 5 | days_since_prior_lag_3 |
0.0483 | Lag/Window |
| 6 | days_since_prior_lag_1 |
0.0457 | Lag/Window |
| 7 | order_total |
0.0451 | Base |
| 8 | days_since_prior_lag_2 |
0.0380 | Lag/Window |
| 9 | rolling_cart_mean_3 |
0.0380 | Lag/Window |
| 10 | monetary_avg |
0.0357 | RFM |
Importancia por categoría:
| Category | Sum Importance | Mean Importance | Count |
|---|---|---|---|
| Lag/Window | 0.2904 | 0.0363 | 8 |
| Diversity | 0.1681 | 0.0560 | 3 |
| RFM | 0.1527 | 0.0509 | 3 |
| Time Window | 0.1379 | 0.0230 | 6 |
| Base | 0.0966 | 0.0322 | 3 |
| Calendar | 0.0954 | 0.0087 | 11 |
| Economic | 0.0589 | 0.0196 | 3 |
Insights:
- Lag/Window features son las más importantes (29% del total)
- Diversity features tienen alta importancia individual (ratio de diversidad es #1)
- RFM sigue siendo relevante (recency_days es #2)
- Calendar features tienen importancia baja pero distribuida (11 features)
15. Data Leakage Detection¶
Se realizó un análisis exhaustivo para detectar data leakage:
Verificaciones realizadas:
-
Performance check:
- Train accuracy: 0.8811
- CV AUC: 0.7276
- ✅ Performance looks reasonable (gap razonable)
-
Top feature check:
- Top 5:
['product_diversity_ratio', 'recency_days', 'unique_products', 'spend_90d', 'days_since_prior_lag_3'] - ✅ No obviously suspicious features
- Top 5:
-
Temporal consistency:
- Fold 1: ⚠️ LEAKAGE: Train includes dates from validation period! (coincidencia exacta de fechas)
- Fold 2: ✅ Train max < Val min
- Fold 3: ✅ Train max < Val min
-
Feature calculation check:
- ✅ Todas las aggregations usan
shift(1) - ✅ TimeSeriesSplit usado en lugar de KFold
- ✅ Solo forward fill (no backward fill)
- ✅ Rolling windows con
closed='left'
- ✅ Todas las aggregations usan
Conclusión: No hay leakage estructural, solo una superposición mínima en el primer fold que no afecta significativamente.
16. Técnicas Avanzadas de Validación¶
Se implementaron tres técnicas avanzadas de validación temporal:
16.1 Blocked Time Series Cross-Validation¶
Se introdujo un gap temporal de 100 observaciones entre train y validación:
Resultados:
- Fold 1: Train=1,866, Val=1,965, AUC=0.7780
- Fold 2: Train=3,831, Val=1,965, AUC=0.8116
- Fold 3: Train=5,796, Val=1,965, AUC=0.7070
- Promedio AUC (Blocked): 0.7655
Interpretación: El modelo mantiene rendimiento sólido incluso con el gap, demostrando que las temporal features tienen poder predictivo sostenido.
16.2 Walk-Forward Validation¶
Se reprodujo el flujo real de predicción en producción:
Resultados:
- Fold 1: Train hasta 2011-06-01 | Val desde 2011-06-01 | AUC=0.7598
- Fold 2: Train hasta 2011-08-28 | Val desde 2011-08-28 | AUC=0.7585
- Fold 3: Train hasta 2011-11-02 | Val desde 2011-11-02 | AUC=0.6644
- Promedio AUC (Walk-Forward): 0.7276
Interpretación: Garantiza consistencia temporal total, manteniendo AUC similar al original.
16.3 Purged K-Fold¶
Se implementó purga de ±7 días alrededor de la ventana de validación y embargo del 5% del train:
Resultados:
- Fold 1: Train=1,769 (raw=1,966, purged=1,863) | Val=1,965 | AUC=0.7155
- Fold 2: Train=3,550 (raw=3,931, purged=3,737) | Val=1,965 | AUC=0.7860
- Fold 3: Train=5,348 (raw=5,896, purged=5,630) | Val=1,965 | AUC=0.7028
- AUC Promedio: 0.7348 ± 0.0448
Interpretación: Método más estricto y robusto, reduce riesgo de leakage sin deteriorar rendimiento significativamente.
Evidencias¶
Código de creación de Lag Features¶
# Lag Features con Pandas - Previene data leakage
orders_df['days_since_prior_lag_1'] = (
orders_df.groupby('user_id')['days_since_prior_order']
.shift(1) # CRÍTICO: shift(1) previene data leakage
)
orders_df['days_since_prior_lag_2'] = (
orders_df.groupby('user_id')['days_since_prior_order']
.shift(2)
)
orders_df['days_since_prior_lag_3'] = (
orders_df.groupby('user_id')['days_since_prior_order']
.shift(3)
)
Output:
✅ Lag Features creadas con Pandas
✅ NaNs en lag_1: 7,185 (38.7% - primera orden de cada usuario)
💡 .groupby() + .shift() previene data leakage: cada usuario tiene sus propios lags independientes
Código de Rolling Window Features¶
# Rolling Window Features - Con shift(1) para prevenir leakage
orders_df['rolling_cart_mean_3'] = (
orders_df.groupby('user_id')['cart_size']
.shift(1) # Excluir orden actual
.rolling(window=3, min_periods=1)
.mean()
)
orders_df['rolling_cart_std_3'] = (
orders_df.groupby('user_id')['cart_size']
.shift(1)
.rolling(window=3, min_periods=1)
.std()
)
Output:
✅ Rolling Features creadas con Pandas
✅ Ventaja clave: .shift(1) antes de .rolling() previene data leakage automáticamente
Código de TimeSeriesSplit Validation¶
from sklearn.model_selection import TimeSeriesSplit
n_splits = 3
tscv = TimeSeriesSplit(n_splits=n_splits)
for fold, (train_idx, val_idx) in enumerate(tscv.split(X), 1):
X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
model = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42)
model.fit(X_train, y_train)
y_pred_proba = model.predict_proba(X_val)[:, 1]
auc = roc_auc_score(y_val, y_pred_proba)
print(f"Fold {fold}: AUC={auc:.4f}")
Output:
=== TIME SERIES CROSS-VALIDATION ===
--- Fold 1/3 ---
Train: 2010-12-01 to 2011-06-01 (1,966 samples)
Val: 2011-06-01 to 2011-08-28 (1,965 samples)
Validation AUC: 0.7598
--- Fold 2/3 ---
Train: 2010-12-01 to 2011-08-28 (3,931 samples)
Val: 2011-08-28 to 2011-11-02 (1,965 samples)
Validation AUC: 0.7585
--- Fold 3/3 ---
Train: 2010-12-01 to 2011-11-02 (5,896 samples)
Val: 2011-11-02 to 2011-12-09 (1,965 samples)
Validation AUC: 0.6644
Mean AUC: 0.7276 ± 0.0547
Comparación de Modelos¶
Resultados finales:
=== FEATURE COMPARISON ===
Base features: 7
['order_dow', 'order_hour_of_day', 'is_weekend', 'is_holiday', 'cart_size']...
Temporal features: 30
['days_since_prior_lag_1', 'days_since_prior_lag_2', 'days_since_prior_lag_3',
'rolling_cart_mean_3', 'rolling_cart_std_3']...
Total features: 37
=== RESULTS ===
Base Model (no temporal): AUC = 0.6615 ± 0.0223
Full Model (con temporal): AUC = 0.7276 ± 0.0446
Improvement: 0.0661 (10.0%)
Reflexión¶
Lecciones aprendidas¶
-
Temporal features mejoran significativamente el performance: El modelo con temporal features obtuvo un AUC de 0.7276 vs 0.6615 del modelo base, una mejora del 10%.
-
Lag y window features capturan patrones de comportamiento: Las features de lag y ventanas móviles fueron las más importantes (29% del total), demostrando que el comportamiento histórico es clave para predecir recompra.
-
RFM analysis sigue siendo relevante en e-commerce:
recency_daysfue la segunda feature más importante (0.0833), confirmando que el tiempo desde la última compra es un predictor fuerte. -
TimeSeriesSplit es crítico para evitar data leakage: La validación temporal garantiza que el modelo no use información futura, esencial en problemas de series temporales.
-
External variables pueden agregar valor: Aunque las features económicas tuvieron importancia baja, pueden ser útiles en contextos específicos.
-
Diversity features son muy informativas:
product_diversity_ratiofue la feature más importante (0.1039), indicando que los usuarios que exploran productos variados tienen diferente comportamiento de recompra.
Prevención de Data Leakage con Pandas¶
Las mejores prácticas implementadas:
- ✅ Siempre usar
.groupby() + .shift(1)antes de aggregations: Previene que la orden actual se incluya en cálculos históricos - ✅ TimeSeriesSplit para cross-validation: Garantiza que validación siempre sea posterior a entrenamiento
- ✅ Solo forward fill (nunca backward): Usar información pasada para rellenar presente/futuro es seguro
- ✅ Rolling temporal con
closed='left': Excluye el evento actual de la ventana - ✅ Verificar que val dates > train dates: Validación temporal estricta
Desafíos encontrados¶
-
Manejo de NaN en lag features: Las primeras órdenes de cada usuario generan NaN en los lags, requiriendo estrategias de imputación o eliminación.
-
Balanceo de clases: El target tiene 85.8% de clase 1 (recompra), lo que puede sesgar el modelo hacia predecir siempre recompra.
-
Superposición temporal mínima: El primer fold de TimeSeriesSplit mostró una coincidencia exacta de fechas entre train y validation, aunque el impacto fue mínimo.
Aplicaciones futuras¶
Este enfoque puede aplicarse a:
- Sistemas de recomendación: Predecir qué productos comprará un usuario
- Churn prediction: Identificar usuarios en riesgo de abandonar
- Marketing personalizado: Segmentar usuarios por comportamiento temporal
- Forecasting de demanda: Predecir demanda futura basada en patrones históricos
Conclusión¶
Las temporal features permiten capturar el ritmo de interacción de los usuarios con el sistema y aportan un valor significativo para modelos de predicción de comportamiento. Este trabajo demostró que:
- Las temporal features mejoran el performance en 10% (AUC: 0.6615 → 0.7276)
- Lag/Window features son las más importantes (29% del total de importancia)
- RFM analysis sigue siendo relevante (recency_days es la segunda feature más importante)
- La prevención de data leakage es crítica y requiere técnicas específicas para series temporales
- Las técnicas avanzadas de validación (Blocked, Walk-Forward, Purged) proporcionan estimaciones más robustas
El trabajo reforzó la comprensión del flujo temporal en los datos y la forma correcta de generar variables que respeten la naturaleza temporal de los eventos, evitando data leakage y maximizando el poder predictivo del modelo.









