Broadcasting: Reglas de Operaciones con Arreglos Multidimensionales
Al finalizar este tema
Comprenderá la razón de ser del broadcasting en NumPy, sabrá determinar si las operaciones son posibles aplicando sus tres reglas y podrá escribir código eficiente utilizando el broadcasting.
¿Por qué es necesario el broadcasting?
import numpy as np
# Quiero sumar 10 a todos los elementosarr = np.array([1, 2, 3, 4, 5])result = arr + 10print(result) # [11 12 13 14 15]arr tiene 5 elementos, mientras que 10 es un escalar (1 elemento). Aunque tienen tamaños diferentes, se pueden sumar porque NumPy realizó una expansión automática de 10 a [10, 10, 10, 10, 10]. Esta expansión automática se conoce como difusión (broadcasting).
Sin la difusión:
# Hay que escribirlo así cada vezresult = arr + np.full(5, 10) # IneficienteOperaciones con formas idénticas — Conceptos básicos
Si las dimensiones son iguales, se realiza una operación elemento por elemento.
a = np.array([1, 2, 3])b = np.array([10, 20, 30])
print(a + b) # [11 22 33]print(a * b) # [10 40 90]Lo mismo ocurre en dos dimensiones.
A = np.array([[1, 2], [3, 4]])B = np.array([[10, 20], [30, 40]])
print(A + B)# [[11 22]# [33 44]]Reglas de difusión — 3 reglas
Cuando las dimensiones son diferentes, NumPy aplica tres reglas en orden.
Regla 1: Si el número de dimensiones es diferente, se añade un 1 al principio de la matriz con menos dimensiones.
a = np.array([[1, 2, 3], # shape: (2, 3) [4, 5, 6]])b = np.array([10, 20, 30]) # shape: (3,)
# Regla 1: expandir la forma de b (3,) a (1, 3)# Después de aplicar la regla 2: (1, 3) → (2, 3)print(a + b)# [[11 22 33]# [14 25 36]]Regla 2: Las dimensiones con tamaño 1 se expanden para que coincidan con las de otro arreglo.
a = np.array([[1, 2, 3]]) # shape: (1, 3)b = np.array([[10], # shape: (3, 1) [20], [30]])
# a: (1, 3) → expansión en dirección de fila → (3, 3)# b: (3, 1) → expansión en dirección de columna → (3, 3)print(a + b)# [[11 12 13]# [21 22 23]# [31 32 33]]Regla 3: Si las dimensiones son diferentes y ninguna de ellas es igual a 1, se produce un error.
a = np.array([1, 2, 3]) # shape: (3,)b = np.array([10, 20]) # shape: (2,)
# 3 vs 2 — ninguno es 1 → ¡Error!# a + b → ValueError: operands could not be broadcast togetherComprensión visual
(4, 3) + (3,) → OK!
a: [[1 2 3] b: [10 20 30]
[4 5 6] ↓ Regla1: (1, 3)
[7 8 9] ↓ Regla2: (4, 3)
[0 1 2]]
b': [[10 20 30]
[10 20 30]
[10 20 30]
[10 20 30]]
Resultado: [[11 22 33]
[14 25 36]
[17 28 39]
[10 21 32]](3, 1) + (1, 4) → ¡OK! Forma del resultado: (3, 4)
a: [[1] b: [[10 20 30 40]]
[2] ↓ Regla 2: (3, 4)
[3]] b': [[10 20 30 40]
↓ Regla 2 [10 20 30 40]
a': [[1 1 1 1] [10 20 30 40]]
[2 2 2 2]
[3 3 3 3]]
Resultado: [[11 21 31 41]
[12 22 32 42]
[13 23 33 43]]Patrón práctico: Normalización
# Restar la media de cada columna para centrar (centrado)data = np.array([[170, 60, 30], [180, 75, 25], [165, 55, 35], [175, 70, 28]])# forma: (4, 3) — 4 personas, 3 mediciones (estatura, peso, edad)
col_mean = data.mean(axis=0) # forma: (3,) — media de cada columnaprint(col_mean) # [172.5 65. 29.5]
centered = data - col_mean # (4, 3) - (3,) → ¡transmisión por difuminado!print(centered)# [[ -2.5 -5. 0.5]# [ 7.5 10. -4.5]# [ -7.5 -10. 5.5]# [ 2.5 5. -1.5]]Normalización por puntuación Z
col_std = data.std(axis=0)z_scores = (data - col_mean) / col_std # ¡Transmisión por difuminado dos veces!(4, 3) - (3,) → OK, (4, 3) / (3,) → OK. Este patrón permite normalizar todo el conjunto de datos.
Transmisión frente a bucle
# Mal: Bucle de Python — lentodata = np.random.rand(10000, 100)mean = data.mean(axis=0)
result = np.zeros_like(data)for i in range(data.shape[0]): for j in range(data.shape[1]): result[i, j] = data[i, j] - mean[j]
# Bien: Transmisión por difuminado — más de 100 veces más rápidoresult = data - meanLa difusión se implementa internamente como operaciones vectoriales en C. Es entre diez y cien veces más rápida que los bucles de Python, y el código consta de una sola línea.
Patrón práctico: Matriz de distancias
Calcula todas las distancias entre dos conjuntos de puntos de forma simultánea.
# 3 puntos: (1,0), (2,3), (4,1)points = np.array([[1, 0], [2, 3], [4, 1]]) # shape: (3, 2)
# Distancia euclidiana entre todos los paresdiff = points[:, np.newaxis, :] - points[np.newaxis, :, :]# (3,1,2) - (1,3,2) → broadcasting → (3,3,2)
dist = np.sqrt((diff ** 2).sum(axis=2))print(dist.round(2))# [[0. 3.16 3.16]# [3.16 0. 2.83]# [3.16 2.83 0. ]]np.newaxis se utiliza para añadir una dimensión y calcular las diferencias entre todos los pares de manera simultánea mediante la función de transmisión (broadcasting). Esto es decenas de veces más rápido que utilizar bucles anidados.
Advertencia: posible consumo excesivo de memoria
a = np.random.rand(10000, 1) # shape: (10000, 1)b = np.random.rand(1, 10000) # shape: (1, 10000)c = a + b # forma: (10000, 10000) — ¡100 millones de elementos!# Memoria: 10000 × 10000 × 8 bytes = 800 MBEl broadcasting amplía conceptualmente el tamaño, pero el array resultante sí ocupa memoria. (10000, 1) + (1, 10000) crea un array de tamaño (10000, 10000). Con conjuntos de datos grandes, calcule de antemano la forma del resultado para verificar el uso de memoria.
# Predicción del uso de memoriaresult_shape = (10000, 10000)dtype_size = 8 # float64 = 8 bytesmemory_bytes = result_shape[0] * result_shape[1] * dtype_sizeprint(f"{memory_bytes / 1e6:.0f} MB") # 800 MBMétodo rápido para determinar la compatibilidad de formas
Alinee los elementos desde la derecha; las dimensiones son compatibles si son iguales o si una de ellas es 1.
(4, 3) + (3,) → (4, 3) + (1, 3) → OK → (4, 3)
(4, 3) + (4, 1) → OK → (4, 3)
(3, 1) + (1, 4) → OK → (3, 4)
(4, 3) + (2,) → (4, 3) + (1, 2) → 3 vs 2 → ERROR
(2, 3, 4) + (3, 1) → (2, 3, 4) + (1, 3, 1) → OK → (2, 3, 4)Resumen clave
| Regla | Descripción |
|---|---|
| Regla 1 | Si el número de dimensiones es diferente, se añade un 1 al lado con la dimensión más pequeña. |
| Regla 2 | Las dimensiones con tamaño 1 se expanden según su escala relativa. |
| Regla 3 | Si los tamaños son diferentes y ninguno de los lados es 1, se produce un error. |
El broadcasting es una de las funciones más potentes de NumPy. Permite realizar normalización de datos, cálculo de distancias y transformaciones de matrices en una sola línea, sin necesidad de bucles for. La clave está en observar la forma (shape) y determinar inmediatamente si "se alinea desde la derecha; está bien si es igual o 1".
El mismo principio se aplica en pandas. Las operaciones DataFrame - Series se difunden a lo largo de las columnas. Comprender el broadcasting de NumPy facilita la comprensión natural de las operaciones vectoriales en pandas.
import pandas as pd
df = pd.DataFrame({"A": [10, 20, 30], "B": [40, 50, 60]})means = df.mean() # Series: A=20, B=50centered = df - means # ¡broadcasting! Resta la media de cada columnaprint(centered)# A B# 0 -10.0 -10.0# 1 0.0 0.0# 2 10.0 10.0