{
 "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 soluciones 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",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "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": [
    "## 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\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Son cosas distintas. Los residuos salen ortogonales SIEMPRE que ajustes por mínimos cuadrados, hasta cuando el modelo no sirve para nada. Es una propiedad del método, no una nota.\n",
    "\n",
    "*c)* Eso es otra cosa y se mide con la correlación entre columnas. La ortogonalidad de la que hablamos es entre los residuos y las columnas.\n",
    "\n",
    "*d)* Sobrar o no sobrar es cuestión de rango, y lo trabajamos en el capítulo 8. Esto es otra propiedad.\n",
    "\n",
    "Los residuos ortogonales son la firma de mínimos cuadrados. Si te salen y el error sigue siendo enorme, el problema no es el ajuste: es que en esas columnas no había respuesta 🌸"
   ]
  },
  {
   "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": [
    "a, b = X[0], X[1]\n",
    "proy = (float(a @ b) / float(b @ b)) * b\n",
    "resto = a - proy\n",
    "print('|a|^2            :', round(float(a @ a), 4))\n",
    "print('|proy|^2 + |resto|^2:',\n",
    "      round(float(proy @ proy + resto @ resto), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "|a|^2            : 230871.3369\n",
    "|proy|^2 + |resto|^2: 230871.3369\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cateto al cuadrado más cateto al cuadrado igual a hipotenusa al cuadrado, en\n",
    "tres dimensiones y con datos de ventas. La geometría del colegio no se fue a\n",
    "ninguna parte."
   ]
  },
  {
   "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": [
    "y = X[:, 1]\n",
    "u = X[:, 0]\n",
    "coef = float(u @ y) / float(u @ u)\n",
    "print('coeficiente:', round(coef, 4))\n",
    "print('o sea que cada unidad vale', round(coef, 2), 'soles segun este ajuste')\n",
    "res = y - coef * u\n",
    "print('residuos contra u:', round(float(u @ res), 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "coeficiente: 55.3417\n",
    "o sea que cada unidad vale 55.34 soles segun este ajuste\n",
    "residuos contra u: 0.0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un solo producto punto dividido por otro, y ya tienes una regresión. Ojo con\n",
    "leerla como causa: que el ajuste dé 55.34 no quiere decir que vender una unidad\n",
    "más traiga 55 soles más."
   ]
  },
  {
   "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": [
    "rng = np.random.default_rng(0)\n",
    "basura = rng.normal(size=X.shape[0])\n",
    "y = X[:, 1]\n",
    "A = np.column_stack([np.ones(X.shape[0]), basura])\n",
    "w = np.linalg.solve(A.T @ A, A.T @ y)\n",
    "res = y - A @ w\n",
    "print('residuos contra cada columna:', np.round(A.T @ res, 6))\n",
    "print('RMSE :', round(float(np.sqrt((res ** 2).mean())), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "residuos contra cada columna: [0. 0.]\n",
    "RMSE : 753.67\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ortogonalidad perfecta con una columna de números aleatorios, y un RMSE de\n",
    "753.67, que es clavado el de predecir la media. O sea que esa columna aportó\n",
    "exactamente nada, y aun así la propiedad se cumple igual de bien.\n",
    "\n",
    "El álgebra no sabe ni le importa si tus columnas significan algo: proyecta lo\n",
    "que le des. El juicio de si esas columnas tenían sentido lo pones tú, y ningún\n",
    "test lo pone por ti."
   ]
  },
  {
   "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
}
