Six (key, attr, val) triples in long format folded into a nested dict-of-dicts in wide format. The replay shows table growing one inner key at a time as each attribute is set under its parent key.

By hand

The Pythonic way

dict.setdefault(k, {}) returns the existing inner dict for k when present, or inserts an empty dict and returns it. Chaining [a] = v onto the return value collapses the seed-or-get guard into a single line per iteration.

naive.py
keys = ['p', 'p', 'q', 'q', 'r', 'r']
attrs = ['x', 'y', 'x', 'y', 'x', 'y']
vals = [1, 2, 3, 4, 5, 6]
table = {}
for k, a, v in zip(keys, attrs, vals):
    if k not in table:
        table[k] = {}
    table[k][a] = v
print('RESULT:', {k: table[k] for k in sorted(table)})
library.py
keys = ['p', 'p', 'q', 'q', 'r', 'r']
attrs = ['x', 'y', 'x', 'y', 'x', 'y']
vals = [1, 2, 3, 4, 5, 6]
table = {}
for k, a, v in zip(keys, attrs, vals):
    table.setdefault(k, {})[a] = v
print('RESULT:', {k: table[k] for k in sorted(table)})
RESULT: {'p': {'x': 1, 'y': 2}, 'q': {'x': 3, 'y': 4}, 'r': {'x': 5, 'y': 6}}

Implementation notes

  • Long format stores one observation per row (three columns: key, attribute, value). Wide format stores all attributes for a key on one row — one column per attribute. Pivoting trades row count for column count.
  • The loop may interleave rows for different keys freely. The seed-or-get pattern handles this because setdefault is idempotent: calling it twice for the same key leaves the existing inner dict unchanged.
  • In pandas, df.pivot(index='key', columns='attr', values='val') performs this transformation in one call. The pandas pivot-table lesson (roadmap) extends this to aggregated values when multiple rows share the same (key, attr) pair.