Problem
I have orders in a long format — one row per observation with a
category column and a quantity. I want them wide — one row per
region with a tea column and a coffee column. Or the other way:
take a wide table and melt it into a long one.
Setup
val orders = [
{ region = "north", category = "tea", quantity = 12 },
{ region = "south", category = "coffee", quantity = 30 },
{ region = "north", category = "tea", quantity = 8 },
{ region = "west", category = "coffee", quantity = 50 },
{ region = "east", category = "tea", quantity = 6 },
{ region = "south", category = "coffee", quantity = 20 },
{ region = "west", category = "tea", quantity = 14 },
{ region = "west", category = "tea", quantity = 25 }
];
Example
Long to wide — pivot category into separate tea and coffee
columns, summing quantity within each. The trick is that sum over <expr> takes an expression, and the expression can be conditional.
One aggregate per target column:
from ord in orders
group ord.region
compute { tea = sum over (if ord.category = "tea" then ord.quantity else 0),
coffee = sum over (if ord.category = "coffee" then ord.quantity else 0) };
val it =
[{coffee=0,region="east",tea=6},{coffee=50,region="south",tea=0},
{coffee=0,region="north",tea=20},{coffee=50,region="west",tea=39}]
: {coffee:int, region:string, tea:int} list
What's happening
Wide-pivoting in Morel is "one aggregate per target column, with a
predicate inside the aggregate expression." It works because Morel's
sum over e is just sum applied to a derived column — and that
column can be anything the row permits, including a conditional.
Write one name = ... over (if ... then ... else 0) per output
column and you've got a pivot.
The catch is that the output columns are fixed at compile time. You
can't pivot a set of categories you don't know until you run the
query. That's a real constraint — most cookbook-style reshaping knows
the list up front ("tea and coffee"), but a generic pivot_by(col)
isn't something Morel 0.8 offers. When you need a dynamic shape,
emit the long-format result and do the final reshape in whatever
tool will consume it.
Long-to-wide's inverse — melt, unpivot, wide-to-long — is easier.
Introduce a second scan over a list of category labels, then yield
one row per (entity, label) pair, looking up the column by name with
case.
Variations
Wide to long. The outer scan iterates rows; the inner scan iterates
category labels; the case picks the right column:
val wide = [
{ region = "north", tea = 20, coffee = 0 },
{ region = "south", tea = 0, coffee = 50 },
{ region = "west", tea = 39, coffee = 50 }
];
from row in wide,
cat in ["tea", "coffee"]
yield { row.region, category = cat,
quantity = case cat of
"tea" => row.tea
| "coffee" => row.coffee
| _ => 0 };
See also
- Recipe 10 — Group and aggregate — the engine behind long-to-wide; pivots are group+compute with a trick.
- Recipe 11 — Join two tables — multi-scan pipelines that look a lot like wide-to-long.
- Recipe 12 — Derive columns with pattern matching — the
caseused inside theyieldfor wide-to-long.