Stack converts a wide DataFrame into a long Series by folding column labels into the row index, producing a MultiIndex. Unstack is the exact inverse — it lifts one index level back out to become column labels again.

By hand

Iterate over sorted(grid) (row keys), then sorted(grid[row]) (column keys), appending (row, col, value) tuples into stacked. This is the manual equivalent of stack — flattening a 2-D grid into a labelled sequence.

naive.py
Replay: real traced execution (multi-file project)
grid = {'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}}
stacked = []
for row in sorted(grid):
    for col in sorted(grid[row]):
        stacked.append((row, col, grid[row][col]))
print('RESULT:', stacked)
  1. grid ← {'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}}

    1grid = {'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}}2stacked = []
    values this step{'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}}grid
  2. stacked ← []

    1grid = {'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}}2stacked = []3for row in sorted(grid):
    values this step[]stacked
  3. row ← 'a'

    2stacked = []3for row in sorted(grid):4    for col in sorted(grid[row]):
    values this step'a'row
  4. col ← 'x'

    3for row in sorted(grid):4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))
    values this step'x'col
  5. stacked ← [('a', 'x', 1)]

    4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))6print('RESULT:', stacked)
    values this step[] [('a', 'x', 1)]stacked
  6. col ← 'y'

    3for row in sorted(grid):4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))
    values this step'x' 'y'col
  7. stacked ← [('a', 'x', 1), ('a', 'y', 2)]

    4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))6print('RESULT:', stacked)
    values this step[('a', 'x', 1)] [('a', 'x', 1), ('a', 'y', 2)]stacked
  8. for col in sorted(grid[row]):

    3for row in sorted(grid):4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))
  9. row ← 'b'

    2stacked = []3for row in sorted(grid):4    for col in sorted(grid[row]):
    values this step'a' 'b'row
  10. col ← 'x'

    3for row in sorted(grid):4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))
    values this step'y' 'x'col
  11. stacked ← [('a', 'x', 1), ('a', 'y', 2), ('b', 'x', 3)]

    4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))6print('RESULT:', stacked)
    values this step[('a', 'x', 1), ('a', 'y', 2)] [('a', 'x', 1), ('a', 'y', 2), ('b', 'x', 3)]stacked
  12. col ← 'y'

    3for row in sorted(grid):4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))
    values this step'x' 'y'col
  13. stacked ← [('a', 'x', 1), ('a', 'y', 2), ('b', 'x', 3), ('b', 'y', 4)]

    4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))6print('RESULT:', stacked)
    values this step[('a', 'x', 1), ('a', 'y', 2), ('b', 'x', 3)] [('a', 'x', 1), ('a', 'y', 2), ('b', 'x', 3), ('b', 'y', 4)]stacked
  14. for col in sorted(grid[row]):

    3for row in sorted(grid):4    for col in sorted(grid[row]):5        stacked.append((row, col, grid[row][col]))
  15. for row in sorted(grid):

    2stacked = []3for row in sorted(grid):4    for col in sorted(grid[row]):
  16. stdout ← RESULT: [('a', 'x', 1), ('a', 'y', 2), ('b', 'x', 3), ('b', 'y', 4)]

    5        stacked.append((row, col, grid[row][col]))6print('RESULT:', stacked)
    values this stepRESULT: [('a', 'x', 1), ('a', 'y', 2), ('b', 'x', 3), ('b', 'y', 4)]stdout

With pandas

df.stack() folds the column labels into a new inner index level, returning a Series with a two-level MultiIndex. stacked.unstack() reverses this, restoring the original wide DataFrame.

library.py
import pandas as pd
from dalib.display import set_display
set_display()

df = pd.DataFrame({'x': [1, 3], 'y': [2, 4]}, index=['a', 'b'])
stacked = df.stack()
unstacked = stacked.unstack()
pairs = [(idx[0], idx[1], int(v)) for idx, v in stacked.items()]
print('df index:', df.index.tolist())
print('df columns:', df.columns.tolist())
print('stacked index:', stacked.index.tolist())
print('stacked values:', stacked.tolist())
print('unstacked columns:', unstacked.columns.tolist())
print('RESULT:', sorted(pairs))
df index: ['a', 'b']
df columns: ['x', 'y']
stacked index: [('a', 'x'), ('a', 'y'), ('b', 'x'), ('b', 'y')]
stacked values: [1, 2, 3, 4]
unstacked columns: ['x', 'y']
RESULT: [('a', 'x', 1), ('a', 'y', 2), ('b', 'x', 3), ('b', 'y', 4)]

Implementation notes

  • stack() moves the innermost column level into the innermost row index level. On a single-level column index this produces a two-level MultiIndex (original_row, col_label).
  • unstack() (no args) reverses the last stack(). Pass a level number or name to control which level to un-pivot back to columns.
  • By default stack() drops individual NaN entries from the resulting Series (dropna=True). Pass dropna=False to keep them.
  • Cross-reference: melt-wide-to-long (this chapter) for a similar wide-to- long transformation using pd.melt; pivot-table-simple for the reverse.