{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# NumPy y la vectorización\n",
    "\n",
    "Por qué el mismo cálculo puede tardar decenas de veces más escrito de una forma que de otra, y el arreglo que está debajo de pandas.\n",
    "\n",
    "Cuaderno de práctica del capítulo 11 de **Python desde cero**, 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/python-desde-cero/numpy/\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: \"WyAxMTguICAgIDI5NS41OSAgNTY2Ljg0ICAxMTIuNjkgMTQxNi4gIF0=\",\n",
    "    2: \"PFUyMQpbJzEwJyAnMjAnICduL2EnICc0MCdd\",\n",
    "    3: \"WzQ4MC4zNyA1MjQuOSBd\",\n",
    "    4: \"VHlwZUVycm9yOiB1ZnVuYyAnYml0d2lzZV9hbmQnIG5vdCBzdXBwb3J0ZWQgZm9yIHRoZSBpbnB1dCB0eXBlcywgYW5kIHRoZSBpbnB1dHMgY291bGQgbm90IGJlIHNhZmVseSBjb2VyY2VkIHRvIGFueSBzdXBwb3J0ZWQgdHlwZXMgYWNjb3JkaW5nIHRvIHRoZSBjYXN0aW5nIHJ1bGUgJydzYWZlJyc=\",\n",
    "    5: \"Mg==\",\n",
    "    6: \"bmFuCjMwMC4wCjIgc2luIGNvbnRlc3RhciBkZSA1\",\n",
    "    7: \"cG9zaWNpb24gMiwgdmFsb3IgMjQ4MC44NA==\",\n",
    "    8: \"WzMxMTYuMDQgICA0OC4gIF0=\",\n",
    "    9: \"RmFsc2UKMAox\",\n",
    "    10: \"WzAuICAgIDAuMTYxIDEuICAgIDAuMTggXQ==\",\n",
    "    11: \"dmVudGFzIGNvbiBtb250byAgOiAzMDM3CmNvbWlzaW9uIHRvdGFsIFMvIDogOTk2MjkuMDgKbGEgbWF5b3IgICAgICAgUy8gOiAyMTEuODQKc29icmUgZWwgdG90YWwgICAgOiA0LjA5ICU=\",\n",
    "    12: \"dG90YWwgYW50ZXMgIDogUy8gMjQzNDY1MS42Nwp0b3RhbCBkZXNwdWVzOiBTLyAyNDI0OTM0LjI5CnNlIHBlcmRpZXJvbiA6IFMvIDk3MTcuMzgKeSB5byBzb2xvIHRvcXVlIHByaW1lcm9zX2RpZXo6IFRydWU=\",\n",
    "    13: \"ZWwgdGVjaG8gZGVsIGludDE2OiAzMjc2NwpndWFyZGFkbyAgICAgICAgICA6IFsgMTAwMCAgNTAwMCAyMDAwMF0KcG9yIGRvcyAgICAgICAgICAgOiBbICAyMDAwICAxMDAwMCAtMjU1MzZdCmVuIGludDMyICAgICAgICAgIDogWyAyMDAwIDEwMDAwIDQwMDAwXQ==\",\n",
    "    14: \"bGEgdGFibGEgZXMgICAgICAgKDMsIDIpCnBvciBjb2x1bW5hIGVzICAgICgyLCkKcG9yIGZpbGEgZXMgICAgICAgKDMsIDEpCgpkZXNjdWVudG8gcG9yIGNvbHVtbmE6CltbIDQzMi4zMyAgMTQ3LjA5XQogWzIyMzIuNzYgIDQ5OC42Nl0KIFsgODAxLiAgICAzMDQuMSBdXQpkZXNjdWVudG8gcG9yIGZpbGE6CltbIDQzMi4zMyAgMTM5LjM1XQogWzE5ODQuNjcgIDQxOS45Ml0KIFsgODQ1LjUgICAzMDQuMSBdXQ==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hola! Ahora hablemos de velocidad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hasta aquí todo lo que hicimos funcionaba. Con tres mil filas todo funciona 🙂\n",
    "\n",
    "El día que tengas un millón, la diferencia entre saber esto y no saberlo se\n",
    "mide en cuántas veces más tarda tu cálculo. Y lo vamos a medir aquí mismo, no a\n",
    "creérnoslo. Ese día llega.\n",
    "\n",
    "NumPy es la librería que está debajo de pandas. Aunque no la uses directo,\n",
    "entenderla te explica por qué pandas se escribe como se escribe 💜\n",
    "\n",
    "Antes de arrancar, una pregunta: **¿cuál es el archivo más grande que has abierto en Excel sin que se te cuelgue?** Guarda ese número en la cabeza, porque de eso va exactamente este capítulo 🚀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Un arreglo no es una lista"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "montos = np.array([480.37, 154.83, 2480.84, 524.90])\n",
    "\n",
    "print(montos)\n",
    "print(type(montos))\n",
    "print(montos.dtype)\n",
    "print(montos.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres diferencias con una lista, y las tres importan:\n",
    "\n",
    "- 🔢 **Un solo tipo.** Ese `float64` vale para todos los\n",
    "elementos. Una lista puede mezclar textos, números y lo que sea; un arreglo no.\n",
    "\n",
    "- 📦 **Guardado junto en memoria.** Por eso es rápido.\n",
    "\n",
    "- 📐 **Tiene forma.** Ese `(4,)` dice que es de una\n",
    "dimensión con cuatro elementos.\n",
    "\n",
    "Y mira lo que pasa si mezclas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mezcla = np.array([1, 2.5, 'tres'])\n",
    "print(mezcla.dtype)\n",
    "print(mezcla)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Convirtió todo a texto para que quepa en un solo tipo, y ahora tus números no\n",
    "son números. **Eso mismo le pasa a una columna de pandas** cuando un\n",
    "archivo trae un \"n/a\" en medio de los montos, y es la razón del capítulo 3 😅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Vectorizar: la operación se aplica a todo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos = np.array([480.37, 154.83, 2480.84, 524.90])\n",
    "\n",
    "print(montos * 1.18)\n",
    "print(montos - 100)\n",
    "print(np.round(montos * 1.18, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sin bucle. Sin `for`. La multiplicación se aplicó a los cuatro\n",
    "números de una.\n",
    "\n",
    "Compáralo con lo que pasa con una lista normal:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lista = [480.37, 154.83, 2480.84, 524.90]\n",
    "print(lista * 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La lista se repitió, igual que el texto del capítulo 3. Con el arreglo se\n",
    "multiplica; con la lista se repite. Son dos mundos distintos y hay que saber en\n",
    "cuál estás 🌸\n",
    "\n",
    "### Y ahora midámoslo, que es lo honesto\n",
    "\n",
    "No te voy a decir \"es más rápido\" y ya. Vamos a contarlo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "\n",
    "n = 1_000_000\n",
    "lista = list(range(n))\n",
    "arreglo = np.arange(n)\n",
    "\n",
    "inicio = time.perf_counter()\n",
    "resultado_bucle = [x * 1.18 for x in lista]\n",
    "tiempo_bucle = time.perf_counter() - inicio\n",
    "\n",
    "inicio = time.perf_counter()\n",
    "resultado_numpy = arreglo * 1.18\n",
    "tiempo_numpy = time.perf_counter() - inicio\n",
    "\n",
    "print(f'con bucle : {tiempo_bucle:.3f} s')\n",
    "print(f'con numpy : {tiempo_numpy:.3f} s')\n",
    "print(f'numpy es {tiempo_bucle / tiempo_numpy:.0f} veces mas rapido')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El número exacto va a cambiar en tu máquina, y el orden de magnitud no: NumPy\n",
    "sale decenas de veces más rápido en la misma operación.\n",
    "\n",
    "La razón es que el bucle de Python revisa el tipo de cada elemento en cada\n",
    "vuelta, un millón de veces. NumPy sabe de antemano que todos son\n",
    "`float64` y le manda el trabajo entero a código en C, que no pregunta\n",
    "nada 🚀\n",
    "\n",
    "De ahí sale la regla de oro de todo este libro:\n",
    "**si estás escribiendo un `for` sobre un arreglo o sobre una\n",
    "columna de pandas, casi seguro hay una forma vectorizada**. Búscala."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Filtrar con una condición"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto es lo que después vas a hacer en pandas todo el día, así que vale la pena\n",
    "verlo aquí primero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos = np.array([480.37, 154.83, 2480.84, 524.90, 95.5])\n",
    "\n",
    "grandes = montos > 500\n",
    "print(grandes)\n",
    "print(montos[grandes])\n",
    "print(montos[montos > 500])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira el paso intermedio, que es la clave: `montos > 500` no\n",
    "devuelve los montos, devuelve **una máscara de verdaderos y falsos**.\n",
    "Y meter esa máscara entre corchetes deja pasar solo los `True`.\n",
    "\n",
    "Cuando entiendas eso, pandas deja de parecer magia 🌟\n",
    "\n",
    "Para combinar condiciones hay un detalle que confunde a todo el mundo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(montos[(montos > 200) & (montos < 1000)])\n",
    "print(montos[(montos < 100) | (montos > 2000)])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se usa `&` y `|`, **no `and` ni\n",
    "`or`**. Y los paréntesis alrededor de cada condición son\n",
    "obligatorios. Si te los saltas:"
   ]
  },
  {
   "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",
    "    montos[montos > 200 & montos < 1000]\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",
    "TypeError: ufunc 'bitwise_and' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mensaje feo, causa simple: `&` se evalúa antes que\n",
    "`>`, así que sin paréntesis está intentando hacer\n",
    "`200 & montos`. Ponle paréntesis a cada condición y listo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los resúmenes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos = np.array([480.37, 154.83, 2480.84, 524.90, 95.5])\n",
    "\n",
    "print('suma    ', montos.sum())\n",
    "print('promedio', round(montos.mean(), 2))\n",
    "print('mediana ', np.median(montos))\n",
    "print('desv    ', round(montos.std(), 2))\n",
    "print('minimo  ', montos.min(), 'maximo', montos.max())\n",
    "print('posicion del maximo:', montos.argmax())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en el promedio contra la mediana: 747 contra 480. Un solo monto de\n",
    "2.480 arrastró el promedio hacia arriba. Es lo mismo que vimos en el capítulo 8\n",
    "y lo desarrollo entero en el\n",
    "[libro de estadística](https://missyera.com/guias/estadistica-desde-cero/).\n",
    "\n",
    "Y `argmax` te da la *posición* del máximo, no el valor. Eso\n",
    "es lo que usas cuando quieres saber qué fila fue la más alta 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los huecos: NaN"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En datos reales faltan valores. NumPy los representa con `np.nan`,\n",
    "que significa \"no es un número\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "con_huecos = np.array([480.37, np.nan, 2480.84, np.nan, 95.5])\n",
    "\n",
    "print(con_huecos.mean())\n",
    "print(np.isnan(con_huecos))\n",
    "print(np.isnan(con_huecos).sum(), 'huecos')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El promedio salió `nan` 😬 Un solo hueco contamina toda la\n",
    "operación, y eso es a propósito: NumPy prefiere avisarte antes que darte un\n",
    "número que ignora datos en silencio.\n",
    "\n",
    "Hay versiones que sí lo ignoran, y las usas cuando esa es tu decisión\n",
    "consciente:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(round(np.nanmean(con_huecos), 2))\n",
    "print(np.nansum(con_huecos))\n",
    "print(round(con_huecos[~np.isnan(con_huecos)].mean(), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `~` es \"lo contrario de\", así que\n",
    "`~np.isnan(x)` son los que **no** son nulos. Se usa\n",
    "muchísimo.\n",
    "\n",
    "Y una rareza que te va a morder alguna vez:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(np.nan == np.nan)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un nulo no es igual ni a sí mismo. Por eso nunca se compara con\n",
    "`==`: se usa `np.isnan()` 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dos dimensiones"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tabla = np.array([\n",
    "    [480.37, 10],\n",
    "    [154.83, 13],\n",
    "    [2480.84, 25],\n",
    "])\n",
    "\n",
    "print(tabla.shape)\n",
    "print(tabla[0])          # primera fila\n",
    "print(tabla[:, 0])       # primera columna\n",
    "print(tabla[1, 1])       # fila 1, columna 1\n",
    "print(tabla.sum(axis=0)) # suma por columna\n",
    "print(tabla.sum(axis=1)) # suma por fila"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `axis` es el que todo el mundo confunde, incluida yo durante\n",
    "mucho tiempo. La forma que a mí me funciona para recordarlo:\n",
    "**`axis=0` aplasta las filas, así que te queda un resultado por\n",
    "columna**. Y `axis=1` al revés."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Un caso de verdad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import csv\n",
    "\n",
    "ARCHIVO = 'ventas-miss-yera.csv'\n",
    "\n",
    "montos = []\n",
    "with open(ARCHIVO, encoding='utf-8') as f:\n",
    "    for fila in csv.DictReader(f):\n",
    "        try:\n",
    "            montos.append(float(fila['monto']))\n",
    "        except ValueError:\n",
    "            montos.append(np.nan)\n",
    "\n",
    "montos = np.array(montos)\n",
    "\n",
    "print('ventas       ', len(montos))\n",
    "print('con problema ', int(np.isnan(montos).sum()))\n",
    "print('total     S/', round(np.nansum(montos), 2))\n",
    "print('promedio  S/', round(np.nanmean(montos), 2))\n",
    "print('mediana   S/', round(float(np.nanmedian(montos)), 2))\n",
    "print('sobre 1000   ', int((montos > 1000).sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para. Seiscientos doce montos con problema 😳\n",
    "\n",
    "Ese contador es la línea más importante del bloque, y por eso lo puse. Si no\n",
    "estuviera, habría reportado un total de S/1.959.990 con toda tranquilidad y me\n",
    "habría faltado la quinta parte del archivo.\n",
    "\n",
    "¿Qué tienen esos 612? Vamos a mirarlos, que es lo que siempre hay que hacer:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "problemas = []\n",
    "with open(ARCHIVO, encoding='utf-8') as f:\n",
    "    for fila in csv.DictReader(f):\n",
    "        try:\n",
    "            float(fila['monto'])\n",
    "        except ValueError:\n",
    "            problemas.append(fila['monto'])\n",
    "\n",
    "print(problemas[:5])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No era basura: son montos con **coma decimal**, como en el\n",
    "capítulo 3. Alguien exportó ese archivo desde un Excel configurado en español y\n",
    "ahí la coma es el separador decimal 🙃\n",
    "\n",
    "Con una línea más, el archivo entero entra:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos = []\n",
    "with open(ARCHIVO, encoding='utf-8') as f:\n",
    "    for fila in csv.DictReader(f):\n",
    "        try:\n",
    "            montos.append(float(fila['monto'].replace(',', '.')))\n",
    "        except ValueError:\n",
    "            montos.append(np.nan)\n",
    "\n",
    "montos = np.array(montos)\n",
    "\n",
    "print('con problema ', int(np.isnan(montos).sum()))\n",
    "print('total     S/', round(np.nansum(montos), 2))\n",
    "print('promedio  S/', round(np.nanmean(montos), 2))\n",
    "print('mediana   S/', round(float(np.nanmedian(montos)), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Casi medio millón de soles de diferencia en el total, entre el primer intento\n",
    "y este. Y lo único que cambió fue un `.replace(',', '.')` 💜\n",
    "\n",
    "Esa es la lección de verdad del capítulo, más que NumPy:\n",
    "**cuenta siempre lo que descartaste, y ve a mirarlo antes de reportar\n",
    "nada**.\n",
    "\n",
    "Y de paso, el promedio contra la mediana otra vez: 801 contra 532. Si le dices\n",
    "a comercial \"el ticket promedio es S/801\" les estás dando un número que no\n",
    "representa a la mayoría de sus ventas. La mediana es la que hay que reportar, y\n",
    "el porqué está en el\n",
    "[libro de estadística](https://missyera.com/guias/estadistica-desde-cero/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## np.where: el if que se aplica a todo de golpe"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya sabes filtrar. Ahora la otra mitad: **decidir un valor distinto según\n",
    "la condición**, sin bucle 🔀"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "muestra = np.array([480.37, 154.83, 2480.84, 524.90, 890.00])\n",
    "\n",
    "etiqueta = np.where(muestra > 1000, 'grande', 'normal')\n",
    "print(etiqueta)\n",
    "\n",
    "comision = np.where(muestra > 1000, muestra * 0.05, muestra * 0.02)\n",
    "print(comision.round(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se lee igual que un `if` de una línea: *si la condición, esto; si\n",
    "no, lo otro*. La diferencia es que lo hace para los cinco montos a la vez.\n",
    "\n",
    "Y como el resultado es otro arreglo, se pueden anidar para tres tramos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tres = np.where(muestra > 1000, 'grande',\n",
    "                np.where(muestra > 500, 'media', 'chica'))\n",
    "print(tres)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con tres tramos todavía se lee. Con cinco ya no, y ahí lo que quieres es\n",
    "`pd.cut`, que llega en el capítulo de pandas 🐼\n",
    "\n",
    "Ojo a una cosa que despista: `np.where` calcula\n",
    "**las dos ramas enteras** y luego elige. Ese\n",
    "`muestra * 0.05` se calculó para los cinco montos, aunque solo uno pase\n",
    "de mil. Con datos normales da igual; con una división de por medio, no, porque la\n",
    "rama que no usas puede dividir entre cero y avisarte igual."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La rebanada que no es una copia"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este es el que quiero que no te pase, porque no da error y te cambia los datos\n",
    "por detrás 😖"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "primeros = muestra[:3]\n",
    "\n",
    "print('antes  :', muestra)\n",
    "primeros[0] = 0\n",
    "print('despues:', muestra)\n",
    "print('comparten memoria:', np.shares_memory(muestra, primeros))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Toqué `primeros` y cambió `muestra`. Yo no toqué\n",
    "`muestra` 🫠\n",
    "\n",
    "Y aquí está la diferencia con las listas, que es justo al revés de lo que\n",
    "esperarías después del capítulo de listas. Una rebanada de lista\n",
    "(`lista[:3]`) **sí** te da una copia. Una rebanada de\n",
    "arreglo de numpy **no**: te da una ventana a los mismos datos, lo que\n",
    "se llama una vista.\n",
    "\n",
    "No es un fallo, es la razón de que numpy sea rápido: no anda copiando\n",
    "millones de números cada vez que miras un trozo. Pero tienes que saberlo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "copia = muestra[:3].copy()\n",
    "copia[1] = 999\n",
    "\n",
    "print('la copia :', copia)\n",
    "print('el origen:', muestra[:3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un `.copy()` y ya. La regla que uso: **si voy a escribir en la\n",
    "rebanada, `.copy()`. Si solo voy a leer, no**, que para leer la\n",
    "vista es mejor y más rápida 🌸\n",
    "\n",
    "Y si alguna vez tienes la duda, `np.shares_memory(a, b)` te lo dice\n",
    "sin adivinar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El entero que se da la vuelta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un arreglo tiene un tipo fijo, y ese tipo tiene un techo. Si te pasas del\n",
    "techo, no hay error: el número **se da la vuelta** 🔄"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unidades_ok = np.array([100, 120, 90], dtype=np.int8)\n",
    "\n",
    "print('tipo         :', unidades_ok.dtype)\n",
    "print('el maximo es :', np.iinfo(np.int8).max)\n",
    "print('sumadas      :', unidades_ok.sum())\n",
    "print('una a una    :', 100 + 120 + 90)\n",
    "\n",
    "doblado = unidades_ok * 2\n",
    "print('multiplicadas:', doblado, 'tipo', doblado.dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira bien las dos últimas líneas, porque son distintas y las dos son\n",
    "correctas 👀\n",
    "\n",
    "**La suma sale bien.** `sum()` se guarda el resultado\n",
    "en un tipo más grande, así que 310 cabe.\n",
    "\n",
    "**La multiplicación sale mal.** `unidades_ok * 2`\n",
    "mantiene el `int8`, y 200 no cabe en un tipo que llega a 127. Así que\n",
    "da la vuelta y sale negativo. Sin aviso, sin error, con los tres valores\n",
    "equivocados.\n",
    "\n",
    "¿Cuándo te va a pasar esto de verdad? Cuando alguien optimiza la memoria de\n",
    "una tabla enorme poniendo `int8` o `int16` a columnas que\n",
    "parecían pequeñas, y meses después alguien multiplica esa columna. El\n",
    "`int64` por defecto llega a nueve trillones y por eso nunca lo ves 🛡️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Broadcasting, o por qué a veces sí y a veces no"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "matriz = np.array([[480.37, 154.83],\n",
    "                   [2480.84, 524.90],\n",
    "                   [890.00, 320.10]])\n",
    "print('forma:', matriz.shape)\n",
    "print((matriz * 1.18).round(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un número suelto se aplica a las seis casillas. Eso ya lo viste. Lo que no\n",
    "sabías es que también funciona con un arreglo, **si las formas\n",
    "encajan**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "descuentos = np.array([0.10, 0.05])\n",
    "print('un descuento por columna:', descuentos.shape)\n",
    "print((matriz * (1 - descuentos)).round(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La tabla es `(3, 2)` y los descuentos son `(2,)`. numpy\n",
    "estira los dos descuentos hacia abajo, para las tres filas, y aplica el primero a\n",
    "la primera columna y el segundo a la segunda 📐\n",
    "\n",
    "Ahora con tres, que es lo que harías si pensaras \"un descuento por fila\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    matriz * np.array([0.1, 0.2, 0.3])\n",
    "except ValueError as e:\n",
    "    print('y con tres:', e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Falla, y el mensaje te da las dos formas. numpy compara de derecha a izquierda:\n",
    "la última dimensión de la tabla es 2 y la del arreglo es 3, y ni coinciden ni una\n",
    "de las dos es 1. No hay manera de encajarlas.\n",
    "\n",
    "Para hacer un descuento por fila hay que decírselo con una columna de verdad:\n",
    "`np.array([0.1, 0.2, 0.3]).reshape(3, 1)`. Ahí la forma es\n",
    "`(3, 1)`, ese 1 se estira a las dos columnas, y sale lo que\n",
    "querías 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Tu primer arreglo\n",
    "\n",
    "Crea un arreglo con cinco montos, aplícale el IGV y\n",
    "redondea."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El arreglo que se volvió texto\n",
    "\n",
    "Crea un arreglo mezclando números y un texto, y mira qué\n",
    "pasó con el tipo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Filtrar por rango\n",
    "\n",
    "De un arreglo de montos, saca solo los que están entre 200 y\n",
    "1000."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El error de los paréntesis\n",
    "\n",
    "Provoca a propósito el error de combinar condiciones sin\n",
    "paréntesis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Contar sin bucle\n",
    "\n",
    "Cuenta cuántos montos superan los S/500, usando la máscara."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Los huecos\n",
    "\n",
    "Con un arreglo que tenga dos nulos, calcula el promedio de\n",
    "dos formas: la que se contamina y la que no."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. La posición del máximo\n",
    "\n",
    "Encuentra el monto más alto y en qué posición está."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Sumar por columna\n",
    "\n",
    "Con una tabla de dos columnas (monto y unidades), saca el\n",
    "total de cada una."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. El nulo que no es igual a sí mismo\n",
    "\n",
    "Comprueba que `np.nan == np.nan` es falso y\n",
    "cuenta los nulos de la forma correcta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 9\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Normalizar\n",
    "\n",
    "Convierte un arreglo de montos a una escala de 0 a 1, donde\n",
    "0 es el mínimo y 1 el máximo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 10\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Comisión por tramos, sin un solo if\n",
    "\n",
    "Aplica 5% a las ventas de más de mil, 3% a las de más de\n",
    "quinientos y 2% al resto, sobre los tres mil montos del archivo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 11\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. La vista que te cambia el original\n",
    "\n",
    "Corta un trozo del arreglo grande, tócalo, y mira qué pasó\n",
    "con el total."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 12\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 13. El tipo pequeño que se da la vuelta\n",
    "\n",
    "Guarda unas unidades en `int16`, multiplícalas, y\n",
    "busca el punto donde deja de caber."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 13\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 14. Estirar bien y estirar mal\n",
    "\n",
    "Aplica un descuento por columna y otro por fila a la misma\n",
    "tabla."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 14\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El trozo que no es una copia"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora la diferencia entre NumPy y las listas que más caro se paga. Si vienes de listas, tu intuición te va a llevar justo al lado equivocado 🪞"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Sacas un trozo del arreglo para trabajar con él sin tocar el original, como harías con una lista. Modificas el trozo.\n",
    "\n",
    "```\n",
    "orig = np.array([1, 2, 3, 4, 5])\n",
    "trozo = orig[1:4]\n",
    "\n",
    "trozo[0] = 999\n",
    "\n",
    "print(orig)\n",
    "# [  1 999   3   4   5]\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tienes que multiplicar por 1,18 un millón de números. ¿Qué escribes?\n",
    "\n",
    "a) La operación sobre el array entero, sin bucle\n",
    "\n",
    "b) Un for que recorra el millón\n",
    "\n",
    "c) Un for, pero con range para que sea más rápido\n",
    "\n",
    "d) Una lista por comprensión"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🔢 Un arreglo tiene un solo tipo, y por eso es rápido.\n",
    "\n",
    "- ⚡ Vectorizar es aplicar la operación a todo de golpe, sin\n",
    "`for`.\n",
    "\n",
    "- 🎭 Una condición sobre un arreglo devuelve una máscara de True y False.\n",
    "\n",
    "- 🔗 Para combinar condiciones: `&` y `|`, con\n",
    "paréntesis en cada una.\n",
    "\n",
    "- 🕳️ Un solo `nan` contamina la operación: usa\n",
    "`np.nanmean` y cuenta los huecos.\n",
    "\n",
    "- 📐 `axis=0` aplasta filas y deja un resultado por columna.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En NumPy un trozo no es una copia. Es la misma memoria mirada por una\n",
    "ventana."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y con esto ya tienes todo lo que hace falta para el capítulo 12, que es pandas\n",
    "y es, sin exagerar, el capítulo por el que la mayoría de la gente aprende Python.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 11 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/numpy/\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
}
