11. Buenas prácticas y optimización

NumPy está pensado para eficiencia y velocidad. Aprovecha implementaciones en C y Fortran para operar con arrays multidimensionales de manera óptima. Veamos cómo sacarle el máximo provecho.

11.1 Ventajas de los arrays sobre listas en eficiencia

Las listas de Python son flexibles, pero no están optimizadas para cálculos matemáticos masivos.

a) Ejemplo con listas

# Sumar 2 a cada elemento de una lista
lista = [i for i in range(1_000_000)]
nueva_lista = [x + 2 for x in lista]

👉 Python itera elemento por elemento: lento con millones de datos.

b) Ejemplo con arrays

import numpy as np

arr = np.arange(1_000_000)
nuevo_arr = arr + 2

👉 Con NumPy no usamos bucles explícitos, la operación se ejecuta en código compilado y los arrays homogéneos usan menos memoria. Resultado: decenas o cientos de veces más rápido.

11.2 Vectorización y evitar bucles

La vectorización aplica operaciones sobre arrays completos, reemplazando bucles en Python.

a) Con bucle (ineficiente)

valores = [1, 2, 3, 4, 5]
cuadrados = []
for v in valores:
    cuadrados.append(v ** 2)
print(cuadrados)  # [1, 4, 9, 16, 25]

b) Vectorizado con NumPy (eficiente)

arr = np.array([1, 2, 3, 4, 5])
print(arr ** 2)  # [ 1  4  9 16 25]

👉 El array entero se eleva al cuadrado en una sola operación.

c) Ejemplo: normalizar datos

# Lista en Python
lista = [10, 20, 30, 40, 50]
media = sum(lista) / len(lista)
desvio = (sum((x - media) ** 2 for x in lista) / len(lista)) ** 0.5
normalizada = [(x - media) / desvio for x in lista]
print(normalizada)

Con NumPy:

arr = np.array([10, 20, 30, 40, 50])
normalizada = (arr - arr.mean()) / arr.std()
print(normalizada)

👉 Más simple, legible y rápido.

11.3 Uso de funciones universales (ufuncs)

Las ufuncs son funciones optimizadas que operan elemento a elemento: np.add, np.multiply, np.sqrt, np.exp, np.log, np.sin, np.cos, entre otras.

a) Ejemplo con suma y multiplicación

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

print(np.add(a, b))      # [5 7 9]
print(np.multiply(a, b)) # [ 4 10 18]

b) Funciones matemáticas

arr = np.array([1, 2, 3, 4])

print("Raíz cuadrada:", np.sqrt(arr))  # [1.         1.41421356 1.73205081 2.        ]
print("Exponencial:", np.exp(arr))     # [ 2.71828183  7.3890561  20.08553692 54.59815003]
print("Logaritmo:", np.log(arr))       # [0.         0.69314718 1.09861229 1.38629436]

c) Ventaja frente a funciones estándar

import math

# Con math (lento, bucle explícito)
lista = [1, 2, 3, 4]
raices = [math.sqrt(x) for x in lista]

# Con NumPy (rápido, vectorizado)
arr = np.array([1, 2, 3, 4])
raices_np = np.sqrt(arr)

👉 La versión con NumPy es mucho más veloz y clara.

Resumen

  • Los arrays de NumPy son más rápidos y eficientes que las listas de Python.
  • La vectorización reemplaza bucles con operaciones directas sobre arrays.
  • Las ufuncs operan elemento a elemento con alto rendimiento.

Nota: Dominar estas prácticas es clave para manejar grandes volúmenes de datos en ciencia de datos y machine learning.