← 16 · ¿Se puede predecir? Y las anomalías H7 · H10

Un R² de 1,0000 indica que la respuesta se ha colado entre las variables de entrada

La pregunta de esta página

¿Cuánto vale una oportunidad de venta antes de cerrarla?

1 El problema

Es una pregunta comercial habitual. Entra un contacto que quiere un servidor en Teruel en marzo y conviene saber cuánto puede valer esa operación para decidir cuánto esfuerzo le dedica el equipo.

El fichero de la empresa tiene 19.984 ventas cerradas con su importe, suficientes para entrenar un modelo.

2 Por qué engaña

Lo fácil es darle al modelo todas las columnas de la tabla. Entre ellas están la cantidad y el precio final, que son las dos que multiplicadas dan el importe. El modelo sale perfecto.

Pero en el momento de usarlo no sirve. Cuando entra el contacto todavía no hay cantidad ni precio cerrado: esas dos columnas solo existen después de que pase lo que se quería predecir.

Cómo se cuela la respuesta
La respuesta no se mete a propósito. Se cuela por una columna calculada, por una fecha de cierre, por un campo que el sistema rellena al final del proceso. La señal suele ser la misma: un resultado demasiado bueno.

3 El principio

Fuga de información
Una variable solo puede entrar en el modelo si estará disponible en el momento en que haga falta la predicción. Lo que hay que preguntarse es si ese dato existe a tiempo.

4 La solución

Para cada variable candidata, situarse en el instante de la predicción y preguntarse si ya se conoce. Aquí, de antemano se conocen el producto, la provincia, la categoría y el mes. No se conocen la cantidad ni el precio final, que no existen hasta cerrar la venta.

Después se entrenan las dos versiones y se comparan, para ver el tamaño del error.

5 Qué pasa con estos datos

Solo con lo que se sabe antes
R² = 0,6507
60 variables
Añadiendo cantidad y precio
R² = 1,0000
62 variables

El modelo sin fuga explica el 65,1 % de la variación del importe, que para una previsión comercial es bastante. El otro da 1,0000, es decir, perfecto, porque se le han dado los dos factores de la multiplicación.

Qué mira cada uno

La importancia de variables del bosque lo muestra de un vistazo. Por eso conviene tener a mano un modelo de árboles, aunque el modelo final sea otro.

Sin fuga
  • Servidor Rack Xeon 71,2 %
  • mes 6,6 %
  • Portátil Core i7 6,4 %
  • Licencia ERP Cloud 5,0 %
  • Suscripción CRM Pro 0,5 %
Con fuga
  • precio 43,9 %
  • Servidor Rack Xeon 28,7 %
  • cantidad 27,0 %
  • cat_Software 0,1 %
  • Portátil Core i7 0,1 %

Sin fuga, el modelo se apoya en el producto: solo la columna del Servidor Rack Xeon pesa un 71,2 %. Con fuga, el precio se lleva el primer puesto y el producto pasa a segundo plano. La lista de importancias sirve para detectarlo: cuando una variable pesa mucho más que el resto, hay que mirar de dónde sale.

6 Para qué sirve

Es lo primero que hay que sospechar ante cualquier modelo que sale demasiado bien. Un R² de 0,99, una precisión del 98 % o un AUC de 0,995 hay que revisarlos como posible fuga antes de celebrarlos.

Y obliga a escribir el momento de la predicción antes de elegir variables, que es una disciplina que sirve igual con un modelo o con una regla de negocio.

El detalle técnico

Bosque aleatorio de 80 árboles, partición 70 % entrenamiento, 30 % prueba, semilla 42. Las variables categóricas entran con indicadores, que son las 60 columnas de la rama sin fuga.

Este caso hubo que rehacerlo. La primera versión predecía el importe con cantidad y precio en las dos ramas y las dos daban 1,0000 exacto. La fuga estaba también en la rama que se llamaba limpia, así que el ejemplo no demostraba nada.

Ver las dos ramas
# Lo que se sabe cuando hay que predecir
antes = pd.get_dummies(d[["prod", "reg", "cat"]]); antes["mes"] = d["mes"]

# Lo que NO se sabe hasta firmar: cantidad y precio final
despues = np.hstack([antes, d[["cant", "precio"]]])

# El mismo bosque, la misma particion, la misma semilla
for X in (antes, despues):
    a, b, ya, yb = train_test_split(X, y, test_size=0.3, random_state=42)
    print(r2_score(yb, RandomForestRegressor(random_state=42).fit(a, ya).predict(b)))

Fuente: segmentacion.json, campo h7_h10_fuga_e_importancia.