{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Qué es PCA, y ya lo usaste\n",
    "\n",
    "Análisis de componentes principales explicado como lo que es: mirar la misma tabla desde las direcciones que los autovectores encontraron.\n",
    "\n",
    "Cuaderno de práctica del capítulo 11 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/que-es-pca/\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: \"W1sgMS4gIDAuICAwLiAgMC4gLTAuXQogWyAwLiAgMS4gIDAuIC0wLiAgMC5dCiBbIDAuICAwLiAgMS4gIDAuICAwLl0KIFsgMC4gLTAuICAwLiAgMS4gIDAuXQogWy0wLiAgMC4gIDAuICAwLiAgMS5dXQ==\",\n",
    "    2: \"cHJvcG9yY2lvbiBzaW4gZXNjYWxhcjogWzAuODcxNSAwLjEyODUgMC4gICAgIDAuICAgICAwLiAgICBdCiAgdW5pZGFkZXMgICAgICAgICAgICAgICAgICAtMC4wMDAwCiAgbW9udG8gICAgICAgICAgICAgICAgICAgICAtMC43Njk2CiAgZGVzY3VlbnRvICAgICAgICAgICAgICAgICAgMC4wMDAwCiAgc2F0aXNmYWNjaW9uICAgICAgICAgICAgICAtMC4wMDAwCiAgbW9udG9fZmluYWxfZmFjdHVyYWRvICAgICAtMC42Mzg1\",\n",
    "    3: \"MSBjb21wb25lbnRlcyAtPiBlcnJvciAwLjU5NzUKMiBjb21wb25lbnRlcyAtPiBlcnJvciAwLjQ5MDUKMyBjb21wb25lbnRlcyAtPiBlcnJvciAwLjM1NzgKNCBjb21wb25lbnRlcyAtPiBlcnJvciAwLjA5NjYKNSBjb21wb25lbnRlcyAtPiBlcnJvciAwLjA=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## PCA no es un algoritmo nuevo 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si hiciste el capítulo 10, PCA ya está hecho. Lo único\n",
    "que falta es multiplicar y leer.\n",
    "\n",
    "Y quiero empezar por lo que PCA **no** es, porque hay dos ideas\n",
    "sueltas por ahí que hacen daño. No es \"quitar columnas\": ninguna columna se\n",
    "elimina, todas participan en todas las componentes. Y no es \"quedarse con lo\n",
    "importante\": lo que se mide es dónde hay **variación**, y variar\n",
    "mucho no es lo mismo que importar."
   ]
  },
  {
   "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": [
    "## Las direcciones, otra vez"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "C = np.cov(Z, rowvar=False)\n",
    "vals, vecs = np.linalg.eigh(C)\n",
    "orden = np.argsort(vals)[::-1]\n",
    "vals, vecs = vals[orden], vecs[:, orden]\n",
    "print('autovalores:', np.round(vals, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y PCA es una multiplicación"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "P = Z @ vecs\n",
    "print('P.shape:', P.shape)\n",
    "print('varianza de cada componente:', np.round(P.var(axis=0, ddof=1), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "P=ZV\n",
    "\n",
    "las componentes principales salen de multiplicar tus datos escalados por la matriz de autovectores, o sea de mirar la misma tabla desde otras direcciones"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está todo el truco. Las varianzas de las componentes **son**\n",
    "los autovalores, clavados. No se parecen: son los mismos números.\n",
    "\n",
    "Eso es lo que quería decir \"los autovalores reparten la varianza\". Cada\n",
    "dirección se lleva un trozo, y el autovalor es el trozo.\n",
    "\n",
    "Fíjate también en la forma: sigue siendo 1.793 por 5. **No se perdió\n",
    "nada**. Es la misma tabla girada, no una tabla recortada. El recorte\n",
    "viene después y es una decisión aparte."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuánto se lleva cada una"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('proporcion:', np.round(vals / vals.sum(), 4))\n",
    "print('acumulada :', np.round(np.cumsum(vals / vals.sum()), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "proporción explicadai=λiλ1+…+λd\n",
    "\n",
    "qué porcentaje de la variación total se lleva cada dirección es su autovalor dividido entre la suma de todos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y acá te tengo que dar una noticia mala sobre mis propios datos: **este\n",
    "PCA no sirve de mucho**.\n",
    "\n",
    "Mira la primera componente: 34.95%. Y para llegar al 95% hacen falta cuatro\n",
    "de las cinco. O sea que puedo tirar una columna y poco más.\n",
    "\n",
    "Cuando en un curso te enseñan PCA, el ejemplo siempre tiene la primera\n",
    "componente en 70% u 80%, porque eligieron un dataset donde funciona. En el mío\n",
    "las columnas son bastante independientes entre sí, así que no hay mucho que\n",
    "comprimir.\n",
    "\n",
    "Eso también es un resultado. \"PCA no encontró estructura\" quiere decir \"tus\n",
    "columnas ya estaban aportando cosas distintas\", y eso es una buena noticia para\n",
    "un modelo, aunque sea una mala noticia para el gráfico bonito 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que sí encontró"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "COLS = ['unidades', 'monto', 'descuento', 'satisfaccion',\n",
    "        'monto_final_facturado']\n",
    "for nombre, peso in zip(COLS, vecs[:, 0]):\n",
    "    print(f'  {nombre:24} {peso:8.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La componente 1 es **plata**. Monto y monto final facturado con\n",
    "casi el mismo peso, y lo demás en cero.\n",
    "\n",
    "O sea que si tuvieras que resumir cada venta en un solo número, ese número\n",
    "sería \"cuánto factura\". Suena obvio dicho así, y el asunto es que nadie se lo\n",
    "dijo: lo encontró la matriz mirando 1.793 filas.\n",
    "\n",
    "Esa es la utilidad honesta de PCA la mayoría de las veces: **no\n",
    "comprimir, sino entender qué se mueve junto**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Recortar, que es lo que la gente quiere"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Z2 = P[:, :2] @ vecs[:, :2].T\n",
    "Z4 = P[:, :4] @ vecs[:, :4].T\n",
    "print('error medio con 2 componentes:', round(float(np.abs(Z - Z2).mean()), 4))\n",
    "print('error medio con 4 componentes:', round(float(np.abs(Z - Z4).mean()), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con dos componentes reconstruyo la tabla con un error medio de 0.49 sobre\n",
    "datos que tienen desviación 1. O sea que me equivoco casi media desviación en\n",
    "cada casilla, que es muchísimo.\n",
    "\n",
    "Con cuatro bajo a 0.0966, que ya es razonable. Otra vez lo mismo: acá se puede\n",
    "tirar una columna, no cuatro."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tres cosas que hay que decir y casi nunca se dicen"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Uno: escalar no es opcional.** Si no escalas, la columna con\n",
    "los números más grandes se lleva la primera componente entera, y no porque\n",
    "importe más. Es el mismo problema del capítulo 7.\n",
    "\n",
    "**Dos: las componentes no se explican.** \"Componente 1\" no es una\n",
    "variable de negocio. Puedes leer sus pesos y ponerle nombre, como hice arriba con\n",
    "\"plata\", pero si le entregas a alguien un modelo entrenado sobre componentes, no\n",
    "vas a poder decirle qué significa ninguna.\n",
    "\n",
    "**Tres: PCA no sabe qué quieres predecir.** Busca dónde hay\n",
    "variación, no dónde hay respuesta. Puede tirar tranquilamente la componente que\n",
    "menos varía y resulta que era justo la que separaba a tus clientes buenos de los\n",
    "malos. Pasa, y cuando pasa no avisa 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- PCA es multiplicar tus datos escalados por los autovectores.\n",
    "\n",
    "- La varianza de cada componente es su autovalor.\n",
    "\n",
    "- PCA gira la tabla, no la recorta. Recortar es una decisión aparte.\n",
    "\n",
    "- Si la varianza se reparte parejo, no hay nada que comprimir, y eso es un\n",
    "resultado.\n",
    "\n",
    "- Sin escalar, PCA solo encuentra la columna más grande.\n",
    "\n",
    "- PCA no sabe qué quieres predecir."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Corres PCA sobre 5 columnas y la varianza explicada acumulada es [0.35, 0.56, 0.76, 0.95, 1.00]. ¿Cuántas componentes te quedas?\n",
    "\n",
    "a) Depende de para qué, y con estos números ninguna opción es buena: hacen falta 4 para llegar al 95%\n",
    "\n",
    "b) Una, porque la primera es la más importante\n",
    "\n",
    "c) Dos, porque es la regla habitual para poder dibujarlo\n",
    "\n",
    "d) Cinco, porque hay que quedarse con el 100%"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Las componentes salen sin correlación entre ellas\n",
    "\n",
    "Esa es la propiedad que hace que PCA sirva de algo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Sin escalar, PCA solo ve la columna grande\n",
    "\n",
    "Corre lo mismo sobre M sin escalar y compara."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Reconstruye componente por componente\n",
    "\n",
    "Mira cómo baja el error al ir sumando direcciones."
   ]
  },
  {
   "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 11 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/que-es-pca/\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
}
