{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# El proyecto entero\n",
    "\n",
    "Todo el libro en un solo archivo, y la pregunta con la que se termina de verdad: a quién llamo el lunes.\n",
    "\n",
    "Cuaderno de práctica del capítulo 28 de **Machine learning desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/machine-learning-desde-cero/proyecto-final/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"Y3Vwbz0gIDUwICBtYXJnZW49Uy8gMjcsOTEyLjQ1ICBhbCBhemFyPVMvICA2LDY5My4yMyAgZ2FuYW5jaWE9Uy8yMSwyMTkuMjMKY3Vwbz0gMTAwICBtYXJnZW49Uy8gNDcsMDk5LjcyICBhbCBhemFyPVMvIDEzLDM4Ni40NiAgZ2FuYW5jaWE9Uy8zMyw3MTMuMjYKY3Vwbz0gMjAwICBtYXJnZW49Uy8gNzYsMzAyLjQxICBhbCBhemFyPVMvIDI2LDc3Mi45MiAgZ2FuYW5jaWE9Uy80OSw1MjkuNDkKY3Vwbz0gNDAwICBtYXJnZW49Uy8xMDgsMzg4LjkyICBhbCBhemFyPVMvIDUzLDU0NS44NCAgZ2FuYW5jaWE9Uy81NCw4NDMuMDkKY3Vwbz0gODAwICBtYXJnZW49Uy8xMzAsNjkxLjM4ICBhbCBhemFyPVMvMTA3LDA5MS42NyAgZ2FuYW5jaWE9Uy8yMyw1OTkuNzE=\",\n",
    "    2: \"Y29pbmNpZGVuIGNvbiBsYSBsaXN0YSBhbnRlcmlvcjogMTc3IGRlIDIwMAoKY2F0ZWdvcmlhCkFiYXJyb3RlcyAgICAgICAgICAgMzIKQmViaWRhcyAgICAgICAgICAgICAzMwpDdWlkYWRvIHBlcnNvbmFsICAgIDQ4CkxpbXBpZXphICAgICAgICAgICAgMzkKU25hY2tzICAgICAgICAgICAgICA0OA==\",\n",
    "    3: \"MjAyNS0xMC0wMSAgZnV0dXJvPTE1MDkgIGdhbmFuY2lhPVMvNjEsODM2LjAwCjIwMjYtMDEtMDEgIGZ1dHVybz0gOTkyICBnYW5hbmNpYT1TLzQ5LDUyOS40OQoyMDI2LTAzLTAxICBmdXR1cm89IDY2NyAgZ2FuYW5jaWE9Uy8zNywwMTYuOTc=\",\n",
    "    4: \"c2VnbWVudG8KSG9yZWNhICAgICAgICAgOQpNYXlvcmlzdGEgICAgMTkxCgp2ZW50YXM6IDE0NSAgbWFyZ2VuOiBTLyA3NTY1MS45NA==\",\n",
    "    5: \"ZW4gbGEgbGlzdGEsIGVsIG1vZGVsbyBlc3TDoSBzZWd1cm8gZGU6IDE2MyBkZSAyMDAKYWNpZXJ0YW4gY3VhbmRvIGVzdMOhIHNlZ3VybyA6IDAuNzczCmFjaWVydGFuIGN1YW5kbyBubyBsbyBlc3TDoSAgOiAwLjU2NzY=\",\n",
    "    7: \"ZW50cmVuYWRvIGNvbiAgICAgICAgICAgICAgICAgICAgMjAyNS0wMS0wMSBhIDIwMjUtMTItMzEKZmlsYXMgZGUgZW50cmVuYW1pZW50byAgICAgICAgICAgMjAwOApjb2x1bW5hcyAgICAgICAgICAgICAgICAgICAgICAgICAxMwpBVUMgcHLDs3hpbW8gdHJpbWVzdHJlICAgICAgICAgICAgMC43MjI3CnZlbnRhcyBkZSBsYSBsaXN0YSAgICAgICAgICAgICAgIDE0Nwp2ZW50YXMgYWwgYXphciAgICAgICAgICAgICAgICAgICAxMTQuMQptYXJnZW4gZGUgbGEgbGlzdGEgICAgICAgICAgICAgICA3NjMwMi40MQpwcmVjaW8gZGUgbGEgY3VvdGEgcG9yIHNlZ21lbnRvICAzMjg2OS44NgpubyBzYWJlIGhhY2VyICAgICAgICAgICAgICAgICAgICBjbGllbnRlcyBudWV2b3MgKGFow60gYmFqYSBhIDAuNjg0Myk=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Veintitrés capítulos y llegamos a lo único que le importa a quien pagó por esto:\n",
    "**a quién llamo el lunes** 📞\n",
    "\n",
    "Este capítulo es el proyecto entero seguido, sin desviarse. Cada decisión lleva\n",
    "al lado el capítulo donde la discutimos, para que puedas volver.\n",
    "\n",
    "Y termina donde tiene que terminar un proyecto de datos: en una decisión de\n",
    "negocio con su precio medido al lado."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El encargo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El equipo comercial puede visitar 200 clientes al trimestre. Hoy los elige por\n",
    "costumbre. La pregunta es si podemos elegirlos mejor.\n",
    "\n",
    "Fíjate que el encargo no dice \"hazme un modelo\". Dice 200. Ese número es el que\n",
    "manda, y es el que va a decidir todo lo que viene 🎯\n",
    "\n",
    "Antes de tocar nada, tres preguntas al que encarga, que son las del capítulo\n",
    "2 aplicadas aquí:\n",
    "\n",
    "- 🎯 **¿Qué se va a hacer distinto?** Se cambia el criterio con\n",
    "el que se arma la ruta de visitas. Si la respuesta hubiera sido \"nada, es para\n",
    "tener el dato\", no hay proyecto.\n",
    "\n",
    "- 📅 **¿Cuándo se decide?** Al empezar el trimestre, así que solo\n",
    "vale lo que se sabe antes de la visita. Eso deja fuera media tabla.\n",
    "\n",
    "- 💰 **¿Contra qué se compara el éxito?** Contra elegir por\n",
    "costumbre. Como no tenemos registro de la costumbre, usamos el azar como listón,\n",
    "que es más exigente de lo que parece.\n",
    "\n",
    "Y una cuarta que casi nadie hace y ahorra disgustos: **¿qué pasa con\n",
    "los que no entran en la lista?** Porque la respuesta por defecto es \"no\n",
    "se les visita\", y eso tiene consecuencias que vamos a ver en el paso 6 y que no\n",
    "salen en ninguna métrica 👀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 1. Cargar y limpiar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Del capítulo 4, tal cual, sin cambiar nada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.compose import ColumnTransformer\n",
    "from sklearn.impute import SimpleImputer\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import roc_auc_score\n",
    "from sklearn.model_selection import GroupShuffleSplit, train_test_split\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.preprocessing import OneHotEncoder, StandardScaler\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'\n",
    "MARGEN = 0.25          # el negocio se queda con 25 centavos de cada sol vendido\n",
    "CUPO = 200             # visitas que caben en el trimestre\n",
    "\n",
    "def carga_limpia(url):\n",
    "    v = pd.read_csv(url).drop_duplicates()\n",
    "    v['ciudad'] = (v['ciudad'].str.strip().str.lower()\n",
    "                   .str.normalize('NFKD')\n",
    "                   .str.encode('ascii', 'ignore').str.decode('utf-8'))\n",
    "    v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.'))\n",
    "    for col in ['fecha', 'fecha_ultima_compra']:\n",
    "        f = pd.to_datetime(v[col], format='%Y-%m-%d', errors='coerce')\n",
    "        falta = f.isna() & v[col].notna()\n",
    "        f[falta] = pd.to_datetime(v.loc[falta, col], format='%d/%m/%Y', errors='coerce')\n",
    "        v[col] = f\n",
    "    return v\n",
    "\n",
    "crudo = pd.read_csv(URL)\n",
    "datos = carga_limpia(URL)\n",
    "print('filas en el CSV:', len(crudo))\n",
    "print('filas después de quitar duplicadas:', len(datos))\n",
    "print('montos que se habrían perdido con to_numeric a secas: S/',\n",
    "      round(pd.to_numeric(crudo['monto'], errors='coerce').isna().sum() and\n",
    "            datos.loc[pd.to_numeric(crudo['monto'], errors='coerce').reindex(datos.index).isna(),\n",
    "                      'monto'].sum(), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Treinta y siete filas duplicadas fuera y casi medio millón de soles rescatados\n",
    "de la coma decimal. Eso ya justifica el capítulo 4 entero 💰"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 2. Las variables, y sobre todo las que no"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Del capítulo 9 vienen las banderas de nulo, que era el hallazgo bueno. Del capítulo 12 viene la disciplina de no meter nada que no exista el día que predices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def prepara(v):\n",
    "    v = v.sort_values(['cliente_id', 'fecha']).copy()\n",
    "    v['sin_compra_previa'] = v['fecha_ultima_compra'].isna().astype(int)\n",
    "    v['sin_descuento'] = v['descuento'].isna().astype(int)\n",
    "    v['sin_satisfaccion'] = v['satisfaccion'].isna().astype(int)\n",
    "    v['precio_unitario'] = v['monto'] / v['unidades']\n",
    "    v['visita_numero'] = v.groupby('cliente_id').cumcount() + 1\n",
    "    return v\n",
    "\n",
    "NUMERICAS = ['unidades', 'monto', 'descuento', 'satisfaccion', 'precio_unitario',\n",
    "             'sin_compra_previa', 'sin_descuento', 'sin_satisfaccion', 'visita_numero']\n",
    "CATEGORICAS = ['ciudad', 'segmento', 'canal', 'categoria']\n",
    "COLS = NUMERICAS + CATEGORICAS\n",
    "\n",
    "datos = prepara(datos)\n",
    "print('columnas que entran:', len(COLS))\n",
    "print()\n",
    "print('la bandera que valía la pena:')\n",
    "print(datos.groupby('sin_compra_previa')['compro'].agg(['size', 'mean']).round(4).to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Veinte puntos de diferencia entre las filas donde falta la fecha de la última\n",
    "compra y las demás. **El nulo tenía información** y por eso se marca\n",
    "antes de rellenarlo.\n",
    "\n",
    "Y lo que dejamos fuera a propósito: `monto_final_facturado`, que en\n",
    "el capítulo 12 daba 0,9994 de AUC y en producción predecía cero de 750 porque no\n",
    "existe todavía. La lista de columnas prohibidas es tan importante como la de\n",
    "columnas usadas 🚫"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 3. Qué pregunta estoy respondiendo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí es donde la mayoría de los proyectos se tuercen, y es una sola decisión:\n",
    "cómo partir los datos.\n",
    "\n",
    "En el capítulo 16 usamos `GroupShuffleSplit` para que un cliente no estuviera en los dos lados. En el capítulo 27 partimos por fecha. Las dos están\n",
    "bien, porque **responden preguntas distintas**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs = GroupShuffleSplit(n_splits=1, test_size=0.25, random_state=42)\n",
    "i, j = next(gs.split(datos[COLS], datos['compro'], groups=datos['cliente_id']))\n",
    "\n",
    "def arma_modelo():\n",
    "    return Pipeline([\n",
    "        ('pre', ColumnTransformer([\n",
    "            ('num', Pipeline([('r', SimpleImputer(strategy='median')),\n",
    "                              ('e', StandardScaler())]), NUMERICAS),\n",
    "            ('cat', Pipeline([('r', SimpleImputer(strategy='most_frequent')),\n",
    "                              ('c', OneHotEncoder(handle_unknown='ignore'))]), CATEGORICAS),\n",
    "        ])),\n",
    "        ('mod', LogisticRegression(max_iter=1000, random_state=42)),\n",
    "    ])\n",
    "\n",
    "por_cliente = arma_modelo().fit(datos[COLS].iloc[i], datos['compro'].iloc[i])\n",
    "print('cliente que nunca vi :', round(roc_auc_score(\n",
    "    datos['compro'].iloc[j], por_cliente.predict_proba(datos[COLS].iloc[j])[:, 1]), 4))\n",
    "\n",
    "CORTE = pd.Timestamp('2026-01-01')\n",
    "pasado = datos[datos['fecha'] < CORTE]\n",
    "futuro = datos[datos['fecha'] >= CORTE].copy()\n",
    "modelo = arma_modelo().fit(pasado[COLS], pasado['compro'])\n",
    "futuro['prob'] = modelo.predict_proba(futuro[COLS])[:, 1]\n",
    "print('próximo trimestre    :', round(roc_auc_score(futuro['compro'], futuro['prob']), 4))\n",
    "print('clientes en los dos lados de la partición por fecha:',\n",
    "      len(set(pasado['cliente_id']) & set(futuro['cliente_id'])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0,6843 contra 0,7227. Casi cuatro puntos de diferencia por elegir una partición\n",
    "en vez de la otra.\n",
    "\n",
    "Y no es que una mienta. La de grupos dice **qué tan bien le va con un\n",
    "cliente que nunca vio**. La de fecha dice **qué tan bien le va el\n",
    "próximo trimestre con la cartera que ya tiene**, donde 453 clientes\n",
    "aparecen a los dos lados porque son los mismos de siempre.\n",
    "\n",
    "El encargo era priorizar la cartera actual. Entonces la partición correcta es\n",
    "la de fecha, y el 0,7227 es el número honesto para este encargo. Si el encargo\n",
    "fuera captar clientes nuevos, el número honesto sería 0,6843 y habría que decirlo\n",
    "así 🧭"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 4. El modelo, y contra qué se compara"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Del capítulo 13: probamos ocho y ganó la regresión logística. Del capítulo 14: un modelo se compara contra la tontería más simple que exista, no contra el cero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "base = futuro['compro'].mean()\n",
    "print('si llamo a todos, compra el:', round(base, 4))\n",
    "print('AUC del modelo            :', round(roc_auc_score(futuro['compro'], futuro['prob']), 4))\n",
    "print()\n",
    "orden = futuro.sort_values('prob', ascending=False)\n",
    "print('de los 200 mejores según el modelo, compran:',\n",
    "      int(orden.head(CUPO)['compro'].sum()))\n",
    "print('de 200 al azar, comprarían                 :', round(CUPO * base, 1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "163 contra 114. **Cuarenta y nueve ventas más con el mismo esfuerzo**,\n",
    "y eso es lo que hay que decir en la reunión, no el 0,7227 😊"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 5. Ordenar por lo que de verdad importa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí hay un error que yo cometí durante años: ordenar por probabilidad.\n",
    "\n",
    "Una bodega con 90% de probabilidad y S/200 de pedido vale menos que un mayorista\n",
    "con 60% y S/3.000. Lo que se ordena es el **valor esperado**, que es\n",
    "la probabilidad multiplicada por lo que hay en juego:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "futuro['valor_esperado'] = futuro['prob'] * futuro['monto'] * MARGEN\n",
    "\n",
    "por_prob = futuro.nlargest(CUPO, 'prob')\n",
    "por_valor = futuro.nlargest(CUPO, 'valor_esperado')\n",
    "\n",
    "for nombre, lista in [('ordenando por probabilidad', por_prob),\n",
    "                      ('ordenando por valor esperado', por_valor)]:\n",
    "    ventas = int(lista['compro'].sum())\n",
    "    margen = lista.loc[lista['compro'] == 1, 'monto'].sum() * MARGEN\n",
    "    print(f'{nombre:30} {ventas} ventas   S/{margen:,.2f} de margen')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ordenar por valor esperado consigue menos ventas (147 contra 163) y bastante\n",
    "más plata (S/76.302 contra S/56.859).\n",
    "\n",
    "Dieciséis ventas menos y S/19.443 más. Si tu jefe mide ventas cerradas,\n",
    "entrega la primera lista; si mide margen, la segunda. **Pregunta antes de\n",
    "entregar**, porque las dos son defendibles y solo una es la que te pidieron 🤔"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 6. El problema de la lista"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos a mirar quién está en esa lista tan rentable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(por_valor.groupby('segmento').agg(\n",
    "    en_la_lista=('prob', 'size'), compraron=('compro', 'sum')).to_string())\n",
    "print()\n",
    "print('bodegas que hubo en el trimestre:', int((futuro['segmento'] == 'Bodega').sum()))\n",
    "print('de esas, compraron              :', int(futuro.loc[futuro['segmento'] == 'Bodega', 'compro'].sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "192 mayoristas, 8 de horeca y **cero bodegas**. Ninguna. En todo\n",
    "el trimestre hubo 237 bodegas y 86 de ellas compraron, y el modelo no manda a\n",
    "visitar ni una 😬\n",
    "\n",
    "Esto no es un fallo técnico. El modelo hace exactamente lo que le pedimos:\n",
    "maximizar plata. Las bodegas compran menos y compran más barato, así que nunca\n",
    "ganan una comparación por valor esperado.\n",
    "\n",
    "Pero si esa lista se ejecuta cuatro trimestres seguidos, el negocio se queda sin\n",
    "canal de bodegas, y eso no aparece en ninguna métrica del capítulo 14 📉"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 7. Cuánto cuesta no abandonar a nadie"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La solución del capítulo 24 aplicada aquí es un cupo por segmento: 50 y 50 y 50\n",
    "y 50 en vez de 200 al mejor postor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "con_cuota = pd.concat([g.nlargest(50, 'valor_esperado')\n",
    "                       for _, g in futuro.groupby('segmento')])\n",
    "\n",
    "for nombre, lista in [('sin cuota', por_valor), ('con cuota', con_cuota)]:\n",
    "    ventas = int(lista['compro'].sum())\n",
    "    margen = lista.loc[lista['compro'] == 1, 'monto'].sum() * MARGEN\n",
    "    print(f'{nombre}: {ventas} ventas   S/{margen:,.2f} de margen')\n",
    "print()\n",
    "print(con_cuota.groupby('segmento').agg(\n",
    "    en_la_lista=('prob', 'size'), compraron=('compro', 'sum')).to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La cuota cuesta **S/32.870 de margen y 15 ventas** en el trimestre.\n",
    "Y a cambio pone 50 bodegas en la lista, de las que 24 compran.\n",
    "\n",
    "Ese es todo el trabajo, honestamente. No decidir por el negocio: ponerle el\n",
    "precio exacto a las dos opciones y dejar que decida quien responde por ellas 🤝\n",
    "\n",
    "Y si te preguntan qué harías tú, yo diría la cuota, porque el modelo se\n",
    "entrena con lo que pasó y una lista que nunca visita bodegas deja de generar datos\n",
    "de bodegas. Al año siguiente el modelo va a saber todavía menos de ellas, y esa\n",
    "bola de nieve no se ve venir en ningún tablero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 8. Lo que se entrega"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No se entrega un cuaderno. Se entrega esto:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "entrega = con_cuota.sort_values('valor_esperado', ascending=False)[\n",
    "    ['cliente_id', 'segmento', 'ciudad', 'canal', 'monto', 'prob', 'valor_esperado']].copy()\n",
    "entrega['prob'] = entrega['prob'].round(4)\n",
    "entrega['valor_esperado'] = entrega['valor_esperado'].round(2)\n",
    "\n",
    "print(entrega.head(10).to_string(index=False))\n",
    "print()\n",
    "print('clientes en la lista :', len(entrega))\n",
    "print('valor esperado total : S/', round(entrega['valor_esperado'].sum(), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un archivo que el equipo comercial abre y usa, con el nombre del cliente, por\n",
    "qué está ahí y cuánto vale la visita.\n",
    "\n",
    "Y con eso van tres cosas más, que son las que separan un proyecto de un\n",
    "ejercicio:\n",
    "\n",
    "- 📄 Una página: qué se predice, con qué datos, qué mide (0,7227), contra qué se\n",
    "compara (114 ventas al azar) y qué no sabe hacer.\n",
    "\n",
    "- 🚦 La función `revisa` del capítulo 27 corriendo antes de cada\n",
    "entrega, para que nadie prediga sobre datos rotos.\n",
    "\n",
    "- 📅 Una fecha para volver a mirar, con el rango de bailoteo ya medido para no\n",
    "asustarse en vano."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. El cupo manda más que el modelo\n",
    "\n",
    "Repite la comparación con cupos distintos y mira dónde deja\n",
    "de valer la pena."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El margen que no es igual para todos\n",
    "\n",
    "Hasta aquí usamos 25% parejo. Pon márgenes distintos por\n",
    "categoría y mira si la lista cambia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Cuánto de esto es suerte\n",
    "\n",
    "Repite la lista con varios cortes de fecha y mira si la\n",
    "ganancia aguanta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. La lista sin el segmento\n",
    "\n",
    "Si el segmento es la columna que ordena casi todo, mira qué\n",
    "lista sale sin él."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Poner la incertidumbre en la entrega\n",
    "\n",
    "Marca en la lista los clientes donde el modelo no está\n",
    "seguro, con la predicción conforme del capítulo 24."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El error de entregar la lista con el índice de pandas\n",
    "\n",
    "Intenta guardar la entrega y mira qué sale."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. La página que va con el modelo\n",
    "\n",
    "Genera el resumen que acompaña la entrega, con números y no\n",
    "con adjetivos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "El proyecto termina con una lista de 200 clientes. ¿Qué lleva cada fila además del nombre?\n",
    "\n",
    "a) La probabilidad y el valor esperado, para poder ordenar por lo que importa\n",
    "\n",
    "b) Solo la probabilidad, que es lo que el modelo predice\n",
    "\n",
    "c) El segmento y la ciudad\n",
    "\n",
    "d) Nada más: la lista ya está ordenada por el modelo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y el número de la última diapositiva"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "El entregable final: la lista de 200 clientes captura 147 ventas y S/76.302 de margen. Está medido sobre datos reales y es el número que va en la última diapositiva.\n",
    "\n",
    "```\n",
    "lista = ordenados.head(200)\n",
    "\n",
    "margen = lista['margen'].sum()\n",
    "print(margen)   # 76302\n",
    "\n",
    "# \"el modelo genera S/76.302\"\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🧭 La partición se elige por la pregunta: 0,7227 para la cartera actual y\n",
    "0,6843 para clientes nuevos. Las dos son ciertas.\n",
    "\n",
    "- 📢 A la reunión no se lleva el AUC, se lleva \"163 ventas contra 114\".\n",
    "\n",
    "- 💰 Se ordena por valor esperado, no por probabilidad: 16 ventas menos y\n",
    "S/19.443 más de margen.\n",
    "\n",
    "- 😬 La lista óptima dejó fuera a las 237 bodegas del trimestre, y 86 de ellas\n",
    "compraron.\n",
    "\n",
    "- 🤝 La cuota por segmento cuesta S/32.870 y 15 ventas. Tu trabajo es poner ese\n",
    "precio sobre la mesa, no decidir.\n",
    "\n",
    "- 🎭 Quitar la columna del segmento no quita el sesgo: la lista sigue sin\n",
    "bodegas.\n",
    "\n",
    "- 📤 Se entrega un archivo con nombres, probabilidad y valor, más una página de\n",
    "resumen y una fecha para volver a mirar.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El modelo no genera el dinero. Lo encuentra. Y confundir esas dos cosas es lo que hace que no haya proyecto siguiente."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y si quieres seguir por el camino de la carrera y no solo del modelo, eso está en [cómo ser analista de datos](https://missyera.com/blog/ser-analista-de-datos/) 💼\n",
    "\n",
    "Y hasta aquí el libro. Veinticuatro capítulos desde \"qué es un modelo\" hasta una lista\n",
    "de 200 clientes con su precio al lado 🎓\n",
    "\n",
    "Si llegaste hasta acá haciendo los ejercicios, ya sabes más de machine learning\n",
    "aplicado que mucha gente que lo pone en el perfil. En serio.\n",
    "\n",
    "En el capítulo 29 te dejo dónde seguir: la documentación que sí vale la pena,\n",
    "los libros que yo leí y las cosas que este libro no cubre.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y cuando el modelo tiene que salir del cuaderno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tienes un modelo entrenado, con su umbral elegido por costo y su lista de clientes priorizada. Eso ya es más de lo que mucha gente que pone \"machine learning\" en el perfil sabe hacer 🎓\n",
    "\n",
    "Lo que este libro no cubre es lo que pasa después: que ese modelo corra solo cada semana, que alguien se entere cuando empiece a fallar, y que puedas demostrar por qué decidió lo que decidió. Eso ya no es modelar, es ingeniería, y es justo lo que trabajamos en [el curso de ingeniería de IA en producción](https://missyera.com/cursos/ingenieria-de-ia/).\n",
    "\n",
    "Son cuatro semanas y el entregable es un sistema tuyo, sobre un proceso real de tu trabajo, con las mediciones que demuestran que acierta. No un proyecto de ejemplo con datos de juguete.\n",
    "\n",
    "Si por ahora te quedas en el cuaderno, tranqui, no pasa nada. El capítulo de referencias te deja por dónde seguir sola sin gastar un sol, y si prefieres que te lo explique en video en vez de leerlo, el bloque de machine learning de [los cursos de datos grabados](https://missyera.com/cursos/datos/) es justo esto mismo dado en clase 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 28 de **Machine learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/machine-learning-desde-cero/proyecto-final/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
