Broadcasting
Center Columns
A 2-row × 3-column grid centered column by column. The first nested loop
computes each column's mean; the second subtracts the column mean from every
element in that column. The trace shows means filling element by element,
then result accumulating one centered row at a time.
By hand
Two passes over the data. First, loop over each column index j: sum every
row's value at j into col_sum, divide by the row count, and append to
means. Second, loop over each row in grid: for each column j subtract
means[j] from row[j] and collect into new_row.
grid = [[1, 2, 3], [3, 6, 9]]
cols = len(grid[0])
means = []
for j in range(cols):
col_sum = 0
for row in grid:
col_sum += row[j]
means.append(col_sum / len(grid))
result = []
for row in grid:
new_row = []
for j in range(cols):
new_row.append(row[j] - means[j])
result.append(new_row)
print('RESULT:', result)
grid ← [[1, 2, 3], [3, 6, 9]]
1grid = [[1, 2, 3], [3, 6, 9]]2cols = len(grid[0])values this step[[1, 2, 3], [3, 6, 9]]gridcols ← 3
1grid = [[1, 2, 3], [3, 6, 9]]2cols = len(grid[0])3means = []values this step3colsmeans ← []
2cols = len(grid[0])3means = []4for j in range(cols):values this step[]meansj ← 0
3means = []4for j in range(cols):5 col_sum = 0values this step0jcol_sum ← 0
4for j in range(cols):5 col_sum = 06 for row in grid:values this step0col_sumrow ← [1, 2, 3]
5col_sum = 06for row in grid:7 col_sum += row[j]values this step[1, 2, 3]rowcol_sum ← 1
6for row in grid:7 col_sum += row[j]8means.append(col_sum / len(grid))values this step0 → 1col_sumrow ← [3, 6, 9]
5col_sum = 06for row in grid:7 col_sum += row[j]values this step[1, 2, 3] → [3, 6, 9]rowcol_sum ← 4
6for row in grid:7 col_sum += row[j]8means.append(col_sum / len(grid))values this step1 → 4col_sumfor row in grid:
5col_sum = 06for row in grid:7 col_sum += row[j]means ← [2.0]
7 col_sum += row[j]8 means.append(col_sum / len(grid))9result = []values this step[] → [2.0]meansj ← 1
3means = []4for j in range(cols):5 col_sum = 0values this step0 → 1jcol_sum ← 0
4for j in range(cols):5 col_sum = 06 for row in grid:values this step4 → 0col_sumrow ← [1, 2, 3]
5col_sum = 06for row in grid:7 col_sum += row[j]values this step[3, 6, 9] → [1, 2, 3]rowcol_sum ← 2
6for row in grid:7 col_sum += row[j]8means.append(col_sum / len(grid))values this step0 → 2col_sumrow ← [3, 6, 9]
5col_sum = 06for row in grid:7 col_sum += row[j]values this step[1, 2, 3] → [3, 6, 9]rowcol_sum ← 8
6for row in grid:7 col_sum += row[j]8means.append(col_sum / len(grid))values this step2 → 8col_sumfor row in grid:
5col_sum = 06for row in grid:7 col_sum += row[j]means ← [2.0, 4.0]
7 col_sum += row[j]8 means.append(col_sum / len(grid))9result = []values this step[2.0] → [2.0, 4.0]meansj ← 2
3means = []4for j in range(cols):5 col_sum = 0values this step1 → 2jcol_sum ← 0
4for j in range(cols):5 col_sum = 06 for row in grid:values this step8 → 0col_sumrow ← [1, 2, 3]
5col_sum = 06for row in grid:7 col_sum += row[j]values this step[3, 6, 9] → [1, 2, 3]rowcol_sum ← 3
6for row in grid:7 col_sum += row[j]8means.append(col_sum / len(grid))values this step0 → 3col_sumrow ← [3, 6, 9]
5col_sum = 06for row in grid:7 col_sum += row[j]values this step[1, 2, 3] → [3, 6, 9]rowcol_sum ← 12
6for row in grid:7 col_sum += row[j]8means.append(col_sum / len(grid))values this step3 → 12col_sumfor row in grid:
5col_sum = 06for row in grid:7 col_sum += row[j]means ← [2.0, 4.0, 6.0]
7 col_sum += row[j]8 means.append(col_sum / len(grid))9result = []values this step[2.0, 4.0] → [2.0, 4.0, 6.0]meansfor j in range(cols):
3means = []4for j in range(cols):5 col_sum = 0result ← []
8 means.append(col_sum / len(grid))9result = []10for row in grid:values this step[]resultrow ← [1, 2, 3]
9result = []10for row in grid:11 new_row = []values this step[3, 6, 9] → [1, 2, 3]rownew_row ← []
10for row in grid:11 new_row = []12 for j in range(cols):values this step[]new_rowj ← 0
11new_row = []12for j in range(cols):13 new_row.append(row[j] - means[j])values this step2 → 0jnew_row ← [-1.0]
12for j in range(cols):13 new_row.append(row[j] - means[j])14result.append(new_row)values this step[] → [-1.0]new_rowj ← 1
11new_row = []12for j in range(cols):13 new_row.append(row[j] - means[j])values this step0 → 1jnew_row ← [-1.0, -2.0]
12for j in range(cols):13 new_row.append(row[j] - means[j])14result.append(new_row)values this step[-1.0] → [-1.0, -2.0]new_rowj ← 2
11new_row = []12for j in range(cols):13 new_row.append(row[j] - means[j])values this step1 → 2jnew_row ← [-1.0, -2.0, -3.0]
12for j in range(cols):13 new_row.append(row[j] - means[j])14result.append(new_row)values this step[-1.0, -2.0] → [-1.0, -2.0, -3.0]new_rowfor j in range(cols):
11new_row = []12for j in range(cols):13 new_row.append(row[j] - means[j])result ← [[-1.0, -2.0, -3.0]]
13 new_row.append(row[j] - means[j])14 result.append(new_row)15print('RESULT:', result)values this step[] → [[-1.0, -2.0, -3.0]]resultrow ← [3, 6, 9]
9result = []10for row in grid:11 new_row = []values this step[1, 2, 3] → [3, 6, 9]rownew_row ← []
10for row in grid:11 new_row = []12 for j in range(cols):values this step[-1.0, -2.0, -3.0] → []new_rowj ← 0
11new_row = []12for j in range(cols):13 new_row.append(row[j] - means[j])values this step2 → 0jnew_row ← [1.0]
12for j in range(cols):13 new_row.append(row[j] - means[j])14result.append(new_row)values this step[] → [1.0]new_rowj ← 1
11new_row = []12for j in range(cols):13 new_row.append(row[j] - means[j])values this step0 → 1jnew_row ← [1.0, 2.0]
12for j in range(cols):13 new_row.append(row[j] - means[j])14result.append(new_row)values this step[1.0] → [1.0, 2.0]new_rowj ← 2
11new_row = []12for j in range(cols):13 new_row.append(row[j] - means[j])values this step1 → 2jnew_row ← [1.0, 2.0, 3.0]
12for j in range(cols):13 new_row.append(row[j] - means[j])14result.append(new_row)values this step[1.0, 2.0] → [1.0, 2.0, 3.0]new_rowfor j in range(cols):
11new_row = []12for j in range(cols):13 new_row.append(row[j] - means[j])result ← [[-1.0, -2.0, -3.0], [1.0, 2.0, 3.0]]
13 new_row.append(row[j] - means[j])14 result.append(new_row)15print('RESULT:', result)values this step[[-1.0, -2.0, -3.0]] → [[-1.0, -2.0, -3.0], [1.0, 2.0, 3.0]]resultfor row in grid:
9result = []10for row in grid:11 new_row = []stdout ← RESULT: [[-1.0, -2.0, -3.0], [1.0, 2.0, 3.0]]
14 result.append(new_row)15print('RESULT:', result)values this stepRESULT: [[-1.0, -2.0, -3.0], [1.0, 2.0, 3.0]]stdout
With NumPy
a.mean(axis=0) reduces along axis 0 (rows), returning a (3,) array of
column means. Subtracting that (3,) vector from the (2, 3) matrix
broadcasts across both rows — the same rule as add-vector-to-rows but with a
computed vector rather than a literal one.
import numpy as np
grid = [[1, 2, 3], [3, 6, 9]]
a = np.array(grid, dtype=float)
means = a.mean(axis=0)
result = a - means
print('means: shape:', means.shape, 'dtype:', means.dtype, 'values:', means.tolist())
print('result: shape:', result.shape, 'dtype:', result.dtype)
print('result values:', result.tolist())
print('RESULT:', result.tolist())
means: shape: (3,) dtype: float64 values: [2.0, 4.0, 6.0]
result: shape: (2, 3) dtype: float64
result values: [[-1.0, -2.0, -3.0], [1.0, 2.0, 3.0]]
RESULT: [[-1.0, -2.0, -3.0], [1.0, 2.0, 3.0]]
Implementation notes
axis=0means "reduce along rows" — collapse all rows into one value per column.a.mean(axis=0)on a(2, 3)array yields a(3,)means vector.- Broadcasting then stretches that
(3,)vector across both rows ofawithout copying memory — the trailing dimension matches, exactly as inadd-vector-to-rows. - After centering, every column's mean is zero (within floating-point precision). This is the first step in standardization (z-scores), where you also divide each column by its standard deviation — that lesson is in the roadmap statistics chapter.
- Axis-based reductions (
mean,sum,std,max) are covered in depth in the upcoming reductions chapter. - Shape, dtype, and values are shown explicitly here because
ndarray.__repr__output varies with NumPy version and print options.