{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b2797579",
   "metadata": {},
   "source": [
    "[← Anterior](07_clases.ipynb) · [Índice](README.md) · [Siguiente →](09_graficos.ipynb)\n",
    "\n",
    "# 08 · NumPy\n",
    "\n",
    "**Audiencia.** Personas que ya han visto algo de programación y quieren repasar Python de forma práctica.  \n",
    "**Prerrequisitos.** Secuencias, funciones, *slicing* y operaciones numéricas básicas. Requiere `numpy`.  \n",
    "**Duración orientativa.** 60–90 minutos, incluida la práctica.\n",
    "\n",
    "## Objetivos de aprendizaje\n",
    "\n",
    "- Crear arrays homogéneos e inspeccionar `shape`, `ndim` y `dtype`.\n",
    "- Indexar, filtrar, remodelar y combinar arrays.\n",
    "- Aplicar vectorización, *broadcasting* y reducciones por ejes.\n",
    "- Usar generación aleatoria reproducible y álgebra lineal básica.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04d21548",
   "metadata": {},
   "source": [
    "## Itinerario\n",
    "\n",
    "1. Arrays\n",
    "2. Creación\n",
    "3. Indexación\n",
    "4. Vectorización\n",
    "5. Broadcasting\n",
    "6. Ejes\n",
    "7. Forma\n",
    "8. Azar y álgebra\n",
    "9. Práctica\n",
    "\n",
    "> **Cómo trabajar:** ejecuta las celdas en orden, predice el resultado antes de verlo y modifica los ejemplos. Cada bloque termina con un ejercicio, un punto de partida y una solución posible.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77574fe1",
   "metadata": {},
   "source": [
    "## 1. El array de NumPy\n",
    "\n",
    "Un `ndarray` es un bloque n-dimensional de valores normalmente homogéneos. Frente a una lista, almacena números de forma compacta y ofrece operaciones vectorizadas. `shape` describe tamaños por dimensión; `ndim`, el número de dimensiones; `dtype`, el tipo almacenado.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5d06d7e7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.705906Z",
     "iopub.status.busy": "2026-08-02T22:29:56.705801Z",
     "iopub.status.idle": "2026-08-02T22:29:56.818250Z",
     "shell.execute_reply": "2026-08-02T22:29:56.817992Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NumPy 1.26.4\n",
      "[[1. 2. 3.]\n",
      " [4. 5. 6.]]\n",
      "shape: (2, 3) ndim: 2 dtype: float64 size: 6\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "print(\"NumPy\", np.__version__)\n",
    "matriz = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64)\n",
    "print(matriz)\n",
    "print(\"shape:\", matriz.shape, \"ndim:\", matriz.ndim, \"dtype:\", matriz.dtype, \"size:\", matriz.size)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6752e33",
   "metadata": {},
   "source": [
    "## 2. Creación y tipos\n",
    "\n",
    "Usa `array` para datos existentes; `zeros`, `ones` y `full` para inicializar; `arange` para pasos enteros y `linspace` para un número exacto de puntos. NumPy promociona tipos para que el array pueda contener todos los valores.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "0a5a73d5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.819723Z",
     "iopub.status.busy": "2026-08-02T22:29:56.819605Z",
     "iopub.status.idle": "2026-08-02T22:29:56.822078Z",
     "shell.execute_reply": "2026-08-02T22:29:56.821889Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ceros\n",
      "→\n",
      "[[0. 0. 0.]\n",
      " [0. 0. 0.]]\n",
      "unos\n",
      "→\n",
      "[1 1 1 1]\n",
      "relleno\n",
      "→\n",
      "[[7 7]\n",
      " [7 7]]\n",
      "rango\n",
      "→\n",
      "[0 2 4 6 8]\n",
      "lineal\n",
      "→\n",
      "[0.   0.25 0.5  0.75 1.  ]\n",
      "identidad\n",
      "→\n",
      "[[1. 0. 0.]\n",
      " [0. 1. 0.]\n",
      " [0. 0. 1.]]\n"
     ]
    }
   ],
   "source": [
    "creaciones = {\n",
    "    \"ceros\": np.zeros((2, 3)),\n",
    "    \"unos\": np.ones(4, dtype=np.int32),\n",
    "    \"relleno\": np.full((2, 2), 7),\n",
    "    \"rango\": np.arange(0, 10, 2),\n",
    "    \"lineal\": np.linspace(0, 1, 5),\n",
    "    \"identidad\": np.eye(3),\n",
    "}\n",
    "for nombre, array in creaciones.items():\n",
    "    print(nombre, \"→\", array, sep=\"\\n\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eab2cd85",
   "metadata": {},
   "source": [
    "## 3. Indexación, *slicing* y vistas\n",
    "\n",
    "Se usa un índice por eje: `a[fila, columna]`. Los *slices* suelen devolver **vistas**: comparten memoria con el original. `.copy()` crea datos independientes.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cee74af8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.823391Z",
     "iopub.status.busy": "2026-08-02T22:29:56.823306Z",
     "iopub.status.idle": "2026-08-02T22:29:56.825365Z",
     "shell.execute_reply": "2026-08-02T22:29:56.825148Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]]\n",
      "Elemento: 6\n",
      "Fila: [4 5 6 7]\n",
      "Bloque:\n",
      " [[1 2]\n",
      " [5 6]]\n",
      "Original tras editar vista/copia:\n",
      " [[99  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]]\n"
     ]
    }
   ],
   "source": [
    "datos = np.arange(12).reshape(3, 4)\n",
    "print(datos)\n",
    "print(\"Elemento:\", datos[1, 2])\n",
    "print(\"Fila:\", datos[1])\n",
    "print(\"Bloque:\\n\", datos[:2, 1:3])\n",
    "\n",
    "vista = datos[:, 0]\n",
    "copia = datos[:, 1].copy()\n",
    "vista[0] = 99\n",
    "copia[0] = -1\n",
    "print(\"Original tras editar vista/copia:\\n\", datos)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5d4c465",
   "metadata": {},
   "source": [
    "La indexación avanzada con listas de posiciones y máscaras booleanas devuelve selecciones flexibles. Una máscara tiene la misma forma compatible y se puede combinar con `&`, `|` y `~`; usa paréntesis alrededor de cada comparación.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "01c06b18",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.826605Z",
     "iopub.status.busy": "2026-08-02T22:29:56.826517Z",
     "iopub.status.idle": "2026-08-02T22:29:56.828610Z",
     "shell.execute_reply": "2026-08-02T22:29:56.828420Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[12 18]\n",
      "[12 20 18]\n",
      "[12  0  7 20  5 18]\n"
     ]
    }
   ],
   "source": [
    "valores = np.array([12, -3, 7, 20, 5, 18])\n",
    "mascara = (valores >= 10) & (valores < 20)\n",
    "print(valores[mascara])\n",
    "print(valores[[0, 3, 5]])\n",
    "\n",
    "ajustados = np.where(valores < 0, 0, valores)\n",
    "print(ajustados)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "683ba731",
   "metadata": {},
   "source": [
    "## 4. Vectorización y funciones universales\n",
    "\n",
    "Los operadores actúan elemento a elemento sin bucles Python explícitos. Las *ufuncs* como `sqrt`, `exp`, `sin` o `maximum` están implementadas eficientemente y aceptan arrays.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d82fba92",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.829848Z",
     "iopub.status.busy": "2026-08-02T22:29:56.829762Z",
     "iopub.status.idle": "2026-08-02T22:29:56.831784Z",
     "shell.execute_reply": "2026-08-02T22:29:56.831553Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[14.  32.  68.  98.6]\n",
      "[0.    0.707 1.    0.707 0.   ]\n"
     ]
    }
   ],
   "source": [
    "celsius = np.array([-10.0, 0.0, 20.0, 37.0])\n",
    "fahrenheit = celsius * 9 / 5 + 32\n",
    "print(fahrenheit)\n",
    "\n",
    "angulos = np.linspace(0, np.pi, 5)\n",
    "print(np.round(np.sin(angulos), 3))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ef802f7",
   "metadata": {},
   "source": [
    "## 5. *Broadcasting*\n",
    "\n",
    "NumPy combina formas compatibles comparando dimensiones desde la derecha. Son compatibles si coinciden o una vale 1. Esto evita replicar datos manualmente.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "65b956ce",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.833059Z",
     "iopub.status.busy": "2026-08-02T22:29:56.832971Z",
     "iopub.status.idle": "2026-08-02T22:29:56.834703Z",
     "shell.execute_reply": "2026-08-02T22:29:56.834503Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[105.  137.   86. ]\n",
      " [ 75.   77.5  94. ]]\n"
     ]
    }
   ],
   "source": [
    "ventas = np.array([\n",
    "    [100, 120, 90],\n",
    "    [80, 75, 110],\n",
    "])\n",
    "factores_mes = np.array([1.0, 1.1, 0.9])\n",
    "ajustadas = ventas * factores_mes\n",
    "\n",
    "sesgo_por_tienda = np.array([[5], [-5]])\n",
    "corregidas = ajustadas + sesgo_por_tienda\n",
    "print(corregidas)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3794780d",
   "metadata": {},
   "source": [
    "## 6. Agregaciones y ejes\n",
    "\n",
    "Una reducción resume datos. Sin `axis` opera sobre todo el array; `axis=0` elimina el eje de filas y resume por columnas; `axis=1` elimina columnas y resume por filas. `keepdims=True` conserva una dimensión de tamaño 1 para facilitar *broadcasting* posterior.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "0a39e0d2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.835942Z",
     "iopub.status.busy": "2026-08-02T22:29:56.835865Z",
     "iopub.status.idle": "2026-08-02T22:29:56.838297Z",
     "shell.execute_reply": "2026-08-02T22:29:56.838077Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Media global: 8.277777777777779\n",
      "Media por prueba: [7.83333333 8.33333333 8.66666667]\n",
      "Media por persona: [8.16666667 7.16666667 9.5       ]\n",
      "Máximos por prueba: [ 9.5  9.  10. ]\n"
     ]
    }
   ],
   "source": [
    "notas = np.array([\n",
    "    [8.0, 7.5, 9.0],\n",
    "    [6.0, 8.5, 7.0],\n",
    "    [9.5, 9.0, 10.0],\n",
    "])\n",
    "print(\"Media global:\", notas.mean())\n",
    "print(\"Media por prueba:\", notas.mean(axis=0))\n",
    "print(\"Media por persona:\", notas.mean(axis=1))\n",
    "print(\"Máximos por prueba:\", notas.max(axis=0))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f8d9000",
   "metadata": {},
   "source": [
    "## 7. Cambiar forma y combinar\n",
    "\n",
    "`reshape` cambia la vista lógica si el número total de elementos coincide. `T` transpone. `concatenate` une sobre un eje existente; `stack` crea un eje nuevo.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "5a9dee8e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.839583Z",
     "iopub.status.busy": "2026-08-02T22:29:56.839492Z",
     "iopub.status.idle": "2026-08-02T22:29:56.841790Z",
     "shell.execute_reply": "2026-08-02T22:29:56.841524Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 1  2  3  4]\n",
      " [ 5  6  7  8]\n",
      " [ 9 10 11 12]]\n",
      "Transpuesta:\n",
      " [[ 1  5  9]\n",
      " [ 2  6 10]\n",
      " [ 3  7 11]\n",
      " [ 4  8 12]]\n",
      "Concatenar: [1 2 3 4 5 6]\n",
      "Apilar:\n",
      " [[1 2 3]\n",
      " [4 5 6]]\n"
     ]
    }
   ],
   "source": [
    "base = np.arange(1, 13)\n",
    "tabla = base.reshape(3, 4)\n",
    "print(tabla)\n",
    "print(\"Transpuesta:\\n\", tabla.T)\n",
    "\n",
    "a = np.array([1, 2, 3])\n",
    "b = np.array([4, 5, 6])\n",
    "print(\"Concatenar:\", np.concatenate([a, b]))\n",
    "print(\"Apilar:\\n\", np.stack([a, b]))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "140ccb68",
   "metadata": {},
   "source": [
    "## 8. Azar reproducible y álgebra lineal\n",
    "\n",
    "Usa `np.random.default_rng(semilla)` en código nuevo. Una semilla reproduce los ejemplos. El operador `@` realiza producto matricial; `np.linalg.solve` resuelve sistemas sin calcular una inversa explícita.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "e496b506",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.843113Z",
     "iopub.status.busy": "2026-08-02T22:29:56.843022Z",
     "iopub.status.idle": "2026-08-02T22:29:56.846177Z",
     "shell.execute_reply": "2026-08-02T22:29:56.845929Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 8.41 10.48  6.21 12.79]\n",
      " [11.28  9.42  9.38 10.61]\n",
      " [ 9.46  9.55 11.44 11.03]]\n",
      "Solución: [2.2 3.6]\n",
      "Comprobación A @ x: [ 8. 13.]\n"
     ]
    }
   ],
   "source": [
    "rng = np.random.default_rng(2026)\n",
    "muestra = rng.normal(loc=10, scale=2, size=(3, 4))\n",
    "print(np.round(muestra, 2))\n",
    "\n",
    "A = np.array([[2.0, 1.0], [1.0, 3.0]])\n",
    "b = np.array([8.0, 13.0])\n",
    "solucion = np.linalg.solve(A, b)\n",
    "print(\"Solución:\", solucion)\n",
    "print(\"Comprobación A @ x:\", A @ solucion)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec491910",
   "metadata": {},
   "source": [
    "## 9. Valores ausentes y persistencia\n",
    "\n",
    "`np.nan` representa un valor flotante ausente. Funciones como `nanmean` lo ignoran de forma explícita. `np.save` conserva forma y tipo; aquí usamos un flujo en memoria.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0852f2aa",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.847410Z",
     "iopub.status.busy": "2026-08-02T22:29:56.847327Z",
     "iopub.status.idle": "2026-08-02T22:29:56.849655Z",
     "shell.execute_reply": "2026-08-02T22:29:56.849420Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Media ignorando NaN: 11.166666666666666\n",
      "[10.   nan 12.5 11. ]\n"
     ]
    }
   ],
   "source": [
    "from io import BytesIO\n",
    "\n",
    "mediciones = np.array([10.0, np.nan, 12.5, 11.0])\n",
    "print(\"Media ignorando NaN:\", np.nanmean(mediciones))\n",
    "\n",
    "buffer = BytesIO()\n",
    "np.save(buffer, mediciones)\n",
    "buffer.seek(0)\n",
    "recuperadas = np.load(buffer)\n",
    "print(recuperadas)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8398e10",
   "metadata": {},
   "source": [
    "## Errores habituales\n",
    "\n",
    "- Confundir `*` (elemento a elemento) con `@` (producto matricial).\n",
    "- Olvidar que un *slice* puede ser una vista y modificar el original.\n",
    "- Usar `and`/`or` con arrays; usa `&`/`|` y paréntesis.\n",
    "- Ignorar `dtype` y provocar truncamiento o desbordamiento.\n",
    "- Crear bucles Python donde basta una operación vectorizada.\n",
    "\n",
    "**Extensión opcional:** explora `einsum`, arrays estructurados y perfiles de memoria.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d694b870",
   "metadata": {},
   "source": [
    "## Práctica · Estandarizar columnas\n",
    "\n",
    "Implementa `estandarizar(matriz)` para que cada columna tenga media aproximada 0 y desviación típica 1:\n",
    "\n",
    "1. calcula por columnas (`axis=0`);\n",
    "2. conserva dimensiones para *broadcasting*;\n",
    "3. rechaza columnas con desviación cero.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "55050250",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.850962Z",
     "iopub.status.busy": "2026-08-02T22:29:56.850879Z",
     "iopub.status.idle": "2026-08-02T22:29:56.852510Z",
     "shell.execute_reply": "2026-08-02T22:29:56.852292Z"
    },
    "tags": [
     "ejercicio"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def estandarizar(matriz):\n",
    "    # TODO: convierte a float, calcula media/desviación y estandariza.\n",
    "    pass\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "bdc3e841",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:56.853660Z",
     "iopub.status.busy": "2026-08-02T22:29:56.853579Z",
     "iopub.status.idle": "2026-08-02T22:29:56.856009Z",
     "shell.execute_reply": "2026-08-02T22:29:56.855795Z"
    },
    "tags": [
     "solucion"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[-1.342 -1.342]\n",
      " [-0.447 -0.447]\n",
      " [ 0.447  0.447]\n",
      " [ 1.342  1.342]]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "def estandarizar(matriz):\n",
    "    datos = np.asarray(matriz, dtype=float)\n",
    "    medias = datos.mean(axis=0, keepdims=True)\n",
    "    desviaciones = datos.std(axis=0, keepdims=True)\n",
    "    if np.any(desviaciones == 0):\n",
    "        raise ValueError(\"no se puede estandarizar una columna constante\")\n",
    "    return (datos - medias) / desviaciones\n",
    "\n",
    "datos = np.array([[1, 10], [2, 20], [3, 30], [4, 40]])\n",
    "normalizados = estandarizar(datos)\n",
    "print(np.round(normalizados, 3))\n",
    "assert np.allclose(normalizados.mean(axis=0), 0)\n",
    "assert np.allclose(normalizados.std(axis=0), 1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69238443",
   "metadata": {},
   "source": [
    "            ## Comprobación rápida\n",
    "\n",
    "            - [ ] Puedo explicar forma, dimensiones y tipo de un array.\n",
    "- [ ] Sé aplicar máscaras, operaciones vectorizadas y broadcasting.\n",
    "- [ ] Elijo correctamente el eje de una reducción.\n",
    "\n",
    "            [← Anterior](07_clases.ipynb) · [Volver al índice](README.md) · [Siguiente →](09_graficos.ipynb)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
