Обобщения и липсващи стойности
Накратко: Обобщенията в NumPy (sum, mean, max, argmax) правят едно число от много, axis казва кой размер изчезва, а NaN отбелязва липсваща стойност.
Проблем
„Кое продаваме най-много?“
Касата изпраща отчет: колко бройки от четири продукта сме продали всяка седмица, за последните шест седмици. Всеки ред е седмица, всяка колона е продукт:
Управителят пита три неща: колко сме продали общо, кой продукт върви най-добре и коя седмица е била най-силна. После праща и оборота по дни, в който един ден липсва.
На всеки въпрос отговорът е едно число (или по едно за всеки продукт), сметнато от много числа. Това се казва обобщение (aggregation).
Идеята
Едно число от много: sum, mean, min, max
Масивът има методи, които обобщават всичките му стойности: sum() за сумата, mean() за средната, min() и max() за най-малката и най-голямата. Ето ги върху цените на 25-те продукта. to_numpy() взима колоната като масив на NumPy:
Сумата на цените излиза 175.89999999999998, а не 175,9. Компютърът пази дробните числа в двоичен вид и 0,1 не се записва точно, затова се трупат дребни грешки. Не са грешки в данните.
За отчет закръгляш: round(x, 2) дава 2 знака след точката. Средната цена става 7,04 €, като в describe() от първия поглед.
По-точно, за любопитните
round е функцията на Python и работи с едно число. За масив има метод .round(2) (или np.round(arr, 2)), който закръглява всяко число в него. Закръгляй накрая, за показване: ако закръглиш по средата на сметката, грешката от закръглянето влиза в следващите стъпки.
Идеята
Кой, а не колко: argmax и argmin
max() казва колко струва най-скъпият продукт. Управителят обаче пита кой е. argmax() дава позицията на най-голямата стойност, а argmin() на най-малката. С позицията взимаш името от масива с имената:
Най-скъп е комплектът ножове на позиция 22 (39 €), а най-евтина е кифлата с масло на позиция 2 (0,80 €). Позициите започват от 0, както при списъците.
По-точно, за любопитните
Ако най-голямата стойност се среща няколко пъти, argmax дава първата ѝ позиция. В отчета на касата седмици 1 и 4 имат по 103 бройки, а argmin по седмици дава само седмица 1. Ако равенствата имат значение, провери ги с маска: week_totals == week_totals.min().
Виж го
По кой размер: axis
sales.sum() събира всички 24 клетки: 654 бройки за шест седмици. За „кой продукт“ ти трябва сума за всяка колона, а за „коя седмица“ сума за всеки ред. Това казва параметърът axis:
axis=0е размерът на редовете (седмиците). Седмиците се сливат и остава по едно число за всеки продукт: хлябът е 263 бройки, кафето 39.axis=1е размерът на колоните (продуктите). Продуктите се сливат и остава по едно число за всяка седмица: от 103 до 120.
Тук всеки се бърка, защото „axis=0“ звучи като „по редове“, а дава резултат по колони. Правилото, което не лъже: axis е размерът, който изчезва. Формата (6, 4) с axis=0 губи 6-те и става (4,), а с axis=1 губи 4-те и става (6,).
Същото важи за mean, min, max, argmax и останалите. Средно на седмица по продукти, закръглено до един знак:
Пробвай
Най-силната седмица
Коя седмица е продала най-много бройки общо? Запиши в week_totals сбора на всяка седмица, а в best_week номера ѝ (от 1 до 6).
Решение
import numpy as np
names = np.array(['Бял хляб', 'Прясно мляко', 'Ябълки', 'Кафе на зърна'])
sales = np.array([
[42, 30, 25, 6],
[38, 34, 28, 4],
[45, 29, 31, 7],
[40, 36, 22, 5],
[51, 33, 27, 9],
[47, 31, 26, 8],
])
week_totals = sales.sum(axis=1)
best_week = week_totals.argmax() + 1
print(week_totals)
print(best_week)week_totals = sales.sum(axis=1) дава [103 104 112 103 120 112]: продуктите изчезват, седмиците остават. week_totals.argmax() е 4, а позиция 4 е петата седмица, затова best_week = 5 (120 бройки). С axis=0 щеше да получиш 4 числа, сумите по продукти, и „най-силната седмица“ щеше да е хлябът.
Идеята
Колко досега: cumsum
Понякога управителят пита не „колко за седмицата“, а „колко от началото до тази седмица“. Това е натрупаната сума: cumsum() събира числата едно по едно и пази всяка междинна сума:
След третата седмица сме продали 319 бройки, а последното число, 654, е общата сума от sales.sum(). Натрупаната сума е линията, която расте на графиките „продажби от началото на годината“.
Идеята
Един липсващ ден: NaN
Сега оборотът по дни за последната седмица. В сряда касата не е изпратила отчет, затова на мястото на сряда стои np.nan: стойността за „липсва“, която видя при първия поглед. Ето какво става със средната:
Обикновената средна е nan. Всяка сметка с NaN дава NaN: колко е 412,5 плюс „не знам“? Не знам. NumPy не дава грешка, а тихо връща nan, и в дълъг отчет то минава незабелязано.
np.isnan(revenue) е маска, която казва къде липсва, а .sum() върху нея брои липсите: 1 ден. np.nanmean и np.nansum прескачат NaN: средният оборот на шестте известни дни е 494,70 €, а сумата им е 2 968,20 €. Тази сума е „известното досега“, а не оборотът за цялата седмица: сряда не е в нея.
Виж го
Защо не 0
Изкушението е да смениш липсващото с 0 и да продължиш. np.nan_to_num прави точно това:
Средната пада от 494,70 € на 424,03 €. Нулата казва „в сряда не продадохме нищо“, а това не е вярно: магазинът е работил, само отчетът липсва. 0 не е „липсва“. Нулата е стойност, а NaN е липсата на стойност.
0 е правилната замяна само когато наистина значи нула. Ако касата не праща ред за продукт, който никой не е купил, тогава липсата е нула продажби. Това се решава от смисъла на данните, а не от кода.
Пробвай
Седмица без бройка
Касата не е пратила колко пакета кафе сме продали през третата седмица. Запиши в avg_coffee колко пакета продаваме средно на седмица.
Решение
import numpy as np
# Пакети кафе по седмици. За третата седмица касата не е пратила бройката.
coffee = np.array([6, 4, np.nan, 5, 9, 8])
avg_coffee = np.nanmean(coffee)
print(avg_coffee)avg_coffee = np.nanmean(coffee) дава 6,4: средната на петте известни седмици (32 пакета за 5 седмици). coffee.mean() дава nan, а с 0 на мястото на липсата излиза около 5,33: шест седмици, едната от които се брои за седмица без продажби, а не е била.
Упражнение
Отчетът за управителя
Отговори на управителя от отчета на касата. Запиши в product_totals бройките на всеки продукт за шестте седмици, в best_product името на най-продавания продукт, а в avg_per_week средно колко бройки на седмица продава всеки продукт, закръглено до един знак.
Решение
import numpy as np
names = np.array(['Бял хляб', 'Прясно мляко', 'Ябълки', 'Кафе на зърна'])
sales = np.array([
[42, 30, 25, 6],
[38, 34, 28, 4],
[45, 29, 31, 7],
[40, 36, 22, 5],
[51, 33, 27, 9],
[47, 31, 26, 8],
])
product_totals = sales.sum(axis=0)
best_product = names[product_totals.argmax()]
avg_per_week = sales.mean(axis=0).round(1)
print(best_product)
print(avg_per_week)product_totals = sales.sum(axis=0) дава [263 193 159 39]: седмиците изчезват. names[product_totals.argmax()] е „Бял хляб“ с 263 бройки. sales.mean(axis=0).round(1) дава [43.8 32.2 26.5 6.5]: хлябът върви по около 44 броя на седмица, а кафето по 6 или 7 пакета. product_totals.max() щеше да отговори „263“ на въпрос „кой“.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Първата таблица · Масивът на NumPy · Векторизация
- Продължава в: Broadcasting: числото, редът и колоната · Series и DataFrame · Филтри и подреждане · Нови колони
- Задачи по темата: NumPy