Este es el capítulo que casi ningún curso enseña y el que decide si el proyecto sirve o no sirve 💛
Va antes del código a propósito, porque todo lo que viene después depende de él. Y no es teoría de consultora: son cuatro preguntas concretas que hay que hacer, y las cuatro tienen respuesta medible en el archivo.
La escena de siempre: "Yera, queremos un modelo que prediga qué clientes van a comprar". Suena clarísimo y no lo es. Vamos a ver por qué.
Pregunta 1. ¿Qué se va a hacer distinto?
Antes que nada, esta. Si la respuesta es "tener el dato", el proyecto no empieza.
Un modelo no sirve para saber: sirve para decidir distinto. Y esa decisión tiene que existir antes de que exista el modelo.
- ✅ "Vamos a llamar a los 200 clientes con más probabilidad de comprar." Perfecto: hay una acción, un número de llamadas y alguien que las hace.
- ✅ "Vamos a darle 10% de descuento a los que están a punto de irse." Perfecto también, y encima define el costo de equivocarse.
- ❌ "Queremos entender mejor a nuestros clientes." Eso no es un modelo, es un análisis, y es más barato y más útil. Capítulo 14 del libro de SQL.
- ❌ "Para el directorio." Ese proyecto muere en tres meses, y lo digo con cariño 🫠
De esa respuesta salen dos cosas que vas a necesitar sí o sí: cuántas predicciones se pueden atender (si el call center hace 200 llamadas, tu lista tiene 200 nombres, no 3.000) y cuánto cuesta cada tipo de error. Las dos vuelven en el capítulo 15.
Pregunta 2. ¿Una fila es qué, exactamente?
Aquí es donde se cae la mitad de los proyectos, y se ve en un número.
import pandas as pd
URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'
ventas = pd.read_csv(URL)
print('filas:', len(ventas))
print('clientes distintos:', ventas['cliente_id'].nunique())
print('tasa por venta:', round(ventas['compro'].mean(), 4))
filas: 3037 clientes distintos: 617 tasa por venta: 0.5772
3.037 ventas de 617 clientes, y el 57,7% de las ventas se cierra.
Ahora la misma pregunta mirando clientes en vez de ventas:
por_cliente = ventas.groupby('cliente_id')['compro'].max()
print('clientes que compraron alguna vez:', round(por_cliente.mean(), 4))
clientes que compraron alguna vez: 0.9303
93%. Contra el 57,7% de antes.
Los dos números son correctos y contestan preguntas distintas. "¿Se cierra esta venta?" es 57,7%. "¿Este cliente compra alguna vez?" es 93%. Si el cliente te pide lo segundo y tú entrenas lo primero, entregas un modelo impecable que no sirve 😳
Y hay un tercer corte, que suele ser el que de verdad quieren:
tasa = ventas.groupby('cliente_id')['compro'].mean()
print('clientes que compran siempre:', round((tasa == 1).mean(), 4))
print('clientes que no compran nunca:', round((tasa == 0).mean(), 4))
clientes que compran siempre: 0.1183 clientes que no compran nunca: 0.0697
Solo el 11,8% compra siempre y el 7% no compra nunca. O sea que el 81% de los clientes a veces sí y a veces no, y esos son los interesantes: los que dependen de algo que puedes cambiar.
La pregunta que hay que hacer en la reunión, palabra por palabra: ¿una fila de tu reporte es una venta o es un cliente? 🎯
Pregunta 3. ¿Qué significa exactamente la etiqueta?
"Compró" parece que no necesita definición. Sí la necesita.
¿Compró si pagó? ¿Si le llegó el pedido? ¿Y si devolvió? ¿Y si compró S/12 cuando el mínimo rentable son S/300?
monto = pd.to_numeric(ventas['monto'].str.replace(',', '.'), errors='coerce')
venta_grande = ((ventas['compro'] == 1) & (monto > 500)).astype(int)
print('objetivo "cerró": ', round(ventas['compro'].mean(), 4))
print('objetivo "cerró y > 500":', round(venta_grande.mean(), 4))
objetivo "cerró": 0.5772 objetivo "cerró y > 500": 0.3523
57,7% contra 35,2%. Es el mismo archivo y son dos problemas distintos, con distinto listón, distinta dificultad y distinto valor para el negocio.
Y fíjate en que la segunda definición es más útil casi siempre: al call center no le sirve saber que alguien va a comprar un chicle 🍬
La definición del objetivo se escribe, se firma y se pega arriba del cuaderno. Cambiarla a mitad de proyecto significa tirar todo lo medido.
Pregunta 4. ¿En qué momento se predice?
Esta es la que más caro sale y la que nadie hace. Se llama el momento de la predicción: qué se sabe y qué no se sabe cuando el modelo tiene que contestar.
Mira esta columna del archivo:
print(ventas.groupby('compro')['monto_final_facturado'].agg(['count', 'min', 'max', 'mean']).round(2))
count min max mean compro 0 1284 0.00 0.0 0.00 1 1753 2.53 4171.7 831.97
Todas las ventas que no se cerraron tienen exactamente 0,00. Todas las que sí, tienen un monto.
O sea que esa columna es la respuesta escrita de otra forma. Un modelo que la use va a acertar el 100%, y va a fallar el 100% en producción, porque el día que tienes que predecir todavía no se facturó nada.
Eso se llama fuga de información y tiene el capítulo 12 entero, con la demostración numérica. Aquí me interesa la parte que no es técnica: se caza preguntando, no programando. Por cada columna de tu tabla, una pregunta:
Esto, ¿cuándo se rellena?
Si la respuesta es "cuando se cierra la venta", fuera. Si es "el mes
siguiente", fuera. Si es "cuando el cliente reclama", fuera. Y ojo, que las
peores no se llaman monto_final_facturado: se llaman
score_riesgo o segmento_asignado y las calculó otro
equipo con información que tú no ves 🕵️♀️
El contrato, en una tabla
Esto es lo que yo pego arriba del cuaderno antes de escribir una línea:
| Punto | Ejemplo con este archivo |
|---|---|
| Decisión | Qué llamadas hace el equipo comercial esta semana |
| Capacidad | 200 llamadas por semana, o sea 200 nombres |
| Unidad | Una fila es una oportunidad de venta |
| Objetivo | compro = 1, o sea que se cerró |
| Momento | Al registrarse la oportunidad, antes de gestionarla |
| Prohibido | monto_final_facturado: solo existe después |
| Costo de fallar | Llamar de más: 15 minutos. No llamar a quien iba a comprar: la venta entera |
| Listón | 57,7% de cierre sin modelo |
Ocho líneas. Escribirlas cuesta una reunión de cuarenta minutos y evita el mes que se pierde cuando alguien dice "ah, pero yo pensaba que era por cliente" 📋
El otro contrato: el de las columnas
Y hay una parte más aburrida que también hay que acordar, porque si no un lunes te llega el archivo con las columnas renombradas y todo revienta.
print(ventas.dtypes)
id_venta int64 fecha str cliente_id str ciudad str segmento str canal str categoria str unidades int64 monto str descuento float64 fecha_ultima_compra str satisfaccion float64 compro int64 monto_final_facturado float64 dtype: object
Ahí ya hay dos avisos: monto es texto cuando debería ser un
número (viene con coma decimal), y fecha también es texto. Los dos
son el capítulo 4.
Lo que se acuerda con quien manda los datos: los nombres de las columnas, su tipo, sus valores posibles y qué significa un hueco. Esa última es la que más se olvida y la que más vale, y por eso tiene su propia sección en el capítulo 4 🕳️
Ejercicios
Siete. Intenta antes de abrir 💛
1. Cuántas oportunidades tiene cada cliente
Distribución de filas por cliente: mínimo, mediana y máximo.
print(ventas['cliente_id'].value_counts().describe().round(2))
count 617.00 mean 4.92 std 2.24 min 1.00 25% 3.00 50% 5.00 75% 6.00 max 14.00 Name: count, dtype: float64
De 1 a 14 oportunidades por cliente, con mediana 5. Ese dato importa para el capítulo 16: si un cliente aparece 14 veces y sus filas caen mitad en entrenamiento y mitad en examen, el modelo lo "conoce" cuando no debería.
Se llama fuga por grupo y se arregla partiendo por cliente y no por fila 🔒
2. El objetivo, definido en soles
Cuánto cambia el listón si el objetivo es "cerró y pasa de S/1.000".
mil = ((ventas['compro'] == 1) & (monto > 1000)).astype(int)
print('tasa:', round(mil.mean(), 4))
print('positivos:', int(mil.sum()))
tasa: 0.1906 positivos: 579
De 1.753 casos positivos bajamos a 579, o sea del 57,7% al 19,1%. El problema se puso mucho más difícil y también mucho más valioso: esas 579 son las ventas que de verdad mueven el mes.
Y ojo con algo: cuando los positivos bajan del 20%, las métricas de siempre empiezan a mentir. Con un 19% de positivos, decir "no" a todo el mundo ya acierta el 81% de las veces 🎯 Eso es el capítulo 18 entero.
Fíjate en el patrón: cada vez que el objetivo se afina, el listón sube y el problema se pone más difícil. No es un motivo para no afinarlo, es un motivo para saberlo antes y no llevarse el susto en la presentación.
3. La columna prohibida, en una línea
Demuestra que monto_final_facturado es la
respuesta disfrazada.
print((ventas['monto_final_facturado'] > 0).astype(int).equals(ventas['compro']))
True
True. Esa columna convertida a "mayor que cero" es
idéntica al objetivo, fila por fila, en las 3.037.
Esta comprobación cabe en una línea y la hago con todas las columnas
sospechosas antes de modelar. Cuando da True, la columna se va y
no hay discusión 🚫
4. Qué se sabe antes y qué después
Clasifica las 14 columnas del archivo según si se conocen antes de gestionar la venta o no.
ANTES = ['id_venta', 'fecha', 'cliente_id', 'ciudad', 'segmento', 'canal',
'categoria', 'unidades', 'monto', 'descuento',
'fecha_ultima_compra', 'satisfaccion']
DESPUES = ['compro', 'monto_final_facturado']
No lleva salida porque no es código, es una decisión. Y esa lista de
ANTES es literalmente la que vas a ver en el capítulo 11 cuando
armemos el pipeline.
Dos de esas doce son discutibles y merecen preguntarse:
descuento (¿se ofreció antes o se negoció durante?) y
satisfaccion (¿es de compras anteriores o de esta?). En este
archivo son de antes, y por eso están. En tu trabajo, pregunta 🤔
5. El error que te va a pasar en la primera reunión
El cliente te dice "agrupa por cliente" y tú escribes lo que suena.
ventas.groupby('cliente')['compro'].mean()
KeyError: 'cliente'
KeyError: 'cliente'. La columna se llama cliente_id.
Parece una tontería y es justo lo que evita el contrato de columnas: los nombres se escriben una vez, se acuerdan, y no se adivinan. Cuando el archivo tiene 80 columnas y te lo manda otro equipo, esto pasa diez veces al día 🫠
6. Cuántas llamadas caben
Si el equipo hace 200 llamadas por semana y hay 3.037 oportunidades, ¿qué porcentaje de la lista puedes atender?
print('oportunidades:', len(ventas))
print('capacidad semanal:', 200)
print('porcentaje atendible:', round(100 * 200 / len(ventas), 1))
oportunidades: 3037 capacidad semanal: 200 porcentaje atendible: 6.6
El 6,6%. Y eso cambia el problema entero: no necesitas un modelo que acierte en todas, necesitas uno que ponga arriba a las 200 mejores.
Es una métrica distinta y tiene nombre, precisión en el top K, y sale en el capítulo 14. Sin la pregunta de la capacidad nunca habrías sabido que era esa la que había que mirar 📞
7. El contrato de tu propio proyecto
Copia esta plantilla y llénala con un caso tuyo, aunque sea inventado. Es el ejercicio que más te va a servir de todo el capítulo.
DECISIÓN : ...qué se hace distinto con la predicción CAPACIDAD : ...cuántos casos se pueden atender UNIDAD : ...una fila es un/una ... OBJETIVO : ...la etiqueta vale 1 cuando ... MOMENTO : ...se predice en el instante en que ... PROHIBIDO : ...columnas que no existen en ese instante COSTO FN : ...qué pasa si digo que no y era que sí COSTO FP : ...qué pasa si digo que sí y era que no LISTÓN : ...qué se acierta hoy sin modelo
Si alguna línea no la puedes llenar, esa es tu próxima pregunta al cliente. Y si son tres o más las que no puedes llenar, todavía no hay proyecto: hay una idea 💛
Comprueba que lo tienes
¿Cuándo se decide qué significa cada columna?
- Antes de tocar el modelo, y por escrito
- Cuando el modelo dé resultados raros
- Lo decide quien programa, mirando los datos
- No hace falta si el modelo funciona
Lo que te llevas
- 🎬 Si nadie va a hacer nada distinto con la predicción, no hay proyecto.
- 🧱 La unidad manda: aquí, 57,7% por venta contra 93% por cliente. Son dos preguntas y solo una es la que te pidieron.
- 🏷️ El objetivo se define por escrito: "cerró" da 57,7% y "cerró y pasa de S/500" da 35,2%.
- ⏰ El momento de la predicción es lo que caza la fuga, y se caza preguntando por cada columna cuándo se rellena.
- 🚫
monto_final_facturadoconvertida a "mayor que cero" es idéntica al objetivo en las 3.037 filas. Fuera. - 📞 La capacidad cambia la métrica: con 200 llamadas para 3.037 oportunidades, lo que importa es el orden, no el acierto global.
- 📋 Ocho líneas de contrato cuestan una reunión y ahorran un mes.
En el capítulo 4 abrimos el archivo de verdad y nos peleamos con la suciedad: Lima escrita de cuatro formas, montos con coma, fechas en dos formatos y tres columnas con huecos.
Que tengas lindo día! 🌸