A 2-row × 3-column grid and a 2-element column vector added together by hand. A nested loop walks each row index i, then each cell value x in that row, adding the row's scalar from col[i]. The replay shows new_row filling cell by cell and result growing row by row.

By hand

With NumPy

c = np.array(col).reshape(2, 1) turns the 1-D vector into a (2, 1) column array. a + c broadcasts c across all 3 columns, adding each row's scalar to every element in that row.

naive.py
grid = [[1, 2, 3], [4, 5, 6]]
col = [10, 20]
result = []
for i in range(len(grid)):
    new_row = []
    for x in grid[i]:
        new_row.append(x + col[i])
    result.append(new_row)
print('RESULT:', result)
library.py
import numpy as np

grid = [[1, 2, 3], [4, 5, 6]]
col = [10, 20]
a = np.array(grid)
c = np.array(col).reshape(2, 1)
result = a + c
print('shape:', a.shape)
print('dtype:', result.dtype)
print('values:', result.tolist())
print('RESULT:', result.tolist())
shape: (2, 3)
dtype: int64
values: [[11, 12, 13], [24, 25, 26]]
RESULT: [[11, 12, 13], [24, 25, 26]]

Implementation notes

  • Broadcasting rule: c is (2, 1), a is (2, 3) — the rows match and the column dimension of c is 1, so NumPy stretches it across all 3 columns without copying memory.
  • Contrast with add-vector-to-rows where the vector was (3,) and broadcast across rows. Here the vector is (2, 1) and broadcast across columns.
  • The .reshape(2, 1) call is the key step — without it, col would be (2,) and NumPy would attempt a row broadcast (wrong shape) and raise an error.
  • Shape, dtype, and values are shown explicitly here because ndarray.__repr__ output varies with NumPy version and print options.