NumPy es la base de casi todo el ecosistema científico de Python: pandas, scikit-learn, TensorFlow y PyTorch usan NumPy por debajo. Su aportación central es el ndarray: una estructura de datos N-dimensional con operaciones vectorizadas que ejecuta en C, eliminando la necesidad de bucles for para cálculos sobre grandes colecciones.
Crear arrays
import numpy as np # Desde listas de Python v = np.array([1, 2, 3, 4, 5]) # array 1D de enteros m = np.array([[1, 2, 3], [4, 5, 6]]) # array 2D (matriz 2x3) c = np.array([1.0, 2.0, 3.0]) # float64 automáticamente # Arrays con valores predefinidos print(np.zeros((3, 4))) # matriz 3x4 de ceros print(np.ones((2, 3))) # matriz 2x3 de unos print(np.eye(3)) # matriz identidad 3x3 print(np.full((2, 2), 7)) # matriz 2x2 rellena con 7 # Secuencias print(np.arange(0, 10, 2)) # [0 2 4 6 8] (como range, pero array) print(np.linspace(0, 1, 5)) # [0. 0.25 0.5 0.75 1. ] (5 puntos equidistantes) # Aleatorios rng = np.random.default_rng(seed=42) print(rng.random((3, 3))) # floats entre 0 y 1 print(rng.integers(0, 100, size=10)) # enteros entre 0 y 99
Propiedades del array
import numpy as np a = np.array([[1, 2, 3], [4, 5, 6]]) print(a.shape) # (2, 3) filas, columnas print(a.ndim) # 2 número de dimensiones print(a.size) # 6 total de elementos print(a.dtype) # int64 tipo de dato print(a.itemsize) # 8 bytes por elemento # Cambiar forma sin copiar datos b = a.reshape(3, 2) # (3, 2) c = a.flatten() # [1, 2, 3, 4, 5, 6] d = a.T # transpuesta: (3, 2)
Operaciones vectorizadas sin bucles for
import numpy as np
precios = np.array([10.0, 25.0, 8.5, 14.0, 30.0])
cantidades = np.array([3, 1, 5, 2, 1])
# Operaciones elemento a elemento (sin for)
totales = precios * cantidades # [30. 25. 42.5 28. 30. ]
con_iva = precios * 1.21 # [12.1 30.25 10.285 ...]
descuento = np.where(precios > 20, precios * 0.9, precios) # 10% dto si precio > 20
# Funciones matemáticas aplicadas a todos los elementos
v = np.array([1.0, 4.0, 9.0, 16.0])
print(np.sqrt(v)) # [1. 2. 3. 4.]
print(np.log(v)) # [0. 1.39 2.2 2.77]
print(np.exp(v)) # [2.72 54.6 8103. ...]
# Comparación: velocidad
import time
n = 1_000_000
lista = list(range(n))
arr = np.arange(n, dtype=float)
inicio = time.perf_counter()
resultado_lista = [x ** 2 for x in lista]
t_lista = time.perf_counter() - inicio
inicio = time.perf_counter()
resultado_np = arr ** 2
t_np = time.perf_counter() - inicio
print(f"Lista: {t_lista:.3f}s NumPy: {t_np:.3f}s")
# Lista: 0.150s NumPy: 0.003s (?50x más rápido)
Indexing y slicing
import numpy as np a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # Acceso a elementos print(a[0, 0]) # 1 print(a[2, 1]) # 8 # Slicing: [filas, columnas] print(a[:, 0]) # [1 4 7] primera columna print(a[0, :]) # [1 2 3] primera fila print(a[1:, 1:]) # [[5 6] [8 9]] submatriz # Indexing booleano (filtrado) datos = np.array([1, -2, 3, -4, 5, -6]) positivos = datos[datos > 0] print(positivos) # [1 3 5] # Indexing con arrays de índices indices = np.array([0, 2, 4]) print(datos[indices]) # [1 3 5]
Broadcasting: operaciones entre arrays de distinto tamaño
Broadcasting es la regla que aplica NumPy cuando las dimensiones de dos arrays no coinciden exactamente. En lugar de copiar datos, "expande" virtualmente las dimensiones para que la operación tenga sentido.
import numpy as np # Sumar un escalar a un array a = np.array([1, 2, 3]) print(a + 10) # [11 12 13] 10 se "expande" a [10, 10, 10] # Sumar vector columna a matriz matriz = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3) fila = np.array([10, 20, 30]) # (3,) print(matriz + fila) # [[11 22 33] # [14 25 36]] # Suma de columna (2x1) con fila (1x3) ? matriz (2x3) col = np.array([[100], [200]]) # (2, 1) fil = np.array([1, 2, 3]) # (3,) print(col + fil) # [[101 102 103] # [201 202 203]] # Caso de uso real: normalización por fila datos = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) medias = datos.mean(axis=1, keepdims=True) # (2, 1) normalizado = datos - medias print(normalizado) # [[-1. 0. 1.] # [-1. 0. 1.]]
Ufuncs y agregaciones
import numpy as np ventas = np.array([[120, 250, 180], [90, 310, 200], [150, 280, 160]]) # Filas: trimestres; Columnas: productos # Agregaciones sobre todo el array print(ventas.sum()) # total global print(ventas.mean()) # media global print(ventas.max()) # máximo global # Agregaciones por eje print(ventas.sum(axis=0)) # total por producto (suma columnas) print(ventas.sum(axis=1)) # total por trimestre (suma filas) print(ventas.mean(axis=0)) # media por producto print(ventas.argmax(axis=1)) # índice del producto más vendido por trimestre # Otras ufuncs útiles print(np.cumsum(ventas, axis=1)) # suma acumulada por fila print(np.diff(ventas, axis=0)) # diferencia entre trimestres consecutivos
El principio fundamental de NumPy: si puedes expresar una operación como una transformación sobre todo el array a la vez, hazlo así. Los bucles for de Python son lentos cuando procesan muchos elementos; las operaciones vectorizadas de NumPy trabajan en C con SIMD y son decenas o cientos de veces más rápidas. El cambio de mentalidad pensar en arrays en lugar de en elementos individuales es lo que diferencia el código NumPy eficiente del que simplemente funciona.
