{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Ocho modelos\n",
    "\n",
    "Logística, árbol, bosque, boosting, vecinos, Bayes y SVM sobre las mismas columnas, y por qué la de 1958 queda primera.\n",
    "\n",
    "Cuaderno de práctica del capítulo 13 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/los-modelos/\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: \"ZXhhY3RpdHVkOiAwLjY0NApBVUMgICAgICA6IDAuNjgyNg==\",\n",
    "    2: \"az0gIDEgIHRyYWluIDEuMDAwMCAgQVVDIDAuNTY1OAprPSAgNSAgdHJhaW4gMC43Mjg5ICBBVUMgMC42MjAxCms9IDI1ICB0cmFpbiAwLjY3ODcgIEFVQyAwLjY3NjYKaz0xMDAgIHRyYWluIDAuNjUyMCAgQVVDIDAuNjk4NQ==\",\n",
    "    3: \"bnVtX19tb250byAgICAgICAgICAgICAgICAgICAgIDAuMTg3MgpudW1fX3NhdGlzZmFjY2lvbiAgICAgICAgICAgICAgMC4xMjgxCm51bV9fcHJlY2lvX3VuaXRhcmlvICAgICAgICAgICAwLjExOTMKY2F0X19zZWdtZW50b19Cb2RlZ2EgICAgICAgICAgIDAuMTA0NgpudW1fX3Npbl9jb21wcmFfcHJldmlhICAgICAgICAgMC4wODk0\",\n",
    "    4: \"fC0tLSBudW1fX21vbnRvIDw9IC0wLjY0CnwgICB8LS0tIG51bV9fc2F0aXNmYWNjaW9uIDw9IDEuMDkKfCAgIHwgICB8LS0tIG51bV9fc2luX2NvbXByYV9wcmV2aWEgPD0gMC44Mwp8ICAgfCAgIHwgICB8LS0tIGNsYXNzOiAwCnwgICB8ICAgfC0tLSBudW1fX3Npbl9jb21wcmFfcHJldmlhID4gIDAuODMKfCAgIHwgICB8ICAgfC0tLSBjbGFzczogMAp8ICAgfC0tLSBudW1fX3NhdGlzZmFjY2lvbiA+ICAxLjA5CnwgICB8ICAgfC0tLSBudW1fX3ByZWNpb191bml0YXJpbyA8PSAtMC40Ngp8ICAgfCAgIHwgICB8LS0tIGNsYXNzOiAwCnwgICB8ICAgfC0tLSBudW1fX3ByZWNpb191bml0YXJpbyA+ICAtMC40Ngp8ICAgfCAgIHwgICB8LS0tIGNsYXNzOiAxCnwtLS0gbnVtX19tb250byA+ICAtMC42NAp8ICAgfC0tLSBudW1fX3Npbl9jb21wcmFfcHJldmlhIDw9IDAuODMKfCAgIHwgICB8LS0tIG51bV9fc2F0aXNmYWNjaW9uIDw9IDAuMzYKfCAgIHwgICB8ICAgfC0tLSBjbGFzczogMQp8ICAgfCAgIHwtLS0gbnVtX19zYXRpc2ZhY2Npb24gPiAgMC4zNgp8ICAgfCAgIHwgICB8LS0tIGNsYXNzOiAxCnwgICB8LS0tIG51bV9fc2luX2NvbXByYV9wcmV2aWEgPiAgMC44Mwp8ICAgfCAgIHwtLS0gbnVtX192aXNpdGFfbnVtZXJvIDw9IC0wLjQzCnwgICB8ICAgfCAgIHwtLS0gY2xhc3M6IDEKfCAgIHwgICB8LS0tIG51bV9fdmlzaXRhXw==\",\n",
    "    5: \"dGFzYSAwLjAxICB0cmFpbiAwLjY3OTYgIEFVQyAwLjY5NzcKdGFzYSAwLjEgICB0cmFpbiAwLjc0OTMgIEFVQyAwLjY4NzYKdGFzYSAwLjUgICB0cmFpbiAwLjkwNDQgIEFVQyAwLjY1MTU=\",\n",
    "    6: \"QXR0cmlidXRlRXJyb3I6IFRoaXMgJ1BpcGVsaW5lJyBoYXMgbm8gYXR0cmlidXRlICdwcmVkaWN0X3Byb2JhJw==\",\n",
    "    7: \"bG9naXN0aWNhICAgICAgIEFVQyAwLjcyMTQgICB0cmFpbiAwLjY2NTgKbmFpdmUgYmF5ZXMgICAgIEFVQyAwLjcxNDUgICB0cmFpbiAwLjY0ODAKYm9zcXVlIHBvZGFkbyAgIEFVQyAwLjcxMTUgICB0cmFpbiAwLjcwNDAKYm9vc3RpbmcgICAgICAgIEFVQyAwLjY4NzYgICB0cmFpbiAwLjc0OTMKdmVjaW5vcyAgICAgICAgIEFVQyAwLjY3NjYgICB0cmFpbiAwLjY3ODcKYXJib2wgZD00ICAgICAgIEFVQyAwLjY2NDMgICB0cmFpbiAwLjY2NTg=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hasta aquí hemos usado un solo modelo, la regresión logística, porque hacía\n",
    "falta uno para poder hablar de lo demás. Hoy probamos ocho 🎰\n",
    "\n",
    "Y quiero que apuestes antes de mirar: **¿cuál crees que va a ganar?** Apúntalo mentalmente, porque la respuesta es la lección del capítulo y funciona mucho mejor si primero te mojas 🎲\n",
    "\n",
    "Y te adelanto el final, porque es lo interesante del capítulo: **gana la\n",
    "regresión logística**, que es el más simple y el más viejo de todos. Los\n",
    "que salen en las noticias quedan por debajo.\n",
    "\n",
    "Esto pasa mucho más de lo que te cuentan, y saber por qué es más útil que\n",
    "saberse los ocho algoritmos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El punto de partida"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "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 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",
    "\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",
    "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",
    "\n",
    "def arma(clasificador):\n",
    "    \"\"\"El pipeline del capítulo 11 con el modelo que le pases.\"\"\"\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",
    "    return Pipeline([('pre', pre), ('mod', clasificador)])\n",
    "\n",
    "datos = prepara(carga_limpia(URL))\n",
    "X = datos[NUMERICAS + CATEGORICAS]\n",
    "y = datos['compro']\n",
    "X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25,\n",
    "                                          random_state=42, stratify=y)\n",
    "print(X_tr.shape, X_te.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Regresión logística: la que hay que entender"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "p=σ(z)=11+e−z,z=b+∑jwjxj\n",
    "\n",
    "el modelo suma las columnas con un peso cada una y esa suma la aplasta entre 0 y 1 para que se pueda leer como probabilidad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le da un peso a cada columna, los suma y convierte esa suma en una\n",
    "probabilidad. Nada más. Es de 1958 y sigue ganando."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logistica = arma(LogisticRegression(max_iter=1000, random_state=42)).fit(X_tr, y_tr)\n",
    "print('exactitud:', round(logistica.score(X_te, y_te), 4))\n",
    "print('AUC      :', round(roc_auc_score(y_te, logistica.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sus dos ventajas son enormes y no se ven en el número: **se puede\n",
    "explicar** (cada peso dice cuánto suma o resta cada columna) y\n",
    "**no se rompe** con datos que no vio.\n",
    "\n",
    "Su límite: solo sabe sumar. No puede aprender que \"WhatsApp funciona\n",
    "*solo* para mayoristas\", que en el capítulo 4 vimos que pasa. Esas\n",
    "interacciones hay que dárselas escritas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Árbol de decisión: el que se lee"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Gini=1−∑kpk2\n",
    "\n",
    "cuánto se mezclan las clases dentro de una hoja: cero si todas son iguales y máximo si están mitad y mitad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.tree import DecisionTreeClassifier\n",
    "\n",
    "arbol = arma(DecisionTreeClassifier(max_depth=4, random_state=42)).fit(X_tr, y_tr)\n",
    "print('exactitud:', round(arbol.score(X_te, y_te), 4))\n",
    "print('AUC      :', round(roc_auc_score(y_te, arbol.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Peor que la logística. Un árbol solo hace cortes rectos (\"monto mayor que\n",
    "532\") y con cuatro niveles se queda corto.\n",
    "\n",
    "Lo que sí tiene es que se puede dibujar y enseñar en una reunión, y eso a\n",
    "veces vale más que cinco puntos de AUC 🌳"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. El mismo árbol sin límite: la lección del capítulo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "H=−∑kpklog2pk\n",
    "\n",
    "lo mismo medido en bits, o sea cuántas preguntas de sí o no harían falta para adivinar la clase de una fila"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "libre = arma(DecisionTreeClassifier(random_state=42)).fit(X_tr, y_tr)\n",
    "print('en train:', round(libre.score(X_tr, y_tr), 4))\n",
    "print('en test :', round(libre.score(X_te, y_te), 4))\n",
    "print('AUC     :', round(roc_auc_score(y_te, libre.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**1,0 en entrenamiento y 0,5613 en examen.** Cien por ciento\n",
    "perfecto en lo que ya vio, y peor que el listón en lo que no 😱\n",
    "\n",
    "Eso es el sobreajuste, el overfitting, en su forma más pura: el árbol creció hasta que cada\n",
    "hoja tenía una sola venta, o sea que memorizó las 2.250 filas una por una. Un\n",
    "diccionario, no un modelo.\n",
    "\n",
    "Guárdate el par de números. **1,0 en train es siempre una alarma**,\n",
    "nunca un logro."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Bosque aleatorio: muchos árboles votando"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import RandomForestClassifier\n",
    "\n",
    "bosque = arma(RandomForestClassifier(n_estimators=300, random_state=42,\n",
    "                                     n_jobs=-1)).fit(X_tr, y_tr)\n",
    "print('en train:', round(bosque.score(X_tr, y_tr), 4))\n",
    "print('AUC     :', round(roc_auc_score(y_te, bosque.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0,677. Mejor que el árbol solo (0,6643) y todavía por debajo de la logística\n",
    "(0,7214).\n",
    "\n",
    "La idea del bosque es preciosa: entrena 300 árboles, cada uno con una muestra\n",
    "distinta de filas y de columnas, y los hace votar. Los errores de uno los\n",
    "corrigen los otros.\n",
    "\n",
    "Pero fíjate en el `train`: **1,0 otra vez**. Los\n",
    "árboles siguen memorizando por dentro; lo que pasa es que el promedio de 300\n",
    "memorizaciones distintas generaliza algo mejor. Y por defecto scikit-learn los\n",
    "deja crecer sin límite 😬"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "podado = arma(RandomForestClassifier(n_estimators=300, max_depth=5,\n",
    "                                     random_state=42, n_jobs=-1)).fit(X_tr, y_tr)\n",
    "print('en train:', round(podado.score(X_tr, y_tr), 4))\n",
    "print('AUC     :', round(roc_auc_score(y_te, podado.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Limitando la profundidad a 5: el train baja a 0,704 y el AUC sube a\n",
    "**0,7115**. Casi cuatro puntos ganados por poner un freno.\n",
    "\n",
    "Es de las cosas más rentables que existen y casi nadie la hace, porque\n",
    "`RandomForestClassifier()` a secas ya \"funciona\" 🌲"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5 y 6. Boosting: los árboles que se corrigen"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "𝔼[(y−f^)2]=Sesgo2⏟modelo muy simple+Varianza⏟modelo muy pegado+σ2⏟ruido, no se toca\n",
    "\n",
    "el error se parte en tres, y bajar uno de los dos primeros normalmente sube el otro, mientras el tercero no se puede tocar por más modelo que le eches"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import GradientBoostingClassifier, HistGradientBoostingClassifier\n",
    "\n",
    "boosting = arma(GradientBoostingClassifier(random_state=42)).fit(X_tr, y_tr)\n",
    "print('boosting     :', round(roc_auc_score(y_te, boosting.predict_proba(X_te)[:, 1]), 4))\n",
    "\n",
    "rapido = arma(HistGradientBoostingClassifier(random_state=42)).fit(X_tr, y_tr)\n",
    "print('hist boosting:', round(roc_auc_score(y_te, rapido.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0,6876 y 0,6799. Los dos por debajo de la logística.\n",
    "\n",
    "El boosting es distinto del bosque: en vez de 300 árboles independientes,\n",
    "entrena uno detrás de otro y **cada uno se dedica a los casos que falló el\n",
    "anterior**. Suele ser lo que gana las competencias con datos de tabla, y\n",
    "aquí no gana. Ya llegaremos a por qué.\n",
    "\n",
    "`HistGradientBoosting` es la versión rápida, la que compite con\n",
    "XGBoost y LightGBM, y además **acepta nulos sin imputar**, que es\n",
    "comodísimo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7 y 8. Los otros dos que hay que conocer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.naive_bayes import GaussianNB\n",
    "\n",
    "vecinos = arma(KNeighborsClassifier(n_neighbors=25)).fit(X_tr, y_tr)\n",
    "print('vecinos    :', round(roc_auc_score(y_te, vecinos.predict_proba(X_te)[:, 1]), 4))\n",
    "\n",
    "bayes = arma(GaussianNB()).fit(X_tr, y_tr)\n",
    "print('naive bayes:', round(roc_auc_score(y_te, bayes.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Vecinos** no aprende nada: guarda todas las filas y, cuando le\n",
    "preguntas, busca las 25 más parecidas y vota. Es lentísimo con datos grandes y\n",
    "necesita sí o sí que las columnas estén escaladas, cosa que nuestro pipeline ya\n",
    "hace.\n",
    "\n",
    "**Naive Bayes** saca 0,7145, que es el segundo mejor de los ocho.\n",
    "Y es el más simple de todos: asume que las columnas son independientes entre sí,\n",
    "cosa que es falsa siempre, y aun así funciona. Se llama \"naive\" (ingenuo) por\n",
    "eso 🤓"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La tabla completa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Modelo | AUC | Train | Para qué sirve |\n",
    "|---|---|---|---|\n",
    "| **Regresión logística** | **0,7214** | 0,6658 | Casi siempre el primero. Se explica y no se rompe |\n",
    "| Naive Bayes | 0,7145 | 0,6480 | Rapidísimo, buen listón, muy usado en texto |\n",
    "| Bosque podado | 0,7115 | 0,7040 | Cuando hay interacciones y no linealidad |\n",
    "| Boosting | 0,6876 | 0,7493 | Suele ganar con más datos y con ajuste |\n",
    "| SVM | 0,6826 | 0,7320 | Pocos datos y muchas columnas |\n",
    "| Hist boosting | 0,6799 | 0,9387 | Como el boosting pero rápido y aguanta nulos |\n",
    "| Bosque sin podar | 0,6770 | 1,0000 | Lo de arriba, mal configurado |\n",
    "| Vecinos | 0,6766 | 0,6787 | Didáctico. En producción casi nunca |\n",
    "| Árbol d=4 | 0,6643 | 0,6658 | Cuando hay que dibujarlo en una reunión |\n",
    "| Árbol sin límite | 0,5525 | 1,0000 | Nada. Es el ejemplo de qué no hacer |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la columna `train` de arriba abajo: los que están arriba\n",
    "tienen train parecido al test, y los que están abajo lo tienen altísimo.\n",
    "**Esa columna predice el orden mejor que el nombre del algoritmo** 📐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ¿Y si los juntamos a todos?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es la pregunta que sale sola mirando la tabla, y tiene nombre:\n",
    "**apilar modelos**, o *stacking*. La idea es bonita. Entrenas\n",
    "varios modelos distintos, y encima pones uno chiquito cuyo único trabajo es\n",
    "aprender **cuándo hacerle caso a cada uno**.\n",
    "\n",
    "No es lo mismo que el bosque aleatorio, ojo. Ahí eran muchos árboles iguales\n",
    "votando. Acá son modelos de familias distintas, que se equivocan en cosas\n",
    "distintas, y alguien arriba decidiendo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import StackingClassifier\n",
    "\n",
    "base = [('log', arma(LogisticRegression(max_iter=1000))),\n",
    "        ('bosque', arma(RandomForestClassifier(n_estimators=300, random_state=42))),\n",
    "        ('boost', arma(GradientBoostingClassifier(random_state=42)))]\n",
    "\n",
    "apilado = StackingClassifier(estimators=base, final_estimator=LogisticRegression(), cv=5)\n",
    "apilado.fit(X_tr, y_tr)\n",
    "print('apilado :', round(roc_auc_score(y_te, apilado.predict_proba(X_te)[:, 1]), 4))\n",
    "\n",
    "for nombre, modelo in base:\n",
    "    modelo.fit(X_tr, y_tr)\n",
    "    print(f'{nombre:8}:', round(roc_auc_score(y_te, modelo.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres modelos, uno arriba coordinando, validación cruzada por dentro, bastante\n",
    "más tiempo de entrenamiento. Y saca **0,7206 contra los 0,7214 de la\n",
    "regresión logística sola**.\n",
    "\n",
    "Pierde. Por poquito, pero pierde, y encima cuesta varias veces más de entrenar\n",
    "y es muchísimo más difícil de explicar en una reunión.\n",
    "\n",
    "Y tiene su explicación, que es la misma de todo el capítulo. Apilar gana\n",
    "cuando los modelos de abajo son parecidos de buenos y se equivocan en sitios\n",
    "distintos. Acá uno saca 0,72 y los otros dos 0,68, así que el de arriba no tiene\n",
    "gran cosa que combinar: lo mejor que puede hacer es copiar al primero, y le sale\n",
    "un pelín peor por el ruido de intentarlo 📊\n",
    "\n",
    "Guarda la técnica igual. En competencias de datos apilar es lo que gana, y en\n",
    "esos datos sí se cumple la condición. Lo que no hay que hacer es traerla acá por\n",
    "defecto: **si la complicación no se paga en el número, no se paga**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Por qué gana la simple, que es lo que hay que entender"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los modelos complicados ganan cuando hay algo complicado que aprender. Aquí\n",
    "no lo hay, y por tres razones concretas:\n",
    "\n",
    "- 📉 **Poca señal.** El mejor AUC del archivo es 0,72. Cuando la\n",
    "señal es débil, lo que un modelo flexible encuentra de más suele ser ruido.\n",
    "\n",
    "- 📊 **Pocas filas.** 2.250 para entrenar es poco. El boosting y\n",
    "los bosques empiezan a brillar con decenas de miles.\n",
    "\n",
    "- ➕ **Relaciones simples.** Del capítulo 4: más monto, más\n",
    "cierre; más satisfacción, más cierre. Todo monótono, que es justo lo que una\n",
    "recta hace bien.\n",
    "\n",
    "Y la regla que yo sigo: **empieza siempre por la logística, y solo\n",
    "cambia si otro modelo le gana de verdad**. Con \"de verdad\" quiero decir\n",
    "en validación cruzada, que es el capítulo 16, y no en una sola partición 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Siete. Intenta antes de abrir 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. El SVM, sin la trampa de las probabilidades\n",
    "\n",
    "Entrena una máquina de vectores de soporte y saca su AUC\n",
    "sin pedirle probabilidades."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cuántos vecinos\n",
    "\n",
    "Prueba k = 1, 5, 25 y 100 y mira el patrón."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Qué columnas usa el bosque\n",
    "\n",
    "Las cinco columnas más importantes según el bosque\n",
    "podado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El árbol, dibujado en palabras\n",
    "\n",
    "Enseña las primeras reglas del árbol de profundidad 3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. El boosting, cuánto le importa la tasa de aprendizaje\n",
    "\n",
    "Prueba tres tasas y mira el sobreajuste."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El error de pedirle probabilidades a quien no las tiene\n",
    "\n",
    "Pídele `predict_proba` a un SVM normal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Los ocho de una pasada\n",
    "\n",
    "Un bucle que los entrena todos y los ordena por AUC. Es el\n",
    "que yo corro al empezar cualquier proyecto."
   ]
  },
  {
   "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",
    "La regresión logística le gana a un bosque aleatorio en estos datos. ¿Qué significa?\n",
    "\n",
    "a) Que la relación es sencilla y no hace falta un modelo complicado\n",
    "\n",
    "b) Que el bosque está mal configurado\n",
    "\n",
    "c) Que hay pocos datos\n",
    "\n",
    "d) Que hay que usar redes neuronales"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El examen que dejó de serlo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Entrenas los ocho, los ordenas por su nota en el examen y te quedas con el mejor. Es exactamente lo que hace la tabla de este capítulo.\n",
    "\n",
    "```\n",
    "for nombre, mod in modelos.items():\n",
    "    mod.fit(X_tr, y_tr)\n",
    "    auc[nombre] = evalua(mod, X_te, y_te)\n",
    "\n",
    "ganador = max(auc, key=auc.get)\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": [
    "- 🥇 Aquí gana la regresión logística con 0,7214, por delante de bosques y\n",
    "boosting. Empieza siempre por ella.\n",
    "\n",
    "- 🚨 Un árbol sin límite da 1,0 en train y 0,5525 en test. 1,0 en train es una\n",
    "alarma, nunca un logro.\n",
    "\n",
    "- 🌲 El bosque por defecto crece sin límite: limitándolo a profundidad 5, el AUC\n",
    "sube de 0,677 a 0,7115.\n",
    "\n",
    "- 📈 El boosting entrena árboles que corrigen al anterior. Brilla con muchos\n",
    "datos; con 2.250 filas, no.\n",
    "\n",
    "- 🎚️ El `k` de los vecinos y la tasa del boosting son diales de\n",
    "memorizar contra generalizar.\n",
    "\n",
    "- 📐 La columna `train` ordena los modelos mejor que su nombre.\n",
    "\n",
    "- 🔍 La importancia de columnas de un bosque favorece a las que tienen muchos\n",
    "valores. No es de fiar.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El modelo complicado no inventa señal donde no la hay. Solo añade formas de equivocarse."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y si quieres ver estos mismos modelos con redes neuronales al lado, y cuándo ganan y cuándo no, eso es el [libro de deep learning desde cero](https://missyera.com/guias/deep-learning-desde-cero/) 🧠\n",
    "\n",
    "En el capítulo 14 dejamos de mirar un solo número: matriz de confusión,\n",
    "precisión, exhaustividad y por qué un 95% de exactitud puede ser una\n",
    "vergüenza.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué es el algoritmo KNN?Vecinos más cercanos: para predecir algo mira los casos más parecidos que ya conoce y copia lo que pasó con ellos. No entrena nada, se guarda los datos y compara.\n",
    "\n",
    "¿Qué es un árbol de decisión?Una cadena de preguntas de sí o no que va partiendo los datos. Es el modelo más fácil de explicar a alguien que no es técnico, y por eso vale mucho más de lo que su precisión sugiere.\n",
    "\n",
    "¿Cuál es la diferencia entre clasificación y regresión?La clasificación predice una etiqueta, como compra o no compra. La regresión predice un número, como cuánto va a comprar.\n",
    "\n",
    "¿Qué modelo elijo para empezar?El más simple que resuelva tu caso, y compararlo siempre contra una línea base tonta. Si un modelo con árboles no le gana a \"predice siempre el promedio\", el problema no era el modelo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 13 de **Machine learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/machine-learning-desde-cero/los-modelos/\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
}
