Векторизация
Накратко: Векторизацията в NumPy е сметка над целия масив наведнъж, без цикъл: аритметика, сравнения, маски с & и | и избор с np.where.
Проблем
Цикъл за всеки въпрос
Управителят иска цените с ДДС. Веднага след това пита колко продукта струват над 5 €. Със списък всеки въпрос е нов цикъл:
Работи: 1,20 € става 1,44 €, а над 5 € са 8 продукта. Само че за всеки следващ въпрос пишеш още един for, още една празна променлива и още един брояч. Самият въпрос се губи между редовете.
Идеята
Сметка над целия масив
С масив сметката се пише веднъж, за всички числа:
prices * 1.2 умножава всяка от 25-те цени. Цикълът е вътре в NumPy, на C. Това се казва векторна сметка (vectorized): операцията е върху целия масив, а не върху отделно число. Ножовете от 39 € стават 46,80 €.
Така работят +, -, *, / и **, а също функции като np.round(масив, 2), която закръглява всяко число до 2 знака.
Идеята
Масив по масив
Два масива с еднаква дължина се смятат число по число: първото с първото, второто с второто. В order_items.csv всеки ред има бройка и цена за брой. Стойността на реда е едното по другото:
Първият ред е 4 броя по 7,90 €, тоест 31,60 €. Без нито един цикъл имаш стойността на всичките 152 реда. .sum() ги събира: 1 963,50 €. На обобщенията като sum ще се спрем в урок 3.
Пробвай
Колко струва складът
Запиши във value стойността на наличността за всеки продукт: цената му по броя на склад.
Решение
import pandas as pd
products = pd.read_csv('products.csv')
prices = products['price'].to_numpy()
stock = products['stock'].to_numpy()
value = prices * stock
valuevalue = prices * stock дава 25 стойности. Баницата и краставиците са с 0: изчерпани са. Подаръчната карта е 19 980 € от общо 22 542,30 €. Това е заради условното 999 от първия урок. Ето защо всяка сметка проверяваш с поглед към данните.
Идеята
Сравнение и маска
Сравнението също е векторно. prices > 5 не дава едно True или False, а по едно за всяка цена:
Такъв масив от True и False се казва маска. Сложена в квадратни скоби, тя оставя само местата с True. А .sum() на маската брои истините, защото при сбор True е 1, а False е 0:
Над 5 € са 8 цени: същият отговор като с цикъла, на един ред. Маската от наличността работи и върху друг масив със същата дължина: имената на изчерпаните продукти са баницата и краставиците.
Идеята
Две условия: &, | и ~
Цените от 2 до 5 € са две условия заедно. В обикновения Python би писал and. С масиви and не става:
and пита „цялото това вярно ли е?“ и иска едно True или False. Масивът има 25. NumPy не гадае дали имаш предвид „всички“ или „поне едно“, затова спира с грешка.
За маски има три знака, които работят число по число:
&е „и“: двете условия са верни;|е „или“: поне едното е вярно;~е „не“: обръща маската.
От 2 до 5 € са 9 цени. Под 1 € или над 20 € са 0,80 €, 0,90 €, 24,90 € и 39 €. Цените на продуктите, които не са на склад, са 2,50 € и 2,80 €.
Виж го
Капанът със скобите
Всяко условие около & и | е в скоби. Ако ги пропуснеш, Python смята & преди >= и <=, тоест започва от 2 & prices:
& с число и дробни цени няма смисъл, затова идва TypeError за bitwise_and. Правилото е: всяко условие в скоби, (prices >= 2) & (prices <= 5).
По-точно, за любопитните
&, | и ~ в Python са побитови операции върху цели числа. Затова са по-силни от сравненията: 2 & 6 > 1 е (2 & 6) > 1. NumPy ги е взел за маските, защото and, or и not не могат да се пренапишат за масиви: те винаги искат едно True или False. Същото ще видиш и при филтрите в pandas.
Пробвай
Евтино и на склад
Колко продукта струват под 3 € и са на склад (наличност над 0)? Запиши броя в count.
Решение
import pandas as pd
products = pd.read_csv('products.csv')
prices = products['price'].to_numpy()
stock = products['stock'].to_numpy()
count = ((prices < 3) & (stock > 0)).sum()
countcount = ((prices < 3) & (stock > 0)).sum() дава 12. Продуктите под 3 € са 14, но два от тях са изчерпани: баницата за 2,50 € и краставиците за 2,80 €. С and кодът пада с ValueError, а без скобите с TypeError.
Идеята
Едно или друго: np.where
Маската избира кои числа да останат. Понякога искаш всички, но различни според условието. np.where(маска, ако_да, ако_не) взима за всяко място едното или другото:
Където наличността е 0, излиза „изчерпан“, а иначе „има“. На мястото на текста могат да са и масиви: np.where(prices > 10, prices * 0.9, prices) смъква с 10% само скъпите цени.
Упражнение
Ценоразписът с ДДС
Обратно към управителя: цените с ДДС и една промоция.
Запиши в with_vat цените с ДДС (по 1,2), закръглени до 2 знака. В over_10 запиши колко от тях са над 10 €.
Решение
import numpy as np
import pandas as pd
prices = pd.read_csv('products.csv')['price'].to_numpy()
with_vat = np.round(prices * 1.2, 2)
over_10 = (with_vat > 10).sum()
over_10with_vat = np.round(prices * 1.2, 2) и over_10 = (with_vat > 10).sum() дава 7. Без ДДС над 10 € са 6 продукта. Седмият е кашкавалът: 9,50 € с ДДС стават 11,40 €. Ако забравиш .sum(), остава самата маска, а не броят.
Промоция: продуктите над 10 € са с 10% по-евтини, а другите остават със старата цена. Запиши в promo целия нов ценоразпис (25 цени).
Решение
import numpy as np
import pandas as pd
prices = pd.read_csv('products.csv')['price'].to_numpy()
promo = np.where(prices > 10, prices * 0.9, prices)
promopromo = np.where(prices > 10, prices * 0.9, prices). Шест цени се сменят: ножовете от 39 € стават 35,10 €, кафето от 14,90 € става 13,41 €. Останалите 19 са същите. prices[prices > 10] * 0.9 дава само шестте нови цени и губи другите, а prices * 0.9 смъква всичко.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Първата таблица · Масивът на NumPy
- Продължава в: Обобщения и липсващи стойности · Broadcasting: числото, редът и колоната · Series и DataFrame · Избиране: [], loc и iloc
- Задачи по темата: NumPy