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. Расположение данных имеет значение

В сочетании со строчно-ориентированным расположением, описанным в Форма и шаги (strides), редукция вдоль последней оси – самый дешёвый случай. Редукция проходит блок данных в том направлении, в котором он хранится, без переходов от строки к строке:

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 один раз и передавайте его.