Коротко
- Операция
a + b(и любая другая поэлементная) работает, даже если формыaиbразные, но совместимые. - Формы сравниваются справа налево; недостающие слева оси считаются равными 1.
- По каждой оси размеры должны совпадать, или один из них должен быть равен 1 — такая ось «растягивается».
- Растягивание виртуальное: данные не копируются, поэтому это быстро и экономно.
- Главная ловушка — форма
(n,)против(n, 1): вектор всегда прикладывается как строка.
Правила
- Выровнять формы по правому краю и дописать слева единицы до одинаковой длины:
(4,)рядом с(3, 4)становится(1, 4). - Сравнить размеры по каждой оси: равны — хорошо; один равен 1 — он растягивается до другого; иначе
ValueError. - Форма результата — максимум по каждой оси.
| Форма A | Форма B | Результат | Почему |
|---|---|---|---|
(3, 4) |
() — число |
(3, 4) |
число растягивается на всё |
(3, 4) |
(4,) |
(3, 4) |
B → (1, 4), строка повторяется 3 раза |
(3, 4) |
(3, 1) |
(3, 4) |
столбец повторяется 4 раза |
(3, 1) |
(4,) |
(3, 4) |
растягиваются оба: «таблица» всех пар |
(2, 3, 4) |
(3, 4) |
(2, 3, 4) |
B → (1, 3, 4), повторяется по первой оси |
(8, 1, 6, 1) |
(7, 1, 5) |
(8, 7, 6, 5) |
единицы с обеих сторон |
(3, 4) |
(3,) |
ошибка | справа 4 и 3 — не равны и не 1 |
Синтаксис
a + b # и любая поэлементная операция или ufunc
a[:, np.newaxis] # (n,) → (n, 1): вектор как столбец
np.broadcast_shapes(*shapes) # форма результата — без вычислений
np.broadcast_to(a, shape) # растянутое представление (только чтение)
np.broadcast_arrays(*arrays) # все массивы, растянутые до общей формыПараметры
shapesформы — кортежи; число вместо кортежа значит одномерную формуобязательныйaмассив или то, что можно в него превратить (список, число)обязательныйshapeцелевая форма; должна быть совместима с формой aобязательныйПримеры
# оценки трёх учеников по четырём предметам
scores = np.array([[4, 5, 3, 4],
[5, 5, 4, 3],
[3, 5, 5, 5]])prices = np.array([120.0, 80.0, 45.0])
prices * 0.9array([108. , 72. , 40.5])
Число растягивается на каждый элемент — это частный случай транслирования.
subject_mean = scores.mean(axis=0) # форма (4,) — среднее по каждому предмету
print(subject_mean)
scores - subject_mean # (3, 4) и (4,) → (3, 4)[4. 5. 4. 4.]
array([[ 0., 0., -1., 0.],
[ 1., 0., 0., -1.],
[-1., 0., 1., 1.]])student_mean = scores.mean(axis=1, keepdims=True) # форма (3, 1)
print(student_mean)
scores - student_mean # (3, 4) и (3, 1) → (3, 4)[[4. ]
[4.25]
[4.5 ]]
array([[ 0. , 1. , -1. , 0. ],
[ 0.75, 0.75, -0.25, -1.25],
[-1.5 , 0.5 , 0.5 , 0.5 ]])keepdims=True оставляет ось длины 1, и среднее получается столбцом (3, 1), а не вектором (3,).
prices = np.array([120, 80, 45]) # три товара
qty = np.array([1, 2, 5, 10]) # варианты количества
prices[:, np.newaxis] * qty # (3, 1) и (4,) → (3, 4)array([[ 120, 240, 600, 1200],
[ 80, 160, 400, 800],
[ 45, 90, 225, 450]])np.newaxis (это None) добавляет ось: prices[:, np.newaxis] имеет форму (3, 1). Результат — стоимость каждого товара при каждом количестве.
print(np.broadcast_shapes((3, 1), (4,)))
print(np.broadcast_shapes((2, 3, 4), (3, 4)))
np.broadcast_shapes((8, 1, 6, 1), (7, 1, 5))(3, 4) (2, 3, 4) (8, 7, 6, 5)
scores - scores.mean(axis=1) # (3, 4) и (3,): 4 ≠ 3ValueError: operands could not be broadcast together with shapes (3,4) (3,)
Подводные камни
Квадратная матрица и вектор — ошибки нет, результат неверный
Для формы (3, 4) вектор (3,) вызывает ошибку, а для квадратной (3, 3) — нет: вектор молча прикладывается к каждой строке, то есть вычитается по столбцам. Для операций «по строкам» делайте среднее столбцом: keepdims=True или [:, np.newaxis].
square = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
row_mean = square.mean(axis=1) # форма (3,)
print(square - row_mean) # молча вычтено по столбцам!
square - square.mean(axis=1, keepdims=True) # правильно: по строкам[[-1. -3. -5.]
[ 2. 0. -2.]
[ 5. 3. 1.]]
array([[-1., 0., 1.],
[-1., 0., 1.],
[-1., 0., 1.]])Огромный промежуточный массив
x[:, np.newaxis] - y для векторов длиной 100 000 создаёт матрицу 10¹⁰ элементов (80 ГБ для float64). Транслирование не копирует входные данные, но результат занимает полный объём. Для больших данных обрабатывайте частями.
broadcast_to возвращает представление только для чтения
Растянутые элементы — это одна и та же ячейка памяти, поэтому записывать в них нельзя. Нужна изменяемая копия — np.broadcast_to(...).copy().
grid = np.broadcast_to(np.array([1, 2, 3]), (2, 3))
print(grid)
grid[0, 0] = 10[[1 2 3] [1 2 3]] ValueError: assignment destination is read-only
При += растягивается только правая часть
В a += b результат пишется в a, поэтому форма результата должна совпадать с формой a. Если растянуть нужно a, пишите a = a + b.
total = np.zeros(3)
total += np.ones((2, 3)) # результат (2, 3) не помещается в массив формы (3,)ValueError: non-broadcastable output operand with shape (3,) doesn't match the broadcast shape (2,3)