Към съдържанието
wannacode

Broadcasting: числото, редът и колоната

Накратко: Broadcasting е правилото, по което NumPy смята с масиви с различна форма: число, ред или колона се разтягат до формата на таблицата, ако размерите пасват.

Проблем

„Колко изкарахме от всеки продукт всяка седмица?“

Управителят ти дава бройките от последните 4 седмици за три продукта: бял хляб, прясно мляко и ябълки. Таблица с 4 реда (седмиците) и 3 колони (продуктите). Цените са 1,20 €, 1,60 € и 2,10 €.

Иска приходите в таблица със същия вид: седмица по продукт. Със списъци в Python това са два вложени for, ред по ред и клетка по клетка.

В NumPy сметките са над целия масив. Но тук масивите са два и са с различна форма: таблица 4 × 3 и ред от 3 цени. Как да ги умножиш един по друг?

Идеята

Числото и масивът

Започни от най-простия случай. Доставчикът вдига всички цени с 20%. products['price'].to_numpy() дава колоната с цените като масив на NumPy, а умножението е по едно число:

Pythonданни: таблиците на магазина

25 цени по едно число дава 25 нови цени: 1,20 € става 1,44 €, а комплектът ножове от 39 € става 46,80 €. Числото е „разтегнато“ до формата на масива: все едно до всяка цена стои по едно 1,2.

Това разтягане се казва broadcasting. Числото е само първият случай. Същото става с ред и таблица.

Идеята

Редът и таблицата

Бройките са таблица (4, 3), цените са ред (3,). Умножи ги направо:

Pythonданни: таблиците на магазина

Редът с цените се повтаря за всяка от 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 * prices
print(units.shape, prices.shape, revenue.shape)
revenue
Стъпка по стъпка, с думи
  1. units е таблица (4, 3): бройките за 4 седмици, по колона за всеки продукт.
  2. prices е ред (3,): 1,20 €, 1,60 €, 2,10 €. Една цена за всеки продукт.
  3. Формите се сравняват отдясно: 3 и 3 са равни. Вляво от (3,) няма нищо, то се брои за 1 и редът става (1, 3).
  4. Единицата се разтяга до 4: редът с цените се повтаря за всяка седмица и става (4, 3).
  5. Сега клетка по клетка. Първата седмица: 30 × 1,2 = 36, 22 × 1,6 = 35,2, 14 × 2,1 = 29,4.
  6. revenue е (4, 3), като units: 12 прихода от едно умножение, без нито един for.

Пробвай

Пасват ли формите?

1. units е с форма (4, 3), а prices с форма (3,). Каква е формата на units * prices?
2. units е с форма (4, 3), а week_bonus с форма (4,). Какво дава units + week_bonus?
3. Коя двойка форми пасва?

Идеята

Когато формите не пасват

Сега управителят пита друго: кой продукт през коя седмица се е продавал над средното за седмицата. Трябва от всеки ред да извадиш средната на същия ред.

units.mean(axis=1) дава средната бройка за всяка седмица (axis е от предишния урок: размерът, който изчезва). Получават се 4 числа. Извади ги от таблицата:

Pythonданни: таблиците на магазина

Средните са 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): същото, с изрично изписана форма.
Pythonданни: таблиците на магазина

Отдясно сега са 3 и 1 (единицата се разтяга до 3), после 4 и 4. Резултатът е таблица (4, 3): в третата седмица хлябът е 12 бройки над средното, а ябълките са 10 под него.

През втората седмица имаше 10% отстъпка на всичко, а през четвъртата 20%. discount съдържа отстъпката за всяка седмица. Запиши в after приходите след отстъпката, таблица 4 × 3.

Pythonданни: таблиците на магазина

Идеята

Тихият капан: 3 × 3 вместо 3

Грешката от NumPy се вижда веднага. По-опасно е, когато формите пасват, а резултатът е друг.

Колега ти праща новите цени, копирани от колона в Excel. Масивът е колона (3, 1), а не ред (3,). Изваждаш старите цени, за да видиш с колко са се вдигнали:

Pythonданни: таблиците на магазина

Очакваш 3 разлики, а получаваш 9. Колоната (3, 1) се разтегна надясно, редът (3,) надолу, и всяка нова цена се извади от всяка стара. Само диагоналът (0,1, 0 и 0,2) е разликата, която търсиш. Другите числа нямат смисъл, а NumPy не казва нищо: формите пасват по правилото.

Затова след всяка сметка с различни форми печатай .shape. Ако очакваш (3,), а виждаш (3, 3), нещо се е разтегнало, без да искаш.

Пробвай

С колко поскъпнаха

new_prices е колона (3, 1) с новите цени. Запиши в change с колко се е вдигнала цената на всеки продукт: 3 числа, форма (3,).

Pythonданни: таблиците на магазина

Упражнение

Колко повече с новите цени

Обратно при управителя. Той иска да знае колко повече щеше да изкара всяка седмица от всеки продукт, ако новите цени бяха важали още тогава.

units са бройките (4, 3), prices старите цени (3,), new_prices новите цени като колона (3, 1). Запиши в extra колко повече щеше да е приходът: таблица 4 × 3, седмица по продукт.

Pythonданни: таблиците на магазина

Тест

Провери се

1. Какво прави prices * 1.2, когато prices е масив от 25 цени?
2. week_mean е с форма (4,), а units с форма (4, 3). Как вадиш от всяка седмица нейната средна?
3. old е с форма (3,), а new с форма (3, 1). Какво дава new - old?
4. Как най-бързо хващаш тихото разтягане в таблица 3 × 3?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·