Reshaping by Hand
Zip Columns to Rows
Three parallel column lists — names, cats, and vals — assembled into a
list of row tuples using an index loop. The trace steps through each index,
building one tuple per iteration with (names[i], cats[i], vals[i]).
By hand
Walk each index from 0 to len(names) - 1 and bundle the three values at
that position into a tuple. Append each tuple to rows.
naive.py
Replay: real traced execution (multi-file project)
names = ['ann', 'bob', 'cal', 'dan']
cats = ['a', 'b', 'a', 'b']
vals = [10, 20, 30, 40]
rows = []
for i in range(len(names)):
rows.append((names[i], cats[i], vals[i]))
print('RESULT:', rows)
names ← ['ann', 'bob', 'cal', 'dan']
1names = ['ann', 'bob', 'cal', 'dan']2cats = ['a', 'b', 'a', 'b']values this step['ann', 'bob', 'cal', 'dan']namescats ← ['a', 'b', 'a', 'b']
1names = ['ann', 'bob', 'cal', 'dan']2cats = ['a', 'b', 'a', 'b']3vals = [10, 20, 30, 40]values this step['a', 'b', 'a', 'b']catsvals ← [10, 20, 30, 40]
2cats = ['a', 'b', 'a', 'b']3vals = [10, 20, 30, 40]4rows = []values this step[10, 20, 30, 40]valsrows ← []
3vals = [10, 20, 30, 40]4rows = []5for i in range(len(names)):values this step[]rowsi ← 0
4rows = []5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))values this step0irows ← [('ann', 'a', 10)]
5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))7print('RESULT:', rows)values this step[] → [('ann', 'a', 10)]rowsi ← 1
4rows = []5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))values this step0 → 1irows ← [('ann', 'a', 10), ('bob', 'b', 20)]
5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))7print('RESULT:', rows)values this step[('ann', 'a', 10)] → [('ann', 'a', 10), ('bob', 'b', 20)]rowsi ← 2
4rows = []5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))values this step1 → 2irows ← [('ann', 'a', 10), ('bob', 'b', 20), ('cal', 'a', 30)]
5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))7print('RESULT:', rows)values this step[('ann', 'a', 10), ('bob', 'b', 20)] → [('ann', 'a', 10), ('bob', 'b', 20), ('cal', 'a', 30)]rowsi ← 3
4rows = []5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))values this step2 → 3irows ← [('ann', 'a', 10), ('bob', 'b', 20), ('cal', 'a', 30), ('dan', 'b', 40)]
5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))7print('RESULT:', rows)values this step[('ann', 'a', 10), ('bob', 'b', 20), ('cal', 'a', 30)] → [('ann', 'a', 10), ('bob', 'b', 20), ('cal', 'a', 30), ('dan', 'b', 40)]rowsfor i in range(len(names)):
4rows = []5for i in range(len(names)):6 rows.append((names[i], cats[i], vals[i]))stdout ← RESULT: [('ann', 'a', 10), ('bob', 'b', 20), ('cal', 'a', 30), ('dan', 'b', 40)]
6 rows.append((names[i], cats[i], vals[i]))7print('RESULT:', rows)values this stepRESULT: [('ann', 'a', 10), ('bob', 'b', 20), ('cal', 'a', 30), ('dan', 'b', 40)]stdout
The Pythonic way
zip(names, cats, vals) pairs the elements by position and returns an
iterator of tuples. Wrap it in list() to materialise all rows at once.
library.py
names = ['ann', 'bob', 'cal', 'dan']
cats = ['a', 'b', 'a', 'b']
vals = [10, 20, 30, 40]
rows = list(zip(names, cats, vals))
print('RESULT:', rows)
RESULT: [('ann', 'a', 10), ('bob', 'b', 20), ('cal', 'a', 30), ('dan', 'b', 40)]
Implementation notes
- The index loop and
zipare equivalent only when all three lists have the same length. With mismatched lengths,zipsilently stops at the shortest while the index loop raisesIndexError. In practice, column data is generated together and is length-aligned, sozipis the safer default. - This operation is invertible:
names, cats, vals = zip(*rows)unpacks a list of row tuples back into three separate column iterators. - In pandas, parallel column lists are passed as a dict to the constructor —
pd.DataFrame({'name': names, 'cat': cats, 'val': vals})— so no explicit zipping is needed.