{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'>&copy; 2024 Francisca Cattan.</font>\n",
    "<br>\n",
    "<font size='1'> Modificado 2026-1 por Equipo Docente IIC2233</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Introducción a NumPy en Python](#Introducción-a-NumPy-en-Python)\n",
    "    1. [Importando NumPy](#Importando-NumPy)\n",
    "2. [Arrays](#Arrays)\n",
    "    1. [Creación de Arrays](#Creación-de-Arrays)\n",
    "3. [Funciones de NumPy](#Funciones-de-NumPy)\n",
    "4. [Operaciones básicas](#Operaciones-básicas)\n",
    "    1. [Operaciones aritméticas](#Operaciones-aritméticas)\n",
    "    2. [Operaciones con escalares](#Operaciones-con-escalares)\n",
    "    3. [Funciones universales](#Funciones-universales)\n",
    "    4. [Operaciones lógicas y comparaciones](#Operaciones-lógicas-y-comparaciones)\n",
    "    5. [Indexación y Slicing](#Indexación-y-Slicing)\n",
    "    6. [Broadcasting](#Broadcasting)\n",
    "    7. [Concatenación y división](#Concatenación-y-división)\n",
    "5. [Análisis y estadísticas](#Análisis-y-estadísticas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introducción a NumPy en Python\n",
    "\n",
    "NumPy es una biblioteca fundamental para el cálculo científico en Python. Proporciona soporte para _arrays_ y matrices multidimensionales, junto con una colección de funciones matemáticas de alto nivel para operar con estos _arrays_ de manera eficiente. En este notebook, exploraremos los usos más comunes de NumPy. Puedes expandir buscando más información en [su documentación](http://www.numpy.org/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Importando NumPy\n",
    "Para utilizar NumPy, primero debemos importarlo. Por convención, se suele importar unsando el alias `np`.\n",
    "\n",
    "El uso de as `np` es una práctica estándar que crea un alias para la biblioteca. Esto simplifica el código al permitirnos acceder a las funciones y clases de NumPy utilizando el prefijo corto `np` en lugar de `numpy`. Por ejemplo, en lugar de escribir `numpy.array()`, simplemente escribimos `np.array()`. Esta convención no solo hace que el código sea más limpio y fácil de leer, sino que también facilita la colaboración y el intercambio de código, ya que es ampliamente reconocida y utilizada en la comunidad de Python. Además, si estás trabajando con múltiples bibliotecas que tienen funciones con nombres similares, el uso de alias ayuda a evitar conflictos y a mantener el código organizado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En caso de que no tengas instalado el módulo, debe correr en la terminal el comando `python3 -m pip install numpy`. Recuerda que si `python3` no funciona, probar con `python`, `py` o `py3`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Arrays\n",
    "\n",
    "**¿Qué es un _array_?**\n",
    "Un _array_ (o arreglo) es una estructura de datos que almacena una colección de elementos, todos del mismo tipo de dato, en posiciones contiguas de memoria. En el contexto de NumPy, un **array** es una matriz multidimensional que permite realizar operaciones matemáticas y lógicas de manera eficiente y rápida.\n",
    "\n",
    "**¿Por qué usar _arrays_ de NumPy en lugar de listas de Python?**\n",
    "Aunque las listas de Python son muy flexibles y pueden contener elementos de diferentes tipos de datos, no están optimizadas para operaciones numéricas y cálculos de alto rendimiento. A continuación, se detallan las ventajas de utilizar _arrays_ de NumPy en comparación a las listas de Python vistas en el curso:\n",
    "| Característica           | Arrays de NumPy                                                                 | Listas de Python                                                                 |\n",
    "|---------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------|\n",
    "| **Eficiencia en memoria** | Almacenan los datos de manera contigua y homogénea en memoria, reduciendo el consumo de espacio y mejorando la velocidad de acceso. | Almacenan referencias a objetos individuales, lo que aumenta el consumo de memoria, especialmente con grandes conjuntos de datos. |\n",
    "| **Velocidad de cálculo**  | Las operaciones matemáticas están implementadas en código compilado (generalmente en C), lo que permite realizar cálculos mucho más rápidos. | Las operaciones requieren bucles explícitos en Python, lo que es significativamente más lento. |\n",
    "| **Operaciones vectorizadas** | Permiten aplicar operaciones a todos los elementos sin necesidad de bucles explícitos. | Requieren iterar manualmente sobre cada elemento. |\n",
    "\n",
    "A continuación, veremos un ejemplo que calcula la suma de dos listas o _arrays_ de gran tamaño. Para ello contaremos el tiempo de procesamiento usando ambas estructuras de forma comparativa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tiempo con listas de Python: 0.05272054672241211 segundos\n",
      "Tiempo con arrays de NumPy: 0.0023980140686035156 segundos\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import time\n",
    "\n",
    "tamano = 1000000\n",
    "\n",
    "lista_a = list(range(tamano))\n",
    "lista_b = list(range(tamano))\n",
    "\n",
    "inicio = time.time()\n",
    "resultado_lista = [a + b for a, b in zip(lista_a, lista_b)]\n",
    "fin = time.time()\n",
    "print(\"Tiempo con listas de Python:\", fin - inicio, \"segundos\")\n",
    "\n",
    "array_a = np.arange(tamano)\n",
    "array_b = np.arange(tamano)\n",
    "\n",
    "inicio = time.time()\n",
    "resultado_array = array_a + array_b\n",
    "fin = time.time()\n",
    "print(\"Tiempo con arrays de NumPy:\", fin - inicio, \"segundos\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creación de Arrays"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podemos crear un _array_ de NumPy a partir de una **lista** o **tupla** de Python utilizando la función `np.array()`. Esta función toma como entrada un objeto que se puede iterar (como una lista o tupla) y devuelve un _array_ de NumPy que contiene los mismos elementos. Esto es especialmente útil para convertir datos existentes en estructuras más eficientes para cálculos numéricos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array creado a partir de una lista: [1 2 3 4 5]\n"
     ]
    }
   ],
   "source": [
    "lista = [1, 2, 3, 4, 5]\n",
    "array = np.array(lista)\n",
    "print(\"Array creado a partir de una lista:\", array)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A continuación veremos otro ejemplo, pero creando un _array_ a partir de una tupla."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array creado a partir de una tupla: [10 20 30 40 50]\n"
     ]
    }
   ],
   "source": [
    "tupla = (10, 20, 30, 40, 50)\n",
    "array = np.array(tupla)\n",
    "print(\"Array creado a partir de una tupla:\", array)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Algo importante a notar es que al crear un _array_, NumPy intenta inferir el tipo de datos (`dtype`) más adecuado según los elementos proporcionados. Si todos los elementos son enteros, el _array_ tendrá tipo entero; si hay flotantes, el _array_ será de tipo flotante."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array con tipos mixtos: [1.   2.5  3.   4.75]\n",
      "Tipo de datos del array: float64\n"
     ]
    }
   ],
   "source": [
    "lista_mixta = [1, 2.5, 3, 4.75]\n",
    "array_mixto = np.array(lista_mixta)\n",
    "print(\"Array con tipos mixtos:\", array_mixto)\n",
    "print(\"Tipo de datos del array:\", array_mixto.dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podríamos, si quisiéramos, controlar el tipo de datos del _array_ resultante, puedes utilizar el parámetro `dtype` en la función `np.array()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array con tipo de datos float: [1. 2. 3. 4. 5.]\n",
      "Tipo de datos del array: float64\n"
     ]
    }
   ],
   "source": [
    "lista_enteros = [1, 2, 3, 4, 5]\n",
    "array_float = np.array(lista_enteros, dtype=float)\n",
    "print(\"Array con tipo de datos float:\", array_float)\n",
    "print(\"Tipo de datos del array:\", array_float.dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "También podemos crear _arrays_ multidimensionales anidando listas o tuplas. En este caso lo utilizaremos para simular una matriz desde una **lista de listas**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 2 3]\n",
      " [4 5 6]]\n"
     ]
    }
   ],
   "source": [
    "matriz = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "print(matriz)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Funciones de NumPy\n",
    "NumPy ofrece varias funciones para crear _arrays_ de manera eficiente.\n",
    "- `np.arange()`: Similar a `range()`, pero devuelve un _array_.\n",
    "- `np.linspace()`: Genera números equiespaciados en un intervalo.\n",
    "- `np.zeros()` y `np.ones()`: Crea _arrays_ de ceros o unos.\n",
    "- `np.eye()`: Crea una matriz identidad.\n",
    "- `np.random`: Genera _arrays_ con números aleatorios."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 2 4 6 8]\n"
     ]
    }
   ],
   "source": [
    "array = np.arange(0, 10, 2)\n",
    "print(array)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.   0.25 0.5  0.75 1.  ]\n"
     ]
    }
   ],
   "source": [
    "array = np.linspace(0, 1, 5)\n",
    "print(array)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Zeros:\n",
      " [[0. 0. 0.]\n",
      " [0. 0. 0.]\n",
      " [0. 0. 0.]]\n",
      "Ones:\n",
      " [[1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]]\n"
     ]
    }
   ],
   "source": [
    "zeros = np.zeros((3, 3))\n",
    "ones = np.ones((2, 4))\n",
    "print(\"Zeros:\\n\", zeros)\n",
    "print(\"Ones:\\n\", ones)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1. 0. 0. 0.]\n",
      " [0. 1. 0. 0.]\n",
      " [0. 0. 1. 0.]\n",
      " [0. 0. 0. 1.]]\n"
     ]
    }
   ],
   "source": [
    "identidad = np.eye(4)\n",
    "print(identidad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.54974014 0.8672938  0.85799574 0.25860653 0.8141549 ]\n"
     ]
    }
   ],
   "source": [
    "aleatorios = np.random.rand(5)\n",
    "print(aleatorios)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Operaciones básicas\n",
    "NumPy permite realizar operaciones matemáticas y lógicas sobre _arrays_ de manera eficiente y sencilla. Estas operaciones pueden ser elemento por elemento o involucrar operaciones matriciales más complejas.\n",
    "\n",
    "## Operaciones aritméticas\n",
    "\n",
    "Las operaciones aritméticas básicas en NumPy se aplican elemento por elemento, lo que significa que la operación se realiza entre elementos correspondientes de los _arrays_. Eso si, es importante considerar que los _arrays_ involucrados en a operación deben tener la misma forma (*shape*)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Suma: [5 7 9]\n",
      "Resta: [-3 -3 -3]\n",
      "Multiplicación: [ 4 10 18]\n",
      "División: [0.25 0.4  0.5 ]\n",
      "Potencia: [1 4 9]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3])\n",
    "b = np.array([4, 5, 6])\n",
    "\n",
    "print(\"Suma:\", a + b)\n",
    "print(\"Resta:\", a - b)\n",
    "print(\"Multiplicación:\", a * b)\n",
    "print(\"División:\", a / b)\n",
    "print(\"Potencia:\", a ** 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Operaciones con escalares\n",
    "Puedes realizar operaciones entre un _array_ y un escalar (un solo número). En este caso, el escalar se aplica a cada elemento del _array_."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Suma con escalar: [11 12 13]\n",
      "Multiplicación con escalar: [3 6 9]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3])\n",
    "\n",
    "print(\"Suma con escalar:\", a + 10)\n",
    "print(\"Multiplicación con escalar:\", a * 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Funciones universales\n",
    "Las funciones universales de NumPy son funciones que se aplican elemento por elemento sobre _arrays_. Incluyen funciones matemáticas como seno, coseno, logaritmo, exponencial, entre otras."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Seno: [0.0000000e+00 1.0000000e+00 1.2246468e-16]\n",
      "Coseno: [ 1.000000e+00  6.123234e-17 -1.000000e+00]\n",
      "Exponencial: [ 1.          4.81047738 23.14069263]\n",
      "Logaritmo: [0.         0.94421571 1.42108041]\n"
     ]
    }
   ],
   "source": [
    "array = np.array([0, np.pi/2, np.pi])\n",
    "\n",
    "print(\"Seno:\", np.sin(array))\n",
    "print(\"Coseno:\", np.cos(array))\n",
    "print(\"Exponencial:\", np.exp(array))\n",
    "print(\"Logaritmo:\", np.log(array + 1))  # para evitar log(0) D:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Operaciones lógicas y comparaciones\n",
    "NumPy permite realizar operaciones lógicas y comparaciones entre _arrays_. Algo interesante es que estas operaciones retornan _arrays_ booleanos, como observaremos en el siguiente ejemplo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Igualdad: [False False False False]\n",
      "Mayor que: [False False  True  True]\n",
      "Menor o igual que: [ True  True False False]\n",
      "Condición lógica: [False False  True  True]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3, 4])\n",
    "b = np.array([4, 3, 2, 1])\n",
    "\n",
    "print(\"Igualdad:\", a == b)\n",
    "print(\"Mayor que:\", a > b)\n",
    "print(\"Menor o igual que:\", a <= b)\n",
    "print(\"Condición lógica:\", (a > 2) & (b < 3)) # se usa & y |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Indexación y Slicing\n",
    "La indexación y el _slicing_ en NumPy permiten acceder y modificar elementos o subconjuntos de un _array_."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Elemento en posición 0: 10\n",
      "Último elemento: 50\n",
      "Slice de 1 a 3: [20 30 40]\n",
      "Array modificado: [100  20  30  40  50]\n"
     ]
    }
   ],
   "source": [
    "array = np.array([10, 20, 30, 40, 50])\n",
    "\n",
    "print(\"Elemento en posición 0:\", array[0])\n",
    "print(\"Último elemento:\", array[-1])\n",
    "print(\"Slice de 1 a 3:\", array[1:4])\n",
    "\n",
    "array[0] = 100\n",
    "print(\"Array modificado:\", array)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Y qué ocurre en _arrays_ multidimensionales? Es importante en este caso especificar índices para cada dimensión. Puedes utilizar el slicing (`:`) para seleccionar rangos de índices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Elemento en fila 0, columna 1: 2\n",
      "Primera fila: [1 2 3]\n",
      "Tercera columna: [3 6]\n",
      "Suma por columnas: [5 7 9]\n",
      "Suma por filas: [ 6 15]\n"
     ]
    }
   ],
   "source": [
    "matriz = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "print(\"Elemento en fila 0, columna 1:\", matriz[0, 1])\n",
    "print(\"Primera fila:\", matriz[0, :])\n",
    "print(\"Tercera columna:\", matriz[:, 2])\n",
    "print(\"Suma por columnas:\", np.sum(matriz, axis=0))\n",
    "print(\"Suma por filas:\", np.sum(matriz, axis=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Broadcasting\n",
    "El _broadcasting_ es una poderosa característica de NumPy que permite realizar operaciones entre _arrays_ de diferentes dimensiones y formas, siempre que sean compatibles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Resultado del broadcasting:\n",
      " [[11 12 13]\n",
      " [21 22 23]\n",
      " [31 32 33]]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3])\n",
    "matriz = np.array([[10], [20], [30]])\n",
    "\n",
    "print(\"Resultado del broadcasting:\\n\", matriz + a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En este ejemplo, el _array_ `a` se expande para que coincida con la forma de `matriz`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Resultado del broadcasting:\n",
      " [[11 12 13 14 15 16 17]\n",
      " [21 22 23 24 25 26 27]]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3, 4, 5, 6, 7])\n",
    "matriz = np.array([[10], [20]])\n",
    "\n",
    "print(\"Resultado del broadcasting:\\n\", a + matriz) # el orden no afecta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Concatenación y división"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Arrays concatenados: [1 2 3 4 5 6]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3])\n",
    "b = np.array([4, 5, 6])\n",
    "\n",
    "concatenado = np.concatenate((a, b))\n",
    "print(\"Arrays concatenados:\", concatenado)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Arrays divididos: [array([1, 2]), array([3, 4]), array([5, 6])]\n"
     ]
    }
   ],
   "source": [
    "array = np.array([1, 2, 3, 4, 5, 6])\n",
    "split_arrays = np.split(array, 3)\n",
    "print(\"Arrays divididos:\", split_arrays)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Análisis y estadísticas\n",
    "NumPy también proporciona funciones para calcular estadísticas básicas. Estas pueden aplicarse a _arrays_ multidimensionales, especificando el eje (`axis`) sobre el cual realizar el cálculo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Suma: 15\n",
      "Media: 3.0\n",
      "Mediana: 3.0\n",
      "Desviación estándar: 1.4142135623730951\n",
      "Varianza: 2.0\n",
      "Valor máximo: 5\n",
      "Valor mínimo: 1\n",
      "Índice del valor máximo: 4\n"
     ]
    }
   ],
   "source": [
    "array = np.array([1, 2, 3, 4, 5])\n",
    "\n",
    "print(\"Suma:\", np.sum(array))\n",
    "print(\"Media:\", np.mean(array))\n",
    "print(\"Mediana:\", np.median(array))\n",
    "print(\"Desviación estándar:\", np.std(array))\n",
    "print(\"Varianza:\", np.var(array))\n",
    "print(\"Valor máximo:\", np.max(array))\n",
    "print(\"Valor mínimo:\", np.min(array))\n",
    "print(\"Índice del valor máximo:\", np.argmax(array))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Te invitamos a conocer otras potencialidades de NumPy. Entre las muchas posibilidades, puedes realizar cálculos de Álgebra Lineal como:\n",
    "- Producto punto\n",
    "- Multiplicación de matrices\n",
    "- Transpuesta de una matriz\n",
    "- Determinante y matriz inversa\n",
    "- Valores y vectores propios\n",
    "- Descomposición en Valores Singulares (SVD)\n",
    "\n",
    "Para consolidar estos conocimientos, recomendamos practicar con ejemplos y ejercicios que apliquen estas operaciones en contextos reales. Puedes iniciar con el procesamiento de datos que te interesen, o resolver sistemas de ecuaciones y aplicar análisis estadístico."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "3.11.11",
   "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.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
