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

Векторизация

Накратко: Векторизацията в NumPy е сметка над целия масив наведнъж, без цикъл: аритметика, сравнения, маски с & и | и избор с np.where.

Проблем

Цикъл за всеки въпрос

Управителят иска цените с ДДС. Веднага след това пита колко продукта струват над 5 €. Със списък всеки въпрос е нов цикъл:

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

Работи: 1,20 € става 1,44 €, а над 5 € са 8 продукта. Само че за всеки следващ въпрос пишеш още един for, още една празна променлива и още един брояч. Самият въпрос се губи между редовете.

Идеята

Сметка над целия масив

С масив сметката се пише веднъж, за всички числа:

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

prices * 1.2 умножава всяка от 25-те цени. Цикълът е вътре в NumPy, на C. Това се казва векторна сметка (vectorized): операцията е върху целия масив, а не върху отделно число. Ножовете от 39 € стават 46,80 €.

Така работят +, -, *, / и **, а също функции като np.round(масив, 2), която закръглява всяко число до 2 знака.

Идеята

Масив по масив

Два масива с еднаква дължина се смятат число по число: първото с първото, второто с второто. В order_items.csv всеки ред има бройка и цена за брой. Стойността на реда е едното по другото:

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

Първият ред е 4 броя по 7,90 €, тоест 31,60 €. Без нито един цикъл имаш стойността на всичките 152 реда. .sum() ги събира: 1 963,50 €. На обобщенията като sum ще се спрем в урок 3.

Пробвай

Колко струва складът

Запиши във value стойността на наличността за всеки продукт: цената му по броя на склад.

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

Идеята

Сравнение и маска

Сравнението също е векторно. prices > 5 не дава едно True или False, а по едно за всяка цена:

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

Такъв масив от True и False се казва маска. Сложена в квадратни скоби, тя оставя само местата с True. А .sum() на маската брои истините, защото при сбор True е 1, а False е 0:

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

Над 5 € са 8 цени: същият отговор като с цикъла, на един ред. Маската от наличността работи и върху друг масив със същата дължина: имената на изчерпаните продукти са баницата и краставиците.

Идеята

Две условия: &, | и ~

Цените от 2 до 5 € са две условия заедно. В обикновения Python би писал and. С масиви and не става:

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

and пита „цялото това вярно ли е?“ и иска едно True или False. Масивът има 25. NumPy не гадае дали имаш предвид „всички“ или „поне едно“, затова спира с грешка.

За маски има три знака, които работят число по число:

  • & е „и“: двете условия са верни;
  • | е „или“: поне едното е вярно;
  • ~ е „не“: обръща маската.
Pythonданни: таблиците на магазина

От 2 до 5 € са 9 цени. Под 1 € или над 20 € са 0,80 €, 0,90 €, 24,90 € и 39 €. Цените на продуктите, които не са на склад, са 2,50 € и 2,80 €.

Виж го

Капанът със скобите

Всяко условие около & и | е в скоби. Ако ги пропуснеш, Python смята & преди >= и <=, тоест започва от 2 & prices:

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

& с число и дробни цени няма смисъл, затова идва TypeError за bitwise_and. Правилото е: всяко условие в скоби, (prices >= 2) & (prices <= 5).

По-точно, за любопитните

&, | и ~ в Python са побитови операции върху цели числа. Затова са по-силни от сравненията: 2 & 6 > 1 е (2 & 6) > 1. NumPy ги е взел за маските, защото and, or и not не могат да се пренапишат за масиви: те винаги искат едно True или False. Същото ще видиш и при филтрите в pandas.

Пробвай

Евтино и на склад

Колко продукта струват под 3 € и са на склад (наличност над 0)? Запиши броя в count.

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

Идеята

Едно или друго: np.where

Маската избира кои числа да останат. Понякога искаш всички, но различни според условието. np.where(маска, ако_да, ако_не) взима за всяко място едното или другото:

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

Където наличността е 0, излиза „изчерпан“, а иначе „има“. На мястото на текста могат да са и масиви: np.where(prices > 10, prices * 0.9, prices) смъква с 10% само скъпите цени.

Упражнение

Ценоразписът с ДДС

Обратно към управителя: цените с ДДС и една промоция.

Запиши в with_vat цените с ДДС (по 1,2), закръглени до 2 знака. В over_10 запиши колко от тях са над 10 €.

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

Промоция: продуктите над 10 € са с 10% по-евтини, а другите остават със старата цена. Запиши в promo целия нов ценоразпис (25 цени).

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

Тест

Провери се

1. Какво дава np.array([2, 3]) * np.array([1.5, 4.0])?
2. Какво дава (prices > 5).sum()?
3. Кой ред взима цените от 2 до 5 €?
4. Защо намалението за скъпите продукти е np.where(prices > 10, prices * 0.9, prices), а не prices[prices > 10] * 0.9?

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