NumPy: вычисления и устройство массивов

Векторная арифметика и where/clip

Содержание курса

Поэлементные арифметические операции над массивами

Когда вы пишете a + b для двух массивов NumPy одинаковой формы, сложение происходит не как «сложить два объекта», а как «сложить каждый элемент a с соответствующим элементом b». Так работают все стандартные операторы: +, -, *, /, //, **, %.

import numpy as np

a = np.array([10, 20, 30, 40])
b = np.array([3, 4, 5, 6])

print(a + b)   # [13 24 35 46]
print(a - b)   # [ 7 16 25 34]
print(a * b)   # [ 30  80 150 240]
print(a / b)   # [3.333... 5.0 6.0 6.666...]
print(a // b)  # [3 5 6 6]
print(a ** 2)  # [ 100  400  900 1600]
print(a % b)   # [1 0 0 4]

Несколько моментов, на которые стоит обратить внимание.

/ выполняет true division. Для обычных целочисленных массивов (как int64 в примере выше) результат — вещественный массив float64. Это отличает / от //: np.array([10]) / np.array([3]) даёт [3.333...], а не [3]. Для вещественных и комплексных dtype конкретный тип результата определяется правилами promotion — например, float32 / float32 остаётся float32.

// — floor division, то есть частное округляется вниз до ближайшего целого. Для положительных чисел это совпадает с обычным усечением, но для отрицательных — нет: np.array([-7]) // np.array([2]) даст [-4], а не [-3].

Исходные массивы не изменяются. Любая операция возвращает новый массив — a и b остаются нетронутыми.

Тип результата определяется dtype операндов по правилам promotion. Два int64-массива при сложении, вычитании или умножении дадут int64; если один из операндов float64 — результат будет float64. NumPy выбирает тип автоматически, и явное приведение типов нужно редко.

Операции со скаляром работают так же — скаляр применяется к каждому элементу:

print(a * 2)   # [20 40 60 80]
print(a + 100) # [110 120 130 140]

Это именно то, что делает NumPy быстрее Python-цикла: вместо последовательного обхода элементов вся операция выполняется на уровне скомпилированного кода за один вызов.