A 2-row × 3-column grid reduced two ways: column sums (collapsing rows) and row means (collapsing columns). The naive version uses two separate loops — one iterating over column indices, one over rows — making the direction of each reduction explicit before the trace.

By hand

Two passes. First, loop over each column index j: sum every row's value at position j into s, then append to col_sums. Second, loop over each row: use the built-in sum divided by len(row) for the mean.

naive.py
Replay: real traced execution (multi-file project)
grid = [[1, 2, 3], [4, 5, 6]]
cols = len(grid[0])
col_sums = []
for j in range(cols):
    s = 0
    for row in grid:
        s += row[j]
    col_sums.append(s)
row_means = []
for row in grid:
    row_means.append(round(sum(row) / len(row), 2))
print('RESULT:', (col_sums, row_means))
  1. grid ← [[1, 2, 3], [4, 5, 6]]

    1grid = [[1, 2, 3], [4, 5, 6]]2cols = len(grid[0])
    values this step[[1, 2, 3], [4, 5, 6]]grid
  2. cols ← 3

    1grid = [[1, 2, 3], [4, 5, 6]]2cols = len(grid[0])3col_sums = []
    values this step3cols
  3. col_sums ← []

    2cols = len(grid[0])3col_sums = []4for j in range(cols):
    values this step[]col_sums
  4. j ← 0, s ← 5, row ← [4, 5, 6], col_sums ← [5]

    pass 1 of 3
    3col_sums = []4for j in range(cols):5    s = 06    for row in grid:7        s += row[j]8    col_sums.append(s)9row_means = []
    values this step0j1 5s[1, 2, 3] [4, 5, 6]row[] [5]col_sums
    All 3 passes — pass 1 is the card above
    passjsrowcol_sums
    101 5[1, 2, 3] [4, 5, 6][] [5]
    20 12 7[1, 2, 3] [4, 5, 6][5] [5, 7]
    31 23 9[1, 2, 3] [4, 5, 6][5, 7] [5, 7, 9]
  5. for j in range(cols):

    3col_sums = []4for j in range(cols):5    s = 0
  6. row_means ← []

    8    col_sums.append(s)9row_means = []10for row in grid:
    values this step[]row_means
  7. row ← [1, 2, 3], row_means ← [2.0]

    pass 1 of 2
    9row_means = []10for row in grid:11    row_means.append(round(sum(row) / len(row), 2))12print('RESULT:', (col_sums, row_means))
    values this step[4, 5, 6] [1, 2, 3]row[] [2.0]row_means
  8. row ← [4, 5, 6], row_means ← [2.0, 5.0]

    pass 2 of 2
    9row_means = []10for row in grid:11    row_means.append(round(sum(row) / len(row), 2))12print('RESULT:', (col_sums, row_means))
    values this step[1, 2, 3] [4, 5, 6]row[2.0] [2.0, 5.0]row_means
  9. for row in grid:

    9row_means = []10for row in grid:11    row_means.append(round(sum(row) / len(row), 2))
  10. stdout ← RESULT: ([5, 7, 9], [2.0, 5.0])

    11    row_means.append(round(sum(row) / len(row), 2))12print('RESULT:', (col_sums, row_means))
    values this stepRESULT: ([5, 7, 9], [2.0, 5.0])stdout

With NumPy

a.sum(axis=0) collapses along axis 0 (rows), producing one sum per column with shape (3,). a.mean(axis=1) collapses along axis 1 (columns), producing one mean per row with shape (2,). The snapshot shows both result shapes next to the input shape.

library.py
import numpy as np

grid = [[1, 2, 3], [4, 5, 6]]
a = np.array(grid)
col_sums = a.sum(axis=0)
row_means = a.mean(axis=1)
print('shape:', a.shape, 'dtype:', a.dtype)
print(
    'col_sums: shape:', col_sums.shape,
    'dtype:', col_sums.dtype,
    'values:', col_sums.tolist(),
)
print(
    'row_means: shape:', row_means.shape,
    'dtype:', row_means.dtype,
    'values:', row_means.tolist(),
)
print('RESULT:', (col_sums.tolist(), row_means.tolist()))
shape: (2, 3) dtype: int64
col_sums: shape: (3,) dtype: int64 values: [5, 7, 9]
row_means: shape: (2,) dtype: float64 values: [2.0, 5.0]
RESULT: ([5, 7, 9], [2.0, 5.0])

Implementation notes

  • axis=0 means "reduce along axis 0" — collapse rows. A (2, 3) array summed with axis=0 loses the first dimension, yielding (3,): one value per column.
  • axis=1 means "reduce along axis 1" — collapse columns. A (2, 3) array averaged with axis=1 loses the second dimension, yielding (2,): one value per row.
  • mean promotes the dtype to float64 even when the input is integer. sum keeps the integer dtype.
  • The column-centering in center-columns used a.mean(axis=0) to get one mean per column — the same axis-0 rule, just applied to mean instead of sum.
  • Shape, dtype, and values are shown explicitly here because ndarray.__repr__ output varies with NumPy version and print options.