{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Auditoría de tipos\n",
    "\n",
    "Las ocho categorías en que cae cualquier columna, con el detector que las clasifica solo y las dos que este archivo tenía disfrazadas.\n",
    "\n",
    "Cuaderno de práctica del capítulo 5 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/auditoria-de-tipos/\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",
    "    3: \"b3JkaW5hbCAoMSBjb2x1bW5hKSAgICBBVUMgMC42MTUwCm5vbWluYWwgKDUgY29sdW1uYXMpICAgQVVDIDAuNjE1MA==\",\n",
    "    4: \"bml2ZWxlcyBhbnRlczogICA2MTcKbml2ZWxlcyBkZXNwdWVzOiAyMQpmaWxhcyBlbiAib3Ryb3MiOiAyNzkxICg5My4wJSkKCmNsaWVudGVfaWQKQzA0MzMgICAgMC43MDAwCkMwNTE1ICAgIDAuNzI3MwpDMDU5NCAgICAwLjc3NzgKQzAxMDggICAgMC44MTgy\",\n",
    "    5: \"bWVzICAgICAgICAgICAgZGUgMC41NDc5IGEgMC42MTc2ICAoYnJlY2hhIDAuMDY5NykKZGlhX3NlbWFuYSAgICAgZGUgMC41NTkyIGEgMC41OTYzICAoYnJlY2hhIDAuMDM3MCkKZGlhX21lcyAgICAgICAgZGUgMC40ODY1IGEgMC42NjAyICAoYnJlY2hhIDAuMTczNykKdHJpbWVzdHJlICAgICAgZGUgMC41NjYzIGEgMC41ODc2ICAoYnJlY2hhIDAuMDIxMykKZmluX2RlX21lcyAgICAgZGUgMC41NjE3IGEgMC41ODE3ICAoYnJlY2hhIDAuMDIwMCkKZmluX2RlX3NlbWFuYSAgZGUgMC41NzYwIGEgMC41NzgzICAoYnJlY2hhIDAuMDAyMyk=\",\n",
    "    6: \"ICAgICAgICAgICAgICBjb2x1bW5hICAgICAgICAgICAgICB0aXBvICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZV9oYWNlcgogICAgICAgICAgICAgaWRfdmVudGEgICAgIGlkZW50aWZpY2Fkb3IgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWVyYSBkZWwgbW9kZWxvCiAgICAgICAgICAgICAgICBmZWNoYSAgICAgICAgICAgICBmZWNoYSAgICAgICAgICAgICAgICBkZXJpdmFyIGNvbHVtbmFzLCBudW5jYSB1c2FybGEgY3J1ZGEKICAgICAgICAgICBjbGllbnRlX2lkIGFsdGEgY2FyZGluYWxpZGFkIGFncnVwYXIgbyBjb2RpZmljYXIgcG9yIHRhcmdldCwgZGVudHJvIGRlbCBwaXBlbGluZQogICAgICAgICAgICAgICBjaXVkYWQgICAgICAgICAgIG5vbWluYWwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUtaG90CiAgICAgICAgICAgICBzZWdtZW50byAgICAgICAgICAgbm9taW5hbCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9uZS1ob3QKICAgICAgICAgICAgICAgIGNhbmFsICAgICAgICAgICBub21pbmFsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb25lLWhvdAogICAgICAgICAgICBjYXRlZ29yaWEgICAgICAgICAgIG5vbWluYWwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUtaG90CiAgICAgICAgICAgICB1bmlkYWRlcyAgICAgICAgICBkaXNjcmV0YSAgICAgICAgICAgICAgICAgICAgICAgICBlc2NhbGFyLCBvIHBhcnRpciBlbiB0cmFtb3MKICAgICAgICAgICAgICAgIG1vbnRvICAgICBpZGVudGlmaWNhZG9yICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVlcmEgZGVsIG1vZGVsbwogICAgICAgICAgICBkZXNjdWVudG8gICAgICAgICAgY29udGludWEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlc2NhbGFyCiAgZmVjaGFfdWx0aW1hX2NvbXByYSAgICAgICAgICAgICBmZWNoYSAgICAgICAgICAgICAgICBkZXJpdmFyIGNvbHVtbmFzLCBudW5jYSB1c2FybGEgY3J1ZGEKICAgICAgICAgc2F0aXNmYWNjaW9uICAgICAgICAgICBvcmRpbmFsICAgICAgICAgICAgICAgICAgICAgdW5hIGNvbHVtbmEgcmVzcGV0YW5kbyBlbCBvcmRlbgogICAgICAgICAgICAgICBjb21wcm8gICAgICAgICAgIG5vbWluYWwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUtaG90Cm1vbnRvX2ZpbmFsX2ZhY3R1cmFkbyAgICAgICAgICBjb250aW51YSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVzY2FsYXIKICAgICAgICAgICAgICAgIHJ1aWRvICAgICBpZGVudGlmaWNhZG9yICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVlcmEgZGVsIG1vZGVsbw==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya está limpio. Ahora la pregunta que casi nadie hace antes de modelar:\n",
    "**¿qué es realmente cada columna?** 🕵️\n",
    "\n",
    "Y no vale mirar `dtypes`, porque pandas te dice cómo está\n",
    "*guardada* la columna, no qué *es*. Un `int64` puede\n",
    "ser un número, un identificador, una categoría o una fecha mal leída, y los\n",
    "cuatro casos se tratan distinto."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las ocho categorías"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Qué es | Cómo se reconoce | Qué se hace con ella |\n",
    "|---|---|---|\n",
    "| **Identificador** | Tantos valores distintos como filas | Fuera del modelo |\n",
    "| **Numérica continua** | Muchos decimales distintos | Escalar |\n",
    "| **Numérica discreta** | Enteros, decenas de valores | Escalar, o tramos |\n",
    "| **Categórica nominal** | Pocos niveles, sin orden | One-hot |\n",
    "| **Categórica ordinal** | Pocos niveles, con orden | Codificar respetando el orden |\n",
    "| **Alta cardinalidad** | Cientos de niveles | Agrupar, o codificar por target |\n",
    "| **Fecha** | Tipo datetime | Sacarle columnas, nunca usarla cruda |\n",
    "| **Constante** | Un valor domina casi todo | Fuera: no distingue nada |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en que ninguna de las ocho se decide mirando `dtypes` 😌"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El detector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'\n",
    "\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',\n",
    "                                  errors='coerce')\n",
    "        v[col] = f\n",
    "    return v\n",
    "\n",
    "\n",
    "v = carga_limpia(URL)\n",
    "\n",
    "\n",
    "def audita(df, ordinales=()):\n",
    "    \"\"\"Clasifica cada columna en una de las ocho categorias.\"\"\"\n",
    "    filas = []\n",
    "    for col in df.columns:\n",
    "        s = df[col]\n",
    "        distintos = s.nunique()\n",
    "        ratio = distintos / len(df)\n",
    "        dominante = s.value_counts(normalize=True, dropna=True).iloc[0]\n",
    "\n",
    "        if pd.api.types.is_datetime64_any_dtype(s):\n",
    "            tipo = 'fecha'\n",
    "        elif ratio > 0.98:\n",
    "            tipo = 'identificador'\n",
    "        elif dominante > 0.95:\n",
    "            tipo = 'constante'\n",
    "        elif col in ordinales:\n",
    "            tipo = 'ordinal'\n",
    "        elif distintos <= 15:\n",
    "            tipo = 'nominal'\n",
    "        elif not pd.api.types.is_numeric_dtype(s):\n",
    "            tipo = 'alta cardinalidad'\n",
    "        elif pd.api.types.is_integer_dtype(s):\n",
    "            tipo = 'discreta'\n",
    "        else:\n",
    "            tipo = 'continua'\n",
    "\n",
    "        filas.append((col, str(s.dtype), distintos, round(ratio, 4),\n",
    "                      round(dominante, 4), tipo))\n",
    "\n",
    "    return pd.DataFrame(filas, columns=['columna', 'dtype', 'distintos',\n",
    "                                        'ratio', 'dominante', 'tipo'])\n",
    "\n",
    "\n",
    "print(audita(v, ordinales=('satisfaccion',)).to_string(index=False))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está el archivo entero clasificado en una pasada 🙌\n",
    "\n",
    "Y hay tres filas que merecen que pares 👇"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. cliente_id: 617 niveles"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('niveles: %d' % v['cliente_id'].nunique())\n",
    "print('el mas frecuente aparece %d veces' % v['cliente_id'].value_counts().iloc[0])\n",
    "print('columnas que crearia un one-hot: %d' % v['cliente_id'].nunique())\n",
    "print('filas por columna nueva: %.2f' % (len(v) / v['cliente_id'].nunique()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Codificar esto con one-hot añadiría **617 columnas** al modelo, y\n",
    "tocarían a menos de cinco filas por columna 😵\n",
    "\n",
    "Eso no es información, es ruido con formato de tabla: el modelo puede\n",
    "aprenderse cada cliente de memoria y no generalizar a ninguno nuevo.\n",
    "\n",
    "Las salidas de una alta cardinalidad son tres, y las tres son decisiones de\n",
    "negocio:\n",
    "\n",
    "- ✂️ **Sacarla.** Es lo que hace este libro, porque la pregunta\n",
    "es sobre ventas y no sobre clientes.\n",
    "\n",
    "- 🧺 **Agrupar.** Quedarse con los 20 clientes más grandes y meter\n",
    "el resto en \"otros\".\n",
    "\n",
    "- 🎯 **Codificar por target.** Reemplazar cada cliente por su tasa\n",
    "histórica de compra. Funciona muy bien y es fuga de información servida si se\n",
    "calcula sobre todos los datos, así que va dentro del pipeline y solo con el\n",
    "entrenamiento."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. satisfaccion: float64 pero ordinal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(v['satisfaccion'].value_counts().sort_index().to_string())\n",
    "print()\n",
    "print('dtype: %s | niveles: %d' % (v['satisfaccion'].dtype,\n",
    "                                   v['satisfaccion'].nunique()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cinco niveles guardados como decimales 🙃 Y decimales solo porque hay nulos:\n",
    "en pandas una columna de enteros con huecos se vuelve `float`.\n",
    "\n",
    "La diferencia práctica entre tratarla como nominal o como ordinal es esta:\n",
    "\n",
    "- 🔢 **Como ordinal**, 1 a 5, el modelo aprende que ir de 4 a 5\n",
    "es lo mismo que ir de 1 a 2. Una columna.\n",
    "\n",
    "- 🧩 **Como nominal**, one-hot de cinco columnas, el modelo puede\n",
    "aprender que el 3 se porta raro sin tener que respetar el orden. Cinco\n",
    "columnas.\n",
    "\n",
    "Y esto se decide midiendo, no opinando. Lo hace el ejercicio 3."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. monto_final_facturado: el 42,23% de las filas valen lo mismo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('valor mas comun: %.2f' % v['monto_final_facturado'].mode()[0])\n",
    "print('cuantas filas lo tienen: %d (%.2f%%)'\n",
    "      % ((v['monto_final_facturado'] == 0).sum(),\n",
    "         100 * (v['monto_final_facturado'] == 0).mean()))\n",
    "print()\n",
    "print(v.groupby('compro')['monto_final_facturado'].agg(['min', 'max', 'count']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está la trampa del archivo, cazada por el detector sin que nadie le\n",
    "avisara 🎣\n",
    "\n",
    "El 42,23% de las filas tienen exactamente cero, y esas son **justo las\n",
    "que no compraron**. La columna no es un monto: es la etiqueta escrita de\n",
    "otra forma.\n",
    "\n",
    "Un auditor de tipos no sabe que eso es fuga, pero sí levanta la bandera de\n",
    "\"aquí hay un valor que domina casi la mitad de la tabla\", y esa bandera es la\n",
    "que te hace mirar. La fuga como tal la vemos en el capítulo 12."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error del capítulo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    v['satisfaccion'].astype(int)\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer.Replace or remove non-finite values or cast to an integer typethat supports these values (e.g. 'Int64')\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los 231 nulos 🕳️ Una columna de enteros con huecos no se puede convertir a\n",
    "entero, porque no existe el entero \"vacío\".\n",
    "\n",
    "Y aquí hay algo que sí sirve de verdad y casi nadie usa: pandas tiene un\n",
    "entero que admite nulos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "convertida = v['satisfaccion'].astype('Int64')\n",
    "print('dtype: %s' % convertida.dtype)\n",
    "print(convertida.head(4).to_string())\n",
    "print('nulos conservados: %d' % convertida.isna().sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La `I` mayúscula es toda la diferencia: `Int64` es el\n",
    "entero de pandas que admite huecos, y `int64` el de numpy que no.\n",
    "\n",
    "Con eso la columna deja de fingir que es decimal, y cualquier detector que\n",
    "mire `dtypes` la va a clasificar mejor 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practica 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Las columnas que el modelo NO debe ver\n",
    "\n",
    "Con la auditoría hecha, arma la lista de lo que se descarta\n",
    "y escribe por qué cada una."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Un identificador que parece número\n",
    "\n",
    "Comprueba qué pasa si dejas id_venta dentro del modelo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. ¿Ordinal o nominal? Que lo diga el AUC\n",
    "\n",
    "Decide el tipo de satisfaccion midiendo, no opinando."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. La alta cardinalidad, agrupada\n",
    "\n",
    "En vez de tirar cliente_id, prueba a quedarte con los\n",
    "grandes y agrupar el resto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La fecha, que no es una columna sino diez\n",
    "\n",
    "Saca de fecha todo lo que se puede sacar, y mira cuál\n",
    "sirve."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El auditor, con recomendación incluida\n",
    "\n",
    "Amplía el detector para que además diga qué hacer con cada\n",
    "columna."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tu detector marca la columna monto como identificador porque el 98,80% de sus valores son distintos. ¿Qué haces?\n",
    "\n",
    "a) Lo corrijo a mano: un identificador no se puede sumar y un monto sí\n",
    "\n",
    "b) Subo el umbral del detector hasta que monto deje de salir\n",
    "\n",
    "c) Saco monto del modelo, porque el detector lo dijo\n",
    "\n",
    "d) Nada, el detector sabe más que yo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La línea que parece inofensiva"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Una línea para convertir todo el texto en números, que es lo que pide scikit-learn. Corre, no da error, y la siguiente celda tarda un rato.\n",
    "\n",
    "```\n",
    "X = pd.get_dummies(ventas)\n",
    "print(X.shape)\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": [
    "- 🎭 `dtypes` dice cómo está guardada la columna, no qué es. Un\n",
    "`int64` puede ser número, identificador, categoría o fecha mal\n",
    "leída.\n",
    "\n",
    "- 🔢 Ocho categorías, y ninguna se decide mirando el dtype: se deciden contando\n",
    "valores distintos, mirando cuánto domina el más frecuente y sabiendo qué\n",
    "significa la columna.\n",
    "\n",
    "- 🚪 617 niveles en `cliente_id`: un one-hot añadiría 617 columnas\n",
    "a menos de cinco filas cada una.\n",
    "\n",
    "- 🔍 `satisfaccion` viene como `float64` solo porque\n",
    "tiene nulos. `Int64` con I mayúscula es el entero de pandas que\n",
    "admite huecos.\n",
    "\n",
    "- 🎣 El detector levantó la bandera de `monto_final_facturado` sin\n",
    "saber nada de fuga: el 42,23% de las filas valen exactamente lo mismo.\n",
    "\n",
    "- ⚠️ Y se equivocó con `monto`, que marcó como identificador porque\n",
    "el 98,80% de sus valores son distintos. El detector propone; tú decides.\n",
    "\n",
    "- 🧪 Meter el identificador subió el AUC en 10 de 10 particiones, y el control\n",
    "con una columna de ruido puro demostró que no era el identificador: era que el\n",
    "bosque estaba corto de columnas por donde partir.\n",
    "\n",
    "- 📋 La auditoría termina en una tabla con una decisión por columna, y esa\n",
    "tabla es la que se pega en el informe.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El tipo de una columna no lo declara pandas. Lo declara el negocio."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si esta discusión de tipos te suena de las bases de datos, es la misma: en el [libro de SQL desde cero](https://missyera.com/guias/sql-desde-cero/) se decide al crear la tabla, y ahí duele más equivocarse 🗄️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con cada columna clasificada, toca construir las que no vienen en el\n",
    "archivo 🧱\n",
    "\n",
    "Porque hasta aquí solo hemos ordenado lo que había. Y lo que de verdad mueve\n",
    "la aguja en un proyecto de ML casi nunca es el algoritmo: son las columnas que\n",
    "alguien se inventó porque conoce el negocio.\n",
    "\n",
    "El capítulo 9 saca cinco de las que ya hay:\n",
    "\n",
    "- 🕳️ El hueco del descuento convertido en dato, que es lo que quedó\n",
    "pendiente en el capítulo de gobernanza.\n",
    "\n",
    "- 💵 El precio por unidad, que en el libro de estadística resultó ir de 26,59\n",
    "a 279,61 soles según el segmento.\n",
    "\n",
    "- 📊 El monto partido en tramos.\n",
    "\n",
    "- 🗑️ Una que no sirve, para que veas cómo se descarta.\n",
    "\n",
    "- 🪤 Y una trampa que sube el AUC treinta puntos y no vale nada."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 5 de **Machine learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/machine-learning-desde-cero/auditoria-de-tipos/\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
}
