6.12. Вибірка та впорядкування

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

6.12.1. Умовний вибір

where() повертає масив, що приймає елементи з x там, де умова істинна, і з y в іншому випадку. Три операнди транслюються разом:

a = np.array([1, 2, 3, 4, 5], dtype=np.float)
np.where(a < 3, a, 0.0)
# array([1.0, 2.0, 0.0, 0.0, 0.0])

Це правильний інструмент для «if/else на кожен елемент» без написання циклу Python.

clip() є скороченням для maximum(lo, minimum(a, hi)) – насичення значень до діапазону:

np.clip(a, 2.0, 4.0)
# array([2.0, 2.0, 3.0, 4.0, 4.0])

maximum() і minimum() приймають два операнди і повертають поелементний більший / менший:

np.maximum(a, 3.0)
np.minimum(a, np.array([5, 4, 3, 2, 1]))

6.12.2. Пошук індексів

nonzero() повертає координати кожного ненульового елемента, розділені на один масив індексів на вимір. Для двовимірного вхідного масиву результат — це кортеж з двох масивів: перший містить індекси рядків, другий — індекси стовпців. Попарне зіставлення дає (row, col) кожної ненульової позиції:

m = np.array([[0, 2, 0],
              [3, 0, 0]], dtype=np.float)
np.nonzero(m)
# (array([0, 1], dtype=uint16), array([1, 0], dtype=uint16))

Ненульові записи в m: m[0, 1] = 2 і m[1, 0] = 3. Перший повернутий масив [0, 1] містить їхні індекси рядків; другий [1, 0] – індекси стовпців. Читаючи обидва масиви поряд, отримуємо позиції (0, 1) і (1, 0).

Дві редукції також повертають індекси:

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

  • argsort() – цілочисельний масив, що відсортував би вхідний масив вздовж заданої осі (за замовчуванням — остання):

    a = np.array([40, 10, 30, 20], dtype=np.uint8)
    idx = np.argsort(a)             # array([1, 3, 2, 0], dtype=uint16)
    a[idx]                          # array([10, 20, 30, 40])
    

    argsort завжди повертає uint16; сортований масив тому не повинен мати більше 65 535 елементів по осі сортування.

bincount() підраховує кількість входжень кожного невід’ємного цілого числа у одновимірному вхідному масиві типу uint8 / uint16

histogram = np.bincount(np.array([0, 1, 1, 2, 2, 2], dtype=np.uint8))
# array([1, 2, 3], dtype=uint16)

Корисно для побудови гістограм значень пікселів малих цілих чисел без написання циклу Python.

6.12.3. Сортування та впорядкування

sort() повертає відсортовану копію масиву вздовж заданої осі (за замовчуванням — остання). Для сортування на місці використовуйте sort() безпосередньо на масиві:

np.sort(np.array([3, 1, 2], dtype=np.float))
# array([1.0, 2.0, 3.0])

flip() обертає порядок вздовж заданої осі (по всіх осях, якщо axis не вказано):

np.flip(np.array([1, 2, 3, 4]))
# array([4, 3, 2, 1])

roll() циклічно зміщує елементи на задану кількість позицій. Корисно для реалізації кільцевого буфера у стилі регістра зсуву:

np.roll(np.array([1, 2, 3, 4]), 1)
# array([4, 1, 2, 3])

take() — явна форма індексування довільних позицій – вибір елементів за довільними індексами:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.take(a, [0, 2, 4])
# array([10, 30, 50], dtype=uint8)

6.12.4. Фільтрація та структурні зміни

compress() — явна форма булевого індексування – повертає зрізи a, вибрані булевою умовою:

a = np.array([10, 20, 30, 40], dtype=np.uint8)
np.compress(a > 15, a)
# array([20, 30, 40], dtype=uint8)

delete() повертає копію з видаленими записами за вказаними індексами:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.delete(a, [1, 3])
# array([10, 30, 50], dtype=uint8)

diff() повертає n-ту дискретну пряму різницю масиву вздовж осі. Використовується для обчислення змін першого порядку між сусідніми відліками:

samples = np.array([1, 3, 6, 10, 15], dtype=np.float)
np.diff(samples)
# array([2.0, 3.0, 4.0, 5.0])

6.12.5. Вартість кожної операції

Майже кожна функція на цій сторінці повертає щойно виділений масив. Два винятки:

  • sort() сортує на місці; вільна функція sort() повертає відсортовану копію.

  • take() приймає ключове слово out= для запису в уже існуючий буфер.

У циклі, що виконується багато разів на секунду, надавайте перевагу sort() на місці та повторно використовуйте заздалегідь виділені буфери скрізь. Булеві маски самі виділяються щоразу під час виконання порівняння — створіть маску один раз і повторно використовуйте її між операціями замість того, щоб перебудовувати на кожній ітерації.