{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Transpuesta e inversa, y por qué casi nunca se invierte\n",
    "\n",
    "Qué hace la transpuesta, qué es la inversa, por qué existe la identidad y por qué en código de verdad se usa solve y no inv.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 6 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/transpuesta-e-inversa/\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": [
    "## Dos operaciones que van juntas por un motivo 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La transpuesta ya la viste de pasada en el capítulo 1. La inversa\n",
    "es nueva. Van en el mismo capítulo porque la fórmula más famosa de todo machine\n",
    "learning las usa a las dos seguidas, y al final del capítulo la vas a poder\n",
    "leer entera."
   ]
  },
  {
   "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": [
    "## La transpuesta gira la tabla"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('X.shape  :', X.shape)\n",
    "print('X.T.shape:', X.T.shape)\n",
    "print('(X.T).T es X:', np.array_equal(X.T.T, X))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Girarla dos veces te devuelve la de partida, que es lo que uno esperaría de\n",
    "algo que se llama \"girar\".\n",
    "\n",
    "Y para qué sirve: sirve para que las formas encajen. En el capítulo 5 viste que `W @ X` reventaba. La\n",
    "transpuesta es lo que arregla eso, y el 90% de las veces que la vas a usar es\n",
    "por eso mismo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## X traspuesta por X, la matriz que aparece en todas partes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos a trabajar con dos columnas para que los números se lean:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "U = X[:, [0, 2]]\n",
    "G = U.T @ U\n",
    "print('U.shape:', U.shape)\n",
    "print('G = U.T @ U')\n",
    "print(G)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en la forma: entra `(2, 2238)` por `(2238, 2)` y\n",
    "sale `(2, 2)`. Los 2.238 se gastaron.\n",
    "\n",
    "Eso es lo que hace X⊤X y por eso está en todas\n",
    "partes: **convierte una tabla enorme en una matriz chiquita** que\n",
    "solo tiene el tamaño de tus columnas. Da igual que tengas mil filas o un millón,\n",
    "esa matriz sigue siendo de 2 por 2.\n",
    "\n",
    "Y fíjate en otra cosa: los dos 78592 de las esquinas son el mismo número. Sale\n",
    "simétrica siempre, y no es casualidad. Es el producto punto de la columna 0 con\n",
    "la columna 1, que da lo mismo en los dos sentidos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La inversa deshace"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Gi = np.linalg.inv(G)\n",
    "print('inversa de G:')\n",
    "print(np.round(Gi, 8))\n",
    "print('G @ Gi redondeado a 10:')\n",
    "print(np.round(G @ Gi, 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "AA−1=A−1A=I\n",
    "\n",
    "la inversa es la matriz que al multiplicarla por la original te devuelve la identidad, que es el 1 de las matrices"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Símbolo | Qué es |\n",
    "|---|---|\n",
    "| **A** | la matriz de partida, que tiene que ser cuadrada |\n",
    "| **A-1** | su inversa, que se lee \"a a la menos uno\" |\n",
    "| **I** | la identidad: unos en la diagonal y ceros en el resto |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La inversa es el **dividir** de las matrices. Con números, 5 por\n",
    "un quinto da 1. Con matrices, A por su inversa da la identidad, que es el 1 de\n",
    "este mundo.\n",
    "\n",
    "Y ojo con ese `-0.` de la salida. No es un error de NumPy: es\n",
    "coma flotante. El resultado no es exactamente cero, es algo como diez a la menos\n",
    "diecisiete, y al redondear sale con signo. Por eso nunca compares matrices con\n",
    "`==`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(np.allclose(G @ Gi, np.eye(2)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`np.allclose` pregunta \"¿son iguales dentro de lo razonable?\" y esa\n",
    "es la pregunta correcta cuando hay decimales de por medio."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuándo NO hay inversa, y por qué da más miedo de lo que parece"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le pego a X una columna que son las unidades multiplicadas por dos, o sea\n",
    "información que ya estaba:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "D = np.column_stack([X[:, 0], X[:, 0] * 2])\n",
    "GD = D.T @ D\n",
    "print('GD ='); print(GD)\n",
    "print('rango:', np.linalg.matrix_rank(GD), 'de 2')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Rango 1 de 2 quiere decir que ahí solo hay una columna de información. Esta\n",
    "matriz **no tiene inversa**: se dice que es\n",
    "**singular**.\n",
    "\n",
    "Y ahora la parte incómoda:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "inv = np.linalg.inv(GD)\n",
    "print('inv ='); print(inv)\n",
    "print('GD @ inv, que tendria que ser la identidad:')\n",
    "print(np.round(GD @ inv, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**No dio error.** Te devolvió una matriz con toda la\n",
    "tranquilidad del mundo.\n",
    "\n",
    "Pero mírala: números en el orden de diez mil millones, cuando la matriz de\n",
    "partida tenía cientos de miles. Y la comprobación no miente: eso no es la\n",
    "identidad ni de lejos. Debajo tendría que salir uno, cero, cero, uno, y sale un\n",
    "dos y un menos cero coma veintisiete.\n",
    "\n",
    "Es basura. Basura que se puede imprimir, meter en un informe y presentar en\n",
    "una reunión 🙃\n",
    "\n",
    "A veces sí avisa. Si la dependencia sale tan limpia que el determinante da\n",
    "cero exacto, NumPy corta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    Xd = np.column_stack([X, X[:, 0] * 2])\n",
    "    np.linalg.inv(Xd.T @ Xd)\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "LinAlgError: Singular matrix\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pero no puedes contar con eso. Depende de los números que te tocaron, y en\n",
    "datos de verdad el aviso casi nunca llega.\n",
    "\n",
    "Si en tu tabla metes \"precio en soles\" y \"precio en dólares\" con un tipo de\n",
    "cambio fijo, acabas de hacer exactamente esto. La forma de detectarlo antes, sin\n",
    "depender de que algo reviente, es mirar el rango, que es el capítulo 8."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora la fórmula famosa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "𝐰=(X⊤X)−1X⊤𝐲\n",
    "\n",
    "los pesos de una regresión lineal salen de invertir X traspuesta por X y multiplicar por X traspuesta por y, aunque en la práctica se resuelve sin invertir nada"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léela de derecha a izquierda: agarra tus datos y tu objetivo, gíralos para que\n",
    "encajen, y deshaz. Lo que sale son los pesos.\n",
    "\n",
    "Eso es una regresión lineal resuelta de una vez, sin entrenar ni iterar nada.\n",
    "Se llaman las **ecuaciones normales**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pero en código no se invierte"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = X[:, 1]\n",
    "U = X[:, [0, 2]]\n",
    "G = U.T @ U\n",
    "\n",
    "print('con solve:', np.round(np.linalg.solve(G, U.T @ y), 4))\n",
    "print('con inv  :', np.round(np.linalg.inv(G) @ (U.T @ y), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dan lo mismo, y aun así **usa siempre `solve`**. Dos\n",
    "motivos: hace menos cuentas, y arrastra menos error de redondeo. Cuando la\n",
    "matriz está casi en el borde de ser singular, `inv` te devuelve\n",
    "números que parecen razonables y no lo son.\n",
    "\n",
    "La regla práctica: si escribiste `inv` en código que va a\n",
    "producción, casi seguro querías `solve` 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- La transpuesta cambia filas por columnas y se usa sobre todo para que las\n",
    "formas encajen.\n",
    "\n",
    "- X⊤X convierte una tabla enorme en una matriz del\n",
    "tamaño de tus columnas, y sale simétrica.\n",
    "\n",
    "- La inversa deshace, y la identidad es el 1 de las matrices.\n",
    "\n",
    "- Compara con `np.allclose`, nunca con `==`.\n",
    "\n",
    "- Singular quiere decir que sobra información.\n",
    "\n",
    "- En código: `solve`, no `inv`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Calculas np.linalg.inv(A) y NumPy te tira LinAlgError: Singular matrix. ¿Qué te está diciendo?\n",
    "\n",
    "a) Que hay columnas que se pueden construir con las otras, así que no hay inversa\n",
    "\n",
    "b) Que la matriz tiene números demasiado grandes\n",
    "\n",
    "c) Que la matriz no es cuadrada\n",
    "\n",
    "d) Que hay valores nulos dentro\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* El tamaño de los números no impide invertir. Lo que lo impide es que sobre información: que una columna sea combinación de las demás.\n",
    "\n",
    "*c)* Eso da otro error distinto, y lo dice con esas palabras. Singular quiere decir que es cuadrada pero igual no se puede.\n",
    "\n",
    "*d)* Los nulos dan nan y se propagan, pero el mensaje sería otro. Singular es un problema de estructura, no de datos sucios.\n",
    "\n",
    "Singular quiere decir"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La simetría no es casualidad\n",
    "\n",
    "Comprueba que X traspuesta por X siempre sale simétrica."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = X.T @ X\n",
    "print('es simetrica:', np.allclose(G, G.T))\n",
    "print('forma       :', G.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "es simetrica: True\n",
    "forma       : (3, 3)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La casilla (i, j) es el producto punto de la columna i con la j, y la (j, i)\n",
    "es el mismo producto punto al revés. Como entre vectores el orden da igual,\n",
    "salen iguales. Esa simetría es la que hace que estas matrices tengan\n",
    "autovectores reales, y eso es el capítulo de autovalores y autovectores."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. La identidad no hace nada, compruébalo\n",
    "\n",
    "Multiplicar por la identidad tiene que devolver lo mismo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "I = np.eye(3)\n",
    "print(np.array_equal(X @ I, X))\n",
    "print(np.array_equal(I @ X.T, X.T))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "True\n",
    "True\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por eso se llama identidad: deja la cosa idéntica. Y por eso la definición de\n",
    "inversa es \"la que multiplicada por la original da la identidad\", o sea \"la que\n",
    "deshace\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Rompe la inversa tú mismo\n",
    "\n",
    "Fabrica una columna redundante y mira cómo revienta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "D = np.column_stack([X[:, 0], X[:, 0] * 2])\n",
    "print('las dos primeras filas:')\n",
    "print(D[:2])\n",
    "print('determinante:', float(np.linalg.det(D.T @ D)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "las dos primeras filas:\n",
    "[[10. 20.]\n",
    " [10. 20.]]\n",
    "determinante: -8.848053403198714e-05\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira bien ese número, porque enseña dos cosas de una. Matemáticamente el\n",
    "determinante es **cero exacto**: la segunda columna es la primera\n",
    "por dos y no hay inversa posible. Lo que sale es menos ocho coma ocho por diez a la\n",
    "menos cinco, y encima negativo.\n",
    "\n",
    "Eso es coma flotante. Los números de esa matriz están en el orden de\n",
    "380.000, así que un residuo de 0.000088 es cero para todos los efectos. Pero si\n",
    "tú escribes `if det == 0` en tu código, ese `if` no entra\n",
    "nunca.\n",
    "\n",
    "Por eso el determinante no se usa para decidir: se usa\n",
    "`np.linalg.matrix_rank`, que hace la pregunta bien planteada. Eso es el capítulo 8."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Cómo se calcula la inversa de una matriz?Con NumPy es una llamada. Lo importante no es calcularla sino saber cuándo no existe, porque en ese caso la función no siempre avisa: devuelve números enormes que parecen un resultado.\n",
    "\n",
    "¿Qué es una matriz singular?Una que no tiene inversa, porque alguna de sus columnas se puede construir con las otras. En datos pasa todo el rato: dos columnas que miden lo mismo con otro nombre.\n",
    "\n",
    "¿Qué es la traspuesta de una matriz?Las filas pasan a ser columnas. Se escribe traspuesta o transpuesta, las dos son correctas.\n",
    "\n",
    "¿Para qué sirve la matriz identidad?Para comprobar. Si multiplicas una matriz por su inversa y no sale la identidad, la inversa está mal y ese chequeo se hace siempre."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 6 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/transpuesta-e-inversa/\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
}
