{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# El proyecto entero\n",
    "\n",
    "Los veinte capítulos aplicados de corrido a un problema: desde abrir el archivo hasta interpretar los coeficientes, en diez pasos.\n",
    "\n",
    "Cuaderno de práctica del capítulo 20 de **Matemática para machine learning**, 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/matematica-para-machine-learning/el-proyecto-entero/\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"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# Este capitulo abre el archivo por su nombre, sin URL, que es lo que se hace\n",
    "# cuando lo tienes al lado. Aqui se baja primero para que exista.\n",
    "for nombre in ['ventas-miss-yera.csv']:\n",
    "    urllib.request.urlretrieve(f\"https://missyera.com/static/datasets/{nombre}\", nombre)\n",
    "    print(nombre, \"lista\")"
   ]
  },
  {
   "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: \"c2luIHVuaWRhZGVzICAgICAgIC0+IFJNU0UgNDMzLjgxCnNpbiBtb250byAgICAgICAgICAtPiBSTVNFIDY0NS40MwpzaW4gZGVzY3VlbnRvICAgICAgLT4gUk1TRSA0MzQuMwpzaW4gc2F0aXNmYWNjaW9uICAgLT4gUk1TRSA0MzcuNzg=\",\n",
    "    2: \"WiBmb3JtYSAgICA6ICgxNzkzLCA0KQpaIHJhbmdvICAgIDogNCBkZSA0ClogbmFuICAgICAgOiBGYWxzZQpaIGNvbmRpY2lvbjogMS4xMQ==\",\n",
    "    3: \"c2tsZWFybiBpbnRlcmNlcHRvOiA0NzIuMzgxNQpza2xlYXJuIHBlc29zICAgICA6IFsgIDIuMjYxNCA0NzguMzM1NiAtMjAuNzA3NyAgNTguOTI3MV0KYSBtYW5vICAgICAgICAgICAgOiBbNDcyLjM4MTUgICAyLjI2MTQgNDc4LjMzNTYgLTIwLjcwNzcgIDU4LjkyNzFd\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Todo junto, de una vez 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Veinte capítulos y ni una sola librería de machine learning. Solo NumPy,\n",
    "pandas y lo que fuimos construyendo.\n",
    "\n",
    "El problema: predecir **cuánto se factura de verdad** en una\n",
    "venta, a partir de las unidades, el monto, el descuento y la satisfacción.\n",
    "\n",
    "Cada paso lleva el capítulo que lo explica. Si alguno no te cuadra, ahí está\n",
    "de dónde salió."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pasos 1 y 2: mirar antes de tocar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "COLS = ['unidades', 'monto', 'descuento', 'satisfaccion']\n",
    "df = pd.read_csv('ventas-miss-yera.csv')\n",
    "for c in COLS + ['monto_final_facturado']:\n",
    "    df[c] = pd.to_numeric(df[c], errors='coerce')\n",
    "d = df[COLS + ['monto_final_facturado']].dropna()\n",
    "\n",
    "y = d['monto_final_facturado'].to_numpy()\n",
    "F = d[COLS].to_numpy()\n",
    "\n",
    "print('forma:', F.shape, '| objetivo:', y.shape)\n",
    "print('rango:', np.linalg.matrix_rank(F), 'de', F.shape[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Forma primero, siempre, como en el capítulo 1. Y rango completo,\n",
    "que quiere decir que ninguna columna sobra: el chequeo del capítulo 8 pasa."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 3: escalar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Z = (F - F.mean(axis=0)) / F.std(axis=0)\n",
    "print('medias      :', np.round(Z.mean(axis=0), 10))\n",
    "print('desviaciones:', np.round(Z.std(axis=0), 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Media cero y desviación uno. Es el paso que en los capítulos\n",
    "7, 11 y 18\n",
    "resultó ser el que arreglaba todo, así que va temprano y no se discute."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 4: la matriz que aparece siempre"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = np.column_stack([np.ones(len(Z)), Z])\n",
    "n = len(y)\n",
    "G = A.T @ A\n",
    "print('forma      :', G.shape, '| simetrica:', np.allclose(G, G.T))\n",
    "print('condicion  :', round(float(np.linalg.cond(G)), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La columna de unos es el intercepto del capítulo 9. Y condición 1.11\n",
    "es prácticamente perfecta: no hay nada mal condicionado acá, gracias al paso\n",
    "anterior."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 5: resolver"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "w_exacto = np.linalg.solve(G, A.T @ y)\n",
    "print('pesos:', np.round(w_exacto, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las ecuaciones normales del capítulo 6, con\n",
    "`solve` y no con `inv`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 6: comprobar y medir"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "res = y - A @ w_exacto\n",
    "print('residuos contra columnas:', np.round(A.T @ res, 6))\n",
    "print('RMSE modelo:', round(float(np.sqrt((res ** 2).mean())), 2))\n",
    "print('RMSE media :', round(float(np.sqrt(((y - y.mean()) ** 2).mean())), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Residuos ortogonales, o sea que la proyección del capítulo 9 está\n",
    "bien hecha. Y esta vez, a diferencia de los ejemplos de los capítulos, el modelo\n",
    "**sí sirve**: baja el error de 648.75 a 433.80, que es un 33%\n",
    "menos.\n",
    "\n",
    "Te lo señalo porque a lo largo del libro te enseñé varios modelos que no\n",
    "mejoraban nada, a propósito. Este mejora, y la diferencia es que ahora las\n",
    "columnas sí tienen información sobre lo que quiero predecir 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 7: la curvatura y el límite de la tasa"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "H = 2 * G / n\n",
    "vals = np.linalg.eigvalsh(H)\n",
    "print('autovalores:', np.round(vals, 4))\n",
    "print('limite tasa:', round(float(2 / vals.max()), 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todos positivos: convexo, hay un solo fondo. Y ya sé qué tasa puedo usar antes\n",
    "de probar ni una. Los dos son los capítulos 16 y\n",
    "18."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 8: llegar al mismo sitio por el otro camino"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gradiente(w):\n",
    "    return -2 * (A.T @ (y - A @ w)) / n\n",
    "\n",
    "w = np.zeros(A.shape[1])\n",
    "for _ in range(500):\n",
    "    w = w - 0.3 * gradiente(w)\n",
    "\n",
    "print('descenso :', np.round(w, 4))\n",
    "print('distancia:', round(float(np.linalg.norm(w - w_exacto)), 8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Distancia cero. El descenso de gradiente del capítulo 17 y la\n",
    "fórmula cerrada llegan exactamente al mismo punto.\n",
    "\n",
    "Este es el momento del libro que más me gusta. Álgebra por un lado, cálculo\n",
    "por el otro, y el mismo vector 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 9: mirar la estructura"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "C = np.cov(Z, rowvar=False)\n",
    "ev, evec = np.linalg.eigh(C)\n",
    "orden = np.argsort(ev)[::-1]\n",
    "print('varianza explicada:', np.round(ev[orden] / ev.sum(), 4))\n",
    "print('primer autovector :', np.round(evec[:, orden][:, 0], 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "26%, 26%, 24%, 24%. Repartido casi perfecto entre las cuatro.\n",
    "\n",
    "Eso es el capítulo 11 diciéndote **que no apliques PCA**. No\n",
    "hay redundancia que comprimir: las cuatro columnas aportan cosas distintas. Y\n",
    "eso, para un modelo, es una buena noticia."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 10: regularizar, y comprobar que no hace falta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for al in (0.0, 10.0, 1000.0):\n",
    "    wr = np.linalg.solve(G + al * np.eye(A.shape[1]), A.T @ y)\n",
    "    print('alpha', str(al).rjust(6), np.round(wr, 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con alpha 10 los pesos casi no se mueven. Compáralo con lo que pasaba en el capítulo 19, donde alpha 1 hacía saltar un coeficiente de\n",
    "-565 a 174.\n",
    "\n",
    "La diferencia es el paso 2: allá había una columna casi duplicada y aquí no.\n",
    "**Cuando no hay multicolinealidad, regularizar apenas cambia nada**,\n",
    "y con alpha 1000 solo estropea. Que un parámetro no haga nada también es\n",
    "información."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora la parte que ninguna fórmula hace"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los pesos son 2.26 para unidades, 478.34 para monto, -20.71 para descuento y\n",
    "58.93 para satisfacción, y como escalamos, todos están medidos en desviaciones y\n",
    "sí se pueden comparar.\n",
    "\n",
    "El monto manda por goleada, y tiene todo el sentido: lo que se factura sale\n",
    "de lo que se cobra. El descuento resta, que también cuadra.\n",
    "\n",
    "Y ahora el aviso, que es lo último que quiero que te lleves del libro. Ese\n",
    "-20.71 **no dice que dar descuento baje la facturación**. Dice que\n",
    "en este archivo, las ventas con más descuento facturaron menos, y eso puede ser\n",
    "porque el descuento baja el monto, o porque los descuentos grandes se dan\n",
    "justamente en las ventas chicas, o por diez cosas más.\n",
    "\n",
    "**El álgebra lineal encuentra relaciones. La causa la pones tú, o no la\n",
    "pone nadie.** Toda la matemática de este libro te da coeficientes\n",
    "correctísimos de una pregunta que a lo mejor no era la que querías hacer 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Forma y rango antes de tocar nada.\n",
    "\n",
    "- Escalar temprano, porque arregla la distancia, PCA y la convergencia.\n",
    "\n",
    "- `solve`, nunca `inv`.\n",
    "\n",
    "- Comparar siempre contra predecir la media.\n",
    "\n",
    "- Dos caminos independientes que dan el mismo resultado es la mejor\n",
    "comprobación.\n",
    "\n",
    "- PCA repartido parejo quiere decir que no apliques PCA.\n",
    "\n",
    "- Que regularizar no cambie nada también es información.\n",
    "\n",
    "- Los coeficientes son relaciones, no causas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "En el proyecto, el descenso de gradiente llega al mismo punto que la fórmula exacta con distancia 0.0. ¿Qué te permite concluir?\n",
    "\n",
    "a) Que el problema estaba bien escalado y bien condicionado, y por eso los dos caminos coinciden\n",
    "\n",
    "b) Que el modelo predice bien\n",
    "\n",
    "c) Que el descenso de gradiente siempre llega al mismo sitio\n",
    "\n",
    "d) Que la fórmula exacta sobra"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Quita una columna y mira cuánto costó\n",
    "\n",
    "Cada columna aporta algo medible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El chequeo que deberías correr siempre\n",
    "\n",
    "Cinco líneas que te ahorran tardes enteras."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Compruébalo contra scikit-learn\n",
    "\n",
    "Que lo que escribiste a mano sea lo mismo que usa todo el\n",
    "mundo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 20 de **Matemática para machine learning**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/matematica-para-machine-learning/el-proyecto-entero/\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
}
