Нашли ошибку?
среднийNumPy 2.5.3

Транслирование (broadcasting)

Поэлементные операции над массивами разной формы — без циклов и без копирования данных.

Нужно знать:Арифметика с массивамиОси: axis и keepdimsОси: T, newaxis, squeeze

Содержание статьи

Коротко

  • Операция a + b (и любая другая поэлементная) работает, даже если формы a и b разные, но совместимые.
  • Формы сравниваются справа налево; недостающие слева оси считаются равными 1.
  • По каждой оси размеры должны совпадать, или один из них должен быть равен 1 — такая ось «растягивается».
  • Растягивание виртуальное: данные не копируются, поэтому это быстро и экономно.
  • Главная ловушка — форма (n,) против (n, 1): вектор всегда прикладывается как строка.

Правила

  1. Выровнять формы по правому краю и дописать слева единицы до одинаковой длины: (4,) рядом с (3, 4) становится (1, 4).
  2. Сравнить размеры по каждой оси: равны — хорошо; один равен 1 — он растягивается до другого; иначе ValueError.
  3. Форма результата — максимум по каждой оси.
Форма 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.9
Вывод
array([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)
Несовместимые формыошибка: ValueError
scores - scores.mean(axis=1)          # (3, 4) и (3,): 4 ≠ 3
Ошибка
ValueError: operands could not be broadcast together with shapes (3,4) (3,)

Подводные камни

Квадратная матрица и вектор — ошибки нет, результат неверный

Для формы (3, 4) вектор (3,) вызывает ошибку, а для квадратной (3, 3) — нет: вектор молча прикладывается к каждой строке, то есть вычитается по столбцам. Для операций «по строкам» делайте среднее столбцом: keepdims=True или [:, np.newaxis].

Среднее по строкам без keepdims
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().

Запись в растянутый массивошибка: ValueError
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.

Результат не помещается в левую частьошибка: ValueError
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)