6.11. Редукції

Редукція згортає масив вздовж однієї або кількох осей шляхом підсумовування, усереднення, знаходження мінімуму тощо. Кожна редукція — це один виклик бібліотеки для всього масиву, що значно швидше за еквівалентний цикл Python. numpy охоплює найпоширеніші з них:

  • sum() – сума всіх елементів

  • mean() – середнє арифметичне (сума, поділена на кількість елементів)

  • std() – стандартне відхилення; ddof= коригує дільник (N - ddof)

  • min() / max() – найменший і найбільший елемент

  • median() – середнє значення відсортованого масиву (50-й перцентиль)

  • argmin() / argmax()індекс мінімального або максимального елемента

  • all() / any() – редукції логічних значень для булевих масивів

6.11.1. Без ключового слова axis

Якщо axis= не вказано, редукція повертає скаляр, що охоплює весь масив:

a = np.array([1, 2, 3, 4], dtype=np.float)
np.sum(a)           # 10.0
np.mean(a)          # 2.5
np.std(a)           # 1.118...
np.median(a)        # 2.5

b = np.array([40, 10, 30, 20], dtype=np.float)
np.max(b)           # 40.0
np.argmax(b)        # 0  (index of the maximum)

6.11.2. З ключовим словом axis

axis= стискає зазначену вісь, залишаючи інші незмінними. Результат — масив на один ранг нижчий за вхідний:

m = np.arange(12, dtype=np.float).reshape((3, 4))

np.sum(m)               # 66.0          - scalar
np.sum(m, axis=0)       # length-4      - column sums
np.sum(m, axis=1)       # length-3      - row sums

Те саме правило форми застосовується до кожної редукції: axis=0 згортає першу вісь, axis=1 — другу і так далі. Наприклад, середнє та стандартне відхилення вздовж рядка записуються як np.mean(m, axis=1) і np.std(m, axis=1). Результат має довжину іншої осі.

Ключове слово keepdims=True зберігає стиснуту вісь з довжиною 1 замість того, щоб видаляти її. Це важливо, коли скорочений результат потрібно транслювати назад відносно оригіналу: keepdims зберігає ранг, що підтримує правила трансляції в узгодженому стані по осях.

Типове застосування — відняти середнє кожного рядка від самого рядка:

m = np.arange(12, dtype=np.float).reshape((3, 4))
row_means = np.mean(m, axis=1, keepdims=True)
# row_means has shape (3, 1)
centred = m - row_means
# (3, 4) - (3, 1) -> (3, 4), each row centred on its own mean

Без keepdims, np.mean(m, axis=1) повертає одновимірний результат форми (3,). Трансляція (3, 4) - (3,) вирівнює (3,) як (1, 3) після додавання рангу, що несумісне з (3, 4): останні осі не збігаються (4 проти 3) і жодна не дорівнює 1, тому numpy генерує ValueError. keepdims=True робить операцію віднімання коректною.

6.11.3. Важливість розташування в пам’яті

У поєднанні з порядком рядків, розглянутим у Форма та кроки, редукція вздовж останньої осі є найдешевшою. Вона обходить блок даних у напрямку зберігання без стрибків між рядками:

m = np.arange(2000, dtype=np.float).reshape((2, 1000))
np.sum(m, axis=1)       # cheap - long axis is the inner one
np.sum(m, axis=0)       # has to jump rows on every step

Якщо застосунок має вибір щодо розташування буфера, розміщуйте довгу вісь останньою, щоб редукції вздовж неї виконувались у швидкому напрямку.

6.11.4. Ітеровані об’єкти як вхідні дані

Більшість редукцій приймають ітерований об’єкт Python (list, range, кортеж) замість ndarray. Зручність обходиться кількома мікросекундами на неявне перетворення — що швидко накопичується в циклі. Якщо одні й ті самі дані редукуються кілька разів, створіть ndarray один раз і передавайте його далі.