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

Нови колони

Накратко: Нова колона в pandas се смята от другите колони наведнъж: аритметика, np.where или map; assign връща нова таблица, а apply е най-бавният път.

Проблем

Колко струва всеки ред

order_items.csv казва за всеки ред от поръчка кой продукт е, колко броя и на каква цена. Колко е струвал целият ред обаче не пише никъде:

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

Управителят иска оборота, но без отказаните поръчки. Иска да знае и кои редове са големи, а в отчета да има имена на категории, не номера. За всичко това ти трябват колони, които ги няма във файла. Ще ги сметнеш от тези, които ги има.

Идеята

Колона от сметка

Колоната е Series, а сметките със Series са векторни, както с масивите на NumPy. Бройката по цената дава стойността на всичките 152 реда наведнъж. Запишеш ли резултата под ново име, таблицата получава нова колона:

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

total се появи вдясно: първият ред е 4 броя по 7,90 €, тоест 31,60 €. Ако името вече съществува, колоната се заменя с новите стойности.

Сборът на всички редове е 1 963,50 €. Това още не е оборотът: в тези редове са и поръчките, които клиентите са отказали.

Виж го

Оборотът без отказаните

Отказаната поръчка не е платена, затова не влиза в оборота. Статусът е в orders.csv, а редовете са в order_items.csv. Номерата на отказаните поръчки взимаш с loc, а после isin от миналия урок намира редовете им:

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

Отказани са 7 поръчки. Без техните редове остават 131 от 152, а оборотът е 1 697,00 €. Правилото важи за целия курс: когато въпросът е за оборот, отказаните поръчки не се броят.

Пробвай

Парите на склад

Добави в products колона stock_value: колко пари стоят на склад от всеки продукт (цената по наличността). После запиши в top3 трите продукта с най-голяма stock_value, без подаръчната карта (id 25).

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

Идеята

Нова таблица с assign

items['total'] = … променя самата таблица. assign прави обратното: връща нова таблица с още една колона, а старата остава същата. Така можеш да продължиш веднага с филтър:

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

6 реда са за 50 € или повече. Последният ред показва, че в items няма total: assign не я е пипнал. Това е Copy-on-Write от урока за избиране: промяната дава нова таблица, а оригиналът не се мени.

Вътре в assign можеш да подадеш функция вместо колона. lambda d: … получава таблицата в този момент, затова второто assign вижда total от първото:

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

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

Идеята

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

„Голям“ ред е за 20 € или повече. np.where от урока за NumPy работи и с колони: за всеки ред взима едната или другата стойност:

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

Големите редове са 35, а малките 117. Резултатът има по една стойност за всеки ред, затова става колона.

Идеята

Номер към име: map

В products категорията е номер, а управителят иска име. map с речник сменя всяка стойност с тази, която ѝ съответства в речника:

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

3 става „Хляб и тестени“, 8 става „Кухня“. Подаръчната карта няма категория, ключ за нея в речника няма и map оставя NaN.

Речникът не е нужно да го пишеш на ръка. categories.set_index('id')['name'] е Series с индекс номерата и стойности имената, а map работи и с такава Series: търси стойността в индекса ѝ.

1. Речникът names няма ключ за подаръчната карта. Какво дава map за нея?

Идеята

Твоя функция: apply

Магазинът дава отстъпка за голям ред: 10% от 50 € нагоре и 5% от 20 € нагоре. Правилото е функция на Python с if. apply я пуска за всяка стойност в колоната:

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

117 реда са без отстъпка, 29 са с 5% и 6 с 10%. Всички отстъпки заедно са 84,14 €.

apply е удобен, когато правилото вече е функция, която работи с една стойност. Това правило може да се напише и векторно, с две np.where едно в друго. На 152 реда разликата не се усеща. На много редове се усеща.

Виж го

Колко струва apply

Ето една и съща сметка по два начина върху всичките 26 619 реда от поръчките за годината. apply(…, axis=1) пуска функцията за всеки ред, а time.perf_counter() мери колко време минава:

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

Резултатът е същият (True на предпоследния ред). В нашите пускания apply беше над сто пъти по-бавен от векторната сметка. Точните числа зависят от машината, но разликата остава.

Правилото: първо търсиш векторна сметка, np.where или map. apply остава за правило, което не може да се напише иначе.

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

apply с axis=1 прави от всеки ред малка Series и вика функцията ти на Python 26 619 пъти. Векторната сметка минава по колоната веднъж, в цикъл на C вътре в NumPy. Затова разликата расте с броя на редовете: на 152 реда е незабележима, а на милион е разликата между секунда и минута.

Идеята

Отчетът: rename и drop

Преди да пратиш таблицата на управителя, махаш колоните, които не му трябват, и даваш имена, които разбира. drop(columns=[…]) маха колони, а rename(columns={старо: ново}) ги преименува:

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

И двата връщат нова таблица, както assign. Колоните, които не са в речника на rename, остават със старото си име.

Упражнение

Оборотът за годината

Колко е оборотът за цялата година? В year_items добави колона total (бройка по цена), махни редовете на отказаните поръчки и запиши сумата, закръглена до 2 знака, в revenue.

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

Колко е донесло кафето на зърна (продукт 17) за годината, без отказаните поръчки? Запиши сумата, закръглена до 2 знака, в coffee_revenue.

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

Тест

Провери се

1. Пускаш items.assign(total=...) и не записваш резултата никъде. Какво има после в items?
2. Кое е по-добре за стойността на реда в 26 619 реда?
3. Кога apply с твоя функция е на място?
4. Кафето струваше 13,90 € до 1 март, а сега е 14,90 €. С коя цена се смята оборотът от кафе за годината?

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