Нови колони
Накратко: Нова колона в pandas се смята от другите колони наведнъж: аритметика, np.where или map; assign връща нова таблица, а apply е най-бавният път.
Проблем
Колко струва всеки ред
order_items.csv казва за всеки ред от поръчка кой продукт е, колко броя и на каква цена. Колко е струвал целият ред обаче не пише никъде:
Управителят иска оборота, но без отказаните поръчки. Иска да знае и кои редове са големи, а в отчета да има имена на категории, не номера. За всичко това ти трябват колони, които ги няма във файла. Ще ги сметнеш от тези, които ги има.
Идеята
Колона от сметка
Колоната е Series, а сметките със Series са векторни, както с масивите на NumPy. Бройката по цената дава стойността на всичките 152 реда наведнъж. Запишеш ли резултата под ново име, таблицата получава нова колона:
total се появи вдясно: първият ред е 4 броя по 7,90 €, тоест 31,60 €. Ако името вече съществува, колоната се заменя с новите стойности.
Сборът на всички редове е 1 963,50 €. Това още не е оборотът: в тези редове са и поръчките, които клиентите са отказали.
Виж го
Оборотът без отказаните
Отказаната поръчка не е платена, затова не влиза в оборота. Статусът е в orders.csv, а редовете са в order_items.csv. Номерата на отказаните поръчки взимаш с loc, а после isin от миналия урок намира редовете им:
Отказани са 7 поръчки. Без техните редове остават 131 от 152, а оборотът е 1 697,00 €. Правилото важи за целия курс: когато въпросът е за оборот, отказаните поръчки не се броят.
Пробвай
Парите на склад
Добави в products колона stock_value: колко пари стоят на склад от всеки продукт (цената по наличността). После запиши в top3 трите продукта с най-голяма stock_value, без подаръчната карта (id 25).
Решение
import pandas as pd
products = pd.read_csv('products.csv')
products['stock_value'] = products['price'] * products['stock']
shelf = products[products['id'] != 25]
top3 = shelf.nlargest(3, 'stock_value')
top3[['name', 'price', 'stock', 'stock_value']]products['stock_value'] = products['price'] * products['stock'], а после products[products['id'] != 25].nlargest(3, 'stock_value'). Най-много пари стоят в кафето (223,50 €), тигана (199,20 €) и ножовете (195,00 €). Без филтъра първа е подаръчната карта с 19 980 €, но 999 е условно число, а не карти на рафта.
Идеята
Нова таблица с assign
items['total'] = … променя самата таблица. assign прави обратното: връща нова таблица с още една колона, а старата остава същата. Така можеш да продължиш веднага с филтър:
6 реда са за 50 € или повече. Последният ред показва, че в items няма total: assign не я е пипнал. Това е Copy-on-Write от урока за избиране: промяната дава нова таблица, а оригиналът не се мени.
Вътре в assign можеш да подадеш функция вместо колона. lambda d: … получава таблицата в този момент, затова второто assign вижда total от първото:
Такава поредица от методи, всеки върху резултата на предишния, се казва верига. Скобите около нея позволяват да я пишеш на няколко реда.
Идеята
Едно или друго: np.where
„Голям“ ред е за 20 € или повече. np.where от урока за NumPy работи и с колони: за всеки ред взима едната или другата стойност:
Големите редове са 35, а малките 117. Резултатът има по една стойност за всеки ред, затова става колона.
Идеята
Номер към име: map
В products категорията е номер, а управителят иска име. map с речник сменя всяка стойност с тази, която ѝ съответства в речника:
3 става „Хляб и тестени“, 8 става „Кухня“. Подаръчната карта няма категория, ключ за нея в речника няма и map оставя NaN.
Речникът не е нужно да го пишеш на ръка. categories.set_index('id')['name'] е Series с индекс номерата и стойности имената, а map работи и с такава Series: търси стойността в индекса ѝ.
Идеята
Твоя функция: apply
Магазинът дава отстъпка за голям ред: 10% от 50 € нагоре и 5% от 20 € нагоре. Правилото е функция на Python с if. apply я пуска за всяка стойност в колоната:
117 реда са без отстъпка, 29 са с 5% и 6 с 10%. Всички отстъпки заедно са 84,14 €.
apply е удобен, когато правилото вече е функция, която работи с една стойност. Това правило може да се напише и векторно, с две np.where едно в друго. На 152 реда разликата не се усеща. На много редове се усеща.
Виж го
Колко струва apply
Ето една и съща сметка по два начина върху всичките 26 619 реда от поръчките за годината. apply(…, axis=1) пуска функцията за всеки ред, а time.perf_counter() мери колко време минава:
Резултатът е същият (True на предпоследния ред). В нашите пускания apply беше над сто пъти по-бавен от векторната сметка. Точните числа зависят от машината, но разликата остава.
Правилото: първо търсиш векторна сметка, np.where или map. apply остава за правило, което не може да се напише иначе.
По-точно, за любопитните
apply с axis=1 прави от всеки ред малка Series и вика функцията ти на Python 26 619 пъти. Векторната сметка минава по колоната веднъж, в цикъл на C вътре в NumPy. Затова разликата расте с броя на редовете: на 152 реда е незабележима, а на милион е разликата между секунда и минута.
Идеята
Отчетът: rename и drop
Преди да пратиш таблицата на управителя, махаш колоните, които не му трябват, и даваш имена, които разбира. drop(columns=[…]) маха колони, а rename(columns={старо: ново}) ги преименува:
И двата връщат нова таблица, както assign. Колоните, които не са в речника на rename, остават със старото си име.
Упражнение
Оборотът за годината
Колко е оборотът за цялата година? В year_items добави колона total (бройка по цена), махни редовете на отказаните поръчки и запиши сумата, закръглена до 2 знака, в revenue.
Решение
import pandas as pd
year = pd.read_csv('year_orders.csv')
year_items = pd.read_csv('year_items.csv')
year_items['total'] = year_items['quantity'] * year_items['unit_price']
cancelled = year.loc[year['status'] == 'cancelled', 'id']
kept = year_items[~year_items['order_id'].isin(cancelled)]
revenue = round(kept['total'].sum(), 2)
print(revenue)year_items['total'] = year_items['quantity'] * year_items['unit_price'], после kept = year_items[~year_items['order_id'].isin(cancelled)]. Оборотът е 111 186,20 €. С отказаните поръчки излизат 117 902,70 €: 6 716,50 € в 1 550 реда, които никой не е платил. Без ~ получаваш само тези 6 716,50 €.
Колко е донесло кафето на зърна (продукт 17) за годината, без отказаните поръчки? Запиши сумата, закръглена до 2 знака, в coffee_revenue.
Решение
import pandas as pd
year = pd.read_csv('year_orders.csv')
year_items = pd.read_csv('year_items.csv')
cancelled = year.loc[year['status'] == 'cancelled', 'id']
coffee = year_items[(year_items['product_id'] == 17) & ~year_items['order_id'].isin(cancelled)]
coffee = coffee.assign(total=coffee['quantity'] * coffee['unit_price'])
coffee_revenue = round(coffee['total'].sum(), 2)
print(coffee['unit_price'].value_counts())
print(coffee_revenue)coffee_revenue е 9 585,10 €. value_counts на unit_price показва две цени: 13,90 € в 192 реда (до 1 март) и 14,90 € в 345 реда. Ако вземеш днешната цена от products с map, излизат 9 819,10 €: 234 € повече, отколкото клиентите са платили. С отказаните поръчки излизат 10 021,10 €.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Обобщения и липсващи стойности · Series и DataFrame · Избиране: [], loc и iloc · Филтри и подреждане
- Продължава в: Типове и липсващи стойности · Групи: groupby · Съединяване: merge · Широко и дълго: pivot_table и melt
- Задачи по темата: Таблици с pandas