{
 "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 soluciones 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",
    "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": [
    "## 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\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* La fórmula las trata igual, y ese es justo el problema: una diferencia de 300 soles y una diferencia de 2 puntos entran con su tamaño crudo.\n",
    "\n",
    "*c)* Al revés. Cuanto más chico es el rango, menos aporta al cuadrado de la diferencia.\n",
    "\n",
    "*d)* Ojalá diera error. No lo da: te devuelve un número con toda la tranquilidad, y ese número está dominado por una sola columna.\n",
    "\n",
    "Por eso antes de medir distancias se escala. Es el paso que más se olvida y el que más silenciosamente arruina un modelo 🌸"
   ]
  },
  {
   "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": [
    "media = X.mean(axis=0)\n",
    "distancias = np.linalg.norm(X - media, axis=1)\n",
    "i = int(distancias.argmax())\n",
    "print('fila      :', i)\n",
    "print('unidades  :', X[i, 0], '| promedio:', round(float(media[0]), 2))\n",
    "print('monto     :', X[i, 1], '| promedio:', round(float(media[1]), 2))\n",
    "print('satisfacc.:', X[i, 2], '| promedio:', round(float(media[2]), 2))\n",
    "print('distancia :', round(float(distancias[i]), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "fila      : 2130\n",
    "unidades  : 6.0 | promedio: 11.68\n",
    "monto     : 4236.71 | promedio: 803.39\n",
    "satisfacc.: 2.0 | promedio: 3.02\n",
    "distancia : 3433.33\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El `axis=1` ahí sí es el correcto: quieres una norma por fila. Y\n",
    "fíjate en que la fila más lejos lo es por el monto, otra vez. Detectar outliers\n",
    "con distancias sin escalar es detectar la columna más grande."
   ]
  },
  {
   "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": [
    "a = X[0]\n",
    "print('L1 :', round(float(np.linalg.norm(a, 1)), 2))\n",
    "print('L2 :', round(float(np.linalg.norm(a, 2)), 2))\n",
    "print('L1 - L2 :', round(float(np.linalg.norm(a, 1) - np.linalg.norm(a, 2)), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "L1 : 494.37\n",
    "L2 : 480.49\n",
    "L1 - L2 : 13.88\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La L1 es más grande porque suma todo tal cual. La L2 casi ignora las\n",
    "componentes chicas, porque al cuadrado se hacen todavía más chicas frente a la\n",
    "grande. Esa es la intuición que hay que llevarse a regularización."
   ]
  },
  {
   "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": [
    "Z = (X - X.mean(axis=0)) / X.std(axis=0)\n",
    "a, b = Z[0], Z[1]\n",
    "print('sin escalar :', round(float(np.linalg.norm(X[0] - X[1])), 2))\n",
    "print('escalado    :', round(float(np.linalg.norm(a - b)), 2))\n",
    "print('aporte por columna, escalado:', np.round(np.abs(a - b), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "sin escalar : 44.54\n",
    "escalado    : 0.7\n",
    "aporte por columna, escalado: [0.   0.06 0.7 ]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora la satisfacción aporta 0.7 y el monto 0.06, o sea justo al revés que\n",
    "antes. Ninguna de las dos distancias es \"la correcta\": lo que hay que entender\n",
    "es que **escalar es una decisión**, y que no escalar también lo es,\n",
    "solo que tomada sin darte cuenta."
   ]
  },
  {
   "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
}
