Capítulo 2 de 25 8 secciones 11 min

El contrato de datos, o qué se acuerda antes de modelar

Las cuatro preguntas que deciden si el proyecto sirve, y las cuatro tienen respuesta medible en el archivo.

Antes de entrenar se acuerdan cuatro cosas: qué decisión va a cambiar con la predicción, qué es una fila (aquí, 57,7% de cierre por venta contra 93% por cliente), qué significa exactamente la etiqueta, y en qué momento se predice, que es lo que caza las columnas que solo existen después. En este archivo, monto_final_facturado convertida a "mayor que cero" es idéntica al objetivo en las 3.037 filas.

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:

PuntoEjemplo con este archivo
DecisiónQué llamadas hace el equipo comercial esta semana
Capacidad200 llamadas por semana, o sea 200 nombres
UnidadUna fila es una oportunidad de venta
Objetivocompro = 1, o sea que se cerró
MomentoAl registrarse la oportunidad, antes de gestionarla
Prohibidomonto_final_facturado: solo existe después
Costo de fallarLlamar de más: 15 minutos. No llamar a quien iba a comprar: la venta entera
Listón57,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_facturado convertida 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! 🌸

¿Tienes alguna duda o consulta?