Broadcasting: числото, редът и колоната
Накратко: Broadcasting е правилото, по което NumPy смята с масиви с различна форма: число, ред или колона се разтягат до формата на таблицата, ако размерите пасват.
Проблем
„Колко изкарахме от всеки продукт всяка седмица?“
Управителят ти дава бройките от последните 4 седмици за три продукта: бял хляб, прясно мляко и ябълки. Таблица с 4 реда (седмиците) и 3 колони (продуктите). Цените са 1,20 €, 1,60 € и 2,10 €.
Иска приходите в таблица със същия вид: седмица по продукт. Със списъци в Python това са два вложени for, ред по ред и клетка по клетка.
В NumPy сметките са над целия масив. Но тук масивите са два и са с различна форма: таблица 4 × 3 и ред от 3 цени. Как да ги умножиш един по друг?
Идеята
Числото и масивът
Започни от най-простия случай. Доставчикът вдига всички цени с 20%. products['price'].to_numpy() дава колоната с цените като масив на NumPy, а умножението е по едно число:
25 цени по едно число дава 25 нови цени: 1,20 € става 1,44 €, а комплектът ножове от 39 € става 46,80 €. Числото е „разтегнато“ до формата на масива: все едно до всяка цена стои по едно 1,2.
Това разтягане се казва broadcasting. Числото е само първият случай. Същото става с ред и таблица.
Идеята
Редът и таблицата
Бройките са таблица (4, 3), цените са ред (3,). Умножи ги направо:
Редът с цените се повтаря за всяка от 4-те седмици и всяка клетка се умножава по цената на своята колона. Първата седмица: 30 хляба по 1,20 € са 36 €, 22 млека по 1,60 € са 35,20 €, 14 кг ябълки по 2,10 € са 29,40 €. Без нито един for.
Кога NumPy разтяга и кога спира? Правилото е едно:
- Формите се сравняват отдясно наляво. (4, 3) и (3,): първо 3 и 3, после 4 и празното място.
- Размерите трябва да са равни, или единият да е 1. Празното място се брои за 1.
- Единицата се разтяга до другия размер. Тук редът (3,) става (1, 3) и се разтяга до (4, 3).
По-точно, за любопитните
NumPy не копира реда 4 пъти в паметта. Той само чете същите 3 цени отново за всеки ред. Затова broadcasting е и бърз, и пести памет, дори когато таблицата е с милиони редове.
Числото от предишната карта е масив с форма (), без размери. Пред него се добавят единици, докато стане (1,) или (1, 1), и после се разтяга. Правилото е същото.
Виж го
Как редът пасва на таблицата
Ето същата сметка бавно: таблицата 4 × 3, редът от 3 цени, разтягането надолу и умножението клетка по клетка.
import numpy as np
# бройки за 4 седмици: бял хляб, прясно мляко, ябълки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 * pricesprint(units.shape, prices.shape, revenue.shape)revenueСтъпка по стъпка, с думи
- units е таблица (4, 3): бройките за 4 седмици, по колона за всеки продукт.
- prices е ред (3,): 1,20 €, 1,60 €, 2,10 €. Една цена за всеки продукт.
- Формите се сравняват отдясно: 3 и 3 са равни. Вляво от (3,) няма нищо, то се брои за 1 и редът става (1, 3).
- Единицата се разтяга до 4: редът с цените се повтаря за всяка седмица и става (4, 3).
- Сега клетка по клетка. Първата седмица: 30 × 1,2 = 36, 22 × 1,6 = 35,2, 14 × 2,1 = 29,4.
- revenue е (4, 3), като units: 12 прихода от едно умножение, без нито един for.
Пробвай
Пасват ли формите?
Идеята
Когато формите не пасват
Сега управителят пита друго: кой продукт през коя седмица се е продавал над средното за седмицата. Трябва от всеки ред да извадиш средната на същия ред.
units.mean(axis=1) дава средната бройка за всяка седмица (axis е от предишния урок: размерът, който изчезва). Получават се 4 числа. Извади ги от таблицата:
Средните са 22, 23, 22 и 25, с форма (4,). NumPy сравнява отдясно: 3 от таблицата срещу 4 от средните. Не са равни и никое не е 1, затова спира: operands could not be broadcast together with shapes (4,3) (4,).
Грешката е полезна. Ред от 4 числа наистина не може да легне върху 3 колони.
Идеята
Колоната и таблицата
4-те средни трябва да са колона: по едно число на ред, което се разтяга надясно по продуктите. Колона е форма (4, 1). Става по два начина:
week_mean[:, None]:Noneна второ място добавя размер 1;week_mean.reshape(4, 1): същото, с изрично изписана форма.
Отдясно сега са 3 и 1 (единицата се разтяга до 3), после 4 и 4. Резултатът е таблица (4, 3): в третата седмица хлябът е 12 бройки над средното, а ябълките са 10 под него.
През втората седмица имаше 10% отстъпка на всичко, а през четвъртата 20%. discount съдържа отстъпката за всяка седмица. Запиши в after приходите след отстъпката, таблица 4 × 3.
Решение
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]). Отстъпката е по седмици, значи трябва колона (4, 1). Без [:, None] NumPy сравнява 3 с 4 и дава грешка. Втората седмица става 28,08 €, 36 € и 34,02 €, а четвъртата 26,88 €, 34,56 € и 33,60 €. Седмиците без отстъпка остават същите. С revenue - discount[:, None] ще извадиш по 10 цента от всяка клетка, а не 10%.
Идеята
Тихият капан: 3 × 3 вместо 3
Грешката от NumPy се вижда веднага. По-опасно е, когато формите пасват, а резултатът е друг.
Колега ти праща новите цени, копирани от колона в Excel. Масивът е колона (3, 1), а не ред (3,). Изваждаш старите цени, за да видиш с колко са се вдигнали:
Очакваш 3 разлики, а получаваш 9. Колоната (3, 1) се разтегна надясно, редът (3,) надолу, и всяка нова цена се извади от всяка стара. Само диагоналът (0,1, 0 и 0,2) е разликата, която търсиш. Другите числа нямат смисъл, а NumPy не казва нищо: формите пасват по правилото.
Затова след всяка сметка с различни форми печатай .shape. Ако очакваш (3,), а виждаш (3, 3), нещо се е разтегнало, без да искаш.
Пробвай
С колко поскъпнаха
new_prices е колона (3, 1) с новите цени. Запиши в change с колко се е вдигнала цената на всеки продукт: 3 числа, форма (3,).
Решение
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) прави от колоната ред (3,), и редът минус ред дава 3 разлики: хлябът поскъпва с 0,10 €, млякото остава на 1,60 €, а ябълките поскъпват с 0,20 €. new_prices - prices минава без грешка и дава таблица 3 × 3: това е капанът.
Упражнение
Колко повече с новите цени
Обратно при управителя. Той иска да знае колко повече щеше да изкара всяка седмица от всеки продукт, ако новите цени бяха важали още тогава.
units са бройките (4, 3), prices старите цени (3,), new_prices новите цени като колона (3, 1). Запиши в extra колко повече щеше да е приходът: таблица 4 × 3, седмица по продукт.
Решение
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). Разликата в цената е ред (3,) и се разтяга по 4-те седмици като цените от сцената. Първата седмица дава 3 € от хляба, 0 от млякото и 2,80 € от ябълките. За четирите седмици заедно са 24,60 €.
С units * (new_prices - prices) разликата става таблица 3 × 3 (тихият капан), а после (4, 3) по (3, 3) вече не пасва и NumPy дава грешка. Този път капанът се хваща сам, но не винаги е така.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Първата таблица · Масивът на NumPy · Векторизация · Обобщения и липсващи стойности
- Продължава в: Честни графики · Извадка и популация
- Задачи по темата: NumPy