{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Autovalores y autovectores, sin empezar por el determinante\n",
    "\n",
    "Qué es una dirección que una matriz no gira, qué mide el número que la acompaña, y por qué eso es lo que hay debajo de PCA.\n",
    "\n",
    "Cuaderno de práctica del capítulo 10 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/autovalores-y-autovectores/\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: \"ZW50cmEgOiBbMS4gMC4gMC4gMC4gMC5dCnNhbGUgIDogWyAxLjAwMDZlKzAwICA1LjAwMDBlLTA0IC0xLjIyMDBlLTAyIC0zLjU2MDBlLTAyICAxLjAwMDBlLTAzXQptaXNtbyBzZW50aWRvOiBGYWxzZQ==\",\n",
    "    2: \"YXV0b3ZhbG9yIG1hcyBjaGljbzogMC4yNTY1CiAgdW5pZGFkZXMgICAgICAgICAgICAgICAgICAgMC4wMDQ1CiAgbW9udG8gICAgICAgICAgICAgICAgICAgICAgMC43MDE0CiAgZGVzY3VlbnRvICAgICAgICAgICAgICAgICAtMC4wMjAzCiAgc2F0aXNmYWNjaW9uICAgICAgICAgICAgICAgMC4wOTAwCiAgbW9udG9fZmluYWxfZmFjdHVyYWRvICAgICAtMC43MDY4\",\n",
    "    3: \"dnVlbHZlIGEgc2FsaXIgQzogVHJ1ZQ==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Te van a enseñar esto empezando por el determinante. Yo no 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La forma clásica de enseñar autovalores es plantear una ecuación con un\n",
    "determinante, resolver un polinomio y sacar unas raíces. Se puede hacer y no\n",
    "sirve para entender nada.\n",
    "\n",
    "Vamos por el otro lado: qué pregunta contesta."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La pregunta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una matriz, cuando multiplica a un vector, normalmente lo **gira y lo\n",
    "estira**. Le cambia la dirección y el largo.\n",
    "\n",
    "La pregunta es: ¿hay alguna dirección que esta matriz **no gire**?\n",
    "¿Alguna donde el vector salga apuntando exactamente igual, solo más largo o más\n",
    "corto?\n",
    "\n",
    "Casi siempre las hay. Esas direcciones son los **autovectores**,\n",
    "y cuánto se estira cada una es su **autovalor**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A𝐯=λ𝐯\n",
    "\n",
    "un autovector es una dirección que la matriz no gira: la deja apuntando igual y solo la estira o la encoge, y cuánto la estira es el autovalor"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Símbolo | Qué es |\n",
    "|---|---|\n",
    "| **A** | la matriz |\n",
    "| **v** | el autovector: la dirección que no gira |\n",
    "| **λ** | el autovalor, la letra lambda: cuánto se estira esa dirección |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léela así: \"multiplicar por la matriz hace lo mismo que multiplicar por un\n",
    "número\". Eso es todo lo que dice, y es rarísimo que pase. Por eso las direcciones\n",
    "donde pasa son especiales."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Abrimos el archivo con más columnas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este bloque necesita una tabla más ancha. Con las tres de siempre no hay nada\n",
    "que descomponer, porque están casi sin relación entre ellas. Así que meto dos\n",
    "más: el descuento y el monto final facturado."
   ]
  },
  {
   "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",
    "        'monto_final_facturado']\n",
    "\n",
    "df = pd.read_csv('ventas-miss-yera.csv')\n",
    "for c in COLS:\n",
    "    df[c] = pd.to_numeric(df[c], errors='coerce')\n",
    "M = df[COLS].dropna().to_numpy()\n",
    "\n",
    "Z = (M - M.mean(axis=0)) / M.std(axis=0)\n",
    "print('M.shape:', M.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa `Z` es la tabla escalada: a cada columna le resté su promedio\n",
    "y la dividí por su desviación. Después de eso todas tienen media cero y\n",
    "desviación uno, o sea que ninguna manda por tener números más grandes. En el capítulo 7 viste por qué eso hace falta."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La matriz que nos importa"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "C = np.cov(Z, rowvar=False)\n",
    "print(np.round(C, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es la **matriz de covarianza**: en la diagonal está cuánto varía\n",
    "cada columna, y fuera cuánto se mueven juntas dos columnas. Como escalamos, la\n",
    "diagonal sale toda cerca de uno.\n",
    "\n",
    "Y hay un número que salta: **0.7377**, entre el monto y el monto\n",
    "final facturado. Esas dos columnas se mueven juntas, y tiene todo el sentido del\n",
    "mundo, porque una es casi la otra con el descuento aplicado.\n",
    "\n",
    "Es simétrica, como te adelanté en el capítulo 6. Guárdate eso\n",
    "porque es lo que hace que todo lo que viene funcione."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sacamos las direcciones"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vals, vecs = np.linalg.eigh(C)\n",
    "orden = np.argsort(vals)[::-1]\n",
    "vals, vecs = vals[orden], vecs[:, orden]\n",
    "\n",
    "print('autovalores:', np.round(vals, 4))\n",
    "print('autovectores, uno por columna:')\n",
    "print(np.round(vecs, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Uso `eigh` y no `eig` porque la matriz es simétrica.\n",
    "La versión con hache lo aprovecha, va más rápido y devuelve números reales\n",
    "garantizados. Con `eig` a veces salen complejos por redondeo y\n",
    "empieza el circo.\n",
    "\n",
    "`eigh` te los da de menor a mayor, así que hay que darles la\n",
    "vuelta. Eso hace el `[::-1]`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprobamos que la fórmula se cumple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v0 = vecs[:, 0]\n",
    "print('C @ v0      :', np.round(C @ v0, 6))\n",
    "print('lambda * v0 :', np.round(vals[0] * v0, 6))\n",
    "print('son iguales :', np.allclose(C @ v0, vals[0] * v0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Multiplicar por la matriz de cinco por cinco dio exactamente lo mismo que\n",
    "multiplicar por el número 1.7482. Eso es un autovector."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora léelo, que es lo bueno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la primera columna de los autovectores, que es la dirección más\n",
    "importante:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Columna | Cuánto pesa en la dirección 1 |\n",
    "|---|---|\n",
    "| unidades | -0.0000 |\n",
    "| monto | 0.6996 |\n",
    "| descuento | -0.0969 |\n",
    "| satisfaccion | 0.0631 |\n",
    "| monto_final_facturado | 0.7051 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos números grandes y tres cerca de cero. La dirección donde más varían tus\n",
    "datos es **plata**: monto y monto final facturado, casi con el mismo\n",
    "peso, y lo demás no participa.\n",
    "\n",
    "Eso no lo sabía nadie de antemano. Lo encontró la matriz sola 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tres propiedades que hacen falta después"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('largo del autovector :', round(float(np.linalg.norm(v0)), 6))\n",
    "print('v0 . v1              :', round(float(vecs[:, 0] @ vecs[:, 1]), 10))\n",
    "print('traza de C           :', round(float(np.trace(C)), 4))\n",
    "print('suma de autovalores  :', round(float(vals.sum()), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Uno.** Los autovectores vienen con largo 1. Solo marcan\n",
    "dirección, no distancia.\n",
    "\n",
    "**Dos.** El producto punto entre dos de ellos da cero, o sea que\n",
    "son **perpendiculares** entre sí, en el sentido del capítulo 9. Cada uno mira a un sitio que los otros no miran. Eso pasa\n",
    "porque la matriz es simétrica, y es la razón de que la simetría importara.\n",
    "\n",
    "**Tres.** Los autovalores suman lo mismo que la diagonal. La\n",
    "varianza total no se creó ni se destruyó: se **repartió** entre\n",
    "direcciones. Y esa frase es PCA entero, que es el capítulo que viene."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Un autovector es una dirección que la matriz no gira.\n",
    "\n",
    "- El autovalor es cuánto la estira.\n",
    "\n",
    "- A**v** = λ**v**: multiplicar por la\n",
    "matriz hace lo mismo que multiplicar por un número.\n",
    "\n",
    "- Para matrices simétricas usa `eigh`, no `eig`.\n",
    "\n",
    "- Salen con largo 1 y perpendiculares entre sí.\n",
    "\n",
    "- Los autovalores suman la varianza total: la reparten, no la inventan."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "La matriz de covarianza de tus 5 columnas escaladas tiene autovalores [1.75, 1.04, 1.00, 0.96, 0.26]. ¿Qué te está diciendo el último?\n",
    "\n",
    "a) Que hay una dirección donde tus datos casi no varían, o sea información repetida entre columnas\n",
    "\n",
    "b) Que una columna tiene menos datos que las otras\n",
    "\n",
    "c) Que la quinta columna es la menos importante\n",
    "\n",
    "d) Que hay un error de escala en los datos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Una dirección cualquiera SÍ gira\n",
    "\n",
    "Para que veas que ser autovector es raro."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El autovalor más chico y qué columnas lo forman\n",
    "\n",
    "La dirección donde no pasa nada es la más informativa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Vuelve a armar la matriz con sus piezas\n",
    "\n",
    "Autovalores y autovectores contienen la matriz entera."
   ]
  },
  {
   "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 10 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/autovalores-y-autovectores/\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
}
