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() на месте и повсюду повторно используйте предварительно выделенные буферы. Сами булевы маски выделяются каждый раз при выполнении сравнения – создайте маску один раз и повторно используйте её в разных операциях, а не пересоздавайте её внутри каждой итерации.