{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Proyecciones: lo que hace la regresión lineal por dentro\n",
    "\n",
    "Qué es proyectar un vector sobre otro, por qué lo que sobra siempre queda perpendicular, y por qué una regresión es exactamente eso.\n",
    "\n",
    "Cuaderno de práctica del capítulo 9 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/proyecciones/\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: \"fGF8XjIgICAgICAgICAgICA6IDIzMDg3MS4zMzY5Cnxwcm95fF4yICsgfHJlc3RvfF4yOiAyMzA4NzEuMzM2OQ==\",\n",
    "    2: \"Y29lZmljaWVudGU6IDU1LjM0MTcKbyBzZWEgcXVlIGNhZGEgdW5pZGFkIHZhbGUgNTUuMzQgc29sZXMgc2VndW4gZXN0ZSBhanVzdGUKcmVzaWR1b3MgY29udHJhIHU6IDAuMA==\",\n",
    "    3: \"cmVzaWR1b3MgY29udHJhIGNhZGEgY29sdW1uYTogWzAuIDAuXQpSTVNFIDogNzUzLjY3\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La sombra de un vector sobre otro 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Imagínate una linterna justo encima de un palo apoyado en la pared. La sombra\n",
    "que cae sobre la pared es más corta que el palo, y va en la dirección de la\n",
    "pared. Eso es una proyección.\n",
    "\n",
    "En vectores es lo mismo: dado **a**, ¿cuánto de **a**\n",
    "va en la dirección de **b**?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Volvemos a abrir el archivo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cada capítulo arranca de cero, así que estas líneas se repiten. Son las\n",
    "mismas del capítulo 1 y las vas a ver en todos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "df = pd.read_csv('ventas-miss-yera.csv')\n",
    "for c in ('unidades', 'monto', 'satisfaccion'):\n",
    "    df[c] = pd.to_numeric(df[c], errors='coerce')\n",
    "X = df[['unidades', 'monto', 'satisfaccion']].dropna().to_numpy()\n",
    "print('X.shape:', X.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Proyectar, que son dos productos punto"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a, b = X[0], X[1]\n",
    "coef = float(a @ b) / float(b @ b)\n",
    "proy = coef * b\n",
    "\n",
    "print('a          :', np.round(a, 2))\n",
    "print('b          :', np.round(b, 2))\n",
    "print('coeficiente:', round(coef, 6))\n",
    "print('proyeccion :', np.round(proy, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "proy𝐛(𝐚)=𝐚·𝐛𝐛·𝐛𝐛\n",
    "\n",
    "proyectar a sobre b es quedarse con el trozo de a que va en la dirección de b, y ese trozo se calcula con dos productos punto y una división"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Símbolo | Qué es |\n",
    "|---|---|\n",
    "| **proyb(a**) | el trozo de a que va en dirección de b |\n",
    "| **a** · **b** | cuánto se parecen los dos |\n",
    "| **b** · **b** | el largo de b al cuadrado, que sirve para normalizar |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese 0.915 quiere decir \"a es como el 91.5% de b, en esa dirección\". Y la\n",
    "proyección es b estirado por ese número, o sea la operación de escalar del capítulo 3.\n",
    "\n",
    "Fíjate en que la proyección va **en la dirección de b**. No es\n",
    "un pedazo de a: es b encogido para que se parezca lo más posible a a."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que sobra queda perpendicular"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "resto = a - proy\n",
    "print('resto     :', np.round(resto, 2))\n",
    "print('resto . b :', round(float(resto @ b), 8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(𝐚−proy𝐛(𝐚))·𝐛=0\n",
    "\n",
    "lo que sobra después de proyectar es perpendicular a b, y perpendicular quiere decir que su producto punto da cero"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Producto punto cero quiere decir **perpendicular**, y no es\n",
    "casualidad: es la definición. Dos vectores son ortogonales cuando su producto\n",
    "punto da cero, punto.\n",
    "\n",
    "Y esto pasa siempre, no solo con estos números. La proyección es, por\n",
    "construcción, la parte que sí se puede explicar con b. Lo que queda es\n",
    "justamente lo que b no puede explicar, y por eso no le queda nada de b\n",
    "dentro 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y aquí está la regresión"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hasta ahora proyectamos sobre un vector. Ahora proyecto sobre **todas\n",
    "las columnas a la vez**: quiero predecir el monto con las unidades y la\n",
    "satisfacción."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = X[:, 1]\n",
    "A = np.column_stack([np.ones(X.shape[0]), X[:, 0], X[:, 2]])\n",
    "w = np.linalg.solve(A.T @ A, A.T @ y)\n",
    "\n",
    "print('intercepto  :', round(float(w[0]), 4))\n",
    "print('unidades    :', round(float(w[1]), 4))\n",
    "print('satisfaccion:', round(float(w[2]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa columna de unos del principio es el intercepto, la **b** de\n",
    "la fórmula del capítulo 4. Meterla como una columna más es el truco\n",
    "para que la fórmula la trate igual que a las otras.\n",
    "\n",
    "Y ahora la comprobación que quiero que veas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "yhat = A @ w\n",
    "res = y - yhat\n",
    "print('yhat[:3] :', np.round(yhat[:3], 2))\n",
    "print('residuos contra cada columna:', np.round(A.T @ res, 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ceros. Los residuos son **ortogonales a las tres columnas**, y\n",
    "eso es exactamente lo mismo que pasó arriba con dos vectores, solo que en tres\n",
    "direcciones a la vez.\n",
    "\n",
    "Eso es lo que significa \"mínimos cuadrados\": de todos los puntos que se\n",
    "pueden construir con tus columnas, la predicción es el más cercano a la\n",
    "realidad. Y el más cercano es siempre la sombra 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ahora la parte incómoda"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('RMSE del modelo         :',\n",
    "      round(float(np.sqrt((res ** 2).mean())), 2))\n",
    "print('RMSE de predecir la media:',\n",
    "      round(float(np.sqrt(((y - y.mean()) ** 2).mean())), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "753.47 contra 753.67. El modelo mejoró veinte céntimos sobre no hacer\n",
    "nada.\n",
    "\n",
    "Y quiero que te quedes con esto porque es lo que un curso normal te esconde:\n",
    "**la proyección funcionó perfecto y el modelo no sirve**. Las dos\n",
    "cosas a la vez.\n",
    "\n",
    "El álgebra hizo su trabajo impecablemente: encontró la mejor combinación\n",
    "posible de esas columnas. Lo que pasa es que en las unidades y la satisfacción\n",
    "de este archivo no hay información sobre el monto, así que la mejor combinación\n",
    "posible sigue siendo mala.\n",
    "\n",
    "Los residuos ortogonales son la firma de que ajustaste bien. No son una nota\n",
    "del modelo. Confundir las dos cosas es de las equivocaciones más caras que hay\n",
    "en este oficio 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Proyectar es quedarse con el trozo que va en una dirección, y son dos\n",
    "productos punto.\n",
    "\n",
    "- Producto punto cero quiere decir perpendicular.\n",
    "\n",
    "- Lo que sobra después de proyectar siempre queda perpendicular.\n",
    "\n",
    "- Una regresión por mínimos cuadrados es una proyección sobre tus columnas.\n",
    "\n",
    "- La columna de unos es el intercepto.\n",
    "\n",
    "- Residuos ortogonales dicen que ajustaste bien, no que el modelo sirva."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Ajustas una regresión y compruebas que los residuos son ortogonales a cada columna de X. ¿Qué te dice eso?\n",
    "\n",
    "a) Que el modelo ya exprimió toda la información lineal que había en esas columnas\n",
    "\n",
    "b) Que el modelo es bueno y predice bien\n",
    "\n",
    "c) Que las columnas de X no están correlacionadas entre sí\n",
    "\n",
    "d) Que hay que quitar columnas porque sobran"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Pitágoras sigue valiendo\n",
    "\n",
    "Si la proyección y el resto son perpendiculares, sus\n",
    "cuadrados tienen que sumar el cuadrado de a."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Proyecta sobre una sola columna\n",
    "\n",
    "La regresión más simple que existe, sin intercepto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Ortogonal no quiere decir bueno\n",
    "\n",
    "Ajusta con una columna de puro ruido y mira qué pasa."
   ]
  },
  {
   "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 9 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/proyecciones/\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
}
