Broadcasting: el número, la fila y la columna
En resumen: El broadcasting es la regla con la que NumPy calcula con arreglos de formas distintas: un número, una fila o una columna se estira a la forma de la tabla cuando los tamaños encajan.
Problema
«¿Cuánto vendió cada producto cada semana?»
El gerente te da las unidades vendidas en las últimas 4 semanas de tres productos: pan blanco, leche fresca y manzanas. Una tabla con 4 filas (las semanas) y 3 columnas (los productos). Los precios son 1,20 €, 1,60 € y 2,10 €.
Quiere los ingresos en una tabla del mismo tipo: semana por producto. Con listas de Python son dos bucles for anidados, fila por fila y celda por celda.
En NumPy los cálculos actúan sobre todo el arreglo. Pero aquí hay dos arreglos con formas distintas: una tabla de 4 × 3 y una fila de 3 precios. ¿Cómo multiplicas uno por otro?
La idea
El número y el arreglo
Empieza por el caso más simple. El proveedor sube todos los precios un 20 %. products['price'].to_numpy() da la columna de precios como arreglo de NumPy, y se multiplica por un solo número:
25 precios por un número dan 25 precios nuevos: 1,20 € pasa a 1,44 €, y el juego de cuchillos de 39 € pasa a 46,80 €. El número se «estira» a la forma del arreglo: como si junto a cada precio hubiera un 1.2.
Este estiramiento se llama broadcasting. El número es solo el primer caso. Lo mismo pasa con una fila y una tabla.
La idea
La fila y la tabla
Las unidades son una tabla (4, 3), los precios son una fila (3,). Multiplícalos directamente:
La fila de precios se repite para cada una de las 4 semanas, y cada celda se multiplica por el precio de su columna. La primera semana: 30 panes a 1,20 € son 36 €, 22 leches a 1,60 € son 35,20 €, 14 kg de manzanas a 2,10 € son 29,40 €. Ni un solo for.
¿Cuándo estira NumPy y cuándo se detiene? Hay una regla:
- Las formas se comparan de derecha a izquierda. (4, 3) y (3,): primero 3 y 3, luego 4 y el lugar vacío.
- Los tamaños deben ser iguales, o uno de ellos debe ser 1. Un lugar vacío cuenta como 1.
- El 1 se estira al otro tamaño. Aquí la fila (3,) pasa a (1, 3) y se estira a (4, 3).
Más precisamente, para curiosos
NumPy no copia la fila 4 veces en la memoria. Lee los mismos 3 precios otra vez para cada fila. Por eso el broadcasting es rápido y ligero en memoria, incluso cuando la tabla tiene millones de filas.
El número de la tarjeta anterior es un arreglo de forma (), sin tamaños. Se le ponen unos delante hasta que es (1,) o (1, 1), y entonces se estira. La regla es la misma.
Míralo
Cómo encaja la fila en la tabla
Aquí está el mismo cálculo despacio: la tabla de 4 × 3, la fila de 3 precios, el estiramiento hacia abajo y la multiplicación celda por celda.
import numpy as np
# unidades en 4 semanas: pan blanco, leche fresca, manzanasunits = np.array([[30, 22, 14], [26, 25, 18], [34, 20, 12], [28, 27, 20]])prices = np.array([1.2, 1.6, 2.1])
revenue = units * pricesprint(units.shape, prices.shape, revenue.shape)revenuePaso a paso, en palabras
- units es una tabla (4, 3): las unidades de 4 semanas, una columna por producto.
- prices es una fila (3,): 1,20 €, 1,60 €, 2,10 €. Un precio por producto.
- Las formas se comparan desde la derecha: 3 y 3 son iguales. A la izquierda de (3,) no hay nada, eso cuenta como 1 y la fila pasa a (1, 3).
- El 1 se estira a 4: la fila de precios se repite para cada semana y pasa a (4, 3).
- Ahora celda por celda. La primera semana: 30 × 1,2 = 36, 22 × 1,6 = 35,2, 14 × 2,1 = 29,4.
- revenue es (4, 3), como units: 12 ingresos de una multiplicación, sin un solo for.
Pruébalo
¿Encajan las formas?
La idea
Cuando las formas no encajan
Ahora el gerente pregunta otra cosa: qué producto vendió por encima del promedio de la semana en qué semana. Tienes que restar a cada fila la media de esa misma fila.
units.mean(axis=1) da las unidades medias de cada semana (axis viene de la lección anterior: el tamaño que desaparece). Son 4 números. Réstalos de la tabla:
Las medias son 22, 23, 22 y 25, con la forma (4,). NumPy compara desde la derecha: 3 de la tabla contra 4 de las medias. No son iguales y ninguno es 1, así que se detiene: operands could not be broadcast together with shapes (4,3) (4,).
El error es útil. Una fila de 4 números de verdad no puede tenderse sobre 3 columnas.
La idea
La columna y la tabla
Las 4 medias tienen que ser una columna: un número por fila, estirado hacia la derecha a lo largo de los productos. Una columna tiene la forma (4, 1). Hay dos maneras de conseguirla:
week_mean[:, None]:Noneen segundo lugar añade un tamaño 1;week_mean.reshape(4, 1): lo mismo, con la forma escrita.
A la derecha ahora hay 3 y 1 (el 1 se estira a 3), luego 4 y 4. El resultado es una tabla (4, 3): en la tercera semana el pan está 12 unidades por encima del promedio, y las manzanas 10 por debajo.
En la segunda semana todo tuvo un 10 % de descuento, y en la cuarta un 20 %. discount tiene el descuento de cada semana. Guarda en after los ingresos después del descuento, una tabla de 4 × 3.
Solución
import numpy as np
units = np.array([[30, 22, 14],
[26, 25, 18],
[34, 20, 12],
[28, 27, 20]])
prices = np.array([1.2, 1.6, 2.1])
revenue = units * prices
discount = np.array([0, 0.1, 0, 0.2])
after = revenue * (1 - discount[:, None])
afterafter = revenue * (1 - discount[:, None]). El descuento es por semana, así que tiene que ser una columna (4, 1). Sin [:, None] NumPy compara 3 con 4 y lanza un error. La segunda semana pasa a 28,08 €, 36 € y 34,02 €, y la cuarta a 26,88 €, 34,56 € y 33,60 €. Las semanas sin descuento no cambian. Con revenue - discount[:, None] restas 10 céntimos a cada celda, no un 10 %.
La idea
La trampa silenciosa: 3 × 3 en lugar de 3
Un error de NumPy se ve enseguida. Es más peligroso cuando las formas encajan y el resultado es otra cosa.
Un colega te manda los precios nuevos, copiados de una columna de Excel. El arreglo es una columna (3, 1), no una fila (3,). Restas los precios viejos para ver cuánto subieron:
Esperas 3 diferencias y obtienes 9. La columna (3, 1) se estiró hacia la derecha, la fila (3,) se estiró hacia abajo, y cada precio nuevo se restó de cada precio viejo. Solo la diagonal (0.1, 0 y 0.2) es la diferencia que quieres. Los demás números no significan nada, y NumPy no dice nada: las formas cumplen la regla.
Por eso, después de cada cálculo con formas distintas, imprime .shape. Si esperas (3,) y ves (3, 3), algo se estiró sin que lo pidieras.
Pruébalo
Cuánto subieron los precios
new_prices es una columna (3, 1) con los precios nuevos. Guarda en change cuánto subió el precio de cada producto: 3 números, forma (3,).
Solución
import numpy as np
prices = np.array([1.2, 1.6, 2.1])
new_prices = np.array([[1.3], [1.6], [2.3]])
change = new_prices.reshape(3) - prices
changechange = new_prices.reshape(3) - prices. reshape(3) convierte la columna en una fila (3,), y fila menos fila da 3 diferencias: el pan sube 0,10 €, la leche se queda en 1,60 € y las manzanas suben 0,20 €. new_prices - prices se ejecuta sin error y da una tabla de 3 × 3: esa es la trampa.
Ejercicio
Cuánto más con los precios nuevos
De vuelta con el gerente. Quiere saber cuánto más habría ingresado cada producto cada semana si los precios nuevos ya hubieran estado en vigor.
units son las unidades (4, 3), prices los precios viejos (3,), new_prices los precios nuevos como columna (3, 1). Guarda en extra cuánto más altos habrían sido los ingresos: una tabla de 4 × 3, semana por producto.
Solución
import numpy as np
units = np.array([[30, 22, 14],
[26, 25, 18],
[34, 20, 12],
[28, 27, 20]])
prices = np.array([1.2, 1.6, 2.1])
new_prices = np.array([[1.3], [1.6], [2.3]])
extra = units * (new_prices.reshape(3) - prices)
extraextra = units * (new_prices.reshape(3) - prices). La diferencia de precios es una fila (3,) y se estira a lo largo de las 4 semanas como los precios en la escena. La primera semana da 3 € del pan, 0 de la leche y 2,80 € de las manzanas. En las cuatro semanas juntas son 24,60 €.
Con units * (new_prices - prices) la diferencia pasa a ser una tabla de 3 × 3 (la trampa silenciosa), y entonces (4, 3) por (3, 3) ya no encaja y NumPy lanza un error. Esta vez la trampa se delata sola, pero no siempre es así.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: La primera tabla · El arreglo de NumPy · Vectorización · Resúmenes y valores faltantes
- Continúa en: Gráficos honestos · Muestra y población
- Ejercicios sobre el tema: NumPy