A dict-of-lists in wide/nested format unrolled into flat (category, item) row tuples with a nested loop. The trace shows k and vs loading each outer key in turn, then v stepping through its values — one (k, v) pair per inner iteration.

By hand

Walk each key k and its list vs with an outer loop, then walk vs with an inner loop, appending (k, v) to rows each time. (rows is excluded from the trace because it overflows at 5 items; the outer key and current item are visible instead.)

naive.py
data = {'fruit': ['apple', 'pear'], 'veg': ['carrot', 'pea'], 'grain': ['oat', 'rye']}
rows = []
# trace: ignore rows
for k, vs in data.items():
    for v in vs:
        rows.append((k, v))
print('RESULT:', (len(rows), rows[-1]))

The Pythonic way

A nested list comprehension mirrors the two loops in a single expression. The outer clause for k, vs in data.items() iterates keys; the inner clause for v in vs iterates each value list.

library.py
data = {'fruit': ['apple', 'pear'], 'veg': ['carrot', 'pea'], 'grain': ['oat', 'rye']}
rows = [(k, v) for k, vs in data.items() for v in vs]
print('RESULT:', (len(rows), rows[-1]))
RESULT: (6, ('grain', 'rye'))

Implementation notes

  • This is the inverse of pivot-long-to-wide: where that lesson folded long (key, attr, val) rows into a nested dict-of-dicts, this lesson unfolds a nested dict-of-lists back into long (category, item) rows.
  • dict.items() returns key-value pairs in insertion order (guaranteed since Python 3.7). If the input order is not meaningful, sort the output with sorted(rows) for a deterministic result.
  • In pandas, df.melt(id_vars=[...], value_vars=[...]) performs the wide-to- long transformation. The pandas melt lesson (roadmap) covers aggregation and column naming in the melted result.