{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Norma y distancia: cómo mide un modelo que se equivocó\n",
    "\n",
    "Qué es la norma de un vector, la diferencia entre L1 y L2, y por qué el error de un modelo es literalmente una distancia.\n",
    "\n",
    "Cuaderno de práctica del capítulo 7 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/norma-y-distancia/\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: \"ZmlsYSAgICAgIDogMjEzMAp1bmlkYWRlcyAgOiA2LjAgfCBwcm9tZWRpbzogMTEuNjgKbW9udG8gICAgIDogNDIzNi43MSB8IHByb21lZGlvOiA4MDMuMzkKc2F0aXNmYWNjLjogMi4wIHwgcHJvbWVkaW86IDMuMDIKZGlzdGFuY2lhIDogMzQzMy4zMw==\",\n",
    "    2: \"TDEgOiA0OTQuMzcKTDIgOiA0ODAuNDkKTDEgLSBMMiA6IDEzLjg4\",\n",
    "    3: \"c2luIGVzY2FsYXIgOiA0NC41NAplc2NhbGFkbyAgICA6IDAuNwphcG9ydGUgcG9yIGNvbHVtbmEsIGVzY2FsYWRvOiBbMC4gICAwLjA2IDAuNyBd\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El capítulo donde el error deja de ser un misterio 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 3 te dije que restar dos vectores es cómo\n",
    "se mide una diferencia. Y quedó pendiente algo: la resta te da otro vector, no\n",
    "un número. Para decir \"me equivoqué tanto\" hace falta convertir ese vector en un\n",
    "número solo.\n",
    "\n",
    "Eso es la norma."
   ]
  },
  {
   "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": [
    "## Cuánto mide un vector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = X[0]\n",
    "print('a        :', np.round(a, 2))\n",
    "print('norma L2 :', round(float(np.linalg.norm(a)), 2))\n",
    "print('a mano   :', round(float(np.sqrt((a ** 2).sum())), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "‖𝐯‖2=v12+v22+…+vd2\n",
    "\n",
    "la norma L2 eleva cada componente al cuadrado, las suma y saca la raíz, que es el teorema de Pitágoras estirado a las dimensiones que hagan falta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si en el colegio hiciste Pitágoras, ya sabías esto: cateto al cuadrado más\n",
    "cateto al cuadrado, raíz, y sale la hipotenusa. La norma L2 es eso mismo pero\n",
    "sin parar en dos componentes.\n",
    "\n",
    "Y mira el resultado: 480.49, que es casi el monto solo. Los otros dos números\n",
    "casi no se notan. Guárdate ese detalle porque es el corazón del capítulo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La otra norma"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('norma L1 :', round(float(np.linalg.norm(a, 1)), 2))\n",
    "print('a mano   :', round(float(np.abs(a).sum()), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "‖𝐯‖1=|v1|+|v2|+…+|vd|\n",
    "\n",
    "la norma L1 suma los valores absolutos sin elevar nada al cuadrado, así que un componente enorme no pesa más que su tamaño"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La L1 no eleva al cuadrado: suma los valores absolutos. Parece un detalle\n",
    "tonto y cambia el comportamiento entero.\n",
    "\n",
    "Al elevar al cuadrado, la L2 **castiga mucho más los números\n",
    "grandes**: una diferencia de 10 pesa cien, una de 100 pesa diez mil. La\n",
    "L1 los trata a todos con su tamaño y ya.\n",
    "\n",
    "Esa diferencia es la que hace que L1 y L2 se comporten distinto como\n",
    "penalización dentro de un modelo, y es lo que explica por qué una hace ceros y\n",
    "la otra no. Eso es el capítulo de regularización entero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La distancia es la norma de la resta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a, b = X[0], X[1]\n",
    "print('a - b     :', np.round(a - b, 2))\n",
    "print('distancia :', round(float(np.linalg.norm(a - b)), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "d(𝐚,𝐛)=‖𝐚−𝐛‖2\n",
    "\n",
    "la distancia entre dos puntos es la norma de su resta, o sea primero se mide la diferencia y después se mide cuánto vale esa diferencia"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos pasos y ninguno es nuevo: restas, y mides cuánto mide la resta.\n",
    "\n",
    "Esto es lo que hay debajo de \"clientes parecidos\". Cuando un algoritmo te\n",
    "agrupa clientes o te recomienda el producto de alguien parecido a ti, está\n",
    "midiendo distancias entre filas 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y aquí está el error de un modelo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El modelo más tonto posible predice siempre el promedio. Vamos a medir cuánto\n",
    "se equivoca:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "media = X.mean(axis=0)\n",
    "err = X - media\n",
    "print('RMSE por columna :', np.round(np.sqrt((err ** 2).mean(axis=0)), 2))\n",
    "print('a mano, el monto :',\n",
    "      round(float(np.linalg.norm(err[:, 1]) / np.sqrt(X.shape[0])), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese 753.67 es el **RMSE**, el error cuadrático medio con raíz,\n",
    "que es la métrica que más vas a ver en regresión.\n",
    "\n",
    "Y mira lo que acaba de pasar: lo calculé de dos formas y salió lo mismo. El\n",
    "RMSE **es** la norma L2 del vector de errores dividida por la raíz\n",
    "del número de filas. No es una métrica que alguien se inventó: es una distancia\n",
    "con un nombre comercial.\n",
    "\n",
    "Predecir siempre el promedio te deja con 753 soles de error típico. Cualquier\n",
    "modelo que hagas tiene que bajar de ahí, y si no baja, el modelo sobra."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El problema que nadie te avisa"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a, b = X[0], X[1]\n",
    "print('solo unidades y satisfaccion:',\n",
    "      round(float(np.linalg.norm((a - b)[[0, 2]])), 2))\n",
    "print('con el monto dentro         :',\n",
    "      round(float(np.linalg.norm(a - b)), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mismo par de ventas. Sin el monto, la distancia es 1. Con el monto, 44.54.\n",
    "\n",
    "O sea que el monto se come el 98% de la distancia, y no porque importe más,\n",
    "sino porque sus números son más grandes. Una diferencia de 44 soles es\n",
    "insignificante para el negocio y aplasta a una diferencia de un punto entero de\n",
    "satisfacción, que sí importa.\n",
    "\n",
    "**Toda distancia sin escalar es una mentira educada.** Por eso\n",
    "antes de medir distancias se escalan las columnas, y es el paso que más se\n",
    "olvida. En mi libro de machine learning está trabajado entero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- La norma es cuánto mide un vector. La L2 es Pitágoras sin límite de\n",
    "dimensiones.\n",
    "\n",
    "- La L2 castiga los números grandes al cuadrado, la L1 no.\n",
    "\n",
    "- Distancia entre dos filas = norma de su resta.\n",
    "\n",
    "- El RMSE es la norma L2 de los errores entre la raíz de n. Es una distancia\n",
    "con otro nombre.\n",
    "\n",
    "- Sin escalar, la columna de números más grandes se come la distancia."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Mides la distancia entre dos clientes con columnas \"monto\" en soles y \"satisfacción\" del 1 al 5. ¿Qué va a pasar?\n",
    "\n",
    "a) El monto va a mandar casi del todo, porque sus números son mucho más grandes\n",
    "\n",
    "b) Las dos columnas van a pesar igual, porque la fórmula las trata igual\n",
    "\n",
    "c) La satisfacción va a mandar, porque va del 1 al 5 y es más limpia\n",
    "\n",
    "d) Va a dar error porque las unidades son distintas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La venta más lejos del promedio\n",
    "\n",
    "Encuentra la fila más rara de la tabla."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. L1 contra L2 en el mismo vector\n",
    "\n",
    "Mira cuánto se separan cuando hay un componente grande."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Escala y mira cómo cambia todo\n",
    "\n",
    "Divide cada columna por su desviación y vuelve a medir."
   ]
  },
  {
   "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 7 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/norma-y-distancia/\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
}
